diff options
author | Tomas Popela <tpopela@redhat.com> | 2014-06-09 22:32:25 +0800 |
---|---|---|
committer | Tomas Popela <tpopela@redhat.com> | 2014-06-09 22:32:25 +0800 |
commit | 8650fb139a9143f04615de74ff569bce3e0c4ce3 (patch) | |
tree | 89a41d08f179a5359b8eaee0c9344b8a5bf07cb3 /composer | |
parent | 04b7c97275ae420dca43f3e65c2ef54d02f01bdd (diff) | |
download | gsoc2013-evolution-8650fb139a9143f04615de74ff569bce3e0c4ce3.tar gsoc2013-evolution-8650fb139a9143f04615de74ff569bce3e0c4ce3.tar.gz gsoc2013-evolution-8650fb139a9143f04615de74ff569bce3e0c4ce3.tar.bz2 gsoc2013-evolution-8650fb139a9143f04615de74ff569bce3e0c4ce3.tar.lz gsoc2013-evolution-8650fb139a9143f04615de74ff569bce3e0c4ce3.tar.xz gsoc2013-evolution-8650fb139a9143f04615de74ff569bce3e0c4ce3.tar.zst gsoc2013-evolution-8650fb139a9143f04615de74ff569bce3e0c4ce3.zip |
Bug 540362: [webkit-composer] Use webkit for composer
Merge wip/webkit-composer branch into master.
Diffstat (limited to 'composer')
-rw-r--r-- | composer/Makefile.am | 8 | ||||
-rw-r--r-- | composer/e-composer-actions.c | 81 | ||||
-rw-r--r-- | composer/e-composer-actions.h | 4 | ||||
-rw-r--r-- | composer/e-composer-activity.c | 187 | ||||
-rw-r--r-- | composer/e-composer-activity.h | 64 | ||||
-rw-r--r-- | composer/e-composer-private.c | 858 | ||||
-rw-r--r-- | composer/e-composer-private.h | 25 | ||||
-rw-r--r-- | composer/e-composer-spell-header.c | 13 | ||||
-rw-r--r-- | composer/e-composer-spell-header.h | 3 | ||||
-rw-r--r-- | composer/e-msg-composer.c | 1318 | ||||
-rw-r--r-- | composer/e-msg-composer.h | 30 |
11 files changed, 1280 insertions, 1311 deletions
diff --git a/composer/Makefile.am b/composer/Makefile.am index aa7886403e..86a411a07e 100644 --- a/composer/Makefile.am +++ b/composer/Makefile.am @@ -10,7 +10,6 @@ evolution_mail_composer_includedir = $(privincludedir)/composer evolution_mail_composer_include_HEADERS = \ e-composer-actions.h \ - e-composer-activity.h \ e-composer-common.h \ e-composer-from-header.h \ e-composer-header-table.h \ @@ -36,13 +35,12 @@ libevolution_mail_composer_la_CPPFLAGS = \ -DG_LOG_DOMAIN=\"composer\" \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ - $(GTKHTML_CFLAGS) \ - $(CODE_COVERAGE_CFLAGS) + $(CODE_COVERAGE_CFLAGS) \ + $(NULL) libevolution_mail_composer_la_SOURCES = \ $(evolution_mail_composer_include_HEADERS) \ e-composer-actions.c \ - e-composer-activity.c \ e-composer-from-header.c \ e-composer-header-table.c \ e-composer-header.c \ @@ -63,7 +61,7 @@ libevolution_mail_composer_la_LIBADD = \ $(top_builddir)/addressbook/gui/contact-list-editor/libecontactlisteditor.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ - $(GTKHTML_LIBS) + $(NULL) ui_DATA = evolution-composer.ui diff --git a/composer/e-composer-actions.c b/composer/e-composer-actions.c index 047bf735bd..f917a2c70e 100644 --- a/composer/e-composer-actions.c +++ b/composer/e-composer-actions.c @@ -134,20 +134,24 @@ static void action_pgp_encrypt_cb (GtkToggleAction *action, EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, TRUE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, TRUE); } static void action_pgp_sign_cb (GtkToggleAction *action, EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, TRUE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, TRUE); } static void @@ -198,12 +202,14 @@ static void action_save_cb (GtkAction *action, EMsgComposer *composer) { - GtkhtmlEditor *editor = GTKHTML_EDITOR (composer); + EHTMLEditor *editor; + EHTMLEditorView *view; const gchar *filename; gint fd; GError *error = NULL; - filename = gtkhtml_editor_get_filename (editor); + editor = e_msg_composer_get_editor (composer); + filename = e_html_editor_get_filename (editor); if (filename == NULL) { gtk_action_activate (ACTION (SAVE_AS)); return; @@ -233,7 +239,7 @@ action_save_cb (GtkAction *action, } else close (fd); - if (!gtkhtml_editor_save (editor, filename, TRUE, &error)) { + if (!e_html_editor_save (editor, filename, TRUE, &error)) { e_alert_submit ( E_ALERT_SINK (composer), E_ALERT_NO_SAVE_FILE, @@ -242,13 +248,15 @@ action_save_cb (GtkAction *action, return; } - gtkhtml_editor_run_command (GTKHTML_EDITOR (composer), "saved"); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, TRUE); } static void action_save_as_cb (GtkAction *action, EMsgComposer *composer) { + EHTMLEditor *editor; GtkWidget *dialog; gchar *filename; gint response; @@ -272,8 +280,9 @@ action_save_as_cb (GtkAction *action, if (response != GTK_RESPONSE_OK) goto exit; + editor = e_msg_composer_get_editor (composer); filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); - gtkhtml_editor_set_filename (GTKHTML_EDITOR (composer), filename); + e_html_editor_set_filename (editor, filename); g_free (filename); gtk_action_activate (ACTION (SAVE)); @@ -300,20 +309,24 @@ static void action_smime_encrypt_cb (GtkToggleAction *action, EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, TRUE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, TRUE); } static void action_smime_sign_cb (GtkToggleAction *action, EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, TRUE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, TRUE); } static gboolean @@ -514,15 +527,15 @@ e_composer_actions_init (EMsgComposer *composer) GtkActionGroup *action_group; GtkAccelGroup *accel_group; GtkUIManager *ui_manager; - GtkhtmlEditor *editor; - EWebViewGtkHTML *web_view; + EHTMLEditor *editor; + EHTMLEditorView *view; gboolean visible; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - editor = GTKHTML_EDITOR (composer); - web_view = e_msg_composer_get_web_view (composer); - ui_manager = gtkhtml_editor_get_ui_manager (editor); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + ui_manager = e_html_editor_get_ui_manager (editor); /* Composer Actions */ action_group = composer->priv->composer_actions; @@ -566,23 +579,33 @@ e_composer_actions_init (EMsgComposer *composer) ACTION (SAVE_DRAFT), "short-label", _("Save Draft"), NULL); g_object_bind_property ( - composer, "html-mode", + view, "html-mode", ACTION (PICTURE_GALLERY), "sensitive", G_BINDING_SYNC_CREATE); g_object_bind_property ( - web_view, "editable", - GTKHTML_EDITOR_ACTION_EDIT_MENU (editor), "sensitive", + view, "editable", + e_html_editor_get_action (editor, "edit-menu"), "sensitive", G_BINDING_SYNC_CREATE); g_object_bind_property ( - web_view, "editable", - GTKHTML_EDITOR_ACTION_FORMAT_MENU (editor), "sensitive", + view, "editable", + e_html_editor_get_action (editor, "format-menu"), "sensitive", G_BINDING_SYNC_CREATE); g_object_bind_property ( - web_view, "editable", - GTKHTML_EDITOR_ACTION_INSERT_MENU (editor), "sensitive", + view, "editable", + e_html_editor_get_action (editor, "insert-menu"), "sensitive", + G_BINDING_SYNC_CREATE); + + g_object_bind_property ( + view, "editable", + e_html_editor_get_action (editor, "options-menu"), "sensitive", + G_BINDING_SYNC_CREATE); + + g_object_bind_property ( + view, "editable", + e_html_editor_get_action (editor, "picture-gallery"), "sensitive", G_BINDING_SYNC_CREATE); #if defined (HAVE_NSS) diff --git a/composer/e-composer-actions.h b/composer/e-composer-actions.h index 611154e9be..291953109e 100644 --- a/composer/e-composer-actions.h +++ b/composer/e-composer-actions.h @@ -18,7 +18,9 @@ #define E_COMPOSER_ACTIONS_H #define E_COMPOSER_ACTION(composer, name) \ - (gtkhtml_editor_get_action (GTKHTML_EDITOR (composer), (name))) + (e_html_editor_get_action ( \ + e_msg_composer_get_editor ( \ + E_MSG_COMPOSER (composer)), (name))) #define E_COMPOSER_ACTION_ATTACH(composer) \ E_COMPOSER_ACTION ((composer), "attach") diff --git a/composer/e-composer-activity.c b/composer/e-composer-activity.c deleted file mode 100644 index 513915f189..0000000000 --- a/composer/e-composer-activity.c +++ /dev/null @@ -1,187 +0,0 @@ -/* - * e-composer-activity.c - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation. - * - * 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 Lesser General Public License - * along with this program; if not, see <http://www.gnu.org/licenses/>. - * - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include "e-composer-private.h" - -#define E_COMPOSER_ACTIVITY_GET_PRIVATE(obj) \ - (G_TYPE_INSTANCE_GET_PRIVATE \ - ((obj), E_TYPE_COMPOSER_ACTIVITY, EComposerActivityPrivate)) - -struct _EComposerActivityPrivate { - EMsgComposer *composer; - gboolean saved_editable; -}; - -enum { - PROP_0, - PROP_COMPOSER -}; - -G_DEFINE_TYPE ( - EComposerActivity, - e_composer_activity, - E_TYPE_ACTIVITY) - -static void -composer_activity_lock_interface (EComposerActivity *activity) -{ - GtkActionGroup *action_group; - EMsgComposer *composer; - EWebViewGtkHTML *web_view; - gboolean editable; - - composer = e_composer_activity_get_composer (activity); - - web_view = e_msg_composer_get_web_view (composer); - editable = e_web_view_gtkhtml_get_editable (web_view); - e_web_view_gtkhtml_set_editable (web_view, FALSE); - activity->priv->saved_editable = editable; - - action_group = composer->priv->async_actions; - gtk_action_group_set_sensitive (action_group, FALSE); -} - -static void -composer_activity_unlock_interface (EComposerActivity *activity) -{ - GtkActionGroup *action_group; - EMsgComposer *composer; - EWebViewGtkHTML *web_view; - gboolean editable; - - composer = e_composer_activity_get_composer (activity); - - editable = activity->priv->saved_editable; - web_view = e_msg_composer_get_web_view (composer); - e_web_view_gtkhtml_set_editable (web_view, editable); - - action_group = composer->priv->async_actions; - gtk_action_group_set_sensitive (action_group, TRUE); -} - -static void -composer_activity_set_composer (EComposerActivity *activity, - EMsgComposer *composer) -{ - g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - g_return_if_fail (activity->priv->composer == NULL); - - activity->priv->composer = g_object_ref (composer); - - composer_activity_lock_interface (activity); -} - -static void -composer_activity_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) -{ - switch (property_id) { - case PROP_COMPOSER: - composer_activity_set_composer ( - E_COMPOSER_ACTIVITY (object), - g_value_get_object (value)); - return; - } - - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); -} - -static void -composer_activity_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec) -{ - switch (property_id) { - case PROP_COMPOSER: - g_value_set_object ( - value, e_composer_activity_get_composer ( - E_COMPOSER_ACTIVITY (object))); - return; - } - - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); -} - -static void -composer_activity_dispose (GObject *object) -{ - EComposerActivity *activity; - - activity = E_COMPOSER_ACTIVITY (object); - - if (activity->priv->composer != NULL) { - composer_activity_unlock_interface (activity); - g_object_unref (activity->priv->composer); - activity->priv->composer = NULL; - } - - /* Chain up to parent's dispose() method. */ - G_OBJECT_CLASS (e_composer_activity_parent_class)->dispose (object); -} - -static void -e_composer_activity_class_init (EComposerActivityClass *class) -{ - GObjectClass *object_class; - - g_type_class_add_private (class, sizeof (EComposerActivityPrivate)); - - object_class = G_OBJECT_CLASS (class); - object_class->set_property = composer_activity_set_property; - object_class->get_property = composer_activity_get_property; - object_class->dispose = composer_activity_dispose; - - g_object_class_install_property ( - object_class, - PROP_COMPOSER, - g_param_spec_object ( - "composer", - NULL, - NULL, - E_TYPE_MSG_COMPOSER, - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY)); -} - -static void -e_composer_activity_init (EComposerActivity *activity) -{ - activity->priv = E_COMPOSER_ACTIVITY_GET_PRIVATE (activity); -} - -EActivity * -e_composer_activity_new (EMsgComposer *composer) -{ - return g_object_new ( - E_TYPE_COMPOSER_ACTIVITY, - "composer", composer, NULL); -} - -EMsgComposer * -e_composer_activity_get_composer (EComposerActivity *activity) -{ - g_return_val_if_fail (E_IS_COMPOSER_ACTIVITY (activity), NULL); - - return activity->priv->composer; -} diff --git a/composer/e-composer-activity.h b/composer/e-composer-activity.h deleted file mode 100644 index 0966eba332..0000000000 --- a/composer/e-composer-activity.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * e-composer-activity.h - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation. - * - * 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 Lesser General Public License - * along with this program; if not, see <http://www.gnu.org/licenses/>. - * - */ - -#ifndef E_COMPOSER_ACTIVITY_H -#define E_COMPOSER_ACTIVITY_H - -#include <composer/e-msg-composer.h> - -/* Standard GObject macros */ -#define E_TYPE_COMPOSER_ACTIVITY \ - (e_composer_activity_get_type ()) -#define E_COMPOSER_ACTIVITY(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST \ - ((obj), E_TYPE_COMPOSER_ACTIVITY, EComposerActivity)) -#define E_COMPOSER_ACTIVITY_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_CAST \ - ((cls), E_TYPE_COMPOSER_ACTIVITY, EComposerActivityClass)) -#define E_IS_COMPOSER_ACTIVITY(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE \ - ((obj), E_TYPE_COMPOSER_ACTIVITY)) -#define E_IS_COMPOSER_ACTIVITY_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_TYPE \ - ((cls), E_TYPE_COMPOSER_ACTIVITY)) -#define E_COMPOSER_ACTIVITY_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS \ - ((obj), E_TYPE_COMPOSER_ACTIVITY, EComposerActivityClass)) - -G_BEGIN_DECLS - -typedef struct _EComposerActivity EComposerActivity; -typedef struct _EComposerActivityClass EComposerActivityClass; -typedef struct _EComposerActivityPrivate EComposerActivityPrivate; - -struct _EComposerActivity { - EActivity parent; - EComposerActivityPrivate *priv; -}; - -struct _EComposerActivityClass { - EActivityClass parent_class; -}; - -GType e_composer_activity_get_type (void); -EActivity * e_composer_activity_new (EMsgComposer *composer); -EMsgComposer * e_composer_activity_get_composer - (EComposerActivity *activity); - -G_END_DECLS - -#endif /* E_COMPOSER_ACTIVITY_H */ diff --git a/composer/e-composer-private.c b/composer/e-composer-private.c index 59e1625639..bc3b6d8078 100644 --- a/composer/e-composer-private.c +++ b/composer/e-composer-private.c @@ -27,15 +27,19 @@ /* Initial height of the picture gallery. */ #define GALLERY_INITIAL_HEIGHT 150 +#define UNICODE_ZERO_WIDTH_SPACE "\xe2\x80\x8b" + static void composer_setup_charset_menu (EMsgComposer *composer) { + EHTMLEditor *editor; GtkUIManager *ui_manager; const gchar *path; GList *list; guint merge_id; - ui_manager = gtkhtml_editor_get_ui_manager (GTKHTML_EDITOR (composer)); + editor = e_msg_composer_get_editor (composer); + ui_manager = e_html_editor_get_ui_manager (editor); path = "/main-menu/options-menu/charset-menu"; merge_id = gtk_ui_manager_new_merge_id (ui_manager); @@ -58,62 +62,22 @@ composer_setup_charset_menu (EMsgComposer *composer) } static void -msg_composer_url_requested_cb (GtkHTML *html, - const gchar *uri, - GtkHTMLStream *stream, - EMsgComposer *composer) -{ - GByteArray *array; - GHashTable *hash_table; - CamelDataWrapper *wrapper; - CamelStream *camel_stream; - CamelMimePart *mime_part; - - hash_table = composer->priv->inline_images_by_url; - mime_part = g_hash_table_lookup (hash_table, uri); - - if (mime_part == NULL) { - hash_table = composer->priv->inline_images; - mime_part = g_hash_table_lookup (hash_table, uri); - } - - /* If this is not an inline image request, - * allow the signal emission to continue. */ - if (mime_part == NULL) - return; - - array = g_byte_array_new (); - camel_stream = camel_stream_mem_new_with_byte_array (array); - wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part)); - camel_data_wrapper_decode_to_stream_sync ( - wrapper, camel_stream, NULL, NULL); - - gtk_html_write (html, stream, (gchar *) array->data, array->len); - - gtk_html_end (html, stream, GTK_HTML_STREAM_OK); - - g_object_unref (camel_stream); - - /* gtk_html_end() destroys the GtkHTMLStream, so we need to - * stop the signal emission so nothing else tries to use it. */ - g_signal_stop_emission_by_name (html, "url-requested"); -} - -static void composer_update_gallery_visibility (EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; GtkToggleAction *toggle_action; gboolean gallery_active; - gboolean html_mode; + gboolean is_html; - editor = GTKHTML_EDITOR (composer); - html_mode = gtkhtml_editor_get_html_mode (editor); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + is_html = e_html_editor_view_get_html_mode (view); toggle_action = GTK_TOGGLE_ACTION (ACTION (PICTURE_GALLERY)); gallery_active = gtk_toggle_action_get_active (toggle_action); - if (html_mode && gallery_active) { + if (is_html && gallery_active) { gtk_widget_show (composer->priv->gallery_scrolled_window); gtk_widget_show (composer->priv->gallery_icon_view); } else { @@ -122,30 +86,16 @@ composer_update_gallery_visibility (EMsgComposer *composer) } } -static void -composer_spell_languages_changed (EMsgComposer *composer, - GList *languages) -{ - EComposerHeader *header; - EComposerHeaderTable *table; - - table = e_msg_composer_get_header_table (composer); - header = e_composer_header_table_get_header ( - table, E_COMPOSER_HEADER_SUBJECT); - - e_composer_spell_header_set_languages ( - E_COMPOSER_SPELL_HEADER (header), languages); -} - void e_composer_private_constructed (EMsgComposer *composer) { EMsgComposerPrivate *priv = composer->priv; EFocusTracker *focus_tracker; + EComposerHeader *header; EShell *shell; - EWebViewGtkHTML *web_view; EClientCache *client_cache; - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; GtkUIManager *ui_manager; GtkAction *action; GtkWidget *container; @@ -158,14 +108,14 @@ e_composer_private_constructed (EMsgComposer *composer) gint ii; GError *error = NULL; - editor = GTKHTML_EDITOR (composer); - ui_manager = gtkhtml_editor_get_ui_manager (editor); + editor = e_msg_composer_get_editor (composer); + ui_manager = e_html_editor_get_ui_manager (editor); + view = e_html_editor_get_view (editor); settings = g_settings_new ("org.gnome.evolution.mail"); shell = e_msg_composer_get_shell (composer); client_cache = e_shell_get_client_cache (shell); - web_view = e_msg_composer_get_web_view (composer); /* Each composer window gets its own window group. */ window = GTK_WINDOW (composer); @@ -179,19 +129,17 @@ e_composer_private_constructed (EMsgComposer *composer) priv->extra_hdr_names = g_ptr_array_new (); priv->extra_hdr_values = g_ptr_array_new (); - priv->inline_images = g_hash_table_new_full ( - g_str_hash, g_str_equal, - (GDestroyNotify) g_free, - (GDestroyNotify) NULL); - - priv->inline_images_by_url = g_hash_table_new_full ( - g_str_hash, g_str_equal, - (GDestroyNotify) g_free, - (GDestroyNotify) g_object_unref); - priv->charset = e_composer_get_default_charset (); + priv->is_from_draft = FALSE; priv->is_from_message = FALSE; + priv->is_from_new_message = FALSE; + priv->set_signature_from_message = FALSE; + priv->disable_signature = FALSE; + priv->busy = FALSE; + priv->saved_editable= FALSE; + + priv->focused_entry = NULL; e_composer_actions_init (composer); @@ -216,48 +164,58 @@ e_composer_private_constructed (EMsgComposer *composer) focus_tracker = e_focus_tracker_new (GTK_WINDOW (composer)); - action = gtkhtml_editor_get_action (editor, "cut"); + action = e_html_editor_get_action (editor, "cut"); e_focus_tracker_set_cut_clipboard_action (focus_tracker, action); - action = gtkhtml_editor_get_action (editor, "copy"); + action = e_html_editor_get_action (editor, "copy"); e_focus_tracker_set_copy_clipboard_action (focus_tracker, action); - action = gtkhtml_editor_get_action (editor, "paste"); + action = e_html_editor_get_action (editor, "paste"); e_focus_tracker_set_paste_clipboard_action (focus_tracker, action); - action = gtkhtml_editor_get_action (editor, "select-all"); + action = e_html_editor_get_action (editor, "select-all"); e_focus_tracker_set_select_all_action (focus_tracker, action); priv->focus_tracker = focus_tracker; - container = editor->vbox; + widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (composer), widget); + gtk_widget_show (widget); + + container = widget; - /* Construct the activity bar. */ + /* Construct the main menu and toolbar. */ - widget = e_activity_bar_new (); + widget = e_html_editor_get_managed_widget (editor, "/main-menu"); gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); - priv->activity_bar = g_object_ref_sink (widget); - /* EActivityBar controls its own visibility. */ - - /* Construct the alert bar for errors. */ + gtk_widget_show (widget); - widget = e_alert_bar_new (); + widget = e_html_editor_get_managed_widget (editor, "/main-toolbar"); gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); - priv->alert_bar = g_object_ref_sink (widget); - /* EAlertBar controls its own visibility. */ + gtk_widget_show (widget); /* Construct the header table. */ widget = e_composer_header_table_new (client_cache); gtk_container_set_border_width (GTK_CONTAINER (widget), 6); gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); - gtk_box_reorder_child (GTK_BOX (container), widget, 2); - priv->header_table = g_object_ref_sink (widget); + priv->header_table = g_object_ref (widget); gtk_widget_show (widget); - g_signal_connect ( - G_OBJECT (composer), "spell-languages-changed", - G_CALLBACK (composer_spell_languages_changed), NULL); + header = e_composer_header_table_get_header ( + E_COMPOSER_HEADER_TABLE (widget), + E_COMPOSER_HEADER_SUBJECT); + g_object_bind_property ( + view, "spell-checker", + header->input_widget, "spell-checker", + G_BINDING_SYNC_CREATE); + + /* Construct the editing toolbars. We'll have to reparent + * the embedded EHTMLEditorView a little further down. */ + + widget = GTK_WIDGET (editor); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); + gtk_widget_show (widget); /* Construct the attachment paned. */ @@ -267,8 +225,8 @@ e_composer_private_constructed (EMsgComposer *composer) gtk_widget_show (widget); g_object_bind_property ( - web_view, "editable", - widget, "editable", + view, "editable", + widget, "sensitive", G_BINDING_SYNC_CREATE); container = e_attachment_paned_get_content_area ( @@ -288,13 +246,13 @@ e_composer_private_constructed (EMsgComposer *composer) GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN); gtk_widget_set_size_request (widget, -1, GALLERY_INITIAL_HEIGHT); gtk_paned_pack1 (GTK_PANED (container), widget, FALSE, FALSE); - priv->gallery_scrolled_window = g_object_ref_sink (widget); + priv->gallery_scrolled_window = g_object_ref (widget); gtk_widget_show (widget); - /* Reparent the scrolled window containing the GtkHTML widget - * into the content area of the top attachment pane. */ + /* Reparent the scrolled window containing the web view + * widget into the content area of the top attachment pane. */ - widget = GTK_WIDGET (web_view); + widget = GTK_WIDGET (view); widget = gtk_widget_get_parent (widget); gtk_widget_reparent (widget, container); @@ -310,16 +268,16 @@ e_composer_private_constructed (EMsgComposer *composer) priv->gallery_icon_view = g_object_ref_sink (widget); g_free (gallery_path); - e_signal_connect_notify ( - composer, "notify::html-mode", - G_CALLBACK (composer_update_gallery_visibility), NULL); + e_signal_connect_notify_swapped ( + view, "notify::mode", + G_CALLBACK (composer_update_gallery_visibility), composer); g_signal_connect_swapped ( ACTION (PICTURE_GALLERY), "toggled", G_CALLBACK (composer_update_gallery_visibility), composer); - /* XXX What is this for? */ - g_object_set_data (G_OBJECT (composer), "vbox", editor->vbox); + /* Initial sync */ + composer_update_gallery_visibility (composer); /* Bind headers to their corresponding actions. */ @@ -361,20 +319,21 @@ e_composer_private_constructed (EMsgComposer *composer) G_BINDING_SYNC_CREATE); } - /* Install a handler for inline images. */ + /* Disable actions that start asynchronous activities while an + * asynchronous activity is in progress. We enforce this with + * a simple inverted binding to EMsgComposer's "busy" property. */ - /* XXX We no longer use GtkhtmlEditor::uri-requested because it - * conflicts with EWebView's url_requested() method, which - * unconditionally launches an async operation. I changed - * GtkHTML::url-requested to be a G_SIGNAL_RUN_LAST so that - * our handler runs first. If we can handle the request - * we'll stop the signal emission to prevent EWebView from - * launching an async operation. Messy, but works until we - * switch to WebKit. --mbarnes */ + g_object_bind_property ( + composer, "busy", + priv->async_actions, "sensitive", + G_BINDING_SYNC_CREATE | + G_BINDING_INVERT_BOOLEAN); - g_signal_connect ( - web_view, "url-requested", - G_CALLBACK (msg_composer_url_requested_cb), composer); + g_object_bind_property ( + composer, "busy", + priv->header_table, "sensitive", + G_BINDING_SYNC_CREATE | + G_BINDING_INVERT_BOOLEAN); g_object_unref (settings); } @@ -389,21 +348,16 @@ e_composer_private_dispose (EMsgComposer *composer) composer->priv->shell = NULL; } + if (composer->priv->editor != NULL) { + g_object_unref (composer->priv->editor); + composer->priv->editor = NULL; + } + if (composer->priv->header_table != NULL) { g_object_unref (composer->priv->header_table); composer->priv->header_table = NULL; } - if (composer->priv->activity_bar != NULL) { - g_object_unref (composer->priv->activity_bar); - composer->priv->activity_bar = NULL; - } - - if (composer->priv->alert_bar != NULL) { - g_object_unref (composer->priv->alert_bar); - composer->priv->alert_bar = NULL; - } - if (composer->priv->attachment_paned != NULL) { g_object_unref (composer->priv->attachment_paned); composer->priv->attachment_paned = NULL; @@ -434,11 +388,10 @@ e_composer_private_dispose (EMsgComposer *composer) composer->priv->composer_actions = NULL; } - g_clear_object (&composer->priv->gallery_icon_view); - g_clear_object (&composer->priv->gallery_scrolled_window); - - g_hash_table_remove_all (composer->priv->inline_images); - g_hash_table_remove_all (composer->priv->inline_images_by_url); + if (composer->priv->gallery_scrolled_window != NULL) { + g_object_unref (composer->priv->gallery_scrolled_window); + composer->priv->gallery_scrolled_window = NULL; + } if (composer->priv->redirect != NULL) { g_object_unref (composer->priv->redirect); @@ -462,10 +415,6 @@ e_composer_private_finalize (EMsgComposer *composer) g_free (composer->priv->charset); g_free (composer->priv->mime_type); g_free (composer->priv->mime_body); - g_free (composer->priv->selected_signature_uid); - - g_hash_table_destroy (composer->priv->inline_images); - g_hash_table_destroy (composer->priv->inline_images_by_url); } gchar * @@ -525,92 +474,13 @@ e_composer_get_default_charset (void) return charset; } -gchar * -e_composer_decode_clue_value (const gchar *encoded_value) -{ - GString *buffer; - const gchar *cp; - - /* Decode a GtkHtml "ClueFlow" value. */ - - g_return_val_if_fail (encoded_value != NULL, NULL); - - buffer = g_string_sized_new (strlen (encoded_value)); - - /* Copy the value, decoding escaped characters as we go. */ - cp = encoded_value; - while (*cp != '\0') { - if (*cp == '.') { - cp++; - switch (*cp) { - case '.': - g_string_append_c (buffer, '.'); - break; - case '1': - g_string_append_c (buffer, '"'); - break; - case '2': - g_string_append_c (buffer, '='); - break; - default: - /* Invalid escape sequence. */ - g_string_free (buffer, TRUE); - return NULL; - } - } else - g_string_append_c (buffer, *cp); - cp++; - } - - return g_string_free (buffer, FALSE); -} - -gchar * -e_composer_encode_clue_value (const gchar *decoded_value) -{ - gchar *encoded_value; - gchar **strv; - - /* Encode a GtkHtml "ClueFlow" value. */ - - g_return_val_if_fail (decoded_value != NULL, NULL); - - /* XXX This is inefficient but easy to understand. */ - - encoded_value = g_strdup (decoded_value); - - /* Substitution: '.' --> '..' (do this first) */ - if (strchr (encoded_value, '.') != NULL) { - strv = g_strsplit (encoded_value, ".", 0); - g_free (encoded_value); - encoded_value = g_strjoinv ("..", strv); - g_strfreev (strv); - } - - /* Substitution: '"' --> '.1' */ - if (strchr (encoded_value, '"') != NULL) { - strv = g_strsplit (encoded_value, """", 0); - g_free (encoded_value); - encoded_value = g_strjoinv (".1", strv); - g_strfreev (strv); - } - - /* Substitution: '=' --> '.2' */ - if (strchr (encoded_value, '=') != NULL) { - strv = g_strsplit (encoded_value, "=", 0); - g_free (encoded_value); - encoded_value = g_strjoinv (".2", strv); - g_strfreev (strv); - } - - return encoded_value; -} - gboolean e_composer_paste_html (EMsgComposer *composer, GtkClipboard *clipboard) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *editor_selection; gchar *html; g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE); @@ -619,9 +489,15 @@ e_composer_paste_html (EMsgComposer *composer, html = e_clipboard_wait_for_html (clipboard); g_return_val_if_fail (html != NULL, FALSE); - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_insert_html (editor, html); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + editor_selection = e_html_editor_view_get_selection (view); + e_html_editor_selection_insert_html (editor_selection, html); + e_html_editor_view_check_magic_links (view, FALSE); + e_html_editor_view_force_spell_check (view); + + e_html_editor_selection_scroll_to_caret (editor_selection); g_free (html); return TRUE; @@ -631,7 +507,8 @@ gboolean e_composer_paste_image (EMsgComposer *composer, GtkClipboard *clipboard) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *html_editor_view; EAttachmentStore *store; EAttachmentView *view; GdkPixbuf *pixbuf = NULL; @@ -643,7 +520,6 @@ e_composer_paste_image (EMsgComposer *composer, g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE); g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE); - editor = GTKHTML_EDITOR (composer); view = e_msg_composer_get_attachment_view (composer); store = e_attachment_view_get_store (view); @@ -673,9 +549,15 @@ e_composer_paste_image (EMsgComposer *composer, /* In HTML mode, paste the image into the message body. * In text mode, add the image to the attachment store. */ - if (gtkhtml_editor_get_html_mode (editor)) - gtkhtml_editor_insert_image (editor, uri); - else { + editor = e_msg_composer_get_editor (composer); + html_editor_view = e_html_editor_get_view (editor); + if (e_html_editor_view_get_html_mode (html_editor_view)) { + EHTMLEditorSelection *selection; + + selection = e_html_editor_view_get_selection (html_editor_view); + e_html_editor_selection_insert_image (selection, uri); + e_html_editor_selection_scroll_to_caret (selection); + } else { EAttachment *attachment; attachment = e_attachment_new_for_uri (uri); @@ -705,7 +587,9 @@ gboolean e_composer_paste_text (EMsgComposer *composer, GtkClipboard *clipboard) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *editor_selection; gchar *text; g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE); @@ -714,8 +598,18 @@ e_composer_paste_text (EMsgComposer *composer, text = gtk_clipboard_wait_for_text (clipboard); g_return_val_if_fail (text != NULL, FALSE); - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_insert_text (editor, text); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + editor_selection = e_html_editor_view_get_selection (view); + /* If WebView doesn't have focus, focus it */ + if (!gtk_widget_has_focus (GTK_WIDGET (view))) + gtk_widget_grab_focus (GTK_WIDGET (view)); + + e_html_editor_selection_insert_text (editor_selection, text); + + e_html_editor_view_check_magic_links (view, FALSE); + e_html_editor_view_force_spell_check (view); + e_html_editor_selection_scroll_to_caret (editor_selection); g_free (text); @@ -757,6 +651,35 @@ e_composer_paste_uris (EMsgComposer *composer, } gboolean +e_composer_selection_is_base64_uris (EMsgComposer *composer, + GtkSelectionData *selection) +{ + gboolean all_base64_uris = TRUE; + gchar **uris; + guint ii; + + g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE); + g_return_val_if_fail (selection != NULL, FALSE); + + uris = gtk_selection_data_get_uris (selection); + + if (!uris) + return FALSE; + + for (ii = 0; uris[ii] != NULL; ii++) { + if (!((g_str_has_prefix (uris[ii], "data:") || strstr (uris[ii], ";data:")) + && strstr (uris[ii], ";base64,"))) { + all_base64_uris = FALSE; + break; + } + } + + g_strfreev (uris); + + return all_base64_uris; +} + +gboolean e_composer_selection_is_image_uris (EMsgComposer *composer, GtkSelectionData *selection) { @@ -769,7 +692,7 @@ e_composer_selection_is_image_uris (EMsgComposer *composer, uris = gtk_selection_data_get_uris (selection); - if (uris == NULL) + if (!uris) return FALSE; for (ii = 0; uris[ii] != NULL; ii++) { @@ -859,19 +782,255 @@ use_top_signature (EMsgComposer *composer) } static void +composer_size_allocate_cb (GtkWidget *widget, + gpointer user_data) +{ + GtkWidget *scrolled_window; + GtkAdjustment *adj; + + scrolled_window = gtk_widget_get_parent (GTK_WIDGET (widget)); + adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled_window)); + + /* Scroll only when there is some size allocated */ + if (gtk_adjustment_get_upper (adj) != 0.0) { + /* Scroll web view down to caret */ + gtk_adjustment_set_value (adj, gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj)); + gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (scrolled_window), adj); + /* Disconnect because we don't want to scroll down the view on every window size change */ + g_signal_handlers_disconnect_by_func ( + widget, G_CALLBACK (composer_size_allocate_cb), NULL); + } +} + +static void +insert_paragraph_with_input (WebKitDOMElement *paragraph, + WebKitDOMElement *body) +{ + WebKitDOMNode *node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)); + + if (node) { + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (paragraph), + node, + NULL); + } else { + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (paragraph), + NULL); + } +} + +static void +composer_move_caret (EMsgComposer *composer) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *editor_selection; + GSettings *settings; + gboolean start_bottom, html_mode, top_signature; + gboolean has_paragraphs_in_body = TRUE; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMDOMSelection *dom_selection; + WebKitDOMElement *input_start, *element, *signature; + WebKitDOMHTMLElement *body; + WebKitDOMNodeList *list, *blockquotes; + WebKitDOMRange *new_range; + + /* When there is an option composer-reply-start-bottom set we have + * to move the caret between reply and signature. */ + settings = g_settings_new ("org.gnome.evolution.mail"); + start_bottom = g_settings_get_boolean (settings, "composer-reply-start-bottom"); + g_object_unref (settings); + + top_signature = + use_top_signature (composer) && + !composer->priv->is_from_message && + !composer->priv->is_from_new_message; + + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + editor_selection = e_html_editor_view_get_selection (view); + html_mode = e_html_editor_view_get_html_mode (view); + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (window); + + body = webkit_dom_document_get_body (document); + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (body), "data-message", "", NULL); + new_range = webkit_dom_document_create_range (document); + + element = webkit_dom_document_get_element_by_id (document, "-x-evo-caret-position"); + /* Caret position found => composer mode changed */ + if (element) { + e_html_editor_selection_restore_caret_position (editor_selection); + /* We want to force spellcheck just in case that we switched to plain + * text mode (when switching to html mode, the underlined words are + * preserved */ + if (!html_mode) + e_html_editor_view_force_spell_check (view); + return; + } + + /* If editing message as new don't handle with caret */ + if (composer->priv->is_from_message || composer->priv->is_from_draft) { + if (composer->priv->is_from_message) + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (body), + "data-edit-as-new", + "", + NULL); + e_html_editor_selection_restore_caret_position (editor_selection); + e_html_editor_selection_scroll_to_caret (editor_selection); + + e_html_editor_view_force_spell_check (view); + return; + } + + e_html_editor_selection_block_selection_changed (editor_selection); + + /* When the new message is written from the beginning - note it into body */ + if (composer->priv->is_from_new_message) { + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (body), "data-new-message", "", NULL); + } + + list = webkit_dom_document_get_elements_by_class_name (document, "-x-evo-paragraph"); + signature = webkit_dom_document_query_selector (document, ".-x-evo-signature", NULL); + /* Situation when wrapped paragraph is just in signature and not in message body */ + if (webkit_dom_node_list_get_length (list) == 1) { + if (signature && webkit_dom_element_query_selector (signature, ".-x-evo-paragraph", NULL)) + has_paragraphs_in_body = FALSE; + } + + if (webkit_dom_node_list_get_length (list) == 0) + has_paragraphs_in_body = FALSE; + + blockquotes = webkit_dom_document_get_elements_by_tag_name (document, "blockquote"); + + if (!has_paragraphs_in_body) { + element = e_html_editor_selection_get_paragraph_element ( + editor_selection, document, -1, 0); + webkit_dom_element_set_id (element, "-x-evo-input-start"); + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (element), UNICODE_ZERO_WIDTH_SPACE, NULL); + if (top_signature) + element_add_class (element, "-x-evo-top-signature"); + } + + if (start_bottom) { + if (webkit_dom_node_list_get_length (blockquotes) != 0) { + if (!has_paragraphs_in_body) { + if (!top_signature) { + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (element), + signature ? + webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (signature)) : + webkit_dom_node_get_next_sibling ( + webkit_dom_node_list_item ( + blockquotes, 0)), + NULL); + } else { + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (element), + NULL); + } + } + + e_html_editor_selection_restore_caret_position (editor_selection); + if (!html_mode) + e_html_editor_view_quote_plain_text (view); + e_html_editor_view_force_spell_check (view); + + input_start = webkit_dom_document_get_element_by_id ( + document, "-x-evo-input-start"); + if (input_start) + webkit_dom_range_select_node_contents ( + new_range, WEBKIT_DOM_NODE (input_start), NULL); + + webkit_dom_range_collapse (new_range, FALSE, NULL); + } else { + if (!has_paragraphs_in_body) + insert_paragraph_with_input ( + element, WEBKIT_DOM_ELEMENT (body)); + + webkit_dom_range_select_node_contents ( + new_range, + webkit_dom_node_get_first_child ( + WEBKIT_DOM_NODE (body)), + NULL); + webkit_dom_range_collapse (new_range, TRUE, NULL); + } + + g_signal_connect ( + view, "size-allocate", + G_CALLBACK (composer_size_allocate_cb), NULL); + } else { + /* Move caret on the beginning of message */ + if (!has_paragraphs_in_body) { + insert_paragraph_with_input ( + element, WEBKIT_DOM_ELEMENT (body)); + + if (webkit_dom_node_list_get_length (blockquotes) != 0) { + if (!html_mode) { + WebKitDOMNode *blockquote; + + blockquote = webkit_dom_node_list_item (blockquotes, 0); + + /* FIXME determine when we can skip this */ + e_html_editor_selection_wrap_paragraph ( + editor_selection, + WEBKIT_DOM_ELEMENT (blockquote)); + + e_html_editor_selection_restore_caret_position (editor_selection); + e_html_editor_view_quote_plain_text (view); + body = webkit_dom_document_get_body (document); + } + } + } + + e_html_editor_view_force_spell_check (view); + + webkit_dom_range_select_node_contents ( + new_range, + WEBKIT_DOM_NODE ( + webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body))), + NULL); + webkit_dom_range_collapse (new_range, TRUE, NULL); + } + + webkit_dom_dom_selection_remove_all_ranges (dom_selection); + webkit_dom_dom_selection_add_range (dom_selection, new_range); + + e_html_editor_selection_unblock_selection_changed (editor_selection); +} + +static void composer_load_signature_cb (EMailSignatureComboBox *combo_box, GAsyncResult *result, EMsgComposer *composer) { GString *html_buffer = NULL; - GtkhtmlEditor *editor; gchar *contents = NULL; gsize length = 0; const gchar *active_id; - gchar *encoded_uid = NULL; gboolean top_signature; gboolean is_html; GError *error = NULL; + EHTMLEditor *editor; + EHTMLEditorView *view; + WebKitDOMDocument *document; + WebKitDOMNodeList *signatures; + gulong list_length, ii; + GSettings *settings; + gboolean start_bottom; e_mail_signature_combo_box_load_selected_finish ( combo_box, result, &contents, &length, &is_html, &error); @@ -887,7 +1046,12 @@ composer_load_signature_cb (EMailSignatureComboBox *combo_box, * Always put the signature at the bottom for that case. */ top_signature = use_top_signature (composer) && - !composer->priv->is_from_message; + !composer->priv->is_from_message && + !composer->priv->is_from_new_message; + + settings = g_settings_new ("org.gnome.evolution.mail"); + start_bottom = g_settings_get_boolean (settings, "composer-reply-start-bottom"); + g_object_unref (settings); if (contents == NULL) goto insert; @@ -911,24 +1075,13 @@ composer_load_signature_cb (EMailSignatureComboBox *combo_box, /* The combo box active ID is the signature's ESource UID. */ active_id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box)); - if (active_id != NULL && *active_id != '\0') - encoded_uid = e_composer_encode_clue_value (active_id); - g_string_append_printf ( html_buffer, - "<!--+GtkHTML:<DATA class=\"ClueFlow\" " - " key=\"signature\" value=\"1\">-->" - "<!--+GtkHTML:<DATA class=\"ClueFlow\" " - " key=\"signature_name\" value=\"uid:%s\">-->", - (encoded_uid != NULL) ? encoded_uid : ""); - - g_string_append ( - html_buffer, - "<TABLE WIDTH=\"100%%\" CELLSPACING=\"0\"" - " CELLPADDING=\"0\"><TR><TD>"); + "<SPAN class=\"-x-evo-signature\" id=\"1\" name=\"%s\">", + (active_id != NULL) ? active_id : ""); if (!is_html) - g_string_append (html_buffer, "<PRE>\n"); + g_string_append (html_buffer, "<PRE>"); /* The signature dash convention ("-- \n") is specified * in the "Son of RFC 1036", section 4.3.2. @@ -939,8 +1092,8 @@ composer_load_signature_cb (EMailSignatureComboBox *combo_box, const gchar *delim_nl; if (is_html) { - delim = "-- \n<BR>"; - delim_nl = "\n-- \n<BR>"; + delim = "-- <BR>"; + delim_nl = "\n-- <BR>"; } else { delim = "-- \n"; delim_nl = "\n-- \n"; @@ -958,73 +1111,148 @@ composer_load_signature_cb (EMailSignatureComboBox *combo_box, g_string_append_len (html_buffer, contents, length); if (!is_html) - g_string_append (html_buffer, "</PRE>\n"); - - if (top_signature) - g_string_append (html_buffer, "<BR>"); - - g_string_append (html_buffer, "</TD></TR></TABLE>"); + g_string_append (html_buffer, "</PRE>"); - g_free (encoded_uid); + g_string_append (html_buffer, "</SPAN>"); g_free (contents); insert: /* Remove the old signature and insert the new one. */ - editor = GTKHTML_EDITOR (composer); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + + signatures = webkit_dom_document_get_elements_by_class_name ( + document, "-x-evo-signature"); + list_length = webkit_dom_node_list_get_length (signatures); + for (ii = 0; ii < list_length; ii++) { + WebKitDOMNode *node; + gchar *id; + + node = webkit_dom_node_list_item (signatures, ii); + id = webkit_dom_element_get_id (WEBKIT_DOM_ELEMENT (node)); + + /* When we are editing a message with signature we need to set active + * signature id in signature combo box otherwise no signature will be + * added but we have to do it just once when the composer opens */ + if (composer->priv->is_from_message && composer->priv->set_signature_from_message) { + gchar *name = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "name"); + gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), name); + g_free (name); + composer->priv->set_signature_from_message = FALSE; + } + + if (id && (strlen (id) == 1) && (*id == '1')) { + /* We have to remove the div containing the span with signature */ + WebKitDOMNode *next_sibling; + WebKitDOMNode *parent; + + parent = webkit_dom_node_get_parent_node (node); + next_sibling = webkit_dom_node_get_next_sibling (parent); - /* This prevents our command before/after callbacks from - * screwing around with the signature as we insert it. */ - composer->priv->in_signature_insert = TRUE; + if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (next_sibling)) + webkit_dom_node_remove_child ( + webkit_dom_node_get_parent_node (next_sibling), + next_sibling, + NULL); - gtkhtml_editor_freeze (editor); - gtkhtml_editor_run_command (editor, "cursor-position-save"); - gtkhtml_editor_undo_begin (editor, "Set signature", "Reset signature"); + webkit_dom_node_remove_child ( + webkit_dom_node_get_parent_node (parent), + parent, + NULL); + + g_free (id); + break; + } - gtkhtml_editor_run_command (editor, "block-selection"); - gtkhtml_editor_run_command (editor, "cursor-bod"); - if (gtkhtml_editor_search_by_data (editor, 1, "ClueFlow", "signature", "1")) { - gtkhtml_editor_run_command (editor, "select-paragraph"); - gtkhtml_editor_run_command (editor, "delete"); - gtkhtml_editor_set_paragraph_data (editor, "signature", "0"); - gtkhtml_editor_run_command (editor, "delete-back"); + g_free (id); } - gtkhtml_editor_run_command (editor, "unblock-selection"); if (html_buffer != NULL) { - gtkhtml_editor_run_command (editor, "insert-paragraph"); - if (!gtkhtml_editor_run_command (editor, "cursor-backward")) - gtkhtml_editor_run_command (editor, "insert-paragraph"); - else - gtkhtml_editor_run_command (editor, "cursor-forward"); - - gtkhtml_editor_set_paragraph_data (editor, "orig", "0"); - gtkhtml_editor_run_command (editor, "indent-zero"); - gtkhtml_editor_run_command (editor, "style-normal"); - gtkhtml_editor_insert_html (editor, html_buffer->str); + if (*html_buffer->str) { + WebKitDOMElement *element; + WebKitDOMHTMLElement *body; + + body = webkit_dom_document_get_body (document); + element = webkit_dom_document_create_element (document, "DIV", NULL); + + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (element), html_buffer->str, NULL); + + if (top_signature) { + WebKitDOMNode *signature_inserted; + WebKitDOMNode *child = + webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)); + WebKitDOMElement *br = + webkit_dom_document_create_element ( + document, "br", NULL); + + if (start_bottom) { + signature_inserted = webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (element), + child, + NULL); + } else { + WebKitDOMElement *input_start = + webkit_dom_document_get_element_by_id ( + document, "-x-evo-input-start"); + /* When we are using signature on top the caret + * should be before the signature */ + signature_inserted = webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (element), + input_start ? + webkit_dom_node_get_next_sibling ( + WEBKIT_DOM_NODE (input_start)) : + child, + NULL); + } + + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (br), + webkit_dom_node_get_next_sibling (signature_inserted), + NULL); + } else { + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (element), + NULL); + } + } g_string_free (html_buffer, TRUE); - - } else if (top_signature) { - /* Insert paragraph after the signature ClueFlow stuff. */ - if (gtkhtml_editor_run_command (editor, "cursor-forward")) - gtkhtml_editor_run_command (editor, "insert-paragraph"); } - gtkhtml_editor_undo_end (editor); - gtkhtml_editor_run_command (editor, "cursor-position-restore"); - gtkhtml_editor_thaw (editor); - - composer->priv->in_signature_insert = FALSE; + composer_move_caret (composer); exit: g_object_unref (composer); } -static gboolean -is_null_or_none (const gchar *text) +static void +composer_web_view_load_status_changed_cb (WebKitWebView *webkit_web_view, + GParamSpec *pspec, + EMsgComposer *composer) { - return !text || g_strcmp0 (text, "none") == 0; + WebKitLoadStatus status; + + g_return_if_fail (E_IS_MSG_COMPOSER (composer)); + + status = webkit_web_view_get_load_status (webkit_web_view); + + if (status != WEBKIT_LOAD_FINISHED) + return; + + g_signal_handlers_disconnect_by_func ( + webkit_web_view, + G_CALLBACK (composer_web_view_load_status_changed_cb), + NULL); + + e_composer_update_signature (composer); } void @@ -1032,29 +1260,35 @@ e_composer_update_signature (EMsgComposer *composer) { EComposerHeaderTable *table; EMailSignatureComboBox *combo_box; - const gchar *signature_uid; + EHTMLEditor *editor; + EHTMLEditorView *view; + WebKitLoadStatus status; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - /* Do nothing if we're redirecting a message. */ - if (composer->priv->redirect) + /* Do nothing if we're redirecting a message or we disabled the signature * on purpose */ + if (composer->priv->redirect || composer->priv->disable_signature) return; table = e_msg_composer_get_header_table (composer); - signature_uid = e_composer_header_table_get_signature_uid (table); - - /* this is a case when the signature combo cleared itself for a reload */ - if (!signature_uid) - return; - - if (g_strcmp0 (signature_uid, composer->priv->selected_signature_uid) == 0 || - (is_null_or_none (signature_uid) && is_null_or_none (composer->priv->selected_signature_uid))) - return; - - g_free (composer->priv->selected_signature_uid); - composer->priv->selected_signature_uid = g_strdup (signature_uid); - combo_box = e_composer_header_table_get_signature_combo_box (table); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + + status = webkit_web_view_get_load_status (WEBKIT_WEB_VIEW (view)); + /* If document is not loaded, we will wait for him */ + if (status != WEBKIT_LOAD_FINISHED) { + /* Disconnect previous handlers */ + g_signal_handlers_disconnect_by_func ( + WEBKIT_WEB_VIEW (view), + G_CALLBACK (composer_web_view_load_status_changed_cb), + composer); + g_signal_connect ( + WEBKIT_WEB_VIEW(view), "notify::load-status", + G_CALLBACK (composer_web_view_load_status_changed_cb), + composer); + return; + } /* XXX Signature files should be local and therefore load quickly, * so while we do load them asynchronously we don't allow for diff --git a/composer/e-composer-private.h b/composer/e-composer-private.h index b09e025e0c..46c72b78ee 100644 --- a/composer/e-composer-private.h +++ b/composer/e-composer-private.h @@ -32,7 +32,6 @@ #include <libebackend/libebackend.h> #include "e-composer-actions.h" -#include "e-composer-activity.h" #include "e-composer-header-table.h" #ifdef HAVE_XFREE @@ -58,11 +57,11 @@ struct _EMsgComposerPrivate { gpointer shell; /* weak pointer */ + EHTMLEditor *editor; + /*** UI Management ***/ GtkWidget *header_table; - GtkWidget *activity_bar; - GtkWidget *alert_bar; GtkWidget *attachment_paned; EFocusTracker *focus_tracker; @@ -82,10 +81,6 @@ struct _EMsgComposerPrivate { GtkWidget *address_dialog; - GHashTable *inline_images; - GHashTable *inline_images_by_url; - GList *current_images; - gchar *mime_type; gchar *mime_body; gchar *charset; @@ -97,9 +92,18 @@ struct _EMsgComposerPrivate { CamelMimeMessage *redirect; + gboolean busy; + gboolean disable_signature; + gboolean is_from_draft; gboolean is_from_message; - - gchar *selected_signature_uid; + gboolean is_from_new_message; + /* The web view is uneditable while the editor is busy. + * This is used to restore the previous editable state. */ + gboolean saved_editable; + gboolean set_signature_from_message; + + gint focused_entry_selection_start; + gint focused_entry_selection_end; }; void e_composer_private_constructed (EMsgComposer *composer); @@ -121,6 +125,9 @@ gboolean e_composer_paste_text (EMsgComposer *composer, GtkClipboard *clipboard); gboolean e_composer_paste_uris (EMsgComposer *composer, GtkClipboard *clipboard); +gboolean e_composer_selection_is_base64_uris + (EMsgComposer *composer, + GtkSelectionData *selection); gboolean e_composer_selection_is_image_uris (EMsgComposer *composer, GtkSelectionData *selection); diff --git a/composer/e-composer-spell-header.c b/composer/e-composer-spell-header.c index c4fc471bc9..b2d2dfa2a4 100644 --- a/composer/e-composer-spell-header.c +++ b/composer/e-composer-spell-header.c @@ -63,16 +63,3 @@ e_composer_spell_header_new_button (ESourceRegistry *registry, "registry", registry, NULL); } -void -e_composer_spell_header_set_languages (EComposerSpellHeader *header, - GList *languages) -{ - ESpellEntry *spell_entry; - - g_return_if_fail (header != NULL); - - spell_entry = E_SPELL_ENTRY (E_COMPOSER_HEADER (header)->input_widget); - g_return_if_fail (spell_entry != NULL); - - e_spell_entry_set_languages (spell_entry, languages); -} diff --git a/composer/e-composer-spell-header.h b/composer/e-composer-spell-header.h index d440ee157c..f0034a0f84 100644 --- a/composer/e-composer-spell-header.h +++ b/composer/e-composer-spell-header.h @@ -66,9 +66,6 @@ EComposerHeader * e_composer_spell_header_new_button (ESourceRegistry *registry, const gchar *label); -void e_composer_spell_header_set_languages - (EComposerSpellHeader *header, - GList *languages); G_END_DECLS diff --git a/composer/e-msg-composer.c b/composer/e-msg-composer.c index 57b716c1ad..d3891beb4e 100644 --- a/composer/e-msg-composer.c +++ b/composer/e-msg-composer.c @@ -35,6 +35,7 @@ #include <unistd.h> #include <ctype.h> #include <fcntl.h> +#include <enchant/enchant.h> #include "e-composer-private.h" @@ -72,19 +73,22 @@ struct _AsyncContext { /* Flags for building a message. */ typedef enum { - COMPOSER_FLAG_HTML_CONTENT = 1 << 0, - COMPOSER_FLAG_SAVE_OBJECT_DATA = 1 << 1, - COMPOSER_FLAG_PRIORITIZE_MESSAGE = 1 << 2, - COMPOSER_FLAG_REQUEST_READ_RECEIPT = 1 << 3, - COMPOSER_FLAG_PGP_SIGN = 1 << 4, - COMPOSER_FLAG_PGP_ENCRYPT = 1 << 5, - COMPOSER_FLAG_SMIME_SIGN = 1 << 6, - COMPOSER_FLAG_SMIME_ENCRYPT = 1 << 7, - COMPOSER_FLAG_DRAFT = 1 << 8 + COMPOSER_FLAG_HTML_CONTENT = 1 << 0, + COMPOSER_FLAG_SAVE_OBJECT_DATA = 1 << 1, + COMPOSER_FLAG_PRIORITIZE_MESSAGE = 1 << 2, + COMPOSER_FLAG_REQUEST_READ_RECEIPT = 1 << 3, + COMPOSER_FLAG_PGP_SIGN = 1 << 4, + COMPOSER_FLAG_PGP_ENCRYPT = 1 << 5, + COMPOSER_FLAG_SMIME_SIGN = 1 << 6, + COMPOSER_FLAG_SMIME_ENCRYPT = 1 << 7, + COMPOSER_FLAG_HTML_MODE = 1 << 8, + COMPOSER_FLAG_SAVE_DRAFT = 1 << 9 } ComposerFlags; enum { PROP_0, + PROP_BUSY, + PROP_EDITOR, PROP_FOCUS_TRACKER, PROP_SHELL }; @@ -98,6 +102,24 @@ enum { LAST_SIGNAL }; +enum DndTargetType { + DND_TARGET_TYPE_TEXT_URI_LIST, + DND_TARGET_TYPE_MOZILLA_URL, + DND_TARGET_TYPE_TEXT_HTML, + DND_TARGET_TYPE_UTF8_STRING, + DND_TARGET_TYPE_TEXT_PLAIN, + DND_TARGET_TYPE_STRING +}; + +static GtkTargetEntry drag_dest_targets[] = { + { (gchar *) "text/uri-list", 0, DND_TARGET_TYPE_TEXT_URI_LIST }, + { (gchar *) "_NETSCAPE_URL", 0, DND_TARGET_TYPE_MOZILLA_URL }, + { (gchar *) "text/html", 0, DND_TARGET_TYPE_TEXT_HTML }, + { (gchar *) "UTF8_STRING", 0, DND_TARGET_TYPE_UTF8_STRING }, + { (gchar *) "text/plain", 0, DND_TARGET_TYPE_TEXT_PLAIN }, + { (gchar *) "STRING", 0, DND_TARGET_TYPE_STRING }, +}; + static guint signals[LAST_SIGNAL]; /* used by e_msg_composer_add_message_attachments () */ @@ -128,14 +150,10 @@ static void handle_multipart_signed (EMsgComposer *composer, GCancellable *cancellable, gint depth); -static void e_msg_composer_alert_sink_init (EAlertSinkInterface *iface); - G_DEFINE_TYPE_WITH_CODE ( EMsgComposer, e_msg_composer, - GTKHTML_TYPE_EDITOR, - G_IMPLEMENT_INTERFACE ( - E_TYPE_ALERT_SINK, e_msg_composer_alert_sink_init) + GTK_TYPE_WINDOW, G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)) static void @@ -407,45 +425,6 @@ best_charset (GByteArray *buf, return g_strdup (charset); } -static void -clear_current_images (EMsgComposer *composer) -{ - EMsgComposerPrivate *p = composer->priv; - g_list_free (p->current_images); - p->current_images = NULL; -} - -void -e_msg_composer_clear_inlined_table (EMsgComposer *composer) -{ - EMsgComposerPrivate *p = composer->priv; - - g_hash_table_remove_all (p->inline_images); - g_hash_table_remove_all (p->inline_images_by_url); -} - -static void -add_inlined_images (EMsgComposer *composer, - CamelMultipart *multipart) -{ - EMsgComposerPrivate *p = composer->priv; - - GList *d = p->current_images; - GHashTable *added; - - added = g_hash_table_new (g_direct_hash, g_direct_equal); - while (d) { - CamelMimePart *part = d->data; - - if (!g_hash_table_lookup (added, part)) { - camel_multipart_add_part (multipart, part); - g_hash_table_insert (added, part, part); - } - d = d->next; - } - g_hash_table_destroy (added); -} - /* These functions builds a CamelMimeMessage for the message that the user has * composed in 'composer'. */ @@ -1041,6 +1020,25 @@ composer_build_message_thread (GSimpleAsyncResult *simple, } static void +composer_add_evolution_composer_mode_header (CamelMedium *medium, + ComposerFlags flags) +{ + GString *string; + + string = g_string_sized_new (128); + + if (flags & COMPOSER_FLAG_HTML_MODE) + g_string_append (string, "text/html"); + else + g_string_append (string, "text/plain"); + + camel_medium_add_header ( + medium, "X-Evolution-Composer-Mode", string->str); + + g_string_free (string, TRUE); +} + +static void composer_add_evolution_format_header (CamelMedium *medium, ComposerFlags flags) { @@ -1082,7 +1080,6 @@ composer_build_message (EMsgComposer *composer, EMsgComposerPrivate *priv; GSimpleAsyncResult *simple; AsyncContext *context; - GtkhtmlEditor *editor; EAttachmentView *view; EAttachmentStore *store; EComposerHeaderTable *table; @@ -1103,7 +1100,6 @@ composer_build_message (EMsgComposer *composer, gint i; priv = composer->priv; - editor = GTKHTML_EDITOR (composer); table = e_msg_composer_get_header_table (composer); view = e_msg_composer_get_attachment_view (composer); store = e_attachment_view_get_store (view); @@ -1120,19 +1116,17 @@ composer_build_message (EMsgComposer *composer, context->session = e_msg_composer_ref_session (composer); context->from = e_msg_composer_get_from (composer); - if ((flags & COMPOSER_FLAG_DRAFT) == 0) { - if ((flags & COMPOSER_FLAG_PGP_SIGN) != 0) - context->pgp_sign = TRUE; + if (flags & COMPOSER_FLAG_PGP_SIGN) + context->pgp_sign = TRUE; - if ((flags & COMPOSER_FLAG_PGP_ENCRYPT) != 0) - context->pgp_encrypt = TRUE; + if (flags & COMPOSER_FLAG_PGP_ENCRYPT) + context->pgp_encrypt = TRUE; - if ((flags & COMPOSER_FLAG_SMIME_SIGN) != 0) - context->smime_sign = TRUE; + if (flags & COMPOSER_FLAG_SMIME_SIGN) + context->smime_sign = TRUE; - if ((flags & COMPOSER_FLAG_SMIME_ENCRYPT) != 0) - context->smime_encrypt = TRUE; - } + if (flags & COMPOSER_FLAG_SMIME_ENCRYPT) + context->smime_encrypt = TRUE; context->need_thread = context->pgp_sign || context->pgp_encrypt || @@ -1210,6 +1204,41 @@ composer_build_message (EMsgComposer *composer, composer_add_evolution_format_header ( CAMEL_MEDIUM (context->message), flags); + /* X-Evolution-Composer-Mode */ + composer_add_evolution_composer_mode_header ( + CAMEL_MEDIUM (context->message), flags); + + if (flags & COMPOSER_FLAG_SAVE_DRAFT) { + gchar *text; + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *selection; + + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + selection = e_html_editor_view_get_selection (view); + + data = g_byte_array_new (); + + e_html_editor_view_embed_styles (view); + e_html_editor_selection_save_caret_position (selection); + + text = e_html_editor_view_get_text_html_for_drafts (view); + + e_html_editor_view_remove_embed_styles (view); + e_html_editor_selection_restore_caret_position (selection); + + g_byte_array_append (data, (guint8 *) text, strlen (text)); + + g_free (text); + + type = camel_content_type_new ("text", "html"); + camel_content_type_set_param (type, "charset", "utf-8"); + iconv_charset = camel_iconv_charset_name ("utf-8"); + + goto wrap_drafts_html; + } + /* Build the text/plain part. */ if (priv->mime_body) { @@ -1235,11 +1264,14 @@ composer_build_message (EMsgComposer *composer, } else { gchar *text; - gsize length; + EHTMLEditor *editor; + EHTMLEditorView *view; + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); data = g_byte_array_new (); - text = gtkhtml_editor_get_text_plain (editor, &length); - g_byte_array_append (data, (guint8 *) text, (guint) length); + text = e_html_editor_view_get_text_plain (view); + g_byte_array_append (data, (guint8 *) text, strlen (text)); g_free (text); type = camel_content_type_new ("text", "plain"); @@ -1252,6 +1284,7 @@ composer_build_message (EMsgComposer *composer, } } + wrap_drafts_html: mem_stream = camel_stream_mem_new_with_byte_array (data); stream = camel_stream_filter_new (mem_stream); g_object_unref (mem_stream); @@ -1298,25 +1331,27 @@ composer_build_message (EMsgComposer *composer, * ... */ - if (flags & COMPOSER_FLAG_HTML_CONTENT) { + if ((flags & COMPOSER_FLAG_HTML_CONTENT) != 0 && + !(flags & COMPOSER_FLAG_SAVE_DRAFT)) { gchar *text; + guint count; gsize length; gboolean pre_encode; + EHTMLEditor *editor; + EHTMLEditorView *view; + GList *inline_images; - clear_current_images (composer); - - if (flags & COMPOSER_FLAG_SAVE_OBJECT_DATA) - gtkhtml_editor_run_command (editor, "save-data-on"); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + inline_images = e_html_editor_view_get_parts_for_inline_images (view); data = g_byte_array_new (); - text = gtkhtml_editor_get_text_html (editor, &length); + text = e_html_editor_view_get_text_html (view); + length = strlen (text); g_byte_array_append (data, (guint8 *) text, (guint) length); pre_encode = text_requires_quoted_printable (text, length); g_free (text); - if (flags & COMPOSER_FLAG_SAVE_OBJECT_DATA) - gtkhtml_editor_run_command (editor, "save-data-off"); - mem_stream = camel_stream_mem_new_with_byte_array (data); stream = camel_stream_filter_new (mem_stream); g_object_unref (mem_stream); @@ -1366,7 +1401,9 @@ composer_build_message (EMsgComposer *composer, /* If there are inlined images, construct a multipart/related * containing the multipart/alternative and the images. */ - if (priv->current_images) { + count = g_list_length (inline_images); + if (count > 0) { + guint ii; CamelMultipart *html_with_images; html_with_images = camel_multipart_new (); @@ -1385,8 +1422,12 @@ composer_build_message (EMsgComposer *composer, g_object_unref (body); - add_inlined_images (composer, html_with_images); - clear_current_images (composer); + for (ii = 0; ii < count; ii++) { + CamelMimePart *part = g_list_nth_data (inline_images, ii); + camel_multipart_add_part ( + html_with_images, part); + g_object_unref (part); + } context->top_level_part = CAMEL_DATA_WRAPPER (html_with_images); @@ -1493,7 +1534,8 @@ use_top_signature (EMsgComposer *composer) return top_signature; } -#define NO_SIGNATURE_TEXT \ +/* FIXME WEBKIT Nope....*/ +#define NO_SIGNATURE_TEXT \ "<!--+GtkHTML:<DATA class=\"ClueFlow\" " \ " key=\"signature\" " \ " value=\"1\">-->" \ @@ -1506,7 +1548,8 @@ set_editor_text (EMsgComposer *composer, const gchar *text, gboolean set_signature) { - gchar *body = NULL; + EHTMLEditor *editor; + EHTMLEditorView *view; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); g_return_if_fail (text != NULL); @@ -1527,20 +1570,23 @@ set_editor_text (EMsgComposer *composer, /* "Edit as New Message" sets "priv->is_from_message". * Always put the signature at the bottom for that case. */ + + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + if (!composer->priv->is_from_message && use_top_signature (composer)) { + gchar *body; /* put marker to the top */ - body = g_strdup_printf ("<BR>" NO_SIGNATURE_TEXT "%s", text); + body = g_strdup_printf ("<BR>%s", text); + e_html_editor_view_set_text_html (view, body); + g_free (body); } else { - /* no marker => to the bottom */ - body = g_strdup_printf ("%s<BR>", text); + e_html_editor_view_set_text_html (view, text); } - gtkhtml_editor_set_text_html (GTKHTML_EDITOR (composer), body, -1); - if (set_signature) e_composer_update_signature (composer); - g_free (body); } /* Miscellaneous callbacks. */ @@ -1548,12 +1594,16 @@ set_editor_text (EMsgComposer *composer, static void attachment_store_changed_cb (EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; /* Mark the editor as changed so it prompts about unsaved * changes on close. */ - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, TRUE); + editor = e_msg_composer_get_editor (composer); + if (editor) { + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, TRUE); + } } static void @@ -1648,11 +1698,11 @@ msg_composer_paste_clipboard_targets_cb (GtkClipboard *clipboard, gint n_targets, EMsgComposer *composer) { - GtkhtmlEditor *editor; - gboolean html_mode; + EHTMLEditor *editor; + EHTMLEditorView *view; - editor = GTKHTML_EDITOR (composer); - html_mode = gtkhtml_editor_get_html_mode (editor); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); /* Order is important here to ensure common use cases are * handled correctly. See GNOME bug #603715 for details. */ @@ -1663,7 +1713,7 @@ msg_composer_paste_clipboard_targets_cb (GtkClipboard *clipboard, } /* Only paste HTML content in HTML mode. */ - if (html_mode) { + if (e_html_editor_view_get_html_mode (view)) { if (e_targets_include_html (targets, n_targets)) { e_composer_paste_html (composer, clipboard); return; @@ -1682,48 +1732,31 @@ msg_composer_paste_clipboard_targets_cb (GtkClipboard *clipboard, } static void -msg_composer_paste_clipboard_cb (EWebViewGtkHTML *web_view, - EMsgComposer *composer) +msg_composer_paste_primary_clipboard_cb (EHTMLEditorView *view, + EMsgComposer *composer) { GtkClipboard *clipboard; - clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY); gtk_clipboard_request_targets ( clipboard, (GtkClipboardTargetsReceivedFunc) msg_composer_paste_clipboard_targets_cb, composer); - - g_signal_stop_emission_by_name (web_view, "paste-clipboard"); } static void -msg_composer_realize_gtkhtml_cb (GtkWidget *widget, +msg_composer_paste_clipboard_cb (EHTMLEditorView *view, EMsgComposer *composer) { - EAttachmentView *view; - GtkTargetList *target_list; - GtkTargetEntry *targets; - gint n_targets; - - /* XXX GtkHTML doesn't set itself up as a drag destination until - * it's realized, and we need to amend to its target list so - * it will accept the same drag targets as the attachment bar. - * Do this any earlier and GtkHTML will just overwrite us. */ - - /* When redirecting a message, the message body is not - * editable and therefore cannot be a drag destination. */ - if (!e_web_view_gtkhtml_get_editable (E_WEB_VIEW_GTKHTML (widget))) - return; - - view = e_msg_composer_get_attachment_view (composer); + GtkClipboard *clipboard; - target_list = e_attachment_view_get_target_list (view); - targets = gtk_target_table_new_from_list (target_list, &n_targets); + clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); - target_list = gtk_drag_dest_get_target_list (widget); - gtk_target_list_add_table (target_list, targets, n_targets); + gtk_clipboard_request_targets ( + clipboard, (GtkClipboardTargetsReceivedFunc) + msg_composer_paste_clipboard_targets_cb, composer); - gtk_target_table_free (targets, n_targets); + g_signal_stop_emission_by_name (view, "paste-clipboard"); } static gboolean @@ -1744,6 +1777,31 @@ msg_composer_drag_motion_cb (GtkWidget *widget, return e_attachment_view_drag_motion (view, context, x, y, time); } +static gchar * +next_uri (guchar **uri_list, + gint *len, + gint *list_len) +{ + guchar *uri, *begin; + + begin = *uri_list; + *len = 0; + while (**uri_list && **uri_list != '\n' && **uri_list != '\r' && *list_len) { + (*uri_list) ++; + (*len) ++; + (*list_len) --; + } + + uri = (guchar *) g_strndup ((gchar *) begin, *len); + + while ((!**uri_list || **uri_list == '\n' || **uri_list == '\r') && *list_len) { + (*uri_list) ++; + (*list_len) --; + } + + return (gchar *) uri; +} + static void msg_composer_drag_data_received_cb (GtkWidget *widget, GdkDragContext *context, @@ -1755,43 +1813,87 @@ msg_composer_drag_data_received_cb (GtkWidget *widget, EMsgComposer *composer) { EAttachmentView *view; + EHTMLEditor *editor; + EHTMLEditorView *html_editor_view; + EHTMLEditorSelection *editor_selection; - /* HTML mode has a few special cases for drops... */ - if (gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (composer))) { + editor = e_msg_composer_get_editor (composer); + html_editor_view = e_html_editor_get_view (editor); + editor_selection = e_html_editor_view_get_selection (html_editor_view); + /* HTML mode has a few special cases for drops... */ + if (e_html_editor_view_get_html_mode (html_editor_view)) { /* If we're receiving an image, we want the image to be * inserted in the message body. Let GtkHtml handle it. */ + /* FIXME WebKit - how to reproduce this? if (gtk_selection_data_targets_include_image (selection, TRUE)) return; - + */ /* If we're receiving URIs and -all- the URIs point to * image files, we want the image(s) to be inserted in - * the message body. Let GtkHtml handle it. */ - if (e_composer_selection_is_image_uris (composer, selection)) - return; - } + * the message body. */ + if (e_composer_selection_is_image_uris (composer, selection)) { + const guchar *data; + gint length; + gint list_len, len; + gchar *uri; + + data = gtk_selection_data_get_data (selection); + length = gtk_selection_data_get_length (selection); + + if (!data || length < 0) + return; + + list_len = length; + do { + uri = next_uri ((guchar **) &data, &len, &list_len); + e_html_editor_selection_insert_image (editor_selection, uri); + } while (list_len); + } - view = e_msg_composer_get_attachment_view (composer); + if (e_composer_selection_is_base64_uris (composer, selection)) { + const guchar *data; + gint length; + gint list_len, len; + gchar *uri; - /* Forward the data to the attachment view. Note that calling - * e_attachment_view_drag_data_received() will not work because - * that function only handles the case where all the other drag - * handlers have failed. */ - e_attachment_paned_drag_data_received ( - E_ATTACHMENT_PANED (view), - context, x, y, selection, info, time); + data = gtk_selection_data_get_data (selection); + length = gtk_selection_data_get_length (selection); - /* Stop the signal from propagating to GtkHtml. */ + if (!data || length < 0) + return; + + list_len = length; + do { + uri = next_uri ((guchar **) &data, &len, &list_len); + + e_html_editor_selection_insert_image (editor_selection, uri); + } while (list_len); + } + } else { + view = e_msg_composer_get_attachment_view (composer); + + /* Forward the data to the attachment view. Note that calling + * e_attachment_view_drag_data_received() will not work because + * that function only handles the case where all the other drag + * handlers have failed. */ + e_attachment_paned_drag_data_received ( + E_ATTACHMENT_PANED (view), + context, x, y, selection, info, time); + } + /* Stop the signal from propagating */ g_signal_stop_emission_by_name (widget, "drag-data-received"); } static void msg_composer_notify_header_cb (EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, TRUE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, TRUE); } static gboolean @@ -1892,6 +1994,12 @@ msg_composer_get_property (GObject *object, GParamSpec *pspec) { switch (property_id) { + case PROP_BUSY: + g_value_set_boolean ( + value, e_msg_composer_is_busy ( + E_MSG_COMPOSER (object))); + return; + case PROP_FOCUS_TRACKER: g_value_set_object ( value, e_msg_composer_get_focus_tracker ( @@ -1951,30 +2059,71 @@ msg_composer_gallery_drag_data_get (GtkIconView *icon_view, } static void +composer_notify_activity_cb (EActivityBar *activity_bar, + GParamSpec *pspec, + EMsgComposer *composer) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + WebKitWebView *web_view; + gboolean editable; + gboolean busy; + + busy = (e_activity_bar_get_activity (activity_bar) != NULL); + + if (busy == composer->priv->busy) + return; + + composer->priv->busy = busy; + + if (busy) + e_msg_composer_save_focused_widget (composer); + + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + web_view = WEBKIT_WEB_VIEW (view); + + if (busy) { + editable = webkit_web_view_get_editable (web_view); + webkit_web_view_set_editable (web_view, FALSE); + composer->priv->saved_editable = editable; + } else { + editable = composer->priv->saved_editable; + webkit_web_view_set_editable (web_view, editable); + } + + g_object_notify (G_OBJECT (composer), "busy"); + + if (!busy) + e_msg_composer_restore_focus_on_composer (composer); +} + +static void msg_composer_constructed (GObject *object) { EShell *shell; - GtkhtmlEditor *editor; EMsgComposer *composer; + EActivityBar *activity_bar; EAttachmentView *view; EAttachmentStore *store; EComposerHeaderTable *table; - EWebViewGtkHTML *web_view; + EHTMLEditor *editor; + EHTMLEditorView *html_editor_view; GtkUIManager *ui_manager; GtkToggleAction *action; GSettings *settings; const gchar *id; gboolean active; - editor = GTKHTML_EDITOR (object); composer = E_MSG_COMPOSER (object); shell = e_msg_composer_get_shell (composer); e_composer_private_constructed (composer); - web_view = e_msg_composer_get_web_view (composer); - ui_manager = gtkhtml_editor_get_ui_manager (editor); + editor = e_msg_composer_get_editor (composer); + html_editor_view = e_html_editor_get_view (editor); + ui_manager = e_html_editor_get_ui_manager (editor); view = e_msg_composer_get_attachment_view (composer); table = E_COMPOSER_HEADER_TABLE (composer->priv->header_table); @@ -2004,6 +2153,12 @@ msg_composer_constructed (GObject *object) "/org/gnome/evolution/mail/composer-window/", E_RESTORE_WINDOW_SIZE); + activity_bar = e_html_editor_get_activity_bar (editor); + g_signal_connect ( + activity_bar, "notify::activity", + G_CALLBACK (composer_notify_activity_cb), composer); + + /* Honor User Preferences */ /* FIXME This should be an EMsgComposer property. */ @@ -2016,21 +2171,21 @@ msg_composer_constructed (GObject *object) /* Clipboard Support */ g_signal_connect ( - web_view, "paste-clipboard", + html_editor_view, "paste-clipboard", G_CALLBACK (msg_composer_paste_clipboard_cb), composer); - /* Drag-and-Drop Support */ - g_signal_connect ( - web_view, "realize", - G_CALLBACK (msg_composer_realize_gtkhtml_cb), composer); + html_editor_view, "paste-primary-clipboard", + G_CALLBACK (msg_composer_paste_primary_clipboard_cb), composer); + + /* Drag-and-Drop Support */ g_signal_connect ( - web_view, "drag-motion", + html_editor_view, "drag-motion", G_CALLBACK (msg_composer_drag_motion_cb), composer); g_signal_connect ( - web_view, "drag-data-received", + html_editor_view, "drag-data-received", G_CALLBACK (msg_composer_drag_data_received_cb), composer); g_signal_connect ( @@ -2079,7 +2234,13 @@ msg_composer_constructed (GObject *object) G_CALLBACK (attachment_store_changed_cb), composer); /* Initialization may have tripped the "changed" state. */ - gtkhtml_editor_set_changed (editor, FALSE); + e_html_editor_view_set_changed (html_editor_view, FALSE); + + gtk_drag_dest_set ( + GTK_WIDGET (html_editor_view), + GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, + drag_dest_targets, G_N_ELEMENTS (drag_dest_targets), + GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK); id = "org.gnome.evolution.composer"; e_plugin_ui_register_manager (ui_manager, id, composer); @@ -2087,6 +2248,7 @@ msg_composer_constructed (GObject *object) e_extensible_load_extensions (E_EXTENSIBLE (composer)); + e_msg_composer_set_body_text (composer, "", TRUE); /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (e_msg_composer_parent_class)->constructed (object); } @@ -2119,14 +2281,19 @@ msg_composer_dispose (GObject *object) static void msg_composer_map (GtkWidget *widget) { + EMsgComposer *composer; EComposerHeaderTable *table; GtkWidget *input_widget; + EHTMLEditor *editor; + EHTMLEditorView *view; const gchar *text; /* Chain up to parent's map() method. */ GTK_WIDGET_CLASS (e_msg_composer_parent_class)->map (widget); - table = e_msg_composer_get_header_table (E_MSG_COMPOSER (widget)); + composer = E_MSG_COMPOSER (widget); + editor = e_msg_composer_get_editor (composer); + table = e_msg_composer_get_header_table (composer); /* If the 'To' field is empty, focus it. */ input_widget = @@ -2149,7 +2316,8 @@ msg_composer_map (GtkWidget *widget) } /* Jump to the editor as a last resort. */ - gtkhtml_editor_run_command (GTKHTML_EDITOR (widget), "grab-focus"); + view = e_html_editor_get_view (editor); + gtk_widget_grab_focus (GTK_WIDGET (view)); } static gboolean @@ -2158,12 +2326,12 @@ msg_composer_key_press_event (GtkWidget *widget, { EMsgComposer *composer; GtkWidget *input_widget; - GtkhtmlEditor *editor; - EWebViewGtkHTML *web_view; + EHTMLEditor *editor; + EHTMLEditorView *view; - editor = GTKHTML_EDITOR (widget); composer = E_MSG_COMPOSER (widget); - web_view = e_msg_composer_get_web_view (composer); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); input_widget = e_composer_header_table_get_header ( @@ -2183,12 +2351,12 @@ msg_composer_key_press_event (GtkWidget *widget, } if (event->keyval == GDK_KEY_Tab && gtk_widget_is_focus (input_widget)) { - gtkhtml_editor_run_command (editor, "grab-focus"); + gtk_widget_grab_focus (GTK_WIDGET (view)); return TRUE; } if (event->keyval == GDK_KEY_ISO_Left_Tab && - gtk_widget_is_focus (GTK_WIDGET (web_view))) { + gtk_widget_is_focus (GTK_WIDGET (view))) { gtk_widget_grab_focus (input_widget); return TRUE; } @@ -2198,160 +2366,6 @@ msg_composer_key_press_event (GtkWidget *widget, key_press_event (widget, event); } -static void -msg_composer_cut_clipboard (GtkhtmlEditor *editor) -{ - /* Do nothing. EFocusTracker handles this. */ -} - -static void -msg_composer_copy_clipboard (GtkhtmlEditor *editor) -{ - /* Do nothing. EFocusTracker handles this. */ -} - -static void -msg_composer_paste_clipboard (GtkhtmlEditor *editor) -{ - /* Do nothing. EFocusTracker handles this. */ -} - -static void -msg_composer_select_all (GtkhtmlEditor *editor) -{ - /* Do nothing. EFocusTracker handles this. */ -} - -static void -msg_composer_command_before (GtkhtmlEditor *editor, - const gchar *command) -{ - EMsgComposer *composer; - const gchar *data; - - composer = E_MSG_COMPOSER (editor); - - if (strcmp (command, "insert-paragraph") != 0) - return; - - if (composer->priv->in_signature_insert) - return; - - data = gtkhtml_editor_get_paragraph_data (editor, "orig"); - if (data != NULL && *data == '1') { - gtkhtml_editor_run_command (editor, "text-default-color"); - gtkhtml_editor_run_command (editor, "italic-off"); - return; - }; - - data = gtkhtml_editor_get_paragraph_data (editor, "signature"); - if (data != NULL && *data == '1') { - gtkhtml_editor_run_command (editor, "text-default-color"); - gtkhtml_editor_run_command (editor, "italic-off"); - } -} - -static void -msg_composer_command_after (GtkhtmlEditor *editor, - const gchar *command) -{ - EMsgComposer *composer; - const gchar *data; - - composer = E_MSG_COMPOSER (editor); - - if (strcmp (command, "insert-paragraph") != 0) - return; - - if (composer->priv->in_signature_insert) - return; - - gtkhtml_editor_run_command (editor, "italic-off"); - - data = gtkhtml_editor_get_paragraph_data (editor, "orig"); - if (data != NULL && *data == '1') - e_msg_composer_reply_indent (composer); - gtkhtml_editor_set_paragraph_data (editor, "orig", "0"); - - data = gtkhtml_editor_get_paragraph_data (editor, "signature"); - if (data == NULL || *data != '1') - return; - - /* Clear the signature. */ - if (gtkhtml_editor_is_paragraph_empty (editor)) - gtkhtml_editor_set_paragraph_data (editor, "signature" ,"0"); - - else if (gtkhtml_editor_is_previous_paragraph_empty (editor) && - gtkhtml_editor_run_command (editor, "cursor-backward")) { - - gtkhtml_editor_set_paragraph_data (editor, "signature", "0"); - gtkhtml_editor_run_command (editor, "cursor-forward"); - } - - gtkhtml_editor_run_command (editor, "text-default-color"); - gtkhtml_editor_run_command (editor, "italic-off"); -} - -static gchar * -msg_composer_image_uri (GtkhtmlEditor *editor, - const gchar *uri) -{ - EMsgComposer *composer; - GHashTable *hash_table; - CamelMimePart *part; - const gchar *cid; - - composer = E_MSG_COMPOSER (editor); - - hash_table = composer->priv->inline_images_by_url; - part = g_hash_table_lookup (hash_table, uri); - - if (part == NULL && g_str_has_prefix (uri, "file:")) - part = e_msg_composer_add_inline_image_from_file ( - composer, uri + 5); - - if (part == NULL && g_str_has_prefix (uri, "cid:")) { - hash_table = composer->priv->inline_images; - part = g_hash_table_lookup (hash_table, uri); - } - - if (part == NULL) - return NULL; - - composer->priv->current_images = - g_list_prepend (composer->priv->current_images, part); - - cid = camel_mime_part_get_content_id (part); - if (cid == NULL) - return NULL; - - return g_strconcat ("cid:", cid, NULL); -} - -static void -msg_composer_object_deleted (GtkhtmlEditor *editor) -{ - const gchar *data; - - if (!gtkhtml_editor_is_paragraph_empty (editor)) - return; - - data = gtkhtml_editor_get_paragraph_data (editor, "orig"); - if (data != NULL && *data == '1') { - gtkhtml_editor_set_paragraph_data (editor, "orig", "0"); - gtkhtml_editor_run_command (editor, "indent-zero"); - gtkhtml_editor_run_command (editor, "style-normal"); - gtkhtml_editor_run_command (editor, "text-default-color"); - gtkhtml_editor_run_command (editor, "italic-off"); - gtkhtml_editor_run_command (editor, "insert-paragraph"); - gtkhtml_editor_run_command (editor, "delete-back"); - } - - data = gtkhtml_editor_get_paragraph_data (editor, "signature"); - if (data != NULL && *data == '1') - gtkhtml_editor_set_paragraph_data (editor, "signature", "0"); -} - static gboolean msg_composer_presend (EMsgComposer *composer) { @@ -2359,34 +2373,6 @@ msg_composer_presend (EMsgComposer *composer) return TRUE; } -static void -msg_composer_submit_alert (EAlertSink *alert_sink, - EAlert *alert) -{ - EMsgComposerPrivate *priv; - EAlertBar *alert_bar; - GtkWidget *dialog; - GtkWindow *parent; - - priv = E_MSG_COMPOSER_GET_PRIVATE (alert_sink); - - switch (e_alert_get_message_type (alert)) { - case GTK_MESSAGE_INFO: - case GTK_MESSAGE_WARNING: - case GTK_MESSAGE_ERROR: - alert_bar = E_ALERT_BAR (priv->alert_bar); - e_alert_bar_add_alert (alert_bar, alert); - break; - - default: - parent = GTK_WINDOW (alert_sink); - dialog = e_alert_dialog_new (parent, alert); - gtk_dialog_run (GTK_DIALOG (dialog)); - gtk_widget_destroy (dialog); - break; - } -} - static gboolean msg_composer_accumulator_false_abort (GSignalInvocationHint *ihint, GValue *return_accu, @@ -2402,12 +2388,27 @@ msg_composer_accumulator_false_abort (GSignalInvocationHint *ihint, return v_boolean; } +/** + * e_msg_composer_is_busy: + * @composer: an #EMsgComposer + * + * Returns %TRUE only while an #EActivity is in progress. + * + * Returns: whether @composer is busy + **/ +gboolean +e_msg_composer_is_busy (EMsgComposer *composer) +{ + g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE); + + return composer->priv->busy; +} + static void e_msg_composer_class_init (EMsgComposerClass *class) { GObjectClass *object_class; GtkWidgetClass *widget_class; - GtkhtmlEditorClass *editor_class; g_type_class_add_private (class, sizeof (EMsgComposerPrivate)); @@ -2422,21 +2423,21 @@ e_msg_composer_class_init (EMsgComposerClass *class) widget_class->map = msg_composer_map; widget_class->key_press_event = msg_composer_key_press_event; - editor_class = GTKHTML_EDITOR_CLASS (class); - editor_class->cut_clipboard = msg_composer_cut_clipboard; - editor_class->copy_clipboard = msg_composer_copy_clipboard; - editor_class->paste_clipboard = msg_composer_paste_clipboard; - editor_class->select_all = msg_composer_select_all; - editor_class->command_before = msg_composer_command_before; - editor_class->command_after = msg_composer_command_after; - editor_class->image_uri = msg_composer_image_uri; - editor_class->link_clicked = NULL; /* EWebView handles this */ - editor_class->object_deleted = msg_composer_object_deleted; - class->presend = msg_composer_presend; g_object_class_install_property ( object_class, + PROP_BUSY, + g_param_spec_boolean ( + "busy", + "Busy", + "Whether an activity is in progress", + FALSE, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property ( + object_class, PROP_FOCUS_TRACKER, g_param_spec_object ( "focus-tracker", @@ -2512,15 +2513,11 @@ e_msg_composer_class_init (EMsgComposerClass *class) } static void -e_msg_composer_alert_sink_init (EAlertSinkInterface *iface) -{ - iface->submit_alert = msg_composer_submit_alert; -} - -static void e_msg_composer_init (EMsgComposer *composer) { composer->priv = E_MSG_COMPOSER_GET_PRIVATE (composer); + + composer->priv->editor = g_object_ref_sink (e_html_editor_new ()); } /** @@ -2538,7 +2535,23 @@ e_msg_composer_new (EShell *shell) return g_object_new ( E_TYPE_MSG_COMPOSER, - "html", e_web_view_gtkhtml_new (), "shell", shell, NULL); + "shell", shell, NULL); +} + +/** + * e_msg_composer_get_editor: + * @composer: an #EMsgComposer + * + * Returns @composer's internal #EHTMLEditor instance. + * + * Returns: an #EHTMLEditor + **/ +EHTMLEditor * +e_msg_composer_get_editor (EMsgComposer *composer) +{ + g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); + + return composer->priv->editor; } EFocusTracker * @@ -2581,12 +2594,16 @@ add_attachments_handle_mime_part (EMsgComposer *composer, { CamelContentType *content_type; CamelDataWrapper *wrapper; + EHTMLEditor *editor; + EHTMLEditorView *view; if (!mime_part) return; content_type = camel_mime_part_get_content_type (mime_part); wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part)); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); if (CAMEL_IS_MULTIPART (wrapper)) { /* another layer of multipartness... */ @@ -2596,10 +2613,10 @@ add_attachments_handle_mime_part (EMsgComposer *composer, } else if (just_inlines) { if (camel_mime_part_get_content_id (mime_part) || camel_mime_part_get_content_location (mime_part)) - e_msg_composer_add_inline_image_from_mime_part ( - composer, mime_part); + e_html_editor_view_add_inline_image_from_mime_part ( + view, mime_part); } else if (related && camel_content_type_is (content_type, "image", "*")) { - e_msg_composer_add_inline_image_from_mime_part (composer, mime_part); + e_html_editor_view_add_inline_image_from_mime_part (view, mime_part); } else if (camel_content_type_is (content_type, "text", "*") && camel_mime_part_get_filename (mime_part) == NULL) { /* Do nothing if this is a text/anything without a @@ -2988,16 +3005,40 @@ handle_multipart (EMsgComposer *composer, /* Since the first part is not multipart/alternative, * this must be the body. */ - html = emcu_part_to_html ( - composer, mime_part, &length, keep_signature, cancellable); - if (html) - e_msg_composer_set_pending_body (composer, html, length); + + /* If we are opening message from Drafts */ + if (composer->priv->is_from_draft) { + /* Extract the body */ + CamelDataWrapper *dw; + + dw = camel_medium_get_content ((CamelMedium *) mime_part); + if (dw) { + CamelStream *mem = camel_stream_mem_new (); + GByteArray *bytes; + + camel_data_wrapper_decode_to_stream_sync (dw, mem, cancellable, NULL); + camel_stream_close (mem, cancellable, NULL); + + bytes = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (mem)); + if (bytes && bytes->len) + html = g_strndup ((const gchar *) bytes->data, bytes->len); + + g_object_unref (mem); + } + } else { + html = emcu_part_to_html ( + composer, mime_part, &length, keep_signature, cancellable); + } + e_msg_composer_set_pending_body (composer, html, length); } else if (camel_mime_part_get_content_id (mime_part) || camel_mime_part_get_content_location (mime_part)) { /* special in-line attachment */ - e_msg_composer_add_inline_image_from_mime_part ( - composer, mime_part); + EHTMLEditor *editor; + + editor = e_msg_composer_get_editor (composer); + e_html_editor_view_add_inline_image_from_mime_part ( + e_html_editor_get_view (editor), mime_part); } else { /* normal attachment */ @@ -3009,28 +3050,46 @@ handle_multipart (EMsgComposer *composer, static void set_signature_gui (EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; + WebKitDOMDocument *document; + WebKitDOMNodeList *nodes; EComposerHeaderTable *table; EMailSignatureComboBox *combo_box; - const gchar *data; gchar *uid; + gulong ii, length; - editor = GTKHTML_EDITOR (composer); table = e_msg_composer_get_header_table (composer); combo_box = e_composer_header_table_get_signature_combo_box (table); - if (!gtkhtml_editor_search_by_data (editor, 1, "ClueFlow", "signature", "1")) - return; - - data = gtkhtml_editor_get_paragraph_data (editor, "signature_name"); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); - if (!g_str_has_prefix (data, "uid:")) - return; + uid = NULL; + nodes = webkit_dom_document_get_elements_by_class_name ( + document, "-x-evo-signature"); + length = webkit_dom_node_list_get_length (nodes); + for (ii = 0; ii < length; ii++) { + WebKitDOMNode *node; + gchar *id; + + node = webkit_dom_node_list_item (nodes, ii); + id = webkit_dom_element_get_id (WEBKIT_DOM_ELEMENT (node)); + if (id && (strlen (id) == 1) && (*id == '1')) { + uid = webkit_dom_element_get_attribute ( + WEBKIT_DOM_ELEMENT (node), "name"); + g_free (id); + break; + } + g_free (id); + } /* The combo box active ID is the signature's ESource UID. */ - uid = e_composer_decode_clue_value (data + 4); - gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), uid); - g_free (uid); + if (uid != NULL) { + gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), uid); + g_free (uid); + } } static void @@ -3100,7 +3159,7 @@ e_msg_composer_new_with_message (EShell *shell, { CamelInternetAddress *to, *cc, *bcc; GList *To = NULL, *Cc = NULL, *Bcc = NULL, *postto = NULL; - const gchar *format, *subject; + const gchar *format, *subject, *composer_mode; EDestination **Tov, **Ccv, **Bccv; GHashTable *auto_cc, *auto_bcc; CamelContentType *content_type; @@ -3110,6 +3169,8 @@ e_msg_composer_new_with_message (EShell *shell, EMsgComposerPrivate *priv; EComposerHeaderTable *table; ESource *source = NULL; + EHTMLEditor *editor; + EHTMLEditorView *view; GtkToggleAction *action; struct _camel_header_raw *xev; gchar *identity_uid; @@ -3131,7 +3192,9 @@ e_msg_composer_new_with_message (EShell *shell, composer = e_msg_composer_new (shell); priv = E_MSG_COMPOSER_GET_PRIVATE (composer); + editor = e_msg_composer_get_editor (composer); table = e_msg_composer_get_header_table (composer); + view = e_html_editor_get_view (editor); if (postto) { e_composer_header_table_set_post_to_list (table, postto); @@ -3250,6 +3313,13 @@ e_msg_composer_new_with_message (EShell *shell, /* Restore the format editing preference */ format = camel_medium_get_header ( CAMEL_MEDIUM (message), "X-Evolution-Format"); + + composer_mode = camel_medium_get_header ( + CAMEL_MEDIUM (message), "X-Evolution-Composer-Mode"); + + if (composer_mode && *composer_mode) + composer->priv->is_from_draft = TRUE; + if (format != NULL) { gchar **flags; @@ -3259,11 +3329,21 @@ e_msg_composer_new_with_message (EShell *shell, flags = g_strsplit (format, ", ", 0); for (i = 0; flags[i]; i++) { if (g_ascii_strcasecmp (flags[i], "text/html") == 0) { - gtkhtml_editor_set_html_mode ( - GTKHTML_EDITOR (composer), TRUE); + if (g_ascii_strcasecmp (composer_mode, "text/html") == 0) { + e_html_editor_view_set_html_mode ( + view, TRUE); + } else { + e_html_editor_view_set_html_mode ( + view, FALSE); + } } else if (g_ascii_strcasecmp (flags[i], "text/plain") == 0) { - gtkhtml_editor_set_html_mode ( - GTKHTML_EDITOR (composer), FALSE); + if (g_ascii_strcasecmp (composer_mode, "text/html") == 0) { + e_html_editor_view_set_html_mode ( + view, TRUE); + } else { + e_html_editor_view_set_html_mode ( + view, FALSE); + } } else if (g_ascii_strcasecmp (flags[i], "pgp-sign") == 0) { action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN)); gtk_toggle_action_set_active (action, TRUE); @@ -3367,14 +3447,35 @@ e_msg_composer_new_with_message (EShell *shell, ACTION (SMIME_ENCRYPT)), TRUE); } - html = emcu_part_to_html ( - composer, CAMEL_MIME_PART (message), - &length, keep_signature, cancellable); - if (html) - e_msg_composer_set_pending_body (composer, html, length); + /* If we are opening message from Drafts */ + if (composer->priv->is_from_draft) { + /* Extract the body */ + CamelDataWrapper *dw; + + dw = camel_medium_get_content ((CamelMedium *) mime_part); + if (dw) { + CamelStream *mem = camel_stream_mem_new (); + GByteArray *bytes; + + camel_data_wrapper_decode_to_stream_sync (dw, mem, cancellable, NULL); + camel_stream_close (mem, cancellable, NULL); + + bytes = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (mem)); + if (bytes && bytes->len) + html = g_strndup ((const gchar *) bytes->data, bytes->len); + + g_object_unref (mem); + } + } else { + html = emcu_part_to_html ( + composer, CAMEL_MIME_PART (message), + &length, keep_signature, cancellable); + } + e_msg_composer_set_pending_body (composer, html, length); } priv->is_from_message = TRUE; + priv->set_signature_from_message = TRUE; /* We wait until now to set the body text because we need to * ensure that the attachment bar has all the attachments before @@ -3403,7 +3504,8 @@ e_msg_composer_new_redirect (EShell *shell, { EMsgComposer *composer; EComposerHeaderTable *table; - EWebViewGtkHTML *web_view; + EHTMLEditor *editor; + EHTMLEditorView *view; const gchar *subject; g_return_val_if_fail (E_IS_SHELL (shell), NULL); @@ -3421,8 +3523,9 @@ e_msg_composer_new_redirect (EShell *shell, e_composer_header_table_set_identity_uid (table, identity_uid); e_composer_header_table_set_subject (table, subject); - web_view = e_msg_composer_get_web_view (composer); - e_web_view_gtkhtml_set_editable (web_view, FALSE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + webkit_web_view_set_editable (WEBKIT_WEB_VIEW (view), FALSE); return composer; } @@ -3473,30 +3576,6 @@ e_msg_composer_get_shell (EMsgComposer *composer) return E_SHELL (composer->priv->shell); } -/** - * e_msg_composer_get_web_view: - * @composer: an #EMsgComposer - * - * Returns the #EWebView widget in @composer. - * - * Returns: the #EWebView - **/ -EWebViewGtkHTML * -e_msg_composer_get_web_view (EMsgComposer *composer) -{ - GtkHTML *html; - GtkhtmlEditor *editor; - - g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); - - /* This is a convenience function to avoid - * repeating this awkwardness everywhere */ - editor = GTKHTML_EDITOR (composer); - html = gtkhtml_editor_get_html (editor); - - return E_WEB_VIEW_GTKHTML (html); -} - static void msg_composer_send_cb (EMsgComposer *composer, GAsyncResult *result, @@ -3504,7 +3583,8 @@ msg_composer_send_cb (EMsgComposer *composer, { CamelMimeMessage *message; EAlertSink *alert_sink; - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; GError *error = NULL; alert_sink = e_activity_get_alert_sink (context->activity); @@ -3536,8 +3616,9 @@ msg_composer_send_cb (EMsgComposer *composer, g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); /* The callback can set editor 'changed' if anything failed. */ - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, FALSE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, TRUE); g_signal_emit ( composer, signals[SEND], 0, @@ -3557,9 +3638,8 @@ msg_composer_send_cb (EMsgComposer *composer, void e_msg_composer_send (EMsgComposer *composer) { + EHTMLEditor *editor; AsyncContext *context; - EAlertSink *alert_sink; - EActivityBar *activity_bar; GCancellable *cancellable; gboolean proceed_with_send = TRUE; @@ -3573,18 +3653,12 @@ e_msg_composer_send (EMsgComposer *composer) return; } - context = g_slice_new0 (AsyncContext); - context->activity = e_composer_activity_new (composer); - - alert_sink = E_ALERT_SINK (composer); - e_activity_set_alert_sink (context->activity, alert_sink); + editor = e_msg_composer_get_editor (composer); - cancellable = camel_operation_new (); - e_activity_set_cancellable (context->activity, cancellable); - g_object_unref (cancellable); + context = g_slice_new0 (AsyncContext); + context->activity = e_html_editor_new_activity (editor); - activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar); - e_activity_bar_set_activity (activity_bar, context->activity); + cancellable = e_activity_get_cancellable (context->activity); e_msg_composer_get_message ( composer, G_PRIORITY_DEFAULT, cancellable, @@ -3599,7 +3673,8 @@ msg_composer_save_to_drafts_cb (EMsgComposer *composer, { CamelMimeMessage *message; EAlertSink *alert_sink; - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; GError *error = NULL; alert_sink = e_activity_get_alert_sink (context->activity); @@ -3640,8 +3715,9 @@ msg_composer_save_to_drafts_cb (EMsgComposer *composer, g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); /* The callback can set editor 'changed' if anything failed. */ - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, FALSE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, FALSE); g_signal_emit ( composer, signals[SAVE_TO_DRAFTS], @@ -3666,25 +3742,18 @@ msg_composer_save_to_drafts_cb (EMsgComposer *composer, void e_msg_composer_save_to_drafts (EMsgComposer *composer) { + EHTMLEditor *editor; AsyncContext *context; - EAlertSink *alert_sink; - EActivityBar *activity_bar; GCancellable *cancellable; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - context = g_slice_new0 (AsyncContext); - context->activity = e_composer_activity_new (composer); - - alert_sink = E_ALERT_SINK (composer); - e_activity_set_alert_sink (context->activity, alert_sink); + editor = e_msg_composer_get_editor (composer); - cancellable = camel_operation_new (); - e_activity_set_cancellable (context->activity, cancellable); - g_object_unref (cancellable); + context = g_slice_new0 (AsyncContext); + context->activity = e_html_editor_new_activity (editor); - activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar); - e_activity_bar_set_activity (activity_bar, context->activity); + cancellable = e_activity_get_cancellable (context->activity); e_msg_composer_get_message_draft ( composer, G_PRIORITY_DEFAULT, cancellable, @@ -3699,7 +3768,8 @@ msg_composer_save_to_outbox_cb (EMsgComposer *composer, { CamelMimeMessage *message; EAlertSink *alert_sink; - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; GError *error = NULL; alert_sink = e_activity_get_alert_sink (context->activity); @@ -3734,9 +3804,9 @@ msg_composer_save_to_outbox_cb (EMsgComposer *composer, async_context_free (context); - /* XXX This should be elsewhere. */ - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, FALSE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, FALSE); } /** @@ -3748,9 +3818,8 @@ msg_composer_save_to_outbox_cb (EMsgComposer *composer, void e_msg_composer_save_to_outbox (EMsgComposer *composer) { + EHTMLEditor *editor; AsyncContext *context; - EAlertSink *alert_sink; - EActivityBar *activity_bar; GCancellable *cancellable; gboolean proceed_with_save = TRUE; @@ -3762,18 +3831,12 @@ e_msg_composer_save_to_outbox (EMsgComposer *composer) if (!proceed_with_save) return; - context = g_slice_new0 (AsyncContext); - context->activity = e_composer_activity_new (composer); + editor = e_msg_composer_get_editor (composer); - alert_sink = E_ALERT_SINK (composer); - e_activity_set_alert_sink (context->activity, alert_sink); - - cancellable = camel_operation_new (); - e_activity_set_cancellable (context->activity, cancellable); - g_object_unref (cancellable); + context = g_slice_new0 (AsyncContext); + context->activity = e_html_editor_new_activity (editor); - activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar); - e_activity_bar_set_activity (activity_bar, context->activity); + cancellable = e_activity_get_cancellable (context->activity); e_msg_composer_get_message ( composer, G_PRIORITY_DEFAULT, cancellable, @@ -3835,26 +3898,19 @@ void e_msg_composer_print (EMsgComposer *composer, GtkPrintOperationAction print_action) { + EHTMLEditor *editor; AsyncContext *context; - EAlertSink *alert_sink; - EActivityBar *activity_bar; GCancellable *cancellable; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); + editor = e_msg_composer_get_editor (composer); + context = g_slice_new0 (AsyncContext); - context->activity = e_composer_activity_new (composer); + context->activity = e_html_editor_new_activity (editor); context->print_action = print_action; - alert_sink = E_ALERT_SINK (composer); - e_activity_set_alert_sink (context->activity, alert_sink); - - cancellable = camel_operation_new (); - e_activity_set_cancellable (context->activity, cancellable); - g_object_unref (cancellable); - - activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar); - e_activity_bar_set_activity (activity_bar, context->activity); + cancellable = e_activity_get_cancellable (context->activity); e_msg_composer_get_message_print ( composer, G_PRIORITY_DEFAULT, cancellable, @@ -4226,15 +4282,21 @@ e_msg_composer_set_body (EMsgComposer *composer, { EMsgComposerPrivate *priv = composer->priv; EComposerHeaderTable *table; - EWebViewGtkHTML *web_view; + EHTMLEditor *editor; + EHTMLEditorView *view; ESource *source; const gchar *identity_uid; gchar *buff; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); table = e_msg_composer_get_header_table (composer); + /* Disable signature */ + priv->disable_signature = TRUE; + identity_uid = e_composer_header_table_get_identity_uid (table); source = e_composer_header_table_ref_source (table, identity_uid); @@ -4245,10 +4307,8 @@ e_msg_composer_set_body (EMsgComposer *composer, set_editor_text (composer, buff, FALSE); g_free (buff); - gtkhtml_editor_set_html_mode (GTKHTML_EDITOR (composer), FALSE); - - web_view = e_msg_composer_get_web_view (composer); - e_web_view_gtkhtml_set_editable (web_view, FALSE); + e_html_editor_view_set_html_mode (view, FALSE); + webkit_web_view_set_editable (WEBKIT_WEB_VIEW (view), FALSE); g_free (priv->mime_body); priv->mime_body = g_strdup (body); @@ -4459,109 +4519,12 @@ e_msg_composer_attach (EMsgComposer *composer, attachment = e_attachment_new (); e_attachment_set_mime_part (attachment, mime_part); e_attachment_store_add_attachment (store, attachment); - e_attachment_load (attachment, NULL); + e_attachment_load_async ( + attachment, (GAsyncReadyCallback) + e_attachment_load_handle_error, composer); g_object_unref (attachment); } -/** - * e_msg_composer_add_inline_image_from_file: - * @composer: a composer object - * @filename: the name of the file containing the image - * - * This reads in the image in @filename and adds it to @composer - * as an inline image, to be wrapped in a multipart/related. - * - * Returns: the newly-created CamelMimePart (which must be reffed - * if the caller wants to keep its own reference), or %NULL on error. - **/ -CamelMimePart * -e_msg_composer_add_inline_image_from_file (EMsgComposer *composer, - const gchar *filename) -{ - gchar *mime_type, *cid, *url, *name, *dec_file_name; - CamelStream *stream; - CamelDataWrapper *wrapper; - CamelMimePart *part; - EMsgComposerPrivate *p = composer->priv; - - dec_file_name = g_strdup (filename); - camel_url_decode (dec_file_name); - - if (!g_file_test (dec_file_name, G_FILE_TEST_IS_REGULAR)) - return NULL; - - stream = camel_stream_fs_new_with_name ( - dec_file_name, O_RDONLY, 0, NULL); - if (!stream) - return NULL; - - wrapper = camel_data_wrapper_new (); - camel_data_wrapper_construct_from_stream_sync ( - wrapper, stream, NULL, NULL); - g_object_unref (CAMEL_OBJECT (stream)); - - mime_type = e_util_guess_mime_type (dec_file_name, TRUE); - if (mime_type == NULL) - mime_type = g_strdup ("application/octet-stream"); - camel_data_wrapper_set_mime_type (wrapper, mime_type); - g_free (mime_type); - - part = camel_mime_part_new (); - camel_medium_set_content (CAMEL_MEDIUM (part), wrapper); - g_object_unref (wrapper); - - cid = camel_header_msgid_generate (); - camel_mime_part_set_content_id (part, cid); - name = g_path_get_basename (dec_file_name); - camel_mime_part_set_filename (part, name); - g_free (name); - camel_mime_part_set_encoding (part, CAMEL_TRANSFER_ENCODING_BASE64); - - url = g_strdup_printf ("file:%s", dec_file_name); - g_hash_table_insert (p->inline_images_by_url, url, part); - - url = g_strdup_printf ("cid:%s", cid); - g_hash_table_insert (p->inline_images, url, part); - g_free (cid); - - g_free (dec_file_name); - - return part; -} - -/** - * e_msg_composer_add_inline_image_from_mime_part: - * @composer: a composer object - * @part: a CamelMimePart containing image data - * - * This adds the mime part @part to @composer as an inline image, to - * be wrapped in a multipart/related. - **/ -void -e_msg_composer_add_inline_image_from_mime_part (EMsgComposer *composer, - CamelMimePart *part) -{ - gchar *url; - const gchar *location, *cid; - EMsgComposerPrivate *p = composer->priv; - - cid = camel_mime_part_get_content_id (part); - if (!cid) { - camel_mime_part_set_content_id (part, NULL); - cid = camel_mime_part_get_content_id (part); - } - - url = g_strdup_printf ("cid:%s", cid); - g_hash_table_insert (p->inline_images, url, part); - g_object_ref (part); - - location = camel_mime_part_get_content_location (part); - if (location != NULL) - g_hash_table_insert ( - p->inline_images_by_url, - g_strdup (location), part); -} - static void composer_get_message_ready (EMsgComposer *composer, GAsyncResult *result, @@ -4604,16 +4567,21 @@ e_msg_composer_get_message (EMsgComposer *composer, GSimpleAsyncResult *simple; GtkAction *action; ComposerFlags flags = 0; + EHTMLEditor *editor; + EHTMLEditorView *view; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + simple = g_simple_async_result_new ( G_OBJECT (composer), callback, user_data, e_msg_composer_get_message); g_simple_async_result_set_check_cancellable (simple, cancellable); - if (gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (composer))) + if (e_html_editor_view_get_html_mode (view)) flags |= COMPOSER_FLAG_HTML_CONTENT; action = ACTION (PRIORITIZE_MESSAGE); @@ -4730,8 +4698,10 @@ e_msg_composer_get_message_draft (EMsgComposer *composer, GAsyncReadyCallback callback, gpointer user_data) { + EHTMLEditor *editor; + EHTMLEditorView *view; GSimpleAsyncResult *simple; - ComposerFlags flags = COMPOSER_FLAG_DRAFT; + ComposerFlags flags = COMPOSER_FLAG_SAVE_DRAFT; GtkAction *action; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); @@ -4742,8 +4712,13 @@ e_msg_composer_get_message_draft (EMsgComposer *composer, g_simple_async_result_set_check_cancellable (simple, cancellable); - if (gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (composer))) - flags |= COMPOSER_FLAG_HTML_CONTENT; + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + /* We need to remember composer mode */ + if (e_html_editor_view_get_html_mode (view)) + flags |= COMPOSER_FLAG_HTML_MODE; + /* We want to save HTML content everytime when we save as draft */ + flags |= COMPOSER_FLAG_SAVE_DRAFT; action = ACTION (PRIORITIZE_MESSAGE); if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) @@ -4872,17 +4847,19 @@ e_msg_composer_get_reply_to (EMsgComposer *composer) GByteArray * e_msg_composer_get_raw_message_text (EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; GByteArray *array; gchar *text; - gsize length; g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + array = g_byte_array_new (); - editor = GTKHTML_EDITOR (composer); - text = gtkhtml_editor_get_text_plain (editor, &length); - g_byte_array_append (array, (guint8 *) text, (guint) length); + text = e_html_editor_view_get_text_plain (view); + g_byte_array_append (array, (guint8 *) text, strlen (text)); g_free (text); return array; @@ -4915,22 +4892,24 @@ e_msg_composer_can_close (EMsgComposer *composer, gboolean can_save_draft) { gboolean res = FALSE; - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; EComposerHeaderTable *table; GdkWindow *window; GtkWidget *widget; const gchar *subject, *message_name; gint response; - editor = GTKHTML_EDITOR (composer); widget = GTK_WIDGET (composer); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); /* this means that there is an async operation running, * in which case the composer cannot be closed */ if (!gtk_action_group_get_sensitive (composer->priv->async_actions)) return FALSE; - if (!gtkhtml_editor_get_changed (editor)) + if (!e_html_editor_view_get_changed (view)) return TRUE; window = gtk_widget_get_window (widget); @@ -4968,32 +4947,6 @@ e_msg_composer_can_close (EMsgComposer *composer, return res; } -void -e_msg_composer_reply_indent (EMsgComposer *composer) -{ - GtkhtmlEditor *editor; - - g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - - editor = GTKHTML_EDITOR (composer); - - if (!gtkhtml_editor_is_paragraph_empty (editor)) { - if (gtkhtml_editor_is_previous_paragraph_empty (editor)) - gtkhtml_editor_run_command (editor, "cursor-backward"); - else { - gtkhtml_editor_run_command (editor, "text-default-color"); - gtkhtml_editor_run_command (editor, "italic-off"); - gtkhtml_editor_run_command (editor, "insert-paragraph"); - return; - } - } - - gtkhtml_editor_run_command (editor, "style-normal"); - gtkhtml_editor_run_command (editor, "indent-zero"); - gtkhtml_editor_run_command (editor, "text-default-color"); - gtkhtml_editor_run_command (editor, "italic-off"); -} - EComposerHeaderTable * e_msg_composer_get_header_table (EMsgComposer *composer) { @@ -5010,76 +4963,101 @@ e_msg_composer_get_attachment_view (EMsgComposer *composer) return E_ATTACHMENT_VIEW (composer->priv->attachment_paned); } -GList * -e_load_spell_languages (void) +void +e_save_spell_languages (const GList *spell_dicts) { GSettings *settings; - GList *spell_languages = NULL; - gchar **strv; - gint ii; + GPtrArray *lang_array; + + /* Build a list of spell check language codes. */ + lang_array = g_ptr_array_new (); + while (spell_dicts != NULL) { + ESpellDictionary *dict = spell_dicts->data; + const gchar *language_code; + + language_code = e_spell_dictionary_get_code (dict); + g_ptr_array_add (lang_array, (gpointer) language_code); + + spell_dicts = g_list_next (spell_dicts); + } + + g_ptr_array_add (lang_array, NULL); - /* Ask GSettings for a list of spell check language codes. */ + /* Save the language codes to GSettings. */ settings = g_settings_new ("org.gnome.evolution.mail"); - strv = g_settings_get_strv (settings, "composer-spell-languages"); + g_settings_set_strv ( + settings, "composer-spell-languages", + (const gchar * const *) lang_array->pdata); g_object_unref (settings); - /* Convert the codes to spell language structs. */ - for (ii = 0; strv[ii] != NULL; ii++) { - gchar *language_code = strv[ii]; - const GtkhtmlSpellLanguage *language; + g_ptr_array_free (lang_array, TRUE); +} - language = gtkhtml_spell_language_lookup (language_code); - if (language != NULL) - spell_languages = g_list_prepend ( - spell_languages, (gpointer) language); - } +void +e_msg_composer_is_from_new_message (EMsgComposer *composer, + gboolean is_from_new_message) +{ + g_return_if_fail (composer != NULL); + + composer->priv->is_from_new_message = is_from_new_message; +} + +void +e_msg_composer_save_focused_widget (EMsgComposer *composer) +{ + GtkWidget *widget; - g_strfreev (strv); + g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - spell_languages = g_list_reverse (spell_languages); + widget = gtk_window_get_focus (GTK_WINDOW (composer)); + composer->priv->focused_entry = widget; - /* Pick a default spell language if it came back empty. */ - if (spell_languages == NULL) { - const GtkhtmlSpellLanguage *language; + if (E_IS_HTML_EDITOR_VIEW (widget)) { + EHTMLEditorSelection *selection; - language = gtkhtml_spell_language_lookup (NULL); + selection = e_html_editor_view_get_selection ( + E_HTML_EDITOR_VIEW (widget)); - if (language) { - spell_languages = g_list_prepend ( - spell_languages, (gpointer) language); - } + e_html_editor_selection_save (selection); } - return spell_languages; + if (GTK_IS_EDITABLE (widget)) { + gtk_editable_get_selection_bounds ( + GTK_EDITABLE (widget), + &composer->priv->focused_entry_selection_start, + &composer->priv->focused_entry_selection_end); + } } void -e_save_spell_languages (GList *spell_languages) +e_msg_composer_restore_focus_on_composer (EMsgComposer *composer) { - GSettings *settings; - GPtrArray *lang_array; + GtkWidget *widget = composer->priv->focused_entry; - /* Build a list of spell check language codes. */ - lang_array = g_ptr_array_new (); - while (spell_languages != NULL) { - const GtkhtmlSpellLanguage *language; - const gchar *language_code; + g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - language = spell_languages->data; - language_code = gtkhtml_spell_language_get_code (language); - g_ptr_array_add (lang_array, (gpointer) language_code); + if (!widget) + return; + + gtk_window_set_focus (GTK_WINDOW (composer), widget); - spell_languages = g_list_next (spell_languages); + if (GTK_IS_EDITABLE (widget)) { + gtk_editable_select_region ( + GTK_EDITABLE (widget), + composer->priv->focused_entry_selection_start, + composer->priv->focused_entry_selection_end); } - g_ptr_array_add (lang_array, NULL); + if (E_IS_HTML_EDITOR_VIEW (widget)) { + EHTMLEditorSelection *selection; - /* Save the language codes to GSettings. */ - settings = g_settings_new ("org.gnome.evolution.mail"); - g_settings_set_strv ( - settings, "composer-spell-languages", - (const gchar * const *) lang_array->pdata); - g_object_unref (settings); + e_html_editor_view_force_spell_check (E_HTML_EDITOR_VIEW (widget)); - g_ptr_array_free (lang_array, TRUE); + selection = e_html_editor_view_get_selection ( + E_HTML_EDITOR_VIEW (widget)); + + e_html_editor_selection_restore (selection); + } + + composer->priv->focused_entry = NULL; } diff --git a/composer/e-msg-composer.h b/composer/e-msg-composer.h index 1fbc1043fd..f6990415f7 100644 --- a/composer/e-msg-composer.h +++ b/composer/e-msg-composer.h @@ -24,7 +24,6 @@ #define E_MSG_COMPOSER_H #include <camel/camel.h> -#include <gtkhtml-editor.h> #include <libebook/libebook.h> #include <shell/e-shell.h> @@ -57,12 +56,12 @@ typedef struct _EMsgComposerClass EMsgComposerClass; typedef struct _EMsgComposerPrivate EMsgComposerPrivate; struct _EMsgComposer { - GtkhtmlEditor parent; + GtkWindow parent; EMsgComposerPrivate *priv; }; struct _EMsgComposerClass { - GtkhtmlEditorClass parent_class; + GtkWindowClass parent_class; /* Signals */ gboolean (*presend) (EMsgComposer *composer); @@ -93,12 +92,11 @@ EMsgComposer * e_msg_composer_new_redirect (EShell *shell, CamelMimeMessage *message, const gchar *identity_uid, GCancellable *cancellable); +EHTMLEditor * e_msg_composer_get_editor (EMsgComposer *composer); EFocusTracker * e_msg_composer_get_focus_tracker (EMsgComposer *composer); CamelSession * e_msg_composer_ref_session (EMsgComposer *composer); EShell * e_msg_composer_get_shell (EMsgComposer *composer); -EWebViewGtkHTML * - e_msg_composer_get_web_view (EMsgComposer *composer); void e_msg_composer_send (EMsgComposer *composer); void e_msg_composer_save_to_drafts (EMsgComposer *composer); @@ -131,12 +129,6 @@ void e_msg_composer_set_source_headers CamelMessageFlags flags); void e_msg_composer_attach (EMsgComposer *composer, CamelMimePart *mime_part); -CamelMimePart * e_msg_composer_add_inline_image_from_file - (EMsgComposer *composer, - const gchar *filename); -void e_msg_composer_add_inline_image_from_mime_part - (EMsgComposer *composer, - CamelMimePart *part); void e_msg_composer_get_message (EMsgComposer *composer, gint io_priority, GCancellable *cancellable, @@ -175,8 +167,6 @@ CamelInternetAddress * CamelInternetAddress * e_msg_composer_get_reply_to (EMsgComposer *composer); -void e_msg_composer_clear_inlined_table - (EMsgComposer *composer); void e_msg_composer_add_message_attachments (EMsgComposer *composer, CamelMimeMessage *message, @@ -186,8 +176,6 @@ void e_msg_composer_request_close (EMsgComposer *composer); gboolean e_msg_composer_can_close (EMsgComposer *composer, gboolean can_save_draft); -void e_msg_composer_reply_indent (EMsgComposer *composer); - EComposerHeaderTable * e_msg_composer_get_header_table (EMsgComposer *composer); EAttachmentView * @@ -198,9 +186,15 @@ GByteArray * e_msg_composer_get_raw_message_text gboolean e_msg_composer_is_exiting (EMsgComposer *composer); -GList * e_load_spell_languages (void); -void e_save_spell_languages (GList *spell_languages); - +void e_save_spell_languages (const GList *spell_languages); +void e_msg_composer_is_from_new_message + (EMsgComposer *composer, + gboolean is_from_new_message); +void e_msg_composer_save_focused_widget + (EMsgComposer *composer); +void e_msg_composer_restore_focus_on_composer + (EMsgComposer *composer); +gboolean e_msg_composer_is_busy (EMsgComposer *composer); G_END_DECLS #endif /* E_MSG_COMPOSER_H */ |