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 }