1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
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.
7 *
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.
12 *
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/>.
15 *
16 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
17 */
18
19 #include "evolution-config.h"
20
21 #include "e-composer-private.h"
22 #include "e-composer-from-header.h"
23 #include "e-composer-spell-header.h"
24 #include "e-util/e-util-private.h"
25
26 /* Initial height of the picture gallery. */
27 #define GALLERY_INITIAL_HEIGHT 150
28
29 static void
composer_setup_charset_menu(EMsgComposer * composer)30 composer_setup_charset_menu (EMsgComposer *composer)
31 {
32 EHTMLEditor *editor;
33 GtkUIManager *ui_manager;
34 const gchar *path;
35 GList *list;
36 guint merge_id;
37
38 editor = e_msg_composer_get_editor (composer);
39 ui_manager = e_html_editor_get_ui_manager (editor);
40 path = "/main-menu/options-menu/charset-menu";
41 merge_id = gtk_ui_manager_new_merge_id (ui_manager);
42
43 list = gtk_action_group_list_actions (composer->priv->charset_actions);
44 list = g_list_sort (list, (GCompareFunc) e_action_compare_by_label);
45
46 while (list != NULL) {
47 GtkAction *action = list->data;
48
49 gtk_ui_manager_add_ui (
50 ui_manager, merge_id, path,
51 gtk_action_get_name (action),
52 gtk_action_get_name (action),
53 GTK_UI_MANAGER_AUTO, FALSE);
54
55 list = g_list_delete_link (list, list);
56 }
57
58 gtk_ui_manager_ensure_update (ui_manager);
59 }
60
61 static void
composer_update_gallery_visibility(EMsgComposer * composer)62 composer_update_gallery_visibility (EMsgComposer *composer)
63 {
64 EHTMLEditor *editor;
65 EContentEditor *cnt_editor;
66 GtkToggleAction *toggle_action;
67 gboolean gallery_active;
68 gboolean is_html;
69
70 editor = e_msg_composer_get_editor (composer);
71 cnt_editor = e_html_editor_get_content_editor (editor);
72 is_html = e_content_editor_get_html_mode (cnt_editor);
73
74 toggle_action = GTK_TOGGLE_ACTION (ACTION (PICTURE_GALLERY));
75 gallery_active = gtk_toggle_action_get_active (toggle_action);
76
77 if (is_html && gallery_active) {
78 gtk_widget_show (composer->priv->gallery_scrolled_window);
79 gtk_widget_show (composer->priv->gallery_icon_view);
80 } else {
81 gtk_widget_hide (composer->priv->gallery_scrolled_window);
82 gtk_widget_hide (composer->priv->gallery_icon_view);
83 }
84 }
85
86 static gchar *
e_composer_extract_lang_from_source(ESourceRegistry * registry,const gchar * uid)87 e_composer_extract_lang_from_source (ESourceRegistry *registry,
88 const gchar *uid)
89 {
90 ESource *source;
91 gchar *lang = NULL;
92
93 g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
94 g_return_val_if_fail (uid != NULL, NULL);
95
96 source = e_source_registry_ref_source (registry, uid);
97 if (source && e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_COMPOSITION)) {
98 ESourceMailComposition *mail_composition;
99
100 mail_composition = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_COMPOSITION);
101 lang = e_source_mail_composition_dup_language (mail_composition);
102
103 if (lang && !*lang) {
104 g_free (lang);
105 lang = NULL;
106 }
107 }
108
109 g_clear_object (&source);
110
111 return lang;
112 }
113
114 static void
e_composer_from_changed_cb(EComposerFromHeader * header,EMsgComposer * composer)115 e_composer_from_changed_cb (EComposerFromHeader *header,
116 EMsgComposer *composer)
117 {
118 gchar *current_uid;
119
120 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
121
122 current_uid = e_composer_from_header_dup_active_id (header, NULL, NULL);
123
124 if (current_uid && g_strcmp0 (composer->priv->previous_identity_uid, current_uid) != 0) {
125 gchar *previous_lang = NULL, *current_lang = NULL;
126 ESourceRegistry *registry;
127
128 registry = e_composer_header_get_registry (E_COMPOSER_HEADER (header));
129
130 if (composer->priv->previous_identity_uid)
131 previous_lang = e_composer_extract_lang_from_source (registry, composer->priv->previous_identity_uid);
132
133 current_lang = e_composer_extract_lang_from_source (registry, current_uid);
134
135 if (g_strcmp0 (previous_lang, current_lang) != 0) {
136 GSettings *settings;
137 gchar **strv;
138 gboolean have_previous, have_current;
139 gint ii;
140
141 settings = e_util_ref_settings ("org.gnome.evolution.mail");
142 strv = g_settings_get_strv (settings, "composer-spell-languages");
143 g_object_unref (settings);
144
145 have_previous = !previous_lang;
146 have_current = !current_lang;
147
148 for (ii = 0; strv && strv[ii] && (!have_previous || !have_current); ii++) {
149 have_previous = have_previous || g_strcmp0 (previous_lang, strv[ii]) == 0;
150 have_current = have_current || g_strcmp0 (current_lang, strv[ii]) == 0;
151 }
152
153 g_strfreev (strv);
154
155 if (!have_previous || !have_current) {
156 ESpellChecker *spell_checker;
157 EHTMLEditor *editor;
158
159 editor = e_msg_composer_get_editor (composer);
160 spell_checker = e_content_editor_ref_spell_checker (e_html_editor_get_content_editor (editor));
161
162 if (!have_previous)
163 e_spell_checker_set_language_active (spell_checker, previous_lang, FALSE);
164
165 if (!have_current)
166 e_spell_checker_set_language_active (spell_checker, current_lang, TRUE);
167
168 g_clear_object (&spell_checker);
169
170 e_html_editor_update_spell_actions (editor);
171 g_signal_emit_by_name (editor, "spell-languages-changed");
172 }
173 }
174
175 g_free (previous_lang);
176 g_free (current_lang);
177
178 g_free (composer->priv->previous_identity_uid);
179 composer->priv->previous_identity_uid = current_uid;
180 } else {
181 g_free (current_uid);
182 }
183 }
184
185 void
e_composer_private_constructed(EMsgComposer * composer)186 e_composer_private_constructed (EMsgComposer *composer)
187 {
188 EMsgComposerPrivate *priv = composer->priv;
189 EFocusTracker *focus_tracker;
190 EComposerHeader *header;
191 EShell *shell;
192 EClientCache *client_cache;
193 EHTMLEditor *editor;
194 EContentEditor *cnt_editor;
195 GtkUIManager *ui_manager;
196 GtkAction *action;
197 GtkWidget *container;
198 GtkWidget *widget;
199 GtkWidget *send_widget;
200 GtkWindow *window;
201 GSettings *settings;
202 const gchar *path;
203 gchar *filename, *gallery_path;
204 gint ii;
205 GError *error = NULL;
206
207 editor = e_msg_composer_get_editor (composer);
208 ui_manager = e_html_editor_get_ui_manager (editor);
209 cnt_editor = e_html_editor_get_content_editor (editor);
210
211 settings = e_util_ref_settings ("org.gnome.evolution.mail");
212
213 shell = e_msg_composer_get_shell (composer);
214 client_cache = e_shell_get_client_cache (shell);
215
216 /* Each composer window gets its own window group. */
217 window = GTK_WINDOW (composer);
218 priv->window_group = gtk_window_group_new ();
219 gtk_window_group_add_window (priv->window_group, window);
220
221 priv->async_actions = gtk_action_group_new ("async");
222 priv->charset_actions = gtk_action_group_new ("charset");
223 priv->composer_actions = gtk_action_group_new ("composer");
224
225 priv->extra_hdr_names = g_ptr_array_new ();
226 priv->extra_hdr_values = g_ptr_array_new ();
227
228 priv->charset = e_composer_get_default_charset ();
229
230 priv->set_signature_from_message = FALSE;
231 priv->disable_signature = FALSE;
232 priv->soft_busy_count = 0;
233 priv->had_activities = FALSE;
234 priv->saved_editable = FALSE;
235 priv->dnd_history_saved = FALSE;
236 priv->check_if_signature_is_changed = FALSE;
237 priv->ignore_next_signature_change = FALSE;
238
239 priv->focused_entry = NULL;
240
241 e_composer_actions_init (composer);
242
243 filename = e_composer_find_data_file ("evolution-composer.ui");
244 gtk_ui_manager_add_ui_from_file (ui_manager, filename, &error);
245 g_free (filename);
246
247 /* We set the send button as important to have a label */
248 path = "/main-toolbar/pre-main-toolbar/send";
249 send_widget = gtk_ui_manager_get_widget (ui_manager, path);
250 gtk_tool_item_set_is_important (GTK_TOOL_ITEM (send_widget), TRUE);
251
252 composer_setup_charset_menu (composer);
253
254 if (error != NULL) {
255 /* Henceforth, bad things start happening. */
256 g_critical ("%s", error->message);
257 g_clear_error (&error);
258 }
259
260 /* Configure an EFocusTracker to manage selection actions. */
261
262 focus_tracker = e_focus_tracker_new (GTK_WINDOW (composer));
263
264 action = e_html_editor_get_action (editor, "cut");
265 e_focus_tracker_set_cut_clipboard_action (focus_tracker, action);
266
267 action = e_html_editor_get_action (editor, "copy");
268 e_focus_tracker_set_copy_clipboard_action (focus_tracker, action);
269
270 action = e_html_editor_get_action (editor, "paste");
271 e_focus_tracker_set_paste_clipboard_action (focus_tracker, action);
272
273 action = e_html_editor_get_action (editor, "select-all");
274 e_focus_tracker_set_select_all_action (focus_tracker, action);
275
276 action = e_html_editor_get_action (editor, "undo");
277 e_focus_tracker_set_undo_action (focus_tracker, action);
278
279 action = e_html_editor_get_action (editor, "redo");
280 e_focus_tracker_set_redo_action (focus_tracker, action);
281
282 priv->focus_tracker = focus_tracker;
283
284 widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
285 gtk_container_add (GTK_CONTAINER (composer), widget);
286 gtk_widget_show (widget);
287
288 container = widget;
289
290 /* Construct the main menu and toolbar. */
291
292 widget = e_html_editor_get_managed_widget (editor, "/main-menu");
293 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
294 gtk_widget_show (widget);
295
296 widget = e_html_editor_get_managed_widget (editor, "/main-toolbar");
297 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
298 gtk_widget_show (widget);
299
300 /* Construct the header table. */
301
302 widget = e_composer_header_table_new (client_cache);
303 gtk_container_set_border_width (GTK_CONTAINER (widget), 6);
304 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
305 priv->header_table = g_object_ref (widget);
306 gtk_widget_show (widget);
307
308 header = e_composer_header_table_get_header (
309 E_COMPOSER_HEADER_TABLE (widget),
310 E_COMPOSER_HEADER_SUBJECT);
311 e_binding_bind_property (
312 cnt_editor, "spell-checker",
313 header->input_widget, "spell-checker",
314 G_BINDING_SYNC_CREATE);
315
316 /* Construct the editing toolbars. We'll have to reparent
317 * the embedded EHTMLEditorView a little further down. */
318
319 widget = GTK_WIDGET (editor);
320 gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
321 gtk_widget_show (widget);
322
323 /* Construct the attachment paned. */
324
325 widget = e_attachment_paned_new ();
326 gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
327 priv->attachment_paned = g_object_ref_sink (widget);
328 gtk_widget_show (widget);
329
330 e_binding_bind_property (
331 cnt_editor, "editable",
332 widget, "sensitive",
333 G_BINDING_SYNC_CREATE);
334
335 container = e_attachment_paned_get_content_area (
336 E_ATTACHMENT_PANED (priv->attachment_paned));
337
338 widget = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
339 gtk_paned_set_wide_handle (GTK_PANED (widget), TRUE);
340 gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
341 gtk_widget_show (widget);
342
343 container = widget;
344
345 widget = gtk_scrolled_window_new (NULL, NULL);
346 gtk_scrolled_window_set_policy (
347 GTK_SCROLLED_WINDOW (widget),
348 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
349 gtk_widget_set_size_request (widget, -1, GALLERY_INITIAL_HEIGHT);
350 gtk_paned_pack1 (GTK_PANED (container), widget, FALSE, FALSE);
351 priv->gallery_scrolled_window = g_object_ref (widget);
352 gtk_widget_show (widget);
353
354 widget = GTK_WIDGET (cnt_editor);
355 if (GTK_IS_SCROLLABLE (cnt_editor)) {
356 /* Scrollables are packed in a scrolled window */
357 widget = gtk_widget_get_parent (widget);
358 g_warn_if_fail (GTK_IS_SCROLLED_WINDOW (widget));
359 }
360 gtk_widget_reparent (widget, container);
361
362 /* Construct the picture gallery. */
363
364 container = priv->gallery_scrolled_window;
365
366 /* FIXME This should be an EMsgComposer property. */
367 gallery_path = g_settings_get_string (
368 settings, "composer-gallery-path");
369 widget = e_picture_gallery_new (gallery_path);
370 gtk_container_add (GTK_CONTAINER (container), widget);
371 priv->gallery_icon_view = g_object_ref_sink (widget);
372 g_free (gallery_path);
373
374 e_signal_connect_notify_swapped (
375 cnt_editor, "notify::html-mode",
376 G_CALLBACK (composer_update_gallery_visibility), composer);
377
378 g_signal_connect_swapped (
379 ACTION (PICTURE_GALLERY), "toggled",
380 G_CALLBACK (composer_update_gallery_visibility), composer);
381
382 /* Initial sync */
383 composer_update_gallery_visibility (composer);
384
385 /* Bind headers to their corresponding actions. */
386
387 for (ii = 0; ii < E_COMPOSER_NUM_HEADERS; ii++) {
388 EComposerHeaderTable *table;
389 EComposerHeader *header;
390
391 table = E_COMPOSER_HEADER_TABLE (priv->header_table);
392 header = e_composer_header_table_get_header (table, ii);
393
394 switch (ii) {
395 case E_COMPOSER_HEADER_FROM:
396 e_widget_undo_attach (
397 GTK_WIDGET (e_composer_from_header_get_name_entry (E_COMPOSER_FROM_HEADER (header))),
398 focus_tracker);
399 e_widget_undo_attach (
400 GTK_WIDGET (e_composer_from_header_get_address_entry (E_COMPOSER_FROM_HEADER (header))),
401 focus_tracker);
402
403 action = ACTION (VIEW_FROM_OVERRIDE);
404 e_binding_bind_property (
405 header, "override-visible",
406 action, "active",
407 G_BINDING_BIDIRECTIONAL |
408 G_BINDING_SYNC_CREATE);
409
410 g_signal_connect (header, "changed",
411 G_CALLBACK (e_composer_from_changed_cb), composer);
412 continue;
413
414 case E_COMPOSER_HEADER_BCC:
415 action = ACTION (VIEW_BCC);
416 break;
417
418 case E_COMPOSER_HEADER_CC:
419 action = ACTION (VIEW_CC);
420 break;
421
422 case E_COMPOSER_HEADER_REPLY_TO:
423 action = ACTION (VIEW_REPLY_TO);
424 e_widget_undo_attach (
425 GTK_WIDGET (header->input_widget),
426 focus_tracker);
427 break;
428
429 case E_COMPOSER_HEADER_SUBJECT:
430 e_widget_undo_attach (
431 GTK_WIDGET (header->input_widget),
432 focus_tracker);
433 continue;
434
435 default:
436 continue;
437 }
438
439 e_binding_bind_property (
440 header, "sensitive",
441 action, "sensitive",
442 G_BINDING_BIDIRECTIONAL |
443 G_BINDING_SYNC_CREATE);
444
445 e_binding_bind_property (
446 header, "visible",
447 action, "active",
448 G_BINDING_BIDIRECTIONAL |
449 G_BINDING_SYNC_CREATE);
450 }
451
452 g_settings_bind (
453 settings, "composer-visually-wrap-long-lines",
454 cnt_editor, "visually-wrap-long-lines",
455 G_SETTINGS_BIND_DEFAULT);
456
457
458 /* Disable actions that start asynchronous activities while an
459 * asynchronous activity is in progress. We enforce this with
460 * a simple inverted binding to EMsgComposer's "busy" property. */
461
462 e_binding_bind_property (
463 composer, "soft-busy",
464 priv->async_actions, "sensitive",
465 G_BINDING_SYNC_CREATE |
466 G_BINDING_INVERT_BOOLEAN);
467
468 e_binding_bind_property (
469 composer, "busy",
470 priv->header_table, "sensitive",
471 G_BINDING_SYNC_CREATE |
472 G_BINDING_INVERT_BOOLEAN);
473
474 g_object_unref (settings);
475 }
476
477 void
e_composer_private_dispose(EMsgComposer * composer)478 e_composer_private_dispose (EMsgComposer *composer)
479 {
480 if (composer->priv->shell != NULL) {
481 g_object_remove_weak_pointer (
482 G_OBJECT (composer->priv->shell),
483 &composer->priv->shell);
484 composer->priv->shell = NULL;
485 }
486
487 g_clear_object (&composer->priv->editor);
488 g_clear_object (&composer->priv->header_table);
489 g_clear_object (&composer->priv->attachment_paned);
490 g_clear_object (&composer->priv->focus_tracker);
491 g_clear_object (&composer->priv->window_group);
492 g_clear_object (&composer->priv->async_actions);
493 g_clear_object (&composer->priv->charset_actions);
494 g_clear_object (&composer->priv->composer_actions);
495 g_clear_object (&composer->priv->gallery_scrolled_window);
496 g_clear_object (&composer->priv->redirect);
497 }
498
499 void
e_composer_private_finalize(EMsgComposer * composer)500 e_composer_private_finalize (EMsgComposer *composer)
501 {
502 GPtrArray *array;
503
504 array = composer->priv->extra_hdr_names;
505 g_ptr_array_foreach (array, (GFunc) g_free, NULL);
506 g_ptr_array_free (array, TRUE);
507
508 array = composer->priv->extra_hdr_values;
509 g_ptr_array_foreach (array, (GFunc) g_free, NULL);
510 g_ptr_array_free (array, TRUE);
511
512 g_clear_object (&composer->priv->load_signature_cancellable);
513
514 g_free (composer->priv->charset);
515 g_free (composer->priv->mime_type);
516 g_free (composer->priv->mime_body);
517 g_free (composer->priv->previous_identity_uid);
518
519 g_clear_pointer (&composer->priv->content_hash, e_content_editor_util_free_content_hash);
520 }
521
522 gchar *
e_composer_find_data_file(const gchar * basename)523 e_composer_find_data_file (const gchar *basename)
524 {
525 gchar *filename;
526
527 g_return_val_if_fail (basename != NULL, NULL);
528
529 /* Support running directly from the source tree. */
530 filename = g_build_filename (".", basename, NULL);
531 if (g_file_test (filename, G_FILE_TEST_EXISTS))
532 return filename;
533 g_free (filename);
534
535 /* XXX This is kinda broken. */
536 filename = g_build_filename (EVOLUTION_UIDIR, basename, NULL);
537 if (g_file_test (filename, G_FILE_TEST_EXISTS))
538 return filename;
539 g_free (filename);
540
541 g_critical ("Could not locate '%s'", basename);
542
543 return NULL;
544 }
545
546 gchar *
e_composer_get_default_charset(void)547 e_composer_get_default_charset (void)
548 {
549 GSettings *settings;
550 gchar *charset;
551
552 settings = e_util_ref_settings ("org.gnome.evolution.mail");
553
554 charset = g_settings_get_string (settings, "composer-charset");
555
556 if (!charset || !*charset) {
557 g_free (charset);
558 charset = NULL;
559 }
560
561 g_object_unref (settings);
562
563 if (!charset)
564 charset = g_strdup ("UTF-8");
565
566 return charset;
567 }
568
569 gboolean
e_composer_paste_image(EMsgComposer * composer,GtkClipboard * clipboard)570 e_composer_paste_image (EMsgComposer *composer,
571 GtkClipboard *clipboard)
572 {
573 EAttachment *attachment;
574 EAttachmentStore *store;
575 EAttachmentView *view;
576 gchar *uri;
577
578 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
579 g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);
580
581 view = e_msg_composer_get_attachment_view (composer);
582 store = e_attachment_view_get_store (view);
583
584 if (!(uri = e_util_save_image_from_clipboard (clipboard)))
585 return FALSE;
586
587 attachment = e_attachment_new_for_uri (uri);
588 e_attachment_store_add_attachment (store, attachment);
589 e_attachment_load_async (
590 attachment, (GAsyncReadyCallback)
591 e_attachment_load_handle_error, composer);
592 g_object_unref (attachment);
593
594
595 g_free (uri);
596
597 return TRUE;
598 }
599
600 gboolean
e_composer_paste_uris(EMsgComposer * composer,GtkClipboard * clipboard)601 e_composer_paste_uris (EMsgComposer *composer,
602 GtkClipboard *clipboard)
603 {
604 EAttachmentStore *store;
605 EAttachmentView *view;
606 gchar **uris;
607 gint ii;
608
609 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
610 g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);
611
612 view = e_msg_composer_get_attachment_view (composer);
613 store = e_attachment_view_get_store (view);
614
615 /* Extract the URI data from the clipboard. */
616 uris = gtk_clipboard_wait_for_uris (clipboard);
617 g_return_val_if_fail (uris != NULL, FALSE);
618
619 /* Add the URIs to the attachment store. */
620 for (ii = 0; uris[ii] != NULL; ii++) {
621 EAttachment *attachment;
622
623 attachment = e_attachment_new_for_uri (uris[ii]);
624 e_attachment_store_add_attachment (store, attachment);
625 e_attachment_load_async (
626 attachment, (GAsyncReadyCallback)
627 e_attachment_load_handle_error, composer);
628 g_object_unref (attachment);
629 }
630
631 return TRUE;
632 }
633
634 gboolean
e_composer_selection_is_base64_uris(EMsgComposer * composer,GtkSelectionData * selection)635 e_composer_selection_is_base64_uris (EMsgComposer *composer,
636 GtkSelectionData *selection)
637 {
638 gboolean all_base64_uris = TRUE;
639 gchar **uris;
640 guint ii;
641
642 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
643 g_return_val_if_fail (selection != NULL, FALSE);
644
645 uris = gtk_selection_data_get_uris (selection);
646
647 if (!uris)
648 return FALSE;
649
650 for (ii = 0; uris[ii] != NULL; ii++) {
651 if (!((g_str_has_prefix (uris[ii], "data:") || strstr (uris[ii], ";data:"))
652 && strstr (uris[ii], ";base64,"))) {
653 all_base64_uris = FALSE;
654 break;
655 }
656 }
657
658 g_strfreev (uris);
659
660 return all_base64_uris;
661 }
662
663 gboolean
e_composer_selection_is_image_uris(EMsgComposer * composer,GtkSelectionData * selection)664 e_composer_selection_is_image_uris (EMsgComposer *composer,
665 GtkSelectionData *selection)
666 {
667 gboolean all_image_uris = TRUE;
668 gchar **uris;
669 guint ii;
670
671 g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
672 g_return_val_if_fail (selection != NULL, FALSE);
673
674 uris = gtk_selection_data_get_uris (selection);
675
676 if (!uris)
677 return FALSE;
678
679 for (ii = 0; uris[ii] != NULL; ii++) {
680 GFile *file;
681 GFileInfo *file_info;
682 GdkPixbufLoader *loader;
683 const gchar *attribute;
684 const gchar *content_type;
685 gchar *mime_type = NULL;
686
687 file = g_file_new_for_uri (uris[ii]);
688 attribute = G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE;
689
690 /* XXX This blocks, but we're requesting the fast content
691 * type (which only inspects filenames), so hopefully
692 * it won't be noticeable. Also, this is best effort
693 * so we don't really care if it fails. */
694 file_info = g_file_query_info (
695 file, attribute, G_FILE_QUERY_INFO_NONE, NULL, NULL);
696
697 if (file_info == NULL) {
698 g_object_unref (file);
699 all_image_uris = FALSE;
700 break;
701 }
702
703 content_type = g_file_info_get_attribute_string (
704 file_info, attribute);
705 mime_type = g_content_type_get_mime_type (content_type);
706
707 g_object_unref (file_info);
708 g_object_unref (file);
709
710 if (mime_type == NULL) {
711 all_image_uris = FALSE;
712 break;
713 }
714
715 /* Easy way to determine if a MIME type is a supported
716 * image format: try creating a GdkPixbufLoader for it. */
717 loader = gdk_pixbuf_loader_new_with_mime_type (mime_type, NULL);
718
719 g_free (mime_type);
720
721 if (loader == NULL) {
722 all_image_uris = FALSE;
723 break;
724 }
725
726 gdk_pixbuf_loader_close (loader, NULL);
727 g_object_unref (loader);
728 }
729
730 g_strfreev (uris);
731
732 return all_image_uris;
733 }
734
735 typedef struct _UpdateSignatureData {
736 EMsgComposer *composer;
737 gboolean can_reposition_caret;
738 } UpdateSignatureData;
739
740 static void
update_signature_data_free(gpointer ptr)741 update_signature_data_free (gpointer ptr)
742 {
743 UpdateSignatureData *usd = ptr;
744
745 if (usd) {
746 g_clear_object (&usd->composer);
747 g_slice_free (UpdateSignatureData, usd);
748 }
749 }
750
751 static void
composer_load_signature_cb(EMailSignatureComboBox * combo_box,GAsyncResult * result,gpointer user_data)752 composer_load_signature_cb (EMailSignatureComboBox *combo_box,
753 GAsyncResult *result,
754 gpointer user_data)
755 {
756 UpdateSignatureData *usd = user_data;
757 EMsgComposer *composer = usd->composer;
758 gchar *contents = NULL, *new_signature_id;
759 gsize length = 0;
760 gboolean is_html;
761 GError *error = NULL;
762 EHTMLEditor *editor;
763 EContentEditor *cnt_editor;
764
765 e_mail_signature_combo_box_load_selected_finish (
766 combo_box, result, &contents, &length, &is_html, &error);
767
768 /* FIXME Use an EAlert here. */
769 if (error != NULL) {
770 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
771 g_warning ("%s: %s", G_STRFUNC, error->message);
772 g_error_free (error);
773 update_signature_data_free (usd);
774 return;
775 }
776
777 g_clear_object (&composer->priv->load_signature_cancellable);
778
779 if (composer->priv->ignore_next_signature_change) {
780 composer->priv->ignore_next_signature_change = FALSE;
781 update_signature_data_free (usd);
782 return;
783 }
784
785 editor = e_msg_composer_get_editor (composer);
786 cnt_editor = e_html_editor_get_content_editor (editor);
787
788 new_signature_id = e_content_editor_insert_signature (
789 cnt_editor,
790 contents,
791 is_html,
792 usd->can_reposition_caret,
793 gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box)),
794 &composer->priv->set_signature_from_message,
795 &composer->priv->check_if_signature_is_changed,
796 &composer->priv->ignore_next_signature_change);
797
798 if (new_signature_id && *new_signature_id) {
799 gboolean been_ignore = composer->priv->ignore_next_signature_change;
800 gboolean signature_changed = g_strcmp0 (gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box)), new_signature_id) != 0;
801
802 composer->priv->ignore_next_signature_change = been_ignore && signature_changed;
803
804 if (!gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), new_signature_id)) {
805 signature_changed = g_strcmp0 (gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box)), "none") != 0;
806
807 composer->priv->ignore_next_signature_change = been_ignore && signature_changed;
808
809 gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), "none");
810 }
811
812 if (!signature_changed && composer->priv->check_if_signature_is_changed) {
813 composer->priv->set_signature_from_message = FALSE;
814 composer->priv->check_if_signature_is_changed = FALSE;
815 composer->priv->ignore_next_signature_change = FALSE;
816 }
817 }
818
819 g_free (new_signature_id);
820 g_free (contents);
821 update_signature_data_free (usd);
822 }
823
824 static void
content_editor_load_finished_cb(EContentEditor * cnt_editor,EMsgComposer * composer)825 content_editor_load_finished_cb (EContentEditor *cnt_editor,
826 EMsgComposer *composer)
827 {
828 g_signal_handlers_disconnect_by_func (
829 cnt_editor, G_CALLBACK (content_editor_load_finished_cb), composer);
830
831 e_composer_update_signature (composer);
832 }
833
834 void
e_composer_update_signature(EMsgComposer * composer)835 e_composer_update_signature (EMsgComposer *composer)
836 {
837 EComposerHeaderTable *table;
838 EMailSignatureComboBox *combo_box;
839 EHTMLEditor *editor;
840 EContentEditor *cnt_editor;
841 UpdateSignatureData *usd;
842
843 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
844
845 if (composer->priv->load_signature_cancellable) {
846 g_cancellable_cancel (composer->priv->load_signature_cancellable);
847 g_clear_object (&composer->priv->load_signature_cancellable);
848 }
849
850 /* Do nothing if we're redirecting a message or we disabled
851 * the signature on purpose */
852 if (composer->priv->redirect || composer->priv->disable_signature)
853 return;
854
855 table = e_msg_composer_get_header_table (composer);
856 combo_box = e_composer_header_table_get_signature_combo_box (table);
857 editor = e_msg_composer_get_editor (composer);
858 cnt_editor = e_html_editor_get_content_editor (editor);
859
860 if (!e_content_editor_is_ready (cnt_editor)) {
861 g_signal_connect (
862 cnt_editor, "load-finished",
863 G_CALLBACK (content_editor_load_finished_cb),
864 composer);
865 return;
866 }
867
868 composer->priv->load_signature_cancellable = g_cancellable_new ();
869
870 usd = g_slice_new (UpdateSignatureData);
871 usd->composer = g_object_ref (composer);
872 usd->can_reposition_caret = e_msg_composer_get_is_reply_or_forward (composer) &&
873 !gtk_widget_get_realized (GTK_WIDGET (composer));
874
875 /* XXX Signature files should be local and therefore load quickly,
876 * so while we do load them asynchronously we don't allow for
877 * user cancellation and we keep the composer alive until the
878 * asynchronous loading is complete. */
879 e_mail_signature_combo_box_load_selected (
880 combo_box, G_PRIORITY_DEFAULT, composer->priv->load_signature_cancellable,
881 (GAsyncReadyCallback) composer_load_signature_cb,
882 usd);
883 }
884