diff options
author | Ettore Perazzoli <ettore@src.gnome.org> | 2003-10-22 02:28:34 +0800 |
---|---|---|
committer | Ettore Perazzoli <ettore@src.gnome.org> | 2003-10-22 02:28:34 +0800 |
commit | 0fb08f3ff81575a4749d851404233f34252dd2f2 (patch) | |
tree | 7e03befedc3a76fd104921dbbc616810d87333be /mail | |
parent | 0e19f2c16de592607a341eb9974d31e4e47e02b5 (diff) | |
download | gsoc2013-evolution-0fb08f3ff81575a4749d851404233f34252dd2f2.tar.gz gsoc2013-evolution-0fb08f3ff81575a4749d851404233f34252dd2f2.tar.zst gsoc2013-evolution-0fb08f3ff81575a4749d851404233f34252dd2f2.zip |
Merge new-ui-branch to the trunk.
svn path=/trunk/; revision=22964
Diffstat (limited to 'mail')
71 files changed, 20395 insertions, 616 deletions
diff --git a/mail/ChangeLog b/mail/ChangeLog index 1ca3dae2d2..a5548f1232 100644 --- a/mail/ChangeLog +++ b/mail/ChangeLog @@ -1,3 +1,85 @@ +2003-10-21 Not Zed <NotZed@Ximian.com> + + * mail-component.c (emc_popup_properties): implement. + (emc_popup_properties_got_folder): builds dynamic + folder-properties dialogue. + (emc_popup_properties_response): set the properties on the folder + on an ok response. + (emc_popup_properties_free): free the properties working data. + + * mail-folder-cache.c (unset_folder_info): unhook from the right + function for message_changed. + +2003-10-20 Not Zed <NotZed@Ximian.com> + + * mail-component.c (emc_popup_new_folder): pass the right object + to set_selected(). Fixes a new real bug. Undid reformatting. + +2003-10-17 Jeffrey Stedfast <fejj@ximian.com> + + * mail-component.c: General compile fixes. + (emc_popup_new_folder): Fixed to not shadow a parameter. Fixes a + real bug. + + * mail-component.h: Added some prototypes. + +2003-10-13 Not Zed <NotZed@Ximian.com> + + * em-popup.c (em_popup_create_menu_once): only hookup target free + if we have a target set. + + * mail-component.c (load_accounts): removed debug i accidentally + left in. + (emc_tree_right_click): handle right-click context menu, using an + EMPopup table. + (emc_popup_*): setup empty popup handlers. + +2003-10-13 Not Zed <NotZed@Ximian.com> + + * em-folder-selection.c (em_select_folder): asynchornous folder + selection call. + (emfs_folder_selected): callback for folder selected. + + * em-folder-view.c (emfv_popup_move): implement. + (emfv_popup_copy): " + (emfv_popup_move_cb): async folder select callback to run it. + +2003-10-10 Not Zed <NotZed@Ximian.com> + + * mail-account-gui.c + (mail_account_gui_folder_selector_button_new): use + em_folder_selection_button. + (mail_account_gui_new): " + (folder_selected): " + + * em-folder-selection-button.c: Make this use camel uri's rather + than camelfolders. + (set_selection): removed, redundant. + (impl_dispose): removed, not needed. + + * em-folder-selection-button.h: change the selected signal not to + actually return the selection, which must get retrieved later. + + * mail-component.c (em_uri_from_camel): create an evo mail uri + from a camel one. + (em_uri_to_camel): the reverse. + + * mail-signature-editor.c (mail_signature_editor): up the version + of the gtkhtml editor. + +2003-10-09 Not Zed <NotZed@Ximian.com> + + * em-folder-selection-button.c (set_selection): always set + selected_folder, otherwise we don't unset it properly. + + * em-folder-selection.c (em_folder_selection_run_dialog): fix a + small memleak. + (em_folder_selection_run_dialog_uri): do the same as run_dialog + but take, and return physical uri's. + + * mail-component-factory.c (factory): removed some fixme's, and + re-hookedup the composer. + 2003-10-09 Frederic Crozat <fcrozat@mandrakesoft.com> * em-icon-stream.c: (emis_sync_close): @@ -10,6 +92,38 @@ create-rule-from-message bars so that we don't segfault when we right click with a multi-selection. +2003-10-08 Chris Toshok <toshok@ximian.com> + + * em-utils.c (em_utils_camel_address_to_destination): EDestination + => EABDestination, and e_destination => eab_destination. + (reply_get_composer): same. + (post_reply_to_message): same. + + * em-composer-utils.c (ask_confirm_for_unwanted_html_mail) + EDestination => EABDestination, and e_destination => + eab_destination. + (composer_get_message):same. + +2003-10-08 Not Zed <NotZed@Ximian.com> + + * mail-component.c (mail_component_peek): setup vfolders once we + hve the component, since its setup will call mail_component_peek, + fun recursion. + +2003-10-08 Not Zed <NotZed@Ximian.com> + + * mail-component.c (setup_local_folder): removed. + (setup_local_store): setup various needed globals properly. + (setup_account_storages): renamed to load_accounts. + (go_online): turn on interactivity as well as onlinedness. + + * GNOME_Evolution_Mail.server.in.in: point the preferences pages + to the right factory. + +2003-10-07 Not Zed <NotZed@Ximian.com> + + * mail-component.[ch]: Fix copyrights. + 2003-10-06 Jeffrey Stedfast <fejj@ximian.com> * mail-config-druid.c (identity_prepare): Fixed. @@ -28,6 +142,22 @@ the first unread message for now. This is actually annoying the fuck out of me, Radek, and a few other people. +2003-10-02 Not Zed <NotZed@Ximian.com> + + * mail-component.c (add_storage): Add the storage to the hash + after we've initialised it. + (mail_component_evomail_uri_from_folder): hardcode "local" account + pseudo-id for local folders. + (mail_component_get_folder_from_evomail_uri): handle the "local" + account case. + +2003-10-02 Not Zed <NotZed@Ximian.com> + + * mail-component.c (setup_local_store): use mbox:/path rather than + mbox:///path - the mbox code is 'wrong', but this is easier to + fix. fixes local unread counts. maybe the provider url-compare + should address this too. + 2003-10-02 Suresh Chandrasekharan <suresh.chandrasekharan@sun.com> * mail-config-druid.c: Fix for 40917 "Backspace shouldn't @@ -101,6 +231,27 @@ * em-format.c (emf_init): Oops, put the arguments in the right order. +2003-09-29 Ettore Perazzoli <ettore@ximian.com> + + * mail-component.c: New member local_store in + MailComponentPrivate. + (impl_dispose): Unref. + (mail_component_load_storage_by_uri): Return the CamelStore. + (setup_local_folder): New. + (setup_local_store): New. + (mail_component_init): Call it. + (mail_component_peek_storage_set): New. + (mail_component_get_folder_from_evomail_uri): New. + (mail_component_evomail_uri_from_folder): New. + + * em-folder-selection-button.c: New. + * em-folder-selection-button.h: New. + + * em-folder-selection.c: New. + * em-folder-selection.h: New. + + * em-marshal.list: Add NONE:POINTER. + 2003-09-25 Jeffrey Stedfast <fejj@ximian.com> * mail-account-gui.c (mail_account_gui_save): Allow any file: uri @@ -120,6 +271,33 @@ charset string is empty, default the charset to the user's locale charset. Partial fix for bug #47638. +2003-09-23 Ettore Perazzoli <ettore@ximian.com> + + * mail-component.c (add_storage): Remove unused arg "uri". + (mail_component_add_store): Likewise. + (add_storage): Don't set the "Connecting..." node. + (mail_component_init): Set up local store at + ~/.evolution/mail/local. + + * evolution-mbox-upgrade.c (get_local_store): Remove a double + xmlFree() that was causing it to crash. + + +2003-09-23 Ettore Perazzoli <ettore@ximian.com> + + * mail-component.c (add_storage): Note the new store. + + * mail-component-factory.c: Don't include "mail-callbacks.h" + anymore. + + * em-format-html.c (em_format_html_get_type): Get the base + directory with mail_component_peek_base_directory(). + * em-utils.c (filter_editor_response): Likewise. + (em_utils_edit_filters): Likewise. + + * em-folder-browser.c (emfb_init): Get the search context through + mail_component_peek_search_context(). + 2003-09-23 Jeffrey Stedfast <fejj@ximian.com> * evolution-mbox-upgrade.c (get_local_store): Don't xmlFree (name) @@ -273,6 +451,14 @@ * evolution-mbox-upgrade.c: New source file to migrate from the old mbox structure to the new mbox structure. +2003-09-08 Ettore Perazzoli <ettore@ximian.com> + + * mail-folder-cache.c (mail_note_store): Allow NULL storage in + precondition. + + * mail-component.c (mail_component_init): Remove debugging + message. + 2003-08-22 Not Zed <NotZed@Ximian.com> * mail-format.c (write_date): translate the local time format. @@ -284,14 +470,13 @@ day names, and the autoconf magic which made Not Zed dislike the inclusion of the timezone name. -2003-08-14 Jeffrey Stedfast <fejj@ximian.com> +2003-08-18 Ettore Perazzoli <ettore@ximian.com> - * mail-ops.c (mail_send_message): Don't abort at the first failure - after sending (filtering, appending to Sent, syncing). Instead, - keep a running tab of exceptions and then set a culmulative - exception at the end to report to our caller. Also, if we fail to - append to the account Sent folder, try again with the local Sent - folder. Fixes bug #46512. + * GNOME_Evolution_Mail.server.in.in: Rename + GNOME_Evolution_Mail_Component2 to + GNOME_Evolution_Mail_Component_2 and GNOME_Evolution_Mail_Factory2 + to GNOME_Evolution_Mail_Factory_2. + * mail-component-factory.c: Update accordingly. 2003-08-18 Jeffrey Stedfast <fejj@ximian.com> @@ -336,6 +521,15 @@ * mail-display.c (mail_display_render): Change "%P" to "%p" so that strftime() can work under solaris. +2003-08-14 Jeffrey Stedfast <fejj@ximian.com> + + * mail-ops.c (mail_send_message): Don't abort at the first failure + after sending (filtering, appending to Sent, syncing). Instead, + keep a running tab of exceptions and then set a culmulative + exception at the end to report to our caller. Also, if we fail to + append to the account Sent folder, try again with the local Sent + folder. Fixes bug #46512. + 2003-08-13 Suresh Chandrasekharan <suresh.chandrasekharan@sun.com> * e-searching-tokenizer.c (searcher_next_token): Fix for 45818 ( @@ -353,6 +547,12 @@ * mail-session.c (remove_timeout): Removed. (register_timeout): Removed. +2003-08-09 Ettore Perazzoli <ettore@ximian.com> + + * mail-component.c (storage_go_online): Pass NULL for the + operation pointer to mail_note_store(), to sync with Michael's + changes. + 2003-08-05 Jeffrey Stedfast <fejj@ximian.com> * mail-format.c (handle_multipart_encrypted): Updated for @@ -1308,6 +1508,154 @@ * message-browser.c (message_browser_new): Handle our own Delete key presses. Fixes bug #45597. +2003-07-25 Ettore Perazzoli <ettore@ximian.com> + + * mail-callbacks.c (do_view_message): No need to pass a shell + argument to message_browser_new() anymore. + + * message-browser.c (message_browser_new): Removed arg shell. No + need to pass it to folder_browser_new() either. + + * mail-component.c (create_view_callback): No need to pass a shell + arg to folder_browser_factory_new_control() anymore. + + * folder-browser-factory.c (folder_browser_factory_new_control): + Removed arg shell; folder_browser_browser_new() doesn't need it + anymore. + + * folder-browser.c (folder_browser_destroy): No need to unref + ->shell anymore. + (folder_browser_new): Removed shell arg. + (folder_browser_gui_init): Removed a const qualifier that was not + supposed to be there. + + * folder-browser.h: Removed member shell from struct + FolderBrowser. + +2003-07-25 Ettore Perazzoli <ettore@ximian.com> + + * folder-browser.c (folder_browser_gui_init): Get the search + context through mail_component_peek_search_context(), since it's + no longer a global variable. + (folder_browser_gui_init): Cleaned up an extra unneeded if() + statement. + + * mail-component.c: New member search_context in struct + MailComponentPrivate. + (mail_component_peek_search_context): New. + (setup_search_context): New function to initialize the + search_context, based on the old code in component-factory.c. + (mail_component_init): Call it here. + (impl_dispose): Unref the rule_context. + + * mail-component-factory.c: Removed global variable + search_context. + +2003-07-25 Ettore Perazzoli <ettore@ximian.com> + + * mail-component.c (browser_page_switched_callback): New callback + for the "page_switched" signal on EStorageBrowser; deactivate the + previous page, activate the new one. + (impl_createControls): Connect. + +2003-07-24 Ettore Perazzoli <ettore@ximian.com> + + * mail-mt.c (do_op_status): Pass "evolution-mail" as the ID to + evolution_activity_client_new(). [This is just a temporary thing + to avoid the fact that we don't have component-factory.h anymore. + Eventually we'll just get rid of the activity client stuff.] + + * mail-component-factory.c: Added to the build. Also, finished + implementing and moving the factory over from component-factory.c. + + * component-factory.c: Removed from the build. + * component-factory.h: Removed from the build. + + * mail-component.c: Removed some debugging messages. + +2003-07-23 Ettore Perazzoli <ettore@ximian.com> + + * subscribe-dialog.c: Converted to use EStorages instead of + EvolutionStorages and the new MailComponent object. + + * mail.h: Nuked a bunch of stuff. This will go away when I am + done refactoring. + + * mail-offline-handler.c: Use the new MailComponent object. + + * mail-folder-cache.c, mail-folder-cache.h: Converted to use + EStorages instead of EvolutionStorages. + + * mail-display.c: Use g_timeout and g_source functions instead of + gtk_timeout functions. + + * mail-send-recv.c: Use g_timeout and g_source functions instead + of gtk_timeout functions. + (receive_update_got_store): Updated for the new mail_note_store(). + + * mail-session.c: Use g_timeout and g_source functions instead of + gtk_timeout functions. + + * mail-config-factory.c (factory): Removed. + + * folder-browser.c (folder_browser_destroy): Use GLib + timeout/source functions instead of the deprecated GTK ones. + (done_message_selected): Likewise. + (folder_browser_gui_init): Protect against fb->search being NULL. + + * mail-account-gui.c (add_new_store): Use new MailComponent object + and EStorages instead of EvolutionStorages. + (mail_account_gui_save): Likewise. + + * mail-accounts.c (account_delete_clicked): Use new MailComponent + object and EStorages instead of EvolutionStorages. + (account_able_clicked): Likewise. + (account_able_toggled): Likewise. + + * mail-autofilter.c: Use mail_component_peek_base_directory() + instead of the evolution_dir global. + * mail-callbacks.c: Likewise. + * mail-config.c (uri_to_evname): Likewise. + (mail_config_get_signature_list): Likewise. + (delete_unused_signature_file): Likewise. + * mail-display.c (mail_display_class_init): Likewise. + * mail-importer.c (mail_importer_make_local_folder): Likewise. + * mail-local.c (mlf_getv): Likewise. + * mail-ops.c (uid_cachename_hack): Likewise. + * mail-summary.c (generate_folder_summaries): Likewise. + * mail-tools.c (mail_tool_get_local_inbox): Likewise. + (mail_tools_folder_to_url): Likewise. + * mail-vfolder.c (mail_vfolder_delete_uri): Likewise. + (mail_vfolder_rename_uri): Likewise. + (context_rule_removed): Likewise. + (store_folder_deleted): Likewise. + (store_folder_renamed): Likewise. + (vfolder_load_storage): Likewise. + (vfolder_editor_response): Likewise. + (edit_rule_response): Likewise. + (new_rule_clicked): Likewise. + (vfolder_gui_add_rule): Likewise. + * mail-session.c (main_get_filter_driver): Likewise. + (mail_session_forget_password): Likewise. + (mail_session_init): Get a base_directory arg. + + * component-factory.c, component-factory.h: Disabled a bunch of + stuff to get it to compile in the new configuration. These files + will eventually go away when I am done refactoring this. + + * Makefile.am: Do not build importers, compile generate + skels/stubs for Evolution. + + * GNOME_Evolution_Mail.server.in.in: Rename control factory to + OAFIID:GNOME_Evolution_Mail_Factory2. Add new component + GNOME_Evolution_Mail_Component2. + + * mail-component-factory.c: New file implementing the Bonobo + factory. + + * mail-component.c, mail-component.h: New files implementing the + new mail component, using the new Evolution::Component IDL. + 2003-07-23 Jeffrey Stedfast <fejj@ximian.com> * mail-format.c (mail_format_data_wrapper_write_to_stream): Revert diff --git a/mail/GNOME_Evolution_Mail.server.in.in b/mail/GNOME_Evolution_Mail.server.in.in index 9d0391d0be..30fcd5b070 100644 --- a/mail/GNOME_Evolution_Mail.server.in.in +++ b/mail/GNOME_Evolution_Mail.server.in.in @@ -3,7 +3,7 @@ <!-- Folder display control --> <!-- (factory) --> - <oaf_server iid="OAFIID:GNOME_Evolution_Mail_ControlFactory" + <oaf_server iid="OAFIID:GNOME_Evolution_Mail_Factory_2" type="shlib" location="@COMPONENTDIR@/libevolution-mail.so"> @@ -29,6 +29,22 @@ _value="Evolution Mail folder viewer"/> </oaf_server> + <!-- Component Interface --> + + <oaf_server iid="OAFIID:GNOME_Evolution_Mail_Component_2" + type="factory" + location="OAFIID:GNOME_Evolution_Mail_Factory_2"> + + <oaf_attribute name="repo_ids" type="stringv"> + <item value="IDL:GNOME/Evolution/Component:1.0"/> + </oaf_attribute> + + <oaf_attribute name="name" type="string" _value="Evolution Mail component"/> + + <oaf_attribute name="evolution:component_icon" type="string" value="evolution-inbox.png"/> + <oaf_attribute name="evolution:component_display_order" type="number" value="1"/> + </oaf_server> + <!-- Shell Component --> <oaf_server iid="OAFIID:GNOME_Evolution_Mail_ShellComponent" @@ -53,7 +69,7 @@ <!-- (composer) --> <oaf_server iid="OAFIID:GNOME_Evolution_Mail_Composer" type="factory" - location="OAFIID:GNOME_Evolution_Mail_ControlFactory"> + location="OAFIID:GNOME_Evolution_Mail_Factory_2"> <oaf_attribute name="repo_ids" type="stringv"> <item value="IDL:GNOME/Evolution:Composer:1.0"/> @@ -109,7 +125,7 @@ <!-- Account Editor --> <oaf_server iid="OAFIID:GNOME_Evolution_Mail_Accounts_ConfigControl" type="factory" - location="OAFIID:GNOME_Evolution_Mail_ControlFactory"> + location="OAFIID:GNOME_Evolution_Mail_Factory_2"> <oaf_attribute name="repo_ids" type="stringv"> <item value="IDL:GNOME/Evolution/ConfigControl:1.0"/> @@ -138,7 +154,7 @@ <!-- Mail Preferences --> <oaf_server iid="OAFIID:GNOME_Evolution_Mail_Preferences_ConfigControl" type="factory" - location="OAFIID:GNOME_Evolution_Mail_ControlFactory"> + location="OAFIID:GNOME_Evolution_Mail_Factory_2"> <oaf_attribute name="repo_ids" type="stringv"> <item value="IDL:GNOME/Evolution/ConfigControl:1.0"/> @@ -163,7 +179,7 @@ <!-- Composer Preferences --> <oaf_server iid="OAFIID:GNOME_Evolution_Mail_ComposerPrefs_ConfigControl" type="factory" - location="OAFIID:GNOME_Evolution_Mail_ControlFactory"> + location="OAFIID:GNOME_Evolution_Mail_Factory_2"> <oaf_attribute name="repo_ids" type="stringv"> <item value="IDL:GNOME/Evolution/ConfigControl:1.0"/> diff --git a/mail/Makefile.am b/mail/Makefile.am index 75802f396e..ac0b6827bd 100644 --- a/mail/Makefile.am +++ b/mail/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = importers +SUBDIRS = # importers FIXME libexec_PROGRAMS = \ evolution-mbox-upgrade @@ -39,128 +39,153 @@ INCLUDES = \ component_LTLIBRARIES = libevolution-mail.la -EVOLUTION_MAIL_CORBA_GENERATED_H = \ + +# Code generation for Mailer.idl + +MAILER_IDL = Mailer.idl + +MAILER_IDL_GENERATED_H = \ Mailer.h -EVOLUTION_MAIL_CORBA_GENERATED_C = \ +MAILER_IDL_GENERATED_C = \ Mailer-common.c \ Mailer-skels.c \ Mailer-stubs.c -EVOLUTION_MAIL_CORBA_GENERATED = $(EVOLUTION_MAIL_CORBA_GENERATED_C) $(EVOLUTION_MAIL_CORBA_GENERATED_H) +MAILER_IDL_GENERATED = $(MAILER_IDL_GENERATED_C) $(MAILER_IDL_GENERATED_H) + +$(MAILER_IDL_GENERATED_H): Mailer.idl + $(ORBIT_IDL) -I $(srcdir) -I $(datadir)/idl $(IDL_INCLUDES) $(srcdir)/Mailer.idl +$(MAILER_IDL_GENERATED_C): $(MAILER_IDL_GENERATED_H) + + +# Code generation for Spell.idl SPELL_IDL = Spell.idl -IDL_GENERATED_H = \ +SPELL_IDL_GENERATED_H = \ Spell.h -IDL_GENERATED_C = \ +SPELL_IDL_GENERATED_C = \ Spell-common.c \ Spell-skels.c \ Spell-stubs.c -IDL_GENERATED = $(IDL_GENERATED_C) $(IDL_GENERATED_H) +SPELL_IDL_GENERATED = $(SPELL_IDL_GENERATED_C) $(SPELL_IDL_GENERATED_H) -$(IDL_GENERATED_H): $(SPELL_IDL) +$(SPELL_IDL_GENERATED_H): $(SPELL_IDL) $(ORBIT_IDL) -I $(srcdir) -I $(datadir)/idl $(IDL_INCLUDES) $(srcdir)/Spell.idl -$(IDL_GENERATED_C): $(IDL_GENERATED_H) +$(SPELL_IDL_GENERATED_C): $(SPELL_IDL_GENERATED_H) Spell-impl.o: Spell.h -libevolution_mail_la_SOURCES = \ - $(EVOLUTION_MAIL_CORBA_GENERATED) \ - $(IDL_GENERATED) \ - component-factory.c \ - component-factory.h \ - e-searching-tokenizer.c \ - e-searching-tokenizer.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-info.c \ - folder-info.h \ - mail-account-editor.c \ - mail-account-editor.h \ - mail-account-gui.c \ - mail-account-gui.h \ - mail-accounts.c \ - mail-accounts.h \ - mail-autofilter.c \ - mail-autofilter.h \ - mail-composer-prefs.c \ - mail-composer-prefs.h \ - mail-config.c \ - mail-config.h \ - mail-config-druid.c \ - mail-config-druid.h \ - mail-crypto.c \ - mail-crypto.h \ - mail-config-factory.c \ - mail-config-factory.h \ - mail-preferences.c \ - mail-preferences.h \ - mail-folder-cache.c \ - mail-folder-cache.h \ - mail-importer.c \ - mail-importer.h \ - mail-local.c \ - mail-local.h \ - mail-mt.c \ - mail-mt.h \ - mail-offline-handler.c \ - mail-offline-handler.h \ - mail-ops.c \ - mail-ops.h \ - mail-send-recv.c \ - mail-send-recv.h \ - mail-session.c \ - mail-session.h \ - mail-signature-editor.c \ - mail-signature-editor.h \ - mail-tools.c \ - mail-tools.h \ - mail-types.h \ - mail-vfolder.c \ - mail-vfolder.h \ - message-list.c \ - message-list.h \ - message-tag-editor.c \ - message-tag-editor.h \ - message-tag-followup.c \ - message-tag-followup.h \ + +# libevolution-mail + +libevolution_mail_la_SOURCES = \ + $(SPELL_IDL_GENERATED) \ + $(MAILER_IDL_GENERATED) \ + e-searching-tokenizer.c \ + e-searching-tokenizer.h \ + em-inline-filter.c \ + em-inline-filter.h \ + em-folder-selection.c \ + em-folder-selection.h \ + em-folder-selection-button.c \ + em-folder-selection-button.h \ + em-folder-selector.c \ + em-folder-selector.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-info.c \ + folder-info.h \ + mail-account-editor.c \ + mail-account-editor.h \ + mail-account-gui.c \ + mail-account-gui.h \ + mail-accounts.c \ + mail-accounts.h \ + mail-autofilter.c \ + mail-autofilter.h \ + mail-component-factory.c \ + mail-component.c \ + mail-component.h \ + mail-composer-prefs.c \ + mail-composer-prefs.h \ + mail-config.c \ + mail-config.h \ + mail-config-druid.c \ + mail-config-druid.h \ + mail-crypto.c \ + mail-crypto.h \ + mail-config-factory.c \ + mail-config-factory.h \ + mail-preferences.c \ + mail-preferences.h \ + mail-folder-cache.c \ + mail-folder-cache.h \ + mail-importer.c \ + mail-importer.h \ + mail-mt.c \ + mail-mt.h \ + mail-offline-handler.c \ + mail-offline-handler.h \ + mail-ops.c \ + mail-ops.h \ + mail-send-recv.c \ + mail-send-recv.h \ + mail-session.c \ + mail-session.h \ + mail-signature-editor.c \ + mail-signature-editor.h \ + mail-tools.c \ + mail-tools.h \ + mail-types.h \ + mail-vfolder.c \ + mail-vfolder.h \ + message-list.c \ + message-list.h \ + message-tag-editor.c \ + message-tag-editor.h \ + message-tag-followup.c \ + message-tag-followup.h \ mail.h +# EPFIXME: Functionality to be brought back to life. +# +# mail-local.c +# mail-local.h + libevolution_mail_la_LIBADD = \ $(top_builddir)/shell/importer/libevolution-importer.la \ $(top_builddir)/camel/libcamel.la \ @@ -177,6 +202,9 @@ libevolution_mail_la_LIBADD = \ libevolution_mail_la_LDFLAGS = \ -avoid-version -module + +# .server files + evolution_mbox_upgrade_SOURCES = evolution-mbox-upgrade.c evolution_mbox_upgrade_LDADD = \ $(top_builddir)/camel/libcamel.la \ @@ -190,21 +218,16 @@ server_DATA = $(server_in_files:.server.in.in=.server) sed -e "s|\@COMPONENTDIR\@|$(componentdir)|" $< > $@ @INTLTOOL_SERVER_RULE@ -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 = message-list.etspec +# Misc data to install -schemadir = $(GCONF_SCHEMA_FILE_DIR) -schema_DATA = evolution-mail.schemas +glade_DATA = mail-config.glade local-config.glade subscribe-dialog.glade message-tags.glade mail-search.glade +MARSHAL_GENERATED = em-marshal.c em-marshal.h +@EVO_MARSHAL_RULE@ -idl_DATA = Mailer.idl +etspec_DATA = mail-accounts.etspec message-list.etspec subscribe-dialog.etspec -$(EVOLUTION_MAIL_CORBA_GENERATED_H): Mailer.idl - $(ORBIT_IDL) -I $(srcdir) -I $(datadir)/idl $(IDL_INCLUDES) $(srcdir)/Mailer.idl -$(EVOLUTION_MAIL_CORBA_GENERATED_C): $(EVOLUTION_MAIL_CORBA_GENERATED_H) +idl_DATA = $(MAILER_IDL) EXTRA_DIST = \ ChangeLog.pre-1-4 \ @@ -217,6 +240,9 @@ EXTRA_DIST = \ $(server_DATA) \ $(etspec_DATA) + +# Purify support + if ENABLE_PURIFY PLINK = $(LIBTOOL) --mode=link $(PURIFY) $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ @@ -228,6 +254,12 @@ evolution-mail.pure: evolution-mail endif + +# GConf + +schemadir = $(GCONF_SCHEMA_FILE_DIR) +schema_DATA = evolution-mail.schemas + install-data-local: if test -z "$(DESTDIR)" ; then \ for p in $(schema_DATA) ; do \ @@ -235,8 +267,11 @@ install-data-local: done \ fi + +# Prologue + dist-hook: cd $(distdir); rm -f $(BUILT_SOURCES) -BUILT_SOURCES = $(EVOLUTION_MAIL_CORBA_GENERATED) $(IDL_GENERATED) $(MARSHAL_GENERATED) $(server_DATA) +BUILT_SOURCES = $(MAILER_IDL_GENERATED) $(SPELL_IDL_GENERATED) $(MARSHAL_GENERATED) $(server_DATA) CLEANFILES = $(BUILT_SOURCES) diff --git a/mail/component-factory.c b/mail/component-factory.c index a1d0c322eb..9f361ec5fc 100644 --- a/mail/component-factory.c +++ b/mail/component-factory.c @@ -20,6 +20,8 @@ * Boston, MA 02111-1307, USA. */ +/* EPFIXME: This file should go away. */ + #ifdef HAVE_CONFIG_H #include <config.h> @@ -54,6 +56,7 @@ #include "mail-config.h" #include "mail-config-factory.h" #include "mail-preferences.h" +#include "mail-component.h" #include "mail-composer-prefs.h" #include "mail-tools.h" #include "mail-ops.h" @@ -79,7 +82,6 @@ char *default_sent_folder_uri; CamelFolder *sent_folder = NULL; char *default_outbox_folder_uri; CamelFolder *outbox_folder = NULL; -char *evolution_dir; EvolutionShellClient *global_shell_client = NULL; @@ -136,6 +138,14 @@ type_is_vtrash (const char *type) return !strcmp (type, "vtrash"); } + +/* Forward decls just to get it to compile without warnings. EPFIXME: This is + all junk at this point in the refactoring anyways, so it will all die. */ +static void mail_load_storage_by_uri (GNOME_Evolution_Shell shell, const char *uri, const char *name); +static void mail_load_storages (GNOME_Evolution_Shell shell, EAccountList *accounts); +static void mail_hash_storage (CamelService *store, EvolutionStorage *storage); + + /* EvolutionShellComponent methods and signals. */ static BonoboControl * @@ -766,6 +776,7 @@ owner_set_cb (EvolutionShellComponent *shell_component, const char *evolution_homedir, gpointer user_data) { +#if 0 GNOME_Evolution_Shell corba_shell; EAccountList *accounts; int i; @@ -774,7 +785,6 @@ owner_set_cb (EvolutionShellComponent *shell_component, global_shell_client = shell_client; g_object_weak_ref ((GObject *) shell_client, (GWeakNotify) shell_client_destroy, NULL); - evolution_dir = g_strdup (evolution_homedir); mail_session_init (); async_event = mail_async_event_new(); @@ -831,6 +841,7 @@ owner_set_cb (EvolutionShellComponent *shell_component, /* Everything should be ready now */ evolution_folder_info_notify_ready (); +#endif } static void @@ -1037,7 +1048,7 @@ request_quit (EvolutionShellComponent *shell_component, } static BonoboObject * -create_component (void) +create_shell_component (void) { EvolutionShellComponentDndDestinationFolder *destination_interface; MailOfflineHandler *offline_handler; @@ -1336,12 +1347,14 @@ storage_xfer_folder (EvolutionStorage *storage, camel_exception_clear (&ex); } +#if 0 /* EPFIXME */ static void storage_connected (CamelStore *store, CamelFolderInfo *info, void *listener) { notify_listener (listener, (info ? GNOME_Evolution_Storage_OK : GNOME_Evolution_Storage_GENERIC_ERROR)); } +#endif static void storage_connect (EvolutionStorage *storage, @@ -1349,8 +1362,10 @@ storage_connect (EvolutionStorage *storage, const char *path, CamelStore *store) { +#if 0 /* EPFIXME */ mail_note_store (CAMEL_STORE (store), NULL, storage, CORBA_OBJECT_NIL, storage_connected, listener); +#endif } static void @@ -1373,7 +1388,7 @@ add_storage (const char *name, const char *uri, CamelService *store, evolution_storage_has_subfolders (storage, "/", _("Connecting...")); mail_hash_storage (store, storage); /*if (auto_connect)*/ - mail_note_store ((CamelStore *) store, NULL, storage, CORBA_OBJECT_NIL, NULL, NULL); + /* EPFIXME mail_note_store ((CamelStore *) store, NULL, storage, CORBA_OBJECT_NIL, NULL, NULL); */ /* falllll */ case EVOLUTION_STORAGE_ERROR_ALREADYREGISTERED: case EVOLUTION_STORAGE_ERROR_EXISTS: @@ -1386,34 +1401,7 @@ add_storage (const char *name, const char *uri, CamelService *store, } } -void -mail_add_storage (CamelStore *store, const char *name, const char *uri) -{ - EvolutionShellClient *shell_client; - GNOME_Evolution_Shell shell; - CamelException ex; - - g_return_if_fail (CAMEL_IS_STORE (store)); - - shell_client = evolution_shell_component_get_owner (shell_component); - shell = evolution_shell_client_corba_objref (shell_client); - - camel_exception_init (&ex); - - if (name == NULL) { - char *service_name; - - service_name = camel_service_get_name ((CamelService *) store, TRUE); - add_storage (service_name, uri, (CamelService *) store, shell, &ex); - g_free (service_name); - } else { - add_storage (name, uri, (CamelService *) store, shell, &ex); - } - - camel_exception_clear (&ex); -} - -void +static void mail_load_storage_by_uri (GNOME_Evolution_Shell shell, const char *uri, const char *name) { CamelException ex; @@ -1468,7 +1456,7 @@ mail_load_storage_by_uri (GNOME_Evolution_Shell shell, const char *uri, const ch camel_object_unref (CAMEL_OBJECT (store)); } -void +static void mail_load_storages (GNOME_Evolution_Shell shell, EAccountList *accounts) { CamelException ex; @@ -1500,103 +1488,23 @@ mail_load_storages (GNOME_Evolution_Shell shell, EAccountList *accounts) g_object_unref (iter); } -void +static void mail_hash_storage (CamelService *store, EvolutionStorage *storage) { camel_object_ref (CAMEL_OBJECT (store)); g_hash_table_insert (storages_hash, store, storage); } -EvolutionStorage * -mail_lookup_storage (CamelStore *store) -{ - EvolutionStorage *storage; - - /* Because the storages_hash holds a reference to each store - * used as a key in it, none of them will ever be gc'ed, meaning - * any call to camel_session_get_{service,store} with the same - * URL will always return the same object. So this works. - */ - - storage = g_hash_table_lookup (storages_hash, store); - if (storage) - bonobo_object_ref (BONOBO_OBJECT (storage)); - - return storage; -} - +#if 0 static void store_disconnect(CamelStore *store, void *event_data, void *data) { camel_service_disconnect (CAMEL_SERVICE (store), TRUE, NULL); camel_object_unref (CAMEL_OBJECT (store)); } +#endif -void -mail_remove_storage (CamelStore *store) -{ - EvolutionStorage *storage; - EvolutionShellClient *shell_client; - GNOME_Evolution_Shell corba_shell; - - /* Because the storages_hash holds a reference to each store - * used as a key in it, none of them will ever be gc'ed, meaning - * any call to camel_session_get_{service,store} with the same - * URL will always return the same object. So this works. - */ - - storage = g_hash_table_lookup (storages_hash, store); - if (!storage) - return; - - g_hash_table_remove (storages_hash, store); - - /* so i guess potentially we could have a race, add a store while one - being removed. ?? */ - mail_note_store_remove(store); - - shell_client = evolution_shell_component_get_owner (shell_component); - corba_shell = evolution_shell_client_corba_objref(shell_client); - - evolution_storage_deregister_on_shell (storage, corba_shell); - - mail_async_event_emit(async_event, MAIL_ASYNC_THREAD, (MailAsyncFunc)store_disconnect, store, NULL, NULL); -} - -void -mail_remove_storage_by_uri (const char *uri) -{ - CamelProvider *prov; - CamelService *store; - - prov = camel_session_get_provider (session, uri, NULL); - if (!prov) - return; - if (!(prov->flags & CAMEL_PROVIDER_IS_STORAGE) || - (prov->flags & CAMEL_PROVIDER_IS_EXTERNAL)) - return; - - store = camel_session_get_service (session, uri, CAMEL_PROVIDER_STORE, NULL); - if (store != NULL) { - mail_remove_storage (CAMEL_STORE (store)); - camel_object_unref (CAMEL_OBJECT (store)); - } -} - -int -mail_storages_count (void) -{ - return g_hash_table_size (storages_hash); -} - -void -mail_storages_foreach (GHFunc func, gpointer data) -{ - g_hash_table_foreach (storages_hash, func, data); -} - - -#define FACTORY_ID "OAFIID:GNOME_Evolution_Mail_ControlFactory" +#define FACTORY_ID "OAFIID:GNOME_Evolution_Mail_Factory_2" #define MAIL_CONFIG_IID "OAFIID:GNOME_Evolution_MailConfig" #define WIZARD_IID "OAFIID:GNOME_Evolution_Mail_Wizard" @@ -1608,9 +1516,14 @@ factory (BonoboGenericFactory *factory, const char *component_id, void *closure) { - if (strcmp (component_id, COMPONENT_ID) == 0) - return create_component(); - else if (strcmp(component_id, MAIL_CONFIG_IID) == 0) + if (strcmp (component_id, SHELL_COMPONENT_ID) == 0) + return create_shell_component(); + else if (strcmp (component_id, COMPONENT_ID) == 0) { + MailComponent *component = mail_component_peek (); + + bonobo_object_ref (BONOBO_OBJECT (component)); + return BONOBO_OBJECT (component); + } else if (strcmp(component_id, MAIL_CONFIG_IID) == 0) return (BonoboObject *)g_object_new (evolution_mail_config_get_type (), NULL); else if (strcmp(component_id, FOLDER_INFO_IID) == 0) return evolution_folder_info_new(); diff --git a/mail/component-factory.h b/mail/component-factory.h index 3abd058556..fdc7c4056f 100644 --- a/mail/component-factory.h +++ b/mail/component-factory.h @@ -23,7 +23,8 @@ #ifndef COMPONENT_FACTORY_H #define COMPONENT_FACTORY_H -#define COMPONENT_ID "OAFIID:GNOME_Evolution_Mail_ShellComponent" +#define COMPONENT_ID "OAFIID:GNOME_Evolution_Mail_Component2" +#define SHELL_COMPONENT_ID "OAFIID:GNOME_Evolution_Mail_ShellComponent" #define SUMMARY_FACTORY_ID "OAFIID:GNOME_Evolution_Mail_ExecutiveSummaryComponentFactory" #endif diff --git a/mail/em-composer-utils.c b/mail/em-composer-utils.c index e181c2cb33..75166a19b4 100644 --- a/mail/em-composer-utils.c +++ b/mail/em-composer-utils.c @@ -101,7 +101,7 @@ composer_destroy_cb (gpointer user_data, GObject *deadbeef) } static gboolean -ask_confirm_for_unwanted_html_mail (EMsgComposer *composer, EDestination **recipients) +ask_confirm_for_unwanted_html_mail (EMsgComposer *composer, EABDestination **recipients) { gboolean show_again, res; GConfClient *gconf; @@ -117,10 +117,10 @@ ask_confirm_for_unwanted_html_mail (EMsgComposer *composer, EDestination **recip 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])) { + if (!eab_destination_get_html_mail_pref (recipients[i])) { const char *name; - name = e_destination_get_textrep (recipients[i], FALSE); + name = eab_destination_get_textrep (recipients[i], FALSE); g_string_append_printf (str, " %s\n", name); } @@ -262,7 +262,7 @@ static CamelMimeMessage * composer_get_message (EMsgComposer *composer, gboolean post, gboolean save_html_object_data) { CamelMimeMessage *message = NULL; - EDestination **recipients, **recipients_bcc; + EABDestination **recipients, **recipients_bcc; gboolean send_html, confirm_html; CamelInternetAddress *cia; int hidden = 0, shown = 0; @@ -286,15 +286,15 @@ composer_get_message (EMsgComposer *composer, gboolean post, gboolean save_html_ /* 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]); + const char *addr = eab_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])) { + if (eab_destination_is_evolution_list (recipients[i]) + && !eab_destination_list_show_addresses (recipients[i])) { hidden++; } else { shown++; @@ -307,7 +307,7 @@ composer_get_message (EMsgComposer *composer, gboolean post, gboolean save_html_ 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]); + const char *addr = eab_destination_get_address (recipients_bcc[i]); if (addr && addr[0]) { camel_address_decode ((CamelAddress *) cia, addr); @@ -318,7 +318,7 @@ composer_get_message (EMsgComposer *composer, gboolean post, gboolean save_html_ } } - e_destination_freev (recipients_bcc); + eab_destination_freev (recipients_bcc); } camel_object_unref (cia); @@ -347,7 +347,7 @@ composer_get_message (EMsgComposer *composer, gboolean post, gboolean save_html_ if (recipients) { for (i = 0; recipients[i] != NULL && !html_problem; i++) { - if (!e_destination_get_html_mail_pref (recipients[i])) + if (!eab_destination_get_html_mail_pref (recipients[i])) html_problem = TRUE; } } @@ -384,12 +384,12 @@ composer_get_message (EMsgComposer *composer, gboolean post, gboolean save_html_ /* Get the message recipients and 'touch' them, boosting their use scores */ if (recipients) - e_destination_touchv (recipients); + eab_destination_touchv (recipients); finished: if (recipients) - e_destination_freev (recipients); + eab_destination_freev (recipients); return message; } diff --git a/mail/em-folder-browser.c b/mail/em-folder-browser.c index 243ea449eb..03ab55800f 100644 --- a/mail/em-folder-browser.c +++ b/mail/em-folder-browser.c @@ -156,9 +156,8 @@ static void emfb_init(GObject *o) { EMFolderBrowser *emfb = (EMFolderBrowser *)o; + RuleContext *search_context = mail_component_peek_search_context (mail_component_peek ()); struct _EMFolderBrowserPrivate *p; - /* FIXME ... */ - extern RuleContext *search_context; p = emfb->priv = g_malloc0(sizeof(struct _EMFolderBrowserPrivate)); diff --git a/mail/em-folder-selection-button.c b/mail/em-folder-selection-button.c new file mode 100644 index 0000000000..ac5a6e1d06 --- /dev/null +++ b/mail/em-folder-selection-button.c @@ -0,0 +1,242 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* em-folder-selection-button.c + * + * Copyright (C) 2003 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ettore Perazzoli <ettore@ximian.com> + */ + +#include <config.h> + +#include <string.h> + +#include "em-folder-selection-button.h" + +#include "mail-component.h" +#include "em-folder-selector.h" + +#include <gal/util/e-util.h> + +#include <gtk/gtkimage.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkhbox.h> + + +#define PARENT_TYPE gtk_button_get_type () +static GtkButtonClass *parent_class = NULL; + + +struct _EMFolderSelectionButtonPrivate { + GtkWidget *icon; + GtkWidget *label; + + char *uri; + + char *title; + char *caption; +}; + +enum { + SELECTED, + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0 }; + + +/* Utility functions. */ + +static void +set_contents_unselected (EMFolderSelectionButton *button) +{ + gtk_image_set_from_pixbuf (GTK_IMAGE (button->priv->icon), NULL); + gtk_label_set_text (GTK_LABEL (button->priv->label), _("<click here to select a folder>")); +} + +static void +set_contents (EMFolderSelectionButton *button) +{ + EMFolderSelectionButtonPrivate *priv = button->priv; + char *path, *tmp, *label; + + if (priv->uri == NULL) + goto unset; + + /* We set the button name directly from the storage set path, which is /accountname/path/foldername */ + path = e_storage_set_get_path_for_physical_uri(mail_component_peek_storage_set(mail_component_peek()), priv->uri); + + if (path == NULL) + goto unknown; + + tmp = strchr(path+1, '/'); + if (tmp == NULL) + goto unknown; + *tmp++ = 0; + + label = g_strdup_printf(_("\"%s\" in \"%s\""), tmp, path+1); + gtk_label_set_text (GTK_LABEL (priv->label), label); + g_free (label); + + g_free(path); + return; + +unknown: + g_free(path); +unset: + set_contents_unselected(button); +} + +static void +impl_finalize (GObject *object) +{ + EMFolderSelectionButtonPrivate *priv = EM_FOLDER_SELECTION_BUTTON (object)->priv; + + g_free (priv->title); + g_free (priv->caption); + g_free(priv->uri); + g_free (priv); + + (* G_OBJECT_CLASS (parent_class)->finalize) (object); +} + +static void +emfsb_selector_response(EMFolderSelector *emfs, int response, EMFolderSelectionButton *button) +{ + if (response == GTK_RESPONSE_OK) { + const char *uri = em_folder_selector_get_selected_uri(emfs); + + em_folder_selection_button_set_selection(button, uri); + g_signal_emit(button, signals[SELECTED], 0); + } + + gtk_widget_destroy((GtkWidget *)emfs); +} + +static void +impl_clicked (GtkButton *button) +{ + EMFolderSelectionButtonPrivate *priv = EM_FOLDER_SELECTION_BUTTON (button)->priv; + EStorageSet *ess; + GtkWidget *w; + GtkWidget *toplevel; + + if (GTK_BUTTON_CLASS (parent_class)->clicked != NULL) + (* GTK_BUTTON_CLASS (parent_class)->clicked) (button); + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (button)); + ess = mail_component_peek_storage_set(mail_component_peek()); + w = em_folder_selector_new(ess, EM_FOLDER_SELECTOR_CAN_CREATE, priv->title, priv->caption); + em_folder_selector_set_selected_uri((EMFolderSelector *)w, priv->uri); + g_signal_connect(w, "response", G_CALLBACK(emfsb_selector_response), button); + gtk_widget_show(w); +} +#if 0 +{ + uri = em_folder_selection_run_dialog_uri((GtkWindow *)toplevel, + priv->title, + priv->caption, + priv->uri); + + em_folder_selection_button_set_selection (EM_FOLDER_SELECTION_BUTTON (button), uri); + g_free(uri); + + g_signal_emit (button, signals[SELECTED], 0); +} +#endif + +static void +class_init (EMFolderSelectionButtonClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkButtonClass *button_class = GTK_BUTTON_CLASS (class); + + object_class->finalize = impl_finalize; + + button_class->clicked = impl_clicked; + + parent_class = g_type_class_peek_parent (class); + + signals[SELECTED] = g_signal_new ("selected", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (EMFolderSelectionButtonClass, selected), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +init (EMFolderSelectionButton *folder_selection_button) +{ + EMFolderSelectionButtonPrivate *priv; + GtkWidget *box; + + priv = g_new0 (EMFolderSelectionButtonPrivate, 1); + folder_selection_button->priv = priv; + + box = gtk_hbox_new (FALSE, 4); + + priv->icon = gtk_image_new (); + gtk_box_pack_start (GTK_BOX (box), priv->icon, FALSE, TRUE, 0); + + priv->label = gtk_label_new (""); + gtk_label_set_justify (GTK_LABEL (priv->label), GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.0); + gtk_box_pack_start (GTK_BOX (box), priv->label, TRUE, TRUE, 0); + + gtk_widget_show_all (box); + gtk_container_add (GTK_CONTAINER (folder_selection_button), box); + + set_contents (folder_selection_button); +} + +GtkWidget * +em_folder_selection_button_new(const char *title, const char *caption) +{ + EMFolderSelectionButton *button = g_object_new (EM_TYPE_FOLDER_SELECTION_BUTTON, NULL); + + button->priv->title = g_strdup (title); + button->priv->caption = g_strdup (caption); + + return GTK_WIDGET (button); +} + + +void +em_folder_selection_button_set_selection(EMFolderSelectionButton *button, const char *uri) +{ + EMFolderSelectionButtonPrivate *p = button->priv; + + g_return_if_fail(EM_IS_FOLDER_SELECTION_BUTTON(button)); + + if (p->uri != uri) { + g_free(p->uri); + p->uri = g_strdup(uri); + } + + set_contents(button); +} + + +const char * +em_folder_selection_button_get_selection(EMFolderSelectionButton *button) +{ + g_return_val_if_fail (EM_IS_FOLDER_SELECTION_BUTTON (button), NULL); + + return button->priv->uri; +} + +E_MAKE_TYPE (em_folder_selection_button, "EMFolderSelectionButton", EMFolderSelectionButton, class_init, init, PARENT_TYPE) diff --git a/mail/em-folder-selection-button.h b/mail/em-folder-selection-button.h new file mode 100644 index 0000000000..1ab4eb461c --- /dev/null +++ b/mail/em-folder-selection-button.h @@ -0,0 +1,59 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* em-folder-selection-button.h + * + * Copyright (C) 2003 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ettore Perazzoli <ettore@ximian.com> + */ + +#ifndef _EM_FOLDER_SELECTION_BUTTON_H_ +#define _EM_FOLDER_SELECTION_BUTTON_H_ + +#include <gtk/gtkbutton.h> + +#define EM_TYPE_FOLDER_SELECTION_BUTTON (em_folder_selection_button_get_type ()) +#define EM_FOLDER_SELECTION_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EM_TYPE_FOLDER_SELECTION_BUTTON, EMFolderSelectionButton)) +#define EM_FOLDER_SELECTION_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EM_TYPE_FOLDER_SELECTION_BUTTON, EMFolderSelectionButtonClass)) +#define EM_IS_FOLDER_SELECTION_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EM_TYPE_FOLDER_SELECTION_BUTTON)) +#define EM_IS_FOLDER_SELECTION_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), EM_TYPE_FOLDER_SELECTION_BUTTON)) + +typedef struct _EMFolderSelectionButton EMFolderSelectionButton; +typedef struct _EMFolderSelectionButtonPrivate EMFolderSelectionButtonPrivate; +typedef struct _EMFolderSelectionButtonClass EMFolderSelectionButtonClass; + +struct _EMFolderSelectionButton { + GtkButton parent; + + EMFolderSelectionButtonPrivate *priv; +}; + +struct _EMFolderSelectionButtonClass { + GtkButtonClass parent_class; + + /* Signals. */ + + void (* selected) (EMFolderSelectionButton *button); +}; + +GType em_folder_selection_button_get_type (void); + +GtkWidget *em_folder_selection_button_new(const char *title, const char *caption); + +void em_folder_selection_button_set_selection(EMFolderSelectionButton *button, const char *uri); +const char *em_folder_selection_button_get_selection(EMFolderSelectionButton *button); + +#endif /* _EM_FOLDER_SELECTION_BUTTON_H_ */ diff --git a/mail/em-folder-selection.c b/mail/em-folder-selection.c new file mode 100644 index 0000000000..c65ad80fc7 --- /dev/null +++ b/mail/em-folder-selection.c @@ -0,0 +1,170 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* em-folder-selection.c - UI for selecting folders. + * + * Copyright (C) 2002 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ettore Perazzoli <ettore@ximian.com> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "em-folder-selection.h" + +#include "mail-component.h" +#include "mail-tools.h" + +#include "shell/e-folder-selection-dialog.h" + + +CamelFolder * +em_folder_selection_run_dialog (GtkWindow *parent_window, + const char *title, + const char *caption, + CamelFolder *default_folder) +{ + EStorageSet *storage_set = mail_component_peek_storage_set (mail_component_peek ()); + char *default_path = NULL; + CamelStore *default_store; + GtkWidget *dialog; + EFolder *selected_e_folder; + CamelFolder *selected_camel_folder; + int response; + + default_store = camel_folder_get_parent_store (default_folder); + if (default_store != NULL) { + EStorage *storage = mail_component_lookup_storage (mail_component_peek (), default_store); + + if (storage != NULL) { + default_path = g_strconcat ("/", + e_storage_get_name (storage), + "/", + camel_folder_get_full_name (default_folder), + NULL); + } + } + + /* EPFIXME: Allowed types? */ + dialog = e_folder_selection_dialog_new (storage_set, title, caption, default_path, NULL, FALSE); + g_free(default_path); + response = gtk_dialog_run (GTK_DIALOG (dialog)); + + if (response != GTK_RESPONSE_OK) { + gtk_widget_destroy (dialog); + return NULL; + } + + selected_e_folder = e_storage_set_get_folder (storage_set, + e_folder_selection_dialog_get_selected_path (E_FOLDER_SELECTION_DIALOG (dialog))); + if (selected_e_folder == NULL) { + gtk_widget_destroy (dialog); + return NULL; + } + + selected_camel_folder = mail_tool_uri_to_folder (e_folder_get_physical_uri (selected_e_folder), 0, NULL); + gtk_widget_destroy (dialog); + + return selected_camel_folder; +} + +/* FIXME: This isn't the way to do it, but then neither is the above, really ... */ +char * +em_folder_selection_run_dialog_uri(GtkWindow *parent_window, + const char *title, + const char *caption, + const char *default_folder_uri) +{ + EStorageSet *storage_set = mail_component_peek_storage_set (mail_component_peek ()); + char *default_path; + GtkWidget *dialog; + EFolder *selected_e_folder; + int response; + + default_path = e_storage_set_get_path_for_physical_uri(storage_set, default_folder_uri); + dialog = e_folder_selection_dialog_new (storage_set, title, caption, default_path, NULL, FALSE); + g_free(default_path); + response = gtk_dialog_run (GTK_DIALOG (dialog)); + + if (response != GTK_RESPONSE_OK) { + gtk_widget_destroy (dialog); + return NULL; + } + + selected_e_folder = e_storage_set_get_folder (storage_set, + e_folder_selection_dialog_get_selected_path (E_FOLDER_SELECTION_DIALOG (dialog))); + gtk_widget_destroy (dialog); + if (selected_e_folder == NULL) + return NULL; + + return g_strdup(e_folder_get_physical_uri(selected_e_folder)); +} + + +struct _select_folder_data { + void (*done)(const char *uri, void *data); + void *data; +}; + +static void +emfs_folder_selected(GtkWidget *w, const char *path, struct _select_folder_data *d) +{ + const char *uri = NULL; + EStorageSet *storage_set = mail_component_peek_storage_set (mail_component_peek ()); + EFolder *folder; + + folder = e_storage_set_get_folder(storage_set, path); + if (folder) + uri = e_folder_get_physical_uri(folder); + + gtk_widget_hide(w); + + d->done(uri, d->data); + + gtk_widget_destroy(w); +} + +static void +emfs_folder_cancelled(GtkWidget *w, struct _select_folder_data *d) +{ + gtk_widget_destroy(w); +} + +void +em_select_folder(GtkWindow *parent_window, const char *title, const char *text, const char *default_folder_uri, void (*done)(const char *uri, void *data), void *data) +{ + EStorageSet *storage_set = mail_component_peek_storage_set (mail_component_peek ()); + char *path; + GtkWidget *dialog; + struct _select_folder_data *d; + + d = g_malloc0(sizeof(*d)); + d->data = data; + d->done = done; + + if (default_folder_uri) + path = e_storage_set_get_path_for_physical_uri(storage_set, default_folder_uri); + else + path = NULL; + dialog = e_folder_selection_dialog_new(storage_set, title, text, path, NULL, TRUE); + g_free(path); + /* ugh, painful api ... */ + g_signal_connect(dialog, "folder_selected", G_CALLBACK(emfs_folder_selected), d); + g_signal_connect(dialog, "cancelled", G_CALLBACK(emfs_folder_cancelled), d); + g_object_set_data_full((GObject *)dialog, "emfs_data", d, g_free); + gtk_widget_show(dialog); +} diff --git a/mail/em-folder-selection.h b/mail/em-folder-selection.h new file mode 100644 index 0000000000..374e1eec3f --- /dev/null +++ b/mail/em-folder-selection.h @@ -0,0 +1,41 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* em-folder-selection.h - UI for selecting folders. + * + * Copyright (C) 2002 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ettore Perazzoli <ettore@ximian.com> + */ + +#ifndef EM_FOLDER_SELECTION_H +#define EM_FOLDER_SELECTION_H + +#include <camel/camel-folder.h> + +#include <gtk/gtkwindow.h> + +CamelFolder *em_folder_selection_run_dialog (GtkWindow *parent_window, + const char *title, + const char *caption, + CamelFolder *default_folder); +char *em_folder_selection_run_dialog_uri(GtkWindow *parent_window, + const char *title, + const char *caption, + const char *default_folder_uri); + +void em_select_folder(GtkWindow *parent_window, const char *title, const char *text, const char *default_folder_uri, void (*done)(const char *uri, void *data), void *data); + +#endif /* EM_FOLDER_SELECTION_H */ diff --git a/mail/em-folder-selector.c b/mail/em-folder-selector.c new file mode 100644 index 0000000000..3ccf00e497 --- /dev/null +++ b/mail/em-folder-selector.c @@ -0,0 +1,349 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- + * + * Copyright(C) 2000, 2001, 2002, 2003 Ximian, Inc. + * + * Authors: Ettore Perazzoli + * Michael Zucchi + * + * 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 "em-folder-selector.h" + +#include "shell/e-storage-set-view.h" +#include "shell/e-storage-set.h" + +#include <libgnome/gnome-i18n.h> + +#include <gal/util/e-util.h> +#include <gal/widgets/e-gui-utils.h> + +#include <gtk/gtkentry.h> +#include <gtk/gtkbox.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtksignal.h> +#include <gtk/gtkstock.h> + +#include <camel/camel-url.h> + +#include <string.h> + +#define PARENT_TYPE (gtk_dialog_get_type()) +static GtkDialogClass *parent_class = NULL; + +static gboolean +check_folder_type_valid(EMFolderSelector *emfs) +{ + const char *selected; + EFolder *folder; + + selected = e_storage_set_view_get_current_folder(emfs->essv); + if (selected == NULL) + return FALSE; + + folder = e_storage_set_get_folder(emfs->ess, selected); + if (folder == NULL) + return FALSE; + + return TRUE; +} + +#if 0 /* EPFIXME */ +static void +folder_creation_dialog_result_cb(EShell *shell, + EShellFolderCreationDialogResult result, + const char *path, + void *data) +{ + EMFolderSelector *dialog; + + dialog = EM_FOLDER_SELECTOR(data); + + if (result == E_SHELL_FOLDER_CREATION_DIALOG_RESULT_SUCCESS) + e_storage_set_view_set_current_folder(E_STORAGE_SET_VIEW(priv->storage_set_view), + path); +} +#endif + +static void +emfs_dispose(GObject *object) +{ + EMFolderSelector *emfs = (EMFolderSelector *)object; + + if (emfs->ess != NULL) { + g_object_unref(emfs->ess); + emfs->ess = NULL; + emfs->essv = NULL; + } + + (* G_OBJECT_CLASS(parent_class)->dispose)(object); +} + +static void +emfs_finalize(GObject *object) +{ + /*EMFolderSelector *emfs = (EMFolderSelector *)object;*/ + + (* G_OBJECT_CLASS(parent_class)->finalize)(object); +} + +static void +emfs_response(GtkDialog *dialog, int response) +{ + EMFolderSelector *emfs = (EMFolderSelector *)dialog; + const char *path; + + switch (response) { + case EM_FOLDER_SELECTOR_RESPONSE_NEW: + path = e_storage_set_view_get_current_folder(emfs->essv); + + printf("create new folder, default parent '%s'\n", path); + break; + } +} + +static void +emfs_class_init(EMFolderSelectorClass *klass) +{ + GObjectClass *object_class; + GtkDialogClass *dialog_class; + + parent_class = g_type_class_ref(PARENT_TYPE); + object_class = G_OBJECT_CLASS(klass); + dialog_class = GTK_DIALOG_CLASS(klass); + + object_class->dispose = emfs_dispose; + object_class->finalize = emfs_finalize; + + dialog_class->response = emfs_response; +} + +static void +emfs_init(EMFolderSelector *emfs) +{ + emfs->flags = 0; +} + +static void +folder_selected_cb(EStorageSetView *essv, const char *path, EMFolderSelector *emfs) +{ + if (check_folder_type_valid(emfs)) + gtk_dialog_set_response_sensitive(GTK_DIALOG(emfs), GTK_RESPONSE_OK, TRUE); + else + gtk_dialog_set_response_sensitive(GTK_DIALOG(emfs), GTK_RESPONSE_OK, FALSE); +} + +static void +double_click_cb(EStorageSetView *essv, int row, ETreePath path, int col, GdkEvent *event, EMFolderSelector *emfs) +{ + if (check_folder_type_valid(emfs)) { + /*g_signal_emit(emfs, signals[FOLDER_SELECTED], 0, + em_folder_selector_get_selected(emfs));*/ + printf("double clicked!\n"); + } +} + +void +em_folder_selector_construct(EMFolderSelector *emfs, EStorageSet *ess, guint32 flags, const char *title, const char *text) +{ + GtkWidget *scrolled_window; + GtkWidget *text_label; + + gtk_window_set_default_size(GTK_WINDOW(emfs), 350, 300); + gtk_window_set_modal(GTK_WINDOW(emfs), TRUE); + gtk_window_set_title(GTK_WINDOW(emfs), title); + gtk_container_set_border_width(GTK_CONTAINER(emfs), 6); + + emfs->flags = flags; + if (flags & EM_FOLDER_SELECTOR_CAN_CREATE) + gtk_dialog_add_buttons(GTK_DIALOG(emfs), GTK_STOCK_NEW, EM_FOLDER_SELECTOR_RESPONSE_NEW, NULL); + + gtk_dialog_add_buttons(GTK_DIALOG(emfs), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_OK, + NULL); + + gtk_dialog_set_response_sensitive(GTK_DIALOG(emfs), GTK_RESPONSE_OK, FALSE); + gtk_dialog_set_default_response(GTK_DIALOG(emfs), GTK_RESPONSE_OK); + + emfs->ess = ess; + g_object_ref(ess); + + emfs->essv = (EStorageSetView *)e_storage_set_create_new_view(ess, NULL); + e_storage_set_view_set_allow_dnd(emfs->essv, FALSE); + e_storage_set_view_enable_search(emfs->essv, TRUE); + + g_signal_connect(emfs->essv, "double_click", G_CALLBACK(double_click_cb), emfs); + g_signal_connect(emfs->essv, "folder_selected", G_CALLBACK(folder_selected_cb), emfs); + + scrolled_window = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_IN); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + + gtk_container_add(GTK_CONTAINER(scrolled_window), (GtkWidget *)emfs->essv); + + gtk_box_pack_end(GTK_BOX(GTK_DIALOG(emfs)->vbox), scrolled_window, TRUE, TRUE, 6); + gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(emfs)->vbox), 6); + + gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(emfs)->vbox), 6); + + gtk_widget_show((GtkWidget *)emfs->essv); + gtk_widget_show(scrolled_window); + + if (text != NULL) { + text_label = gtk_label_new(text); + gtk_label_set_justify(GTK_LABEL(text_label), GTK_JUSTIFY_LEFT); + gtk_widget_show(text_label); + + gtk_box_pack_end(GTK_BOX(GTK_DIALOG(emfs)->vbox), text_label, FALSE, TRUE, 6); + gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(emfs)->vbox), 6); + } + + GTK_WIDGET_SET_FLAGS((GtkWidget *)emfs->essv, GTK_CAN_FOCUS); + gtk_widget_grab_focus((GtkWidget *)emfs->essv); +} + +GtkWidget * +em_folder_selector_new(EStorageSet *ess, guint32 flags, const char *title, const char *text) +{ + EMFolderSelector *emfs; + + g_return_val_if_fail(E_IS_STORAGE_SET(ess), NULL); + + emfs = g_object_new(em_folder_selector_get_type(), NULL); + em_folder_selector_construct(emfs, ess, flags, title, text); + + return GTK_WIDGET(emfs); +} + +static void +emfs_create_name_changed(GtkEntry *entry, EMFolderSelector *emfs) +{ + int active; + + active = e_storage_set_view_get_current_folder(emfs->essv) != NULL + && emfs->name_entry->text_length > 0; + + gtk_dialog_set_response_sensitive((GtkDialog *)emfs, GTK_RESPONSE_OK, active); +} + +static void +emfs_create_name_activate(GtkEntry *entry, EMFolderSelector *emfs) +{ + printf("entry activated, woop\n"); +} + +GtkWidget * +em_folder_selector_create_new(EStorageSet *ess, guint32 flags, const char *title, const char *text) +{ + EMFolderSelector *emfs; + GtkWidget *hbox, *w; + + g_return_val_if_fail(E_IS_STORAGE_SET(ess), NULL); + + emfs = g_object_new(em_folder_selector_get_type(), NULL); + em_folder_selector_construct(emfs, ess, flags, title, text); + + hbox = gtk_hbox_new(FALSE, 0); + w = gtk_label_new_with_mnemonic(_("Folder _name")); + gtk_box_pack_start((GtkBox *)hbox, w, FALSE, FALSE, 6); + emfs->name_entry = (GtkEntry *)gtk_entry_new(); + g_signal_connect(emfs->name_entry, "changed", G_CALLBACK(emfs_create_name_changed), emfs); + g_signal_connect(emfs->name_entry, "activate", G_CALLBACK(emfs_create_name_activate), emfs); + gtk_box_pack_start((GtkBox *)hbox, (GtkWidget *)emfs->name_entry, TRUE, FALSE, 6); + gtk_widget_show_all(hbox); + + gtk_box_pack_start((GtkBox *)((GtkDialog *)emfs)->vbox, hbox, FALSE, TRUE, 0); + + return GTK_WIDGET(emfs); +} + +void +em_folder_selector_set_selected(EMFolderSelector *emfs, const char *path) +{ + e_storage_set_view_set_current_folder(emfs->essv, path); +} + +void +em_folder_selector_set_selected_uri(EMFolderSelector *emfs, const char *uri) +{ + const char *path; + + path = e_storage_set_get_path_for_physical_uri(emfs->ess, uri); + if (path) + e_storage_set_view_set_current_folder(emfs->essv, path); +} + +const char * +em_folder_selector_get_selected(EMFolderSelector *emfs) +{ + const char *path; + + path = e_storage_set_view_get_current_folder(emfs->essv); + if (emfs->name_entry) { + g_free(emfs->selected); + emfs->selected = g_strdup_printf("%s/%s", path, gtk_entry_get_text(emfs->name_entry)); + path = emfs->selected; + } + + return path; +} + +const char * +em_folder_selector_get_selected_uri(EMFolderSelector *emfs) +{ + const char *path; + EFolder *folder; + + path = e_storage_set_view_get_current_folder(emfs->essv); + if (path == NULL) { + printf("current folder is null?\n"); + return NULL; + } + + folder = e_storage_set_get_folder(emfs->ess, path); + if (folder == NULL) { + printf("path ok, but can't get folder?\n"); + return NULL; + } + + path = e_folder_get_physical_uri(folder); + if (path && emfs->name_entry) { + CamelURL *url; + char *newpath; + + url = camel_url_new(path, NULL); + newpath = g_strdup_printf("%s/%s", url->fragment?url->fragment:url->path, gtk_entry_get_text(emfs->name_entry)); + if (url->fragment) + camel_url_set_fragment(url, newpath); + else + camel_url_set_path(url, newpath); + g_free(emfs->selected_uri); + emfs->selected_uri = camel_url_to_string(url, 0); + camel_url_free(url); + path = emfs->selected_uri; + } + + return path; +} + +E_MAKE_TYPE(em_folder_selector, "EMFolderSelector", EMFolderSelector, emfs_class_init, emfs_init, PARENT_TYPE) diff --git a/mail/em-folder-selector.h b/mail/em-folder-selector.h new file mode 100644 index 0000000000..48fc6758b5 --- /dev/null +++ b/mail/em-folder-selector.h @@ -0,0 +1,96 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* e-folder-selection-dialog.h + * + * Copyright (C) 2000, 2001, 2002, 2003 Ximian, Inc. + * + * Authors: Ettore Perazzoli + * Michael Zucchi + * + * 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_FOLDER_SELECTOR_H +#define EM_FOLDER_SELECTOR_H + +#include <gtk/gtkdialog.h> + +#ifdef cplusplus +extern "C" { +#pragma } +#endif /* cplusplus */ + +#define EM_TYPE_FOLDER_SELECTOR (em_folder_selector_get_type ()) +#define EM_FOLDER_SELECTOR(obj) (GTK_CHECK_CAST ((obj), E_TYPEM_FOLDER_SELECTOR, EMFolderSelector)) +#define EM_FOLDER_SELECTOR_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), E_TYPEM_FOLDER_SELECTOR, EMFolderSelectorClass)) +#define EM_IS_FOLDER_SELECTOR(obj) (GTK_CHECK_TYPE ((obj), E_TYPEM_FOLDER_SELECTOR)) +#define EM_IS_FOLDER_SELECTOR_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((obj), E_TYPEM_FOLDER_SELECTOR)) + +typedef struct _EMFolderSelector EMFolderSelector; +typedef struct _EMFolderSelectorPrivate EMFolderSelectorPrivate; +typedef struct _EMFolderSelectorClass EMFolderSelectorClass; + +struct _EStorageSet; +struct _EStorageSetView; + +struct _EMFolderSelector { + GtkDialog parent; + + guint32 flags; + struct _EStorageSet *ess; + struct _EStorageSetView *essv; + + struct _GtkEntry *name_entry; + char *selected; + char *selected_uri; +}; + +struct _EMFolderSelectorClass { + GtkDialogClass parent_class; + +#if 0 + void (* folder_selected) (EMFolderSelector *folder_selection_dialog, + const char *path); + void (* cancelled) (EMFolderSelector *folder_selection_dialog); +#endif +}; + +enum { + EM_FOLDER_SELECTOR_CAN_CREATE = 1, +}; + +enum { + EM_FOLDER_SELECTOR_RESPONSE_NEW = 1, +}; + +GtkType em_folder_selector_get_type (void); +void em_folder_selector_construct(EMFolderSelector *, struct _EStorageSet *, guint32, const char *, const char *); +/* for selecting folders */ +GtkWidget *em_folder_selector_new (struct _EStorageSet *, guint32, const char *, const char *); + +/* for creating folders */ +GtkWidget *em_folder_selector_create_new(struct _EStorageSet *ess, guint32 flags, const char *title, const char *text); + +void em_folder_selector_set_selected (EMFolderSelector *emfs, const char *path); +void em_folder_selector_set_selected_uri(EMFolderSelector *emfs, const char *uri); + +const char *em_folder_selector_get_selected (EMFolderSelector *emfs); +const char *em_folder_selector_get_selected_uri(EMFolderSelector *emfs); + +#ifdef cplusplus +} +#endif /* cplusplus */ + +#endif /* EM_FOLDER_SELECTOR_H */ diff --git a/mail/em-folder-view.c b/mail/em-folder-view.c index 8099191b01..fac7d155d3 100644 --- a/mail/em-folder-view.c +++ b/mail/em-folder-view.c @@ -542,16 +542,52 @@ emfv_popup_undelete(GtkWidget *w, EMFolderView *emfv) em_folder_view_mark_selected(emfv, CAMEL_MESSAGE_DELETED, 0); } +struct _move_data { + EMFolderView *emfv; + GPtrArray *uids; + int delete; +}; + +static void +emfv_popup_move_cb(const char *uri, void *data) +{ + struct _move_data *d = data; + + if (uri) + mail_transfer_messages(d->emfv->folder, d->uids, d->delete, uri, 0, NULL, NULL); + else + em_utils_uids_free(d->uids); + + g_object_unref(d->emfv); + g_free(d); +} + static void emfv_popup_move(GtkWidget *w, EMFolderView *emfv) { - /* FIXME */ + struct _move_data *d; + + d = g_malloc(sizeof(*d)); + d->emfv = emfv; + g_object_ref(emfv); + d->uids = message_list_get_selected(emfv->list); + d->delete = TRUE; + + em_select_folder((GtkWidget *)emfv, _("Select folder"), NULL, NULL, emfv_popup_move_cb, d); } static void emfv_popup_copy(GtkWidget *w, EMFolderView *emfv) { - /* FIXME */ + struct _move_data *d; + + d = g_malloc(sizeof(*d)); + d->emfv = emfv; + g_object_ref(emfv); + d->uids = message_list_get_selected(emfv->list); + d->delete = FALSE; + + em_select_folder((GtkWidget *)emfv, _("Select folder"), NULL, NULL, emfv_popup_move_cb, d); } static void diff --git a/mail/em-format-html.c b/mail/em-format-html.c index 6d38eda3f5..f95624e655 100644 --- a/mail/em-format-html.c +++ b/mail/em-format-html.c @@ -62,6 +62,8 @@ #include <camel/camel-file-utils.h> #include <e-util/e-msgport.h> + +#include "mail-component.h" #include "mail-mt.h" #include "em-format-html.h" @@ -214,15 +216,15 @@ em_format_html_get_type(void) sizeof(EMFormatHTML), 0, (GInstanceInitFunc)efh_init }; - extern char *evolution_dir; + const char *base_directory = mail_component_peek_base_directory (mail_component_peek ()); 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); + path = alloca(strlen(base_directory)+16); + sprintf(path, "%s/cache", base_directory); 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); diff --git a/mail/em-marshal.list b/mail/em-marshal.list index 910bfb1b3d..0c8bfbbbfb 100644 --- a/mail/em-marshal.list +++ b/mail/em-marshal.list @@ -1 +1,2 @@ BOOLEAN:BOXED,POINTER,POINTER +NONE:POINTER diff --git a/mail/em-popup.c b/mail/em-popup.c index 69fd1d0b6d..74ba86c33b 100644 --- a/mail/em-popup.c +++ b/mail/em-popup.c @@ -353,7 +353,8 @@ em_popup_create_menu_once(EMPopup *emp, EMPopupTarget *target, guint32 hide_mask menu = em_popup_create_menu(emp, hide_mask, disable_mask); - g_signal_connect_swapped(menu, "selection_done", G_CALLBACK(em_popup_target_free), target); + if (target) + 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; diff --git a/mail/em-popup.h b/mail/em-popup.h index 877b28fc28..0ad1d0015c 100644 --- a/mail/em-popup.h +++ b/mail/em-popup.h @@ -69,6 +69,7 @@ enum _em_popup_target_t { EM_POPUP_TARGET_SELECT, EM_POPUP_TARGET_URI, EM_POPUP_TARGET_PART, + EM_POPUP_TARGET_FOLDER, }; /* Flags that describe a TARGET_SELECT */ @@ -103,6 +104,13 @@ enum { EM_POPUP_PART_IMAGE = 1<<1, }; +/* Flags that describe TARGET_FOLDER */ +enum { + EM_POPUP_FOLDER_LOCAL = 1<<0, + EM_POPUP_FOLDER_REMOTE = 1<<1, + EM_POPUP_FOLDER_VFOLDER = 1<<2, +}; + struct _EMPopupTarget { enum _em_popup_target_t type; guint32 mask; /* depends on type, see above */ @@ -118,6 +126,9 @@ struct _EMPopupTarget { char *mime_type; struct _CamelMimePart *part; } part; + struct { + char *folder_uri; + } folder; } data; }; diff --git a/mail/em-utils.c b/mail/em-utils.c index 49ef9af792..5b16d0cd7b 100644 --- a/mail/em-utils.c +++ b/mail/em-utils.c @@ -36,6 +36,7 @@ #include <filter/filter-editor.h> +#include "mail-component.h" #include "mail-mt.h" #include "mail-ops.h" #include "mail-tools.h" @@ -215,14 +216,14 @@ 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); + user = g_strdup_printf ("%s/filters.xml", + mail_component_peek_base_directory (mail_component_peek ())); rule_context_save ((RuleContext *) fc, user); g_free (user); } @@ -249,7 +250,7 @@ static const char *filter_source_names[] = { void em_utils_edit_filters (GtkWidget *parent) { - extern char *evolution_dir; + const char *base_directory = mail_component_peek_base_directory (mail_component_peek ()); char *user, *system; FilterContext *fc; @@ -259,7 +260,7 @@ em_utils_edit_filters (GtkWidget *parent) } fc = filter_context_new (); - user = g_strdup_printf ("%s/filters.xml", evolution_dir); + user = g_strdup_printf ("%s/filters.xml", base_directory); system = EVOLUTION_PRIVDATADIR "/filtertypes.xml"; rule_context_load ((RuleContext *) fc, system, user); g_free (user); @@ -773,10 +774,10 @@ generate_account_hash (void) return account_hash; } -static EDestination ** +static EABDestination ** em_utils_camel_address_to_destination (CamelInternetAddress *iaddr) { - EDestination *dest, **destv; + EABDestination *dest, **destv; int n, i, j; if (iaddr == NULL) @@ -785,14 +786,14 @@ em_utils_camel_address_to_destination (CamelInternetAddress *iaddr) if ((n = camel_address_length ((CamelAddress *) iaddr)) == 0) return NULL; - destv = g_malloc (sizeof (EDestination *) * (n + 1)); + destv = g_malloc (sizeof (EABDestination *) * (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); + dest = eab_destination_new (); + eab_destination_set_name (dest, name); + eab_destination_set_email (dest, addr); destv[j++] = dest; } @@ -813,7 +814,7 @@ reply_get_composer (GtkWidget *parent, CamelMimeMessage *message, EAccount *acco CamelInternetAddress *to, CamelInternetAddress *cc) { const char *message_id, *references; - EDestination **tov, **ccv; + EABDestination **tov, **ccv; EMsgComposer *composer; char *subject; @@ -1211,7 +1212,7 @@ post_reply_to_message (CamelFolder *folder, const char *uid, CamelMimeMessage *m const char *message_id, *references; CamelInternetAddress *to = NULL; GtkWidget *parent = user_data; - EDestination **tov = NULL; + EABDestination **tov = NULL; EMsgComposer *composer; char *subject, *url; EAccount *account; diff --git a/mail/em-utils.h b/mail/em-utils.h index 0114c86ae2..a30c9109fb 100644 --- a/mail/em-utils.h +++ b/mail/em-utils.h @@ -36,6 +36,7 @@ struct _GtkWindow; struct _CamelFolder; struct _CamelStream; struct _CamelMimeMessage; +struct _CamelMimePart; struct _GtkSelectionData; struct _GtkAdjustment; struct _EMsgComposer; diff --git a/mail/folder-browser-factory.c b/mail/folder-browser-factory.c index f0157bac9a..194396b062 100644 --- a/mail/folder-browser-factory.c +++ b/mail/folder-browser-factory.c @@ -118,14 +118,13 @@ control_destroy_cb (GtkObject *fb, GObject *control) } BonoboControl * -folder_browser_factory_new_control (const char *uri, - const GNOME_Evolution_Shell shell) +folder_browser_factory_new_control (const char *uri) { BonoboControl *control; GtkWidget *fb; #if 0 - if (!(fb = folder_browser_new (shell, uri))) + if (!(fb = folder_browser_new (uri))) return NULL; FOLDER_BROWSER (fb)->pref_master = TRUE; /* save UI settings changed in this FB */ diff --git a/mail/folder-browser-factory.h b/mail/folder-browser-factory.h index 2c858c9c56..109938c27e 100644 --- a/mail/folder-browser-factory.h +++ b/mail/folder-browser-factory.h @@ -15,8 +15,7 @@ #include "Evolution.h" #include "e-util/e-list.h" -BonoboControl *folder_browser_factory_new_control (const char *uri, - const GNOME_Evolution_Shell shell); +BonoboControl *folder_browser_factory_new_control (const char *uri); EList *folder_browser_factory_get_control_list (void); struct _EMFolderBrowser *folder_browser_factory_get_browser(const char *uri); diff --git a/mail/folder-browser-ui.c b/mail/folder-browser-ui.c new file mode 100644 index 0000000000..9ccdb2db79 --- /dev/null +++ b/mail/folder-browser-ui.c @@ -0,0 +1,816 @@ +/* -*- 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 new file mode 100644 index 0000000000..5c2bc1fa28 --- /dev/null +++ b/mail/folder-browser-ui.h @@ -0,0 +1,36 @@ +/* -*- 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 new file mode 100644 index 0000000000..6d7deb2bdb --- /dev/null +++ b/mail/folder-browser.c @@ -0,0 +1,2679 @@ +/* -*- 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-component.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) { + g_source_remove (folder_browser->seen_id); + folder_browser->seen_id = 0; + } + + if (folder_browser->loading_id != 0) { + g_source_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_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); + 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) +{ + RuleContext *search_context = mail_component_peek_search_context (mail_component_peek ()); + 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) + g_source_remove (fb->seen_id); + + if (msg && gconf_client_get_bool (gconf, "/apps/evolution/mail/display/mark_seen", NULL)) { + if (timeout > 0) + fb->seen_id = g_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) + g_source_remove (fb->loading_id); + + g_free (fb->new_uid); + fb->new_uid = g_strdup (uid); + + if (fb->preview_shown) + fb->loading_id = g_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 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); + + 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 new file mode 100644 index 0000000000..1b03ec9efb --- /dev/null +++ b/mail/folder-browser.h @@ -0,0 +1,192 @@ +/* -*- 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_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 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 62ddfc4d06..6e6ce85d2c 100644 --- a/mail/mail-account-gui.c +++ b/mail/mail-account-gui.c @@ -36,17 +36,21 @@ #include <e-util/e-account-list.h> #include <e-util/e-dialog-utils.h> -#include "evolution-folder-selector-button.h" +#include "em-folder-selection-button.h" #include "mail-account-gui.h" #include "mail-session.h" #include "mail-send-recv.h" #include "mail-signature-editor.h" +#include "mail-component.h" #include "mail-composer-prefs.h" #include "mail-config.h" #include "mail-ops.h" #include "mail-mt.h" #include "mail.h" +#include "e-storage.h" + + #define d(x) extern char *default_drafts_folder_uri, *default_sent_folder_uri; @@ -1060,16 +1064,13 @@ extract_values (MailAccountGuiService *source, GHashTable *extra_config, CamelUR } } - static void -folder_selected (EvolutionFolderSelectorButton *button, - GNOME_Evolution_Folder *corba_folder, - gpointer user_data) +folder_selected (EMFolderSelectionButton *button, gpointer user_data) { char **folder_name = user_data; g_free (*folder_name); - *folder_name = g_strdup (corba_folder->physicalUri); + *folder_name = g_strdup(em_folder_selection_button_get_selection(button)); } static void @@ -1080,14 +1081,12 @@ default_folders_clicked (GtkButton *button, gpointer user_data) /* Drafts folder */ g_free (gui->drafts_folder_uri); gui->drafts_folder_uri = g_strdup (default_drafts_folder_uri); - evolution_folder_selector_button_set_uri (EVOLUTION_FOLDER_SELECTOR_BUTTON (gui->drafts_folder_button), - gui->drafts_folder_uri); + em_folder_selection_button_set_selection((EMFolderSelectionButton *)gui->drafts_folder_button, gui->drafts_folder_uri); /* Sent folder */ g_free (gui->sent_folder_uri); gui->sent_folder_uri = g_strdup (default_sent_folder_uri); - evolution_folder_selector_button_set_uri (EVOLUTION_FOLDER_SELECTOR_BUTTON (gui->sent_folder_button), - gui->sent_folder_uri); + em_folder_selection_button_set_selection((EMFolderSelectionButton *)gui->sent_folder_button, gui->sent_folder_uri); } GtkWidget *mail_account_gui_folder_selector_button_new (char *widget_name, char *string1, char *string2, int int1, int int2); @@ -1097,7 +1096,7 @@ mail_account_gui_folder_selector_button_new (char *widget_name, char *string1, char *string2, int int1, int int2) { - return (GtkWidget *)g_object_new (EVOLUTION_TYPE_FOLDER_SELECTOR_BUTTON, NULL); + return (GtkWidget *)em_folder_selection_button_new(_("Select Folder"), NULL); } static gboolean @@ -1410,7 +1409,6 @@ prepare_signatures (MailAccountGui *gui) MailAccountGui * mail_account_gui_new (EAccount *account, MailAccountsTab *dialog) { - const char *allowed_types[] = { "mail/*", NULL }; MailAccountGui *gui; GtkWidget *button; @@ -1499,31 +1497,21 @@ mail_account_gui_new (EAccount *account, MailAccountsTab *dialog) /* Drafts folder */ gui->drafts_folder_button = GTK_BUTTON (glade_xml_get_widget (gui->xml, "drafts_button")); - g_signal_connect (gui->drafts_folder_button, "selected", - G_CALLBACK (folder_selected), &gui->drafts_folder_uri); + g_signal_connect (gui->drafts_folder_button, "selected", G_CALLBACK (folder_selected), &gui->drafts_folder_uri); if (account->drafts_folder_uri) gui->drafts_folder_uri = g_strdup (account->drafts_folder_uri); else gui->drafts_folder_uri = g_strdup (default_drafts_folder_uri); - evolution_folder_selector_button_construct (EVOLUTION_FOLDER_SELECTOR_BUTTON (gui->drafts_folder_button), - global_shell_client, - _("Select Folder"), - gui->drafts_folder_uri, - allowed_types); + em_folder_selection_button_set_selection((EMFolderSelectionButton *)gui->drafts_folder_button, gui->drafts_folder_uri); /* Sent folder */ gui->sent_folder_button = GTK_BUTTON (glade_xml_get_widget (gui->xml, "sent_button")); - g_signal_connect (gui->sent_folder_button, "selected", - G_CALLBACK (folder_selected), &gui->sent_folder_uri); + g_signal_connect (gui->sent_folder_button, "selected", G_CALLBACK (folder_selected), &gui->sent_folder_uri); if (account->sent_folder_uri) gui->sent_folder_uri = g_strdup (account->sent_folder_uri); else gui->sent_folder_uri = g_strdup (default_sent_folder_uri); - evolution_folder_selector_button_construct (EVOLUTION_FOLDER_SELECTOR_BUTTON (gui->sent_folder_button), - global_shell_client, - _("Select Folder"), - gui->sent_folder_uri, - allowed_types); + em_folder_selection_button_set_selection((EMFolderSelectionButton *)gui->sent_folder_button, gui->sent_folder_uri); /* Special Folders "Reset Defaults" button */ button = glade_xml_get_widget (gui->xml, "default_folders_button"); @@ -1842,18 +1830,21 @@ static void add_new_store (char *uri, CamelStore *store, void *user_data) { EAccount *account = user_data; - EvolutionStorage *storage; + MailComponent *component = mail_component_peek (); + EStorage *storage; if (store == NULL) return; + + /* EPFIXME: Strange refcounting semantics here?! */ - storage = mail_lookup_storage (store); + storage = mail_component_lookup_storage (component, store); if (storage) { /* store is already in the folder tree, so do nothing */ - bonobo_object_unref (BONOBO_OBJECT (storage)); + g_object_unref (storage); } else { /* store is *not* in the folder tree, so lets add it. */ - mail_add_storage (store, account->name, account->source->url); + mail_component_add_store (component, store, account->name); } } @@ -1968,7 +1959,7 @@ mail_account_gui_save (MailAccountGui *gui) #define sources_equal(old,new) (new->url && !strcmp (old->url, new->url)) if (!sources_equal (account->source, new->source)) { /* Remove the old storage from the folder-tree */ - mail_remove_storage_by_uri (account->source->url); + mail_component_remove_storage_by_uri (mail_component_peek (), account->source->url); } } diff --git a/mail/mail-accounts.c b/mail/mail-accounts.c index fb06efd6ba..b17652bb66 100644 --- a/mail/mail-accounts.c +++ b/mail/mail-accounts.c @@ -33,9 +33,11 @@ #include <gtk/gtktreeselection.h> #include "mail.h" +#include "mail-component.h" #include "mail-config.h" #include "mail-config-druid.h" #include "mail-account-editor.h" +#include "mail-ops.h" #include "mail-send-recv.h" #include "art/mark.xpm" @@ -248,8 +250,8 @@ account_delete_clicked (GtkButton *button, gpointer user_data) /* remove it from the folder-tree in the shell */ if (account->enabled && account->source && account->source->url) - mail_remove_storage_by_uri (account->source->url); - + mail_component_remove_storage_by_uri (mail_component_peek (), account->source->url); + /* remove it from the config file */ mail_config_remove_account (account); accounts = mail_config_get_accounts (); @@ -297,6 +299,7 @@ account_default_clicked (GtkButton *button, gpointer user_data) static void account_able_clicked (GtkButton *button, gpointer user_data) { + MailComponent *component = mail_component_peek (); MailAccountsTab *prefs = user_data; GtkTreeSelection *selection; EAccount *account = NULL; @@ -317,9 +320,11 @@ account_able_clicked (GtkButton *button, gpointer user_data) folder-tree, otherwise add it to the folder-tree */ if (account->source->url) { if (account->enabled) - mail_load_storage_by_uri (prefs->shell, account->source->url, account->name); + mail_component_load_storage_by_uri (component, + account->source->url, + account->name); else - mail_remove_storage_by_uri (account->source->url); + mail_component_remove_storage_by_uri (component, account->source->url); } mail_autoreceive_setup (); @@ -354,17 +359,18 @@ account_able_toggled (GtkCellRendererToggle *renderer, char *arg1, gpointer user gtk_tree_path_free (path); if (account) { + MailComponent *component = mail_component_peek (); + /* if the account got disabled, remove it from the folder-tree, otherwise add it to the folder-tree */ if (account->source->url) { if (account->enabled) - mail_load_storage_by_uri (prefs->shell, account->source->url, account->name); + mail_component_load_storage_by_uri (component, account->source->url, account->name); else - mail_remove_storage_by_uri (account->source->url); + mail_component_remove_storage_by_uri (component, account->source->url); } mail_autoreceive_setup (); - mail_config_write (); } } diff --git a/mail/mail-accounts.etspec b/mail/mail-accounts.etspec new file mode 100644 index 0000000000..22c09370b1 --- /dev/null +++ b/mail/mail-accounts.etspec @@ -0,0 +1,12 @@ +<ETableSpecification cursor-mode="line" draw-grid="false" draw-focus="true" selection-mode="single"> + <ETableColumn model_col= "0" _title="Enabled" pixbuf="enabled" expansion="0.0" minimum_width="18" resizable="false" cell="render_message_status" compare="integer" sortable="false"/> + + <ETableColumn model_col= "1" _title="Account name" expansion="1.6" minimum_width="32" resizable="true" cell="render_text" compare="string"/> + + <ETableColumn model_col= "2" _title="Protocol" expansion="0.8" minimum_width="32" resizable="true" cell="render_text" compare="string"/> + + <ETableState> + <column source="0"/> <column source="1"/> <column source="2"/> + <grouping> </grouping> + </ETableState> +</ETableSpecification> diff --git a/mail/mail-autofilter.c b/mail/mail-autofilter.c index ac116aea7d..9e98758700 100644 --- a/mail/mail-autofilter.c +++ b/mail/mail-autofilter.c @@ -51,7 +51,6 @@ #include "filter/filter-editor.h" #include "filter/filter-option.h" -extern char *evolution_dir; static void rule_match_recipients (RuleContext *context, FilterRule *rule, CamelInternetAddress *iaddr) @@ -313,7 +312,8 @@ filter_gui_add_from_message (CamelMimeMessage *msg, const char *source, int flag g_return_if_fail (msg != NULL); fc = filter_context_new (); - user = g_strdup_printf ("%s/filters.xml", evolution_dir); + user = g_strdup_printf ("%s/filters.xml", + mail_component_peek_base_directory (mail_component_peek ())); system = EVOLUTION_PRIVDATADIR "/filtertypes.xml"; rule_context_load ((RuleContext *)fc, system, user); rule = filter_rule_from_message (fc, msg, flags); @@ -334,7 +334,7 @@ mail_filter_rename_uri(CamelStore *store, const char *olduri, const char *newuri GList *changed; fc = filter_context_new (); - user = g_strdup_printf ("%s/filters.xml", evolution_dir); + user = g_strdup_printf ("%s/filters.xml", mail_component_peek_base_directory (mail_component_peek ())); system = EVOLUTION_PRIVDATADIR "/filtertypes.xml"; rule_context_load ((RuleContext *)fc, system, user); @@ -359,7 +359,7 @@ mail_filter_delete_uri(CamelStore *store, const char *uri) GList *deleted; fc = filter_context_new (); - user = g_strdup_printf ("%s/filters.xml", evolution_dir); + user = g_strdup_printf ("%s/filters.xml", mail_component_peek_base_directory (mail_component_peek ())); system = EVOLUTION_PRIVDATADIR "/filtertypes.xml"; rule_context_load ((RuleContext *)fc, system, user); diff --git a/mail/mail-callbacks.c b/mail/mail-callbacks.c new file mode 100644 index 0000000000..cfb657b542 --- /dev/null +++ b/mail/mail-callbacks.c @@ -0,0 +1,3228 @@ +/* -*- 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-component.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 +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", mail_component_peek_base_directory (mail_component_peek ())); + 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", mail_component_peek_base_directory (mail_component_peek ())); + 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->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 new file mode 100644 index 0000000000..6b2c4573c9 --- /dev/null +++ b/mail/mail-callbacks.h @@ -0,0 +1,144 @@ +/* -*- 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-component-factory.c b/mail/mail-component-factory.c new file mode 100644 index 0000000000..00087b7439 --- /dev/null +++ b/mail/mail-component-factory.c @@ -0,0 +1,104 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* mail-component-factory.c + * + * Authors: Ettore Perazzoli <ettore@ximian.com> + * + * Copyright (C) 2003 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "em-utils.h" +#include "evolution-composer.h" +#include "mail-accounts.h" +#include "mail-component.h" +#include "mail-composer-prefs.h" +#include "mail-config-druid.h" +#include "mail-config-factory.h" +#include "mail-config.h" +#include "mail-mt.h" +#include "mail-preferences.h" + +#include <bonobo-activation/bonobo-activation.h> +#include <bonobo/bonobo-shlib-factory.h> + +#include <string.h> + + +#define FACTORY_ID "OAFIID:GNOME_Evolution_Mail_Factory_2" + +#define COMPONENT_ID "OAFIID:GNOME_Evolution_Mail_Component_2" +#define COMPOSER_ID "OAFIID:GNOME_Evolution_Mail_Composer" +#define FOLDER_INFO_ID "OAFIID:GNOME_Evolution_FolderInfo" +#define MAIL_CONFIG_ID "OAFIID:GNOME_Evolution_MailConfig" +#define WIZARD_ID "OAFIID:GNOME_Evolution_Mail_Wizard" + + +/* EPFIXME: This stuff is here just to get it to compile, it should be moved + out of the way (was originally in component-factory.c). */ +EvolutionShellClient *global_shell_client = NULL; + +static BonoboObject * +factory (BonoboGenericFactory *factory, + const char *component_id, + void *closure) +{ + /* EPFIXME this is messy. The IDs are defined all over the place + without a logic... */ + + if (strcmp (component_id, COMPONENT_ID) == 0) { + MailComponent *component = mail_component_peek (); + + bonobo_object_ref (BONOBO_OBJECT (component)); + return BONOBO_OBJECT (component); + } else if (strcmp(component_id, MAIL_CONFIG_ID) == 0) { + return (BonoboObject *)g_object_new (evolution_mail_config_get_type (), NULL); + } else if (strcmp(component_id, WIZARD_ID) == 0) { + return evolution_mail_config_wizard_new(); + } else if (strcmp (component_id, MAIL_ACCOUNTS_CONTROL_ID) == 0 + || strcmp (component_id, MAIL_PREFERENCES_CONTROL_ID) == 0 + || strcmp (component_id, MAIL_COMPOSER_PREFS_CONTROL_ID) == 0) { + return mail_config_control_factory_cb (factory, component_id, CORBA_OBJECT_NIL); + } else if (strcmp(component_id, COMPOSER_ID) == 0) { + /* FIXME: how to remove need for callbacks, probably make the composer more tightly integrated with mail */ + 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; +} + +static Bonobo_Unknown +make_factory (PortableServer_POA poa, const char *iid, gpointer impl_ptr, CORBA_Environment *ev) +{ + static int init = 0; + + if (!init) { + mail_config_init (); + mail_msg_init (); + init = 1; + } + + return bonobo_shlib_factory_std (FACTORY_ID, poa, impl_ptr, factory, NULL, ev); +} + +static BonoboActivationPluginObject plugin_list[] = { + { FACTORY_ID, make_factory}, + { NULL } +}; + +const BonoboActivationPlugin Bonobo_Plugin_info = { + plugin_list, "Evolution Mail component factory" +}; diff --git a/mail/mail-component.c b/mail/mail-component.c new file mode 100644 index 0000000000..e4289eda19 --- /dev/null +++ b/mail/mail-component.c @@ -0,0 +1,1625 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* mail-component.c + * + * Copyright (C) 2003 Ximian Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ettore Perazzoli <ettore@ximian.com> + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include "e-storage.h" +#include "e-storage-set.h" +#include "e-storage-browser.h" +#include "e-storage-set-view.h" +#include "em-folder-selector.h" +#include "em-folder-selection.h" + +#include "folder-browser-factory.h" +#include "mail-config.h" +#include "mail-component.h" +#include "mail-folder-cache.h" +#include "mail-vfolder.h" +#include "mail-mt.h" +#include "mail-ops.h" +#include "mail-send-recv.h" +#include "mail-session.h" + +#include "em-popup.h" + +#include <gtk/gtklabel.h> + +#include <gal/e-table/e-tree.h> +#include <gal/e-table/e-tree-memory.h> + +#include <camel/camel.h> + +#include <bonobo/bonobo-control.h> +#include <bonobo/bonobo-widget.h> + + +#define PARENT_TYPE bonobo_object_get_type () +static BonoboObjectClass *parent_class = NULL; + +struct _MailComponentPrivate { + char *base_directory; + + MailAsyncEvent *async_event; + GHashTable *storages_hash; /* storage by store */ + + EFolderTypeRegistry *folder_type_registry; + EStorageSet *storage_set; + + RuleContext *search_context; + + char *context_path; /* current path for right-click menu */ + + CamelStore *local_store; +}; + +static int emc_tree_right_click(ETree *tree, gint row, ETreePath path, gint col, GdkEvent *event, MailComponent *component); + +/* Utility functions. */ + +/* EPFIXME: Eeek, this totally sucks. See comment in e-storage.h, + async_open_folder() should NOT be a signal. */ + +struct _StorageConnectedData { + EStorage *storage; + char *path; + EStorageDiscoveryCallback callback; + void *callback_data; +}; +typedef struct _StorageConnectedData StorageConnectedData; + +static void +storage_connected_callback (CamelStore *store, + CamelFolderInfo *info, + StorageConnectedData *data) +{ + EStorageResult result; + + if (info != NULL) + result = E_STORAGE_OK; + else + result = E_STORAGE_GENERICERROR; + + (* data->callback) (data->storage, result, data->path, data->callback_data); + + g_object_unref (data->storage); + g_free (data->path); + g_free (data); +} + +static void +storage_async_open_folder_callback (EStorage *storage, + const char *path, + EStorageDiscoveryCallback callback, + void *callback_data, + CamelStore *store) +{ + StorageConnectedData *storage_connected_data = g_new0 (StorageConnectedData, 1); + + g_object_ref (storage); + + storage_connected_data->storage = storage; + storage_connected_data->path = g_strdup (path); + storage_connected_data->callback = callback; + storage_connected_data->callback_data = callback_data; + + mail_note_store (store, NULL, storage, + (void *) storage_connected_callback, storage_connected_data); +} + +static void +add_storage (MailComponent *component, + const char *name, + CamelService *store, + CamelException *ex) +{ + EStorage *storage; + EFolder *root_folder; + + root_folder = e_folder_new (name, "noselect", ""); + storage = e_storage_new (name, root_folder); + e_storage_declare_has_subfolders(storage, "/", _("Connecting...")); + + camel_object_ref(store); + + g_object_set_data((GObject *)storage, "em-store", store); + g_hash_table_insert (component->priv->storages_hash, store, storage); + + g_signal_connect(storage, "async_open_folder", + G_CALLBACK (storage_async_open_folder_callback), store); + +#if 0 + /* EPFIXME these are not needed anymore. */ + g_signal_connect(storage, "create_folder", G_CALLBACK(storage_create_folder), store); + g_signal_connect(storage, "remove_folder", G_CALLBACK(storage_remove_folder), store); + g_signal_connect(storage, "xfer_folder", G_CALLBACK(storage_xfer_folder), store); +#endif + + e_storage_set_add_storage (component->priv->storage_set, storage); + + mail_note_store ((CamelStore *) store, NULL, storage, NULL, NULL); + + g_object_unref (storage); +} + +static void +load_accounts(MailComponent *component, EAccountList *accounts) +{ + EIterator *iter; + + /* Load each service (don't connect!). Check its provider and + * see if this belongs in the shell's folder list. If so, add + * it. + */ + + iter = e_list_get_iterator ((EList *) accounts); + while (e_iterator_is_valid (iter)) { + EAccountService *service; + EAccount *account; + const char *name; + + account = (EAccount *) e_iterator_get (iter); + service = account->source; + name = account->name; + + if (account->enabled && service->url != NULL) + mail_component_load_storage_by_uri (component, service->url, name); + + e_iterator_next (iter); + } + + g_object_unref (iter); +} + +static inline gboolean +type_is_mail (const char *type) +{ + return !strcmp (type, "mail") || !strcmp (type, "mail/public"); +} + +static inline gboolean +type_is_vtrash (const char *type) +{ + return !strcmp (type, "vtrash"); +} + +static void +storage_go_online (gpointer key, gpointer value, gpointer data) +{ + CamelStore *store = key; + CamelService *service = CAMEL_SERVICE (store); + + if (! (service->provider->flags & CAMEL_PROVIDER_IS_REMOTE) + || (service->provider->flags & CAMEL_PROVIDER_IS_EXTERNAL)) + return; + + if ((CAMEL_IS_DISCO_STORE (service) + && camel_disco_store_status (CAMEL_DISCO_STORE (service)) == CAMEL_DISCO_STORE_OFFLINE) + || service->status != CAMEL_SERVICE_DISCONNECTED) { + mail_store_set_offline (store, FALSE, NULL, NULL); + mail_note_store (store, NULL, NULL, NULL, NULL); + } +} + +static void +go_online (MailComponent *component) +{ + camel_session_set_online(session, TRUE); + mail_session_set_interactive(TRUE); + mail_component_storages_foreach(component, storage_go_online, NULL); +} + +static void +setup_search_context (MailComponent *component) +{ + MailComponentPrivate *priv = component->priv; + char *user = g_strdup_printf ("%s/evolution/searches.xml", g_get_home_dir ()); /* EPFIXME should be somewhere else. */ + char *system = g_strdup (EVOLUTION_PRIVDATADIR "/vfoldertypes.xml"); + + priv->search_context = rule_context_new (); + g_object_set_data_full (G_OBJECT (priv->search_context), "user", user, g_free); + g_object_set_data_full (G_OBJECT (priv->search_context), "system", system, g_free); + + rule_context_add_part_set (priv->search_context, "partset", filter_part_get_type (), + rule_context_add_part, rule_context_next_part); + + rule_context_add_rule_set (priv->search_context, "ruleset", filter_rule_get_type (), + rule_context_add_rule, rule_context_next_rule); + + rule_context_load (priv->search_context, system, user); +} + +/* Local store setup. */ +char *default_drafts_folder_uri; +CamelFolder *drafts_folder = NULL; +char *default_sent_folder_uri; +CamelFolder *sent_folder = NULL; +char *default_outbox_folder_uri; +CamelFolder *outbox_folder = NULL; +char *default_inbox_folder_uri; +CamelFolder *inbox_folder = NULL; + +static struct { + char *base; + char **uri; + CamelFolder **folder; +} default_folders[] = { + { "Inbox", &default_inbox_folder_uri, &inbox_folder }, + { "Drafts", &default_drafts_folder_uri, &drafts_folder }, + { "Outbox", &default_outbox_folder_uri, &outbox_folder }, + { "Sent", &default_sent_folder_uri, &sent_folder }, +}; + +static void +setup_local_store(MailComponent *component) +{ + MailComponentPrivate *p = component->priv; + CamelException ex; + char *store_uri; + int i; + + g_assert(p->local_store == NULL); + + /* EPFIXME It should use base_directory once we have moved it. */ + store_uri = g_strconcat("mbox:", g_get_home_dir(), "/.evolution/mail/local", NULL); + p->local_store = mail_component_load_storage_by_uri(component, store_uri, _("On this Computer")); + camel_object_ref(p->local_store); + + camel_exception_init (&ex); + for (i=0;i<sizeof(default_folders)/sizeof(default_folders[0]);i++) { + /* FIXME: should this uri be account relative? */ + *default_folders[i].uri = g_strdup_printf("%s#%s", store_uri, default_folders[i].base); + *default_folders[i].folder = camel_store_get_folder(p->local_store, default_folders[i].base, + CAMEL_STORE_FOLDER_CREATE, &ex); + camel_exception_clear(&ex); + } + + g_free(store_uri); +} + +/* EStorageBrowser callbacks. */ + +static BonoboControl * +create_noselect_control (void) +{ + GtkWidget *label; + + label = gtk_label_new (_("This folder cannot contain messages.")); + gtk_widget_show (label); + return bonobo_control_new (label); +} + +static GtkWidget * +create_view_callback (EStorageBrowser *browser, + const char *path, + void *unused_data) +{ + BonoboControl *control; + EFolder *folder; + const char *folder_type; + const char *physical_uri; + + folder = e_storage_set_get_folder (e_storage_browser_peek_storage_set (browser), path); + if (folder == NULL) { + g_warning ("No folder at %s", path); + return gtk_label_new ("(You should not be seeing this label)"); + } + + folder_type = e_folder_get_type_string (folder); + physical_uri = e_folder_get_physical_uri (folder); + + if (type_is_mail (folder_type)) { + const char *noselect; + CamelURL *url; + + url = camel_url_new (physical_uri, NULL); + noselect = url ? camel_url_get_param (url, "noselect") : NULL; + if (noselect && !strcasecmp (noselect, "yes")) + control = create_noselect_control (); + else + control = folder_browser_factory_new_control (physical_uri); + camel_url_free (url); + } else if (type_is_vtrash (folder_type)) { + if (!strncasecmp (physical_uri, "file:", 5)) + control = folder_browser_factory_new_control ("vtrash:file:/"); + else + control = folder_browser_factory_new_control (physical_uri); + } else + return NULL; + + if (!control) + return NULL; + + /* EPFIXME: This leaks the control. */ + return bonobo_widget_new_control_from_objref (BONOBO_OBJREF (control), CORBA_OBJECT_NIL); +} + +static void +browser_page_switched_callback (EStorageBrowser *browser, + GtkWidget *old_page, + GtkWidget *new_page, + BonoboControl *parent_control) +{ + if (BONOBO_IS_WIDGET (old_page)) { + BonoboControlFrame *control_frame = bonobo_widget_get_control_frame (BONOBO_WIDGET (old_page)); + + bonobo_control_frame_control_deactivate (control_frame); + } + + if (BONOBO_IS_WIDGET (new_page)) { + BonoboControlFrame *control_frame = bonobo_widget_get_control_frame (BONOBO_WIDGET (new_page)); + Bonobo_UIContainer ui_container = bonobo_control_get_remote_ui_container (parent_control, NULL); + + /* This is necessary because we are not embedding the folder browser control + directly; we are putting the folder browser control into a notebook which + is then exported to the shell as a control. So we need to forward the + notebook's UIContainer to the folder browser. */ + bonobo_control_frame_set_ui_container (control_frame, ui_container, NULL); + + bonobo_control_frame_control_activate (control_frame); + } +} + +/* GObject methods. */ + +static void +impl_dispose (GObject *object) +{ + MailComponentPrivate *priv = MAIL_COMPONENT (object)->priv; + + if (priv->storage_set != NULL) { + g_object_unref (priv->storage_set); + priv->storage_set = NULL; + } + + if (priv->folder_type_registry != NULL) { + g_object_unref (priv->folder_type_registry); + priv->folder_type_registry = NULL; + } + + if (priv->search_context != NULL) { + g_object_unref (priv->search_context); + priv->search_context = NULL; + } + + if (priv->local_store != NULL) { + camel_object_unref (CAMEL_OBJECT (priv->local_store)); + priv->local_store = NULL; + } + + (* G_OBJECT_CLASS (parent_class)->dispose) (object); +} + +static void +impl_finalize (GObject *object) +{ + MailComponentPrivate *priv = MAIL_COMPONENT (object)->priv; + + g_free (priv->base_directory); + + mail_async_event_destroy (priv->async_event); + + g_hash_table_destroy (priv->storages_hash); /* EPFIXME free the data within? */ + + if (mail_async_event_destroy (priv->async_event) == -1) { + g_warning("Cannot destroy async event: would deadlock"); + g_warning(" system may be unstable at exit"); + } + + g_free(priv->context_path); + g_free (priv); + + (* G_OBJECT_CLASS (parent_class)->finalize) (object); +} + + +/* Evolution::Component CORBA methods. */ + +static void +impl_createControls (PortableServer_Servant servant, + Bonobo_Control *corba_sidebar_control, + Bonobo_Control *corba_view_control, + CORBA_Environment *ev) +{ + MailComponent *mail_component = MAIL_COMPONENT (bonobo_object_from_servant (servant)); + MailComponentPrivate *priv = mail_component->priv; + EStorageBrowser *browser; + GtkWidget *tree_widget; + GtkWidget *view_widget; + BonoboControl *sidebar_control; + BonoboControl *view_control; + + browser = e_storage_browser_new (priv->storage_set, "/", create_view_callback, NULL); + + tree_widget = e_storage_browser_peek_tree_widget (browser); + view_widget = e_storage_browser_peek_view_widget (browser); + + gtk_widget_show (tree_widget); + gtk_widget_show (view_widget); + + sidebar_control = bonobo_control_new (tree_widget); + view_control = bonobo_control_new (view_widget); + + *corba_sidebar_control = CORBA_Object_duplicate (BONOBO_OBJREF (sidebar_control), ev); + *corba_view_control = CORBA_Object_duplicate (BONOBO_OBJREF (view_control), ev); + + g_signal_connect_object (browser, "page_switched", + G_CALLBACK (browser_page_switched_callback), view_control, 0); + + g_signal_connect(tree_widget, "right_click", G_CALLBACK(emc_tree_right_click), mail_component); +} + + +/* Initialization. */ + +static void +mail_component_class_init (MailComponentClass *class) +{ + POA_GNOME_Evolution_Component__epv *epv = &class->epv; + GObjectClass *object_class = G_OBJECT_CLASS (class); + + parent_class = g_type_class_peek_parent (class); + + object_class->dispose = impl_dispose; + object_class->finalize = impl_finalize; + + epv->createControls = impl_createControls; +} + +static void +mail_component_init (MailComponent *component) +{ + MailComponentPrivate *priv; + EAccountList *accounts; + + priv = g_new0 (MailComponentPrivate, 1); + component->priv = priv; + + /* EPFIXME: Move to a private directory. */ + /* EPFIXME: Create the directory. */ + priv->base_directory = g_build_filename (g_get_home_dir (), "evolution", NULL); + + /* EPFIXME: Turn into an object? */ + mail_session_init (priv->base_directory); + + priv->async_event = mail_async_event_new(); + priv->storages_hash = g_hash_table_new (NULL, NULL); + + priv->folder_type_registry = e_folder_type_registry_new (); + priv->storage_set = e_storage_set_new (priv->folder_type_registry); + +#if 0 /* EPFIXME TODO somehow */ + for (i = 0; i < sizeof (standard_folders) / sizeof (standard_folders[0]); i++) + *standard_folders[i].uri = g_strdup_printf ("file://%s/local/%s", evolution_dir, standard_folders[i].name); +#endif + setup_local_store (component); + + accounts = mail_config_get_accounts (); + load_accounts(component, accounts); + +#if 0 + /* EPFIXME? */ + mail_local_storage_startup (shell_client, evolution_dir); + mail_importer_init (shell_client); + + for (i = 0; i < sizeof (standard_folders) / sizeof (standard_folders[0]); i++) { + mail_msg_wait (mail_get_folder (*standard_folders[i].uri, CAMEL_STORE_FOLDER_CREATE, + got_folder, standard_folders[i].folder, mail_thread_new)); + } +#endif + + /* mail_autoreceive_setup (); EPFIXME keep it off for testing */ + + setup_search_context (component); + +#if 0 + /* EPFIXME this shouldn't be here. */ + if (mail_config_is_corrupt ()) { + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE, + _("Some of your mail settings seem corrupt, " + "please check that everything is in order.")); + g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog); + gtk_widget_show (dialog); + } +#endif + +#if 0 + /* EPFIXME if we nuke the summary this is not necessary anymore. */ + + /* Everything should be ready now */ + evolution_folder_info_notify_ready (); +#endif + + /* EPFIXME not sure about this. */ + go_online (component); +} + + +/* Public API. */ + +MailComponent * +mail_component_peek (void) +{ + static MailComponent *component = NULL; + + if (component == NULL) { + component = g_object_new (mail_component_get_type (), NULL); + + /* FIXME: this should all be initialised in a starutp routine, not from the peek function, + this covers much of the ::init method's content too */ + vfolder_load_storage(); + } + + return component; +} + + +const char * +mail_component_peek_base_directory (MailComponent *component) +{ + return component->priv->base_directory; +} + +RuleContext * +mail_component_peek_search_context (MailComponent *component) +{ + return component->priv->search_context; +} + + +void +mail_component_add_store (MailComponent *component, + CamelStore *store, + const char *name) +{ + CamelException ex; + + camel_exception_init (&ex); + + if (name == NULL) { + char *service_name; + + service_name = camel_service_get_name ((CamelService *) store, TRUE); + add_storage (component, service_name, (CamelService *) store, &ex); + g_free (service_name); + } else { + add_storage (component, name, (CamelService *) store, &ex); + } + + camel_exception_clear (&ex); +} + + +/** + * mail_component_load_storage_by_uri: + * @component: + * @uri: + * @name: + * + * + * + * Return value: Pointer to the newly added CamelStore. The caller is supposed + * to ref the object if it wants to store it. + **/ +CamelStore * +mail_component_load_storage_by_uri (MailComponent *component, + const char *uri, + const char *name) +{ + CamelException ex; + CamelService *store; + CamelProvider *prov; + + camel_exception_init (&ex); + + /* Load the service (don't connect!). Check its provider and + * see if this belongs in the shell's folder list. If so, add + * it. + */ + + prov = camel_session_get_provider (session, uri, &ex); + if (prov == NULL) { + /* EPFIXME: real error dialog */ + g_warning ("couldn't get service %s: %s\n", uri, + camel_exception_get_description (&ex)); + camel_exception_clear (&ex); + return NULL; + } + + if (!(prov->flags & CAMEL_PROVIDER_IS_STORAGE) || + (prov->flags & CAMEL_PROVIDER_IS_EXTERNAL)) + return NULL; + + store = camel_session_get_service (session, uri, CAMEL_PROVIDER_STORE, &ex); + if (store == NULL) { + /* EPFIXME: real error dialog */ + g_warning ("couldn't get service %s: %s\n", uri, + camel_exception_get_description (&ex)); + camel_exception_clear (&ex); + return NULL; + } + + if (name != NULL) { + add_storage (component, name, store, &ex); + } else { + char *service_name; + + service_name = camel_service_get_name (store, TRUE); + add_storage (component, service_name, store, &ex); + g_free (service_name); + } + + if (camel_exception_is_set (&ex)) { + /* EPFIXME: real error dialog */ + g_warning ("Cannot load storage: %s", + camel_exception_get_description (&ex)); + camel_exception_clear (&ex); + } + + camel_object_unref (CAMEL_OBJECT (store)); + return CAMEL_STORE (store); /* (Still has one ref in the hash.) */ +} + + +static void +store_disconnect (CamelStore *store, + void *event_data, + void *data) +{ + camel_service_disconnect (CAMEL_SERVICE (store), TRUE, NULL); + camel_object_unref (CAMEL_OBJECT (store)); +} + +void +mail_component_remove_storage (MailComponent *component, + CamelStore *store) +{ + MailComponentPrivate *priv = component->priv; + EStorage *storage; + + /* Because the storages_hash holds a reference to each store + * used as a key in it, none of them will ever be gc'ed, meaning + * any call to camel_session_get_{service,store} with the same + * URL will always return the same object. So this works. + */ + + storage = g_hash_table_lookup (priv->storages_hash, store); + if (!storage) + return; + + g_hash_table_remove (priv->storages_hash, store); + + /* so i guess potentially we could have a race, add a store while one + being removed. ?? */ + mail_note_store_remove (store); + + e_storage_set_remove_storage (priv->storage_set, storage); + + mail_async_event_emit(priv->async_event, MAIL_ASYNC_THREAD, (MailAsyncFunc) store_disconnect, store, NULL, NULL); +} + + +void +mail_component_remove_storage_by_uri (MailComponent *component, + const char *uri) +{ + CamelProvider *prov; + CamelService *store; + + prov = camel_session_get_provider (session, uri, NULL); + if (!prov) + return; + if (!(prov->flags & CAMEL_PROVIDER_IS_STORAGE) || + (prov->flags & CAMEL_PROVIDER_IS_EXTERNAL)) + return; + + store = camel_session_get_service (session, uri, CAMEL_PROVIDER_STORE, NULL); + if (store != NULL) { + mail_component_remove_storage (component, CAMEL_STORE (store)); + camel_object_unref (CAMEL_OBJECT (store)); + } +} + + +EStorage * +mail_component_lookup_storage (MailComponent *component, + CamelStore *store) +{ + EStorage *storage; + + /* Because the storages_hash holds a reference to each store + * used as a key in it, none of them will ever be gc'ed, meaning + * any call to camel_session_get_{service,store} with the same + * URL will always return the same object. So this works. + */ + + storage = g_hash_table_lookup (component->priv->storages_hash, store); + if (storage) + g_object_ref (storage); + + return storage; +} + + +int +mail_component_get_storage_count (MailComponent *component) +{ + return g_hash_table_size (component->priv->storages_hash); +} + + +EStorageSet * +mail_component_peek_storage_set (MailComponent *component) +{ + return component->priv->storage_set; +} + + +void +mail_component_storages_foreach (MailComponent *component, + GHFunc func, + void *data) +{ + g_hash_table_foreach (component->priv->storages_hash, func, data); +} + +extern struct _CamelSession *session; + +char *em_uri_from_camel(const char *curi) +{ + CamelURL *curl; + EAccount *account; + const char *uid, *path; + char *euri; + CamelProvider *provider; + + provider = camel_session_get_provider(session, curi, NULL); + if (provider == NULL) + return g_strdup(curi); + + curl = camel_url_new(curi, NULL); + if (curl == NULL) + return g_strdup(curi); + + account = mail_config_get_account_by_source_url(curi); + uid = (account == NULL)?"local@local":account->uid; + path = (provider->url_flags & CAMEL_URL_FRAGMENT_IS_PATH)?curl->fragment:curl->path; + if (path[0] == '/') + path++; + euri = g_strdup_printf("email://%s/%s", uid, path); + printf("em uri from camel '%s' -> '%s'\n", curi, euri); + + return euri; +} + +char *em_uri_to_camel(const char *euri) +{ + EAccountList *accounts; + const EAccount *account; + EAccountService *service; + CamelProvider *provider; + CamelURL *eurl, *curl; + char *uid, *curi; + + eurl = camel_url_new(euri, NULL); + if (eurl == NULL) + return g_strdup(euri); + + if (strcmp(eurl->protocol, "email") != 0) { + camel_url_free(eurl); + return g_strdup(euri); + } + + g_assert(eurl->user != NULL); + g_assert(eurl->host != NULL); + + if (strcmp(eurl->user, "local") == 0 && strcmp(eurl->host, "local") == 0) { + /* FIXME: needs to track real local store location */ + curi = g_strdup_printf("mbox:%s/.evolution/mail/local#%s", g_get_home_dir(), eurl->path); + camel_url_free(eurl); + return curi; + } + + uid = g_strdup_printf("%s@%s", eurl->user, eurl->host); + + accounts = mail_config_get_accounts(); + account = e_account_list_find(accounts, E_ACCOUNT_FIND_UID, uid); + g_free(uid); + + if (account == NULL) { + camel_url_free(eurl); + return g_strdup(euri); + } + + service = account->source; + provider = camel_session_get_provider(session, service->url, NULL); + + curl = camel_url_new(service->url, NULL); + if (provider->url_flags & CAMEL_URL_FRAGMENT_IS_PATH) + camel_url_set_fragment(curl, eurl->path); + else + camel_url_set_path(curl, eurl->path); + + curi = camel_url_to_string(curl, 0); + + camel_url_free(eurl); + camel_url_free(curl); + + printf("em uri to camel '%s' -> '%s'\n", euri, curi); + + return curi; +} + + +CamelFolder * +mail_component_get_folder_from_evomail_uri (MailComponent *component, + guint32 flags, + const char *evomail_uri, + CamelException *ex) +{ + CamelException local_ex; + EAccountList *accounts; + EIterator *iter; + const char *p; + const char *q; + const char *folder_name; + char *uid; + + camel_exception_init (&local_ex); + + if (strncmp (evomail_uri, "evomail:", 8) != 0) + return NULL; + + p = evomail_uri + 8; + while (*p == '/') + p ++; + + q = strchr (p, '/'); + if (q == NULL) + return NULL; + + uid = g_strndup (p, q - p); + folder_name = q + 1; + + /* since we have no explicit account for 'local' folders, make one up */ + if (strcmp(uid, "local") == 0) { + g_free(uid); + return camel_store_get_folder(component->priv->local_store, folder_name, flags, ex); + } + + accounts = mail_config_get_accounts (); + iter = e_list_get_iterator ((EList *) accounts); + while (e_iterator_is_valid (iter)) { + EAccount *account = (EAccount *) e_iterator_get (iter); + EAccountService *service = account->source; + CamelProvider *provider; + CamelStore *store; + + if (strcmp (account->uid, uid) != 0) + continue; + + provider = camel_session_get_provider (session, service->url, &local_ex); + if (provider == NULL) + goto fail; + + store = (CamelStore *) camel_session_get_service (session, service->url, CAMEL_PROVIDER_STORE, &local_ex); + if (store == NULL) + goto fail; + + g_free (uid); + return camel_store_get_folder (store, folder_name, flags, ex); + } + + fail: + camel_exception_clear (&local_ex); + g_free (uid); + return NULL; +} + + +char * +mail_component_evomail_uri_from_folder (MailComponent *component, + CamelFolder *folder) +{ + CamelStore *store = camel_folder_get_parent_store (folder); + EAccount *account; + char *service_url; + char *evomail_uri; + const char *uid; + + if (store == NULL) + return NULL; + + service_url = camel_service_get_url (CAMEL_SERVICE (store)); + account = mail_config_get_account_by_source_url (service_url); + + if (account == NULL) { + /* since we have no explicit account for 'local' folders, make one up */ + /* TODO: check the folder is really a local one, folder->parent_store == local_store? */ + uid = "local"; + /*g_free (service_url); + return NULL;*/ + } else { + uid = account->uid; + } + + evomail_uri = g_strconcat ("evomail:///", uid, "/", camel_folder_get_full_name (folder), NULL); + g_free (service_url); + + return evomail_uri; +} + + +BONOBO_TYPE_FUNC_FULL (MailComponent, GNOME_Evolution_Component, PARENT_TYPE, mail_component) + + +/* ********************************************************************** */ +#if 0 +static void +emc_popup_view(GtkWidget *w, MailComponent *mc) +{ + +} + +static void +emc_popup_open_new(GtkWidget *w, MailComponent *mc) +{ +} +#endif + +static void +em_copy_folders(CamelStore *tostore, const char *tobase, CamelStore *fromstore, const char *frombase, int delete) +{ + GString *toname, *fromname; + CamelFolderInfo *fi; + GList *pending = NULL; + guint32 flags = CAMEL_STORE_FOLDER_INFO_RECURSIVE; + CamelException *ex = camel_exception_new(); + int fromlen; + const char *tmp; + + if (camel_store_supports_subscriptions(fromstore)) + flags |= CAMEL_STORE_FOLDER_INFO_SUBSCRIBED; + + fi = camel_store_get_folder_info(fromstore, frombase, flags, ex); + if (camel_exception_is_set(ex)) + goto done; + + pending = g_list_append(pending, fi); + + toname = g_string_new(""); + fromname = g_string_new(""); + + tmp = strrchr(frombase, '/'); + if (tmp == NULL) + fromlen = 0; + else + fromlen = tmp-frombase; + + printf("top name is '%s'\n", fi->full_name); + + while (pending) { + CamelFolderInfo *info = pending->data; + + pending = g_list_remove_link(pending, pending); + while (info) { + CamelFolder *fromfolder, *tofolder; + GPtrArray *uids; + + if (info->child) + pending = g_list_append(pending, info->child); + g_string_printf(toname, "%s/%s", tobase, info->full_name + fromlen); + + printf("Copying from '%s' to '%s'\n", info->full_name, toname->str); + + /* This makes sure we create the same tree, e.g. from a nonselectable source */ + /* Not sure if this is really the 'right thing', e.g. for spool stores, but it makes the ui work */ + fromfolder = camel_store_get_folder(fromstore, info->full_name, 0, ex); + tofolder = camel_store_get_folder(tostore, toname->str, CAMEL_STORE_FOLDER_CREATE, ex); + if (tofolder == NULL) { + if (fromfolder) + camel_object_unref(fromfolder); + goto exception; + } + + if (fromfolder) { + uids = camel_folder_get_uids(fromfolder); + camel_folder_transfer_messages_to(fromfolder, uids, tofolder, NULL, FALSE, ex); + camel_folder_free_uids(fromfolder, uids); + + camel_object_unref(fromfolder); + } + camel_object_unref(tofolder); + if (camel_exception_is_set(ex)) + goto exception; + info = info->sibling; + } + } + + camel_store_free_folder_info(fromstore, fi); + +exception: + g_string_free(toname, TRUE); + g_string_free(fromname, TRUE); +done: + printf("exception: %s\n", ex->desc?ex->desc:"<none>"); + camel_exception_free(ex); +} + +struct _copy_folder_data { + MailComponent *mc; + int delete; +}; + +static void +emc_popup_copy_folder_selected(const char *uri, void *data) +{ + struct _copy_folder_data *d = data; + + if (uri == NULL) { + g_free(d); + return; + } + + if (uri) { + EFolder *folder = e_storage_set_get_folder(d->mc->priv->storage_set, d->mc->priv->context_path); + CamelException *ex = camel_exception_new(); + CamelStore *fromstore, *tostore; + char *tobase, *frombase; + CamelURL *url; + + printf("copying folder '%s' to '%s'\n", d->mc->priv->context_path, uri); + + fromstore = camel_session_get_store(session, e_folder_get_physical_uri(folder), ex); + frombase = strchr(d->mc->priv->context_path+1, '/')+1; + + tostore = camel_session_get_store(session, uri, ex); + url = camel_url_new(uri, NULL); + if (url->fragment) + tobase = url->fragment; + else + tobase = url->path; + + em_copy_folders(tostore, tobase, fromstore, frombase, d->delete); + + camel_url_free(url); + camel_exception_free(ex); + } + g_free(d); +} + +static void +emc_popup_copy(GtkWidget *w, MailComponent *mc) +{ + struct _copy_folder_data *d; + + d = g_malloc(sizeof(*d)); + d->mc = mc; + d->delete = 0; + em_select_folder(NULL, _("Select folder"), _("Select destination to copy folder into"), NULL, emc_popup_copy_folder_selected, d); +} + +static void +emc_popup_move(GtkWidget *w, MailComponent *mc) +{ + struct _copy_folder_data *d; + + d = g_malloc(sizeof(*d)); + d->mc = mc; + d->delete = 1; + em_select_folder(NULL, _("Select folder"), _("Select destination to move folder into"), NULL, emc_popup_copy_folder_selected, d); +} +static void +emc_popup_new_folder_create(EStorageSet *ess, EStorageResult result, void *data) +{ + printf("folder created %s\n", result == E_STORAGE_OK?"ok":"failed"); +} + +static void +emc_popup_new_folder_response(EMFolderSelector *emfs, guint response, MailComponent *mc) +{ + if (response == GTK_RESPONSE_OK) { + char *path, *tmp, *name, *full; + EStorage *storage; + CamelStore *store; + CamelException *ex; + + printf("Creating folder: %s (%s)\n", em_folder_selector_get_selected(emfs), + em_folder_selector_get_selected_uri(emfs)); + + path = g_strdup(em_folder_selector_get_selected(emfs)); + tmp = strchr(path+1, '/'); + *tmp++ = 0; + /* FIXME: camel_store_create_folder should just take full path names */ + full = g_strdup(tmp); + name = strrchr(tmp, '/'); + if (name == NULL) { + name = tmp; + tmp = ""; + } else + *name++ = 0; + + storage = e_storage_set_get_storage(mc->priv->storage_set, path+1); + store = g_object_get_data((GObject *)storage, "em-store"); + + printf("creating folder '%s' / '%s' on '%s'\n", tmp, name, path+1); + + ex = camel_exception_new(); + camel_store_create_folder(store, tmp, name, ex); + if (camel_exception_is_set(ex)) { + printf("Create failed: %s\n", ex->desc); + } else if (camel_store_supports_subscriptions(store)) { + camel_store_subscribe_folder(store, full, ex); + if (camel_exception_is_set(ex)) { + printf("Subscribe failed: %s\n", ex->desc); + } + } + + camel_exception_free(ex); + + g_free(full); + g_free(path); + + /* Blah, this should just use camel, we get better error reporting if we do too */ + /*e_storage_set_async_create_folder(mc->priv->storage_set, path, "mail", "", emc_popup_new_folder_create, mc);*/ + } + gtk_widget_destroy((GtkWidget *)emfs); +} + +static void +emc_popup_new_folder (GtkWidget *w, MailComponent *mc) +{ + GtkWidget *dialog; + + dialog = em_folder_selector_create_new(mc->priv->storage_set, 0, _("Create folder"), _("Specify where to create the folder:")); + em_folder_selector_set_selected((EMFolderSelector *)dialog, mc->priv->context_path); + g_signal_connect(dialog, "response", G_CALLBACK(emc_popup_new_folder_response), mc); + gtk_widget_show(dialog); +} + +static void +em_delete_rec(CamelStore *store, CamelFolderInfo *fi, CamelException *ex) +{ + while (fi) { + CamelFolder *folder; + + if (fi->child) + em_delete_rec(store, fi->child, ex); + if (camel_exception_is_set(ex)) + return; + + printf("deleting folder '%s'\n", fi->full_name); + + if (camel_store_supports_subscriptions(store)) + camel_store_unsubscribe_folder(store, fi->full_name, NULL); + + folder = camel_store_get_folder(store, fi->full_name, 0, NULL); + if (folder) { + GPtrArray *uids = camel_folder_get_uids(folder); + int i; + + camel_folder_freeze(folder); + for (i = 0; i < uids->len; i++) + camel_folder_delete_message(folder, uids->pdata[i]); + camel_folder_sync(folder, TRUE, NULL); + camel_folder_thaw(folder); + camel_folder_free_uids(folder, uids); + } + + camel_store_delete_folder(store, fi->full_name, ex); + if (camel_exception_is_set(ex)) + return; + fi = fi->sibling; + } +} + +static void +em_delete_folders(CamelStore *store, const char *base, CamelException *ex) +{ + CamelFolderInfo *fi; + guint32 flags = CAMEL_STORE_FOLDER_INFO_RECURSIVE; + + if (camel_store_supports_subscriptions(store)) + flags |= CAMEL_STORE_FOLDER_INFO_SUBSCRIBED; + + fi = camel_store_get_folder_info(store, base, flags, ex); + if (camel_exception_is_set(ex)) + return; + + em_delete_rec(store, fi, ex); + camel_store_free_folder_info(store, fi); +} + +static void +emc_popup_delete_response(GtkWidget *w, guint response, MailComponent *mc) +{ + gtk_widget_destroy(w); + + if (response == GTK_RESPONSE_OK) { + const char *path = strchr(mc->priv->context_path+1, '/')+1; + EFolder *folder = e_storage_set_get_folder(mc->priv->storage_set, mc->priv->context_path); + CamelException *ex = camel_exception_new(); + CamelStore *store; + + /* FIXME: need to hook onto store changed event and delete view as well, somewhere else tho */ + store = camel_session_get_store(session, e_folder_get_physical_uri(folder), ex); + if (camel_exception_is_set(ex)) + goto exception; + + em_delete_folders(store, path, ex); + if (!camel_exception_is_set(ex)) + goto noexception; + exception: + e_notice(NULL, GTK_MESSAGE_ERROR, + _("Could not delete folder: %s"), ex->desc); + noexception: + camel_exception_free(ex); + if (store) + camel_object_unref(store); + } +} + +static void +emc_popup_delete_folder(GtkWidget *w, MailComponent *mc) +{ + GtkWidget *dialog; + char *title; + const char *path = strchr(mc->priv->context_path+1, '/')+1; + EFolder *folder; + + folder = e_storage_set_get_folder(mc->priv->storage_set, mc->priv->context_path); + if (folder == NULL) + return; + + dialog = gtk_message_dialog_new(NULL, + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_NONE, + _("Really delete folder \"%s\" and all of its subfolders?"), path); + + gtk_dialog_add_button((GtkDialog *)dialog, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); + gtk_dialog_add_button((GtkDialog *)dialog, GTK_STOCK_DELETE, GTK_RESPONSE_OK); + + gtk_dialog_set_default_response((GtkDialog *)dialog, GTK_RESPONSE_OK); + gtk_container_set_border_width((GtkContainer *)dialog, 6); + gtk_box_set_spacing((GtkBox *)((GtkDialog *)dialog)->vbox, 6); + + title = g_strdup_printf(_("Delete \"%s\""), path); + gtk_window_set_title((GtkWindow *)dialog, title); + g_free(title); + + g_signal_connect(dialog, "response", G_CALLBACK(emc_popup_delete_response), mc); + gtk_widget_show(dialog); +} + +static void +emc_popup_rename_folder(GtkWidget *w, MailComponent *mc) +{ + char *prompt, *new; + EFolder *folder; + const char *old, *why; + int done = 0; + + folder = e_storage_set_get_folder(mc->priv->storage_set, mc->priv->context_path); + if (folder == NULL) + return; + + old = e_folder_get_name(folder); + prompt = g_strdup_printf (_("Rename the \"%s\" folder to:"), e_folder_get_name(folder)); + while (!done) { + new = e_request_string(NULL, _("Rename Folder"), prompt, old); + if (new == NULL || strcmp(old, new) == 0) + done = 1; +#if 0 + else if (!e_shell_folder_name_is_valid(new, &why)) + e_notice(NULL, GTK_MESSAGE_ERROR, _("The specified folder name is not valid: %s"), why); +#endif + else { + char *base, *path; + + /* FIXME: we can't use the os independent path crap here, since we want to control the format */ + base = g_path_get_dirname(mc->priv->context_path); + path = g_build_filename(base, new, NULL); + + if (e_storage_set_get_folder(mc->priv->storage_set, path) != NULL) { + e_notice(NULL, GTK_MESSAGE_ERROR, + _("A folder named \"%s\" already exists. Please use a different name."), new); + } else { + CamelStore *store; + CamelException *ex = camel_exception_new(); + const char *oldpath, *newpath; + + oldpath = strchr(mc->priv->context_path+1, '/'); + g_assert(oldpath); + newpath = strchr(path+1, '/'); + g_assert(newpath); + oldpath++; + newpath++; + + printf("renaming %s to %s\n", oldpath, newpath); + + store = camel_session_get_store(session, e_folder_get_physical_uri(folder), ex); + if (camel_exception_is_set(ex)) + goto exception; + + camel_store_rename_folder(store, oldpath, newpath, ex); + if (!camel_exception_is_set(ex)) + goto noexception; + + exception: + e_notice(NULL, GTK_MESSAGE_ERROR, + _("Could not rename folder: %s"), ex->desc); + noexception: + if (store) + camel_object_unref(store); + camel_exception_free(ex); + + done = 1; + } + g_free(path); + g_free(base); + } + g_free(new); + } +} + +struct _prop_data { + void *object; + CamelArgV *argv; + GtkWidget **widgets; +}; + +static void +emc_popup_properties_response(GtkWidget *dialog, int response, struct _prop_data *prop_data) +{ + int i; + CamelArgV *argv = prop_data->argv; + + if (response != GTK_RESPONSE_OK) { + gtk_widget_destroy(dialog); + return; + } + + for (i=0;i<argv->argc;i++) { + CamelArg *arg = &argv->argv[i]; + + switch (arg->tag & CAMEL_ARG_TYPE) { + case CAMEL_ARG_BOO: + arg->ca_int = gtk_toggle_button_get_active(prop_data->widgets[i]); + break; + case CAMEL_ARG_STR: + g_free(arg->ca_str); + arg->ca_str = gtk_entry_get_text(prop_data->widgets[i]); + break; + default: + printf("unknown property type set\n"); + } + } + + camel_object_setv(prop_data->object, NULL, argv); + gtk_widget_destroy(dialog); +} + +static void +emc_popup_properties_free(void *data) +{ + struct _prop_data *prop_data = data; + int i; + + for (i=0; i<prop_data->argv->argc; i++) { + if ((prop_data->argv->argv[i].tag & CAMEL_ARG_TYPE) == CAMEL_ARG_STR) + g_free(prop_data->argv->argv[i].ca_str); + } + camel_object_unref(prop_data->object); + g_free(prop_data->argv); + g_free(prop_data); +} + +static void +emc_popup_properties_got_folder(const char *uri, CamelFolder *folder, void *data) +{ + MailComponent *mc = data; + + if (folder) { + GtkWidget *dialog, *w, *table, *label; + GSList *list, *l; + char *name, *txt; + int row = 1; + gint32 count, i; + struct _prop_data *prop_data; + CamelArgV *argv; + CamelArgGetV *arggetv; + + camel_object_get(folder, NULL, CAMEL_FOLDER_PROPERTIES, &list, CAMEL_FOLDER_NAME, &name, NULL); + + dialog = gtk_dialog_new_with_buttons(_("Folder properties"), + NULL, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, + GTK_RESPONSE_OK, + NULL); + + /* TODO: maybe we want some basic properties here, like message counts/approximate size/etc */ + w = gtk_frame_new(_("Properties")); + gtk_box_pack_start(((GtkDialog *)dialog)->vbox, w, TRUE, TRUE, 6); + table = gtk_table_new(g_slist_length(list)+1, 2, FALSE); + gtk_container_add((GtkContainer *)w, table); + label = gtk_label_new(_("Folder Name")); + gtk_misc_set_alignment(label, 1.0, 0.5); + gtk_table_attach(table, label, 0, 1, 0, 1, GTK_FILL|GTK_EXPAND, 0, 3, 0); + label = gtk_label_new(name); + gtk_misc_set_alignment(label, 0.0, 0.5); + gtk_table_attach(table, label, 1, 2, 0, 1, GTK_FILL|GTK_EXPAND, 0, 3, 0); + + /* build an arggetv/argv to retrieve/store the results */ + count = g_slist_length(list); + arggetv = g_malloc0(sizeof(*arggetv) + (count - CAMEL_ARGV_MAX) * sizeof(arggetv->argv[0])); + arggetv->argc = count; + argv = g_malloc0(sizeof(*argv) + (count - CAMEL_ARGV_MAX) * sizeof(argv->argv[0])); + argv->argc = count; + i = 0; + l = list; + while (l) { + CamelProperty *prop = l->data; + + argv->argv[i].tag = prop->tag; + arggetv->argv[i].tag = prop->tag; + arggetv->argv[i].ca_ptr = &argv->argv[i].ca_ptr; + + l = l->next; + i++; + } + camel_object_getv(folder, NULL, arggetv); + g_free(arggetv); + + prop_data = g_malloc0(sizeof(*prop_data)); + prop_data->widgets = g_malloc0(sizeof(prop_data->widgets[0]) * count); + prop_data->argv = argv; + + /* setup the ui with the values retrieved */ + l = list; + i = 0; + while (l) { + CamelProperty *prop = l->data; + + switch (prop->tag & CAMEL_ARG_TYPE) { + case CAMEL_ARG_BOO: + w = gtk_check_button_new_with_label(prop->description); + gtk_toggle_button_set_active((GtkToggleButton *)w, argv->argv[i].ca_int != 0); + gtk_table_attach(table, w, 0, 2, row, row+1, 0, 0, 3, 3); + prop_data->widgets[i] = w; + break; + case CAMEL_ARG_STR: + label = gtk_label_new(prop->description); + gtk_misc_set_alignment(label, 1.0, 0.5); + gtk_table_attach(table, label, 0, 1, row, row+1, GTK_FILL|GTK_EXPAND, 0, 3, 3); + + w = gtk_entry_new(); + if (argv->argv[i].ca_str) { + gtk_entry_set_text((GtkEntry *)w, txt); + camel_object_free(folder, argv->argv[i].tag, argv->argv[i].ca_str); + argv->argv[i].ca_str = NULL; + } + gtk_table_attach(table, w, 1, 2, row, row+1, GTK_FILL, 0, 3, 3); + prop_data->widgets[i] = w; + break; + default: + w = gtk_label_new("CamelFolder error: unsupported propery type"); + gtk_table_attach(table, w, 0, 2, row, row+1, 0, 0, 3, 3); + break; + } + + row++; + l = l->next; + } + + prop_data->object = folder; + camel_object_ref(folder); + + camel_object_free(folder, CAMEL_FOLDER_PROPERTIES, list); + camel_object_free(folder, CAMEL_FOLDER_NAME, name); + + /* we do 'apply on ok' ... since instant apply may apply some very long running tasks */ + + g_signal_connect(dialog, "response", G_CALLBACK(emc_popup_properties_response), prop_data); + g_object_set_data_full((GObject *)dialog, "e-prop-data", prop_data, emc_popup_properties_free); + gtk_widget_show_all(dialog); + } +} + +static void +emc_popup_properties(GtkWidget *w, MailComponent *mc) +{ + EFolder *efolder; + + /* TODO: Make sure we only have one dialog open for any given folder */ + + efolder = e_storage_set_get_folder(mc->priv->storage_set, mc->priv->context_path); + if (efolder == NULL) + return; + + mail_get_folder(e_folder_get_physical_uri(efolder), 0, emc_popup_properties_got_folder, mc, mail_thread_new); +} + +static EMPopupItem emc_popup_menu[] = { +#if 0 + { EM_POPUP_ITEM, "00.emc.00", N_("_View"), G_CALLBACK(emc_popup_view), NULL, NULL, 0 }, + { EM_POPUP_ITEM, "00.emc.01", N_("Open in _New Window"), G_CALLBACK(emc_popup_open_new), NULL, NULL, 0 }, + + { EM_POPUP_BAR, "10.emc" }, +#endif + { EM_POPUP_ITEM, "10.emc.00", N_("_Copy"), G_CALLBACK(emc_popup_copy), NULL, "folder-copy-16.png", 0 }, + { EM_POPUP_ITEM, "10.emc.01", N_("_Move"), G_CALLBACK(emc_popup_move), NULL, "folder-move-16.png", 0 }, + + { EM_POPUP_BAR, "20.emc" }, + { EM_POPUP_ITEM, "20.emc.00", N_("_New Folder..."), G_CALLBACK(emc_popup_new_folder), NULL, "folder-mini.png", 0 }, + { EM_POPUP_ITEM, "20.emc.01", N_("_Delete"), G_CALLBACK(emc_popup_delete_folder), NULL, "evolution-trash-mini.png", 0 }, + { EM_POPUP_ITEM, "20.emc.01", N_("_Rename"), G_CALLBACK(emc_popup_rename_folder), NULL, NULL, 0 }, + + { EM_POPUP_BAR, "80.emc" }, + { EM_POPUP_ITEM, "80.emc.00", N_("_Properties..."), G_CALLBACK(emc_popup_properties), NULL, "configure_16_folder.xpm", 0 }, +}; + + +static int +emc_tree_right_click(ETree *tree, gint row, ETreePath path, gint col, GdkEvent *event, MailComponent *component) +{ + char *name; + ETreeModel *model = e_tree_get_model(tree); + EMPopup *emp; + int i; + GSList *menus = NULL; + struct _GtkMenu *menu; + + name = e_tree_memory_node_get_data((ETreeMemory *)model, path); + g_free(component->priv->context_path); + component->priv->context_path = g_strdup(name); + printf("right click, path = '%s'\n", name); + + emp = em_popup_new("com.ximian.mail.storageset.popup.select"); + + for (i=0;i<sizeof(emc_popup_menu)/sizeof(emc_popup_menu[0]);i++) { + EMPopupItem *item = &emc_popup_menu[i]; + + item->activate_data = component; + menus = g_slist_prepend(menus, item); + } + + em_popup_add_items(emp, menus, (GDestroyNotify)g_slist_free); + + menu = em_popup_create_menu_once(emp, NULL, 0, 0); + + if (event == NULL || event->type == GDK_KEY_PRESS) { + /* FIXME: menu pos function */ + gtk_menu_popup(menu, NULL, NULL, NULL, NULL, 0, event->key.time); + } else { + gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event->button.button, event->button.time); + } + + return TRUE; +} diff --git a/mail/mail-component.h b/mail/mail-component.h new file mode 100644 index 0000000000..3e832752df --- /dev/null +++ b/mail/mail-component.h @@ -0,0 +1,99 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* mail-component.h + * + * Copyright (C) 2003 Ximian Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * Michael Zucchi <notzed@ximian.com> + * Jeffrey Stedfast <fejj@ximian.com> + * Ettore Perazzoli <ettore@ximian.com> + */ + +#ifndef _MAIL_COMPONENT_H_ +#define _MAIL_COMPONENT_H_ + +#include <bonobo/bonobo-object.h> + +#include <camel/camel-store.h> + +#include "e-storage-set.h" +#include "Evolution.h" + +#include "filter/rule-context.h" + + +#define MAIL_TYPE_COMPONENT (mail_component_get_type ()) +#define MAIL_COMPONENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MAIL_TYPE_COMPONENT, MailComponent)) +#define MAIL_COMPONENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MAIL_TYPE_COMPONENT, MailComponentClass)) +#define MAIL_IS_COMPONENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MAIL_TYPE_COMPONENT)) +#define MAIL_IS_COMPONENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), MAIL_TYPE_COMPONENT)) + + +typedef struct _MailComponent MailComponent; +typedef struct _MailComponentPrivate MailComponentPrivate; +typedef struct _MailComponentClass MailComponentClass; + +struct _MailComponent { + BonoboObject parent; + + MailComponentPrivate *priv; +}; + +struct _MailComponentClass { + BonoboObjectClass parent_class; + + POA_GNOME_Evolution_Component__epv epv; +}; + + +GType mail_component_get_type (void); + +MailComponent *mail_component_peek (void); + +const char *mail_component_peek_base_directory (MailComponent *component); +RuleContext *mail_component_peek_search_context (MailComponent *component); + +void mail_component_add_store (MailComponent *component, + CamelStore *store, + const char *name); +CamelStore *mail_component_load_storage_by_uri (MailComponent *component, + const char *uri, + const char *name); +void mail_component_remove_storage (MailComponent *component, + CamelStore *store); +void mail_component_remove_storage_by_uri (MailComponent *component, + const char *uri); +EStorage *mail_component_lookup_storage (MailComponent *component, + CamelStore *store); + +int mail_component_get_storage_count (MailComponent *component); +EStorageSet *mail_component_peek_storage_set (MailComponent *component); +void mail_component_storages_foreach (MailComponent *component, + GHFunc func, + void *data); + +char *em_uri_from_camel (const char *curi); +char *em_uri_to_camel (const char *euri); + +CamelFolder *mail_component_get_folder_from_evomail_uri (MailComponent *component, + guint32 flags, + const char *evomail_uri, + CamelException *ex); +char *mail_component_evomail_uri_from_folder (MailComponent *component, + CamelFolder *folder); + +#endif /* _MAIL_COMPONENT_H_ */ diff --git a/mail/mail-config-factory.c b/mail/mail-config-factory.c index 5a6db2cb47..50fddb9be9 100644 --- a/mail/mail-config-factory.c +++ b/mail/mail-config-factory.c @@ -34,8 +34,6 @@ #define CONFIG_CONTROL_FACTORY_ID "OAFIID:GNOME_Evolution_Mail_ConfigControlFactory" -static BonoboGenericFactory *factory = NULL; - typedef void (*ApplyFunc) (GtkWidget *prefs); diff --git a/mail/mail-config.c b/mail/mail-config.c index a357f74498..f75babcf36 100644 --- a/mail/mail-config.c +++ b/mail/mail-config.c @@ -53,7 +53,9 @@ #include <gal/widgets/e-gui-utils.h> #include <e-util/e-url.h> #include <e-util/e-passwords.h> + #include "mail.h" +#include "mail-component.h" #include "mail-config.h" #include "mail-mt.h" #include "mail-tools.h" @@ -448,6 +450,8 @@ config_write_style (void) * may not have been set yet * * filename = g_build_filename (evolution_dir, MAIL_CONFIG_RC, NULL); + * + * EPFIXME this kludge needs to go away. */ filename = g_build_filename (g_get_home_dir (), "evolution", MAIL_CONFIG_RC, NULL); @@ -528,6 +532,7 @@ mail_config_init (void) mail_config_clear (); /* + EPFIXME: This kludge needs to go away. filename = g_build_filename (evolution_dir, MAIL_CONFIG_RC, NULL); */ filename = g_build_filename (g_get_home_dir (), "evolution", MAIL_CONFIG_RC, NULL); @@ -933,6 +938,7 @@ mail_config_get_default_transport (void) static char * uri_to_evname (const char *uri, const char *prefix) { + const char *base_directory = mail_component_peek_base_directory (mail_component_peek ()); char *safe; char *tmp; @@ -940,9 +946,9 @@ uri_to_evname (const char *uri, const char *prefix) e_filename_make_safe (safe); /* blah, easiest thing to do */ if (prefix[0] == '*') - tmp = g_strdup_printf ("%s/%s%s.xml", evolution_dir, prefix + 1, safe); + tmp = g_strdup_printf ("%s/%s%s.xml", base_directory, prefix + 1, safe); else - tmp = g_strdup_printf ("%s/%s%s", evolution_dir, prefix, safe); + tmp = g_strdup_printf ("%s/%s%s", base_directory, prefix, safe); g_free (safe); return tmp; } @@ -1057,7 +1063,10 @@ mail_config_folder_to_cachename (CamelFolder *folder, const char *prefix) char *url, *filename; url = mail_config_folder_to_safe_url (folder); - filename = g_strdup_printf ("%s/config/%s%s", evolution_dir, prefix, url); + filename = g_strdup_printf ("%s/config/%s%s", + mail_component_peek_base_directory (mail_component_peek ()), + prefix, + url); g_free (url); return filename; @@ -1328,22 +1337,24 @@ mail_config_get_signature_list (void) static char * get_new_signature_filename (void) { + const char *base_directory; char *filename, *id; struct stat st; int i; - - filename = g_build_filename (evolution_dir, "/signatures", NULL); + + base_directory = mail_component_peek_base_directory (mail_component_peek ()); + filename = g_build_filename (base_directory, "signatures", NULL); if (lstat (filename, &st)) { if (errno == ENOENT) { if (mkdir (filename, 0700)) - g_warning ("Fatal problem creating %s/signatures directory.", evolution_dir); + g_warning ("Fatal problem creating %s directory.", filename); } else - g_warning ("Fatal problem with %s/signatures directory.", evolution_dir); + g_warning ("Fatal problem with %s directory.", filename); } g_free (filename); - filename = g_malloc (strlen (evolution_dir) + sizeof ("/signatures/signature-") + 12); - id = g_stpcpy (filename, evolution_dir); + filename = g_malloc (strlen (base_directory) + sizeof ("/signatures/signature-") + 12); + id = g_stpcpy (filename, base_directory); id = g_stpcpy (id, "/signatures/signature-"); for (i = 0; i < (INT_MAX - 1); i++) { @@ -1400,7 +1411,8 @@ delete_unused_signature_file (const char *filename) char *signatures_dir; int len; - signatures_dir = g_strconcat (evolution_dir, "/signatures", NULL); + signatures_dir = g_strconcat (mail_component_peek_base_directory (mail_component_peek ()), + "/signatures", NULL); /* remove signature file if it's in evolution dir and no other signature uses it */ len = strlen (signatures_dir); diff --git a/mail/mail-display-stream.c b/mail/mail-display-stream.c new file mode 100644 index 0000000000..ae81401680 --- /dev/null +++ b/mail/mail-display-stream.c @@ -0,0 +1,104 @@ +/* -*- 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-stream.h b/mail/mail-display-stream.h new file mode 100644 index 0000000000..943398c49a --- /dev/null +++ b/mail/mail-display-stream.h @@ -0,0 +1,62 @@ +/* -*- 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 MAIL_DISPLAY_STREAM_H +#define MAIL_DISPLAY_STREAM_H + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +#include <camel/camel-stream.h> +#include <gtkhtml/gtkhtml.h> + +#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)) + +typedef struct _MailDisplayStream { + CamelStream parent_stream; + + GtkHTML *html; + GtkHTMLStream *html_stream; +} MailDisplayStream; + +typedef struct { + CamelStreamClass parent_class; + +} MailDisplayStreamClass; + + +CamelType mail_display_stream_get_type (void); + +/* Note: stream does not ref these objects! */ +CamelStream *mail_display_stream_new (GtkHTML *html, GtkHTMLStream *html_stream); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* MAIL_DISPLAY_STREAM_H */ diff --git a/mail/mail-display.c b/mail/mail-display.c new file mode 100644 index 0000000000..6c6d9aaf2a --- /dev/null +++ b/mail/mail-display.c @@ -0,0 +1,2995 @@ +/* -*- 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-component.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) { + g_source_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) { + const char *base_directory = mail_component_peek_base_directory (mail_component_peek ()); + char *path; + + path = g_alloca (strlen (base_directory) + 16); + sprintf (path, "%s/cache", base_directory); + + /* 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) + g_source_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) + g_source_remove (pop->destroy_timeout); + + if (!pop->hidden) + pop->destroy_timeout = g_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 = g_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) + g_source_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); + 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 new file mode 100644 index 0000000000..fe95c95490 --- /dev/null +++ b/mail/mail-display.h @@ -0,0 +1,137 @@ +/* -*- 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-folder-cache.c b/mail/mail-folder-cache.c index c22d3011b0..3d4eb96d53 100644 --- a/mail/mail-folder-cache.c +++ b/mail/mail-folder-cache.c @@ -98,9 +98,7 @@ struct _store_info { CamelStore *store; /* the store for these folders */ - /* only 1 should be set */ - EvolutionStorage *storage; - GNOME_Evolution_Storage corba_storage; + EStorage *storage; /* Outstanding folderinfo requests */ EDList folderinfo_updates; @@ -109,7 +107,7 @@ struct _store_info { static void folder_changed(CamelObject *o, gpointer event_data, gpointer user_data); static void folder_renamed(CamelObject *o, gpointer event_data, gpointer user_data); static void folder_finalised(CamelObject *o, gpointer event_data, gpointer user_data); - +static void message_changed (CamelObject *o, gpointer event_data, gpointer user_data); static guint ping_id = 0; static gboolean ping_cb (gpointer user_data); @@ -188,9 +186,7 @@ real_flush_updates(void *o, void *event_data, void *data) { struct _folder_update *up; struct _store_info *si; - EvolutionStorage *storage; - GNOME_Evolution_Storage corba_storage; - CORBA_Environment ev; + EStorage *storage; time_t now; LOCK(info_lock); @@ -199,11 +195,9 @@ real_flush_updates(void *o, void *event_data, void *data) if (si) { storage = si->storage; if (storage) - bonobo_object_ref((BonoboObject *)storage); - corba_storage = si->corba_storage; + g_object_ref (storage); } else { storage = NULL; - corba_storage = CORBA_OBJECT_NIL; } UNLOCK(info_lock); @@ -214,7 +208,7 @@ real_flush_updates(void *o, void *event_data, void *data) mail_filter_delete_uri(up->store, up->uri); mail_config_uri_deleted(CAMEL_STORE_CLASS(CAMEL_OBJECT_GET_CLASS(up->store))->compare_folder_name, up->uri); if (up->unsub) - evolution_storage_removed_folder (storage, up->path); + e_storage_removed_folder (storage, up->path); } else mail_vfolder_add_uri(up->store, up->uri, TRUE); } else { @@ -222,7 +216,7 @@ real_flush_updates(void *o, void *event_data, void *data) if (up->oldpath) { if (storage != NULL) { d(printf("Removing old folder (rename?) '%s'\n", up->oldpath)); - evolution_storage_removed_folder(storage, up->oldpath); + e_storage_removed_folder(storage, up->oldpath); } /* ELSE? Shell supposed to handle the local snot case */ } @@ -237,24 +231,28 @@ real_flush_updates(void *o, void *event_data, void *data) } if (up->name == NULL) { - if (storage != NULL) { - d(printf("Updating existing folder: %s (%d unread)\n", up->path, up->unread)); - evolution_storage_update_folder(storage, up->path, up->unread); - } else if (corba_storage != CORBA_OBJECT_NIL) { - d(printf("Updating existing (local) folder: %s (%d unread)\n", up->path, up->unread)); - CORBA_exception_init(&ev); - GNOME_Evolution_Storage_updateFolder(corba_storage, up->path, up->unread, &ev); - CORBA_exception_free(&ev); + EFolder *folder = e_storage_get_folder (storage, up->path); + + if (folder != NULL) { + d(printf("updating unread count to '%s' to %d\n", up->path, up->unread)); + e_folder_set_unread_count (folder, up->unread); + } else { + g_warning ("No folder at %s ?!", up->path); } } else if (storage != NULL) { char *type = (strncmp(up->uri, "vtrash:", 7)==0)?"vtrash":"mail"; - + EFolder *new_folder = e_folder_new (up->name, type, NULL); + d(printf("Adding new folder: %s\n", up->path)); - evolution_storage_new_folder(storage, - up->path, up->name, type, up->uri, up->name, NULL, - up->unread, - CAMEL_IS_DISCO_STORE(up->store) - && camel_disco_store_can_work_offline((CamelDiscoStore *)up->store), 0); + + e_folder_set_physical_uri (new_folder, up->uri); + e_folder_set_unread_count (new_folder, up->unread); + if (CAMEL_IS_DISCO_STORE(up->store) && camel_disco_store_can_work_offline((CamelDiscoStore *)up->store)) + e_folder_set_can_sync_offline (new_folder, TRUE); + else + e_folder_set_can_sync_offline (new_folder, FALSE); + + e_storage_new_folder(storage, up->path, new_folder); } if (!up->olduri && up->add) @@ -279,9 +277,9 @@ real_flush_updates(void *o, void *event_data, void *data) notify_idle_id = g_idle_add_full (G_PRIORITY_LOW, notify_idle_cb, NULL, NULL); free_update(up); - - if (storage) - bonobo_object_unref((BonoboObject *)storage); + + if (storage != NULL) + g_object_unref (storage); LOCK(info_lock); } @@ -307,7 +305,7 @@ unset_folder_info(struct _folder_info *mfi, int delete, int unsub) CamelFolder *folder = mfi->folder; camel_object_unhook_event(folder, "folder_changed", folder_changed, mfi); - camel_object_unhook_event(folder, "message_changed", folder_changed, mfi); + camel_object_unhook_event(folder, "message_changed", message_changed, mfi); camel_object_unhook_event(folder, "renamed", folder_renamed, mfi); camel_object_unhook_event(folder, "finalize", folder_finalised, mfi); } @@ -907,17 +905,18 @@ store_online_cb (CamelStore *store, void *data) } void -mail_note_store(CamelStore *store, CamelOperation *op, EvolutionStorage *storage, GNOME_Evolution_Storage corba_storage, +mail_note_store(CamelStore *store, CamelOperation *op, EStorage *storage, void (*done)(CamelStore *store, CamelFolderInfo *info, void *data), void *data) { struct _store_info *si; struct _update_data *ud; const char *buf; guint timeout; + + g_return_if_fail (storage == NULL || E_IS_STORAGE (storage)); g_assert(CAMEL_IS_STORE(store)); g_assert(pthread_self() == mail_gui_thread); - g_assert(storage == NULL || corba_storage == CORBA_OBJECT_NIL); LOCK(info_lock); @@ -942,8 +941,7 @@ mail_note_store(CamelStore *store, CamelOperation *op, EvolutionStorage *storage CAMEL_STORE_CLASS(CAMEL_OBJECT_GET_CLASS(store))->compare_folder_name); si->storage = storage; if (storage != NULL) - bonobo_object_ref((BonoboObject *)storage); - si->corba_storage = corba_storage; + g_object_ref (storage); si->store = store; camel_object_ref((CamelObject *)store); g_hash_table_insert(stores, store, si); diff --git a/mail/mail-folder-cache.h b/mail/mail-folder-cache.h index 3d612d7d6b..b9f8f44dfb 100644 --- a/mail/mail-folder-cache.h +++ b/mail/mail-folder-cache.h @@ -25,14 +25,15 @@ #ifndef _MAIL_FOLDER_CACHE_H #define _MAIL_FOLDER_CACHE_H -#include <shell/evolution-storage.h> +#include "e-storage.h" /* Add a store whose folders should appear in the shell The folders are scanned from the store, and/or added at runtime via the folder_created event */ void -mail_note_store(CamelStore *store, CamelOperation *op, EvolutionStorage *storage, GNOME_Evolution_Storage corba_storage, - void (*done)(CamelStore *store, CamelFolderInfo *info, void *data), void *data); +mail_note_store(CamelStore *store, CamelOperation *op, EStorage *storage, + void (*done) (CamelStore *store, CamelFolderInfo *info, void *data), + void *data); /* de-note a store */ void mail_note_store_remove(CamelStore *store); @@ -41,7 +42,7 @@ void mail_note_store_remove(CamelStore *store); The folder must have already been created on the store (which has already been noted) before the folder can be opened */ -void mail_note_folder(struct _CamelFolder *folder); +void mail_note_folder(CamelFolder *folder); /* Returns true if a folder is available (yet), and also sets *folderp (if supplied) to a (referenced) copy of the folder if it has already been opened */ diff --git a/mail/mail-font-prefs.c b/mail/mail-font-prefs.c new file mode 100644 index 0000000000..0d26f1ccbd --- /dev/null +++ b/mail/mail-font-prefs.c @@ -0,0 +1,130 @@ +/* -*- 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 new file mode 100644 index 0000000000..14a1d20b40 --- /dev/null +++ b/mail/mail-font-prefs.h @@ -0,0 +1,66 @@ +/* -*- 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 new file mode 100644 index 0000000000..92428af592 --- /dev/null +++ b/mail/mail-format.c @@ -0,0 +1,2131 @@ +/* -*- 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 new file mode 100644 index 0000000000..e39148ca83 --- /dev/null +++ b/mail/mail-format.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 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 new file mode 100644 index 0000000000..2e3f517145 --- /dev/null +++ b/mail/mail-identify.c @@ -0,0 +1,130 @@ +/* -*- 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-importer.c b/mail/mail-importer.c index 193726aa1e..a915c04668 100644 --- a/mail/mail-importer.c +++ b/mail/mail-importer.c @@ -36,12 +36,12 @@ #include <camel/camel-exception.h> #include <e-util/e-path.h> +#include "mail-component.h" #include "mail-importer.h" #include "mail-local.h" #include "mail.h" static GList *importer_modules = NULL; -extern char *evolution_dir; static GNOME_Evolution_Storage local_storage = NULL; void mail_importer_uninit (void); @@ -95,7 +95,8 @@ mail_importer_make_local_folder(const char *folderpath) } else { struct _create_data data = { GNOME_Evolution_Storage_GENERIC_ERROR, FALSE }; - tmp = g_strdup_printf("file://%s/local", evolution_dir); + tmp = g_strdup_printf("file://%s/local", + mail_component_peek_base_directory (mail_component_peek ())); uri = e_path_to_physical(tmp, folderpath); g_free(tmp); tmp = strrchr(uri, '/'); diff --git a/mail/mail-local.c b/mail/mail-local.c index 0ec4f5a8e9..565e028432 100644 --- a/mail/mail-local.c +++ b/mail/mail-local.c @@ -533,10 +533,11 @@ mlf_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args) /* CamelObject args */ case CAMEL_OBJECT_ARG_DESCRIPTION: if (mlf->description == NULL) { + const char *base_directory = mail_component_peek_base_directory (mail_component_peek ()); int pathlen; /* string to describe a local folder as the location of a message */ - pathlen = strlen(evolution_dir) + strlen("local") + 1; + pathlen = strlen(base_directory) + strlen("local") + 1; if (strlen(folder->full_name) > pathlen) mlf->description = g_strdup_printf(_("Local folders/%s"), folder->full_name+pathlen); else diff --git a/mail/mail-mt.c b/mail/mail-mt.c index 603e55dd50..cb75fe93bf 100644 --- a/mail/mail-mt.c +++ b/mail/mail-mt.c @@ -28,8 +28,6 @@ #include "mail-session.h" #include "mail-mt.h" -#include "component-factory.h" - /*#define MALLOC_CHECK*/ #define LOG_OPS #define LOG_LOCKS @@ -911,10 +909,11 @@ static void do_op_status(struct _mail_msg *mm) what = msg->ops->describe_msg (msg, FALSE); else what = _("Working"); - + + /* EPFIXME: redo activity client stuff. */ if (global_shell_client) { activity = evolution_activity_client_new (global_shell_client, - COMPONENT_ID, + "evolution-mail", progress_icon, what, TRUE, &display); } else { diff --git a/mail/mail-offline-handler.c b/mail/mail-offline-handler.c index ccaff703f5..ce0bcf729f 100644 --- a/mail/mail-offline-handler.c +++ b/mail/mail-offline-handler.c @@ -27,9 +27,10 @@ #endif #include "mail-offline-handler.h" -#include "mail.h" +#include "mail-component.h" #include "mail-ops.h" #include "mail-folder-cache.h" +#include "mail.h" #include <gtk/gtkmain.h> @@ -77,10 +78,10 @@ create_connection_list (void) list = GNOME_Evolution_ConnectionList__alloc (); list->_length = 0; - list->_maximum = mail_storages_count (); + list->_maximum = mail_component_get_storage_count (mail_component_peek ()); list->_buffer = CORBA_sequence_GNOME_Evolution_Connection_allocbuf (list->_maximum); - mail_storages_foreach (add_connection, list); + mail_component_storages_foreach (mail_component_peek (), add_connection, list); return list; } @@ -260,7 +261,7 @@ impl_goOffline (PortableServer_Servant servant, /* FIXME: If send/receive active, wait for it to finish */ - mail_storages_foreach (storage_go_offline, progress_listener); + mail_component_storages_foreach (mail_component_peek (), storage_go_offline, progress_listener); } static void @@ -270,8 +271,7 @@ storage_go_online (gpointer key, gpointer value, gpointer data) if (service_is_relevant (CAMEL_SERVICE (store), FALSE)) { mail_store_set_offline (store, FALSE, NULL, NULL); - mail_note_store (store, NULL, NULL, CORBA_OBJECT_NIL, - NULL, NULL); + mail_note_store (store, NULL, NULL, NULL, NULL); } } @@ -288,7 +288,7 @@ impl_goOnline (PortableServer_Servant servant, /* Enable auto-mail-checking */ camel_session_set_online (session, TRUE); - mail_storages_foreach (storage_go_online, NULL); + mail_component_storages_foreach (mail_component_peek (), storage_go_online, NULL); } /* GObject methods. */ diff --git a/mail/mail-ops.c b/mail/mail-ops.c index 65cc8676c1..8d3711ff4e 100644 --- a/mail/mail-ops.c +++ b/mail/mail-ops.c @@ -38,6 +38,7 @@ #include <camel/camel-vtrash-folder.h> #include <camel/camel-vee-store.h> #include "mail.h" +#include "mail-component.h" #include "mail-tools.h" #include "mail-ops.h" #include "mail-vfolder.h" @@ -229,7 +230,8 @@ uid_cachename_hack (CamelStore *store) url->host); e_filename_make_safe (encoded_url); - filename = g_strdup_printf ("%s/mail/pop3/cache-%s", evolution_dir, encoded_url); + filename = g_strdup_printf ("%s/mail/pop3/cache-%s", + mail_component_peek_base_directory (mail_component_peek ()), encoded_url); /* lame hack, but we can't expect user's to actually migrate their cache files - brain power requirements are too @@ -238,7 +240,9 @@ uid_cachename_hack (CamelStore *store) /* This is either the first time the user has checked mail with this POP provider or else their cache file is in the old location... */ - old_location = g_strdup_printf ("%s/config/cache-%s", evolution_dir, encoded_url); + old_location = g_strdup_printf ("%s/config/cache-%s", + mail_component_peek_base_directory (mail_component_peek ()), + encoded_url); if (stat (old_location, &st) == -1) { /* old location doesn't exist either so use the new location */ g_free (old_location); diff --git a/mail/mail-search.c b/mail/mail-search.c new file mode 100644 index 0000000000..424ccee991 --- /dev/null +++ b/mail/mail-search.c @@ -0,0 +1,403 @@ +/* -*- 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.h b/mail/mail-search.h new file mode 100644 index 0000000000..cbcf563636 --- /dev/null +++ b/mail/mail-search.h @@ -0,0 +1,75 @@ +/* -*- 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 0ba9ae3179..6d70fe5047 100644 --- a/mail/mail-send-recv.c +++ b/mail/mail-send-recv.c @@ -46,6 +46,7 @@ #include "mail.h" #include "mail-mt.h" +#include "mail-component.h" #include "mail-config.h" #include "mail-session.h" #include "mail-tools.h" @@ -167,7 +168,7 @@ static void free_send_info(void *key, struct _send_info *info, void *data) g_free(info->uri); camel_operation_unref(info->cancel); if (info->timeout_id != 0) - gtk_timeout_remove(info->timeout_id); + g_source_remove(info->timeout_id); g_free(info->what); g_free(info); } @@ -207,7 +208,7 @@ static void hide_send_info(void *key, struct _send_info *info, void *data) info->status = NULL; if (info->timeout_id != 0) { - gtk_timeout_remove (info->timeout_id); + g_source_remove (info->timeout_id); info->timeout_id = 0; } } @@ -366,7 +367,7 @@ build_dialogue (EAccountList *accounts, CamelFolder *outbox, const char *destina info->keep = source->keep_on_server; info->cancel = camel_operation_new (operation_status, info); info->state = SEND_ACTIVE; - info->timeout_id = gtk_timeout_add (STATUS_TIMEOUT, operation_status_timeout, info); + info->timeout_id = g_timeout_add (STATUS_TIMEOUT, operation_status_timeout, info); g_hash_table_insert (data->active, info->uri, info); list = g_list_prepend (list, info); @@ -375,7 +376,7 @@ build_dialogue (EAccountList *accounts, CamelFolder *outbox, const char *destina e_iterator_next (iter); continue; } else if (info->timeout_id == 0) - info->timeout_id = gtk_timeout_add (STATUS_TIMEOUT, operation_status_timeout, info); + info->timeout_id = g_timeout_add (STATUS_TIMEOUT, operation_status_timeout, info); recv_icon = gtk_image_new_from_file (EVOLUTION_BUTTONSDIR "/receive-24.png"); @@ -423,12 +424,12 @@ build_dialogue (EAccountList *accounts, CamelFolder *outbox, const char *destina info->keep = FALSE; info->cancel = camel_operation_new (operation_status, info); info->state = SEND_ACTIVE; - info->timeout_id = gtk_timeout_add (STATUS_TIMEOUT, operation_status_timeout, info); + info->timeout_id = g_timeout_add (STATUS_TIMEOUT, operation_status_timeout, info); g_hash_table_insert (data->active, SEND_URI_KEY, info); list = g_list_prepend (list, info); } else if (info->timeout_id == 0) - info->timeout_id = gtk_timeout_add (STATUS_TIMEOUT, operation_status_timeout, info); + info->timeout_id = g_timeout_add (STATUS_TIMEOUT, operation_status_timeout, info); send_icon = gtk_image_new_from_file (EVOLUTION_BUTTONSDIR "/send-24.png"); @@ -663,10 +664,10 @@ receive_update_got_store (char *uri, CamelStore *store, void *data) struct _send_info *info = data; if (store) { - EvolutionStorage *storage = mail_lookup_storage (store); + EStorage *storage = mail_component_lookup_storage (mail_component_peek (), store); if (storage) { - mail_note_store(store, info->cancel, storage, CORBA_OBJECT_NIL, receive_update_done, info); + mail_note_store(store, info->cancel, storage, receive_update_done, info); /*bonobo_object_unref (BONOBO_OBJECT (storage));*/ } else { /* If we get here, store must be an external @@ -770,7 +771,7 @@ static void auto_clean_set(void *key, struct _auto_data *info, GHashTable *set) { d(printf("removing auto-check for %s %p\n", info->uri, info)); g_hash_table_remove(set, info->uri); - gtk_timeout_remove(info->timeout_id); + g_source_remove(info->timeout_id); g_free(info->uri); g_free(info); } @@ -811,15 +812,15 @@ mail_autoreceive_setup (void) info->keep = source->keep_on_server; if (info->period != source->auto_check_time*60) { info->period = source->auto_check_time*60; - gtk_timeout_remove(info->timeout_id); - info->timeout_id = gtk_timeout_add(info->period*1000, auto_timeout, info); + g_source_remove(info->timeout_id); + info->timeout_id = g_timeout_add(info->period*1000, auto_timeout, info); } } else { info = g_malloc0(sizeof(*info)); info->uri = g_strdup(source->url); info->keep = source->keep_on_server; info->period = source->auto_check_time*60; - info->timeout_id = gtk_timeout_add(info->period*1000, auto_timeout, info); + info->timeout_id = g_timeout_add(info->period*1000, auto_timeout, info); g_hash_table_insert(auto_active, info->uri, info); /* If we do this at startup, it can cause the logon dialogue to be hidden, so lets not */ diff --git a/mail/mail-session.c b/mail/mail-session.c index 65b537246b..1c41007c53 100644 --- a/mail/mail-session.c +++ b/mail/mail-session.c @@ -40,6 +40,7 @@ #include "filter/filter-context.h" #include "filter/filter-filter.h" #include "mail.h" +#include "mail-component.h" #include "mail-config.h" #include "mail-session.h" #include "mail-tools.h" @@ -593,7 +594,7 @@ main_get_filter_driver (CamelSession *session, const char *type, CamelException gconf = mail_config_get_gconf_client (); - user = g_strdup_printf ("%s/filters.xml", evolution_dir); + user = g_strdup_printf ("%s/filters.xml", mail_component_peek_base_directory (mail_component_peek ())); system = EVOLUTION_PRIVDATADIR "/filtertypes.xml"; fc = (RuleContext *) filter_context_new (); rule_context_load (fc, system, user); @@ -743,16 +744,16 @@ mail_session_forget_password (const char *key) } void -mail_session_init (void) +mail_session_init (const char *base_directory) { char *camel_dir; - - if (camel_init (evolution_dir, TRUE) != 0) + + if (camel_init (base_directory, TRUE) != 0) exit (0); session = CAMEL_SESSION (camel_object_new (MAIL_SESSION_TYPE)); - camel_dir = g_strdup_printf ("%s/mail", evolution_dir); + camel_dir = g_strdup_printf ("%s/mail", base_directory); camel_session_construct (session, camel_dir); /* The shell will tell us to go online. */ diff --git a/mail/mail-session.h b/mail/mail-session.h index 72a2757fc4..2a7559964c 100644 --- a/mail/mail-session.h +++ b/mail/mail-session.h @@ -32,7 +32,7 @@ extern "C" { #pragma } #endif /* __cplusplus */ -void mail_session_init (void); +void mail_session_init (const char *base_directory); gboolean mail_session_get_interactive (void); void mail_session_set_interactive (gboolean interactive); char *mail_session_request_dialog (const char *prompt, gboolean secret, diff --git a/mail/mail-signature-editor.c b/mail/mail-signature-editor.c index 0c1907b3bb..570a0157c4 100644 --- a/mail/mail-signature-editor.c +++ b/mail/mail-signature-editor.c @@ -395,11 +395,11 @@ mail_signature_editor (MailConfigSignature *sig, GtkWindow *parent, gboolean is_ EVOLUTION_UIDIR "/evolution-signature-editor.xml", "evolution-signature-editor", NULL); - editor->control = bonobo_widget_new_control ("OAFIID:GNOME_GtkHTML_Editor:3.0", + editor->control = bonobo_widget_new_control ("OAFIID:GNOME_GtkHTML_Editor:3.1", bonobo_ui_component_get_container (component)); if (editor->control == NULL) { - g_warning ("Cannot get 'OAFIID:GNOME_GtkHTML_Editor:3.0'."); + g_warning ("Cannot get 'OAFIID:GNOME_GtkHTML_Editor:3.1'."); destroy_editor (editor); return; diff --git a/mail/mail-summary.c b/mail/mail-summary.c index 978347bb43..903c03ea4e 100644 --- a/mail/mail-summary.c +++ b/mail/mail-summary.c @@ -72,7 +72,6 @@ typedef struct { static int queue_len = 0; -extern char *evolution_dir; extern EvolutionStorage *vfolder_storage; #define MAIN_READER main_compipe[0] @@ -295,7 +294,7 @@ generate_folder_summaries (MailSummary *summary) CamelException *ex; int i; - user = g_strdup_printf ("%s/vfolders.xml", evolution_dir); + user = g_strdup_printf ("%s/vfolders.xml", mail_component_peek_base_directory (mail_component_peek ())); system = EVOLUTION_PRIVDATADIR "/vfoldertypes.xml"; context = vfolder_context_new (); diff --git a/mail/mail-tools.c b/mail/mail-tools.c index 99fd749d78..e5fbeed05f 100644 --- a/mail/mail-tools.c +++ b/mail/mail-tools.c @@ -46,6 +46,7 @@ #include "e-util/e-meta.h" #include "mail.h" /*session*/ +#include "mail-component.h" #include "mail-config.h" #include "mail-vfolder.h" #include "mail-tools.h" @@ -62,7 +63,8 @@ mail_tool_get_local_inbox (CamelException *ex) CamelFolder *folder; char *url; - url = g_strdup_printf("file://%s/local/Inbox", evolution_dir); + url = g_strdup_printf("file://%s/local/Inbox", + mail_component_peek_base_directory (mail_component_peek ())); folder = mail_tool_uri_to_folder (url, 0, ex); g_free (url); @@ -120,7 +122,9 @@ mail_tool_get_local_movemail_path (const unsigned char *uri) if (strchr ("/:;=|%&#!*^()\\, ", *c) || !isprint ((int) *c)) *c = '_'; - path = g_strdup_printf ("%s/local/Inbox/movemail.%s", evolution_dir, safe_uri); + path = g_strdup_printf ("%s/local/Inbox/movemail.%s", + mail_component_peek_base_directory (mail_component_peek ()), + safe_uri); g_free (safe_uri); return path; @@ -318,7 +322,7 @@ mail_tool_uri_to_folder (const char *uri, guint32 flags, CamelException *ex) store = camel_session_get_store (session, uri + offset, ex); if (store) { const char *name; - + /* if we have a fragment, then the path is actually used by the store, so the fragment is the path to the folder instead */ if (url->fragment) { @@ -412,6 +416,7 @@ mail_tools_folder_to_url (CamelFolder *folder) static char *meta_data_key(const char *uri, char **pathp) { + const char *base_directory = mail_component_peek_base_directory (mail_component_peek ()); CamelURL *url; GString *path; const char *key; @@ -421,12 +426,12 @@ static char *meta_data_key(const char *uri, char **pathp) if (url == NULL) { g_warning("Trying to retrieve meta-data for unparsable uri: %s", uri); - *pathp = g_build_path(evolution_dir, "meta/unknown", NULL); + *pathp = g_build_path(base_directory, "meta/unknown", NULL); return g_strdup("folder"); } - path = g_string_new(evolution_dir); + path = g_string_new(base_directory); g_string_append_printf(path, "/meta/%s/", url->protocol); if (url->host && url->host[0]) { diff --git a/mail/mail-vfolder.c b/mail/mail-vfolder.c index 001a0ae57e..b27aea30f7 100644 --- a/mail/mail-vfolder.c +++ b/mail/mail-vfolder.c @@ -32,6 +32,8 @@ #include "evolution-storage.h" #include "evolution-shell-component.h" +#include "folder-browser.h" +#include "mail-component.h" #include "mail-vfolder.h" #include "mail-tools.h" #include "mail-autofilter.h" @@ -64,7 +66,6 @@ static GHashTable *vfolder_hash; extern EvolutionShellClient *global_shell_client; /* more globals ... */ -extern char *evolution_dir; extern CamelSession *session; static void rule_changed(FilterRule *rule, CamelFolder *folder); @@ -471,7 +472,8 @@ mail_vfolder_delete_uri(CamelStore *store, const char *uri) g_signal_connect_swapped (dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog); gtk_widget_show (dialog); - user = g_strdup_printf ("%s/vfolders.xml", evolution_dir); + user = g_strdup_printf ("%s/vfolders.xml", + mail_component_peek_base_directory (mail_component_peek ())); rule_context_save ((RuleContext *) context, user); g_free (user); } @@ -526,7 +528,7 @@ mail_vfolder_rename_uri(CamelStore *store, const char *from, const char *to) char *user; d(printf("Vfolders updated from renamed folder\n")); - user = g_strdup_printf("%s/vfolders.xml", evolution_dir); + user = g_strdup_printf("%s/vfolders.xml", mail_component_peek_base_directory (mail_component_peek ())); rule_context_save((RuleContext *)context, user); g_free(user); } @@ -646,7 +648,10 @@ static void context_rule_removed(RuleContext *ctx, FilterRule *rule) /* TODO: remove from folder info cache? */ path = g_strdup_printf("/%s", rule->name); - evolution_storage_removed_folder(mail_lookup_storage(vfolder_store), path); + + /* EPFIXME This leaks, the original code was broken too. */ + e_storage_removed_folder (mail_component_lookup_storage (mail_component_peek (), vfolder_store), path); + g_free(path); LOCK(); @@ -697,7 +702,7 @@ store_folder_deleted(CamelObject *o, void *event_data, void *data) g_object_unref(rule); g_signal_connect(context, "rule_removed", G_CALLBACK(context_rule_removed), context); - user = g_strdup_printf("%s/vfolders.xml", evolution_dir); + user = g_strdup_printf("%s/vfolders.xml", mail_component_peek_base_directory (mail_component_peek ())); rule_context_save((RuleContext *)context, user); g_free(user); } else { @@ -738,7 +743,7 @@ store_folder_renamed(CamelObject *o, void *event_data, void *data) filter_rule_set_name(rule, info->new->full_name); g_signal_connect(rule, "changed", G_CALLBACK(rule_changed), folder); - user = g_strdup_printf("%s/vfolders.xml", evolution_dir); + user = g_strdup_printf("%s/vfolders.xml", mail_component_peek_base_directory (mail_component_peek ())); rule_context_save((RuleContext *)context, user); g_free(user); @@ -750,7 +755,7 @@ store_folder_renamed(CamelObject *o, void *event_data, void *data) } void -vfolder_load_storage(GNOME_Evolution_Shell shell) +vfolder_load_storage(void) { char *user, *storeuri; FilterRule *rule; @@ -758,7 +763,7 @@ vfolder_load_storage(GNOME_Evolution_Shell shell) vfolder_hash = g_hash_table_new(g_str_hash, g_str_equal); /* first, create the vfolder store, and set it up */ - storeuri = g_strdup_printf("vfolder:%s/vfolder", evolution_dir); + storeuri = g_strdup_printf("vfolder:%s/vfolder", mail_component_peek_base_directory (mail_component_peek ())); vfolder_store = camel_session_get_store(session, storeuri, NULL); if (vfolder_store == NULL) { g_warning("Cannot open vfolder store - no vfolders available"); @@ -773,10 +778,10 @@ vfolder_load_storage(GNOME_Evolution_Shell shell) (CamelObjectEventHookFunc)store_folder_renamed, NULL); d(printf("got store '%s' = %p\n", storeuri, vfolder_store)); - mail_load_storage_by_uri(shell, storeuri, _("VFolders")); + mail_component_load_storage_by_uri(mail_component_peek (), storeuri, _("VFolders")); /* load our rules */ - user = g_strdup_printf ("%s/vfolders.xml", evolution_dir); + user = g_strdup_printf ("%s/vfolders.xml", mail_component_peek_base_directory (mail_component_peek ())); context = vfolder_context_new (); if (rule_context_load ((RuleContext *)context, EVOLUTION_PRIVDATADIR "/vfoldertypes.xml", user) != 0) { @@ -806,8 +811,7 @@ vfolder_editor_response (GtkWidget *dialog, int button, void *data) { char *user; - user = alloca(strlen(evolution_dir)+16); - sprintf(user, "%s/vfolders.xml", evolution_dir); + user = g_strdup_printf ("%s/vfolders.xml", mail_component_peek_base_directory (mail_component_peek ())); switch(button) { case GTK_RESPONSE_ACCEPT: @@ -820,6 +824,8 @@ vfolder_editor_response (GtkWidget *dialog, int button, void *data) vfolder_editor = NULL; gtk_widget_destroy(dialog); + + g_free (user); } void @@ -846,7 +852,7 @@ edit_rule_response(GtkWidget *w, int button, void *data) FilterRule *orig = g_object_get_data (G_OBJECT (w), "orig"); filter_rule_copy(orig, rule); - user = g_strdup_printf("%s/vfolders.xml", evolution_dir); + user = g_strdup_printf("%s/vfolders.xml", mail_component_peek_base_directory (mail_component_peek ())); rule_context_save((RuleContext *)context, user); g_free(user); } @@ -877,7 +883,7 @@ vfolder_edit_rule(const char *uri) GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); - gtk_container_set_border_width ((GtkContainer *) gd, 6); + gtk_container_set_border_width (GTK_CONTAINER (gd), 6); gtk_box_set_spacing ((GtkBox *) gd->vbox, 6); gtk_dialog_set_default_response(gd, GTK_RESPONSE_OK); g_object_set(gd, "allow_shrink", FALSE, "allow_grow", TRUE, NULL); @@ -925,7 +931,7 @@ new_rule_clicked(GtkWidget *w, int button, void *data) g_object_ref(rule); rule_context_add_rule((RuleContext *)context, rule); - user = g_strdup_printf("%s/vfolders.xml", evolution_dir); + user = g_strdup_printf("%s/vfolders.xml", mail_component_peek_base_directory (mail_component_peek ())); rule_context_save((RuleContext *)context, user); g_free(user); } @@ -971,7 +977,7 @@ vfolder_gui_add_rule(VfolderRule *rule) GTK_RESPONSE_OK, NULL); gtk_dialog_set_default_response(gd, GTK_RESPONSE_OK); - gtk_container_set_border_width ((GtkContainer *) gd, 6); + gtk_container_set_border_width (GTK_CONTAINER (gd), 6); gtk_box_set_spacing ((GtkBox *) gd->vbox, 6); g_object_set(gd, "allow_shrink", FALSE, "allow_grow", TRUE, NULL); gtk_window_set_default_size (GTK_WINDOW (gd), 500, 500); diff --git a/mail/mail-vfolder.h b/mail/mail-vfolder.h index 955b358c30..50c0dc5d93 100644 --- a/mail/mail-vfolder.h +++ b/mail/mail-vfolder.h @@ -11,7 +11,7 @@ #include "filter/vfolder-rule.h" #include "filter/filter-part.h" -void vfolder_load_storage(GNOME_Evolution_Shell shell); +void vfolder_load_storage(void); void vfolder_edit (void); void vfolder_edit_rule(const char *name); diff --git a/mail/mail.h b/mail/mail.h index 81832cd2a5..86ca868f5f 100644 --- a/mail/mail.h +++ b/mail/mail.h @@ -31,23 +31,7 @@ #include "mail-session.h" #include "mail-types.h" -extern char *evolution_dir; - /* mail-identify */ char *mail_identify_mime_part (CamelMimePart *part, MailDisplay *md); - -/* component factory for lack of a better place */ -void mail_add_storage (CamelStore *store, const char *name, const char *uri); -void mail_load_storage_by_uri (GNOME_Evolution_Shell shell, const char *uri, const char *name); -/*takes a GSList of MailConfigServices */ -void mail_load_storages (GNOME_Evolution_Shell shell, EAccountList *sources); - -void mail_hash_storage (CamelService *store, EvolutionStorage *storage); -EvolutionStorage *mail_lookup_storage (CamelStore *store); -void mail_remove_storage_by_uri (const char *uri); -void mail_remove_storage (CamelStore *store); -void mail_storages_foreach (GHFunc func, gpointer data); -int mail_storages_count (void); - gboolean evolution_folder_info_factory_init (void); diff --git a/mail/main.c b/mail/main.c deleted file mode 100644 index b5192a9f5c..0000000000 --- a/mail/main.c +++ /dev/null @@ -1,161 +0,0 @@ -/* - * main.c: The core of the mail component - * - * Author: - * Miguel de Icaza (miguel@ximian.com) - * - * (C) 2000 Ximian, Inc. - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <signal.h> - -#include <libgnome/gnome-sound.h> -#include <bonobo/bonobo-main.h> -#include <bonobo-activation/bonobo-activation-init.h> -#include <glade/glade.h> -#include <libgnomevfs/gnome-vfs.h> - -#include <gconf/gconf.h> - -#include <gal/widgets/e-gui-utils.h> -#include <gal/widgets/e-cursors.h> - -#include "e-util/e-passwords.h" -#include "e-util/e-proxy.h" - -#include "component-factory.h" -#include "composer/evolution-composer.h" -#include "mail.h" -#include "mail-mt.h" - -/*#define DO_MCHECK*/ - -#ifdef DO_MCHECK -static int blowup(int status) -{ - switch(status) { - case 1: - printf("Double free failure\n"); - break; - case 2: - printf("Memory clobbered before block\n"); - break; - case 3: - printf("Memory clobbered after block\n"); - break; - } - abort(); - return status; -} -#endif - -/* The GNOME SEGV handler will lose if it's not run from the main Gtk - * thread. So if we crash in another thread, redirect the signal. - */ -static void (*gnome_segv_handler) (int); - -static GStaticMutex segv_mutex = G_STATIC_MUTEX_INIT; - -static void -segv_redirect (int sig) -{ - if (pthread_self () == mail_gui_thread) - gnome_segv_handler (sig); - else { - pthread_kill (mail_gui_thread, sig); - /* We can't return from the signal handler or the - * thread may SEGV again. But we can't pthread_exit, - * because then the thread may get cleaned up before - * bug-buddy can get a stack trace. So we block by - * trying to lock a mutex we know is already locked. - */ - g_static_mutex_lock (&segv_mutex); - } -} - -int -main (int argc, char *argv []) -{ - CORBA_ORB orb; - struct sigaction sa, osa; - - /* used to make elfence work */ - free(malloc (10)); -#ifdef DO_MCHECK - /*mtrace();*/ - mcheck(blowup); -#endif - bindtextdomain (GETTEXT_PACKAGE, EVOLUTION_LOCALEDIR); - textdomain (GETTEXT_PACKAGE); - - g_thread_init (NULL); - - gnome_init_with_popt_table ("evolution-mail-component", VERSION, - argc, argv, bonobo_activation_popt_options, 0, NULL); - - sigaction (SIGSEGV, NULL, &osa); - if (osa.sa_handler != SIG_DFL) { - sa.sa_flags = 0; - sigemptyset (&sa.sa_mask); - sa.sa_handler = segv_redirect; - sigaction (SIGSEGV, &sa, NULL); - sigaction (SIGBUS, &sa, NULL); - sigaction (SIGFPE, &sa, NULL); - - sa.sa_handler = SIG_IGN; - sigaction (SIGXFSZ, &sa, NULL); - gnome_segv_handler = osa.sa_handler; - g_static_mutex_lock (&segv_mutex); - } - - if (!bonobo_init (&argc, argv)) { - g_error ("Mail component could not initialize Bonobo.\n" - "If there was a warning message about the " - "RootPOA, it probably means\nyou compiled " - "Bonobo against GOAD instead of Bonobo Activation."); - } - - gconf_init (argc, argv, NULL); - - glade_init (); - - gnome_vfs_init (); - - e_cursors_init (); - - e_proxy_init (); - - mail_config_init (); - mail_msg_init (); - - gnome_sound_init ("localhost"); - - component_factory_init (); - evolution_composer_factory_init (composer_send_cb, composer_save_draft_cb); - - if (gdk_threads_mutex) { - g_mutex_free (gdk_threads_mutex); - gdk_threads_mutex = NULL; - } - - g_print ("Evolution Mail ready and running.\n"); - - GDK_THREADS_ENTER (); - bonobo_main (); - - mail_msg_cleanup(); - - GDK_THREADS_LEAVE (); - - mail_config_write_on_exit (); - - e_passwords_shutdown (); - - gnome_sound_shutdown (); - - return 0; -} diff --git a/mail/message-browser.c b/mail/message-browser.c new file mode 100644 index 0000000000..e8e6ee0ef2 --- /dev/null +++ b/mail/message-browser.c @@ -0,0 +1,402 @@ +/* -*- 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 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 (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 new file mode 100644 index 0000000000..a02b13ef06 --- /dev/null +++ b/mail/message-browser.h @@ -0,0 +1,63 @@ +/* -*- 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 char *uri, + const char *uid); + +#endif /* _MESSAGE_BROWSER_H_ */ + diff --git a/mail/subscribe-dialog.c b/mail/subscribe-dialog.c new file mode 100644 index 0000000000..a1c18ff870 --- /dev/null +++ b/mail/subscribe-dialog.c @@ -0,0 +1,1669 @@ +/* -*- 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-component.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; + EStorage *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 (EStorage *storage, const char *path, const char *name, const char *url) +{ + EFolder *folder; + char *parent, *pname, *p; + + p = strrchr (path, '/'); + if (p && p != path) { + parent = g_strndup (path, p - path); + if (! e_storage_get_folder (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); + } + + folder = e_folder_new (name, "mail", NULL); + e_folder_set_physical_uri (folder, url); + e_folder_set_can_sync_offline (folder, TRUE); + + e_storage_new_folder (storage, path, folder); +} + +/* ** 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); + g_object_unref (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_component_lookup_storage (mail_component_peek (), 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.etspec b/mail/subscribe-dialog.etspec new file mode 100644 index 0000000000..1f5decbb36 --- /dev/null +++ b/mail/subscribe-dialog.etspec @@ -0,0 +1,9 @@ +<ETableSpecification cursor-mode="line" no-headers="true"> + <ETableColumn model_col="0" pixbuf="subscribed-image" expansion="0.0" minimum_width="16" resizable="false" cell="cell_toggle" compare="integer"/> + <ETableColumn model_col="1" _title="Folder" expansion="1.0" minimum_width="20" resizable="true" cell="cell_tree" compare="string"/> + <ETableState> + <column source="0"/> + <column source="1"/> + <grouping></grouping> + </ETableState> +</ETableSpecification> diff --git a/mail/subscribe-dialog.h b/mail/subscribe-dialog.h new file mode 100644 index 0000000000..c2e6b3d46f --- /dev/null +++ b/mail/subscribe-dialog.h @@ -0,0 +1,62 @@ +/* -*- 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_ */ diff --git a/mail/upgrade-mailer.c b/mail/upgrade-mailer.c new file mode 100644 index 0000000000..130194ca6a --- /dev/null +++ b/mail/upgrade-mailer.c @@ -0,0 +1,1169 @@ +/* -*- 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. + * + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <errno.h> +#include <ctype.h> + +#include <bonobo.h> +#include <bonobo-conf/bonobo-config-database.h> +#include <gal/util/e-xml-utils.h> +#include <libxml/xmlmemory.h> +#include <libxml/parser.h> +#include <libxml/tree.h> + +#include <camel/camel-file-utils.h> + +struct _storeinfo { + char *base_url; + char *namespace; + char *encoded_namespace; + char dir_sep; + GPtrArray *folders; +}; + + + +static char +find_dir_sep (const char *lsub_response) +{ + register const unsigned char *inptr; + const unsigned char *inend; + + inptr = (const unsigned char *) lsub_response; + inend = inptr + strlen (inptr); + + if (strncmp (inptr, "* LSUB (", 8)) + return '\0'; + + inptr += 8; + while (inptr < inend && *inptr != ')') + inptr++; + + if (inptr >= inend) + return '\0'; + + inptr++; + while (inptr < inend && isspace ((int) *inptr)) + inptr++; + + if (inptr >= inend) + return '\0'; + + if (*inptr == '\"') + inptr++; + + return inptr < inend ? *inptr : '\0'; +} + +static void +si_free (struct _storeinfo *si) +{ + int i; + + g_free (si->base_url); + g_free (si->namespace); + g_free (si->encoded_namespace); + if (si->folders) { + for (i = 0; i < si->folders->len; i++) + g_free (si->folders->pdata[i]); + g_ptr_array_free (si->folders, TRUE); + } + g_free (si); +} + +static unsigned char tohex[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' +}; + +static char * +hex_encode (const char *in, size_t len) +{ + const unsigned char *inend = in + len; + unsigned char *inptr, *outptr; + char *outbuf; + + outptr = outbuf = g_malloc ((len * 3) + 1); + + inptr = (unsigned char *) in; + while (inptr < inend) { + if (*inptr > 127 || isspace ((int) *inptr)) { + *outptr++ = '%'; + *outptr++ = tohex[(*inptr >> 4) & 0xf]; + *outptr++ = tohex[*inptr & 0xf]; + inptr++; + } else + *outptr++ = *inptr++; + } + + *outptr = '\0'; + + return outbuf; +} + +#define HEXVAL(c) (isdigit (c) ? (c) - '0' : tolower (c) - 'a' + 10) + +static char * +hex_decode (const char *in, size_t len) +{ + const unsigned char *inend = in + len; + unsigned char *inptr, *outptr; + char *outbuf; + + outptr = outbuf = g_malloc (len + 1); + + inptr = (unsigned char *) in; + while (inptr < inend) { + if (*inptr == '%') { + if (isxdigit ((int) inptr[1]) && isxdigit ((int) inptr[2])) { + *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]); + inptr += 3; + } else + *outptr++ = *inptr++; + } else + *outptr++ = *inptr++; + } + + *outptr = '\0'; + + return outbuf; +} + +static char * +parse_lsub (const char *lsub, char *dir_sep) +{ + const unsigned char *inptr = (const unsigned char *) lsub; + const unsigned char *inend; + int inlen, quoted = 0; + + inend = inptr + strlen (inptr); + if (strncmp (inptr, "* LSUB (", 8)) + return NULL; + + inptr += 8; + while (inptr < inend && *inptr != ')') + inptr++; + + if (inptr >= inend) + return NULL; + + inptr++; + while (inptr < inend && isspace ((int) *inptr)) + inptr++; + + if (inptr >= inend) + return NULL; + + /* skip over the dir sep */ + if (*inptr == '\"') + inptr++; + + *dir_sep = (char) *inptr++; + if (*inptr == '\"') + inptr++; + + if (inptr >= inend) + return NULL; + + while (inptr < inend && isspace ((int) *inptr)) + inptr++; + + if (inptr >= inend) + return NULL; + + if (*inptr == '\"') { + inptr++; + quoted = 1; + } else + quoted = 0; + + inlen = strlen (inptr) - quoted; + + return g_strndup (inptr, inlen); +} + +static void +cache_upgrade (struct _storeinfo *si, const char *folder_name) +{ + const char *old_folder_name = folder_name; + char *oldpath, *newpath, *p; + struct dirent *dent; + DIR *dir = NULL; + + if (si->namespace && strcmp ("INBOX", folder_name)) { + if (!strncmp (old_folder_name, si->namespace, strlen (si->namespace))) { + old_folder_name += strlen (si->namespace); + if (*old_folder_name == si->dir_sep) + old_folder_name++; + } + } + + oldpath = g_strdup_printf ("%s/evolution/mail/imap/%s/%s", getenv ("HOME"), + si->base_url + 7, old_folder_name); + + newpath = g_strdup_printf ("%s/evolution/mail/imap/%s/folders/%s", + getenv ("HOME"), si->base_url + 7, folder_name); + + if (!strcmp (folder_name, "folders")) + goto special_case_folders; + + if (si->dir_sep != '/') { + p = newpath + strlen (newpath) - strlen (folder_name) - 1; + while (*p) { + if (*p == si->dir_sep) + *p = '/'; + p++; + } + } + + /* make sure all parent directories exist */ + if ((p = strrchr (newpath, '/'))) { + *p = '\0'; + camel_mkdir (newpath, 0755); + *p = '/'; + } + + if (rename (oldpath, newpath) == -1) { + fprintf (stderr, "Failed to upgrade cache for imap folder %s/%s: %s\n", + si->base_url, folder_name, g_strerror (errno)); + } + + g_free (oldpath); + g_free (newpath); + + return; + + special_case_folders: + + /* the user had a toplevel folder named "folders" */ + if (camel_mkdir (newpath, 0755) == -1) { + /* we don't bother to check EEXIST because well, if + folders/folders exists then we're pretty much + fucked */ + goto exception; + } + + if (!(dir = opendir (oldpath))) + goto exception; + + while ((dent = readdir (dir))) { + char *old_path, *new_path; + + if (!strcmp (dent->d_name, ".") || !strcmp (dent->d_name, "..")) + continue; + + old_path = g_strdup_printf ("%s/%s", oldpath, dent->d_name); + new_path = g_strdup_printf ("%s/%s", newpath, dent->d_name); + + /* make sure all parent directories exist */ + if ((p = strrchr (new_path, '/'))) { + *p = '\0'; + camel_mkdir (new_path, 0755); + *p = '/'; + } + + if (rename (old_path, new_path) == -1) { + g_free (old_path); + g_free (new_path); + goto exception; + } + + g_free (old_path); + g_free (new_path); + } + + closedir (dir); + + g_free (oldpath); + g_free (newpath); + + return; + + exception: + + fprintf (stderr, "Failed to upgrade cache for imap folder %s/%s: %s\n", + si->base_url, folder_name, g_strerror (errno)); + + if (dir) + closedir (dir); + + g_free (oldpath); + g_free (newpath); +} + +static int +foldercmp (const void *f1, const void *f2) +{ + const char **folder1 = (const char **) f1; + const char **folder2 = (const char **) f2; + + return strcmp (*folder1, *folder2); +} + +static void +cache_upgrade_and_free (gpointer key, gpointer val, gpointer user_data) +{ + struct _storeinfo *si = val; + GPtrArray *folders; + char *path = NULL; + char dir_sep; + int i; + + if (si->folders) { + path = g_strdup_printf ("%s/evolution/mail/imap/%s/folders", + getenv ("HOME"), si->base_url + 7); + + if (mkdir (path, 0755) == -1 && errno != EEXIST) { + fprintf (stderr, "Failed to create directory %s: %s", path, g_strerror (errno)); + goto exception; + } + + g_free (path); + folders = g_ptr_array_new (); + for (i = 0; i < si->folders->len; i++) { + if ((path = parse_lsub (si->folders->pdata[i], &dir_sep))) { + g_ptr_array_add (folders, path); + } + } + + /* sort the folders so that parents get created before + their children */ + qsort (folders->pdata, folders->len, sizeof (void *), foldercmp); + + for (i = 0; i < folders->len; i++) { + cache_upgrade (si, folders->pdata[i]); + g_free (folders->pdata[i]); + } + } + + si_free (si); + + return; + + exception: + + fprintf (stderr, "Could not upgrade imap cache for %s: %s\n", + si->base_url + 7, g_strerror (errno)); + + g_free (path); + + si_free (si); +} + +static char * +get_base_url (const char *protocol, const char *uri) +{ + unsigned char *base_url, *p; + + p = (unsigned char *) uri + strlen (protocol) + 1; + if (!strncmp (p, "//", 2)) + p += 2; + + base_url = p; + p = strchr (p, '/'); + base_url = g_strdup_printf ("%s://%.*s", protocol, p ? (int) (p - base_url) : (int) strlen (base_url), base_url); + + return base_url; +} + +static char * +imap_namespace (const char *uri) +{ + unsigned char *name, *p; + + if ((name = strstr (uri, ";namespace=\"")) == NULL) + return NULL; + + name += strlen (";namespace=\""); + p = name; + while (*p && *p != '\"') + p++; + + return g_strndup (name, p - name); +} + +static char * +find_folder (GPtrArray *folders, const char *folder, char *dir_sep) +{ + const unsigned char *inptr, *inend; + int inlen, len, diff, i; + int quoted; + + len = strlen (folder); + + for (i = 0; i < folders->len; i++) { + inptr = folders->pdata[i]; + inend = inptr + strlen (inptr); + if (strncmp (inptr, "* LSUB (", 8)) + continue; + + inptr += 8; + while (inptr < inend && *inptr != ')') + inptr++; + + if (inptr >= inend) + continue; + + inptr++; + while (inptr < inend && isspace ((int) *inptr)) + inptr++; + + if (inptr >= inend) + continue; + + /* skip over the dir sep */ + if (*inptr == '\"') + inptr++; + + *dir_sep = *inptr++; + if (*inptr == '\"') + inptr++; + + if (inptr >= inend) + continue; + + while (inptr < inend && isspace ((int) *inptr)) + inptr++; + + if (inptr >= inend) + continue; + + if (*inptr == '\"') { + inptr++; + quoted = 1; + } else + quoted = 0; + + inlen = strlen (inptr) - quoted; + if (len > inlen) + continue; + + diff = inlen - len; + if (!strncmp (inptr + diff, folder, len)) + return hex_encode (inptr, inlen); + } + + *dir_sep = '\0'; + + return NULL; +} + +static char * +imap_url_upgrade (GHashTable *imap_sources, const char *uri) +{ + struct _storeinfo *si; + unsigned char *base_url, *folder, *p, *new = NULL; + char dir_sep; + + base_url = get_base_url ("imap", uri); + + fprintf (stderr, "checking for %s... ", base_url); + if (!(si = g_hash_table_lookup (imap_sources, base_url))) { + fprintf (stderr, "not found.\n"); + g_warning ("Unknown imap account: %s", base_url); + g_free (base_url); + return NULL; + } + + fprintf (stderr, "found.\n"); + p = (unsigned char *) uri + strlen (base_url) + 1; + if (!strcmp (p, "INBOX")) { + new = g_strdup_printf ("%s/INBOX", base_url); + g_free (base_url); + return new; + } + + p = hex_decode (p, strlen (p)); + + fprintf (stderr, "checking for folder %s on %s... ", p, base_url); + folder = si->folders ? find_folder (si->folders, p, &dir_sep) : NULL; + if (folder == NULL) { + fprintf (stderr, "not found.\n"); + folder = p; + if (si->namespace) { + if (!si->dir_sep) { + fprintf (stderr, "checking for directory separator in namespace param... "); + if (*si->namespace == '/') { + dir_sep = '/'; + } else { + p = si->namespace; + while (*p && !ispunct ((int) *p)) + p++; + + dir_sep = (char) *p; + } + } else { + dir_sep = si->dir_sep; + } + + if (dir_sep) { + fprintf (stderr, "found: '%c'\n", dir_sep); + p = folder; + folder = hex_encode (folder, strlen (folder)); + new = g_strdup_printf ("%s/%s%c%s", base_url, si->encoded_namespace, dir_sep, folder); + g_free (folder); + folder = p; + + p = new + strlen (base_url) + 1; + while (*p) { + if (*p == dir_sep) + *p = '/'; + p++; + } + } else { + fprintf (stderr, "not found."); + g_warning ("Cannot update settings for imap folder %s: unknown directory separator", uri); + } + } else { + g_warning ("Cannot update settings for imap folder %s: unknown namespace", uri); + } + + g_free (base_url); + g_free (folder); + + return new; + } else + g_free (p); + + fprintf (stderr, "found.\n"); + new = g_strdup_printf ("%s/%s", base_url, folder); + g_free (folder); + + if (!si->dir_sep) + si->dir_sep = dir_sep; + + if (dir_sep) { + p = new + strlen (base_url) + 1; + while (*p) { + if (*p == dir_sep) + *p = '/'; + p++; + } + } + + g_free (base_url); + + return new; +} + +static char * +exchange_url_upgrade (const char *uri) +{ + unsigned char *base_url, *folder; + char *url; + + base_url = get_base_url ("exchange", uri); + folder = (unsigned char *) uri + strlen (base_url) + 1; + + if (strncmp (folder, "exchange/", 9)) + return g_strdup (uri); + + folder += 9; + while (*folder && *folder != '/') + folder++; + if (*folder == '/') + folder++; + + folder = hex_decode (folder, strlen (folder)); + url = g_strdup_printf ("%s/personal/%s", base_url, folder); + g_free (base_url); + g_free (folder); + + return url; +} + +static int +mailer_upgrade_account_info (Bonobo_ConfigDatabase db, const char *key, int num, GHashTable *imap_sources) +{ + char *path, *uri, *new; + int i; + + for (i = 0; i < num; i++) { + path = g_strdup_printf ("/Mail/Accounts/account_%s_folder_uri_%d", key, i); + uri = bonobo_config_get_string (db, path, NULL); + if (uri) { + if (!strncmp (uri, "imap:", 5)) { + new = imap_url_upgrade (imap_sources, uri); + if (new) { + bonobo_config_set_string (db, path, new, NULL); + g_free (new); + } + } else if (!strncmp (uri, "exchange:", 9)) { + new = exchange_url_upgrade (uri); + bonobo_config_set_string (db, path, new, NULL); + g_free (new); + } + } + + g_free (uri); + g_free (path); + } + + return 0; +} + +static int +mailer_upgrade_xml_file (GHashTable *imap_sources, const char *filename) +{ + unsigned char *buffer, *inptr, *start, *uri, *new; + ssize_t nread = 0, nwritten, n; + gboolean url_need_upgrade; + struct stat st; + size_t len; + char *bak; + int fd; + + bak = g_strdup_printf ("%s.bak-1.0", filename); + if (stat (bak, &st) != -1) { + /* seems we have already converted this file? */ + fprintf (stderr, "\n%s already exists, assuming %s has already been upgraded\n", bak, filename); + g_free (bak); + return 0; + } + + if (stat (filename, &st) == -1 || (fd = open (filename, O_RDONLY)) == -1) { + /* file doesn't exist? I guess nothing to upgrade here */ + fprintf (stderr, "\nCould not open %s: %s\n", filename, strerror (errno)); + g_free (bak); + return 0; + } + + start = buffer = g_malloc (st.st_size + 1); + do { + do { + n = read (fd, buffer + nread, st.st_size - nread); + } while (n == -1 && errno == EINTR); + + if (n > 0) + nread += n; + } while (n != -1 && nread < st.st_size); + buffer[nread] = '\0'; + + if (nread < st.st_size) { + /* failed to load the entire file? */ + fprintf (stderr, "\nFailed to load %s: %s\n", filename, strerror (errno)); + g_free (buffer); + g_free (bak); + close (fd); + return -1; + } + + close (fd); + + inptr = buffer; + url_need_upgrade = FALSE; + do { + inptr = strstr (inptr, "uri=\""); + if (inptr) { + inptr += 5; + url_need_upgrade = !strncmp (inptr, "imap:", 5) || !strncmp (inptr, "exchange:", 9); + } + } while (inptr && !url_need_upgrade); + + if (inptr == NULL) { + /* no imap urls in this xml file, so no need to "upgrade" it */ + fprintf (stdout, "\nNo updates required for %s\n", filename); + g_free (buffer); + g_free (bak); + return 0; + } + + if (rename (filename, bak) == -1) { + /* failed to backup xml file */ + fprintf (stderr, "\nFailed to create backup file %s: %s\n", bak, strerror (errno)); + g_free (buffer); + g_free (bak); + return -1; + } + + if ((fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC, 0644)) == -1) { + /* failed to create new xml file */ + fprintf (stderr, "\nFailed to create new %s: %s\n", filename, strerror (errno)); + rename (bak, filename); + g_free (buffer); + g_free (bak); + return -1; + } + + while (inptr != NULL) { + len = inptr - start; + nwritten = 0; + do { + do { + n = write (fd, start + nwritten, len - nwritten); + } while (n == -1 && errno == EINTR); + + if (n > 0) + nwritten += n; + } while (n != -1 && nwritten < len); + + if (nwritten < len) + goto exception; + + start = inptr; + while (*start && *start != '"') + start++; + + uri = g_strndup (inptr, start - inptr); + if (!strncmp (uri, "imap:", 5)) { + if ((new = imap_url_upgrade (imap_sources, uri)) == NULL) { + new = uri; + uri = NULL; + } + } else if (!strncmp (uri, "exchange:", 9)) { + new = exchange_url_upgrade (uri); + } else { + new = uri; + uri = NULL; + } + g_free (uri); + + nwritten = 0; + len = strlen (new); + do { + do { + n = write (fd, new + nwritten, len - nwritten); + } while (n == -1 && errno == EINTR); + + if (n > 0) + nwritten += n; + } while (n != -1 && nwritten < len); + + g_free (new); + + if (nwritten < len) + goto exception; + + inptr = start; + url_need_upgrade = FALSE; + do { + inptr = strstr (inptr, "uri=\""); + if (inptr) { + inptr += 5; + url_need_upgrade = !strncmp (inptr, "imap:", 5) || !strncmp (inptr, "exchange:", 9); + } + } while (inptr && !url_need_upgrade); + } + + nwritten = 0; + len = strlen (start); + do { + do { + n = write (fd, start + nwritten, len - nwritten); + } while (n == -1 && errno == EINTR); + + if (n > 0) + nwritten += n; + } while (n != -1 && nwritten < len); + + if (nwritten < len) + goto exception; + + if (fsync (fd) == -1) + goto exception; + + close (fd); + g_free (buffer); + + fprintf (stdout, "\nSuccessfully upgraded %s\nPrevious settings saved in %s\n\n", filename, bak); + + g_free (bak); + + return 0; + + exception: + + fprintf (stderr, "\nFailed to save updated settings to %s: %s\n\n", filename, strerror (errno)); + + close (fd); + g_free (buffer); + unlink (filename); + rename (bak, filename); + g_free (bak); + + return -1; +} + +static char * +shortcuts_upgrade_uri (GHashTable *accounts, GHashTable *imap_sources, const char *account, const char *folder) +{ + char *url, *name, *decoded, *new = NULL; + struct _storeinfo *si; + int type; + + type = GPOINTER_TO_INT ((si = g_hash_table_lookup (accounts, account))); + if (type == 1) { + /* exchange */ + decoded = hex_decode (folder, strlen (folder)); + name = g_strdup_printf ("personal/%s", decoded); + g_free (decoded); + + return name; + } else { + /* imap */ + url = g_strdup_printf ("%s/%s", si->base_url, folder); + new = imap_url_upgrade (imap_sources, url); + g_free (url); + + if (new) { + name = new + strlen (si->base_url) + 1; + name = hex_decode (name, strlen (name)); + g_free (new); + + return name; + } + } + + return NULL; +} + +static int +shortcuts_upgrade_xml_file (GHashTable *accounts, GHashTable *imap_sources, const char *filename) +{ + char *bak, *uri, *account, *folder, *new, *new_uri, *type; + struct stat st; + xmlDoc *doc; + xmlNode *group, *item; + int account_len; + gboolean changed = FALSE; + + bak = g_strdup_printf ("%s.bak-1.0", filename); + if (stat (bak, &st) != -1) { + /* seems we have already converted this file? */ + fprintf (stderr, "\n%s already exists, assuming %s has already been upgraded\n", bak, filename); + g_free (bak); + return 0; + } + + if (stat (filename, &st) == -1) { + /* file doesn't exist? I guess nothing to upgrade here */ + fprintf (stderr, "\nCould not open %s: %s\n", filename, strerror (errno)); + g_free (bak); + return 0; + } + + doc = xmlParseFile (filename); + if (!doc || !doc->xmlRootNode) { + /* failed to load/parse the file? */ + fprintf (stderr, "\nFailed to load %s\n", filename); + g_free (bak); + return -1; + } + + for (group = doc->xmlRootNode->xmlChildrenNode; group; group = group->next) { + for (item = group->xmlChildrenNode; item; item = item->next) { + /* Fix IMAP/Exchange URIs */ + uri = xmlNodeGetContent (item); + if (!strncmp (uri, "evolution:/", 11)) { + if (!strcmp (uri, "evolution:/local/Inbox")) { + xmlNodeSetContent (item, "default:mail"); + changed = TRUE; + } else if (!strcmp (uri, "evolution:/local/Calendar")) { + xmlNodeSetContent (item, "default:calendar"); + changed = TRUE; + } else if (!strcmp (uri, "evolution:/local/Contacts")) { + xmlNodeSetContent (item, "default:contacts"); + changed = TRUE; + } else if (!strcmp (uri, "evolution:/local/Tasks")) { + xmlNodeSetContent (item, "default:tasks"); + changed = TRUE; + } else { + account_len = strcspn (uri + 11, "/"); + account = g_strndup (uri + 11, account_len); + if (g_hash_table_lookup (accounts, account)) { + folder = uri + 11 + account_len; + if (*folder) + folder++; + new = shortcuts_upgrade_uri (accounts, imap_sources, account, folder); + new_uri = g_strdup_printf ("evolution:/%s/%s", account, new); + xmlNodeSetContent (item, new_uri); + changed = TRUE; + g_free (new_uri); + } + g_free (account); + } + } + xmlFree (uri); + + /* Fix LDAP shortcuts */ + type = xmlGetProp (item, "type"); + if (type) { + if (!strcmp (type, "ldap-contacts")) { + xmlSetProp (item, "type", "contacts/ldap"); + changed = TRUE; + } + xmlFree (type); + } + } + } + + if (!changed) { + fprintf (stdout, "\nNo updates required for %s\n", filename); + xmlFreeDoc (doc); + g_free (bak); + return 0; + } + + if (rename (filename, bak) == -1) { + /* failed to backup xml file */ + fprintf (stderr, "\nFailed to create backup file %s: %s\n", bak, strerror (errno)); + xmlFreeDoc (doc); + g_free (bak); + return -1; + } + + if (e_xml_save_file (filename, doc) == -1) { + fprintf (stderr, "\nFailed to save updated settings to %s: %s\n\n", filename, strerror (errno)); + xmlFreeDoc (doc); + unlink (filename); + rename (bak, filename); + g_free (bak); + return -1; + } + + fprintf (stdout, "\nSuccessfully upgraded %s\nPrevious settings saved in %s\n\n", filename, bak); + + xmlFreeDoc (doc); + g_free (bak); + + return 0; +} + + +static int +mailer_upgrade (Bonobo_ConfigDatabase db) +{ + GHashTable *imap_sources, *accounts; + char *path, *uri; + char *account, *transport; + int num, i; + + if ((num = bonobo_config_get_long_with_default (db, "/Mail/Accounts/num", 0, NULL)) == 0) { + /* nothing to upgrade */ + return 0; + } + + accounts = g_hash_table_new (g_str_hash, g_str_equal); + imap_sources = g_hash_table_new (g_str_hash, g_str_equal); + for (i = 0; i < num; i++) { + struct _storeinfo *si; + struct stat st; + char *string; + guint32 tmp; + FILE *fp; + int j; + + path = g_strdup_printf ("/Mail/Accounts/source_url_%d", i); + uri = bonobo_config_get_string (db, path, NULL); + g_free (path); + if (uri && !strncmp (uri, "imap:", 5)) { + path = g_strdup_printf ("/Mail/Accounts/account_name_%d", i); + account = bonobo_config_get_string (db, path, NULL); + g_free (path); + + si = g_new (struct _storeinfo, 1); + si->base_url = get_base_url ("imap", uri); + si->namespace = imap_namespace (uri); + si->encoded_namespace = NULL; + si->dir_sep = '\0'; + si->folders = NULL; + + path = si->base_url + 7; + + path = g_strdup_printf ("%s/evolution/mail/imap/%s/storeinfo", getenv ("HOME"), path); + if (stat (path, &st) != -1 && (fp = fopen (path, "r")) != NULL) { + camel_file_util_decode_uint32 (fp, &tmp); + camel_file_util_decode_uint32 (fp, &tmp); + + j = 0; + si->folders = g_ptr_array_new (); + while (camel_file_util_decode_string (fp, &string) != -1) { + if (j++ > 0) { + g_ptr_array_add (si->folders, string); + } else { + if (!si->namespace) + si->namespace = string; + else + g_free (string); + + camel_file_util_decode_uint32 (fp, &tmp); + si->dir_sep = (char) tmp & 0xff; + } + } + + fclose (fp); + } + g_free (path); + + if (si->folders && si->folders->len > 0) + si->dir_sep = find_dir_sep (si->folders->pdata[0]); + + if (si->namespace) { + /* strip trailing dir_sep from namespace if it's there */ + j = strlen (si->namespace) - 1; + if (si->namespace[j] == si->dir_sep) + si->namespace[j] = '\0'; + + /* set the encoded version of the namespace */ + si->encoded_namespace = g_strdup (si->namespace); + for (j = 0; j < strlen (si->encoded_namespace); j++) { + if (si->encoded_namespace[j] == '/') + si->encoded_namespace[j] = '.'; + } + } + + g_hash_table_insert (imap_sources, si->base_url, si); + + if (account) + g_hash_table_insert (accounts, account, si); + } else if (uri && !strncmp (uri, "exchange:", 9)) { + /* Upgrade transport uri */ + path = g_strdup_printf ("/Mail/Accounts/transport_url_%d", i); + transport = bonobo_config_get_string (db, path, NULL); + if (transport && !strncmp (transport, "exchanget:", 10)) + bonobo_config_set_string (db, path, uri, NULL); + g_free (transport); + g_free (path); + + path = g_strdup_printf ("/Mail/Accounts/account_name_%d", i); + account = bonobo_config_get_string (db, path, NULL); + g_free (path); + + if (account) + g_hash_table_insert (accounts, account, GINT_TO_POINTER (1)); + } + + g_free (uri); + } + + if (g_hash_table_size (accounts) == 0) { + /* user doesn't have any imap/exchange accounts - nothing to upgrade */ + g_hash_table_destroy (imap_sources); + return 0; + } + + /* upgrade user's account info (bug #29135) */ + mailer_upgrade_account_info (db, "drafts", num, imap_sources); + mailer_upgrade_account_info (db, "sent", num, imap_sources); + + /* upgrade user's filters/vfolders (bug #24451) */ + path = g_strdup_printf ("%s/evolution/filters.xml", getenv ("HOME")); + mailer_upgrade_xml_file (imap_sources, path); + g_free (path); + + path = g_strdup_printf ("%s/evolution/vfolders.xml", getenv ("HOME")); + mailer_upgrade_xml_file (imap_sources, path); + g_free (path); + + /* upgrade user's shortcuts (there's no bug # for this one) */ + path = g_strdup_printf ("%s/evolution/shortcuts.xml", getenv ("HOME")); + shortcuts_upgrade_xml_file (accounts, imap_sources, path); + g_free (path); + + g_hash_table_foreach (imap_sources, cache_upgrade_and_free, NULL); + g_hash_table_destroy (imap_sources); +#if 0 + path = g_strdup_printf ("%s/evolution/mail/imap", getenv ("HOME")); + bak = g_strdup_printf ("%s.bak-1.0", path); + + if (rename (path, bak) == -1) + fprintf (stderr, "\nFailed to backup Evolution 1.0's IMAP cache: %s\n", strerror (errno)); + + g_free (path); + g_free (bak); +#endif + + return 0; +} + +static Bonobo_ConfigDatabase +get_config_db (void) +{ + Bonobo_ConfigDatabase db; + CORBA_Environment ev; + + CORBA_exception_init (&ev); + + db = bonobo_get_object ("wombat:", "Bonobo/ConfigDatabase", &ev); + if (BONOBO_EX (&ev) || db == CORBA_OBJECT_NIL) { + fprintf (stderr, "get_config_db(): Could not get the config database object '%s'", + bonobo_exception_get_text (&ev)); + db = CORBA_OBJECT_NIL; + } + + CORBA_exception_free (&ev); + + return db; +} + +static int +upgrade (void) +{ + Bonobo_ConfigDatabase db; + CORBA_Environment ev; + + if ((db = get_config_db ()) == CORBA_OBJECT_NIL) + g_error ("Could not get config db"); + + mailer_upgrade (db); + + CORBA_exception_init (&ev); + Bonobo_ConfigDatabase_sync (db, &ev); + + gtk_main_quit (); + + return FALSE; +} + +int main (int argc, char **argv) +{ + CORBA_ORB orb; + + gnome_init ("evolution-upgrade", "1.0", argc, argv); + + if ((orb = oaf_init (argc, argv)) == NULL) + g_error ("Cannot init oaf"); + + if (bonobo_init (orb, CORBA_OBJECT_NIL, CORBA_OBJECT_NIL) == FALSE) + g_error ("Cannot init bonobo"); + + gtk_idle_add ((GtkFunction) upgrade, NULL); + + bonobo_main (); + + return 0; +} |