Updated Spanish translation
[evolution.git] / composer / e-composer-private.c
blob515de0921d42e6a22e288f82f368c9d7b2c11cce
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 /*
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
16 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
23 #include "e-composer-private.h"
24 #include "e-composer-from-header.h"
25 #include "e-composer-spell-header.h"
26 #include "e-util/e-util-private.h"
28 /* Initial height of the picture gallery. */
29 #define GALLERY_INITIAL_HEIGHT 150
31 #define UNICODE_ZERO_WIDTH_SPACE "\xe2\x80\x8b"
33 static void
34 composer_setup_charset_menu (EMsgComposer *composer)
36 EHTMLEditor *editor;
37 GtkUIManager *ui_manager;
38 const gchar *path;
39 GList *list;
40 guint merge_id;
42 editor = e_msg_composer_get_editor (composer);
43 ui_manager = e_html_editor_get_ui_manager (editor);
44 path = "/main-menu/options-menu/charset-menu";
45 merge_id = gtk_ui_manager_new_merge_id (ui_manager);
47 list = gtk_action_group_list_actions (composer->priv->charset_actions);
48 list = g_list_sort (list, (GCompareFunc) e_action_compare_by_label);
50 while (list != NULL) {
51 GtkAction *action = list->data;
53 gtk_ui_manager_add_ui (
54 ui_manager, merge_id, path,
55 gtk_action_get_name (action),
56 gtk_action_get_name (action),
57 GTK_UI_MANAGER_AUTO, FALSE);
59 list = g_list_delete_link (list, list);
62 gtk_ui_manager_ensure_update (ui_manager);
65 static void
66 composer_update_gallery_visibility (EMsgComposer *composer)
68 EHTMLEditor *editor;
69 EHTMLEditorView *view;
70 GtkToggleAction *toggle_action;
71 gboolean gallery_active;
72 gboolean is_html;
74 editor = e_msg_composer_get_editor (composer);
75 view = e_html_editor_get_view (editor);
76 is_html = e_html_editor_view_get_html_mode (view);
78 toggle_action = GTK_TOGGLE_ACTION (ACTION (PICTURE_GALLERY));
79 gallery_active = gtk_toggle_action_get_active (toggle_action);
81 if (is_html && gallery_active) {
82 gtk_widget_show (composer->priv->gallery_scrolled_window);
83 gtk_widget_show (composer->priv->gallery_icon_view);
84 } else {
85 gtk_widget_hide (composer->priv->gallery_scrolled_window);
86 gtk_widget_hide (composer->priv->gallery_icon_view);
90 void
91 e_composer_private_constructed (EMsgComposer *composer)
93 EMsgComposerPrivate *priv = composer->priv;
94 EFocusTracker *focus_tracker;
95 EComposerHeader *header;
96 EShell *shell;
97 EClientCache *client_cache;
98 EHTMLEditor *editor;
99 EHTMLEditorView *view;
100 GtkUIManager *ui_manager;
101 GtkAction *action;
102 GtkWidget *container;
103 GtkWidget *widget;
104 GtkWidget *send_widget;
105 GtkWindow *window;
106 GSettings *settings;
107 const gchar *path;
108 gchar *filename, *gallery_path;
109 gint ii;
110 GError *error = NULL;
112 editor = e_msg_composer_get_editor (composer);
113 ui_manager = e_html_editor_get_ui_manager (editor);
114 view = e_html_editor_get_view (editor);
116 settings = e_util_ref_settings ("org.gnome.evolution.mail");
118 shell = e_msg_composer_get_shell (composer);
119 client_cache = e_shell_get_client_cache (shell);
121 /* Each composer window gets its own window group. */
122 window = GTK_WINDOW (composer);
123 priv->window_group = gtk_window_group_new ();
124 gtk_window_group_add_window (priv->window_group, window);
126 priv->async_actions = gtk_action_group_new ("async");
127 priv->charset_actions = gtk_action_group_new ("charset");
128 priv->composer_actions = gtk_action_group_new ("composer");
130 priv->extra_hdr_names = g_ptr_array_new ();
131 priv->extra_hdr_values = g_ptr_array_new ();
133 priv->charset = e_composer_get_default_charset ();
135 priv->is_from_new_message = FALSE;
136 priv->set_signature_from_message = FALSE;
137 priv->disable_signature = FALSE;
138 priv->busy = FALSE;
139 priv->saved_editable = FALSE;
140 priv->drop_occured = FALSE;
141 priv->dnd_is_uri = FALSE;
143 priv->focused_entry = NULL;
145 e_composer_actions_init (composer);
147 filename = e_composer_find_data_file ("evolution-composer.ui");
148 gtk_ui_manager_add_ui_from_file (ui_manager, filename, &error);
149 g_free (filename);
151 /* We set the send button as important to have a label */
152 path = "/main-toolbar/pre-main-toolbar/send";
153 send_widget = gtk_ui_manager_get_widget (ui_manager, path);
154 gtk_tool_item_set_is_important (GTK_TOOL_ITEM (send_widget), TRUE);
156 composer_setup_charset_menu (composer);
158 if (error != NULL) {
159 /* Henceforth, bad things start happening. */
160 g_critical ("%s", error->message);
161 g_clear_error (&error);
164 /* Configure an EFocusTracker to manage selection actions. */
166 focus_tracker = e_focus_tracker_new (GTK_WINDOW (composer));
168 action = e_html_editor_get_action (editor, "cut");
169 e_focus_tracker_set_cut_clipboard_action (focus_tracker, action);
171 action = e_html_editor_get_action (editor, "copy");
172 e_focus_tracker_set_copy_clipboard_action (focus_tracker, action);
174 action = e_html_editor_get_action (editor, "paste");
175 e_focus_tracker_set_paste_clipboard_action (focus_tracker, action);
177 action = e_html_editor_get_action (editor, "select-all");
178 e_focus_tracker_set_select_all_action (focus_tracker, action);
180 action = e_html_editor_get_action (editor, "undo");
181 e_focus_tracker_set_undo_action (focus_tracker, action);
183 action = e_html_editor_get_action (editor, "redo");
184 e_focus_tracker_set_redo_action (focus_tracker, action);
186 priv->focus_tracker = focus_tracker;
188 widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
189 gtk_container_add (GTK_CONTAINER (composer), widget);
190 gtk_widget_show (widget);
192 container = widget;
194 /* Construct the main menu and toolbar. */
196 widget = e_html_editor_get_managed_widget (editor, "/main-menu");
197 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
198 gtk_widget_show (widget);
200 widget = e_html_editor_get_managed_widget (editor, "/main-toolbar");
201 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
202 gtk_widget_show (widget);
204 /* Construct the header table. */
206 widget = e_composer_header_table_new (client_cache);
207 gtk_container_set_border_width (GTK_CONTAINER (widget), 6);
208 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
209 priv->header_table = g_object_ref (widget);
210 gtk_widget_show (widget);
212 header = e_composer_header_table_get_header (
213 E_COMPOSER_HEADER_TABLE (widget),
214 E_COMPOSER_HEADER_SUBJECT);
215 e_binding_bind_property (
216 view, "spell-checker",
217 header->input_widget, "spell-checker",
218 G_BINDING_SYNC_CREATE);
220 /* Construct the editing toolbars. We'll have to reparent
221 * the embedded EHTMLEditorView a little further down. */
223 widget = GTK_WIDGET (editor);
224 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
225 gtk_widget_show (widget);
227 /* Construct the attachment paned. */
229 widget = e_attachment_paned_new ();
230 gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
231 priv->attachment_paned = g_object_ref_sink (widget);
232 gtk_widget_show (widget);
234 e_binding_bind_property (
235 view, "editable",
236 widget, "sensitive",
237 G_BINDING_SYNC_CREATE);
239 container = e_attachment_paned_get_content_area (
240 E_ATTACHMENT_PANED (priv->attachment_paned));
242 widget = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
243 gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
244 gtk_widget_show (widget);
246 container = widget;
248 widget = gtk_scrolled_window_new (NULL, NULL);
249 gtk_scrolled_window_set_policy (
250 GTK_SCROLLED_WINDOW (widget),
251 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
252 gtk_widget_set_size_request (widget, -1, GALLERY_INITIAL_HEIGHT);
253 gtk_paned_pack1 (GTK_PANED (container), widget, FALSE, FALSE);
254 priv->gallery_scrolled_window = g_object_ref (widget);
255 gtk_widget_show (widget);
257 /* Reparent the scrolled window containing the web view
258 * widget into the content area of the top attachment pane. */
260 widget = GTK_WIDGET (view);
261 widget = gtk_widget_get_parent (widget);
262 gtk_widget_reparent (widget, container);
264 /* Construct the picture gallery. */
266 container = priv->gallery_scrolled_window;
268 /* FIXME This should be an EMsgComposer property. */
269 gallery_path = g_settings_get_string (
270 settings, "composer-gallery-path");
271 widget = e_picture_gallery_new (gallery_path);
272 gtk_container_add (GTK_CONTAINER (container), widget);
273 priv->gallery_icon_view = g_object_ref_sink (widget);
274 g_free (gallery_path);
276 e_signal_connect_notify_swapped (
277 view, "notify::mode",
278 G_CALLBACK (composer_update_gallery_visibility), composer);
280 g_signal_connect_swapped (
281 ACTION (PICTURE_GALLERY), "toggled",
282 G_CALLBACK (composer_update_gallery_visibility), composer);
284 /* Initial sync */
285 composer_update_gallery_visibility (composer);
287 /* Bind headers to their corresponding actions. */
289 for (ii = 0; ii < E_COMPOSER_NUM_HEADERS; ii++) {
290 EComposerHeaderTable *table;
291 EComposerHeader *header;
292 GtkAction *action;
294 table = E_COMPOSER_HEADER_TABLE (priv->header_table);
295 header = e_composer_header_table_get_header (table, ii);
297 switch (ii) {
298 case E_COMPOSER_HEADER_FROM:
299 e_widget_undo_attach (
300 GTK_WIDGET (e_composer_from_header_get_name_entry (E_COMPOSER_FROM_HEADER (header))),
301 focus_tracker);
302 e_widget_undo_attach (
303 GTK_WIDGET (e_composer_from_header_get_address_entry (E_COMPOSER_FROM_HEADER (header))),
304 focus_tracker);
306 action = ACTION (VIEW_FROM_OVERRIDE);
307 e_binding_bind_property (
308 header, "override-visible",
309 action, "active",
310 G_BINDING_BIDIRECTIONAL |
311 G_BINDING_SYNC_CREATE);
312 continue;
314 case E_COMPOSER_HEADER_BCC:
315 action = ACTION (VIEW_BCC);
316 break;
318 case E_COMPOSER_HEADER_CC:
319 action = ACTION (VIEW_CC);
320 break;
322 case E_COMPOSER_HEADER_REPLY_TO:
323 action = ACTION (VIEW_REPLY_TO);
324 e_widget_undo_attach (
325 GTK_WIDGET (header->input_widget),
326 focus_tracker);
327 break;
329 case E_COMPOSER_HEADER_SUBJECT:
330 e_widget_undo_attach (
331 GTK_WIDGET (header->input_widget),
332 focus_tracker);
333 continue;
335 default:
336 continue;
339 e_binding_bind_property (
340 header, "sensitive",
341 action, "sensitive",
342 G_BINDING_BIDIRECTIONAL |
343 G_BINDING_SYNC_CREATE);
345 e_binding_bind_property (
346 header, "visible",
347 action, "active",
348 G_BINDING_BIDIRECTIONAL |
349 G_BINDING_SYNC_CREATE);
352 /* Disable actions that start asynchronous activities while an
353 * asynchronous activity is in progress. We enforce this with
354 * a simple inverted binding to EMsgComposer's "busy" property. */
356 e_binding_bind_property (
357 composer, "busy",
358 priv->async_actions, "sensitive",
359 G_BINDING_SYNC_CREATE |
360 G_BINDING_INVERT_BOOLEAN);
362 e_binding_bind_property (
363 composer, "busy",
364 priv->header_table, "sensitive",
365 G_BINDING_SYNC_CREATE |
366 G_BINDING_INVERT_BOOLEAN);
368 g_object_unref (settings);
371 void
372 e_composer_private_dispose (EMsgComposer *composer)
374 if (composer->priv->shell != NULL) {
375 g_object_remove_weak_pointer (
376 G_OBJECT (composer->priv->shell),
377 &composer->priv->shell);
378 composer->priv->shell = NULL;
381 if (composer->priv->editor != NULL) {
382 g_object_unref (composer->priv->editor);
383 composer->priv->editor = NULL;
386 if (composer->priv->header_table != NULL) {
387 g_object_unref (composer->priv->header_table);
388 composer->priv->header_table = NULL;
391 if (composer->priv->attachment_paned != NULL) {
392 g_object_unref (composer->priv->attachment_paned);
393 composer->priv->attachment_paned = NULL;
396 if (composer->priv->focus_tracker != NULL) {
397 g_object_unref (composer->priv->focus_tracker);
398 composer->priv->focus_tracker = NULL;
401 if (composer->priv->window_group != NULL) {
402 g_object_unref (composer->priv->window_group);
403 composer->priv->window_group = NULL;
406 if (composer->priv->async_actions != NULL) {
407 g_object_unref (composer->priv->async_actions);
408 composer->priv->async_actions = NULL;
411 if (composer->priv->charset_actions != NULL) {
412 g_object_unref (composer->priv->charset_actions);
413 composer->priv->charset_actions = NULL;
416 if (composer->priv->composer_actions != NULL) {
417 g_object_unref (composer->priv->composer_actions);
418 composer->priv->composer_actions = NULL;
421 if (composer->priv->gallery_scrolled_window != NULL) {
422 g_object_unref (composer->priv->gallery_scrolled_window);
423 composer->priv->gallery_scrolled_window = NULL;
426 if (composer->priv->redirect != NULL) {
427 g_object_unref (composer->priv->redirect);
428 composer->priv->redirect = NULL;
432 void
433 e_composer_private_finalize (EMsgComposer *composer)
435 GPtrArray *array;
437 array = composer->priv->extra_hdr_names;
438 g_ptr_array_foreach (array, (GFunc) g_free, NULL);
439 g_ptr_array_free (array, TRUE);
441 array = composer->priv->extra_hdr_values;
442 g_ptr_array_foreach (array, (GFunc) g_free, NULL);
443 g_ptr_array_free (array, TRUE);
445 g_free (composer->priv->charset);
446 g_free (composer->priv->mime_type);
447 g_free (composer->priv->mime_body);
450 gchar *
451 e_composer_find_data_file (const gchar *basename)
453 gchar *filename;
455 g_return_val_if_fail (basename != NULL, NULL);
457 /* Support running directly from the source tree. */
458 filename = g_build_filename (".", basename, NULL);
459 if (g_file_test (filename, G_FILE_TEST_EXISTS))
460 return filename;
461 g_free (filename);
463 /* XXX This is kinda broken. */
464 filename = g_build_filename (EVOLUTION_UIDIR, basename, NULL);
465 if (g_file_test (filename, G_FILE_TEST_EXISTS))
466 return filename;
467 g_free (filename);
469 g_critical ("Could not locate '%s'", basename);
471 return NULL;
474 gchar *
475 e_composer_get_default_charset (void)
477 GSettings *settings;
478 gchar *charset;
480 settings = e_util_ref_settings ("org.gnome.evolution.mail");
482 charset = g_settings_get_string (settings, "composer-charset");
484 /* See what charset the mailer is using.
485 * XXX We should not have to know where this lives in GSettings.
486 * Need a mail_config_get_default_charset() that does this. */
487 if (!charset || charset[0] == '\0') {
488 g_free (charset);
489 charset = g_settings_get_string (settings, "charset");
490 if (charset != NULL && *charset == '\0') {
491 g_free (charset);
492 charset = NULL;
496 g_object_unref (settings);
498 if (charset == NULL)
499 charset = g_strdup (camel_iconv_locale_charset ());
501 if (charset == NULL)
502 charset = g_strdup ("us-ascii");
504 return charset;
507 gboolean
508 e_composer_paste_html (EMsgComposer *composer,
509 GtkClipboard *clipboard)
511 EHTMLEditor *editor;
512 EHTMLEditorView *view;
513 EHTMLEditorSelection *editor_selection;
514 gchar *html;
516 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
517 g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);
519 html = e_clipboard_wait_for_html (clipboard);
520 g_return_val_if_fail (html != NULL, FALSE);
522 editor = e_msg_composer_get_editor (composer);
523 view = e_html_editor_get_view (editor);
524 editor_selection = e_html_editor_view_get_selection (view);
525 /* If Web View doesn't have focus, focus it */
526 if (!gtk_widget_has_focus (GTK_WIDGET (view)))
527 gtk_widget_grab_focus (GTK_WIDGET (view));
528 e_html_editor_selection_insert_html (editor_selection, html);
530 g_free (html);
532 return TRUE;
535 gboolean
536 e_composer_paste_image (EMsgComposer *composer,
537 GtkClipboard *clipboard)
539 EHTMLEditor *editor;
540 EHTMLEditorView *html_editor_view;
541 EAttachmentStore *store;
542 EAttachmentView *view;
543 GdkPixbuf *pixbuf = NULL;
544 gchar *filename = NULL;
545 gchar *uri = NULL;
546 gboolean success = FALSE;
547 GError *error = NULL;
549 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
550 g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);
552 view = e_msg_composer_get_attachment_view (composer);
553 store = e_attachment_view_get_store (view);
555 /* Extract the image data from the clipboard. */
556 pixbuf = gtk_clipboard_wait_for_image (clipboard);
557 g_return_val_if_fail (pixbuf != NULL, FALSE);
559 /* Reserve a temporary file. */
560 filename = e_mktemp (NULL);
561 if (filename == NULL) {
562 g_set_error (
563 &error, G_FILE_ERROR,
564 g_file_error_from_errno (errno),
565 "Could not create temporary file: %s",
566 g_strerror (errno));
567 goto exit;
570 /* Save the pixbuf as a temporary file in image/png format. */
571 if (!gdk_pixbuf_save (pixbuf, filename, "png", &error, NULL))
572 goto exit;
574 /* Convert the filename to a URI. */
575 uri = g_filename_to_uri (filename, NULL, &error);
576 if (uri == NULL)
577 goto exit;
579 /* In HTML mode, paste the image into the message body.
580 * In text mode, add the image to the attachment store. */
581 editor = e_msg_composer_get_editor (composer);
582 html_editor_view = e_html_editor_get_view (editor);
583 if (e_html_editor_view_get_html_mode (html_editor_view)) {
584 EHTMLEditorSelection *selection;
586 selection = e_html_editor_view_get_selection (html_editor_view);
587 e_html_editor_selection_insert_image (selection, uri);
588 e_html_editor_selection_scroll_to_caret (selection);
589 } else {
590 EAttachment *attachment;
592 attachment = e_attachment_new_for_uri (uri);
593 e_attachment_store_add_attachment (store, attachment);
594 e_attachment_load_async (
595 attachment, (GAsyncReadyCallback)
596 e_attachment_load_handle_error, composer);
597 g_object_unref (attachment);
600 success = TRUE;
602 exit:
603 if (error != NULL) {
604 g_warning ("%s", error->message);
605 g_error_free (error);
608 g_object_unref (pixbuf);
609 g_free (filename);
610 g_free (uri);
612 return success;
615 gboolean
616 e_composer_paste_text (EMsgComposer *composer,
617 GtkClipboard *clipboard)
619 EHTMLEditor *editor;
620 EHTMLEditorView *view;
621 EHTMLEditorSelection *editor_selection;
622 gchar *text;
624 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
625 g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);
627 text = gtk_clipboard_wait_for_text (clipboard);
628 g_return_val_if_fail (text != NULL, FALSE);
630 editor = e_msg_composer_get_editor (composer);
631 view = e_html_editor_get_view (editor);
632 editor_selection = e_html_editor_view_get_selection (view);
633 /* If WebView doesn't have focus, focus it */
634 if (!gtk_widget_has_focus (GTK_WIDGET (view)))
635 gtk_widget_grab_focus (GTK_WIDGET (view));
637 e_html_editor_selection_insert_text (editor_selection, text);
639 g_free (text);
641 return TRUE;
644 gboolean
645 e_composer_paste_uris (EMsgComposer *composer,
646 GtkClipboard *clipboard)
648 EAttachmentStore *store;
649 EAttachmentView *view;
650 gchar **uris;
651 gint ii;
653 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
654 g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);
656 view = e_msg_composer_get_attachment_view (composer);
657 store = e_attachment_view_get_store (view);
659 /* Extract the URI data from the clipboard. */
660 uris = gtk_clipboard_wait_for_uris (clipboard);
661 g_return_val_if_fail (uris != NULL, FALSE);
663 /* Add the URIs to the attachment store. */
664 for (ii = 0; uris[ii] != NULL; ii++) {
665 EAttachment *attachment;
667 attachment = e_attachment_new_for_uri (uris[ii]);
668 e_attachment_store_add_attachment (store, attachment);
669 e_attachment_load_async (
670 attachment, (GAsyncReadyCallback)
671 e_attachment_load_handle_error, composer);
672 g_object_unref (attachment);
675 return TRUE;
678 gboolean
679 e_composer_selection_is_base64_uris (EMsgComposer *composer,
680 GtkSelectionData *selection)
682 gboolean all_base64_uris = TRUE;
683 gchar **uris;
684 guint ii;
686 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
687 g_return_val_if_fail (selection != NULL, FALSE);
689 uris = gtk_selection_data_get_uris (selection);
691 if (!uris)
692 return FALSE;
694 for (ii = 0; uris[ii] != NULL; ii++) {
695 if (!((g_str_has_prefix (uris[ii], "data:") || strstr (uris[ii], ";data:"))
696 && strstr (uris[ii], ";base64,"))) {
697 all_base64_uris = FALSE;
698 break;
702 g_strfreev (uris);
704 return all_base64_uris;
707 gboolean
708 e_composer_selection_is_image_uris (EMsgComposer *composer,
709 GtkSelectionData *selection)
711 gboolean all_image_uris = TRUE;
712 gchar **uris;
713 guint ii;
715 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
716 g_return_val_if_fail (selection != NULL, FALSE);
718 uris = gtk_selection_data_get_uris (selection);
720 if (!uris)
721 return FALSE;
723 for (ii = 0; uris[ii] != NULL; ii++) {
724 GFile *file;
725 GFileInfo *file_info;
726 GdkPixbufLoader *loader;
727 const gchar *attribute;
728 const gchar *content_type;
729 gchar *mime_type = NULL;
731 file = g_file_new_for_uri (uris[ii]);
732 attribute = G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE;
734 /* XXX This blocks, but we're requesting the fast content
735 * type (which only inspects filenames), so hopefully
736 * it won't be noticeable. Also, this is best effort
737 * so we don't really care if it fails. */
738 file_info = g_file_query_info (
739 file, attribute, G_FILE_QUERY_INFO_NONE, NULL, NULL);
741 if (file_info == NULL) {
742 g_object_unref (file);
743 all_image_uris = FALSE;
744 break;
747 content_type = g_file_info_get_attribute_string (
748 file_info, attribute);
749 mime_type = g_content_type_get_mime_type (content_type);
751 g_object_unref (file_info);
752 g_object_unref (file);
754 if (mime_type == NULL) {
755 all_image_uris = FALSE;
756 break;
759 /* Easy way to determine if a MIME type is a supported
760 * image format: try creating a GdkPixbufLoader for it. */
761 loader = gdk_pixbuf_loader_new_with_mime_type (mime_type, NULL);
763 g_free (mime_type);
765 if (loader == NULL) {
766 all_image_uris = FALSE;
767 break;
770 gdk_pixbuf_loader_close (loader, NULL);
771 g_object_unref (loader);
774 g_strfreev (uris);
776 return all_image_uris;
779 static gboolean
780 add_signature_delimiter (EMsgComposer *composer)
782 GSettings *settings;
783 gboolean signature_delim;
785 /* FIXME This should be an EMsgComposer property. */
786 settings = e_util_ref_settings ("org.gnome.evolution.mail");
787 signature_delim = !g_settings_get_boolean (
788 settings, "composer-no-signature-delim");
789 g_object_unref (settings);
791 return signature_delim;
794 static gboolean
795 use_top_signature (EMsgComposer *composer)
797 GSettings *settings;
798 gboolean top_signature;
800 /* FIXME This should be an EMsgComposer property. */
801 settings = e_util_ref_settings ("org.gnome.evolution.mail");
802 top_signature = g_settings_get_boolean (
803 settings, "composer-top-signature");
804 g_object_unref (settings);
806 return top_signature;
809 static void
810 composer_size_allocate_cb (GtkWidget *widget,
811 gpointer user_data)
813 GtkWidget *scrolled_window;
814 GtkAdjustment *adj;
816 scrolled_window = gtk_widget_get_parent (GTK_WIDGET (widget));
817 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled_window));
819 /* Scroll only when there is some size allocated */
820 if (gtk_adjustment_get_upper (adj) != 0.0) {
821 /* Scroll web view down to caret */
822 gtk_adjustment_set_value (adj, gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
823 gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (scrolled_window), adj);
824 /* Disconnect because we don't want to scroll down the view on every window size change */
825 g_signal_handlers_disconnect_by_func (
826 widget, G_CALLBACK (composer_size_allocate_cb), NULL);
830 static WebKitDOMElement *
831 prepare_paragraph (EHTMLEditorSelection *selection,
832 WebKitDOMDocument *document)
834 WebKitDOMElement *br, *paragraph;
836 paragraph = e_html_editor_selection_get_paragraph_element (
837 selection, document, -1, 0);
838 webkit_dom_element_set_id (paragraph, "-x-evo-input-start");
839 br = webkit_dom_document_create_element (document, "BR", NULL);
840 webkit_dom_node_append_child (
841 WEBKIT_DOM_NODE (paragraph), WEBKIT_DOM_NODE (br), NULL);
843 return paragraph;
846 static WebKitDOMElement *
847 prepare_top_signature_spacer (EHTMLEditorSelection *selection,
848 WebKitDOMDocument *document)
850 WebKitDOMElement *element;
852 element = prepare_paragraph (selection, document);
853 webkit_dom_element_remove_attribute (element, "id");
854 element_add_class (element, "-x-evo-top-signature-spacer");
856 return element;
859 static void
860 composer_move_caret (EMsgComposer *composer)
862 EHTMLEditor *editor;
863 EHTMLEditorView *view;
864 EHTMLEditorSelection *editor_selection;
865 GSettings *settings;
866 gboolean start_bottom, top_signature;
867 gboolean is_message_from_draft;
868 gboolean is_message_from_edit_as_new;
869 gboolean has_paragraphs_in_body = TRUE;
870 WebKitDOMDocument *document;
871 WebKitDOMDOMWindow *window;
872 WebKitDOMDOMSelection *dom_selection;
873 WebKitDOMElement *element, *signature;
874 WebKitDOMHTMLElement *body;
875 WebKitDOMNodeList *list;
876 WebKitDOMRange *new_range;
878 /* When there is an option composer-reply-start-bottom set we have
879 * to move the caret between reply and signature. */
880 settings = e_util_ref_settings ("org.gnome.evolution.mail");
881 start_bottom = g_settings_get_boolean (settings, "composer-reply-start-bottom");
882 g_object_unref (settings);
884 editor = e_msg_composer_get_editor (composer);
885 view = e_html_editor_get_view (editor);
886 editor_selection = e_html_editor_view_get_selection (view);
887 is_message_from_draft = e_html_editor_view_is_message_from_draft (view);
888 is_message_from_edit_as_new =
889 e_html_editor_view_is_message_from_edit_as_new (view);
891 top_signature =
892 use_top_signature (composer) &&
893 !is_message_from_edit_as_new &&
894 !composer->priv->is_from_new_message;
896 document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
897 window = webkit_dom_document_get_default_view (document);
898 dom_selection = webkit_dom_dom_window_get_selection (window);
900 body = webkit_dom_document_get_body (document);
901 webkit_dom_element_set_attribute (
902 WEBKIT_DOM_ELEMENT (body), "data-message", "", NULL);
903 new_range = webkit_dom_document_create_range (document);
905 /* If editing message as new don't handle with caret */
906 if (is_message_from_edit_as_new || is_message_from_draft) {
907 if (is_message_from_edit_as_new)
908 webkit_dom_element_set_attribute (
909 WEBKIT_DOM_ELEMENT (body),
910 "data-edit-as-new",
912 NULL);
914 if (is_message_from_edit_as_new) {
915 element = WEBKIT_DOM_ELEMENT (body);
916 e_html_editor_selection_block_selection_changed (editor_selection);
917 goto move_caret;
918 } else
919 e_html_editor_selection_scroll_to_caret (editor_selection);
921 return;
924 e_html_editor_selection_block_selection_changed (editor_selection);
926 /* When the new message is written from the beginning - note it into body */
927 if (composer->priv->is_from_new_message)
928 webkit_dom_element_set_attribute (
929 WEBKIT_DOM_ELEMENT (body), "data-new-message", "", NULL);
931 list = webkit_dom_document_get_elements_by_class_name (document, "-x-evo-paragraph");
932 signature = webkit_dom_document_query_selector (document, ".-x-evo-signature-wrapper", NULL);
933 /* Situation when wrapped paragraph is just in signature and not in message body */
934 if (webkit_dom_node_list_get_length (list) == 1)
935 if (signature && webkit_dom_element_query_selector (signature, ".-x-evo-paragraph", NULL))
936 has_paragraphs_in_body = FALSE;
940 * Keeping Signatures in the beginning of composer
941 * ------------------------------------------------
943 * Purists are gonna blast me for this.
944 * But there are so many people (read Outlook users) who want this.
945 * And Evo is an exchange-client, Outlook-replacement etc.
946 * So Here it goes :(
948 * -- Sankar
951 if (signature && top_signature) {
952 WebKitDOMElement *spacer;
954 spacer = prepare_top_signature_spacer (editor_selection, document);
955 webkit_dom_node_insert_before (
956 WEBKIT_DOM_NODE (body),
957 WEBKIT_DOM_NODE (spacer),
958 webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (signature)),
959 NULL);
962 if (webkit_dom_node_list_get_length (list) == 0)
963 has_paragraphs_in_body = FALSE;
965 element = webkit_dom_document_get_element_by_id (document, "-x-evo-input-start");
966 if (!signature) {
967 if (start_bottom) {
968 if (!element) {
969 element = prepare_paragraph (editor_selection, document);
970 webkit_dom_node_append_child (
971 WEBKIT_DOM_NODE (body),
972 WEBKIT_DOM_NODE (element),
973 NULL);
975 } else
976 element = WEBKIT_DOM_ELEMENT (body);
978 g_object_unref (list);
979 goto move_caret;
982 if (!has_paragraphs_in_body) {
983 element = prepare_paragraph (editor_selection, document);
984 if (top_signature) {
985 if (start_bottom) {
986 webkit_dom_node_append_child (
987 WEBKIT_DOM_NODE (body),
988 WEBKIT_DOM_NODE (element),
989 NULL);
990 } else {
991 webkit_dom_node_insert_before (
992 WEBKIT_DOM_NODE (body),
993 WEBKIT_DOM_NODE (element),
994 WEBKIT_DOM_NODE (signature),
995 NULL);
997 } else {
998 if (start_bottom)
999 webkit_dom_node_insert_before (
1000 WEBKIT_DOM_NODE (body),
1001 WEBKIT_DOM_NODE (element),
1002 WEBKIT_DOM_NODE (signature),
1003 NULL);
1004 else
1005 element = WEBKIT_DOM_ELEMENT (body);
1007 } else {
1008 if (!element && top_signature) {
1009 element = prepare_paragraph (editor_selection, document);
1010 if (start_bottom) {
1011 webkit_dom_node_append_child (
1012 WEBKIT_DOM_NODE (body),
1013 WEBKIT_DOM_NODE (element),
1014 NULL);
1015 } else {
1016 webkit_dom_node_insert_before (
1017 WEBKIT_DOM_NODE (body),
1018 WEBKIT_DOM_NODE (element),
1019 WEBKIT_DOM_NODE (signature),
1020 NULL);
1022 } else if (element && top_signature && !start_bottom) {
1023 webkit_dom_node_insert_before (
1024 WEBKIT_DOM_NODE (body),
1025 WEBKIT_DOM_NODE (element),
1026 WEBKIT_DOM_NODE (signature),
1027 NULL);
1028 } else if (element && start_bottom) {
1029 /* Leave it how it is */
1030 } else
1031 element = WEBKIT_DOM_ELEMENT (body);
1034 g_object_unref (list);
1035 move_caret:
1036 if (element) {
1037 webkit_dom_range_select_node_contents (
1038 new_range, WEBKIT_DOM_NODE (element), NULL);
1039 webkit_dom_range_collapse (new_range, TRUE, NULL);
1040 webkit_dom_dom_selection_remove_all_ranges (dom_selection);
1041 webkit_dom_dom_selection_add_range (dom_selection, new_range);
1043 if (start_bottom)
1044 e_html_editor_selection_scroll_to_caret (editor_selection);
1047 if (start_bottom)
1048 g_signal_connect (
1049 view, "size-allocate",
1050 G_CALLBACK (composer_size_allocate_cb), NULL);
1052 e_html_editor_view_force_spell_check_in_viewport (view);
1054 e_html_editor_selection_unblock_selection_changed (editor_selection);
1057 static void
1058 composer_load_signature_cb (EMailSignatureComboBox *combo_box,
1059 GAsyncResult *result,
1060 EMsgComposer *composer)
1062 GString *html_buffer = NULL;
1063 gchar *contents = NULL;
1064 gsize length = 0;
1065 const gchar *active_id;
1066 gboolean top_signature, is_html, html_mode;
1067 gboolean start_bottom, is_message_from_edit_as_new;
1068 GError *error = NULL;
1069 EHTMLEditor *editor;
1070 EHTMLEditorView *view;
1071 WebKitDOMDocument *document;
1072 WebKitDOMNodeList *signatures;
1073 gulong list_length, ii;
1074 GSettings *settings;
1076 e_mail_signature_combo_box_load_selected_finish (
1077 combo_box, result, &contents, &length, &is_html, &error);
1079 /* FIXME Use an EAlert here. */
1080 if (error != NULL) {
1081 g_warning ("%s: %s", G_STRFUNC, error->message);
1082 g_error_free (error);
1083 goto exit;
1086 editor = e_msg_composer_get_editor (composer);
1087 view = e_html_editor_get_view (editor);
1088 is_message_from_edit_as_new =
1089 e_html_editor_view_is_message_from_edit_as_new (view);
1091 /* "Edit as New Message" sets is_message_from_edit_as_new.
1092 * Always put the signature at the bottom for that case. */
1093 top_signature =
1094 use_top_signature (composer) &&
1095 !is_message_from_edit_as_new &&
1096 !composer->priv->is_from_new_message;
1098 settings = e_util_ref_settings ("org.gnome.evolution.mail");
1099 start_bottom = g_settings_get_boolean (settings, "composer-reply-start-bottom");
1100 g_object_unref (settings);
1102 document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
1103 html_mode = e_html_editor_view_get_html_mode (view);
1105 if (contents == NULL)
1106 goto insert;
1108 /* If inserting HTML signature in plain text composer we have to convert it. */
1109 if (is_html && !html_mode) {
1110 WebKitDOMElement *element;
1111 gchar *inner_text;
1113 element = webkit_dom_document_create_element (document, "div", NULL);
1114 webkit_dom_html_element_set_inner_html (
1115 WEBKIT_DOM_HTML_ELEMENT (element), contents, NULL);
1116 inner_text = webkit_dom_html_element_get_inner_text (
1117 WEBKIT_DOM_HTML_ELEMENT (element));
1119 g_free (contents);
1120 contents = inner_text ? g_strstrip (inner_text) : g_strdup ("");
1121 is_html = FALSE;
1124 if (!is_html) {
1125 gchar *html;
1127 html = camel_text_to_html (contents, 0, 0);
1128 if (html) {
1129 g_free (contents);
1131 contents = html;
1132 length = strlen (contents);
1136 /* Generate HTML code for the signature. */
1138 html_buffer = g_string_sized_new (1024);
1140 /* The combo box active ID is the signature's ESource UID. */
1141 active_id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box));
1143 g_string_append_printf (
1144 html_buffer,
1145 "<SPAN class=\"-x-evo-signature\" id=\"1\" name=\"%s\">",
1146 (active_id != NULL) ? active_id : "");
1148 if (!is_html)
1149 g_string_append (html_buffer, "<PRE>");
1151 /* The signature dash convention ("-- \n") is specified
1152 * in the "Son of RFC 1036", section 4.3.2.
1153 * http://www.chemie.fu-berlin.de/outerspace/netnews/son-of-1036.html
1155 if (add_signature_delimiter (composer)) {
1156 const gchar *delim;
1157 const gchar *delim_nl;
1159 if (is_html) {
1160 delim = "-- <BR>";
1161 delim_nl = "\n-- <BR>";
1162 } else {
1163 delim = "-- \n";
1164 delim_nl = "\n-- \n";
1167 /* Skip the delimiter if the signature already has one. */
1168 if (g_ascii_strncasecmp (contents, delim, strlen (delim)) == 0)
1169 ; /* skip */
1170 else if (e_util_strstrcase (contents, delim_nl) != NULL)
1171 ; /* skip */
1172 else
1173 g_string_append (html_buffer, delim);
1176 g_string_append_len (html_buffer, contents, length);
1178 if (!is_html)
1179 g_string_append (html_buffer, "</PRE>");
1181 g_string_append (html_buffer, "</SPAN>");
1182 g_free (contents);
1184 insert:
1185 /* Remove the old signature and insert the new one. */
1186 signatures = webkit_dom_document_get_elements_by_class_name (
1187 document, "-x-evo-signature-wrapper");
1188 list_length = webkit_dom_node_list_get_length (signatures);
1189 for (ii = 0; ii < list_length; ii++) {
1190 WebKitDOMNode *wrapper, *signature;
1191 gchar *id;
1193 wrapper = webkit_dom_node_list_item (signatures, ii);
1194 signature = webkit_dom_node_get_first_child (wrapper);
1195 id = webkit_dom_element_get_id (WEBKIT_DOM_ELEMENT (signature));
1197 /* When we are editing a message with signature we need to set active
1198 * signature id in signature combo box otherwise no signature will be
1199 * added but we have to do it just once when the composer opens */
1200 if (is_message_from_edit_as_new && composer->priv->set_signature_from_message) {
1201 gchar *name;
1203 composer->priv->set_signature_from_message = FALSE;
1205 name = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (signature), "name");
1206 gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), name);
1207 g_free (name);
1210 if (id && (strlen (id) == 1) && (*id == '1')) {
1211 /* If the top signature was set we have to remove the NL
1212 * that was inserted after it */
1213 if (top_signature) {
1214 WebKitDOMElement *spacer;
1216 spacer = webkit_dom_document_query_selector (
1217 document, ".-x-evo-top-signature-spacer", NULL);
1218 if (spacer)
1219 remove_node_if_empty (WEBKIT_DOM_NODE (spacer));
1221 /* We have to remove the div containing the span with signature */
1222 remove_node (wrapper);
1223 g_object_unref (wrapper);
1225 g_free (id);
1226 break;
1229 g_object_unref (wrapper);
1230 g_free (id);
1232 g_object_unref (signatures);
1234 if (html_buffer != NULL) {
1235 if (*html_buffer->str) {
1236 WebKitDOMElement *element;
1237 WebKitDOMHTMLElement *body;
1239 body = webkit_dom_document_get_body (document);
1240 element = webkit_dom_document_create_element (document, "DIV", NULL);
1241 webkit_dom_element_set_class_name (element, "-x-evo-signature-wrapper");
1243 webkit_dom_html_element_set_inner_html (
1244 WEBKIT_DOM_HTML_ELEMENT (element), html_buffer->str, NULL);
1246 if (top_signature) {
1247 WebKitDOMNode *child =
1248 webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
1250 if (start_bottom) {
1251 webkit_dom_node_insert_before (
1252 WEBKIT_DOM_NODE (body),
1253 WEBKIT_DOM_NODE (element),
1254 child,
1255 NULL);
1256 } else {
1257 /* When we are using signature on top the caret
1258 * should be before the signature */
1259 webkit_dom_node_insert_before (
1260 WEBKIT_DOM_NODE (body),
1261 WEBKIT_DOM_NODE (element),
1262 child,
1263 NULL);
1265 } else {
1266 webkit_dom_node_append_child (
1267 WEBKIT_DOM_NODE (body),
1268 WEBKIT_DOM_NODE (element),
1269 NULL);
1273 g_string_free (html_buffer, TRUE);
1276 if (is_html && html_mode)
1277 e_html_editor_view_fix_file_uri_images (view);
1279 composer_move_caret (composer);
1281 exit:
1282 /* Make sure the flag will be unset and won't influence user's choice */
1283 composer->priv->set_signature_from_message = FALSE;
1285 g_object_unref (composer);
1288 static void
1289 composer_web_view_load_status_changed_cb (WebKitWebView *webkit_web_view,
1290 GParamSpec *pspec,
1291 EMsgComposer *composer)
1293 WebKitLoadStatus status;
1295 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
1297 status = webkit_web_view_get_load_status (webkit_web_view);
1299 if (status != WEBKIT_LOAD_FINISHED)
1300 return;
1302 g_signal_handlers_disconnect_by_func (
1303 webkit_web_view,
1304 G_CALLBACK (composer_web_view_load_status_changed_cb),
1305 NULL);
1307 e_composer_update_signature (composer);
1310 void
1311 e_composer_update_signature (EMsgComposer *composer)
1313 EComposerHeaderTable *table;
1314 EMailSignatureComboBox *combo_box;
1315 EHTMLEditor *editor;
1316 EHTMLEditorView *view;
1317 WebKitLoadStatus status;
1319 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
1321 /* Do nothing if we're redirecting a message or we disabled
1322 * the signature on purpose */
1323 if (composer->priv->redirect || composer->priv->disable_signature)
1324 return;
1326 table = e_msg_composer_get_header_table (composer);
1327 combo_box = e_composer_header_table_get_signature_combo_box (table);
1328 editor = e_msg_composer_get_editor (composer);
1329 view = e_html_editor_get_view (editor);
1331 status = webkit_web_view_get_load_status (WEBKIT_WEB_VIEW (view));
1332 /* If document is not loaded, we will wait for him */
1333 if (status != WEBKIT_LOAD_FINISHED) {
1334 /* Disconnect previous handlers */
1335 g_signal_handlers_disconnect_by_func (
1336 WEBKIT_WEB_VIEW (view),
1337 G_CALLBACK (composer_web_view_load_status_changed_cb),
1338 composer);
1339 g_signal_connect (
1340 WEBKIT_WEB_VIEW(view), "notify::load-status",
1341 G_CALLBACK (composer_web_view_load_status_changed_cb),
1342 composer);
1343 return;
1346 /* XXX Signature files should be local and therefore load quickly,
1347 * so while we do load them asynchronously we don't allow for
1348 * user cancellation and we keep the composer alive until the
1349 * asynchronous loading is complete. */
1350 e_mail_signature_combo_box_load_selected (
1351 combo_box, G_PRIORITY_DEFAULT, NULL,
1352 (GAsyncReadyCallback) composer_load_signature_cb,
1353 g_object_ref (composer));