1 /*
2 *
3 * This program is free software; you can redistribute it and/or modify it
4 * under the terms of the GNU Lesser General Public License as published by
5 * the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful, but
8 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
10 * for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public License
13 * along with this program; if not, see <http://www.gnu.org/licenses/>.
14 *
15 *
16 * Authors:
17 * Michael Zucchi <notzed@novell.com>
18 * Rodrigo Moya <rodrigo@novell.com>
19 *
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
21 *
22 */
23
24 /* Convert a mail message into a task */
25
26 #include "evolution-config.h"
27
28 #include <stdio.h>
29 #include <string.h>
30 #include <glib/gi18n-lib.h>
31
32 #include <libecal/libecal.h>
33
34 #include <shell/e-shell-view.h>
35 #include <shell/e-shell-window-actions.h>
36
37 #include <mail/e-mail-browser.h>
38 #include <mail/em-utils.h>
39 #include <mail/message-list.h>
40
41 #include <calendar/gui/e-comp-editor.h>
42 #include <calendar/gui/itip-utils.h>
43
44 #include "libemail-engine/libemail-engine.h"
45
46 #define E_SHELL_WINDOW_ACTION_CONVERT_TO_APPOINTMENT(window) \
47 E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-appointment")
48 #define E_SHELL_WINDOW_ACTION_CONVERT_TO_MEETING(window) \
49 E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-meeting")
50 #define E_SHELL_WINDOW_ACTION_CONVERT_TO_MEMO(window) \
51 E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-memo")
52 #define E_SHELL_WINDOW_ACTION_CONVERT_TO_TASK(window) \
53 E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-task")
54
55 gboolean mail_browser_init (GtkUIManager *ui_manager,
56 EMailBrowser *browser);
57 gboolean mail_shell_view_init (GtkUIManager *ui_manager,
58 EShellView *shell_view);
59
60 static ECompEditor *
get_component_editor(EShell * shell,ECalClient * client,ECalComponent * comp,gboolean is_new,GError ** error)61 get_component_editor (EShell *shell,
62 ECalClient *client,
63 ECalComponent *comp,
64 gboolean is_new,
65 GError **error)
66 {
67 ECompEditorFlags flags = 0;
68 ECompEditor *comp_editor = NULL;
69 ESourceRegistry *registry;
70
71 g_return_val_if_fail (E_IS_SHELL (shell), NULL);
72 g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL);
73 g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL);
74
75 registry = e_shell_get_registry (shell);
76
77 if (is_new) {
78 flags |= E_COMP_EDITOR_FLAG_IS_NEW;
79 } else {
80 comp_editor = e_comp_editor_find_existing_for (
81 e_client_get_source (E_CLIENT (client)),
82 e_cal_component_get_icalcomponent (comp));
83 }
84
85 if (!comp_editor) {
86 if (itip_organizer_is_user (registry, comp, client))
87 flags |= E_COMP_EDITOR_FLAG_ORGANIZER_IS_USER;
88 if (e_cal_component_has_attendees (comp))
89 flags |= E_COMP_EDITOR_FLAG_WITH_ATTENDEES;
90
91 comp_editor = e_comp_editor_open_for_component (NULL,
92 shell, e_client_get_source (E_CLIENT (client)),
93 e_cal_component_get_icalcomponent (comp), flags);
94
95 if (comp_editor) {
96 /* request save for new events */
97 e_comp_editor_set_changed (comp_editor, is_new);
98 }
99 }
100
101 return comp_editor;
102 }
103
104 static void
set_attendees(ECalComponent * comp,CamelMimeMessage * message,const gchar * organizer)105 set_attendees (ECalComponent *comp,
106 CamelMimeMessage *message,
107 const gchar *organizer)
108 {
109 GSList *attendees = NULL;
110 ECalComponentAttendee *ca;
111 CamelInternetAddress *from, *to, *cc, *bcc, *arr[4];
112 gint len, i, j;
113
114 from = camel_mime_message_get_reply_to (message);
115 if (!from)
116 from = camel_mime_message_get_from (message);
117
118 to = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO);
119 cc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC);
120 bcc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC);
121
122 arr[0] = from; arr[1] = to; arr[2] = cc; arr[3] = bcc;
123
124 for (j = 0; j < 4; j++) {
125 if (!arr[j])
126 continue;
127
128 len = camel_address_length (CAMEL_ADDRESS (arr[j]));
129 for (i = 0; i < len; i++) {
130 const gchar *name, *addr;
131
132 if (camel_internet_address_get (arr[j], i, &name, &addr)) {
133 gchar *temp;
134
135 temp = g_strconcat ("mailto:", addr, NULL);
136 if (organizer && g_ascii_strcasecmp (temp, organizer) == 0) {
137 /* do not add organizer twice */
138 g_free (temp);
139 continue;
140 }
141
142 ca = e_cal_component_attendee_new ();
143
144 e_cal_component_attendee_set_value (ca, temp);
145 e_cal_component_attendee_set_cn (ca, name);
146 e_cal_component_attendee_set_cutype (ca, I_CAL_CUTYPE_INDIVIDUAL);
147 e_cal_component_attendee_set_partstat (ca, I_CAL_PARTSTAT_NEEDSACTION);
148 if (j == 0) {
149 /* From */
150 e_cal_component_attendee_set_role (ca, I_CAL_ROLE_CHAIR);
151 } else if (j == 2) {
152 /* BCC */
153 e_cal_component_attendee_set_role (ca, I_CAL_ROLE_OPTPARTICIPANT);
154 } else {
155 /* all other */
156 e_cal_component_attendee_set_role (ca, I_CAL_ROLE_REQPARTICIPANT);
157 }
158
159 attendees = g_slist_prepend (attendees, ca);
160
161 g_free (temp);
162 }
163 }
164 }
165
166 attendees = g_slist_reverse (attendees);
167
168 e_cal_component_set_attendees (comp, attendees);
169
170 g_slist_free_full (attendees, e_cal_component_attendee_free);
171 }
172
173 static const gchar *
prepend_from(CamelMimeMessage * message,gchar ** text)174 prepend_from (CamelMimeMessage *message,
175 gchar **text)
176 {
177 gchar *res, *tmp, *addr = NULL;
178 const gchar *name = NULL, *eml = NULL;
179 CamelInternetAddress *from;
180
181 g_return_val_if_fail (message != NULL, NULL);
182 g_return_val_if_fail (text != NULL, NULL);
183
184 from = camel_mime_message_get_reply_to (message);
185 if (!from)
186 from = camel_mime_message_get_from (message);
187
188 if (from && camel_internet_address_get (from, 0, &name, &eml))
189 addr = camel_internet_address_format_address (name, eml);
190
191 if (addr && !g_utf8_validate (addr, -1, NULL)) {
192 tmp = e_util_utf8_make_valid (addr);
193 g_free (addr);
194 addr = tmp;
195 }
196
197 /* To Translators: The full sentence looks like: "Created from a mail by John Doe <john.doe@myco.example>" */
198 tmp = g_strdup_printf (_("Created from a mail by %s"), addr ? addr : "");
199
200 res = g_strconcat (tmp, "\n", *text, NULL);
201
202 g_free (tmp);
203 g_free (addr);
204 g_free (*text);
205
206 *text = res;
207
208 return res;
209 }
210
211 static void
set_description(ECalComponent * comp,CamelMimeMessage * message,const gchar * default_charset,const gchar * forced_charset)212 set_description (ECalComponent *comp,
213 CamelMimeMessage *message,
214 const gchar *default_charset,
215 const gchar *forced_charset)
216 {
217 CamelDataWrapper *content;
218 CamelStream *stream;
219 CamelContentType *type;
220 CamelMimePart *mime_part = CAMEL_MIME_PART (message);
221 const gchar *charset = NULL;
222 ECalComponentText *text = NULL;
223 GByteArray *byte_array;
224 GSList *sl = NULL;
225 gchar *str, *convert_str = NULL;
226 gint count = 2;
227
228 content = camel_medium_get_content ((CamelMedium *) message);
229 if (!content)
230 return;
231
232 /*
233 * Get non-multipart content from multipart message.
234 */
235 while (CAMEL_IS_MULTIPART (content) && count > 0) {
236 mime_part = camel_multipart_get_part (CAMEL_MULTIPART (content), 0);
237 content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
238 count--;
239 }
240
241 if (!mime_part)
242 return;
243
244 type = camel_mime_part_get_content_type (mime_part);
245 if (!camel_content_type_is (type, "text", "plain"))
246 return;
247
248 byte_array = g_byte_array_new ();
249 stream = camel_stream_mem_new_with_byte_array (byte_array);
250 camel_data_wrapper_decode_to_stream_sync (content, stream, NULL, NULL);
251 str = g_strndup ((gchar *) byte_array->data, byte_array->len);
252 g_object_unref (stream);
253
254 if (forced_charset && *forced_charset) {
255 charset = forced_charset;
256 } else {
257 CamelContentType *mime_type;
258
259 mime_type = camel_data_wrapper_get_mime_type_field (content);
260
261 if (mime_type) {
262 charset = camel_content_type_param (mime_type, "charset");
263 if (charset && !*charset)
264 charset = NULL;
265 }
266 }
267
268 if (!charset && default_charset && *default_charset)
269 charset = default_charset;
270
271 /* convert to UTF-8 string */
272 if (str && charset) {
273 gsize bytes_read, bytes_written;
274
275 convert_str = g_convert (
276 str, strlen (str),
277 "UTF-8", charset,
278 &bytes_read, &bytes_written, NULL);
279 }
280
281 if (!convert_str && str)
282 convert_str = e_util_utf8_make_valid (str);
283
284 if (convert_str)
285 text = e_cal_component_text_new (prepend_from (message, &convert_str), NULL);
286 else
287 text = e_cal_component_text_new (prepend_from (message, &str), NULL);
288 sl = g_slist_append (sl, text);
289
290 e_cal_component_set_descriptions (comp, sl);
291
292 g_free (str);
293 g_free (convert_str);
294 g_slist_free_full (sl, e_cal_component_text_free);
295 }
296
297 static gchar *
set_organizer(ECalComponent * comp,CamelMimeMessage * message,CamelFolder * folder,const gchar * message_uid)298 set_organizer (ECalComponent *comp,
299 CamelMimeMessage *message,
300 CamelFolder *folder,
301 const gchar *message_uid)
302 {
303 EShell *shell;
304 ESource *source = NULL;
305 ESourceRegistry *registry;
306 ESourceMailIdentity *extension;
307 const gchar *extension_name;
308 const gchar *address, *name;
309 gchar *mailto = NULL;
310 gchar *identity_name = NULL, *identity_address = NULL;
311
312 shell = e_shell_get_default ();
313 registry = e_shell_get_registry (shell);
314
315 source = em_utils_guess_mail_identity_with_recipients (registry, message, folder,
316 message_uid, &identity_name, &identity_address);
317
318 if (!source && folder) {
319 CamelStore *store;
320
321 store = camel_folder_get_parent_store (folder);
322 source = em_utils_ref_mail_identity_for_store (registry, store);
323 }
324
325 if (source == NULL)
326 source = e_source_registry_ref_default_mail_identity (registry);
327
328 g_return_val_if_fail (source != NULL, NULL);
329
330 extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
331 extension = e_source_get_extension (source, extension_name);
332
333 name = identity_name;
334 if (!name || !*name)
335 name = e_source_mail_identity_get_name (extension);
336
337 address = identity_address;
338 if (!address || !*address) {
339 name = e_source_mail_identity_get_name (extension);
340 address = e_source_mail_identity_get_address (extension);
341 }
342
343 if (address && *address) {
344 ECalComponentOrganizer *organizer;
345
346 mailto = g_strconcat ("mailto:", address, NULL);
347
348 organizer = e_cal_component_organizer_new ();
349 e_cal_component_organizer_set_value (organizer, mailto);
350 e_cal_component_organizer_set_cn (organizer, name);
351 e_cal_component_set_organizer (comp, organizer);
352 e_cal_component_organizer_free (organizer);
353 }
354
355 g_object_unref (source);
356 g_free (identity_name);
357 g_free (identity_address);
358
359 return mailto;
360 }
361
362 struct _att_async_cb_data {
363 gchar **uris;
364 EFlag *flag;
365 };
366
367 static void
attachment_load_finished(EAttachmentStore * store,GAsyncResult * result,gpointer user_data)368 attachment_load_finished (EAttachmentStore *store,
369 GAsyncResult *result,
370 gpointer user_data)
371 {
372 struct _att_async_cb_data *data = user_data;
373
374 /* XXX Should be no need to check for error here.
375 * This is just to reset state in the EAttachment. */
376 e_attachment_store_load_finish (store, result, NULL);
377
378 e_flag_set (data->flag);
379 }
380
381 static void
attachment_save_finished(EAttachmentStore * store,GAsyncResult * result,gpointer user_data)382 attachment_save_finished (EAttachmentStore *store,
383 GAsyncResult *result,
384 gpointer user_data)
385 {
386 struct _att_async_cb_data *data = user_data;
387 gchar **uris;
388 GError *error = NULL;
389
390 uris = e_attachment_store_save_finish (store, result, &error);
391 if (error != NULL)
392 data->uris = NULL;
393 else
394 data->uris = uris;
395
396 g_clear_error (&error);
397
398 e_flag_set (data->flag);
399 }
400
401 static void
set_attachments(ECalClient * client,ECalComponent * comp,CamelMimeMessage * message)402 set_attachments (ECalClient *client,
403 ECalComponent *comp,
404 CamelMimeMessage *message)
405 {
406 /* XXX Much of this is copied from CompEditor::get_attachment_list().
407 * Perhaps it should be split off as a separate utility? */
408
409 EAttachmentStore *store;
410 CamelDataWrapper *content;
411 CamelMultipart *multipart;
412 GFile *destination;
413 GList *attachment_list = NULL;
414 GSList *uri_list = NULL;
415 const gchar *comp_uid = NULL;
416 const gchar *local_store;
417 gchar *filename_prefix, *tmp;
418 gint ii, n_parts;
419 struct _att_async_cb_data cb_data;
420
421 cb_data.flag = e_flag_new ();
422 cb_data.uris = NULL;
423
424 content = camel_medium_get_content ((CamelMedium *) message);
425 if (!content || !CAMEL_IS_MULTIPART (content))
426 return;
427
428 n_parts = camel_multipart_get_number (CAMEL_MULTIPART (content));
429 if (n_parts < 1)
430 return;
431
432 comp_uid = e_cal_component_get_uid (comp);
433 g_return_if_fail (comp_uid != NULL);
434
435 tmp = g_strdup (comp_uid);
436 e_util_make_safe_filename (tmp);
437 filename_prefix = g_strconcat (tmp, "-", NULL);
438 g_free (tmp);
439
440 local_store = e_cal_client_get_local_attachment_store (client);
441 destination = g_file_new_for_path (local_store);
442
443 /* Create EAttachments from the MIME parts and add them to the
444 * attachment store. */
445
446 multipart = CAMEL_MULTIPART (content);
447 store = E_ATTACHMENT_STORE (e_attachment_store_new ());
448
449 for (ii = 1; ii < n_parts; ii++) {
450 EAttachment *attachment;
451 CamelMimePart *mime_part;
452
453 attachment = e_attachment_new ();
454 mime_part = camel_multipart_get_part (multipart, ii);
455 e_attachment_set_mime_part (attachment, mime_part);
456
457 attachment_list = g_list_append (attachment_list, attachment);
458 }
459
460 e_flag_clear (cb_data.flag);
461
462 e_attachment_store_load_async (
463 store, attachment_list, (GAsyncReadyCallback)
464 attachment_load_finished, &cb_data);
465
466 /* Loading should be instantaneous since we already have
467 * the full content, but we need to wait for the callback.
468 */
469 e_flag_wait (cb_data.flag);
470
471 g_list_foreach (attachment_list, (GFunc) g_object_unref, NULL);
472 g_list_free (attachment_list);
473
474 cb_data.uris = NULL;
475 e_flag_clear (cb_data.flag);
476
477 e_attachment_store_save_async (
478 store, destination, filename_prefix,
479 (GAsyncReadyCallback) attachment_save_finished, &cb_data);
480
481 g_free (filename_prefix);
482
483 /* We can't return until we have results. */
484 e_flag_wait (cb_data.flag);
485
486 if (cb_data.uris == NULL) {
487 e_flag_free (cb_data.flag);
488 g_warning ("No attachment URIs retrieved.");
489 return;
490 }
491
492 /* Transfer the URI strings to the GSList. */
493 for (ii = 0; cb_data.uris[ii] != NULL; ii++) {
494 uri_list = g_slist_prepend (uri_list, i_cal_attach_new_from_url (cb_data.uris[ii]));
495 }
496
497 e_flag_free (cb_data.flag);
498 g_strfreev (cb_data.uris);
499
500 e_cal_component_set_attachments (comp, uri_list);
501
502 g_slist_free_full (uri_list, g_object_unref);
503 e_attachment_store_remove_all (store);
504 g_object_unref (destination);
505 g_object_unref (store);
506 }
507
508 static void
set_priority(ECalComponent * comp,CamelMimePart * part)509 set_priority (ECalComponent *comp,
510 CamelMimePart *part)
511 {
512 const gchar *prio;
513
514 g_return_if_fail (comp != NULL);
515 g_return_if_fail (part != NULL);
516
517 prio = camel_medium_get_header (CAMEL_MEDIUM (part), "X-Priority");
518 if (prio && atoi (prio) > 0)
519 e_cal_component_set_priority (comp, 1);
520 }
521
522 struct _report_error
523 {
524 gchar *format;
525 gchar *param;
526 };
527
528 static gboolean
do_report_error(struct _report_error * err)529 do_report_error (struct _report_error *err)
530 {
531 if (err) {
532 e_notice (NULL, GTK_MESSAGE_ERROR, err->format, err->param);
533 g_free (err->format);
534 g_free (err->param);
535 g_slice_free (struct _report_error, err);
536 }
537
538 return FALSE;
539 }
540
541 static void
report_error_idle(const gchar * format,const gchar * param)542 report_error_idle (const gchar *format,
543 const gchar *param)
544 {
545 struct _report_error *err = g_slice_new (struct _report_error);
546
547 err->format = g_strdup (format);
548 err->param = g_strdup (param);
549
550 g_usleep (250);
551 g_idle_add ((GSourceFunc) do_report_error, err);
552 }
553
554 struct _manage_comp
555 {
556 ECalClient *client;
557 ECalComponent *comp;
558 ICalComponent *stored_comp; /* the one in client already */
559 GCond cond;
560 GMutex mutex;
561 gint mails_count;
562 gint mails_done;
563 gchar *editor_title;
564 gboolean can_continue;
565 };
566
567 static void
free_manage_comp_struct(struct _manage_comp * mc)568 free_manage_comp_struct (struct _manage_comp *mc)
569 {
570 g_return_if_fail (mc != NULL);
571
572 g_object_unref (mc->comp);
573 g_object_unref (mc->client);
574 g_clear_object (&mc->stored_comp);
575 g_mutex_clear (&mc->mutex);
576 g_cond_clear (&mc->cond);
577 g_free (mc->editor_title);
578
579 g_slice_free (struct _manage_comp, mc);
580 }
581
582 static gint
do_ask(const gchar * text,gboolean is_create_edit_add)583 do_ask (const gchar *text,
584 gboolean is_create_edit_add)
585 {
586 gint res;
587 GtkWidget *dialog = gtk_message_dialog_new (
588 NULL,
589 GTK_DIALOG_MODAL,
590 GTK_MESSAGE_QUESTION,
591 is_create_edit_add ? GTK_BUTTONS_NONE : GTK_BUTTONS_YES_NO,
592 "%s", text);
593
594 if (is_create_edit_add) {
595 gtk_dialog_add_buttons (
596 GTK_DIALOG (dialog),
597 /* Translators: Dialog button to Cancel edit of an existing event/memo/task */
598 C_("mail-to-task", "_Cancel"), GTK_RESPONSE_CANCEL,
599 /* Translators: Dialog button to Edit an existing event/memo/task */
600 C_("mail-to-task", "_Edit"), GTK_RESPONSE_YES,
601 /* Translators: Dialog button to create a New event/memo/task */
602 C_("mail-to-task", "_New"), GTK_RESPONSE_NO,
603 NULL);
604 }
605
606 res = gtk_dialog_run (GTK_DIALOG (dialog));
607
608 gtk_widget_destroy (dialog);
609
610 return res;
611 }
612
613 static const gchar *
get_question_edit_old(ECalClientSourceType source_type)614 get_question_edit_old (ECalClientSourceType source_type)
615 {
616 const gchar *ask = NULL;
617
618 switch (source_type) {
619 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
620 ask = _("Selected calendar contains event “%s” already. Would you like to edit the old event?");
621 break;
622 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
623 ask = _("Selected task list contains task “%s” already. Would you like to edit the old task?");
624 break;
625 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
626 ask = _("Selected memo list contains memo “%s” already. Would you like to edit the old memo?");
627 break;
628 default:
629 g_warn_if_reached ();
630 break;
631 }
632
633 return ask;
634 }
635
636 static const gchar *
get_question_add_all_mails(ECalClientSourceType source_type,gint count)637 get_question_add_all_mails (ECalClientSourceType source_type,
638 gint count)
639 {
640 const gchar *ask = NULL;
641
642 switch (source_type) {
643 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
644 ask = ngettext (
645 /* Translators: Note there are always more than 10 mails selected */
646 "You have selected %d mails to be converted to events. Do you really want to add them all?",
647 "You have selected %d mails to be converted to events. Do you really want to add them all?",
648 count);
649 break;
650 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
651 ask = ngettext (
652 /* Translators: Note there are always more than 10 mails selected */
653 "You have selected %d mails to be converted to tasks. Do you really want to add them all?",
654 "You have selected %d mails to be converted to tasks. Do you really want to add them all?",
655 count);
656 break;
657 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
658 ask = ngettext (
659 /* Translators: Note there are always more than 10 mails selected */
660 "You have selected %d mails to be converted to memos. Do you really want to add them all?",
661 "You have selected %d mails to be converted to memos. Do you really want to add them all?",
662 count);
663 break;
664 default:
665 g_warn_if_reached ();
666 break;
667 }
668
669 return ask;
670 }
671
672 static void
comp_editor_closed(ECompEditor * comp_editor,gboolean saved,struct _manage_comp * mc)673 comp_editor_closed (ECompEditor *comp_editor,
674 gboolean saved,
675 struct _manage_comp *mc)
676 {
677 if (!mc)
678 return;
679
680 if (!saved && mc->mails_done < mc->mails_count)
681 mc->can_continue = (do_ask (_("Do you wish to continue converting remaining mails?"), FALSE) == GTK_RESPONSE_YES);
682
683 /* Signal the do_mail_to_event thread that editor was closed and editor
684 * for next event can be displayed (if any) */
685 g_cond_signal (&mc->cond);
686 }
687
688 /*
689 * This handler takes title of the editor window and
690 * inserts information about number of processed mails and
691 * number of all mails to process, so the window title
692 * will look like "Appointment (3/10) — An appoitment name"
693 */
694 static void
comp_editor_title_changed(GtkWidget * widget,GParamSpec * pspec,struct _manage_comp * mc)695 comp_editor_title_changed (GtkWidget *widget,
696 GParamSpec *pspec,
697 struct _manage_comp *mc)
698 {
699 GtkWindow *editor = GTK_WINDOW (widget);
700 const gchar *title = gtk_window_get_title (editor);
701 gchar *new_title;
702 gchar *splitter;
703 gchar *comp_name, *task_name;
704
705 if (!mc)
706 return;
707
708 /* Recursion prevence */
709 if (mc->editor_title && g_utf8_collate (mc->editor_title, title) == 0)
710 return;
711
712 splitter = strstr (title, "—");
713 if (!splitter)
714 return;
715
716 comp_name = g_strndup (title, splitter - title - 1);
717 task_name = g_strdup (splitter + strlen ("—") + 1);
718 new_title = g_strdup_printf (
719 "%s (%d/%d) — %s",
720 comp_name, mc->mails_done, mc->mails_count, task_name);
721
722 /* Remember the new title, so that when gtk_window_set_title() causes
723 * this handler to be recursively called, we can recognize that and
724 * prevent endless recursion */
725 g_free (mc->editor_title);
726 mc->editor_title = new_title;
727
728 gtk_window_set_title (editor, new_title);
729
730 g_free (comp_name);
731 g_free (task_name);
732 }
733
734 static gboolean
do_manage_comp_idle(struct _manage_comp * mc)735 do_manage_comp_idle (struct _manage_comp *mc)
736 {
737 GError *error = NULL;
738 ECalClientSourceType source_type = E_CAL_CLIENT_SOURCE_TYPE_LAST;
739 ECalComponent *edit_comp = NULL;
740
741 g_return_val_if_fail (mc, FALSE);
742
743 source_type = e_cal_client_get_source_type (mc->client);
744
745 if (source_type == E_CAL_CLIENT_SOURCE_TYPE_LAST) {
746 free_manage_comp_struct (mc);
747
748 g_warning ("mail-to-task: Incorrect call of %s, no data given", G_STRFUNC);
749 return FALSE;
750 }
751
752 if (mc->stored_comp) {
753 const gchar *ask = get_question_edit_old (source_type);
754
755 if (ask) {
756 gchar *msg = g_strdup_printf (ask, i_cal_component_get_summary (mc->stored_comp) ? i_cal_component_get_summary (mc->stored_comp) : _("[No Summary]"));
757 gint chosen;
758
759 chosen = do_ask (msg, TRUE);
760
761 if (chosen == GTK_RESPONSE_YES) {
762 edit_comp = e_cal_component_new ();
763 if (!e_cal_component_set_icalcomponent (edit_comp, i_cal_component_clone (mc->stored_comp))) {
764 g_object_unref (edit_comp);
765 edit_comp = NULL;
766 error = g_error_new (
767 E_CAL_CLIENT_ERROR,
768 E_CAL_CLIENT_ERROR_INVALID_OBJECT,
769 "%s", _("Invalid object returned from a server"));
770
771 }
772 } else if (chosen == GTK_RESPONSE_NO) {
773 /* user wants to create a new event, thus generate a new UID */
774 gchar *new_uid = e_util_generate_uid ();
775 edit_comp = mc->comp;
776 e_cal_component_set_uid (edit_comp, new_uid);
777 e_cal_component_set_recurid (edit_comp, NULL);
778 g_free (new_uid);
779 }
780 g_free (msg);
781 }
782 } else {
783 edit_comp = mc->comp;
784 }
785
786 if (edit_comp) {
787 EShell *shell;
788 ECompEditor *comp_editor;
789
790 /* FIXME Pass in the EShell instance. */
791 shell = e_shell_get_default ();
792 comp_editor = get_component_editor (
793 shell, mc->client, edit_comp,
794 edit_comp == mc->comp, &error);
795
796 if (comp_editor && !error) {
797 comp_editor_title_changed (GTK_WIDGET (comp_editor), NULL, mc);
798
799 e_signal_connect_notify (
800 comp_editor, "notify::title",
801 G_CALLBACK (comp_editor_title_changed), mc);
802 g_signal_connect (
803 comp_editor, "editor-closed",
804 G_CALLBACK (comp_editor_closed), mc);
805
806 gtk_window_present (GTK_WINDOW (comp_editor));
807
808 if (edit_comp != mc->comp)
809 g_object_unref (edit_comp);
810 } else {
811 g_warning ("Failed to create event editor: %s", error ? error->message : "Unknown error");
812 g_cond_signal (&mc->cond);
813 }
814 } else {
815 /* User canceled editing already existing event, so
816 * treat it as if he just closed the editor window. */
817 comp_editor_closed (NULL, FALSE, mc);
818 }
819
820 if (error != NULL) {
821 e_notice (
822 NULL, GTK_MESSAGE_ERROR,
823 _("An error occurred during processing: %s"),
824 error->message);
825 g_clear_error (&error);
826 }
827
828 return FALSE;
829 }
830
831 typedef struct {
832 EClientCache *client_cache;
833 ESource *source;
834 const gchar *extension_name;
835 ECalClientSourceType source_type;
836 CamelFolder *folder;
837 GPtrArray *uids;
838 gchar *selected_text;
839 gchar *default_charset;
840 gchar *forced_charset;
841 gboolean with_attendees;
842 } AsyncData;
843
844 static void
async_data_free(AsyncData * data)845 async_data_free (AsyncData *data)
846 {
847 if (data) {
848 g_free (data->selected_text);
849 g_free (data->default_charset);
850 g_free (data->forced_charset);
851 g_object_unref (data->client_cache);
852 g_object_unref (data->source);
853 g_slice_free (AsyncData, data);
854 }
855 }
856
857 static gboolean
do_mail_to_event(AsyncData * data)858 do_mail_to_event (AsyncData *data)
859 {
860 EClient *client;
861 CamelFolder *folder = data->folder;
862 GPtrArray *uids = data->uids;
863 GError *error = NULL;
864
865 client = e_client_cache_get_client_sync (data->client_cache,
866 data->source, data->extension_name, 30, NULL, &error);
867
868 /* Sanity check. */
869 g_return_val_if_fail (
870 ((client != NULL) && (error == NULL)) ||
871 ((client == NULL) && (error != NULL)), TRUE);
872
873 if (error != NULL) {
874 report_error_idle (_("Cannot open calendar. %s"), error->message);
875 } else if (e_client_is_readonly (E_CLIENT (client))) {
876 switch (data->source_type) {
877 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
878 report_error_idle (_("Selected calendar is read only, thus cannot create event there. Select other calendar, please."), NULL);
879 break;
880 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
881 report_error_idle (_("Selected task list is read only, thus cannot create task there. Select other task list, please."), NULL);
882 break;
883 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
884 report_error_idle (_("Selected memo list is read only, thus cannot create memo there. Select other memo list, please."), NULL);
885 break;
886 default:
887 g_warn_if_reached ();
888 break;
889 }
890 } else {
891 gint i;
892 ECalComponentDateTime *dt, *dt2;
893 ICalTime *tt, *tt2;
894 struct _manage_comp *oldmc = NULL;
895
896 #define cache_backend_prop(prop) { \
897 gchar *val = NULL; \
898 e_client_get_backend_property_sync (E_CLIENT (client), prop, &val, NULL, NULL); \
899 g_free (val); \
900 }
901
902 /* precache backend properties, thus editor have them ready when needed */
903 cache_backend_prop (E_CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS);
904 cache_backend_prop (E_CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS);
905 cache_backend_prop (E_CAL_BACKEND_PROPERTY_DEFAULT_OBJECT);
906 e_client_get_capabilities (E_CLIENT (client));
907
908 #undef cache_backend_prop
909
910 /* set start day of the event as today, without time - easier than looking for a calendar's time zone */
911 tt = i_cal_time_new_today ();
912 tt2 = i_cal_time_clone (tt);
913 i_cal_time_adjust (tt2, 1, 0, 0, 0);
914
915 dt = e_cal_component_datetime_new_take (tt, NULL);
916 dt2 = e_cal_component_datetime_new_take (tt2, NULL);
917
918 for (i = 0; i < (uids ? uids->len : 0); i++) {
919 CamelMimeMessage *message;
920 ECalComponent *comp;
921 ECalComponentText *text;
922 ICalProperty *prop;
923 ICalComponent *icomp;
924 struct _manage_comp *mc;
925 const gchar *message_uid = g_ptr_array_index (uids, i);
926
927 /* retrieve the message from the CamelFolder */
928 /* FIXME Not passing a GCancellable or GError. */
929 message = camel_folder_get_message_sync (folder, message_uid, NULL, NULL);
930 if (!message) {
931 continue;
932 }
933
934 comp = e_cal_component_new ();
935
936 switch (data->source_type) {
937 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
938 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
939 break;
940 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
941 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
942 break;
943 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
944 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
945 break;
946 default:
947 g_warn_if_reached ();
948 break;
949 }
950
951 e_cal_component_set_uid (comp, camel_mime_message_get_message_id (message));
952 e_cal_component_set_dtstart (comp, dt);
953
954 if (data->source_type == E_CAL_CLIENT_SOURCE_TYPE_EVENTS) {
955 /* make it an all-day event */
956 e_cal_component_set_dtend (comp, dt2);
957 }
958
959 /* set the summary */
960 text = e_cal_component_text_new (camel_mime_message_get_subject (message), NULL);
961 e_cal_component_set_summary (comp, text);
962 e_cal_component_text_free (text);
963
964 /* set all fields */
965 if (data->selected_text) {
966 GSList sl;
967
968 text = e_cal_component_text_new (data->selected_text, NULL);
969
970 sl.next = NULL;
971 sl.data = text;
972
973 e_cal_component_set_descriptions (comp, &sl);
974
975 e_cal_component_text_free (text);
976 } else
977 set_description (comp, message, data->default_charset, data->forced_charset);
978
979 if (data->with_attendees) {
980 gchar *organizer;
981
982 /* set actual user as organizer, to be able to change event's properties */
983 organizer = set_organizer (comp, message, data->folder, message_uid);
984 set_attendees (comp, message, organizer);
985 g_free (organizer);
986 }
987
988 /* set attachment files */
989 set_attachments (E_CAL_CLIENT (client), comp, message);
990
991 /* priority */
992 set_priority (comp, CAMEL_MIME_PART (message));
993
994 /* no need to increment a sequence number, this is a new component */
995 e_cal_component_abort_sequence (comp);
996
997 icomp = e_cal_component_get_icalcomponent (comp);
998
999 prop = i_cal_property_new_x ("1");
1000 i_cal_property_set_x_name (prop, "X-EVOLUTION-MOVE-CALENDAR");
1001 i_cal_component_take_property (icomp, prop);
1002
1003 mc = g_slice_new0 (struct _manage_comp);
1004 mc->client = E_CAL_CLIENT (g_object_ref (client));
1005 mc->comp = g_object_ref (comp);
1006 g_mutex_init (&mc->mutex);
1007 g_cond_init (&mc->cond);
1008 mc->mails_count = uids->len;
1009 mc->mails_done = i + 1; /* Current task */
1010 mc->editor_title = NULL;
1011 mc->can_continue = TRUE;
1012
1013 if (oldmc) {
1014 /* Wait for user to quit the editor created in previous iteration
1015 * before displaying next one */
1016 gboolean can_continue;
1017 g_mutex_lock (&oldmc->mutex);
1018 g_cond_wait (&oldmc->cond, &oldmc->mutex);
1019 g_mutex_unlock (&oldmc->mutex);
1020 can_continue = oldmc->can_continue;
1021 free_manage_comp_struct (oldmc);
1022 oldmc = NULL;
1023
1024 if (!can_continue)
1025 break;
1026 }
1027
1028 e_cal_client_get_object_sync (
1029 E_CAL_CLIENT (client),
1030 i_cal_component_get_uid (icomp),
1031 NULL, &mc->stored_comp, NULL, NULL);
1032
1033 /* Prioritize ahead of GTK+ redraws. */
1034 g_idle_add_full (
1035 G_PRIORITY_HIGH_IDLE,
1036 (GSourceFunc) do_manage_comp_idle, mc, NULL);
1037
1038 oldmc = mc;
1039
1040 g_object_unref (comp);
1041 g_object_unref (message);
1042
1043 }
1044
1045 /* Wait for the last editor and then clean up */
1046 if (oldmc) {
1047 g_mutex_lock (&oldmc->mutex);
1048 g_cond_wait (&oldmc->cond, &oldmc->mutex);
1049 g_mutex_unlock (&oldmc->mutex);
1050 free_manage_comp_struct (oldmc);
1051 }
1052
1053 e_cal_component_datetime_free (dt);
1054 e_cal_component_datetime_free (dt2);
1055 }
1056
1057 /* free memory */
1058 if (client != NULL)
1059 g_object_unref (client);
1060 g_ptr_array_unref (uids);
1061 g_object_unref (folder);
1062
1063 async_data_free (data);
1064 g_clear_error (&error);
1065
1066 return TRUE;
1067 }
1068
1069 static gboolean
text_contains_nonwhitespace(const gchar * text,gint len)1070 text_contains_nonwhitespace (const gchar *text,
1071 gint len)
1072 {
1073 const gchar *p;
1074 gunichar c = 0;
1075
1076 if (!text || len <= 0)
1077 return FALSE;
1078
1079 p = text;
1080
1081 while (p && p - text < len) {
1082 c = g_utf8_get_char (p);
1083 if (!c)
1084 break;
1085
1086 if (!g_unichar_isspace (c))
1087 break;
1088
1089 p = g_utf8_next_char (p);
1090 }
1091
1092 return p - text < len - 1 && c != 0;
1093 }
1094
1095 static void
get_charsets(EMailReader * reader,gchar ** default_charset,gchar ** forced_charset)1096 get_charsets (EMailReader *reader,
1097 gchar **default_charset,
1098 gchar **forced_charset)
1099 {
1100 EMailDisplay *display;
1101 EMailFormatter *formatter;
1102
1103 display = e_mail_reader_get_mail_display (reader);
1104 formatter = e_mail_display_get_formatter (display);
1105
1106 *default_charset = e_mail_formatter_dup_default_charset (formatter);
1107 *forced_charset = e_mail_formatter_dup_charset (formatter);
1108 }
1109
1110 static void
start_mail_to_event_thread(AsyncData * data)1111 start_mail_to_event_thread (AsyncData *data)
1112 {
1113 GThread *thread = NULL;
1114 GError *error = NULL;
1115
1116 thread = g_thread_try_new (NULL, (GThreadFunc) do_mail_to_event, data, &error);
1117
1118 if (error != NULL) {
1119 g_warning (G_STRLOC ": %s", error->message);
1120 g_error_free (error);
1121 async_data_free (data);
1122 } else {
1123 g_thread_unref (thread);
1124 }
1125 }
1126
1127 static void
mail_to_task_got_selection_jsc_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)1128 mail_to_task_got_selection_jsc_cb (GObject *source_object,
1129 GAsyncResult *result,
1130 gpointer user_data)
1131 {
1132 AsyncData *data = user_data;
1133 GSList *texts = NULL;
1134 gchar *text;
1135 GError *error = NULL;
1136
1137 g_return_if_fail (data != NULL);
1138 g_return_if_fail (E_IS_WEB_VIEW (source_object));
1139
1140 if (!e_web_view_jsc_get_selection_finish (WEBKIT_WEB_VIEW (source_object), result, &texts, &error)) {
1141 texts = NULL;
1142 g_warning ("%s: Failed to get view selection: %s", G_STRFUNC, error ? error->message : "Unknown error");
1143 }
1144
1145 text = texts ? texts->data : NULL;
1146
1147 if (text && !text_contains_nonwhitespace (text, strlen (text))) {
1148 text = NULL;
1149 } else {
1150 /* Steal the pointer */
1151 if (texts)
1152 texts->data = NULL;
1153 }
1154
1155 data->selected_text = text;
1156
1157 start_mail_to_event_thread (data);
1158
1159 g_slist_free_full (texts, g_free);
1160 g_clear_error (&error);
1161 }
1162
1163 static void
mail_to_event(ECalClientSourceType source_type,gboolean with_attendees,EMailReader * reader)1164 mail_to_event (ECalClientSourceType source_type,
1165 gboolean with_attendees,
1166 EMailReader *reader)
1167 {
1168 EShell *shell;
1169 EMailBackend *backend;
1170 ESourceRegistry *registry;
1171 GPtrArray *uids;
1172 ESource *source = NULL;
1173 ESource *default_source;
1174 GList *list, *iter;
1175 GtkWindow *parent;
1176 const gchar *extension_name;
1177
1178 parent = e_mail_reader_get_window (reader);
1179 uids = e_mail_reader_get_selected_uids (reader);
1180
1181 /* Ask before converting 10 or more mails to events. */
1182 if (uids->len > 10) {
1183 gchar *question;
1184 gint response;
1185
1186 question = g_strdup_printf (
1187 get_question_add_all_mails (source_type, uids->len), uids->len);
1188 response = do_ask (question, FALSE);
1189 g_free (question);
1190
1191 if (response == GTK_RESPONSE_NO) {
1192 g_ptr_array_unref (uids);
1193 return;
1194 }
1195 }
1196
1197 backend = e_mail_reader_get_backend (reader);
1198 shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));
1199 registry = e_shell_get_registry (shell);
1200
1201 switch (source_type) {
1202 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
1203 extension_name = E_SOURCE_EXTENSION_CALENDAR;
1204 default_source = e_source_registry_ref_default_calendar (registry);
1205 break;
1206 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
1207 extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
1208 default_source = e_source_registry_ref_default_memo_list (registry);
1209 break;
1210 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
1211 extension_name = E_SOURCE_EXTENSION_TASK_LIST;
1212 default_source = e_source_registry_ref_default_task_list (registry);
1213 break;
1214 default:
1215 g_return_if_reached ();
1216 }
1217
1218 list = e_source_registry_list_sources (registry, extension_name);
1219
1220 /* If there is only one writable source, no need to prompt the user. */
1221 for (iter = list; iter != NULL; iter = g_list_next (iter)) {
1222 ESource *candidate = E_SOURCE (iter->data);
1223
1224 if (e_source_get_writable (candidate)) {
1225 if (source == NULL)
1226 source = candidate;
1227 else {
1228 source = NULL;
1229 break;
1230 }
1231 }
1232 }
1233
1234 g_list_free_full (list, (GDestroyNotify) g_object_unref);
1235
1236 if (source == NULL) {
1237 GtkWidget *dialog;
1238 ESourceSelector *selector;
1239
1240 /* ask the user which tasks list to save to */
1241 dialog = e_source_selector_dialog_new (
1242 parent, registry, extension_name);
1243
1244 selector = e_source_selector_dialog_get_selector (
1245 E_SOURCE_SELECTOR_DIALOG (dialog));
1246
1247 e_source_selector_set_primary_selection (
1248 selector, default_source);
1249
1250 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
1251 source = e_source_selector_dialog_peek_primary_selection (
1252 E_SOURCE_SELECTOR_DIALOG (dialog));
1253
1254 gtk_widget_destroy (dialog);
1255 }
1256
1257 if (source) {
1258 /* if a source has been selected, perform the mail2event operation */
1259 AsyncData *data;
1260 EMailDisplay *mail_display;
1261
1262 /* Fill the elements in AsynData */
1263 data = g_slice_new0 (AsyncData);
1264 data->client_cache = g_object_ref (e_shell_get_client_cache (shell));
1265 data->source = g_object_ref (source);
1266 data->extension_name = extension_name;
1267 data->source_type = source_type;
1268 data->folder = e_mail_reader_ref_folder (reader);
1269 data->uids = g_ptr_array_ref (uids);
1270 data->with_attendees = with_attendees;
1271 get_charsets (reader, &data->default_charset, &data->forced_charset);
1272
1273 mail_display = e_mail_reader_get_mail_display (reader);
1274
1275 if (uids->len == 1 && e_web_view_has_selection (E_WEB_VIEW (mail_display))) {
1276 e_web_view_jsc_get_selection (WEBKIT_WEB_VIEW (mail_display), E_TEXT_FORMAT_PLAIN, NULL,
1277 mail_to_task_got_selection_jsc_cb, data);
1278 } else {
1279 data->selected_text = NULL;
1280
1281 start_mail_to_event_thread (data);
1282 }
1283 }
1284
1285 g_object_unref (default_source);
1286 g_ptr_array_unref (uids);
1287 }
1288
1289 static void
action_mail_convert_to_event_cb(GtkAction * action,EMailReader * reader)1290 action_mail_convert_to_event_cb (GtkAction *action,
1291 EMailReader *reader)
1292 {
1293 mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_EVENTS, FALSE, reader);
1294 }
1295
1296 static void
action_mail_convert_to_meeting_cb(GtkAction * action,EMailReader * reader)1297 action_mail_convert_to_meeting_cb (GtkAction *action,
1298 EMailReader *reader)
1299 {
1300 mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_EVENTS, TRUE, reader);
1301 }
1302
1303 static void
action_mail_convert_to_memo_cb(GtkAction * action,EMailReader * reader)1304 action_mail_convert_to_memo_cb (GtkAction *action,
1305 EMailReader *reader)
1306 {
1307 mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_MEMOS, FALSE, reader);
1308 }
1309
1310 static void
action_mail_convert_to_task_cb(GtkAction * action,EMailReader * reader)1311 action_mail_convert_to_task_cb (GtkAction *action,
1312 EMailReader *reader)
1313 {
1314 mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_TASKS, FALSE, reader);
1315 }
1316
1317 /* Note, we're not using EPopupActions here because we update the state
1318 * of entire actions groups instead of individual actions. EPopupActions
1319 * just proxy the state of individual actions. */
1320
1321 static GtkActionEntry multi_selection_entries[] = {
1322
1323 { "mail-convert-to-appointment",
1324 "appointment-new",
1325 N_("Create an _Appointment"),
1326 NULL,
1327 N_("Create a new event from the selected message"),
1328 G_CALLBACK (action_mail_convert_to_event_cb) },
1329
1330 { "mail-convert-to-memo",
1331 "stock_insert-note",
1332 N_("Create a Mem_o"),
1333 NULL,
1334 N_("Create a new memo from the selected message"),
1335 G_CALLBACK (action_mail_convert_to_memo_cb) },
1336
1337 { "mail-convert-to-task",
1338 "stock_todo",
1339 N_("Create a _Task"),
1340 NULL,
1341 N_("Create a new task from the selected message"),
1342 G_CALLBACK (action_mail_convert_to_task_cb) }
1343 };
1344
1345 static GtkActionEntry single_selection_entries[] = {
1346
1347 { "mail-convert-to-meeting",
1348 "stock_people",
1349 N_("Create a _Meeting"),
1350 NULL,
1351 N_("Create a new meeting from the selected message"),
1352 G_CALLBACK (action_mail_convert_to_meeting_cb) }
1353 };
1354
1355 static void
update_actions_any_cb(EMailReader * reader,guint32 state,GtkActionGroup * action_group)1356 update_actions_any_cb (EMailReader *reader,
1357 guint32 state,
1358 GtkActionGroup *action_group)
1359 {
1360 gboolean sensitive;
1361
1362 sensitive =
1363 (state & E_MAIL_READER_SELECTION_SINGLE) ||
1364 (state & E_MAIL_READER_SELECTION_MULTIPLE);
1365
1366 gtk_action_group_set_sensitive (action_group, sensitive);
1367 }
1368
1369 static void
update_actions_one_cb(EMailReader * reader,guint32 state,GtkActionGroup * action_group)1370 update_actions_one_cb (EMailReader *reader,
1371 guint32 state,
1372 GtkActionGroup *action_group)
1373 {
1374 gboolean sensitive;
1375
1376 sensitive = (state & E_MAIL_READER_SELECTION_SINGLE);
1377
1378 gtk_action_group_set_sensitive (action_group, sensitive);
1379 }
1380
1381 static void
setup_actions(EMailReader * reader,GtkUIManager * ui_manager)1382 setup_actions (EMailReader *reader,
1383 GtkUIManager *ui_manager)
1384 {
1385 GtkActionGroup *action_group;
1386 const gchar *domain = GETTEXT_PACKAGE;
1387
1388 action_group = gtk_action_group_new ("mail-convert-any");
1389 gtk_action_group_set_translation_domain (action_group, domain);
1390 gtk_action_group_add_actions (
1391 action_group, multi_selection_entries,
1392 G_N_ELEMENTS (multi_selection_entries), reader);
1393 gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
1394 g_object_unref (action_group);
1395
1396 /* GtkUIManager now owns the action group reference.
1397 * The signal we're connecting to will only be emitted
1398 * during the GtkUIManager's lifetime, so the action
1399 * group will not disappear on us. */
1400
1401 g_signal_connect (
1402 reader, "update-actions",
1403 G_CALLBACK (update_actions_any_cb), action_group);
1404
1405 action_group = gtk_action_group_new ("mail-convert-one");
1406 gtk_action_group_set_translation_domain (action_group, domain);
1407 gtk_action_group_add_actions (
1408 action_group, single_selection_entries,
1409 G_N_ELEMENTS (single_selection_entries), reader);
1410 gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
1411 g_object_unref (action_group);
1412
1413 /* GtkUIManager now owns the action group reference.
1414 * The signal we're connecting to will only be emitted
1415 * during the GtkUIManager's lifetime, so the action
1416 * group will not disappear on us. */
1417
1418 g_signal_connect (
1419 reader, "update-actions",
1420 G_CALLBACK (update_actions_one_cb), action_group);
1421 }
1422
1423 gboolean
mail_browser_init(GtkUIManager * ui_manager,EMailBrowser * browser)1424 mail_browser_init (GtkUIManager *ui_manager,
1425 EMailBrowser *browser)
1426 {
1427 setup_actions (E_MAIL_READER (browser), ui_manager);
1428
1429 return TRUE;
1430 }
1431
1432 gboolean
mail_shell_view_init(GtkUIManager * ui_manager,EShellView * shell_view)1433 mail_shell_view_init (GtkUIManager *ui_manager,
1434 EShellView *shell_view)
1435 {
1436 EShellContent *shell_content;
1437
1438 shell_content = e_shell_view_get_shell_content (shell_view);
1439
1440 setup_actions (E_MAIL_READER (shell_content), ui_manager);
1441
1442 return TRUE;
1443 }
1444