evolution-3.6.4/composer/e-composer-private.c

No issues found

   1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
   2 
   3 /*
   4  * This program is free software; you can redistribute it and/or
   5  * modify it under the terms of the GNU Lesser General Public
   6  * License as published by the Free Software Foundation; either
   7  * version 2 of the License, or (at your option) version 3.
   8  *
   9  * This program is distributed in the hope that it will be useful,
  10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  12  * Lesser General Public License for more details.
  13  *
  14  * You should have received a copy of the GNU Lesser General Public
  15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
  16  *
  17  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  18  */
  19 
  20 #ifdef HAVE_CONFIG_H
  21 #include <config.h>
  22 #endif
  23 
  24 #include "e-composer-private.h"
  25 #include "e-composer-spell-header.h"
  26 #include "e-util/e-util-private.h"
  27 
  28 /* Initial height of the picture gallery. */
  29 #define GALLERY_INITIAL_HEIGHT 150
  30 
  31 static void
  32 composer_setup_charset_menu (EMsgComposer *composer)
  33 {
  34 	GtkUIManager *ui_manager;
  35 	const gchar *path;
  36 	GList *list;
  37 	guint merge_id;
  38 
  39 	ui_manager = gtkhtml_editor_get_ui_manager (GTKHTML_EDITOR (composer));
  40 	path = "/main-menu/options-menu/charset-menu";
  41 	merge_id = gtk_ui_manager_new_merge_id (ui_manager);
  42 
  43 	list = gtk_action_group_list_actions (composer->priv->charset_actions);
  44 	list = g_list_sort (list, (GCompareFunc) e_action_compare_by_label);
  45 
  46 	while (list != NULL) {
  47 		GtkAction *action = list->data;
  48 
  49 		gtk_ui_manager_add_ui (
  50 			ui_manager, merge_id, path,
  51 			gtk_action_get_name (action),
  52 			gtk_action_get_name (action),
  53 			GTK_UI_MANAGER_AUTO, FALSE);
  54 
  55 		list = g_list_delete_link (list, list);
  56 	}
  57 
  58 	gtk_ui_manager_ensure_update (ui_manager);
  59 }
  60 
  61 static void
  62 msg_composer_url_requested_cb (GtkHTML *html,
  63                                const gchar *uri,
  64                                GtkHTMLStream *stream,
  65                                EMsgComposer *composer)
  66 {
  67 	GByteArray *array;
  68 	GHashTable *hash_table;
  69 	CamelDataWrapper *wrapper;
  70 	CamelStream *camel_stream;
  71 	CamelMimePart *mime_part;
  72 
  73 	hash_table = composer->priv->inline_images_by_url;
  74 	mime_part = g_hash_table_lookup (hash_table, uri);
  75 
  76 	if (mime_part == NULL) {
  77 		hash_table = composer->priv->inline_images;
  78 		mime_part = g_hash_table_lookup (hash_table, uri);
  79 	}
  80 
  81 	/* If this is not an inline image request,
  82 	 * allow the signal emission to continue. */
  83 	if (mime_part == NULL)
  84 		return;
  85 
  86 	array = g_byte_array_new ();
  87 	camel_stream = camel_stream_mem_new_with_byte_array (array);
  88 	wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
  89 	camel_data_wrapper_decode_to_stream_sync (
  90 		wrapper, camel_stream, NULL, NULL);
  91 
  92 	gtk_html_write (html, stream, (gchar *) array->data, array->len);
  93 
  94 	gtk_html_end (html, stream, GTK_HTML_STREAM_OK);
  95 
  96 	g_object_unref (camel_stream);
  97 
  98 	/* gtk_html_end() destroys the GtkHTMLStream, so we need to
  99 	 * stop the signal emission so nothing else tries to use it. */
 100 	g_signal_stop_emission_by_name (html, "url-requested");
 101 }
 102 
 103 static void
 104 composer_update_gallery_visibility (EMsgComposer *composer)
 105 {
 106 	GtkhtmlEditor *editor;
 107 	GtkToggleAction *toggle_action;
 108 	gboolean gallery_active;
 109 	gboolean html_mode;
 110 
 111 	editor = GTKHTML_EDITOR (composer);
 112 	html_mode = gtkhtml_editor_get_html_mode (editor);
 113 
 114 	toggle_action = GTK_TOGGLE_ACTION (ACTION (PICTURE_GALLERY));
 115 	gallery_active = gtk_toggle_action_get_active (toggle_action);
 116 
 117 	if (html_mode && gallery_active) {
 118 		gtk_widget_show (composer->priv->gallery_scrolled_window);
 119 		gtk_widget_show (composer->priv->gallery_icon_view);
 120 	} else {
 121 		gtk_widget_hide (composer->priv->gallery_scrolled_window);
 122 		gtk_widget_hide (composer->priv->gallery_icon_view);
 123 	}
 124 }
 125 
 126 static void
 127 composer_spell_languages_changed (EMsgComposer *composer,
 128                                   GList *languages)
 129 {
 130 	EComposerHeader *header;
 131 	EComposerHeaderTable *table = e_msg_composer_get_header_table (composer);
 132 
 133 	header = e_composer_header_table_get_header (table, E_COMPOSER_HEADER_SUBJECT);
 134 	e_composer_spell_header_set_languages (E_COMPOSER_SPELL_HEADER (header), languages);
 135 }
 136 
 137 void
 138 e_composer_private_constructed (EMsgComposer *composer)
 139 {
 140 	EMsgComposerPrivate *priv = composer->priv;
 141 	EFocusTracker *focus_tracker;
 142 	EShell *shell;
 143 	EShellSettings *shell_settings;
 144 	EWebViewGtkHTML *web_view;
 145 	ESourceRegistry *registry;
 146 	GtkhtmlEditor *editor;
 147 	GtkUIManager *ui_manager;
 148 	GtkAction *action;
 149 	GtkWidget *container;
 150 	GtkWidget *widget;
 151 	GtkWidget *send_widget;
 152 	GtkWindow *window;
 153 	const gchar *path;
 154 	gboolean small_screen_mode;
 155 	gchar *filename, *gallery_path;
 156 	gint ii;
 157 	GError *error = NULL;
 158 	EComposerHeader *header;
 159 
 160 	editor = GTKHTML_EDITOR (composer);
 161 	ui_manager = gtkhtml_editor_get_ui_manager (editor);
 162 
 163 	shell = e_msg_composer_get_shell (composer);
 164 	registry = e_shell_get_registry (shell);
 165 	shell_settings = e_shell_get_shell_settings (shell);
 166 	web_view = e_msg_composer_get_web_view (composer);
 167 	small_screen_mode = e_shell_get_small_screen_mode (shell);
 168 
 169 	if (small_screen_mode) {
 170 #if 0
 171 		/* In the lite composer, for small screens, we are not
 172 		 * ready yet to hide the menubar.  It still has useful
 173 		 * items like the ones to show/hide the various header
 174 		 * fields, plus the security options.
 175 		 *
 176 		 * When we move those options out of the menu and into
 177 		 * the composer's toplevel, we can probably get rid of
 178 		 * the menu.
 179 		 */
 180 		widget = gtkhtml_editor_get_managed_widget (editor, "/main-menu");
 181 		gtk_widget_hide (widget);
 182 #endif
 183 		widget = gtkhtml_editor_get_managed_widget (editor, "/main-toolbar");
 184 		gtk_toolbar_set_style (
 185 			GTK_TOOLBAR (widget), GTK_TOOLBAR_BOTH_HORIZ);
 186 		gtk_widget_hide (widget);
 187 
 188 	}
 189 
 190 	/* Each composer window gets its own window group. */
 191 	window = GTK_WINDOW (composer);
 192 	priv->window_group = gtk_window_group_new ();
 193 	gtk_window_group_add_window (priv->window_group, window);
 194 
 195 	priv->async_actions = gtk_action_group_new ("async");
 196 	priv->charset_actions = gtk_action_group_new ("charset");
 197 	priv->composer_actions = gtk_action_group_new ("composer");
 198 
 199 	priv->extra_hdr_names = g_ptr_array_new ();
 200 	priv->extra_hdr_values = g_ptr_array_new ();
 201 
 202 	priv->inline_images = g_hash_table_new_full (
 203 		g_str_hash, g_str_equal,
 204 		(GDestroyNotify) g_free,
 205 		(GDestroyNotify) NULL);
 206 
 207 	priv->inline_images_by_url = g_hash_table_new_full (
 208 		g_str_hash, g_str_equal,
 209 		(GDestroyNotify) g_free,
 210 		(GDestroyNotify) g_object_unref);
 211 
 212 	priv->charset = e_composer_get_default_charset ();
 213 
 214 	priv->is_from_message = FALSE;
 215 
 216 	e_composer_actions_init (composer);
 217 
 218 	filename = e_composer_find_data_file ("evolution-composer.ui");
 219 	gtk_ui_manager_add_ui_from_file (ui_manager, filename, &error);
 220 	g_free (filename);
 221 
 222 	/* We set the send button as important to have a label */
 223 	path = "/main-toolbar/pre-main-toolbar/send";
 224 	send_widget = gtk_ui_manager_get_widget (ui_manager, path);
 225 	gtk_tool_item_set_is_important (GTK_TOOL_ITEM (send_widget), TRUE);
 226 
 227 	composer_setup_charset_menu (composer);
 228 
 229 	if (error != NULL) {
 230 		/* Henceforth, bad things start happening. */
 231 		g_critical ("%s", error->message);
 232 		g_clear_error (&error);
 233 	}
 234 
 235 	/* Configure an EFocusTracker to manage selection actions. */
 236 
 237 	focus_tracker = e_focus_tracker_new (GTK_WINDOW (composer));
 238 
 239 	action = gtkhtml_editor_get_action (editor, "cut");
 240 	e_focus_tracker_set_cut_clipboard_action (focus_tracker, action);
 241 
 242 	action = gtkhtml_editor_get_action (editor, "copy");
 243 	e_focus_tracker_set_copy_clipboard_action (focus_tracker, action);
 244 
 245 	action = gtkhtml_editor_get_action (editor, "paste");
 246 	e_focus_tracker_set_paste_clipboard_action (focus_tracker, action);
 247 
 248 	action = gtkhtml_editor_get_action (editor, "select-all");
 249 	e_focus_tracker_set_select_all_action (focus_tracker, action);
 250 
 251 	priv->focus_tracker = focus_tracker;
 252 
 253 	container = editor->vbox;
 254 
 255 	/* Construct the activity bar. */
 256 
 257 	widget = e_activity_bar_new ();
 258 	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
 259 	priv->activity_bar = g_object_ref (widget);
 260 	/* EActivityBar controls its own visibility. */
 261 
 262 	/* Construct the alert bar for errors. */
 263 
 264 	widget = e_alert_bar_new ();
 265 	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
 266 	priv->alert_bar = g_object_ref (widget);
 267 	/* EAlertBar controls its own visibility. */
 268 
 269 	/* Construct the header table. */
 270 
 271 	widget = e_composer_header_table_new (shell, registry);
 272 	gtk_container_set_border_width (GTK_CONTAINER (widget), 6);
 273 	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
 274 	if (small_screen_mode)
 275 		gtk_box_reorder_child (GTK_BOX (container), widget, 1);
 276 	else
 277 		gtk_box_reorder_child (GTK_BOX (container), widget, 2);
 278 	priv->header_table = g_object_ref (widget);
 279 	gtk_widget_show (widget);
 280 
 281 	header = e_composer_header_table_get_header (
 282 		E_COMPOSER_HEADER_TABLE (widget),
 283 		E_COMPOSER_HEADER_SUBJECT);
 284 	g_object_bind_property (
 285 		shell_settings, "composer-inline-spelling",
 286 		header->input_widget, "checking-enabled",
 287 		G_BINDING_SYNC_CREATE);
 288 
 289 	g_signal_connect (
 290 		G_OBJECT (composer), "spell-languages-changed",
 291 		G_CALLBACK (composer_spell_languages_changed), NULL);
 292 
 293 	/* Construct the attachment paned. */
 294 
 295 	if (small_screen_mode) {
 296 		/* Short attachment bar for Anjal. */
 297 		e_attachment_paned_set_default_height (75);
 298 		e_attachment_icon_view_set_default_icon_size (GTK_ICON_SIZE_BUTTON);
 299 	}
 300 
 301 	widget = e_attachment_paned_new ();
 302 	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
 303 	priv->attachment_paned = g_object_ref (widget);
 304 	gtk_widget_show (widget);
 305 
 306 	g_object_bind_property (
 307 		web_view, "editable",
 308 		widget, "editable",
 309 		G_BINDING_SYNC_CREATE);
 310 
 311 	if (small_screen_mode) {
 312 		GtkWidget *tmp, *tmp1, *tmp_box, *container;
 313 		GtkWidget *combo;
 314 
 315 		combo = e_attachment_paned_get_view_combo (
 316 			E_ATTACHMENT_PANED (widget));
 317 		gtk_widget_hide (combo);
 318 		container = e_attachment_paned_get_controls_container (
 319 			E_ATTACHMENT_PANED (widget));
 320 
 321 		tmp_box = gtk_hbox_new (FALSE, 0);
 322 
 323 		tmp = gtk_hbox_new (FALSE, 0);
 324 		tmp1 = gtk_image_new_from_icon_name (
 325 			"mail-send", GTK_ICON_SIZE_BUTTON);
 326 		gtk_box_pack_start ((GtkBox *) tmp, tmp1, FALSE, FALSE, 0);
 327 		tmp1 = gtk_label_new_with_mnemonic (_("S_end"));
 328 		gtk_box_pack_start ((GtkBox *) tmp, tmp1, FALSE, FALSE, 6);
 329 		gtk_widget_show_all (tmp);
 330 		gtk_widget_reparent (send_widget, tmp_box);
 331 		gtk_box_set_child_packing (
 332 			GTK_BOX (tmp_box), send_widget,
 333 			FALSE, FALSE, 6, GTK_PACK_END);
 334 		gtk_tool_item_set_is_important (GTK_TOOL_ITEM (send_widget), TRUE);
 335 		send_widget = gtk_bin_get_child ((GtkBin *) send_widget);
 336 		gtk_container_remove (
 337 			GTK_CONTAINER (send_widget),
 338 			gtk_bin_get_child (GTK_BIN (send_widget)));
 339 		gtk_container_add ((GtkContainer *) send_widget, tmp);
 340 		gtk_button_set_relief ((GtkButton *) send_widget, GTK_RELIEF_NORMAL);
 341 		path = "/main-toolbar/pre-main-toolbar/save-draft";
 342 		send_widget = gtk_ui_manager_get_widget (ui_manager, path);
 343 		tmp = gtk_hbox_new (FALSE, 0);
 344 		tmp1 = gtk_image_new_from_stock (
 345 			GTK_STOCK_SAVE, GTK_ICON_SIZE_BUTTON);
 346 		gtk_box_pack_start ((GtkBox *) tmp, tmp1, FALSE, FALSE, 0);
 347 		tmp1 = gtk_label_new_with_mnemonic (_("Save draft"));
 348 		gtk_box_pack_start ((GtkBox *) tmp, tmp1, FALSE, FALSE, 3);
 349 		gtk_widget_show_all (tmp);
 350 		gtk_widget_reparent (send_widget, tmp_box);
 351 		gtk_box_set_child_packing (
 352 			GTK_BOX (tmp_box), send_widget,
 353 			FALSE, FALSE, 6, GTK_PACK_END);
 354 		gtk_tool_item_set_is_important (GTK_TOOL_ITEM (send_widget), TRUE);
 355 		send_widget = gtk_bin_get_child ((GtkBin *) send_widget);
 356 		gtk_container_remove (
 357 			GTK_CONTAINER (send_widget),
 358 			gtk_bin_get_child (GTK_BIN (send_widget)));
 359 		gtk_container_add ((GtkContainer *) send_widget, tmp);
 360 		gtk_button_set_relief ((GtkButton *) send_widget, GTK_RELIEF_NORMAL);
 361 
 362 		gtk_widget_show (tmp_box);
 363 		gtk_box_pack_end (GTK_BOX (container), tmp_box, FALSE, FALSE, 3);
 364 	}
 365 
 366 	container = e_attachment_paned_get_content_area (
 367 		E_ATTACHMENT_PANED (priv->attachment_paned));
 368 
 369 	widget = gtk_vpaned_new ();
 370 	gtk_container_add (GTK_CONTAINER (container), widget);
 371 	gtk_widget_show (widget);
 372 
 373 	container = widget;
 374 
 375 	widget = gtk_scrolled_window_new (NULL, NULL);
 376 	gtk_scrolled_window_set_policy (
 377 		GTK_SCROLLED_WINDOW (widget),
 378 		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
 379 	gtk_scrolled_window_set_shadow_type (
 380 		GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
 381 	gtk_widget_set_size_request (widget, -1, GALLERY_INITIAL_HEIGHT);
 382 	gtk_paned_pack1 (GTK_PANED (container), widget, FALSE, FALSE);
 383 	priv->gallery_scrolled_window = g_object_ref (widget);
 384 	gtk_widget_show (widget);
 385 
 386 	/* Reparent the scrolled window containing the GtkHTML widget
 387 	 * into the content area of the top attachment pane. */
 388 
 389 	widget = GTK_WIDGET (web_view);
 390 	widget = gtk_widget_get_parent (widget);
 391 	gtk_widget_reparent (widget, container);
 392 
 393 	/* Construct the picture gallery. */
 394 
 395 	container = priv->gallery_scrolled_window;
 396 
 397 	gallery_path = e_shell_settings_get_string (
 398 		shell_settings, "composer-gallery-path");
 399 	widget = e_picture_gallery_new (gallery_path);
 400 	gtk_container_add (GTK_CONTAINER (container), widget);
 401 	priv->gallery_icon_view = g_object_ref (widget);
 402 	g_free (gallery_path);
 403 
 404 	g_signal_connect (
 405 		composer, "notify::html-mode",
 406 		G_CALLBACK (composer_update_gallery_visibility), NULL);
 407 
 408 	g_signal_connect_swapped (
 409 		ACTION (PICTURE_GALLERY), "toggled",
 410 		G_CALLBACK (composer_update_gallery_visibility), composer);
 411 
 412 	/* XXX What is this for? */
 413 	g_object_set_data (G_OBJECT (composer), "vbox", editor->vbox);
 414 
 415 	/* Bind headers to their corresponding actions. */
 416 
 417 	for (ii = 0; ii < E_COMPOSER_NUM_HEADERS; ii++) {
 418 		EComposerHeaderTable *table;
 419 		EComposerHeader *header;
 420 		GtkAction *action;
 421 
 422 		table = E_COMPOSER_HEADER_TABLE (priv->header_table);
 423 		header = e_composer_header_table_get_header (table, ii);
 424 
 425 		switch (ii) {
 426 			case E_COMPOSER_HEADER_BCC:
 427 				action = ACTION (VIEW_BCC);
 428 				break;
 429 
 430 			case E_COMPOSER_HEADER_CC:
 431 				action = ACTION (VIEW_CC);
 432 				break;
 433 
 434 			case E_COMPOSER_HEADER_REPLY_TO:
 435 				action = ACTION (VIEW_REPLY_TO);
 436 				break;
 437 
 438 			default:
 439 				continue;
 440 		}
 441 
 442 		g_object_bind_property (
 443 			header, "sensitive",
 444 			action, "sensitive",
 445 			G_BINDING_BIDIRECTIONAL |
 446 			G_BINDING_SYNC_CREATE);
 447 
 448 		g_object_bind_property (
 449 			header, "visible",
 450 			action, "active",
 451 			G_BINDING_BIDIRECTIONAL |
 452 			G_BINDING_SYNC_CREATE);
 453 	}
 454 
 455 	/* Install a handler for inline images. */
 456 
 457 	/* XXX We no longer use GtkhtmlEditor::uri-requested because it
 458 	 *     conflicts with EWebView's url_requested() method, which
 459 	 *     unconditionally launches an async operation.  I changed
 460 	 *     GtkHTML::url-requested to be a G_SIGNAL_RUN_LAST so that
 461 	 *     our handler runs first.  If we can handle the request
 462 	 *     we'll stop the signal emission to prevent EWebView from
 463 	 *     launching an async operation.  Messy, but works until we
 464 	 *     switch to WebKit.  --mbarnes */
 465 
 466 	g_signal_connect (
 467 		web_view, "url-requested",
 468 		G_CALLBACK (msg_composer_url_requested_cb), composer);
 469 }
 470 
 471 void
 472 e_composer_private_dispose (EMsgComposer *composer)
 473 {
 474 	if (composer->priv->shell != NULL) {
 475 		g_object_remove_weak_pointer (
 476 			G_OBJECT (composer->priv->shell),
 477 			&composer->priv->shell);
 478 		composer->priv->shell = NULL;
 479 	}
 480 
 481 	if (composer->priv->header_table != NULL) {
 482 		g_object_unref (composer->priv->header_table);
 483 		composer->priv->header_table = NULL;
 484 	}
 485 
 486 	if (composer->priv->activity_bar != NULL) {
 487 		g_object_unref (composer->priv->activity_bar);
 488 		composer->priv->activity_bar = NULL;
 489 	}
 490 
 491 	if (composer->priv->alert_bar != NULL) {
 492 		g_object_unref (composer->priv->alert_bar);
 493 		composer->priv->alert_bar = NULL;
 494 	}
 495 
 496 	if (composer->priv->attachment_paned != NULL) {
 497 		g_object_unref (composer->priv->attachment_paned);
 498 		composer->priv->attachment_paned = NULL;
 499 	}
 500 
 501 	if (composer->priv->focus_tracker != NULL) {
 502 		g_object_unref (composer->priv->focus_tracker);
 503 		composer->priv->focus_tracker = NULL;
 504 	}
 505 
 506 	if (composer->priv->window_group != NULL) {
 507 		g_object_unref (composer->priv->window_group);
 508 		composer->priv->window_group = NULL;
 509 	}
 510 
 511 	if (composer->priv->async_actions != NULL) {
 512 		g_object_unref (composer->priv->async_actions);
 513 		composer->priv->async_actions = NULL;
 514 	}
 515 
 516 	if (composer->priv->charset_actions != NULL) {
 517 		g_object_unref (composer->priv->charset_actions);
 518 		composer->priv->charset_actions = NULL;
 519 	}
 520 
 521 	if (composer->priv->composer_actions != NULL) {
 522 		g_object_unref (composer->priv->composer_actions);
 523 		composer->priv->composer_actions = NULL;
 524 	}
 525 
 526 	if (composer->priv->gallery_scrolled_window != NULL) {
 527 		g_object_unref (composer->priv->gallery_scrolled_window);
 528 		composer->priv->gallery_scrolled_window = NULL;
 529 	}
 530 
 531 	g_hash_table_remove_all (composer->priv->inline_images);
 532 	g_hash_table_remove_all (composer->priv->inline_images_by_url);
 533 
 534 	if (composer->priv->redirect != NULL) {
 535 		g_object_unref (composer->priv->redirect);
 536 		composer->priv->redirect = NULL;
 537 	}
 538 }
 539 
 540 void
 541 e_composer_private_finalize (EMsgComposer *composer)
 542 {
 543 	GPtrArray *array;
 544 
 545 	array = composer->priv->extra_hdr_names;
 546 	g_ptr_array_foreach (array, (GFunc) g_free, NULL);
 547 	g_ptr_array_free (array, TRUE);
 548 
 549 	array = composer->priv->extra_hdr_values;
 550 	g_ptr_array_foreach (array, (GFunc) g_free, NULL);
 551 	g_ptr_array_free (array, TRUE);
 552 
 553 	g_free (composer->priv->charset);
 554 	g_free (composer->priv->mime_type);
 555 	g_free (composer->priv->mime_body);
 556 
 557 	g_hash_table_destroy (composer->priv->inline_images);
 558 	g_hash_table_destroy (composer->priv->inline_images_by_url);
 559 }
 560 
 561 gchar *
 562 e_composer_find_data_file (const gchar *basename)
 563 {
 564 	gchar *filename;
 565 
 566 	g_return_val_if_fail (basename != NULL, NULL);
 567 
 568 	/* Support running directly from the source tree. */
 569 	filename = g_build_filename (".", basename, NULL);
 570 	if (g_file_test (filename, G_FILE_TEST_EXISTS))
 571 		return filename;
 572 	g_free (filename);
 573 
 574 	/* XXX This is kinda broken. */
 575 	filename = g_build_filename (EVOLUTION_UIDIR, basename, NULL);
 576 	if (g_file_test (filename, G_FILE_TEST_EXISTS))
 577 		return filename;
 578 	g_free (filename);
 579 
 580 	g_critical ("Could not locate '%s'", basename);
 581 
 582 	return NULL;
 583 }
 584 
 585 gchar *
 586 e_composer_get_default_charset (void)
 587 {
 588 	GSettings *settings;
 589 	gchar *charset;
 590 
 591 	settings = g_settings_new ("org.gnome.evolution.mail");
 592 
 593 	charset = g_settings_get_string (settings, "composer-charset");
 594 
 595 	/* See what charset the mailer is using.
 596 	 * XXX We should not have to know where this lives in GSettings.
 597 	 *     Need a mail_config_get_default_charset() that does this. */
 598 	if (!charset || charset[0] == '\0') {
 599 		g_free (charset);
 600 		charset = g_settings_get_string (settings, "charset");
 601 		if (charset != NULL && *charset == '\0') {
 602 			g_free (charset);
 603 			charset = NULL;
 604 		}
 605 	}
 606 
 607 	g_object_unref (settings);
 608 
 609 	if (charset == NULL)
 610 		charset = g_strdup (camel_iconv_locale_charset ());
 611 
 612 	if (charset == NULL)
 613 		charset = g_strdup ("us-ascii");
 614 
 615 	return charset;
 616 }
 617 
 618 gchar *
 619 e_composer_decode_clue_value (const gchar *encoded_value)
 620 {
 621 	GString *buffer;
 622 	const gchar *cp;
 623 
 624 	/* Decode a GtkHtml "ClueFlow" value. */
 625 
 626 	g_return_val_if_fail (encoded_value != NULL, NULL);
 627 
 628 	buffer = g_string_sized_new (strlen (encoded_value));
 629 
 630 	/* Copy the value, decoding escaped characters as we go. */
 631 	cp = encoded_value;
 632 	while (*cp != '\0') {
 633 		if (*cp == '.') {
 634 			cp++;
 635 			switch (*cp) {
 636 				case '.':
 637 					g_string_append_c (buffer, '.');
 638 					break;
 639 				case '1':
 640 					g_string_append_c (buffer, '"');
 641 					break;
 642 				case '2':
 643 					g_string_append_c (buffer, '=');
 644 					break;
 645 				default:
 646 					/* Invalid escape sequence. */
 647 					g_string_free (buffer, TRUE);
 648 					return NULL;
 649 			}
 650 		} else
 651 			g_string_append_c (buffer, *cp);
 652 		cp++;
 653 	}
 654 
 655 	return g_string_free (buffer, FALSE);
 656 }
 657 
 658 gchar *
 659 e_composer_encode_clue_value (const gchar *decoded_value)
 660 {
 661 	gchar *encoded_value;
 662 	gchar **strv;
 663 
 664 	/* Encode a GtkHtml "ClueFlow" value. */
 665 
 666 	g_return_val_if_fail (decoded_value != NULL, NULL);
 667 
 668 	/* XXX This is inefficient but easy to understand. */
 669 
 670 	encoded_value = g_strdup (decoded_value);
 671 
 672 	/* Substitution: '.' --> '..' (do this first) */
 673 	if (strchr (encoded_value, '.') != NULL) {
 674 		strv = g_strsplit (encoded_value, ".", 0);
 675 		g_free (encoded_value);
 676 		encoded_value = g_strjoinv ("..", strv);
 677 		g_strfreev (strv);
 678 	}
 679 
 680 	/* Substitution: '"' --> '.1' */
 681 	if (strchr (encoded_value, '"') != NULL) {
 682 		strv = g_strsplit (encoded_value, """", 0);
 683 		g_free (encoded_value);
 684 		encoded_value = g_strjoinv (".1", strv);
 685 		g_strfreev (strv);
 686 	}
 687 
 688 	/* Substitution: '=' --> '.2' */
 689 	if (strchr (encoded_value, '=') != NULL) {
 690 		strv = g_strsplit (encoded_value, "=", 0);
 691 		g_free (encoded_value);
 692 		encoded_value = g_strjoinv (".2", strv);
 693 		g_strfreev (strv);
 694 	}
 695 
 696 	return encoded_value;
 697 }
 698 
 699 gboolean
 700 e_composer_paste_html (EMsgComposer *composer,
 701                        GtkClipboard *clipboard)
 702 {
 703 	GtkhtmlEditor *editor;
 704 	gchar *html;
 705 
 706 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
 707 	g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);
 708 
 709 	html = e_clipboard_wait_for_html (clipboard);
 710 	g_return_val_if_fail (html != NULL, FALSE);
 711 
 712 	editor = GTKHTML_EDITOR (composer);
 713 	gtkhtml_editor_insert_html (editor, html);
 714 
 715 	g_free (html);
 716 
 717 	return TRUE;
 718 }
 719 
 720 gboolean
 721 e_composer_paste_image (EMsgComposer *composer,
 722                         GtkClipboard *clipboard)
 723 {
 724 	GtkhtmlEditor *editor;
 725 	EAttachmentStore *store;
 726 	EAttachmentView *view;
 727 	GdkPixbuf *pixbuf = NULL;
 728 	gchar *filename = NULL;
 729 	gchar *uri = NULL;
 730 	gboolean success = FALSE;
 731 	GError *error = NULL;
 732 
 733 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
 734 	g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);
 735 
 736 	editor = GTKHTML_EDITOR (composer);
 737 	view = e_msg_composer_get_attachment_view (composer);
 738 	store = e_attachment_view_get_store (view);
 739 
 740 	/* Extract the image data from the clipboard. */
 741 	pixbuf = gtk_clipboard_wait_for_image (clipboard);
 742 	g_return_val_if_fail (pixbuf != NULL, FALSE);
 743 
 744 	/* Reserve a temporary file. */
 745 	filename = e_mktemp (NULL);
 746 	if (filename == NULL) {
 747 		g_set_error (
 748 			&error, G_FILE_ERROR,
 749 			g_file_error_from_errno (errno),
 750 			"Could not create temporary file: %s",
 751 			g_strerror (errno));
 752 		goto exit;
 753 	}
 754 
 755 	/* Save the pixbuf as a temporary file in image/png format. */
 756 	if (!gdk_pixbuf_save (pixbuf, filename, "png", &error, NULL))
 757 		goto exit;
 758 
 759 	/* Convert the filename to a URI. */
 760 	uri = g_filename_to_uri (filename, NULL, &error);
 761 	if (uri == NULL)
 762 		goto exit;
 763 
 764 	/* In HTML mode, paste the image into the message body.
 765 	 * In text mode, add the image to the attachment store. */
 766 	if (gtkhtml_editor_get_html_mode (editor))
 767 		gtkhtml_editor_insert_image (editor, uri);
 768 	else {
 769 		EAttachment *attachment;
 770 
 771 		attachment = e_attachment_new_for_uri (uri);
 772 		e_attachment_store_add_attachment (store, attachment);
 773 		e_attachment_load_async (
 774 			attachment, (GAsyncReadyCallback)
 775 			e_attachment_load_handle_error, composer);
 776 		g_object_unref (attachment);
 777 	}
 778 
 779 	success = TRUE;
 780 
 781 exit:
 782 	if (error != NULL) {
 783 		g_warning ("%s", error->message);
 784 		g_error_free (error);
 785 	}
 786 
 787 	g_object_unref (pixbuf);
 788 	g_free (filename);
 789 	g_free (uri);
 790 
 791 	return success;
 792 }
 793 
 794 gboolean
 795 e_composer_paste_text (EMsgComposer *composer,
 796                        GtkClipboard *clipboard)
 797 {
 798 	GtkhtmlEditor *editor;
 799 	gchar *text;
 800 
 801 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
 802 	g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);
 803 
 804 	text = gtk_clipboard_wait_for_text (clipboard);
 805 	g_return_val_if_fail (text != NULL, FALSE);
 806 
 807 	editor = GTKHTML_EDITOR (composer);
 808 	gtkhtml_editor_insert_text (editor, text);
 809 
 810 	g_free (text);
 811 
 812 	return TRUE;
 813 }
 814 
 815 gboolean
 816 e_composer_paste_uris (EMsgComposer *composer,
 817                        GtkClipboard *clipboard)
 818 {
 819 	EAttachmentStore *store;
 820 	EAttachmentView *view;
 821 	gchar **uris;
 822 	gint ii;
 823 
 824 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
 825 	g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);
 826 
 827 	view = e_msg_composer_get_attachment_view (composer);
 828 	store = e_attachment_view_get_store (view);
 829 
 830 	/* Extract the URI data from the clipboard. */
 831 	uris = gtk_clipboard_wait_for_uris (clipboard);
 832 	g_return_val_if_fail (uris != NULL, FALSE);
 833 
 834 	/* Add the URIs to the attachment store. */
 835 	for (ii = 0; uris[ii] != NULL; ii++) {
 836 		EAttachment *attachment;
 837 
 838 		attachment = e_attachment_new_for_uri (uris[ii]);
 839 		e_attachment_store_add_attachment (store, attachment);
 840 		e_attachment_load_async (
 841 			attachment, (GAsyncReadyCallback)
 842 			e_attachment_load_handle_error, composer);
 843 		g_object_unref (attachment);
 844 	}
 845 
 846 	return TRUE;
 847 }
 848 
 849 gboolean
 850 e_composer_selection_is_image_uris (EMsgComposer *composer,
 851                                     GtkSelectionData *selection)
 852 {
 853 	gboolean all_image_uris = TRUE;
 854 	gchar **uris;
 855 	guint ii;
 856 
 857 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
 858 	g_return_val_if_fail (selection != NULL, FALSE);
 859 
 860 	uris = gtk_selection_data_get_uris (selection);
 861 
 862 	if (uris == NULL)
 863 		return FALSE;
 864 
 865 	for (ii = 0; uris[ii] != NULL; ii++) {
 866 		GFile *file;
 867 		GFileInfo *file_info;
 868 		GdkPixbufLoader *loader;
 869 		const gchar *attribute;
 870 		const gchar *content_type;
 871 		gchar *mime_type = NULL;
 872 
 873 		file = g_file_new_for_uri (uris[ii]);
 874 		attribute = G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE;
 875 
 876 		/* XXX This blocks, but we're requesting the fast content
 877 		 *     type (which only inspects filenames), so hopefully
 878 		 *     it won't be noticeable.  Also, this is best effort
 879 		 *     so we don't really care if it fails. */
 880 		file_info = g_file_query_info (
 881 			file, attribute, G_FILE_QUERY_INFO_NONE, NULL, NULL);
 882 
 883 		if (file_info == NULL) {
 884 			g_object_unref (file);
 885 			all_image_uris = FALSE;
 886 			break;
 887 		}
 888 
 889 		content_type = g_file_info_get_attribute_string (
 890 			file_info, attribute);
 891 		mime_type = g_content_type_get_mime_type (content_type);
 892 
 893 		g_object_unref (file_info);
 894 		g_object_unref (file);
 895 
 896 		if (mime_type == NULL) {
 897 			all_image_uris = FALSE;
 898 			break;
 899 		}
 900 
 901 		/* Easy way to determine if a MIME type is a supported
 902 		 * image format: try creating a GdkPixbufLoader for it. */
 903 		loader = gdk_pixbuf_loader_new_with_mime_type (mime_type, NULL);
 904 
 905 		g_free (mime_type);
 906 
 907 		if (loader == NULL) {
 908 			all_image_uris = FALSE;
 909 			break;
 910 		}
 911 
 912 		gdk_pixbuf_loader_close (loader, NULL);
 913 		g_object_unref (loader);
 914 	}
 915 
 916 	g_strfreev (uris);
 917 
 918 	return all_image_uris;
 919 }
 920 
 921 static gboolean
 922 add_signature_delimiter (EMsgComposer *composer)
 923 {
 924 	EShell *shell;
 925 	EShellSettings *shell_settings;
 926 
 927 	/* FIXME This preference should be an EMsgComposer property. */
 928 
 929 	shell = e_msg_composer_get_shell (composer);
 930 	shell_settings = e_shell_get_shell_settings (shell);
 931 
 932 	return !e_shell_settings_get_boolean (
 933 		shell_settings, "composer-no-signature-delim");
 934 }
 935 
 936 static gboolean
 937 use_top_signature (EMsgComposer *composer)
 938 {
 939 	EShell *shell;
 940 	EShellSettings *shell_settings;
 941 
 942 	/* FIXME This preference should be an EMsgComposer property. */
 943 
 944 	shell = e_msg_composer_get_shell (composer);
 945 	shell_settings = e_shell_get_shell_settings (shell);
 946 
 947 	return e_shell_settings_get_boolean (
 948 		shell_settings, "composer-top-signature");
 949 }
 950 
 951 static void
 952 composer_load_signature_cb (EMailSignatureComboBox *combo_box,
 953                             GAsyncResult *result,
 954                             EMsgComposer *composer)
 955 {
 956 	GString *html_buffer = NULL;
 957 	GtkhtmlEditor *editor;
 958 	gchar *contents = NULL;
 959 	gsize length = 0;
 960 	const gchar *active_id;
 961 	gchar *encoded_uid = NULL;
 962 	gboolean top_signature;
 963 	gboolean is_html;
 964 	GError *error = NULL;
 965 
 966 	e_mail_signature_combo_box_load_selected_finish (
 967 		combo_box, result, &contents, &length, &is_html, &error);
 968 
 969 	/* FIXME Use an EAlert here. */
 970 	if (error != NULL) {
 971 		g_warning ("%s: %s", G_STRFUNC, error->message);
 972 		g_error_free (error);
 973 		goto exit;
 974 	}
 975 
 976 	/* "Edit as New Message" sets "priv->is_from_message".
 977 	 * Always put the signature at the bottom for that case. */
 978 	top_signature =
 979 		use_top_signature (composer) &&
 980 		!composer->priv->is_from_message;
 981 
 982 	if (contents == NULL)
 983 		goto insert;
 984 
 985 	if (!is_html) {
 986 		gchar *html;
 987 
 988 		html = camel_text_to_html (contents, 0, 0);
 989 		if (html) {
 990 			g_free (contents);
 991 
 992 			contents = html;
 993 			length = strlen (contents);
 994 		}
 995 	}
 996 
 997 	/* Generate HTML code for the signature. */
 998 
 999 	html_buffer = g_string_sized_new (1024);
1000 
1001 	/* The combo box active ID is the signature's ESource UID. */
1002 	active_id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box));
1003 
1004 	if (active_id != NULL && *active_id != '\0')
1005 		encoded_uid = e_composer_encode_clue_value (active_id);
1006 
1007 	g_string_append_printf (
1008 		html_buffer,
1009 		"<!--+GtkHTML:<DATA class=\"ClueFlow\" "
1010 		"    key=\"signature\" value=\"1\">-->"
1011 		"<!--+GtkHTML:<DATA class=\"ClueFlow\" "
1012 		"    key=\"signature_name\" value=\"uid:%s\">-->",
1013 		(encoded_uid != NULL) ? encoded_uid : "");
1014 
1015 	g_string_append (
1016 		html_buffer,
1017 		"<TABLE WIDTH=\"100%%\" CELLSPACING=\"0\""
1018 		" CELLPADDING=\"0\"><TR><TD>");
1019 
1020 	if (!is_html)
1021 		g_string_append (html_buffer, "<PRE>\n");
1022 
1023 	/* The signature dash convention ("-- \n") is specified
1024 	 * in the "Son of RFC 1036", section 4.3.2.
1025 	 * http://www.chemie.fu-berlin.de/outerspace/netnews/son-of-1036.html
1026 	 */
1027 	if (add_signature_delimiter (composer)) {
1028 		const gchar *delim;
1029 		const gchar *delim_nl;
1030 
1031 		if (is_html) {
1032 			delim = "-- \n<BR>";
1033 			delim_nl = "\n-- \n<BR>";
1034 		} else {
1035 			delim = "-- \n";
1036 			delim_nl = "\n-- \n";
1037 		}
1038 
1039 		/* Skip the delimiter if the signature already has one. */
1040 		if (g_ascii_strncasecmp (contents, delim, strlen (delim)) == 0)
1041 			;  /* skip */
1042 		else if (e_util_strstrcase (contents, delim_nl) != NULL)
1043 			;  /* skip */
1044 		else
1045 			g_string_append (html_buffer, delim);
1046 	}
1047 
1048 	g_string_append_len (html_buffer, contents, length);
1049 
1050 	if (!is_html)
1051 		g_string_append (html_buffer, "</PRE>\n");
1052 
1053 	if (top_signature)
1054 		g_string_append (html_buffer, "<BR>");
1055 
1056 	g_string_append (html_buffer, "</TD></TR></TABLE>");
1057 
1058 	g_free (encoded_uid);
1059 	g_free (contents);
1060 
1061 insert:
1062 	/* Remove the old signature and insert the new one. */
1063 
1064 	editor = GTKHTML_EDITOR (composer);
1065 
1066 	/* This prevents our command before/after callbacks from
1067 	 * screwing around with the signature as we insert it. */
1068 	composer->priv->in_signature_insert = TRUE;
1069 
1070 	gtkhtml_editor_freeze (editor);
1071 	gtkhtml_editor_run_command (editor, "cursor-position-save");
1072 	gtkhtml_editor_undo_begin (editor, "Set signature", "Reset signature");
1073 
1074 	gtkhtml_editor_run_command (editor, "block-selection");
1075 	gtkhtml_editor_run_command (editor, "cursor-bod");
1076 	if (gtkhtml_editor_search_by_data (editor, 1, "ClueFlow", "signature", "1")) {
1077 		gtkhtml_editor_run_command (editor, "select-paragraph");
1078 		gtkhtml_editor_run_command (editor, "delete");
1079 		gtkhtml_editor_set_paragraph_data (editor, "signature", "0");
1080 		gtkhtml_editor_run_command (editor, "delete-back");
1081 	}
1082 	gtkhtml_editor_run_command (editor, "unblock-selection");
1083 
1084 	if (html_buffer != NULL) {
1085 		gtkhtml_editor_run_command (editor, "insert-paragraph");
1086 		if (!gtkhtml_editor_run_command (editor, "cursor-backward"))
1087 			gtkhtml_editor_run_command (editor, "insert-paragraph");
1088 		else
1089 			gtkhtml_editor_run_command (editor, "cursor-forward");
1090 
1091 		gtkhtml_editor_set_paragraph_data (editor, "orig", "0");
1092 		gtkhtml_editor_run_command (editor, "indent-zero");
1093 		gtkhtml_editor_run_command (editor, "style-normal");
1094 		gtkhtml_editor_insert_html (editor, html_buffer->str);
1095 
1096 		g_string_free (html_buffer, TRUE);
1097 
1098 	} else if (top_signature) {
1099 		/* Insert paragraph after the signature ClueFlow stuff. */
1100 		if (gtkhtml_editor_run_command (editor, "cursor-forward"))
1101 			gtkhtml_editor_run_command (editor, "insert-paragraph");
1102 	}
1103 
1104 	gtkhtml_editor_undo_end (editor);
1105 	gtkhtml_editor_run_command (editor, "cursor-position-restore");
1106 	gtkhtml_editor_thaw (editor);
1107 
1108 	composer->priv->in_signature_insert = FALSE;
1109 
1110 exit:
1111 	g_object_unref (composer);
1112 }
1113 
1114 void
1115 e_composer_update_signature (EMsgComposer *composer)
1116 {
1117 	EComposerHeaderTable *table;
1118 	EMailSignatureComboBox *combo_box;
1119 
1120 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
1121 
1122 	/* Do nothing if we're redirecting a message. */
1123 	if (composer->priv->redirect)
1124 		return;
1125 
1126 	table = e_msg_composer_get_header_table (composer);
1127 	combo_box = e_composer_header_table_get_signature_combo_box (table);
1128 
1129 	/* XXX Signature files should be local and therefore load quickly,
1130 	 *     so while we do load them asynchronously we don't allow for
1131 	 *     user cancellation and we keep the composer alive until the
1132 	 *     asynchronous loading is complete. */
1133 	e_mail_signature_combo_box_load_selected (
1134 		combo_box, G_PRIORITY_DEFAULT, NULL,
1135 		(GAsyncReadyCallback) composer_load_signature_cb,
1136 		g_object_ref (composer));
1137 }