1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
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
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)
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"
34 composer_setup_charset_menu (EMsgComposer
*composer
)
37 GtkUIManager
*ui_manager
;
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
);
66 composer_update_gallery_visibility (EMsgComposer
*composer
)
69 EHTMLEditorView
*view
;
70 GtkToggleAction
*toggle_action
;
71 gboolean gallery_active
;
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
);
85 gtk_widget_hide (composer
->priv
->gallery_scrolled_window
);
86 gtk_widget_hide (composer
->priv
->gallery_icon_view
);
91 e_composer_private_constructed (EMsgComposer
*composer
)
93 EMsgComposerPrivate
*priv
= composer
->priv
;
94 EFocusTracker
*focus_tracker
;
95 EComposerHeader
*header
;
97 EClientCache
*client_cache
;
99 EHTMLEditorView
*view
;
100 GtkUIManager
*ui_manager
;
102 GtkWidget
*container
;
104 GtkWidget
*send_widget
;
108 gchar
*filename
, *gallery_path
;
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
;
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
);
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
);
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
);
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 (
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
);
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
);
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
;
294 table
= E_COMPOSER_HEADER_TABLE (priv
->header_table
);
295 header
= e_composer_header_table_get_header (table
, 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
))),
302 e_widget_undo_attach (
303 GTK_WIDGET (e_composer_from_header_get_address_entry (E_COMPOSER_FROM_HEADER (header
))),
306 action
= ACTION (VIEW_FROM_OVERRIDE
);
307 e_binding_bind_property (
308 header
, "override-visible",
310 G_BINDING_BIDIRECTIONAL
|
311 G_BINDING_SYNC_CREATE
);
314 case E_COMPOSER_HEADER_BCC
:
315 action
= ACTION (VIEW_BCC
);
318 case E_COMPOSER_HEADER_CC
:
319 action
= ACTION (VIEW_CC
);
322 case E_COMPOSER_HEADER_REPLY_TO
:
323 action
= ACTION (VIEW_REPLY_TO
);
324 e_widget_undo_attach (
325 GTK_WIDGET (header
->input_widget
),
329 case E_COMPOSER_HEADER_SUBJECT
:
330 e_widget_undo_attach (
331 GTK_WIDGET (header
->input_widget
),
339 e_binding_bind_property (
342 G_BINDING_BIDIRECTIONAL
|
343 G_BINDING_SYNC_CREATE
);
345 e_binding_bind_property (
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 (
358 priv
->async_actions
, "sensitive",
359 G_BINDING_SYNC_CREATE
|
360 G_BINDING_INVERT_BOOLEAN
);
362 e_binding_bind_property (
364 priv
->header_table
, "sensitive",
365 G_BINDING_SYNC_CREATE
|
366 G_BINDING_INVERT_BOOLEAN
);
368 g_object_unref (settings
);
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
;
433 e_composer_private_finalize (EMsgComposer
*composer
)
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
);
451 e_composer_find_data_file (const gchar
*basename
)
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
))
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
))
469 g_critical ("Could not locate '%s'", basename
);
475 e_composer_get_default_charset (void)
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') {
489 charset
= g_settings_get_string (settings
, "charset");
490 if (charset
!= NULL
&& *charset
== '\0') {
496 g_object_unref (settings
);
499 charset
= g_strdup (camel_iconv_locale_charset ());
502 charset
= g_strdup ("us-ascii");
508 e_composer_paste_html (EMsgComposer
*composer
,
509 GtkClipboard
*clipboard
)
512 EHTMLEditorView
*view
;
513 EHTMLEditorSelection
*editor_selection
;
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
);
536 e_composer_paste_image (EMsgComposer
*composer
,
537 GtkClipboard
*clipboard
)
540 EHTMLEditorView
*html_editor_view
;
541 EAttachmentStore
*store
;
542 EAttachmentView
*view
;
543 GdkPixbuf
*pixbuf
= NULL
;
544 gchar
*filename
= 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
) {
563 &error
, G_FILE_ERROR
,
564 g_file_error_from_errno (errno
),
565 "Could not create temporary file: %s",
570 /* Save the pixbuf as a temporary file in image/png format. */
571 if (!gdk_pixbuf_save (pixbuf
, filename
, "png", &error
, NULL
))
574 /* Convert the filename to a URI. */
575 uri
= g_filename_to_uri (filename
, NULL
, &error
);
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
);
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
);
604 g_warning ("%s", error
->message
);
605 g_error_free (error
);
608 g_object_unref (pixbuf
);
616 e_composer_paste_text (EMsgComposer
*composer
,
617 GtkClipboard
*clipboard
)
620 EHTMLEditorView
*view
;
621 EHTMLEditorSelection
*editor_selection
;
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
);
645 e_composer_paste_uris (EMsgComposer
*composer
,
646 GtkClipboard
*clipboard
)
648 EAttachmentStore
*store
;
649 EAttachmentView
*view
;
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
);
679 e_composer_selection_is_base64_uris (EMsgComposer
*composer
,
680 GtkSelectionData
*selection
)
682 gboolean all_base64_uris
= TRUE
;
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
);
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
;
704 return all_base64_uris
;
708 e_composer_selection_is_image_uris (EMsgComposer
*composer
,
709 GtkSelectionData
*selection
)
711 gboolean all_image_uris
= TRUE
;
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
);
723 for (ii
= 0; uris
[ii
] != NULL
; ii
++) {
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
;
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
;
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
);
765 if (loader
== NULL
) {
766 all_image_uris
= FALSE
;
770 gdk_pixbuf_loader_close (loader
, NULL
);
771 g_object_unref (loader
);
776 return all_image_uris
;
780 add_signature_delimiter (EMsgComposer
*composer
)
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
;
795 use_top_signature (EMsgComposer
*composer
)
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
;
810 composer_size_allocate_cb (GtkWidget
*widget
,
813 GtkWidget
*scrolled_window
;
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
);
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");
860 composer_move_caret (EMsgComposer
*composer
)
863 EHTMLEditorView
*view
;
864 EHTMLEditorSelection
*editor_selection
;
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
);
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
),
914 if (is_message_from_edit_as_new
) {
915 element
= WEBKIT_DOM_ELEMENT (body
);
916 e_html_editor_selection_block_selection_changed (editor_selection
);
919 e_html_editor_selection_scroll_to_caret (editor_selection
);
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.
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
)),
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");
969 element
= prepare_paragraph (editor_selection
, document
);
970 webkit_dom_node_append_child (
971 WEBKIT_DOM_NODE (body
),
972 WEBKIT_DOM_NODE (element
),
976 element
= WEBKIT_DOM_ELEMENT (body
);
978 g_object_unref (list
);
982 if (!has_paragraphs_in_body
) {
983 element
= prepare_paragraph (editor_selection
, document
);
986 webkit_dom_node_append_child (
987 WEBKIT_DOM_NODE (body
),
988 WEBKIT_DOM_NODE (element
),
991 webkit_dom_node_insert_before (
992 WEBKIT_DOM_NODE (body
),
993 WEBKIT_DOM_NODE (element
),
994 WEBKIT_DOM_NODE (signature
),
999 webkit_dom_node_insert_before (
1000 WEBKIT_DOM_NODE (body
),
1001 WEBKIT_DOM_NODE (element
),
1002 WEBKIT_DOM_NODE (signature
),
1005 element
= WEBKIT_DOM_ELEMENT (body
);
1008 if (!element
&& top_signature
) {
1009 element
= prepare_paragraph (editor_selection
, document
);
1011 webkit_dom_node_append_child (
1012 WEBKIT_DOM_NODE (body
),
1013 WEBKIT_DOM_NODE (element
),
1016 webkit_dom_node_insert_before (
1017 WEBKIT_DOM_NODE (body
),
1018 WEBKIT_DOM_NODE (element
),
1019 WEBKIT_DOM_NODE (signature
),
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
),
1028 } else if (element
&& start_bottom
) {
1029 /* Leave it how it is */
1031 element
= WEBKIT_DOM_ELEMENT (body
);
1034 g_object_unref (list
);
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
);
1044 e_html_editor_selection_scroll_to_caret (editor_selection
);
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
);
1058 composer_load_signature_cb (EMailSignatureComboBox
*combo_box
,
1059 GAsyncResult
*result
,
1060 EMsgComposer
*composer
)
1062 GString
*html_buffer
= NULL
;
1063 gchar
*contents
= NULL
;
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
);
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. */
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
)
1108 /* If inserting HTML signature in plain text composer we have to convert it. */
1109 if (is_html
&& !html_mode
) {
1110 WebKitDOMElement
*element
;
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
));
1120 contents
= inner_text
? g_strstrip (inner_text
) : g_strdup ("");
1127 html
= camel_text_to_html (contents
, 0, 0);
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 (
1145 "<SPAN class=\"-x-evo-signature\" id=\"1\" name=\"%s\">",
1146 (active_id
!= NULL
) ? active_id
: "");
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
)) {
1157 const gchar
*delim_nl
;
1161 delim_nl
= "\n-- <BR>";
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)
1170 else if (e_util_strstrcase (contents
, delim_nl
) != NULL
)
1173 g_string_append (html_buffer
, delim
);
1176 g_string_append_len (html_buffer
, contents
, length
);
1179 g_string_append (html_buffer
, "</PRE>");
1181 g_string_append (html_buffer
, "</SPAN>");
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
;
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
) {
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
);
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
);
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
);
1229 g_object_unref (wrapper
);
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
));
1251 webkit_dom_node_insert_before (
1252 WEBKIT_DOM_NODE (body
),
1253 WEBKIT_DOM_NODE (element
),
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
),
1266 webkit_dom_node_append_child (
1267 WEBKIT_DOM_NODE (body
),
1268 WEBKIT_DOM_NODE (element
),
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
);
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
);
1289 composer_web_view_load_status_changed_cb (WebKitWebView
*webkit_web_view
,
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
)
1302 g_signal_handlers_disconnect_by_func (
1304 G_CALLBACK (composer_web_view_load_status_changed_cb
),
1307 e_composer_update_signature (composer
);
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
)
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
),
1340 WEBKIT_WEB_VIEW(view
), "notify::load-status",
1341 G_CALLBACK (composer_web_view_load_status_changed_cb
),
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
));