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