1 /*
2  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
3  * Copyright (C) 2008 - Diego Escalante Urrelo
4  * Copyright (C) 2018 Red Hat, Inc. (www.redhat.com)
5  *
6  * This program is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by
8  * the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13  * for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Authors:
19  *		Diego Escalante Urrelo <diegoe@gnome.org>
20  *		Bharath Acharya <abharath@novell.com>
21  */
22 
23 #include "evolution-config.h"
24 
25 #include <gtk/gtk.h>
26 #include <glib/gi18n.h>
27 #include <string.h>
28 
29 #include "e-util/e-util.h"
30 
31 #include "shell/e-shell-view.h"
32 
33 #include "mail/e-mail-reader.h"
34 #include "mail/e-mail-reader-utils.h"
35 #include "mail/e-mail-ui-session.h"
36 #include "mail/e-mail-templates.h"
37 #include "mail/e-mail-templates-store.h"
38 #include "mail/em-composer-utils.h"
39 #include "mail/em-utils.h"
40 #include "mail/message-list.h"
41 
42 #include "composer/e-msg-composer.h"
43 
44 #define CONF_KEY_TEMPLATE_PLACEHOLDERS "template-placeholders"
45 
46 typedef struct _AsyncContext AsyncContext;
47 
48 struct _AsyncContext {
49 	EActivity *activity;
50 	EMailReader *reader;
51 	CamelMimeMessage *source_message;
52 	CamelMimeMessage *new_message;
53 	CamelFolder *template_folder;
54 	CamelFolder *source_folder;
55 	gchar *source_folder_uri;
56 	gchar *source_message_uid;
57 	gchar *orig_source_message_uid;
58 	gchar *template_message_uid;
59 	gboolean selection_is_html;
60 	EMailPartValidityFlags validity_pgp_sum;
61 	EMailPartValidityFlags validity_smime_sum;
62 };
63 
64 typedef struct {
65 	GSettings   *settings;
66 	GtkWidget   *treeview;
67 	GtkWidget   *clue_add;
68 	GtkWidget   *clue_edit;
69 	GtkWidget   *clue_remove;
70 	GtkListStore *store;
71 } UIData;
72 
73 enum {
74 	CLUE_KEYWORD_COLUMN,
75 	CLUE_VALUE_COLUMN,
76 	CLUE_N_COLUMNS
77 };
78 
79 GtkWidget *	e_plugin_lib_get_configure_widget
80 						(EPlugin *plugin);
81 gboolean	init_composer_actions		(GtkUIManager *ui_manager,
82 						 EMsgComposer *composer);
83 gboolean	init_shell_actions		(GtkUIManager *ui_manager,
84 						 EShellWindow *shell_window);
85 gint		e_plugin_lib_enable		(EPlugin *plugin,
86 						 gboolean enabled);
87 
88 #define TEMPLATES_DATA_KEY "templates::data"
89 
90 typedef struct _TemplatesData {
91 	EMailTemplatesStore *templates_store;
92 	gulong changed_handler_id;
93 	gboolean changed;
94 	guint merge_id;
95 } TemplatesData;
96 
97 static void
templates_data_free(gpointer ptr)98 templates_data_free (gpointer ptr)
99 {
100 	TemplatesData *td = ptr;
101 
102 	if (td) {
103 		if (td->templates_store && td->changed_handler_id) {
104 			g_signal_handler_disconnect (td->templates_store, td->changed_handler_id);
105 			td->changed_handler_id = 0;
106 		}
107 
108 		g_clear_object (&td->templates_store);
109 		g_free (td);
110 	}
111 }
112 
113 /* Thanks to attachment reminder plugin for this*/
114 static void commit_changes (UIData *ui);
115 
116 static void  key_cell_edited_callback (GtkCellRendererText *cell, gchar *path_string,
117 				   gchar *new_text,UIData *ui);
118 
119 static void  value_cell_edited_callback (GtkCellRendererText *cell, gchar *path_string,
120 				   gchar *new_text,UIData *ui);
121 
122 static gboolean clue_foreach_check_isempty (GtkTreeModel *model, GtkTreePath
123 					*path, GtkTreeIter *iter, UIData *ui);
124 
125 static gboolean plugin_enabled;
126 
127 static void
async_context_free(AsyncContext * context)128 async_context_free (AsyncContext *context)
129 {
130 	g_clear_object (&context->activity);
131 	g_clear_object (&context->reader);
132 	g_clear_object (&context->source_message);
133 	g_clear_object (&context->new_message);
134 	g_clear_object (&context->source_folder);
135 	g_clear_object (&context->template_folder);
136 
137 	g_free (context->source_folder_uri);
138 	g_free (context->source_message_uid);
139 	g_free (context->orig_source_message_uid);
140 	g_free (context->template_message_uid);
141 
142 	g_slice_free (AsyncContext, context);
143 }
144 
145 static void
selection_changed(GtkTreeSelection * selection,UIData * ui)146 selection_changed (GtkTreeSelection *selection,
147                    UIData *ui)
148 {
149 	GtkTreeModel *model;
150 	GtkTreeIter iter;
151 
152 	if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
153 		gtk_widget_set_sensitive (ui->clue_edit, TRUE);
154 		gtk_widget_set_sensitive (ui->clue_remove, TRUE);
155 	} else {
156 		gtk_widget_set_sensitive (ui->clue_edit, FALSE);
157 		gtk_widget_set_sensitive (ui->clue_remove, FALSE);
158 	}
159 }
160 
161 static void
destroy_ui_data(gpointer data)162 destroy_ui_data (gpointer data)
163 {
164 	UIData *ui = (UIData *) data;
165 
166 	if (!ui)
167 		return;
168 
169 	g_object_unref (ui->settings);
170 	g_free (ui);
171 }
172 
173 static void
commit_changes(UIData * ui)174 commit_changes (UIData *ui)
175 {
176 	GtkTreeModel *model = NULL;
177 	GVariantBuilder b;
178 	GtkTreeIter iter;
179 	gboolean valid;
180 	GVariant *v;
181 
182 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (ui->treeview));
183 	valid = gtk_tree_model_get_iter_first (model, &iter);
184 
185 	g_variant_builder_init (&b, G_VARIANT_TYPE ("as"));
186 	while (valid) {
187 		gchar *keyword, *value;
188 		gchar *key;
189 
190 		gtk_tree_model_get (
191 			model, &iter,
192 			CLUE_KEYWORD_COLUMN, &keyword,
193 			CLUE_VALUE_COLUMN, &value,
194 			-1);
195 
196 		/* Check if the keyword and value are not empty */
197 		if ((keyword) && (value) && (g_utf8_strlen (g_strstrip (keyword), -1) > 0)
198 			&& (g_utf8_strlen (g_strstrip (value), -1) > 0)) {
199 			key = g_strdup_printf ("%s=%s", keyword, value);
200 			g_variant_builder_add (&b, "s", key);
201 		}
202 
203 		g_free (keyword);
204 		g_free (value);
205 
206 		valid = gtk_tree_model_iter_next (model, &iter);
207 	}
208 
209 	/* A floating GVariant is returned, which is consumed by the g_settings_set_value() */
210 	v = g_variant_builder_end (&b);
211 	g_settings_set_value (ui->settings, CONF_KEY_TEMPLATE_PLACEHOLDERS, v);
212 }
213 
214 static void
clue_check_isempty(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,UIData * ui)215 clue_check_isempty (GtkTreeModel *model,
216                     GtkTreePath *path,
217                     GtkTreeIter *iter,
218                     UIData *ui)
219 {
220 	GtkTreeSelection *selection;
221 	gchar *keyword = NULL;
222 	gboolean valid;
223 
224 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ui->treeview));
225 	/* move to the previous node */
226 	valid = gtk_tree_path_prev (path);
227 
228 	gtk_tree_model_get (model, iter, CLUE_KEYWORD_COLUMN, &keyword, -1);
229 	if ((keyword) && !(g_utf8_strlen (g_strstrip (keyword), -1) > 0))
230 		gtk_list_store_remove (ui->store, iter);
231 
232 	/* Check if we have a valid row to select. If not, then select
233 	 * the previous row */
234 	if (gtk_list_store_iter_is_valid (GTK_LIST_STORE (model), iter)) {
235 		gtk_tree_selection_select_iter (selection, iter);
236 	} else {
237 		if (path && valid) {
238 			gtk_tree_model_get_iter (model, iter, path);
239 			gtk_tree_selection_select_iter (selection, iter);
240 		}
241 	}
242 
243 	gtk_widget_grab_focus (ui->treeview);
244 	g_free (keyword);
245 }
246 
247 static gboolean
clue_foreach_check_isempty(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,UIData * ui)248 clue_foreach_check_isempty (GtkTreeModel *model,
249                             GtkTreePath *path,
250                             GtkTreeIter *iter,
251                             UIData *ui)
252 {
253 	gboolean valid;
254 
255 	valid = gtk_tree_model_get_iter_first (model, iter);
256 	while (valid && gtk_list_store_iter_is_valid (ui->store, iter)) {
257 		gchar *keyword = NULL;
258 		gtk_tree_model_get (model, iter, CLUE_KEYWORD_COLUMN, &keyword, -1);
259 		/* Check if the keyword is not empty and then emit the row-changed
260 		signal (if we delete the row, then the iter gets corrupted) */
261 		if ((keyword) && !(g_utf8_strlen (g_strstrip (keyword), -1) > 0))
262 			gtk_tree_model_row_changed (model, path, iter);
263 
264 		g_free (keyword);
265 		valid = gtk_tree_model_iter_next (model, iter);
266 	}
267 
268 	return FALSE;
269 }
270 
271 static void
key_cell_edited_callback(GtkCellRendererText * cell,gchar * path_string,gchar * new_text,UIData * ui)272 key_cell_edited_callback (GtkCellRendererText *cell,
273                           gchar *path_string,
274                           gchar *new_text,
275                           UIData *ui)
276 {
277 	GtkTreeModel *model;
278 	GtkTreeIter iter;
279 	gchar *value;
280 
281 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (ui->treeview));
282 
283 	gtk_tree_model_get_iter_from_string (model, &iter, path_string);
284 
285 	gtk_tree_model_get (model, &iter, CLUE_VALUE_COLUMN, &value, -1);
286 	gtk_list_store_set (
287 		GTK_LIST_STORE (model), &iter,
288 		CLUE_KEYWORD_COLUMN, new_text, CLUE_VALUE_COLUMN, value, -1);
289 	g_free (value);
290 
291 	commit_changes (ui);
292 }
293 
294 static void
value_cell_edited_callback(GtkCellRendererText * cell,gchar * path_string,gchar * new_text,UIData * ui)295 value_cell_edited_callback (GtkCellRendererText *cell,
296                             gchar *path_string,
297                             gchar *new_text,
298                             UIData *ui)
299 {
300 	GtkTreeModel *model;
301 	GtkTreeIter iter;
302 	gchar *keyword;
303 
304 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (ui->treeview));
305 
306 	gtk_tree_model_get_iter_from_string (model, &iter, path_string);
307 
308 	gtk_tree_model_get (model, &iter, CLUE_KEYWORD_COLUMN, &keyword, -1);
309 
310 	gtk_list_store_set (
311 		GTK_LIST_STORE (model), &iter,
312 		CLUE_KEYWORD_COLUMN, keyword, CLUE_VALUE_COLUMN, new_text, -1);
313 	g_free (keyword);
314 
315 	commit_changes (ui);
316 }
317 
318 static void
clue_add_clicked(GtkButton * button,UIData * ui)319 clue_add_clicked (GtkButton *button,
320                   UIData *ui)
321 {
322 	GtkTreeModel *model;
323 	GtkTreeIter iter;
324 	gchar *new_clue = NULL;
325 	GtkTreeViewColumn *focus_col;
326 	GtkTreePath *path;
327 
328 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (ui->treeview));
329 	gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc) clue_foreach_check_isempty, ui);
330 
331 	/* Disconnect from signal so that we can create an empty row */
332 	g_signal_handlers_disconnect_matched (
333 		model, G_SIGNAL_MATCH_FUNC,
334 		0, 0, NULL, clue_check_isempty, ui);
335 
336 	/* TODO : Trim and check for blank strings */
337 	new_clue = g_strdup ("");
338 	gtk_list_store_append (GTK_LIST_STORE (model), &iter);
339 	gtk_list_store_set (
340 		GTK_LIST_STORE (model), &iter,
341 		CLUE_KEYWORD_COLUMN, new_clue, CLUE_VALUE_COLUMN, new_clue, -1);
342 
343 	focus_col = gtk_tree_view_get_column (GTK_TREE_VIEW (ui->treeview), CLUE_KEYWORD_COLUMN);
344 	path = gtk_tree_model_get_path (model, &iter);
345 
346 	if (path) {
347 		gtk_tree_view_set_cursor (GTK_TREE_VIEW (ui->treeview), path, focus_col, TRUE);
348 		gtk_tree_view_row_activated (GTK_TREE_VIEW (ui->treeview), path, focus_col);
349 		gtk_tree_path_free (path);
350 	}
351 
352 	/* We have done our job, connect back to the signal */
353 	g_signal_connect (
354 		model, "row-changed",
355 		G_CALLBACK (clue_check_isempty), ui);
356 }
357 
358 static void
clue_remove_clicked(GtkButton * button,UIData * ui)359 clue_remove_clicked (GtkButton *button,
360                      UIData *ui)
361 {
362 	GtkTreeSelection *selection;
363 	GtkTreeModel *model;
364 	GtkTreeIter iter;
365 	GtkTreePath *path;
366 	gboolean valid;
367 	gint len;
368 
369 	valid = FALSE;
370 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ui->treeview));
371 	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
372 		return;
373 
374 	/* Get the path and move to the previous node :) */
375 	path = gtk_tree_model_get_path (model, &iter);
376 	if (path)
377 		valid = gtk_tree_path_prev (path);
378 
379 	gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
380 
381 	len = gtk_tree_model_iter_n_children (model, NULL);
382 	if (len > 0) {
383 		if (gtk_list_store_iter_is_valid (GTK_LIST_STORE (model), &iter)) {
384 			gtk_tree_selection_select_iter (selection, &iter);
385 		} else {
386 			if (path && valid) {
387 				gtk_tree_model_get_iter (model, &iter, path);
388 				gtk_tree_selection_select_iter (selection, &iter);
389 			}
390 		}
391 	} else {
392 		gtk_widget_set_sensitive (ui->clue_edit, FALSE);
393 		gtk_widget_set_sensitive (ui->clue_remove, FALSE);
394 	}
395 
396 	gtk_widget_grab_focus (ui->treeview);
397 	gtk_tree_path_free (path);
398 
399 	commit_changes (ui);
400 }
401 
402 static void
clue_edit_clicked(GtkButton * button,UIData * ui)403 clue_edit_clicked (GtkButton *button,
404                    UIData *ui)
405 {
406 	GtkTreeSelection *selection;
407 	GtkTreeModel *model;
408 	GtkTreePath *path;
409 	GtkTreeIter iter;
410 	GtkTreeViewColumn *focus_col;
411 
412 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ui->treeview));
413 	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
414 		return;
415 
416 	focus_col = gtk_tree_view_get_column (GTK_TREE_VIEW (ui->treeview), CLUE_KEYWORD_COLUMN);
417 	path = gtk_tree_model_get_path (model, &iter);
418 
419 	if (path) {
420 		gtk_tree_view_set_cursor (GTK_TREE_VIEW (ui->treeview), path, focus_col, TRUE);
421 		gtk_tree_path_free (path);
422 	}
423 }
424 
425 GtkWidget *
e_plugin_lib_get_configure_widget(EPlugin * epl)426 e_plugin_lib_get_configure_widget (EPlugin *epl)
427 {
428 	GtkCellRenderer *renderer_key, *renderer_value;
429 	GtkTreeSelection *selection;
430 	GtkTreeIter iter;
431 	GtkWidget *hbox;
432 	gchar **clue_list;
433 	gint i;
434 	GtkTreeModel *model;
435 	GtkWidget *templates_configuration_box;
436 	GtkWidget *clue_container;
437 	GtkWidget *scrolledwindow1;
438 	GtkWidget *clue_treeview;
439 	GtkWidget *vbuttonbox2;
440 	GtkWidget *clue_add;
441 	GtkWidget *clue_edit;
442 	GtkWidget *clue_remove;
443 
444 	UIData *ui = g_new0 (UIData, 1);
445 
446 	templates_configuration_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
447 	gtk_widget_show (templates_configuration_box);
448 	gtk_widget_set_size_request (templates_configuration_box, 385, 189);
449 
450 	clue_container = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
451 	gtk_widget_show (clue_container);
452 	gtk_box_pack_start (GTK_BOX (templates_configuration_box), clue_container, TRUE, TRUE, 0);
453 
454 	scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL);
455 	gtk_widget_show (scrolledwindow1);
456 	gtk_box_pack_start (GTK_BOX (clue_container), scrolledwindow1, TRUE, TRUE, 0);
457 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow1), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
458 
459 	clue_treeview = gtk_tree_view_new ();
460 	gtk_widget_show (clue_treeview);
461 	gtk_container_add (GTK_CONTAINER (scrolledwindow1), clue_treeview);
462 	gtk_container_set_border_width (GTK_CONTAINER (clue_treeview), 1);
463 
464 	vbuttonbox2 = gtk_button_box_new (GTK_ORIENTATION_VERTICAL);
465 	gtk_widget_show (vbuttonbox2);
466 	gtk_box_pack_start (GTK_BOX (clue_container), vbuttonbox2, FALSE, TRUE, 0);
467 	gtk_button_box_set_layout (GTK_BUTTON_BOX (vbuttonbox2), GTK_BUTTONBOX_START);
468 	gtk_box_set_spacing (GTK_BOX (vbuttonbox2), 6);
469 
470 	clue_add = e_dialog_button_new_with_icon ("list-add", _("_Add"));
471 	gtk_widget_show (clue_add);
472 	gtk_container_add (GTK_CONTAINER (vbuttonbox2), clue_add);
473 	gtk_widget_set_can_default (clue_add, TRUE);
474 
475 	clue_edit = gtk_button_new_with_mnemonic (_("_Edit"));
476 	gtk_widget_show (clue_edit);
477 	gtk_container_add (GTK_CONTAINER (vbuttonbox2), clue_edit);
478 	gtk_widget_set_can_default (clue_edit, TRUE);
479 
480 	clue_remove = e_dialog_button_new_with_icon ("list-remove", _("_Remove"));
481 	gtk_widget_show (clue_remove);
482 	gtk_container_add (GTK_CONTAINER (vbuttonbox2), clue_remove);
483 	gtk_widget_set_can_default (clue_remove, TRUE);
484 
485 	ui->settings = e_util_ref_settings ("org.gnome.evolution.plugin.templates");
486 
487 	ui->treeview = clue_treeview;
488 
489 	ui->store = gtk_list_store_new (CLUE_N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
490 
491 	gtk_tree_view_set_model (GTK_TREE_VIEW (ui->treeview), GTK_TREE_MODEL (ui->store));
492 
493 	renderer_key = gtk_cell_renderer_text_new ();
494 	gtk_tree_view_insert_column_with_attributes (
495 		GTK_TREE_VIEW (ui->treeview), -1, _("Keywords"),
496 		renderer_key, "text", CLUE_KEYWORD_COLUMN, NULL);
497 	g_object_set (renderer_key, "editable", TRUE, NULL);
498 	g_signal_connect (
499 		renderer_key, "edited",
500 		(GCallback) key_cell_edited_callback, ui);
501 
502 	renderer_value = gtk_cell_renderer_text_new ();
503 	gtk_tree_view_insert_column_with_attributes (
504 		GTK_TREE_VIEW (ui->treeview), -1, _("Values"),
505 		renderer_value, "text", CLUE_VALUE_COLUMN, NULL);
506 	g_object_set (renderer_value, "editable", TRUE, NULL);
507 	g_signal_connect (
508 		renderer_value, "edited",
509 		(GCallback) value_cell_edited_callback, ui);
510 
511 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ui->treeview));
512 	gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
513 	g_signal_connect (
514 		selection, "changed",
515 		G_CALLBACK (selection_changed), ui);
516 	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (ui->treeview), TRUE);
517 
518 	ui->clue_add = clue_add;
519 	g_signal_connect (
520 		ui->clue_add, "clicked",
521 		G_CALLBACK (clue_add_clicked), ui);
522 
523 	ui->clue_remove = clue_remove;
524 	g_signal_connect (
525 		ui->clue_remove, "clicked",
526 		G_CALLBACK (clue_remove_clicked), ui);
527 	gtk_widget_set_sensitive (ui->clue_remove, FALSE);
528 
529 	ui->clue_edit = clue_edit;
530 	g_signal_connect (
531 		ui->clue_edit, "clicked",
532 		G_CALLBACK (clue_edit_clicked), ui);
533 	gtk_widget_set_sensitive (ui->clue_edit, FALSE);
534 
535 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (ui->treeview));
536 	g_signal_connect (
537 		model, "row-changed",
538 		G_CALLBACK (clue_check_isempty), ui);
539 
540 	/* Populate tree view with values from GSettings */
541 	clue_list = g_settings_get_strv (ui->settings, CONF_KEY_TEMPLATE_PLACEHOLDERS);
542 
543 	for (i = 0; clue_list[i] != NULL; i++) {
544 		gchar **temp = g_strsplit (clue_list[i], "=", 2);
545 		gtk_list_store_append (ui->store, &iter);
546 		gtk_list_store_set (ui->store, &iter, CLUE_KEYWORD_COLUMN, temp[0], CLUE_VALUE_COLUMN, temp[1], -1);
547 		g_strfreev (temp);
548 	}
549 
550 	g_strfreev (clue_list);
551 
552 	/* Add the list here */
553 
554 	hbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
555 
556 	gtk_box_pack_start (GTK_BOX (hbox), templates_configuration_box, TRUE, TRUE, 0);
557 
558 	/* to let free data properly on destroy of configuration widget */
559 	g_object_set_data_full (G_OBJECT (hbox), "myui-data", ui, destroy_ui_data);
560 
561 	return hbox;
562 }
563 
564 static void
create_new_message_composer_created_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)565 create_new_message_composer_created_cb (GObject *source_object,
566 					GAsyncResult *result,
567 					gpointer user_data)
568 {
569 	AsyncContext *context = user_data;
570 	EAlertSink *alert_sink;
571 	EMsgComposer *composer;
572 	GError *error = NULL;
573 
574 	g_return_if_fail (context != NULL);
575 
576 	alert_sink = e_activity_get_alert_sink (context->activity);
577 
578 	composer = e_msg_composer_new_finish (result, &error);
579 
580 	if (e_activity_handle_cancellation (context->activity, error)) {
581 		async_context_free (context);
582 		g_error_free (error);
583 		return;
584 
585 	} else if (error != NULL) {
586 		e_alert_submit (
587 			alert_sink, "mail:no-retrieve-message",
588 			error->message, NULL);
589 		async_context_free (context);
590 		g_error_free (error);
591 		return;
592 	}
593 
594 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
595 
596 	/* Create the composer */
597 	em_utils_edit_message (composer, context->template_folder, context->new_message, context->source_message_uid, TRUE);
598 
599 	em_composer_utils_update_security (composer, context->validity_pgp_sum, context->validity_smime_sum);
600 
601 	if (context->source_folder_uri && context->source_message_uid)
602 		e_msg_composer_set_source_headers (
603 			composer, context->source_folder_uri,
604 			context->source_message_uid, CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_SEEN);
605 
606 	async_context_free (context);
607 }
608 
609 static void
templates_template_applied_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)610 templates_template_applied_cb (GObject *source_object,
611 			       GAsyncResult *result,
612 			       gpointer user_data)
613 {
614 	AsyncContext *context = user_data;
615 	EAlertSink *alert_sink;
616 	EMailBackend *backend;
617 	EShell *shell;
618 	GError *error = NULL;
619 
620 	g_return_if_fail (context != NULL);
621 
622 	alert_sink = e_activity_get_alert_sink (context->activity);
623 
624 	context->new_message = e_mail_templates_apply_finish (source_object, result, &error);
625 
626 	if (e_activity_handle_cancellation (context->activity, error)) {
627 		g_warn_if_fail (context->new_message == NULL);
628 		async_context_free (context);
629 		g_error_free (error);
630 		return;
631 
632 	} else if (error != NULL) {
633 		g_warn_if_fail (context->new_message == NULL);
634 		e_alert_submit (
635 			alert_sink, "mail:no-retrieve-message",
636 			error->message, NULL);
637 		async_context_free (context);
638 		g_error_free (error);
639 		return;
640 	}
641 
642 	g_warn_if_fail (context->new_message != NULL);
643 
644 	backend = e_mail_reader_get_backend (context->reader);
645 	shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));
646 
647 	e_msg_composer_new (shell, create_new_message_composer_created_cb, context);
648 }
649 
650 static void
template_got_message_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)651 template_got_message_cb (GObject *source_object,
652 			 GAsyncResult *result,
653 			 gpointer user_data)
654 {
655 	AsyncContext *context = user_data;
656 	EAlertSink *alert_sink;
657 	CamelFolder *folder = NULL;
658 	CamelMimeMessage *message;
659 	GError *error = NULL;
660 
661 	alert_sink = e_activity_get_alert_sink (context->activity);
662 
663 	message = e_mail_reader_utils_get_selection_or_message_finish (E_MAIL_READER (source_object), result,
664 			NULL, &folder, NULL, NULL, &context->validity_pgp_sum, &context->validity_smime_sum, &error);
665 
666 	if (e_activity_handle_cancellation (context->activity, error)) {
667 		g_warn_if_fail (message == NULL);
668 		async_context_free (context);
669 		g_error_free (error);
670 		return;
671 
672 	} else if (error != NULL) {
673 		g_warn_if_fail (message == NULL);
674 		e_alert_submit (
675 			alert_sink, "mail:no-retrieve-message",
676 			error->message, NULL);
677 		async_context_free (context);
678 		g_error_free (error);
679 		return;
680 	}
681 
682 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
683 
684 	context->source_message = message;
685 
686 	e_mail_templates_apply (context->source_message, folder, context->orig_source_message_uid,
687 		context->template_folder, context->template_message_uid,
688 		e_activity_get_cancellable (context->activity), templates_template_applied_cb, context);
689 }
690 
691 static void
action_reply_with_template_cb(EMailTemplatesStore * templates_store,CamelFolder * template_folder,const gchar * template_message_uid,gpointer user_data)692 action_reply_with_template_cb (EMailTemplatesStore *templates_store,
693 			       CamelFolder *template_folder,
694 			       const gchar *template_message_uid,
695 			       gpointer user_data)
696 {
697 	EActivity *activity;
698 	AsyncContext *context;
699 	GCancellable *cancellable;
700 	CamelFolder *folder;
701 	EShellView *shell_view = user_data;
702 	EShellContent *shell_content;
703 	EMailReader *reader;
704 	GPtrArray *uids;
705 	const gchar *message_uid;
706 
707 	shell_content = e_shell_view_get_shell_content (shell_view);
708 	reader = E_MAIL_READER (shell_content);
709 
710 	uids = e_mail_reader_get_selected_uids (reader);
711 	g_return_if_fail (uids != NULL && uids->len == 1);
712 	message_uid = g_ptr_array_index (uids, 0);
713 
714 	activity = e_mail_reader_new_activity (reader);
715 	cancellable = e_activity_get_cancellable (activity);
716 
717 	context = g_slice_new0 (AsyncContext);
718 	context->activity = activity;
719 	context->reader = g_object_ref (reader);
720 	context->orig_source_message_uid = g_strdup (message_uid);
721 	context->template_folder = g_object_ref (template_folder);
722 	context->template_message_uid = g_strdup (template_message_uid);
723 
724 	folder = e_mail_reader_ref_folder (reader);
725 
726 	em_utils_get_real_folder_uri_and_message_uid (
727 		folder, message_uid,
728 		&context->source_folder_uri,
729 		&context->source_message_uid);
730 
731 	if (context->source_message_uid == NULL)
732 		context->source_message_uid = g_strdup (message_uid);
733 
734 	e_mail_reader_utils_get_selection_or_message (reader, NULL, cancellable,
735 		template_got_message_cb, context);
736 
737 	g_clear_object (&folder);
738 	g_ptr_array_unref (uids);
739 }
740 
741 static gchar *
get_account_templates_folder_uri(EMsgComposer * composer)742 get_account_templates_folder_uri (EMsgComposer *composer)
743 {
744 	EComposerHeaderTable *table;
745 	ESource *source;
746 	gchar *identity_uid;
747 	gchar *templates_folder_uri = NULL;
748 
749 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
750 
751 	table = e_msg_composer_get_header_table (composer);
752 	identity_uid = e_composer_header_table_dup_identity_uid (table, NULL, NULL);
753 	source = e_composer_header_table_ref_source (table, identity_uid);
754 
755 	/* Get the selected identity's preferred Templates folder. */
756 	if (source != NULL) {
757 		ESourceMailComposition *extension;
758 		const gchar *extension_name;
759 
760 		extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
761 		extension = e_source_get_extension (source, extension_name);
762 		templates_folder_uri = e_source_mail_composition_dup_templates_folder (extension);
763 
764 		g_object_unref (source);
765 	}
766 
767 	g_free (identity_uid);
768 
769 	return templates_folder_uri;
770 }
771 
772 typedef struct _SaveTemplateAsyncData {
773 	EMsgComposer *composer;
774 	EMailSession *session;
775 	CamelMimeMessage *message;
776 	CamelMessageInfo *info;
777 	gchar *templates_folder_uri;
778 } SaveTemplateAsyncData;
779 
780 static void
save_template_async_data_free(gpointer ptr)781 save_template_async_data_free (gpointer ptr)
782 {
783 	SaveTemplateAsyncData *sta = ptr;
784 
785 	if (sta) {
786 		g_clear_object (&sta->composer);
787 		g_clear_object (&sta->session);
788 		g_clear_object (&sta->message);
789 		g_clear_object (&sta->info);
790 		g_free (sta->templates_folder_uri);
791 		g_slice_free (SaveTemplateAsyncData, sta);
792 	}
793 }
794 
795 static void
save_template_thread(EAlertSinkThreadJobData * job_data,gpointer user_data,GCancellable * cancellable,GError ** error)796 save_template_thread (EAlertSinkThreadJobData *job_data,
797 		      gpointer user_data,
798 		      GCancellable *cancellable,
799 		      GError **error)
800 {
801 	SaveTemplateAsyncData *sta = user_data;
802 	CamelFolder *templates_folder = NULL;
803 
804 	if (sta->templates_folder_uri && *sta->templates_folder_uri) {
805 		templates_folder = e_mail_session_uri_to_folder_sync (sta->session,
806 			sta->templates_folder_uri, 0, cancellable, error);
807 		if (!templates_folder)
808 			return;
809 	}
810 
811 	if (!templates_folder) {
812 		e_mail_session_append_to_local_folder_sync (
813 			sta->session, E_MAIL_LOCAL_FOLDER_TEMPLATES,
814 			sta->message, sta->info,
815 			NULL, cancellable, error);
816 	} else {
817 		e_mail_folder_append_message_sync (
818 			templates_folder, sta->message, sta->info,
819 			NULL, cancellable, error);
820 	}
821 
822 	g_clear_object (&templates_folder);
823 }
824 
825 static void
got_message_draft_cb(EMsgComposer * composer,GAsyncResult * result)826 got_message_draft_cb (EMsgComposer *composer,
827                       GAsyncResult *result)
828 {
829 	EShell *shell;
830 	EShellBackend *shell_backend;
831 	EMailBackend *backend;
832 	EMailSession *session;
833 	EHTMLEditor *html_editor;
834 	CamelMimeMessage *message;
835 	CamelMessageInfo *info;
836 	SaveTemplateAsyncData *sta;
837 	EActivity *activity;
838 	GError *error = NULL;
839 
840 	message = e_msg_composer_get_message_draft_finish (
841 		composer, result, &error);
842 
843 	/* Ignore cancellations. */
844 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
845 		g_warn_if_fail (message == NULL);
846 		g_error_free (error);
847 		return;
848 	}
849 
850 	if (error != NULL) {
851 		g_warn_if_fail (message == NULL);
852 		e_alert_run_dialog_for_args (
853 			GTK_WINDOW (composer),
854 			"mail-composer:no-build-message",
855 			error->message, NULL);
856 		g_error_free (error);
857 		return;
858 	}
859 
860 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
861 
862 	shell = e_shell_get_default ();
863 	shell_backend = e_shell_get_backend_by_name (shell, "mail");
864 
865 	backend = E_MAIL_BACKEND (shell_backend);
866 	session = e_mail_backend_get_session (backend);
867 
868 	info = camel_message_info_new (NULL);
869 
870 	/* The last argument is a bit mask which tells the function
871 	 * which flags to modify.  In this case, ~0 means all flags.
872 	 * So it clears all the flags and then sets SEEN and DRAFT. */
873 	camel_message_info_set_flags (
874 		info, CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DRAFT |
875 		(camel_mime_message_has_attachment (message) ? CAMEL_MESSAGE_ATTACHMENTS : 0), ~0);
876 
877 	sta = g_slice_new0 (SaveTemplateAsyncData);
878 	sta->composer = g_object_ref (composer);
879 	sta->session = g_object_ref (session);
880 	sta->message = message;
881 	sta->info = info;
882 	sta->templates_folder_uri = get_account_templates_folder_uri (composer);
883 
884 	html_editor = e_msg_composer_get_editor (composer);
885 
886 	activity = e_alert_sink_submit_thread_job (E_ALERT_SINK (html_editor),
887 			_("Saving message template"),
888 			"mail-composer:failed-save-template",
889 			NULL, save_template_thread, sta, save_template_async_data_free);
890 
891 	g_clear_object (&activity);
892 }
893 
894 static void
action_template_cb(GtkAction * action,EMsgComposer * composer)895 action_template_cb (GtkAction *action,
896                     EMsgComposer *composer)
897 {
898 	/* XXX Pass a GCancellable */
899 	e_msg_composer_get_message_draft (
900 		composer, G_PRIORITY_DEFAULT, NULL,
901 		(GAsyncReadyCallback) got_message_draft_cb, NULL);
902 }
903 
904 static GtkActionEntry composer_entries[] = {
905 
906 	{ "template",
907 	  "document-save",
908 	  N_("Save as _Template"),
909 	  "<Shift><Control>t",
910 	  N_("Save as Template"),
911 	  G_CALLBACK (action_template_cb) }
912 };
913 
914 static void
templates_update_actions_cb(EShellView * shell_view,GtkActionGroup * action_group)915 templates_update_actions_cb (EShellView *shell_view,
916 			     GtkActionGroup *action_group)
917 {
918 	TemplatesData *td;
919 
920 	if (!plugin_enabled)
921 		return;
922 
923 	td = g_object_get_data (G_OBJECT (shell_view), TEMPLATES_DATA_KEY);
924 	if (td) {
925 		if (td->changed) {
926 			EShellWindow *shell_window;
927 			GtkUIManager *ui_manager;
928 
929 			td->changed = FALSE;
930 
931 			shell_window = e_shell_view_get_shell_window (shell_view);
932 			ui_manager = e_shell_window_get_ui_manager (shell_window);
933 
934 			e_mail_templates_store_build_menu (td->templates_store, shell_view, ui_manager, action_group,
935 				"/main-menu/custom-menus/mail-message-menu/mail-reply-template",
936 				"/mail-message-popup/mail-message-popup-common-actions/mail-popup-reply-template",
937 				td->merge_id,
938 				action_reply_with_template_cb, shell_view);
939 		}
940 	}
941 
942 	gtk_action_group_set_sensitive (action_group, TRUE);
943 	gtk_action_group_set_visible (action_group, TRUE);
944 }
945 
946 gboolean
init_composer_actions(GtkUIManager * ui_manager,EMsgComposer * composer)947 init_composer_actions (GtkUIManager *ui_manager,
948                        EMsgComposer *composer)
949 {
950 	EHTMLEditor *editor;
951 
952 	editor = e_msg_composer_get_editor (composer);
953 
954 	/* Add actions to the "composer" action group. */
955 	gtk_action_group_add_actions (
956 		e_html_editor_get_action_group (editor, "composer"),
957 		composer_entries, G_N_ELEMENTS (composer_entries), composer);
958 
959 	return TRUE;
960 }
961 
962 static void
templates_store_changed_cb(EMailTemplatesStore * templates_store,gpointer user_data)963 templates_store_changed_cb (EMailTemplatesStore *templates_store,
964 			    gpointer user_data)
965 {
966 	TemplatesData *td = user_data;
967 
968 	g_return_if_fail (td != NULL);
969 
970 	td->changed = TRUE;
971 }
972 
973 static void
mail_shell_view_created_cb(EShellWindow * shell_window,EShellView * shell_view)974 mail_shell_view_created_cb (EShellWindow *shell_window,
975                             EShellView *shell_view)
976 {
977 	EMailBackend *backend;
978 	EMailSession *session;
979 	EShellBackend *shell_backend;
980 	GtkUIManager *ui_manager;
981 	GtkActionGroup *action_group;
982 	TemplatesData *td;
983 
984 	ui_manager = e_shell_window_get_ui_manager (shell_window);
985 	e_shell_window_add_action_group_full (shell_window, "templates", "mail");
986 	action_group = e_lookup_action_group (ui_manager, "templates");
987 
988 	shell_backend = e_shell_view_get_shell_backend (shell_view);
989 
990 	backend = E_MAIL_BACKEND (shell_backend);
991 	session = e_mail_backend_get_session (backend);
992 
993 	td = g_new0 (TemplatesData, 1);
994 	td->templates_store = e_mail_templates_store_ref_default (e_mail_ui_session_get_account_store (E_MAIL_UI_SESSION (session)));
995 	td->changed_handler_id = g_signal_connect (td->templates_store, "changed", G_CALLBACK (templates_store_changed_cb), td);
996 	td->merge_id = gtk_ui_manager_new_merge_id (ui_manager);
997 	td->changed = TRUE;
998 
999 	g_object_set_data_full (G_OBJECT (shell_view), TEMPLATES_DATA_KEY, td, templates_data_free);
1000 
1001 	g_signal_connect (
1002 		shell_view, "update-actions",
1003 		G_CALLBACK (templates_update_actions_cb), action_group);
1004 }
1005 
1006 gboolean
init_shell_actions(GtkUIManager * ui_manager,EShellWindow * shell_window)1007 init_shell_actions (GtkUIManager *ui_manager,
1008                     EShellWindow *shell_window)
1009 {
1010 	EShellView *shell_view;
1011 
1012 	/* Be careful not to instantiate the mail view ourselves. */
1013 	shell_view = e_shell_window_peek_shell_view (shell_window, "mail");
1014 	if (shell_view != NULL)
1015 		mail_shell_view_created_cb (shell_window, shell_view);
1016 	else
1017 		g_signal_connect (
1018 			shell_window, "shell-view-created::mail",
1019 			G_CALLBACK (mail_shell_view_created_cb), NULL);
1020 
1021 	return TRUE;
1022 }
1023 
1024 gint
e_plugin_lib_enable(EPlugin * plugin,gboolean enabled)1025 e_plugin_lib_enable (EPlugin *plugin,
1026                      gboolean enabled)
1027 {
1028 	plugin_enabled = enabled;
1029 
1030 	return 0;
1031 }
1032