evolution-3.6.4/plugins/mail-to-task/mail-to-task.c

Location Tool Test ID Function Issue
mail-to-task.c:293:13 clang-analyzer Access to field 'mime_type' results in a dereference of a null pointer (loaded from variable 'content')
mail-to-task.c:293:13 clang-analyzer Access to field 'mime_type' results in a dereference of a null pointer (loaded from variable 'content')
   1 /*
   2  *
   3  * This program is free software; you can redistribute it and/or
   4  * modify it under the terms of the GNU Lesser General Public
   5  * License as published by the Free Software Foundation; either
   6  * version 2 of the License, or (at your option) version 3.
   7  *
   8  * This program is distributed in the hope that it will be useful,
   9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  11  * Lesser General Public License for more details.
  12  *
  13  * You should have received a copy of the GNU Lesser General Public
  14  * License along with the program; if not, see <http://www.gnu.org/licenses/>
  15  *
  16  *
  17  * Authors:
  18  *		Michael Zucchi <notzed@novell.com>
  19  *		Rodrigo Moya <rodrigo@novell.com>
  20  *
  21  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  22  *
  23  */
  24 
  25 /* Convert a mail message into a task */
  26 
  27 #ifdef HAVE_CONFIG_H
  28 #include <config.h>
  29 #endif
  30 
  31 #include <stdio.h>
  32 #include <string.h>
  33 #include <glib/gi18n-lib.h>
  34 
  35 #include <libecal/libecal.h>
  36 #include <libedataserverui/libedataserverui.h>
  37 
  38 #include <libemail-engine/e-mail-utils.h>
  39 
  40 #include <e-util/e-dialog-utils.h>
  41 
  42 #include <misc/e-popup-action.h>
  43 #include <misc/e-attachment-store.h>
  44 
  45 #include <shell/e-shell-view.h>
  46 #include <shell/e-shell-window-actions.h>
  47 
  48 #include <mail/e-mail-browser.h>
  49 #include <mail/em-utils.h>
  50 #include <mail/message-list.h>
  51 
  52 #include <calendar/gui/dialogs/comp-editor.h>
  53 #include <calendar/gui/dialogs/event-editor.h>
  54 #include <calendar/gui/dialogs/memo-editor.h>
  55 #include <calendar/gui/dialogs/task-editor.h>
  56 
  57 #define E_SHELL_WINDOW_ACTION_CONVERT_TO_APPOINTMENT(window) \
  58 	E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-appointment")
  59 #define E_SHELL_WINDOW_ACTION_CONVERT_TO_MEETING(window) \
  60 	E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-meeting")
  61 #define E_SHELL_WINDOW_ACTION_CONVERT_TO_MEMO(window) \
  62 	E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-memo")
  63 #define E_SHELL_WINDOW_ACTION_CONVERT_TO_TASK(window) \
  64 	E_SHELL_WINDOW_ACTION ((window), "mail-convert-to-task")
  65 
  66 gboolean	mail_browser_init		(GtkUIManager *ui_manager,
  67 						 EMailBrowser *browser);
  68 gboolean	mail_shell_view_init		(GtkUIManager *ui_manager,
  69 						 EShellView *shell_view);
  70 
  71 static CompEditor *
  72 get_component_editor (EShell *shell,
  73                       ECalClient *client,
  74                       ECalComponent *comp,
  75                       gboolean is_new,
  76                       GError **error)
  77 {
  78 	ECalComponentId *id;
  79 	CompEditorFlags flags = 0;
  80 	CompEditor *editor = NULL;
  81 	ESourceRegistry *registry;
  82 
  83 	g_return_val_if_fail (E_IS_SHELL (shell), NULL);
  84 	g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL);
  85 	g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL);
  86 
  87 	registry = e_shell_get_registry (shell);
  88 
  89 	id = e_cal_component_get_id (comp);
  90 	g_return_val_if_fail (id != NULL, NULL);
  91 	g_return_val_if_fail (id->uid != NULL, NULL);
  92 
  93 	if (is_new) {
  94 		flags |= COMP_EDITOR_NEW_ITEM;
  95 	} else {
  96 		editor = comp_editor_find_instance (id->uid);
  97 	}
  98 
  99 	if (!editor) {
 100 		if (itip_organizer_is_user (registry, comp, client))
 101 			flags |= COMP_EDITOR_USER_ORG;
 102 
 103 		switch (e_cal_component_get_vtype (comp)) {
 104 		case E_CAL_COMPONENT_EVENT:
 105 			if (e_cal_component_has_attendees (comp))
 106 				flags |= COMP_EDITOR_MEETING;
 107 
 108 			editor = event_editor_new (client, shell, flags);
 109 
 110 			if (flags & COMP_EDITOR_MEETING)
 111 				event_editor_show_meeting (EVENT_EDITOR (editor));
 112 			break;
 113 		case E_CAL_COMPONENT_TODO:
 114 			if (e_cal_component_has_attendees (comp))
 115 				flags |= COMP_EDITOR_IS_ASSIGNED;
 116 
 117 			editor = task_editor_new (client, shell, flags);
 118 
 119 			if (flags & COMP_EDITOR_IS_ASSIGNED)
 120 				task_editor_show_assignment (TASK_EDITOR (editor));
 121 			break;
 122 		case E_CAL_COMPONENT_JOURNAL:
 123 			if (e_cal_component_has_organizer (comp))
 124 				flags |= COMP_EDITOR_IS_SHARED;
 125 
 126 			editor = memo_editor_new (client, shell, flags);
 127 			break;
 128 		default:
 129 			if (error)
 130 				*error = e_client_error_create (E_CLIENT_ERROR_INVALID_ARG, NULL);
 131 			break;
 132 		}
 133 
 134 		if (editor) {
 135 			comp_editor_edit_comp (editor, comp);
 136 
 137 			/* request save for new events */
 138 			comp_editor_set_changed (editor, is_new);
 139 		}
 140 	}
 141 
 142 	e_cal_component_free_id (id);
 143 
 144 	return editor;
 145 }
 146 
 147 static void
 148 set_attendees (ECalComponent *comp,
 149                CamelMimeMessage *message,
 150                const gchar *organizer)
 151 {
 152 	GSList *attendees = NULL, *to_free = NULL;
 153 	ECalComponentAttendee *ca;
 154 	CamelInternetAddress *from = NULL, *to, *cc, *bcc, *arr[4];
 155 	gint len, i, j;
 156 
 157 	if (message->reply_to)
 158 		from = message->reply_to;
 159 	else if (message->from)
 160 		from = message->from;
 161 
 162 	to = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO);
 163 	cc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC);
 164 	bcc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC);
 165 
 166 	arr[0] = from; arr[1] = to; arr[2] = cc; arr[3] = bcc;
 167 
 168 	for (j = 0; j < 4; j++) {
 169 		if (!arr[j])
 170 			continue;
 171 
 172 		len = CAMEL_ADDRESS (arr[j])->addresses->len;
 173 		for (i = 0; i < len; i++) {
 174 			const gchar *name, *addr;
 175 
 176 			if (camel_internet_address_get (arr[j], i, &name, &addr)) {
 177 				gchar *temp;
 178 
 179 				temp = g_strconcat ("mailto:", addr, NULL);
 180 				if (organizer && g_ascii_strcasecmp (temp, organizer) == 0) {
 181 					/* do not add organizer twice */
 182 					g_free (temp);
 183 					continue;
 184 				}
 185 
 186 				ca = g_new0 (ECalComponentAttendee, 1);
 187 
 188 				ca->value = temp;
 189 				ca->cn = name;
 190 				ca->cutype = ICAL_CUTYPE_INDIVIDUAL;
 191 				ca->status = ICAL_PARTSTAT_NEEDSACTION;
 192 				if (j == 0) {
 193 					/* From */
 194 					ca->role = ICAL_ROLE_CHAIR;
 195 				} else if (j == 2) {
 196 					/* BCC  */
 197 					ca->role = ICAL_ROLE_OPTPARTICIPANT;
 198 				} else {
 199 					/* all other */
 200 					ca->role = ICAL_ROLE_REQPARTICIPANT;
 201 				}
 202 
 203 				to_free = g_slist_prepend (to_free, temp);
 204 
 205 				attendees = g_slist_append (attendees, ca);
 206 			}
 207 		}
 208 	}
 209 
 210 	e_cal_component_set_attendee_list (comp, attendees);
 211 
 212 	g_slist_foreach (attendees, (GFunc) g_free, NULL);
 213 	g_slist_foreach (to_free, (GFunc) g_free, NULL);
 214 
 215 	g_slist_free (to_free);
 216 	g_slist_free (attendees);
 217 }
 218 
 219 static const gchar *
 220 prepend_from (CamelMimeMessage *message,
 221               gchar **text)
 222 {
 223 	gchar *res, *tmp, *addr = NULL;
 224 	const gchar *name = NULL, *eml = NULL;
 225 	CamelInternetAddress *from = NULL;
 226 
 227 	g_return_val_if_fail (message != NULL, NULL);
 228 	g_return_val_if_fail (text != NULL, NULL);
 229 
 230 	if (message->reply_to)
 231 		from = message->reply_to;
 232 	else if (message->from)
 233 		from = message->from;
 234 
 235 	if (from && camel_internet_address_get (from, 0, &name, &eml))
 236 		addr = camel_internet_address_format_address (name, eml);
 237 
 238 	/* To Translators: The full sentence looks like: "Created from a mail by John Doe <john.doe@myco.example>" */
 239 	tmp = g_strdup_printf (_("Created from a mail by %s"), addr ? addr : "");
 240 
 241 	res = g_strconcat (tmp, "\n", *text, NULL);
 242 
 243 	g_free (tmp);
 244 	g_free (*text);
 245 
 246 	*text = res;
 247 
 248 	return res;
 249 }
 250 
 251 static void
 252 set_description (ECalComponent *comp,
 253                  CamelMimeMessage *message)
 254 {
 255 	CamelDataWrapper *content;
 256 	CamelStream *stream;
 257 	CamelContentType *type;
 258 	CamelMimePart *mime_part = CAMEL_MIME_PART (message);
 259 	ECalComponentText *text = NULL;
 260 	GByteArray *byte_array;
 261 	GSList *sl = NULL;
 262 	gchar *str, *convert_str = NULL;
 263 	gsize bytes_read, bytes_written;
 264 	gint count = 2;
 265 
 266 	content = camel_medium_get_content ((CamelMedium *) message);
 267 	if (!content)
 268 		return;
 269 
 270 	/*
 271 	 * Get non-multipart content from multipart message.
 272 	 */
 273 	while (CAMEL_IS_MULTIPART (content) && count > 0) {
 274 		mime_part = camel_multipart_get_part (CAMEL_MULTIPART (content), 0);
 275 		content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
 276 		count--;
 277 	}
 278 
 279 	if (!mime_part)
 280 		return;
 281 
 282 	type = camel_mime_part_get_content_type (mime_part);
 283 	if (!camel_content_type_is (type, "text", "plain"))
 284 		return;
 285 
 286 	byte_array = g_byte_array_new ();
 287 	stream = camel_stream_mem_new_with_byte_array (byte_array);
 288 	camel_data_wrapper_decode_to_stream_sync (content, stream, NULL, NULL);
 289 	str = g_strndup ((gchar *) byte_array->data, byte_array->len);
 290 	g_object_unref (stream);
 291 
 292 	/* convert to UTF-8 string */
 293 	if (str && content->mime_type->params && content->mime_type->params->value) {
Access to field 'mime_type' results in a dereference of a null pointer (loaded from variable 'content')
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

Access to field 'mime_type' results in a dereference of a null pointer (loaded from variable 'content')
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

294 convert_str = g_convert ( 295 str, strlen (str), 296 "UTF-8", content->mime_type->params->value, 297 &bytes_read, &bytes_written, NULL); 298 } 299 300 text = g_new0 (ECalComponentText, 1); 301 if (convert_str) 302 text->value = prepend_from (message, &convert_str); 303 else 304 text->value = prepend_from (message, &str); 305 text->altrep = NULL; 306 sl = g_slist_append (sl, text); 307 308 e_cal_component_set_description_list (comp, sl); 309 310 g_free (str); 311 if (convert_str) 312 g_free (convert_str); 313 e_cal_component_free_text_list (sl); 314 } 315 316 static gchar * 317 set_organizer (ECalComponent *comp, 318 CamelFolder *folder) 319 { 320 EShell *shell; 321 ESource *source = NULL; 322 ESourceRegistry *registry; 323 ESourceMailIdentity *extension; 324 const gchar *extension_name; 325 const gchar *address, *name; 326 ECalComponentOrganizer organizer = {NULL, NULL, NULL, NULL}; 327 gchar *mailto = NULL; 328 329 shell = e_shell_get_default (); 330 registry = e_shell_get_registry (shell); 331 332 if (folder != NULL) { 333 CamelStore *store; 334 335 store = camel_folder_get_parent_store (folder); 336 source = em_utils_ref_mail_identity_for_store (registry, store); 337 } 338 339 if (source == NULL) 340 source = e_source_registry_ref_default_mail_identity (registry); 341 342 g_return_val_if_fail (source != NULL, NULL); 343 344 extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY; 345 extension = e_source_get_extension (source, extension_name); 346 347 name = e_source_mail_identity_get_name (extension); 348 address = e_source_mail_identity_get_address (extension); 349 350 if (name != NULL && address != NULL) { 351 mailto = g_strconcat ("mailto:", address, NULL); 352 organizer.value = mailto; 353 organizer.cn = name; 354 e_cal_component_set_organizer (comp, &organizer); 355 } 356 357 g_object_unref (source); 358 359 return mailto; 360 } 361 362 struct _att_async_cb_data { 363 gchar **uris; 364 EFlag *flag; 365 }; 366 367 static void 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 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) 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 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 e_cal_component_get_uid (comp, &comp_uid); 433 g_return_if_fail (comp_uid != NULL); 434 435 tmp = g_strdup (comp_uid); 436 e_filename_make_safe (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, cb_data.uris[ii]); 495 cb_data.uris[ii] = NULL; 496 } 497 498 e_flag_free (cb_data.flag); 499 g_free (cb_data.uris); 500 501 /* XXX Does this take ownership of the list? */ 502 e_cal_component_set_attachment_list (comp, uri_list); 503 504 e_attachment_store_remove_all (store); 505 g_object_unref (destination); 506 g_object_unref (store); 507 } 508 509 static void 510 set_priority (ECalComponent *comp, 511 CamelMimePart *part) 512 { 513 const gchar *prio; 514 515 g_return_if_fail (comp != NULL); 516 g_return_if_fail (part != NULL); 517 518 prio = camel_header_raw_find (& (part->headers), "X-Priority", NULL); 519 if (prio && atoi (prio) > 0) { 520 gint priority = 1; 521 522 e_cal_component_set_priority (comp, &priority); 523 } 524 } 525 526 struct _report_error 527 { 528 gchar *format; 529 gchar *param; 530 }; 531 532 static gboolean 533 do_report_error (struct _report_error *err) 534 { 535 if (err) { 536 e_notice (NULL, GTK_MESSAGE_ERROR, err->format, err->param); 537 g_free (err->format); 538 g_free (err->param); 539 g_free (err); 540 } 541 542 return FALSE; 543 } 544 545 static void 546 report_error_idle (const gchar *format, 547 const gchar *param) 548 { 549 struct _report_error *err = g_new (struct _report_error, 1); 550 551 err->format = g_strdup (format); 552 err->param = g_strdup (param); 553 554 g_usleep (250); 555 g_idle_add ((GSourceFunc) do_report_error, err); 556 } 557 558 struct _manage_comp 559 { 560 ECalClient *client; 561 ECalComponent *comp; 562 icalcomponent *stored_comp; /* the one in client already */ 563 GCond *cond; 564 GMutex *mutex; 565 gint mails_count; 566 gint mails_done; 567 gchar *editor_title; 568 gboolean can_continue; 569 }; 570 571 static void 572 free_manage_comp_struct (struct _manage_comp *mc) 573 { 574 g_return_if_fail (mc != NULL); 575 576 g_object_unref (mc->comp); 577 g_object_unref (mc->client); 578 if (mc->stored_comp) 579 icalcomponent_free (mc->stored_comp); 580 if (mc->mutex) 581 g_mutex_free (mc->mutex); 582 if (mc->cond) 583 g_cond_free (mc->cond); 584 if (mc->editor_title) 585 g_free (mc->editor_title); 586 587 g_free (mc); 588 } 589 590 static gint 591 do_ask (const gchar *text, 592 gboolean is_create_edit_add) 593 { 594 gint res; 595 GtkWidget *dialog = gtk_message_dialog_new ( 596 NULL, 597 GTK_DIALOG_MODAL, 598 GTK_MESSAGE_QUESTION, 599 is_create_edit_add ? GTK_BUTTONS_NONE : GTK_BUTTONS_YES_NO, 600 "%s", text); 601 602 if (is_create_edit_add) { 603 gtk_dialog_add_buttons ( 604 GTK_DIALOG (dialog), 605 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, 606 GTK_STOCK_EDIT, GTK_RESPONSE_YES, 607 GTK_STOCK_NEW, GTK_RESPONSE_NO, 608 NULL); 609 } 610 611 res = gtk_dialog_run (GTK_DIALOG (dialog)); 612 613 gtk_widget_destroy (dialog); 614 615 return res; 616 } 617 618 static const gchar * 619 get_question_edit_old (ECalClientSourceType source_type) 620 { 621 const gchar *ask = NULL; 622 623 switch (source_type) { 624 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: 625 ask = _("Selected calendar contains event '%s' already. Would you like to edit the old event?"); 626 break; 627 case E_CAL_CLIENT_SOURCE_TYPE_TASKS: 628 ask = _("Selected task list contains task '%s' already. Would you like to edit the old task?"); 629 break; 630 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: 631 ask = _("Selected memo list contains memo '%s' already. Would you like to edit the old memo?"); 632 break; 633 default: 634 g_assert_not_reached (); 635 break; 636 } 637 638 return ask; 639 } 640 641 static const gchar * 642 get_question_add_all_mails (ECalClientSourceType source_type, 643 gint count) 644 { 645 const gchar *ask = NULL; 646 647 switch (source_type) { 648 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: 649 /* Translators: Note there are always more than 10 mails selected */ 650 ask = ngettext ( 651 "You have selected %d mails to be converted to events. Do you really want to add them all?", 652 "You have selected %d mails to be converted to events. Do you really want to add them all?", 653 count); 654 break; 655 case E_CAL_CLIENT_SOURCE_TYPE_TASKS: 656 /* Translators: Note there are always more than 10 mails selected */ 657 ask = ngettext ( 658 "You have selected %d mails to be converted to tasks. Do you really want to add them all?", 659 "You have selected %d mails to be converted to tasks. Do you really want to add them all?", 660 count); 661 break; 662 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: 663 /* Translators: Note there are always more than 10 mails selected */ 664 ask = ngettext ( 665 "You have selected %d mails to be converted to memos. Do you really want to add them all?", 666 "You have selected %d mails to be converted to memos. Do you really want to add them all?", 667 count); 668 break; 669 default: 670 g_assert_not_reached (); 671 break; 672 } 673 674 return ask; 675 } 676 677 static void 678 comp_editor_closed (CompEditor *editor, 679 gboolean accepted, 680 struct _manage_comp *mc) 681 { 682 if (!mc) 683 return; 684 685 if (!accepted && mc->mails_done < mc->mails_count) 686 mc->can_continue = (do_ask (_("Do you wish to continue converting remaining mails?"), FALSE) == GTK_RESPONSE_YES); 687 688 /* Signal the do_mail_to_event thread that editor was closed and editor 689 * for next event can be displayed (if any) */ 690 g_cond_signal (mc->cond); 691 } 692 693 /* 694 * This handler takes title of the editor window and 695 * inserts information about number of processed mails and 696 * number of all mails to process, so the window title 697 * will look like "Appointment (3/10) - An appoitment name" 698 */ 699 static void 700 comp_editor_title_changed (GtkWidget *widget, 701 GParamSpec *pspec, 702 struct _manage_comp *mc) 703 { 704 GtkWindow *editor = GTK_WINDOW (widget); 705 const gchar *title = gtk_window_get_title (editor); 706 gchar *new_title; 707 gchar *splitter; 708 gchar *comp_name, *task_name; 709 710 if (!mc) 711 return; 712 713 /* Recursion prevence */ 714 if (mc->editor_title && g_utf8_collate (mc->editor_title, title) == 0) 715 return; 716 717 splitter = strchr (title, '-'); 718 if (!splitter) 719 return; 720 721 comp_name = g_strndup (title, splitter - title - 1); 722 task_name = g_strdup (splitter + 2); 723 new_title = g_strdup_printf ( 724 "%s (%d/%d) - %s", 725 comp_name, mc->mails_done, mc->mails_count, task_name); 726 727 /* Remember the new title, so that when gtk_window_set_title() causes 728 * this handler to be recursively called, we can recognize that and 729 * prevent endless recursion */ 730 if (mc->editor_title) 731 g_free (mc->editor_title); 732 mc->editor_title = new_title; 733 734 gtk_window_set_title (editor, new_title); 735 736 g_free (comp_name); 737 g_free (task_name); 738 } 739 740 static gboolean 741 do_manage_comp_idle (struct _manage_comp *mc) 742 { 743 GError *error = NULL; 744 ECalClientSourceType source_type = E_CAL_CLIENT_SOURCE_TYPE_LAST; 745 ECalComponent *edit_comp = NULL; 746 747 g_return_val_if_fail (mc, FALSE); 748 749 source_type = e_cal_client_get_source_type (mc->client); 750 751 if (source_type == E_CAL_CLIENT_SOURCE_TYPE_LAST) { 752 free_manage_comp_struct (mc); 753 754 g_warning ("mail-to-task: Incorrect call of %s, no data given", G_STRFUNC); 755 return FALSE; 756 } 757 758 if (mc->stored_comp) { 759 const gchar *ask = get_question_edit_old (source_type); 760 761 if (ask) { 762 gchar *msg = g_strdup_printf (ask, icalcomponent_get_summary (mc->stored_comp) ? icalcomponent_get_summary (mc->stored_comp) : _("[No Summary]")); 763 gint chosen; 764 765 chosen = do_ask (msg, TRUE); 766 767 if (chosen == GTK_RESPONSE_YES) { 768 edit_comp = e_cal_component_new (); 769 if (!e_cal_component_set_icalcomponent (edit_comp, icalcomponent_new_clone (mc->stored_comp))) { 770 g_object_unref (edit_comp); 771 edit_comp = NULL; 772 error = g_error_new ( 773 E_CAL_CLIENT_ERROR, 774 E_CAL_CLIENT_ERROR_INVALID_OBJECT, 775 "%s", _("Invalid object returned from a server")); 776 777 } 778 } else if (chosen == GTK_RESPONSE_NO) { 779 /* user wants to create a new event, thus generate a new UID */ 780 gchar *new_uid = e_cal_component_gen_uid (); 781 edit_comp = mc->comp; 782 e_cal_component_set_uid (edit_comp, new_uid); 783 e_cal_component_set_recurid (edit_comp, NULL); 784 g_free (new_uid); 785 } 786 g_free (msg); 787 } 788 } else { 789 edit_comp = mc->comp; 790 } 791 792 if (edit_comp) { 793 EShell *shell; 794 CompEditor *editor; 795 796 /* FIXME Pass in the EShell instance. */ 797 shell = e_shell_get_default (); 798 editor = get_component_editor ( 799 shell, mc->client, edit_comp, 800 edit_comp == mc->comp, &error); 801 802 if (editor && !error) { 803 /* Force editor's title change */ 804 comp_editor_title_changed (GTK_WIDGET (editor), NULL, mc); 805 806 g_signal_connect ( 807 editor, "notify::title", 808 G_CALLBACK (comp_editor_title_changed), mc); 809 g_signal_connect ( 810 editor, "comp_closed", 811 G_CALLBACK (comp_editor_closed), mc); 812 813 gtk_window_present (GTK_WINDOW (editor)); 814 815 if (edit_comp != mc->comp) 816 g_object_unref (edit_comp); 817 } else { 818 g_warning ("Failed to create event editor: %s", error ? error->message : "Unknown error"); 819 g_cond_signal (mc->cond); 820 } 821 } else { 822 /* User canceled editing already existing event, so treat it as if he just closed the editor window */ 823 comp_editor_closed (NULL, FALSE, mc); 824 } 825 826 if (error) { 827 e_notice (NULL, GTK_MESSAGE_ERROR, _("An error occurred during processing: %s"), error->message); 828 g_clear_error (&error); 829 } 830 831 return FALSE; 832 } 833 834 typedef struct { 835 ECalClient *client; 836 CamelFolder *folder; 837 GPtrArray *uids; 838 gchar *selected_text; 839 gboolean with_attendees; 840 }AsyncData; 841 842 static gboolean 843 do_mail_to_event (AsyncData *data) 844 { 845 ECalClient *client = data->client; 846 CamelFolder *folder = data->folder; 847 GPtrArray *uids = data->uids; 848 GError *err = NULL; 849 850 /* open the task client */ 851 e_client_open_sync (E_CLIENT (client), FALSE, NULL, &err); 852 853 if (err != NULL) { 854 report_error_idle (_("Cannot open calendar. %s"), err->message); 855 } else if (e_client_is_readonly (E_CLIENT (client))) { 856 if (err) 857 report_error_idle ("Check readonly failed. %s", err->message); 858 else { 859 switch (e_cal_client_get_source_type (client)) { 860 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: 861 report_error_idle (_("Selected source is read only, thus cannot create event there. Select other source, please."), NULL); 862 break; 863 case E_CAL_CLIENT_SOURCE_TYPE_TASKS: 864 report_error_idle (_("Selected source is read only, thus cannot create task there. Select other source, please."), NULL); 865 break; 866 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: 867 report_error_idle (_("Selected source is read only, thus cannot create memo there. Select other source, please."), NULL); 868 break; 869 default: 870 g_assert_not_reached (); 871 break; 872 } 873 } 874 } else { 875 gint i; 876 ECalClientSourceType source_type = e_cal_client_get_source_type (client); 877 ECalComponentDateTime dt, dt2; 878 struct icaltimetype tt, tt2; 879 struct _manage_comp *oldmc = NULL; 880 881 #define cache_backend_prop(prop) { \ 882 gchar *val = NULL; \ 883 e_client_get_backend_property_sync (E_CLIENT (client), prop, &val, NULL, NULL); \ 884 g_free (val); \ 885 } 886 887 /* precache backend properties, thus editor have them ready when needed */ 888 cache_backend_prop (CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS); 889 cache_backend_prop (CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS); 890 cache_backend_prop (CAL_BACKEND_PROPERTY_DEFAULT_OBJECT); 891 e_client_get_capabilities (E_CLIENT (client)); 892 893 #undef cache_backend_prop 894 895 /* set start day of the event as today, without time - easier than looking for a calendar's time zone */ 896 tt = icaltime_today (); 897 dt.value = &tt; 898 dt.tzid = NULL; 899 900 tt2 = tt; 901 icaltime_adjust (&tt2, 1, 0, 0, 0); 902 dt2.value = &tt2; 903 dt2.tzid = NULL; 904 905 for (i = 0; i < (uids ? uids->len : 0); i++) { 906 CamelMimeMessage *message; 907 ECalComponent *comp; 908 ECalComponentText text; 909 icalproperty *icalprop; 910 icalcomponent *icalcomp; 911 struct _manage_comp *mc; 912 913 /* retrieve the message from the CamelFolder */ 914 /* FIXME Not passing a GCancellable or GError. */ 915 message = camel_folder_get_message_sync ( 916 folder, g_ptr_array_index (uids, i), 917 NULL, NULL); 918 if (!message) { 919 continue; 920 } 921 922 comp = e_cal_component_new (); 923 924 switch (source_type) { 925 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: 926 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT); 927 break; 928 case E_CAL_CLIENT_SOURCE_TYPE_TASKS: 929 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO); 930 break; 931 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: 932 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL); 933 break; 934 default: 935 g_assert_not_reached (); 936 break; 937 } 938 939 e_cal_component_set_uid (comp, camel_mime_message_get_message_id (message)); 940 e_cal_component_set_dtstart (comp, &dt); 941 942 if (source_type == E_CAL_CLIENT_SOURCE_TYPE_EVENTS) { 943 /* make it an all-day event */ 944 e_cal_component_set_dtend (comp, &dt2); 945 } 946 947 /* set the summary */ 948 text.value = camel_mime_message_get_subject (message); 949 text.altrep = NULL; 950 e_cal_component_set_summary (comp, &text); 951 952 /* set all fields */ 953 if (data->selected_text) { 954 GSList sl; 955 956 text.value = data->selected_text; 957 text.altrep = NULL; 958 sl.next = NULL; 959 sl.data = &text; 960 961 e_cal_component_set_description_list (comp, &sl); 962 } else 963 set_description (comp, message); 964 965 if (data->with_attendees) { 966 gchar *organizer; 967 968 /* set actual user as organizer, to be able to change event's properties */ 969 organizer = set_organizer (comp, data->folder); 970 set_attendees (comp, message, organizer); 971 g_free (organizer); 972 } 973 974 /* set attachment files */ 975 set_attachments (client, comp, message); 976 977 /* priority */ 978 set_priority (comp, CAMEL_MIME_PART (message)); 979 980 /* no need to increment a sequence number, this is a new component */ 981 e_cal_component_abort_sequence (comp); 982 983 icalcomp = e_cal_component_get_icalcomponent (comp); 984 985 icalprop = icalproperty_new_x ("1"); 986 icalproperty_set_x_name (icalprop, "X-EVOLUTION-MOVE-CALENDAR"); 987 icalcomponent_add_property (icalcomp, icalprop); 988 989 mc = g_new0 (struct _manage_comp, 1); 990 mc->client = g_object_ref (client); 991 mc->comp = g_object_ref (comp); 992 mc->mutex = g_mutex_new (); 993 mc->cond = g_cond_new (); 994 mc->mails_count = uids->len; 995 mc->mails_done = i + 1; /* Current task */ 996 mc->editor_title = NULL; 997 mc->can_continue = TRUE; 998 999 if (oldmc) { 1000 /* Wait for user to quit the editor created in previous iteration 1001 * before displaying next one */ 1002 gboolean can_continue; 1003 g_mutex_lock (oldmc->mutex); 1004 g_cond_wait (oldmc->cond, oldmc->mutex); 1005 g_mutex_unlock (oldmc->mutex); 1006 can_continue = oldmc->can_continue; 1007 free_manage_comp_struct (oldmc); 1008 oldmc = NULL; 1009 1010 if (!can_continue) 1011 break; 1012 } 1013 1014 if (!e_cal_client_get_object_sync (client, icalcomponent_get_uid (icalcomp), NULL, &(mc->stored_comp), NULL, NULL)) 1015 mc->stored_comp = NULL; 1016 1017 /* Prioritize ahead of GTK+ redraws. */ 1018 g_idle_add_full ( 1019 G_PRIORITY_HIGH_IDLE, 1020 (GSourceFunc) do_manage_comp_idle, mc, NULL); 1021 1022 oldmc = mc; 1023 1024 g_object_unref (comp); 1025 g_object_unref (message); 1026 1027 } 1028 1029 /* Wait for the last editor and then clean up */ 1030 if (oldmc) { 1031 g_mutex_lock (oldmc->mutex); 1032 g_cond_wait (oldmc->cond, oldmc->mutex); 1033 g_mutex_unlock (oldmc->mutex); 1034 free_manage_comp_struct (oldmc); 1035 } 1036 } 1037 1038 /* free memory */ 1039 g_object_unref (data->client); 1040 em_utils_uids_free (uids); 1041 g_object_unref (folder); 1042 g_free (data->selected_text); 1043 g_free (data); 1044 data = NULL; 1045 1046 if (err) 1047 g_error_free (err); 1048 1049 return TRUE; 1050 } 1051 1052 static gboolean 1053 text_contains_nonwhitespace (const gchar *text, 1054 gint len) 1055 { 1056 const gchar *p; 1057 gunichar c = 0; 1058 1059 if (!text || len <= 0) 1060 return FALSE; 1061 1062 p = text; 1063 1064 while (p && p - text < len) { 1065 c = g_utf8_get_char (p); 1066 if (!c) 1067 break; 1068 1069 if (!g_unichar_isspace (c)) 1070 break; 1071 1072 p = g_utf8_next_char (p); 1073 } 1074 1075 return p - text < len - 1 && c != 0; 1076 } 1077 1078 /* should be freed with g_free after done with it */ 1079 static gchar * 1080 get_selected_text (EMailReader *reader) 1081 { 1082 EMailDisplay *display; 1083 gchar *text = NULL; 1084 1085 display = e_mail_reader_get_mail_display (reader); 1086 1087 if (!e_web_view_is_selection_active (E_WEB_VIEW (display))) 1088 return NULL; 1089 1090 text = e_mail_display_get_selection_plain_text (display); 1091 1092 if (text == NULL || !text_contains_nonwhitespace (text, strlen (text))) { 1093 g_free (text); 1094 return NULL; 1095 } 1096 1097 return text; 1098 } 1099 1100 static void 1101 mail_to_event (ECalClientSourceType source_type, 1102 gboolean with_attendees, 1103 EMailReader *reader) 1104 { 1105 EShell *shell; 1106 EMailBackend *backend; 1107 ESourceRegistry *registry; 1108 CamelFolder *folder; 1109 GPtrArray *uids; 1110 ESource *source = NULL; 1111 ESource *default_source; 1112 GList *list, *iter; 1113 GtkWindow *parent; 1114 const gchar *extension_name; 1115 GError *error = NULL; 1116 1117 folder = e_mail_reader_get_folder (reader); 1118 parent = e_mail_reader_get_window (reader); 1119 uids = e_mail_reader_get_selected_uids (reader); 1120 1121 /* Ask before converting 10 or more mails to events. */ 1122 if (uids->len > 10) { 1123 gchar *question; 1124 gint response; 1125 1126 question = g_strdup_printf ( 1127 get_question_add_all_mails (source_type, uids->len), uids->len); 1128 response = do_ask (question, FALSE); 1129 g_free (question); 1130 1131 if (response == GTK_RESPONSE_NO) { 1132 em_utils_uids_free (uids); 1133 g_object_unref (folder); 1134 return; 1135 } 1136 } 1137 1138 backend = e_mail_reader_get_backend (reader); 1139 shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend)); 1140 registry = e_shell_get_registry (shell); 1141 1142 switch (source_type) { 1143 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: 1144 extension_name = E_SOURCE_EXTENSION_CALENDAR; 1145 default_source = e_source_registry_ref_default_calendar (registry); 1146 break; 1147 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: 1148 extension_name = E_SOURCE_EXTENSION_MEMO_LIST; 1149 default_source = e_source_registry_ref_default_memo_list (registry); 1150 break; 1151 case E_CAL_CLIENT_SOURCE_TYPE_TASKS: 1152 extension_name = E_SOURCE_EXTENSION_TASK_LIST; 1153 default_source = e_source_registry_ref_default_task_list (registry); 1154 break; 1155 default: 1156 g_return_if_reached (); 1157 } 1158 1159 list = e_source_registry_list_sources (registry, extension_name); 1160 1161 /* If there is only one writable source, no need to prompt the user. */ 1162 for (iter = list; iter != NULL; iter = g_list_next (iter)) { 1163 ESource *candidate = E_SOURCE (iter->data); 1164 1165 if (e_source_get_writable (candidate)) { 1166 if (source == NULL) 1167 source = candidate; 1168 else { 1169 source = NULL; 1170 break; 1171 } 1172 } 1173 } 1174 1175 g_list_free_full (list, (GDestroyNotify) g_object_unref); 1176 1177 if (source == NULL) { 1178 GtkWidget *dialog; 1179 ESourceSelector *selector; 1180 1181 /* ask the user which tasks list to save to */ 1182 dialog = e_source_selector_dialog_new ( 1183 parent, registry, extension_name); 1184 1185 selector = e_source_selector_dialog_get_selector ( 1186 E_SOURCE_SELECTOR_DIALOG (dialog)); 1187 1188 e_source_selector_set_primary_selection ( 1189 selector, default_source); 1190 1191 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) 1192 source = e_source_selector_dialog_peek_primary_selection ( 1193 E_SOURCE_SELECTOR_DIALOG (dialog)); 1194 1195 gtk_widget_destroy (dialog); 1196 } else if (!source && default_source) { 1197 source = default_source; 1198 } else if (!source) { 1199 e_notice (NULL, GTK_MESSAGE_ERROR, _("No writable calendar is available.")); 1200 1201 em_utils_uids_free (uids); 1202 g_object_unref (folder); 1203 if (error) 1204 g_error_free (error); 1205 goto exit; 1206 } 1207 1208 if (source) { 1209 /* if a source has been selected, perform the mail2event operation */ 1210 ECalClient *client = NULL; 1211 AsyncData *data = NULL; 1212 GError *error = NULL; 1213 1214 client = e_cal_client_new (source, source_type, &error); 1215 if (!client) { 1216 e_notice ( 1217 parent, GTK_MESSAGE_ERROR, 1218 "Could not connect to '%s'", 1219 e_source_get_display_name (source)); 1220 goto exit; 1221 } 1222 1223 /* Fill the elements in AsynData */ 1224 data = g_new0 (AsyncData, 1); 1225 data->client = client; 1226 data->folder = folder; 1227 data->uids = uids; 1228 data->with_attendees = with_attendees; 1229 1230 if (uids->len == 1) 1231 data->selected_text = get_selected_text (reader); 1232 else 1233 data->selected_text = NULL; 1234 1235 g_thread_create ( 1236 (GThreadFunc) do_mail_to_event, data, FALSE, &error); 1237 if (error != NULL) { 1238 g_warning (G_STRLOC ": %s", error->message); 1239 g_error_free (error); 1240 } 1241 } 1242 1243 exit: 1244 g_object_unref (default_source); 1245 } 1246 1247 static void 1248 action_mail_convert_to_event_cb (GtkAction *action, 1249 EMailReader *reader) 1250 { 1251 mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_EVENTS, FALSE, reader); 1252 } 1253 1254 static void 1255 action_mail_convert_to_meeting_cb (GtkAction *action, 1256 EMailReader *reader) 1257 { 1258 mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_EVENTS, TRUE, reader); 1259 } 1260 1261 static void 1262 action_mail_convert_to_memo_cb (GtkAction *action, 1263 EMailReader *reader) 1264 { 1265 mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_MEMOS, FALSE, reader); 1266 } 1267 1268 static void 1269 action_mail_convert_to_task_cb (GtkAction *action, 1270 EMailReader *reader) 1271 { 1272 mail_to_event (E_CAL_CLIENT_SOURCE_TYPE_TASKS, FALSE, reader); 1273 } 1274 1275 /* Note, we're not using EPopupActions here because we update the state 1276 * of entire actions groups instead of individual actions. EPopupActions 1277 * just proxy the state of individual actions. */ 1278 1279 static GtkActionEntry multi_selection_entries[] = { 1280 1281 { "mail-convert-to-appointment", 1282 "appointment-new", 1283 N_("Create an _Appointment"), 1284 NULL, 1285 N_("Create a new event from the selected message"), 1286 G_CALLBACK (action_mail_convert_to_event_cb) }, 1287 1288 { "mail-convert-to-memo", 1289 "stock_insert-note", 1290 N_("Create a Mem_o"), 1291 NULL, 1292 N_("Create a new memo from the selected message"), 1293 G_CALLBACK (action_mail_convert_to_memo_cb) }, 1294 1295 { "mail-convert-to-task", 1296 "stock_todo", 1297 N_("Create a _Task"), 1298 NULL, 1299 N_("Create a new task from the selected message"), 1300 G_CALLBACK (action_mail_convert_to_task_cb) } 1301 }; 1302 1303 static GtkActionEntry single_selection_entries[] = { 1304 1305 { "mail-convert-to-meeting", 1306 "stock_new-meeting", 1307 N_("Create a _Meeting"), 1308 NULL, 1309 N_("Create a new meeting from the selected message"), 1310 G_CALLBACK (action_mail_convert_to_meeting_cb) } 1311 }; 1312 1313 static void 1314 update_actions_any_cb (EMailReader *reader, 1315 guint32 state, 1316 GtkActionGroup *action_group) 1317 { 1318 gboolean sensitive; 1319 1320 sensitive = 1321 (state & E_MAIL_READER_SELECTION_SINGLE) || 1322 (state & E_MAIL_READER_SELECTION_MULTIPLE); 1323 1324 gtk_action_group_set_sensitive (action_group, sensitive); 1325 } 1326 1327 static void 1328 update_actions_one_cb (EMailReader *reader, 1329 guint32 state, 1330 GtkActionGroup *action_group) 1331 { 1332 gboolean sensitive; 1333 1334 sensitive = (state & E_MAIL_READER_SELECTION_SINGLE); 1335 1336 gtk_action_group_set_sensitive (action_group, sensitive); 1337 } 1338 1339 static void 1340 setup_actions (EMailReader *reader, 1341 GtkUIManager *ui_manager) 1342 { 1343 GtkActionGroup *action_group; 1344 const gchar *domain = GETTEXT_PACKAGE; 1345 1346 action_group = gtk_action_group_new ("mail-convert-any"); 1347 gtk_action_group_set_translation_domain (action_group, domain); 1348 gtk_action_group_add_actions ( 1349 action_group, multi_selection_entries, 1350 G_N_ELEMENTS (multi_selection_entries), reader); 1351 gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); 1352 g_object_unref (action_group); 1353 1354 /* GtkUIManager now owns the action group reference. 1355 * The signal we're connecting to will only be emitted 1356 * during the GtkUIManager's lifetime, so the action 1357 * group will not disappear on us. */ 1358 1359 g_signal_connect ( 1360 reader, "update-actions", 1361 G_CALLBACK (update_actions_any_cb), action_group); 1362 1363 action_group = gtk_action_group_new ("mail-convert-one"); 1364 gtk_action_group_set_translation_domain (action_group, domain); 1365 gtk_action_group_add_actions ( 1366 action_group, single_selection_entries, 1367 G_N_ELEMENTS (single_selection_entries), reader); 1368 gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); 1369 g_object_unref (action_group); 1370 1371 /* GtkUIManager now owns the action group reference. 1372 * The signal we're connecting to will only be emitted 1373 * during the GtkUIManager's lifetime, so the action 1374 * group will not disappear on us. */ 1375 1376 g_signal_connect ( 1377 reader, "update-actions", 1378 G_CALLBACK (update_actions_one_cb), action_group); 1379 } 1380 1381 gboolean 1382 mail_browser_init (GtkUIManager *ui_manager, 1383 EMailBrowser *browser) 1384 { 1385 setup_actions (E_MAIL_READER (browser), ui_manager); 1386 1387 return TRUE; 1388 } 1389 1390 gboolean 1391 mail_shell_view_init (GtkUIManager *ui_manager, 1392 EShellView *shell_view) 1393 { 1394 EShellContent *shell_content; 1395 1396 shell_content = e_shell_view_get_shell_content (shell_view); 1397 1398 setup_actions (E_MAIL_READER (shell_content), ui_manager); 1399 1400 return TRUE; 1401 }