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