aboutsummaryrefslogtreecommitdiffstats
path: root/composer/e-composer-private.c
diff options
context:
space:
mode:
Diffstat (limited to 'composer/e-composer-private.c')
-rw-r--r--composer/e-composer-private.c858
1 files changed, 546 insertions, 312 deletions
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