update team list
[claws.git] / src / compose.c
blob7abba6b41dd3b1e038b41dc6686fd6e8e3589a86
1 /*
2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2021 the Claws Mail team and Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #include "claws-features.h"
22 #endif
24 #include "defs.h"
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
28 #endif
30 #include <glib.h>
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtk.h>
35 #include <pango/pango-break.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <ctype.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <unistd.h>
43 #include <time.h>
44 #include <stdlib.h>
45 #if HAVE_SYS_WAIT_H
46 # include <sys/wait.h>
47 #endif
48 #include <signal.h>
49 #include <errno.h>
50 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
51 #include <libgen.h>
52 #endif
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
55 # include <wchar.h>
56 # include <wctype.h>
57 #endif
59 #include "claws.h"
60 #include "main.h"
61 #include "mainwindow.h"
62 #include "compose.h"
63 #ifndef USE_ALT_ADDRBOOK
64 #include "addressbook.h"
65 #else
66 #include "addressbook-dbus.h"
67 #include "addressadd.h"
68 #endif
69 #include "folderview.h"
70 #include "procmsg.h"
71 #include "menu.h"
72 #include "stock_pixmap.h"
73 #include "send_message.h"
74 #include "imap.h"
75 #include "news.h"
76 #include "customheader.h"
77 #include "prefs_common.h"
78 #include "prefs_account.h"
79 #include "action.h"
80 #include "account.h"
81 #include "filesel.h"
82 #include "procheader.h"
83 #include "procmime.h"
84 #include "statusbar.h"
85 #include "about.h"
86 #include "quoted-printable.h"
87 #include "codeconv.h"
88 #include "utils.h"
89 #include "gtkutils.h"
90 #include "gtkshruler.h"
91 #include "socket.h"
92 #include "alertpanel.h"
93 #include "manage_window.h"
94 #include "folder.h"
95 #include "folder_item_prefs.h"
96 #include "addr_compl.h"
97 #include "quote_fmt.h"
98 #include "undo.h"
99 #include "foldersel.h"
100 #include "toolbar.h"
101 #include "inc.h"
102 #include "message_search.h"
103 #include "combobox.h"
104 #include "hooks.h"
105 #include "privacy.h"
106 #include "timing.h"
107 #include "autofaces.h"
108 #include "spell_entry.h"
109 #include "headers.h"
110 #include "file-utils.h"
112 #ifdef USE_LDAP
113 #include "password.h"
114 #include "ldapserver.h"
115 #endif
117 enum
119 COL_MIMETYPE = 0,
120 COL_SIZE = 1,
121 COL_NAME = 2,
122 COL_CHARSET = 3,
123 COL_DATA = 4,
124 COL_AUTODATA = 5,
125 N_COL_COLUMNS
128 #define N_ATTACH_COLS (N_COL_COLUMNS)
130 typedef enum
132 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
135 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
137 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
139 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
141 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
142 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
143 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
144 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
145 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
146 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
147 } ComposeCallAdvancedAction;
149 typedef enum
151 PRIORITY_HIGHEST = 1,
152 PRIORITY_HIGH,
153 PRIORITY_NORMAL,
154 PRIORITY_LOW,
155 PRIORITY_LOWEST
156 } PriorityLevel;
158 typedef enum
160 COMPOSE_INSERT_SUCCESS,
161 COMPOSE_INSERT_READ_ERROR,
162 COMPOSE_INSERT_INVALID_CHARACTER,
163 COMPOSE_INSERT_NO_FILE
164 } ComposeInsertResult;
166 typedef enum
168 COMPOSE_WRITE_FOR_SEND,
169 COMPOSE_WRITE_FOR_STORE
170 } ComposeWriteType;
172 typedef enum
174 COMPOSE_QUOTE_FORCED,
175 COMPOSE_QUOTE_CHECK,
176 COMPOSE_QUOTE_SKIP
177 } ComposeQuoteMode;
179 typedef enum {
180 TO_FIELD_PRESENT,
181 SUBJECT_FIELD_PRESENT,
182 BODY_FIELD_PRESENT,
183 NO_FIELD_PRESENT
184 } MailField;
186 #define B64_LINE_SIZE 57
187 #define B64_BUFFSIZE 77
189 #define MAX_REFERENCES_LEN 999
191 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
192 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
194 #define COMPOSE_PRIVACY_WARNING() { \
195 alertpanel_error(_("You have opted to sign and/or encrypt this " \
196 "message but have not selected a privacy system.\n\n" \
197 "Signing and encrypting have been disabled for this " \
198 "message.")); \
201 static GdkColor default_header_bgcolor = {
202 (gulong)0,
203 (gushort)0,
204 (gushort)0,
205 (gushort)0
208 static GdkColor default_header_color = {
209 (gulong)0,
210 (gushort)0,
211 (gushort)0,
212 (gushort)0
215 static GList *compose_list = NULL;
216 static GSList *extra_headers = NULL;
218 static Compose *compose_generic_new (PrefsAccount *account,
219 const gchar *to,
220 FolderItem *item,
221 GList *attach_files,
222 GList *listAddress );
224 static Compose *compose_create (PrefsAccount *account,
225 FolderItem *item,
226 ComposeMode mode,
227 gboolean batch);
229 static void compose_entry_indicate (Compose *compose,
230 const gchar *address);
231 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
232 ComposeQuoteMode quote_mode,
233 gboolean to_all,
234 gboolean to_sender,
235 const gchar *body);
236 static Compose *compose_forward_multiple (PrefsAccount *account,
237 GSList *msginfo_list);
238 static Compose *compose_reply (MsgInfo *msginfo,
239 ComposeQuoteMode quote_mode,
240 gboolean to_all,
241 gboolean to_ml,
242 gboolean to_sender,
243 const gchar *body);
244 static Compose *compose_reply_mode (ComposeMode mode,
245 GSList *msginfo_list,
246 gchar *body);
247 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
248 static void compose_update_privacy_systems_menu(Compose *compose);
250 static GtkWidget *compose_account_option_menu_create
251 (Compose *compose);
252 static void compose_set_out_encoding (Compose *compose);
253 static void compose_set_template_menu (Compose *compose);
254 static void compose_destroy (Compose *compose);
256 static MailField compose_entries_set (Compose *compose,
257 const gchar *mailto,
258 ComposeEntryType to_type);
259 static gint compose_parse_header (Compose *compose,
260 MsgInfo *msginfo);
261 static gint compose_parse_manual_headers (Compose *compose,
262 MsgInfo *msginfo,
263 HeaderEntry *entries);
264 static gchar *compose_parse_references (const gchar *ref,
265 const gchar *msgid);
267 static gchar *compose_quote_fmt (Compose *compose,
268 MsgInfo *msginfo,
269 const gchar *fmt,
270 const gchar *qmark,
271 const gchar *body,
272 gboolean rewrap,
273 gboolean need_unescape,
274 const gchar *err_msg);
276 static void compose_reply_set_entry (Compose *compose,
277 MsgInfo *msginfo,
278 gboolean to_all,
279 gboolean to_ml,
280 gboolean to_sender,
281 gboolean
282 followup_and_reply_to);
283 static void compose_reedit_set_entry (Compose *compose,
284 MsgInfo *msginfo);
286 static void compose_insert_sig (Compose *compose,
287 gboolean replace);
288 static ComposeInsertResult compose_insert_file (Compose *compose,
289 const gchar *file);
291 static gboolean compose_attach_append (Compose *compose,
292 const gchar *file,
293 const gchar *type,
294 const gchar *content_type,
295 const gchar *charset);
296 static void compose_attach_parts (Compose *compose,
297 MsgInfo *msginfo);
299 static gboolean compose_beautify_paragraph (Compose *compose,
300 GtkTextIter *par_iter,
301 gboolean force);
302 static void compose_wrap_all (Compose *compose);
303 static void compose_wrap_all_full (Compose *compose,
304 gboolean autowrap);
306 static void compose_set_title (Compose *compose);
307 static void compose_select_account (Compose *compose,
308 PrefsAccount *account,
309 gboolean init);
311 static PrefsAccount *compose_current_mail_account(void);
312 /* static gint compose_send (Compose *compose); */
313 static gboolean compose_check_for_valid_recipient
314 (Compose *compose);
315 static gboolean compose_check_entries (Compose *compose,
316 gboolean check_everything);
317 static gint compose_write_to_file (Compose *compose,
318 FILE *fp,
319 gint action,
320 gboolean attach_parts);
321 static gint compose_write_body_to_file (Compose *compose,
322 const gchar *file);
323 static gint compose_remove_reedit_target (Compose *compose,
324 gboolean force);
325 static void compose_remove_draft (Compose *compose);
326 static ComposeQueueResult compose_queue_sub (Compose *compose,
327 gint *msgnum,
328 FolderItem **item,
329 gchar **msgpath,
330 gboolean perform_checks,
331 gboolean remove_reedit_target);
332 static int compose_add_attachments (Compose *compose,
333 MimeInfo *parent);
334 static gchar *compose_get_header (Compose *compose);
335 static gchar *compose_get_manual_headers_info (Compose *compose);
337 static void compose_convert_header (Compose *compose,
338 gchar *dest,
339 gint len,
340 gchar *src,
341 gint header_len,
342 gboolean addr_field);
344 static void compose_attach_info_free (AttachInfo *ainfo);
345 static void compose_attach_remove_selected (GtkAction *action,
346 gpointer data);
348 static void compose_template_apply (Compose *compose,
349 Template *tmpl,
350 gboolean replace);
351 static void compose_attach_property (GtkAction *action,
352 gpointer data);
353 static void compose_attach_property_create (gboolean *cancelled);
354 static void attach_property_ok (GtkWidget *widget,
355 gboolean *cancelled);
356 static void attach_property_cancel (GtkWidget *widget,
357 gboolean *cancelled);
358 static gint attach_property_delete_event (GtkWidget *widget,
359 GdkEventAny *event,
360 gboolean *cancelled);
361 static gboolean attach_property_key_pressed (GtkWidget *widget,
362 GdkEventKey *event,
363 gboolean *cancelled);
365 static void compose_exec_ext_editor (Compose *compose);
366 #ifdef G_OS_UNIX
367 static gint compose_exec_ext_editor_real (const gchar *file,
368 GdkNativeWindow socket_wid);
369 static gboolean compose_ext_editor_kill (Compose *compose);
370 static gboolean compose_input_cb (GIOChannel *source,
371 GIOCondition condition,
372 gpointer data);
373 static void compose_set_ext_editor_sensitive (Compose *compose,
374 gboolean sensitive);
375 static gboolean compose_get_ext_editor_cmd_valid();
376 static gboolean compose_get_ext_editor_uses_socket();
377 static gboolean compose_ext_editor_plug_removed_cb
378 (GtkSocket *socket,
379 Compose *compose);
380 #endif /* G_OS_UNIX */
382 static void compose_undo_state_changed (UndoMain *undostruct,
383 gint undo_state,
384 gint redo_state,
385 gpointer data);
387 static void compose_create_header_entry (Compose *compose);
388 static void compose_add_header_entry (Compose *compose, const gchar *header,
389 gchar *text, ComposePrefType pref_type);
390 static void compose_remove_header_entries(Compose *compose);
392 static void compose_update_priority_menu_item(Compose * compose);
393 #if USE_ENCHANT
394 static void compose_spell_menu_changed (void *data);
395 static void compose_dict_changed (void *data);
396 #endif
397 static void compose_add_field_list ( Compose *compose,
398 GList *listAddress );
400 /* callback functions */
402 static void compose_notebook_size_alloc (GtkNotebook *notebook,
403 GtkAllocation *allocation,
404 GtkPaned *paned);
405 static gboolean compose_edit_size_alloc (GtkEditable *widget,
406 GtkAllocation *allocation,
407 GtkSHRuler *shruler);
408 static void account_activated (GtkComboBox *optmenu,
409 gpointer data);
410 static void attach_selected (GtkTreeView *tree_view,
411 GtkTreePath *tree_path,
412 GtkTreeViewColumn *column,
413 Compose *compose);
414 static gboolean attach_button_pressed (GtkWidget *widget,
415 GdkEventButton *event,
416 gpointer data);
417 static gboolean attach_key_pressed (GtkWidget *widget,
418 GdkEventKey *event,
419 gpointer data);
420 static void compose_send_cb (GtkAction *action, gpointer data);
421 static void compose_send_later_cb (GtkAction *action, gpointer data);
423 static void compose_save_cb (GtkAction *action,
424 gpointer data);
426 static void compose_attach_cb (GtkAction *action,
427 gpointer data);
428 static void compose_insert_file_cb (GtkAction *action,
429 gpointer data);
430 static void compose_insert_sig_cb (GtkAction *action,
431 gpointer data);
432 static void compose_replace_sig_cb (GtkAction *action,
433 gpointer data);
435 static void compose_close_cb (GtkAction *action,
436 gpointer data);
437 static void compose_print_cb (GtkAction *action,
438 gpointer data);
440 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
442 static void compose_address_cb (GtkAction *action,
443 gpointer data);
444 static void about_show_cb (GtkAction *action,
445 gpointer data);
446 static void compose_template_activate_cb(GtkWidget *widget,
447 gpointer data);
449 static void compose_ext_editor_cb (GtkAction *action,
450 gpointer data);
452 static gint compose_delete_cb (GtkWidget *widget,
453 GdkEventAny *event,
454 gpointer data);
456 static void compose_undo_cb (GtkAction *action,
457 gpointer data);
458 static void compose_redo_cb (GtkAction *action,
459 gpointer data);
460 static void compose_cut_cb (GtkAction *action,
461 gpointer data);
462 static void compose_copy_cb (GtkAction *action,
463 gpointer data);
464 static void compose_paste_cb (GtkAction *action,
465 gpointer data);
466 static void compose_paste_as_quote_cb (GtkAction *action,
467 gpointer data);
468 static void compose_paste_no_wrap_cb (GtkAction *action,
469 gpointer data);
470 static void compose_paste_wrap_cb (GtkAction *action,
471 gpointer data);
472 static void compose_allsel_cb (GtkAction *action,
473 gpointer data);
475 static void compose_advanced_action_cb (GtkAction *action,
476 gpointer data);
478 static void compose_grab_focus_cb (GtkWidget *widget,
479 Compose *compose);
481 static void compose_changed_cb (GtkTextBuffer *textbuf,
482 Compose *compose);
484 static void compose_wrap_cb (GtkAction *action,
485 gpointer data);
486 static void compose_wrap_all_cb (GtkAction *action,
487 gpointer data);
488 static void compose_find_cb (GtkAction *action,
489 gpointer data);
490 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
491 gpointer data);
492 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
493 gpointer data);
495 static void compose_toggle_ruler_cb (GtkToggleAction *action,
496 gpointer data);
497 static void compose_toggle_sign_cb (GtkToggleAction *action,
498 gpointer data);
499 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
500 gpointer data);
501 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
502 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
503 static void compose_activate_privacy_system (Compose *compose,
504 PrefsAccount *account,
505 gboolean warn);
506 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item);
507 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
508 gpointer data);
509 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
510 gpointer data);
511 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
512 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
513 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
515 static void compose_attach_drag_received_cb (GtkWidget *widget,
516 GdkDragContext *drag_context,
517 gint x,
518 gint y,
519 GtkSelectionData *data,
520 guint info,
521 guint time,
522 gpointer user_data);
523 static void compose_insert_drag_received_cb (GtkWidget *widget,
524 GdkDragContext *drag_context,
525 gint x,
526 gint y,
527 GtkSelectionData *data,
528 guint info,
529 guint time,
530 gpointer user_data);
531 static void compose_header_drag_received_cb (GtkWidget *widget,
532 GdkDragContext *drag_context,
533 gint x,
534 gint y,
535 GtkSelectionData *data,
536 guint info,
537 guint time,
538 gpointer user_data);
540 static gboolean compose_drag_drop (GtkWidget *widget,
541 GdkDragContext *drag_context,
542 gint x, gint y,
543 guint time, gpointer user_data);
544 static gboolean completion_set_focus_to_subject
545 (GtkWidget *widget,
546 GdkEventKey *event,
547 Compose *user_data);
549 static void text_inserted (GtkTextBuffer *buffer,
550 GtkTextIter *iter,
551 const gchar *text,
552 gint len,
553 Compose *compose);
554 static Compose *compose_generic_reply(MsgInfo *msginfo,
555 ComposeQuoteMode quote_mode,
556 gboolean to_all,
557 gboolean to_ml,
558 gboolean to_sender,
559 gboolean followup_and_reply_to,
560 const gchar *body);
562 static void compose_headerentry_changed_cb (GtkWidget *entry,
563 ComposeHeaderEntry *headerentry);
564 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
565 GdkEventKey *event,
566 ComposeHeaderEntry *headerentry);
567 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
568 ComposeHeaderEntry *headerentry);
570 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
572 static void compose_allow_user_actions (Compose *compose, gboolean allow);
574 static void compose_nothing_cb (GtkAction *action, gpointer data)
579 #if USE_ENCHANT
580 static void compose_check_all (GtkAction *action, gpointer data);
581 static void compose_highlight_all (GtkAction *action, gpointer data);
582 static void compose_check_backwards (GtkAction *action, gpointer data);
583 static void compose_check_forwards_go (GtkAction *action, gpointer data);
584 #endif
586 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
588 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
590 #ifdef USE_ENCHANT
591 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
592 FolderItem *folder_item);
593 #endif
594 static void compose_attach_update_label(Compose *compose);
595 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
596 gboolean respect_default_to);
597 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
598 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
600 static GtkActionEntry compose_popup_entries[] =
602 {"Compose", NULL, "Compose", NULL, NULL, NULL },
603 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
604 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
605 {"Compose/---", NULL, "---", NULL, NULL, NULL },
606 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
609 static GtkActionEntry compose_entries[] =
611 {"Menu", NULL, "Menu", NULL, NULL, NULL },
612 /* menus */
613 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
614 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
615 #if USE_ENCHANT
616 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
617 #endif
618 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
619 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
620 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
621 /* Message menu */
622 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
623 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
624 {"Message/---", NULL, "---", NULL, NULL, NULL },
626 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
627 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
628 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
629 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
630 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
631 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
632 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
633 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
634 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
635 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
637 /* Edit menu */
638 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
639 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
640 {"Edit/---", NULL, "---", NULL, NULL, NULL },
642 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
643 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
644 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
646 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
647 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
648 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
649 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
651 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
653 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
654 {"Edit/Advanced/BackChar", NULL, N_("Move a character backward"), "<shift><control>B", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
655 {"Edit/Advanced/ForwChar", NULL, N_("Move a character forward"), "<shift><control>F", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
656 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
657 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
658 {"Edit/Advanced/BegLine", NULL, N_("Move to beginning of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
659 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
660 {"Edit/Advanced/PrevLine", NULL, N_("Move to previous line"), "<control>P", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
661 {"Edit/Advanced/NextLine", NULL, N_("Move to next line"), "<control>N", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
662 {"Edit/Advanced/DelBackChar", NULL, N_("Delete a character backward"), "<control>H", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
663 {"Edit/Advanced/DelForwChar", NULL, N_("Delete a character forward"), "<control>D", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
664 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
665 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
666 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
667 {"Edit/Advanced/DelEndLine", NULL, N_("Delete to end of line"), "<control>K", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
669 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
670 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
672 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
673 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
674 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
675 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
676 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
677 #if USE_ENCHANT
678 /* Spelling menu */
679 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
680 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
681 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
682 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
684 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
685 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
686 #endif
688 /* Options menu */
689 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
690 {"Options/---", NULL, "---", NULL, NULL, NULL },
691 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
692 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
694 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
695 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
697 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
698 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
699 #define ENC_ACTION(cs_char,c_char,string) \
700 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
702 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
703 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
704 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
705 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
706 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
707 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
708 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
709 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
710 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
712 /* Tools menu */
713 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
715 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
716 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
717 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
718 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
720 /* Help menu */
721 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
724 static GtkToggleActionEntry compose_toggle_entries[] =
726 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
727 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
728 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
729 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
730 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
731 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
732 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
735 static GtkRadioActionEntry compose_radio_rm_entries[] =
737 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
738 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
739 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
740 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
743 static GtkRadioActionEntry compose_radio_prio_entries[] =
745 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
746 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
747 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
748 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
749 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
752 static GtkRadioActionEntry compose_radio_enc_entries[] =
754 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
777 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
778 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
779 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
780 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
781 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
782 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
783 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
784 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
785 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
786 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
789 static GtkTargetEntry compose_mime_types[] =
791 {"text/uri-list", 0, 0},
792 {"UTF8_STRING", 0, 0},
793 {"text/plain", 0, 0}
796 static gboolean compose_put_existing_to_front(MsgInfo *info)
798 const GList *compose_list = compose_get_compose_list();
799 const GList *elem = NULL;
801 if (compose_list) {
802 for (elem = compose_list; elem != NULL && elem->data != NULL;
803 elem = elem->next) {
804 Compose *c = (Compose*)elem->data;
806 if (!c->targetinfo || !c->targetinfo->msgid ||
807 !info->msgid)
808 continue;
810 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
811 gtkut_window_popup(c->window);
812 return TRUE;
816 return FALSE;
819 static GdkColor quote_color1 =
820 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
821 static GdkColor quote_color2 =
822 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
823 static GdkColor quote_color3 =
824 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
826 static GdkColor quote_bgcolor1 =
827 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
828 static GdkColor quote_bgcolor2 =
829 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
830 static GdkColor quote_bgcolor3 =
831 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
833 static GdkColor signature_color = {
834 (gulong)0,
835 (gushort)0x7fff,
836 (gushort)0x7fff,
837 (gushort)0x7fff
840 static GdkColor uri_color = {
841 (gulong)0,
842 (gushort)0,
843 (gushort)0,
844 (gushort)0
847 static void compose_create_tags(GtkTextView *text, Compose *compose)
849 GtkTextBuffer *buffer;
850 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
852 buffer = gtk_text_view_get_buffer(text);
854 if (prefs_common.enable_color) {
855 /* grab the quote colors, converting from an int to a GdkColor */
856 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1],
857 &quote_color1);
858 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2],
859 &quote_color2);
860 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3],
861 &quote_color3);
862 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1_BG],
863 &quote_bgcolor1);
864 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2_BG],
865 &quote_bgcolor2);
866 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3_BG],
867 &quote_bgcolor3);
868 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_SIGNATURE],
869 &signature_color);
870 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_URI],
871 &uri_color);
872 } else {
873 signature_color = quote_color1 = quote_color2 = quote_color3 =
874 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
877 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
878 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
879 "foreground-gdk", &quote_color1,
880 "paragraph-background-gdk", &quote_bgcolor1,
881 NULL);
882 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
883 "foreground-gdk", &quote_color2,
884 "paragraph-background-gdk", &quote_bgcolor2,
885 NULL);
886 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
887 "foreground-gdk", &quote_color3,
888 "paragraph-background-gdk", &quote_bgcolor3,
889 NULL);
890 } else {
891 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
892 "foreground-gdk", &quote_color1,
893 NULL);
894 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
895 "foreground-gdk", &quote_color2,
896 NULL);
897 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
898 "foreground-gdk", &quote_color3,
899 NULL);
902 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
903 "foreground-gdk", &signature_color,
904 NULL);
906 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
907 "foreground-gdk", &uri_color,
908 NULL);
909 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
910 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
913 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
914 GList *attach_files)
916 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
919 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
921 return compose_generic_new(account, mailto, item, NULL, NULL);
924 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
926 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
929 #define SCROLL_TO_CURSOR(compose) { \
930 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
931 gtk_text_view_get_buffer( \
932 GTK_TEXT_VIEW(compose->text))); \
933 gtk_text_view_scroll_mark_onscreen( \
934 GTK_TEXT_VIEW(compose->text), \
935 cmark); \
938 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
940 GtkEditable *entry;
941 if (folderidentifier) {
942 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
943 prefs_common.compose_save_to_history = add_history(
944 prefs_common.compose_save_to_history, folderidentifier);
945 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
946 prefs_common.compose_save_to_history);
949 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
950 if (folderidentifier)
951 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
952 else
953 gtk_entry_set_text(GTK_ENTRY(entry), "");
956 static gchar *compose_get_save_to(Compose *compose)
958 GtkEditable *entry;
959 gchar *result = NULL;
960 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
961 result = gtk_editable_get_chars(entry, 0, -1);
963 if (result) {
964 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
965 prefs_common.compose_save_to_history = add_history(
966 prefs_common.compose_save_to_history, result);
967 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
968 prefs_common.compose_save_to_history);
970 return result;
973 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
974 GList *attach_files, GList *listAddress )
976 Compose *compose;
977 GtkTextView *textview;
978 GtkTextBuffer *textbuf;
979 GtkTextIter iter;
980 const gchar *subject_format = NULL;
981 const gchar *body_format = NULL;
982 gchar *mailto_from = NULL;
983 PrefsAccount *mailto_account = NULL;
984 MsgInfo* dummyinfo = NULL;
985 gint cursor_pos = -1;
986 MailField mfield = NO_FIELD_PRESENT;
987 gchar* buf;
988 GtkTextMark *mark;
990 /* check if mailto defines a from */
991 if (mailto && *mailto != '\0') {
992 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
993 /* mailto defines a from, check if we can get account prefs from it,
994 if not, the account prefs will be guessed using other ways, but we'll keep
995 the from anyway */
996 if (mailto_from) {
997 mailto_account = account_find_from_address(mailto_from, TRUE);
998 if (mailto_account == NULL) {
999 gchar *tmp_from;
1000 Xstrdup_a(tmp_from, mailto_from, return NULL);
1001 extract_address(tmp_from);
1002 mailto_account = account_find_from_address(tmp_from, TRUE);
1005 if (mailto_account)
1006 account = mailto_account;
1009 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1010 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1011 account = account_find_from_id(item->prefs->default_account);
1013 /* if no account prefs set, fallback to the current one */
1014 if (!account) account = cur_account;
1015 cm_return_val_if_fail(account != NULL, NULL);
1017 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1018 compose_apply_folder_privacy_settings(compose, item);
1020 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1021 (account->default_encrypt || account->default_sign))
1022 COMPOSE_PRIVACY_WARNING();
1024 /* override from name if mailto asked for it */
1025 if (mailto_from) {
1026 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1027 g_free(mailto_from);
1028 } else
1029 /* override from name according to folder properties */
1030 if (item && item->prefs &&
1031 item->prefs->compose_with_format &&
1032 item->prefs->compose_override_from_format &&
1033 *item->prefs->compose_override_from_format != '\0') {
1035 gchar *tmp = NULL;
1036 gchar *buf = NULL;
1038 dummyinfo = compose_msginfo_new_from_compose(compose);
1040 /* decode \-escape sequences in the internal representation of the quote format */
1041 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1042 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1044 #ifdef USE_ENCHANT
1045 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1046 compose->gtkaspell);
1047 #else
1048 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1049 #endif
1050 quote_fmt_scan_string(tmp);
1051 quote_fmt_parse();
1053 buf = quote_fmt_get_buffer();
1054 if (buf == NULL)
1055 alertpanel_error(_("New message From format error."));
1056 else
1057 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1058 quote_fmt_reset_vartable();
1059 quote_fmtlex_destroy();
1061 g_free(tmp);
1064 compose->replyinfo = NULL;
1065 compose->fwdinfo = NULL;
1067 textview = GTK_TEXT_VIEW(compose->text);
1068 textbuf = gtk_text_view_get_buffer(textview);
1069 compose_create_tags(textview, compose);
1071 undo_block(compose->undostruct);
1072 #ifdef USE_ENCHANT
1073 compose_set_dictionaries_from_folder_prefs(compose, item);
1074 #endif
1076 if (account->auto_sig)
1077 compose_insert_sig(compose, FALSE);
1078 gtk_text_buffer_get_start_iter(textbuf, &iter);
1079 gtk_text_buffer_place_cursor(textbuf, &iter);
1081 if (account->protocol != A_NNTP) {
1082 if (mailto && *mailto != '\0') {
1083 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1085 } else {
1086 compose_set_folder_prefs(compose, item, TRUE);
1088 if (item && item->ret_rcpt) {
1089 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1091 } else {
1092 if (mailto && *mailto != '\0') {
1093 if (!strchr(mailto, '@'))
1094 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1095 else
1096 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1097 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1098 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1099 mfield = TO_FIELD_PRESENT;
1102 * CLAWS: just don't allow return receipt request, even if the user
1103 * may want to send an email. simple but foolproof.
1105 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1107 compose_add_field_list( compose, listAddress );
1109 if (item && item->prefs && item->prefs->compose_with_format) {
1110 subject_format = item->prefs->compose_subject_format;
1111 body_format = item->prefs->compose_body_format;
1112 } else if (account->compose_with_format) {
1113 subject_format = account->compose_subject_format;
1114 body_format = account->compose_body_format;
1115 } else if (prefs_common.compose_with_format) {
1116 subject_format = prefs_common.compose_subject_format;
1117 body_format = prefs_common.compose_body_format;
1120 if (subject_format || body_format) {
1122 if ( subject_format
1123 && *subject_format != '\0' )
1125 gchar *subject = NULL;
1126 gchar *tmp = NULL;
1127 gchar *buf = NULL;
1129 if (!dummyinfo)
1130 dummyinfo = compose_msginfo_new_from_compose(compose);
1132 /* decode \-escape sequences in the internal representation of the quote format */
1133 tmp = g_malloc(strlen(subject_format)+1);
1134 pref_get_unescaped_pref(tmp, subject_format);
1136 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1137 #ifdef USE_ENCHANT
1138 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1139 compose->gtkaspell);
1140 #else
1141 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1142 #endif
1143 quote_fmt_scan_string(tmp);
1144 quote_fmt_parse();
1146 buf = quote_fmt_get_buffer();
1147 if (buf == NULL)
1148 alertpanel_error(_("New message subject format error."));
1149 else
1150 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1151 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1152 quote_fmt_reset_vartable();
1153 quote_fmtlex_destroy();
1155 g_free(subject);
1156 g_free(tmp);
1157 mfield = SUBJECT_FIELD_PRESENT;
1160 if ( body_format
1161 && *body_format != '\0' )
1163 GtkTextView *text;
1164 GtkTextBuffer *buffer;
1165 GtkTextIter start, end;
1166 gchar *tmp = NULL;
1168 if (!dummyinfo)
1169 dummyinfo = compose_msginfo_new_from_compose(compose);
1171 text = GTK_TEXT_VIEW(compose->text);
1172 buffer = gtk_text_view_get_buffer(text);
1173 gtk_text_buffer_get_start_iter(buffer, &start);
1174 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1175 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1177 compose_quote_fmt(compose, dummyinfo,
1178 body_format,
1179 NULL, tmp, FALSE, TRUE,
1180 _("The body of the \"New message\" template has an error at line %d."));
1181 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1182 quote_fmt_reset_vartable();
1184 g_free(tmp);
1185 #ifdef USE_ENCHANT
1186 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1187 gtkaspell_highlight_all(compose->gtkaspell);
1188 #endif
1189 mfield = BODY_FIELD_PRESENT;
1193 procmsg_msginfo_free( &dummyinfo );
1195 if (attach_files) {
1196 GList *curr;
1197 AttachInfo *ainfo;
1199 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1200 ainfo = (AttachInfo *) curr->data;
1201 if (ainfo->insert)
1202 compose_insert_file(compose, ainfo->file);
1203 else
1204 compose_attach_append(compose, ainfo->file, ainfo->file,
1205 ainfo->content_type, ainfo->charset);
1209 compose_show_first_last_header(compose, TRUE);
1211 /* Set save folder */
1212 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1213 gchar *folderidentifier;
1215 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1216 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1217 folderidentifier = folder_item_get_identifier(item);
1218 compose_set_save_to(compose, folderidentifier);
1219 g_free(folderidentifier);
1222 /* Place cursor according to provided input (mfield) */
1223 switch (mfield) {
1224 case NO_FIELD_PRESENT:
1225 if (compose->header_last)
1226 gtk_widget_grab_focus(compose->header_last->entry);
1227 break;
1228 case TO_FIELD_PRESENT:
1229 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1230 if (buf) {
1231 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1232 g_free(buf);
1234 gtk_widget_grab_focus(compose->subject_entry);
1235 break;
1236 case SUBJECT_FIELD_PRESENT:
1237 textview = GTK_TEXT_VIEW(compose->text);
1238 if (!textview)
1239 break;
1240 textbuf = gtk_text_view_get_buffer(textview);
1241 if (!textbuf)
1242 break;
1243 mark = gtk_text_buffer_get_insert(textbuf);
1244 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1245 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1247 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1248 * only defers where it comes to the variable body
1249 * is not null. If no body is present compose->text
1250 * will be null in which case you cannot place the
1251 * cursor inside the component so. An empty component
1252 * is therefore created before placing the cursor
1254 case BODY_FIELD_PRESENT:
1255 cursor_pos = quote_fmt_get_cursor_pos();
1256 if (cursor_pos == -1)
1257 gtk_widget_grab_focus(compose->header_last->entry);
1258 else
1259 gtk_widget_grab_focus(compose->text);
1260 break;
1263 undo_unblock(compose->undostruct);
1265 if (prefs_common.auto_exteditor)
1266 compose_exec_ext_editor(compose);
1268 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1270 SCROLL_TO_CURSOR(compose);
1272 compose->modified = FALSE;
1273 compose_set_title(compose);
1275 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1277 return compose;
1280 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1281 gboolean override_pref, const gchar *system)
1283 const gchar *privacy = NULL;
1285 cm_return_if_fail(compose != NULL);
1286 cm_return_if_fail(account != NULL);
1288 if (privacy_system_can_encrypt(compose->privacy_system) == FALSE ||
1289 (override_pref == FALSE && account->default_encrypt_reply == FALSE))
1290 return;
1292 if (account->default_privacy_system && strlen(account->default_privacy_system))
1293 privacy = account->default_privacy_system;
1294 else if (system)
1295 privacy = system;
1296 else {
1297 GSList *privacy_avail = privacy_get_system_ids();
1298 if (privacy_avail && g_slist_length(privacy_avail)) {
1299 privacy = (gchar *)(privacy_avail->data);
1301 g_slist_free_full(privacy_avail, g_free);
1303 if (privacy != NULL) {
1304 if (system) {
1305 g_free(compose->privacy_system);
1306 compose->privacy_system = NULL;
1307 g_free(compose->encdata);
1308 compose->encdata = NULL;
1310 if (compose->privacy_system == NULL)
1311 compose->privacy_system = g_strdup(privacy);
1312 else if (*(compose->privacy_system) == '\0') {
1313 g_free(compose->privacy_system);
1314 g_free(compose->encdata);
1315 compose->encdata = NULL;
1316 compose->privacy_system = g_strdup(privacy);
1318 compose_update_privacy_system_menu_item(compose, FALSE);
1319 compose_use_encryption(compose, TRUE);
1323 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1325 const gchar *privacy = NULL;
1326 if (privacy_system_can_sign(compose->privacy_system) == FALSE)
1327 return;
1329 if (account->default_privacy_system && strlen(account->default_privacy_system))
1330 privacy = account->default_privacy_system;
1331 else if (system)
1332 privacy = system;
1333 else {
1334 GSList *privacy_avail = privacy_get_system_ids();
1335 if (privacy_avail && g_slist_length(privacy_avail)) {
1336 privacy = (gchar *)(privacy_avail->data);
1340 if (privacy != NULL) {
1341 if (system) {
1342 g_free(compose->privacy_system);
1343 compose->privacy_system = NULL;
1344 g_free(compose->encdata);
1345 compose->encdata = NULL;
1347 if (compose->privacy_system == NULL)
1348 compose->privacy_system = g_strdup(privacy);
1349 compose_update_privacy_system_menu_item(compose, FALSE);
1350 compose_use_signing(compose, TRUE);
1354 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1356 MsgInfo *msginfo;
1357 guint list_len;
1358 Compose *compose = NULL;
1360 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1362 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1363 cm_return_val_if_fail(msginfo != NULL, NULL);
1365 list_len = g_slist_length(msginfo_list);
1367 switch (mode) {
1368 case COMPOSE_REPLY:
1369 case COMPOSE_REPLY_TO_ADDRESS:
1370 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1371 FALSE, prefs_common.default_reply_list, FALSE, body);
1372 break;
1373 case COMPOSE_REPLY_WITH_QUOTE:
1374 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1375 FALSE, prefs_common.default_reply_list, FALSE, body);
1376 break;
1377 case COMPOSE_REPLY_WITHOUT_QUOTE:
1378 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1379 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1380 break;
1381 case COMPOSE_REPLY_TO_SENDER:
1382 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1383 FALSE, FALSE, TRUE, body);
1384 break;
1385 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1386 compose = compose_followup_and_reply_to(msginfo,
1387 COMPOSE_QUOTE_CHECK,
1388 FALSE, FALSE, body);
1389 break;
1390 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1391 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1392 FALSE, FALSE, TRUE, body);
1393 break;
1394 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1395 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1396 FALSE, FALSE, TRUE, NULL);
1397 break;
1398 case COMPOSE_REPLY_TO_ALL:
1399 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1400 TRUE, FALSE, FALSE, body);
1401 break;
1402 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1403 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1404 TRUE, FALSE, FALSE, body);
1405 break;
1406 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1407 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1408 TRUE, FALSE, FALSE, NULL);
1409 break;
1410 case COMPOSE_REPLY_TO_LIST:
1411 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1412 FALSE, TRUE, FALSE, body);
1413 break;
1414 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1415 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1416 FALSE, TRUE, FALSE, body);
1417 break;
1418 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1419 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1420 FALSE, TRUE, FALSE, NULL);
1421 break;
1422 case COMPOSE_FORWARD:
1423 if (prefs_common.forward_as_attachment) {
1424 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1425 return compose;
1426 } else {
1427 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1428 return compose;
1430 break;
1431 case COMPOSE_FORWARD_INLINE:
1432 /* check if we reply to more than one Message */
1433 if (list_len == 1) {
1434 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1435 break;
1437 /* more messages FALL THROUGH */
1438 case COMPOSE_FORWARD_AS_ATTACH:
1439 compose = compose_forward_multiple(NULL, msginfo_list);
1440 break;
1441 case COMPOSE_REDIRECT:
1442 compose = compose_redirect(NULL, msginfo, FALSE);
1443 break;
1444 default:
1445 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1448 if (compose == NULL) {
1449 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1450 return NULL;
1453 compose->rmode = mode;
1454 switch (compose->rmode) {
1455 case COMPOSE_REPLY:
1456 case COMPOSE_REPLY_WITH_QUOTE:
1457 case COMPOSE_REPLY_WITHOUT_QUOTE:
1458 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1459 debug_print("reply mode Normal\n");
1460 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1461 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1462 break;
1463 case COMPOSE_REPLY_TO_SENDER:
1464 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1465 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1466 debug_print("reply mode Sender\n");
1467 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1468 break;
1469 case COMPOSE_REPLY_TO_ALL:
1470 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1471 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1472 debug_print("reply mode All\n");
1473 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1474 break;
1475 case COMPOSE_REPLY_TO_LIST:
1476 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1477 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1478 debug_print("reply mode List\n");
1479 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1480 break;
1481 case COMPOSE_REPLY_TO_ADDRESS:
1482 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1483 break;
1484 default:
1485 break;
1487 return compose;
1490 static Compose *compose_reply(MsgInfo *msginfo,
1491 ComposeQuoteMode quote_mode,
1492 gboolean to_all,
1493 gboolean to_ml,
1494 gboolean to_sender,
1495 const gchar *body)
1497 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1498 to_sender, FALSE, body);
1501 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1502 ComposeQuoteMode quote_mode,
1503 gboolean to_all,
1504 gboolean to_sender,
1505 const gchar *body)
1507 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1508 to_sender, TRUE, body);
1511 static void compose_extract_original_charset(Compose *compose)
1513 MsgInfo *info = NULL;
1514 if (compose->replyinfo) {
1515 info = compose->replyinfo;
1516 } else if (compose->fwdinfo) {
1517 info = compose->fwdinfo;
1518 } else if (compose->targetinfo) {
1519 info = compose->targetinfo;
1521 if (info) {
1522 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1523 MimeInfo *partinfo = mimeinfo;
1524 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1525 partinfo = procmime_mimeinfo_next(partinfo);
1526 if (partinfo) {
1527 compose->orig_charset =
1528 g_strdup(procmime_mimeinfo_get_parameter(
1529 partinfo, "charset"));
1531 procmime_mimeinfo_free_all(&mimeinfo);
1535 #define SIGNAL_BLOCK(buffer) { \
1536 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1537 G_CALLBACK(compose_changed_cb), \
1538 compose); \
1539 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1540 G_CALLBACK(text_inserted), \
1541 compose); \
1544 #define SIGNAL_UNBLOCK(buffer) { \
1545 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1546 G_CALLBACK(compose_changed_cb), \
1547 compose); \
1548 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1549 G_CALLBACK(text_inserted), \
1550 compose); \
1553 static Compose *compose_generic_reply(MsgInfo *msginfo,
1554 ComposeQuoteMode quote_mode,
1555 gboolean to_all, gboolean to_ml,
1556 gboolean to_sender,
1557 gboolean followup_and_reply_to,
1558 const gchar *body)
1560 Compose *compose;
1561 PrefsAccount *account = NULL;
1562 GtkTextView *textview;
1563 GtkTextBuffer *textbuf;
1564 gboolean quote = FALSE;
1565 const gchar *qmark = NULL;
1566 const gchar *body_fmt = NULL;
1567 gchar *s_system = NULL;
1568 START_TIMING("");
1569 cm_return_val_if_fail(msginfo != NULL, NULL);
1570 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1572 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1574 cm_return_val_if_fail(account != NULL, NULL);
1576 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1577 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1579 compose->updating = TRUE;
1581 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1582 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1584 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1585 if (!compose->replyinfo)
1586 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1588 compose_extract_original_charset(compose);
1590 if (msginfo->folder && msginfo->folder->ret_rcpt)
1591 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1593 /* Set save folder */
1594 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1595 gchar *folderidentifier;
1597 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1598 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1599 folderidentifier = folder_item_get_identifier(msginfo->folder);
1600 compose_set_save_to(compose, folderidentifier);
1601 g_free(folderidentifier);
1604 if (compose_parse_header(compose, msginfo) < 0) {
1605 compose->updating = FALSE;
1606 compose_destroy(compose);
1607 return NULL;
1610 /* override from name according to folder properties */
1611 if (msginfo->folder && msginfo->folder->prefs &&
1612 msginfo->folder->prefs->reply_with_format &&
1613 msginfo->folder->prefs->reply_override_from_format &&
1614 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1616 gchar *tmp = NULL;
1617 gchar *buf = NULL;
1619 /* decode \-escape sequences in the internal representation of the quote format */
1620 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1621 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1623 #ifdef USE_ENCHANT
1624 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1625 compose->gtkaspell);
1626 #else
1627 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1628 #endif
1629 quote_fmt_scan_string(tmp);
1630 quote_fmt_parse();
1632 buf = quote_fmt_get_buffer();
1633 if (buf == NULL)
1634 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1635 else
1636 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1637 quote_fmt_reset_vartable();
1638 quote_fmtlex_destroy();
1640 g_free(tmp);
1643 textview = (GTK_TEXT_VIEW(compose->text));
1644 textbuf = gtk_text_view_get_buffer(textview);
1645 compose_create_tags(textview, compose);
1647 undo_block(compose->undostruct);
1648 #ifdef USE_ENCHANT
1649 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1650 gtkaspell_block_check(compose->gtkaspell);
1651 #endif
1653 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1654 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1655 /* use the reply format of folder (if enabled), or the account's one
1656 (if enabled) or fallback to the global reply format, which is always
1657 enabled (even if empty), and use the relevant quotemark */
1658 quote = TRUE;
1659 if (msginfo->folder && msginfo->folder->prefs &&
1660 msginfo->folder->prefs->reply_with_format) {
1661 qmark = msginfo->folder->prefs->reply_quotemark;
1662 body_fmt = msginfo->folder->prefs->reply_body_format;
1664 } else if (account->reply_with_format) {
1665 qmark = account->reply_quotemark;
1666 body_fmt = account->reply_body_format;
1668 } else {
1669 qmark = prefs_common.quotemark;
1670 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1671 body_fmt = gettext(prefs_common.quotefmt);
1672 else
1673 body_fmt = "";
1677 if (quote) {
1678 /* empty quotemark is not allowed */
1679 if (qmark == NULL || *qmark == '\0')
1680 qmark = "> ";
1681 compose_quote_fmt(compose, compose->replyinfo,
1682 body_fmt, qmark, body, FALSE, TRUE,
1683 _("The body of the \"Reply\" template has an error at line %d."));
1684 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1685 quote_fmt_reset_vartable();
1688 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1689 compose_force_encryption(compose, account, FALSE, s_system);
1692 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1693 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1694 compose_force_signing(compose, account, s_system);
1696 g_free(s_system);
1698 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1699 ((account->default_encrypt || account->default_sign) ||
1700 (account->default_encrypt_reply && MSG_IS_ENCRYPTED(compose->replyinfo->flags)) ||
1701 (account->default_sign_reply && MSG_IS_SIGNED(compose->replyinfo->flags))))
1702 COMPOSE_PRIVACY_WARNING();
1704 SIGNAL_BLOCK(textbuf);
1706 if (account->auto_sig)
1707 compose_insert_sig(compose, FALSE);
1709 compose_wrap_all(compose);
1711 #ifdef USE_ENCHANT
1712 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1713 gtkaspell_highlight_all(compose->gtkaspell);
1714 gtkaspell_unblock_check(compose->gtkaspell);
1715 #endif
1716 SIGNAL_UNBLOCK(textbuf);
1718 gtk_widget_grab_focus(compose->text);
1720 undo_unblock(compose->undostruct);
1722 if (prefs_common.auto_exteditor)
1723 compose_exec_ext_editor(compose);
1725 compose->modified = FALSE;
1726 compose_set_title(compose);
1728 compose->updating = FALSE;
1729 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1730 SCROLL_TO_CURSOR(compose);
1732 if (compose->deferred_destroy) {
1733 compose_destroy(compose);
1734 return NULL;
1736 END_TIMING();
1738 return compose;
1741 #define INSERT_FW_HEADER(var, hdr) \
1742 if (msginfo->var && *msginfo->var) { \
1743 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1744 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1745 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1748 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1749 gboolean as_attach, const gchar *body,
1750 gboolean no_extedit,
1751 gboolean batch)
1753 Compose *compose;
1754 GtkTextView *textview;
1755 GtkTextBuffer *textbuf;
1756 gint cursor_pos = -1;
1757 ComposeMode mode;
1759 cm_return_val_if_fail(msginfo != NULL, NULL);
1760 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1762 if (!account && !(account = compose_find_account(msginfo)))
1763 account = cur_account;
1765 if (!prefs_common.forward_as_attachment)
1766 mode = COMPOSE_FORWARD_INLINE;
1767 else
1768 mode = COMPOSE_FORWARD;
1769 compose = compose_create(account, msginfo->folder, mode, batch);
1770 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1772 compose->updating = TRUE;
1773 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1774 if (!compose->fwdinfo)
1775 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1777 compose_extract_original_charset(compose);
1779 if (msginfo->subject && *msginfo->subject) {
1780 gchar *buf, *buf2, *p;
1782 buf = p = g_strdup(msginfo->subject);
1783 p += subject_get_prefix_length(p);
1784 memmove(buf, p, strlen(p) + 1);
1786 buf2 = g_strdup_printf("Fw: %s", buf);
1787 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1789 g_free(buf);
1790 g_free(buf2);
1793 /* override from name according to folder properties */
1794 if (msginfo->folder && msginfo->folder->prefs &&
1795 msginfo->folder->prefs->forward_with_format &&
1796 msginfo->folder->prefs->forward_override_from_format &&
1797 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1799 gchar *tmp = NULL;
1800 gchar *buf = NULL;
1801 MsgInfo *full_msginfo = NULL;
1803 if (!as_attach)
1804 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1805 if (!full_msginfo)
1806 full_msginfo = procmsg_msginfo_copy(msginfo);
1808 /* decode \-escape sequences in the internal representation of the quote format */
1809 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1810 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1812 #ifdef USE_ENCHANT
1813 gtkaspell_block_check(compose->gtkaspell);
1814 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1815 compose->gtkaspell);
1816 #else
1817 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1818 #endif
1819 quote_fmt_scan_string(tmp);
1820 quote_fmt_parse();
1822 buf = quote_fmt_get_buffer();
1823 if (buf == NULL)
1824 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1825 else
1826 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1827 quote_fmt_reset_vartable();
1828 quote_fmtlex_destroy();
1830 g_free(tmp);
1831 procmsg_msginfo_free(&full_msginfo);
1834 textview = GTK_TEXT_VIEW(compose->text);
1835 textbuf = gtk_text_view_get_buffer(textview);
1836 compose_create_tags(textview, compose);
1838 undo_block(compose->undostruct);
1839 if (as_attach) {
1840 gchar *msgfile;
1842 msgfile = procmsg_get_message_file(msginfo);
1843 if (!is_file_exist(msgfile))
1844 g_warning("%s: file does not exist", msgfile);
1845 else
1846 compose_attach_append(compose, msgfile, msgfile,
1847 "message/rfc822", NULL);
1849 g_free(msgfile);
1850 } else {
1851 const gchar *qmark = NULL;
1852 const gchar *body_fmt = NULL;
1853 MsgInfo *full_msginfo;
1855 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1856 if (!full_msginfo)
1857 full_msginfo = procmsg_msginfo_copy(msginfo);
1859 /* use the forward format of folder (if enabled), or the account's one
1860 (if enabled) or fallback to the global forward format, which is always
1861 enabled (even if empty), and use the relevant quotemark */
1862 if (msginfo->folder && msginfo->folder->prefs &&
1863 msginfo->folder->prefs->forward_with_format) {
1864 qmark = msginfo->folder->prefs->forward_quotemark;
1865 body_fmt = msginfo->folder->prefs->forward_body_format;
1867 } else if (account->forward_with_format) {
1868 qmark = account->forward_quotemark;
1869 body_fmt = account->forward_body_format;
1871 } else {
1872 qmark = prefs_common.fw_quotemark;
1873 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1874 body_fmt = gettext(prefs_common.fw_quotefmt);
1875 else
1876 body_fmt = "";
1879 /* empty quotemark is not allowed */
1880 if (qmark == NULL || *qmark == '\0')
1881 qmark = "> ";
1883 compose_quote_fmt(compose, full_msginfo,
1884 body_fmt, qmark, body, FALSE, TRUE,
1885 _("The body of the \"Forward\" template has an error at line %d."));
1886 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1887 quote_fmt_reset_vartable();
1888 compose_attach_parts(compose, msginfo);
1890 procmsg_msginfo_free(&full_msginfo);
1893 SIGNAL_BLOCK(textbuf);
1895 if (account->auto_sig)
1896 compose_insert_sig(compose, FALSE);
1898 compose_wrap_all(compose);
1900 #ifdef USE_ENCHANT
1901 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1902 gtkaspell_highlight_all(compose->gtkaspell);
1903 gtkaspell_unblock_check(compose->gtkaspell);
1904 #endif
1905 SIGNAL_UNBLOCK(textbuf);
1907 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1908 (account->default_encrypt || account->default_sign))
1909 COMPOSE_PRIVACY_WARNING();
1911 cursor_pos = quote_fmt_get_cursor_pos();
1912 if (cursor_pos == -1)
1913 gtk_widget_grab_focus(compose->header_last->entry);
1914 else
1915 gtk_widget_grab_focus(compose->text);
1917 if (!no_extedit && prefs_common.auto_exteditor)
1918 compose_exec_ext_editor(compose);
1920 /*save folder*/
1921 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1922 gchar *folderidentifier;
1924 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1925 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1926 folderidentifier = folder_item_get_identifier(msginfo->folder);
1927 compose_set_save_to(compose, folderidentifier);
1928 g_free(folderidentifier);
1931 undo_unblock(compose->undostruct);
1933 compose->modified = FALSE;
1934 compose_set_title(compose);
1936 compose->updating = FALSE;
1937 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1938 SCROLL_TO_CURSOR(compose);
1940 if (compose->deferred_destroy) {
1941 compose_destroy(compose);
1942 return NULL;
1945 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1947 return compose;
1950 #undef INSERT_FW_HEADER
1952 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1954 Compose *compose;
1955 GtkTextView *textview;
1956 GtkTextBuffer *textbuf;
1957 GtkTextIter iter;
1958 GSList *msginfo;
1959 gchar *msgfile;
1960 gboolean single_mail = TRUE;
1962 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1964 if (g_slist_length(msginfo_list) > 1)
1965 single_mail = FALSE;
1967 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1968 if (((MsgInfo *)msginfo->data)->folder == NULL)
1969 return NULL;
1971 /* guess account from first selected message */
1972 if (!account &&
1973 !(account = compose_find_account(msginfo_list->data)))
1974 account = cur_account;
1976 cm_return_val_if_fail(account != NULL, NULL);
1978 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1979 if (msginfo->data) {
1980 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1981 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1985 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1986 g_warning("no msginfo_list");
1987 return NULL;
1990 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1991 compose_apply_folder_privacy_settings(compose, ((MsgInfo *)msginfo_list->data)->folder);
1992 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1993 (account->default_encrypt || account->default_sign))
1994 COMPOSE_PRIVACY_WARNING();
1996 compose->updating = TRUE;
1998 /* override from name according to folder properties */
1999 if (msginfo_list->data) {
2000 MsgInfo *msginfo = msginfo_list->data;
2002 if (msginfo->folder && msginfo->folder->prefs &&
2003 msginfo->folder->prefs->forward_with_format &&
2004 msginfo->folder->prefs->forward_override_from_format &&
2005 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2007 gchar *tmp = NULL;
2008 gchar *buf = NULL;
2010 /* decode \-escape sequences in the internal representation of the quote format */
2011 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2012 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2014 #ifdef USE_ENCHANT
2015 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2016 compose->gtkaspell);
2017 #else
2018 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2019 #endif
2020 quote_fmt_scan_string(tmp);
2021 quote_fmt_parse();
2023 buf = quote_fmt_get_buffer();
2024 if (buf == NULL)
2025 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2026 else
2027 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2028 quote_fmt_reset_vartable();
2029 quote_fmtlex_destroy();
2031 g_free(tmp);
2035 textview = GTK_TEXT_VIEW(compose->text);
2036 textbuf = gtk_text_view_get_buffer(textview);
2037 compose_create_tags(textview, compose);
2039 undo_block(compose->undostruct);
2040 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2041 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2043 if (!is_file_exist(msgfile))
2044 g_warning("%s: file does not exist", msgfile);
2045 else
2046 compose_attach_append(compose, msgfile, msgfile,
2047 "message/rfc822", NULL);
2048 g_free(msgfile);
2051 if (single_mail) {
2052 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2053 if (info->subject && *info->subject) {
2054 gchar *buf, *buf2, *p;
2056 buf = p = g_strdup(info->subject);
2057 p += subject_get_prefix_length(p);
2058 memmove(buf, p, strlen(p) + 1);
2060 buf2 = g_strdup_printf("Fw: %s", buf);
2061 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2063 g_free(buf);
2064 g_free(buf2);
2066 } else {
2067 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2068 _("Fw: multiple emails"));
2071 SIGNAL_BLOCK(textbuf);
2073 if (account->auto_sig)
2074 compose_insert_sig(compose, FALSE);
2076 compose_wrap_all(compose);
2078 SIGNAL_UNBLOCK(textbuf);
2080 gtk_text_buffer_get_start_iter(textbuf, &iter);
2081 gtk_text_buffer_place_cursor(textbuf, &iter);
2083 if (prefs_common.auto_exteditor)
2084 compose_exec_ext_editor(compose);
2086 gtk_widget_grab_focus(compose->header_last->entry);
2087 undo_unblock(compose->undostruct);
2088 compose->modified = FALSE;
2089 compose_set_title(compose);
2091 compose->updating = FALSE;
2092 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2093 SCROLL_TO_CURSOR(compose);
2095 if (compose->deferred_destroy) {
2096 compose_destroy(compose);
2097 return NULL;
2100 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2102 return compose;
2105 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2107 GtkTextIter start = *iter;
2108 GtkTextIter end_iter;
2109 int start_pos = gtk_text_iter_get_offset(&start);
2110 gchar *str = NULL;
2111 if (!compose->account->sig_sep)
2112 return FALSE;
2114 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2115 start_pos+strlen(compose->account->sig_sep));
2117 /* check sig separator */
2118 str = gtk_text_iter_get_text(&start, &end_iter);
2119 if (!strcmp(str, compose->account->sig_sep)) {
2120 gchar *tmp = NULL;
2121 /* check end of line (\n) */
2122 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2123 start_pos+strlen(compose->account->sig_sep));
2124 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2125 start_pos+strlen(compose->account->sig_sep)+1);
2126 tmp = gtk_text_iter_get_text(&start, &end_iter);
2127 if (!strcmp(tmp,"\n")) {
2128 g_free(str);
2129 g_free(tmp);
2130 return TRUE;
2132 g_free(tmp);
2134 g_free(str);
2136 return FALSE;
2139 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2141 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2142 Compose *compose = (Compose *)data;
2143 FolderItem *old_item = NULL;
2144 FolderItem *new_item = NULL;
2145 gchar *old_id, *new_id;
2147 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2148 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2149 return FALSE;
2151 old_item = hookdata->item;
2152 new_item = hookdata->item2;
2154 old_id = folder_item_get_identifier(old_item);
2155 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2157 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2158 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2159 compose->targetinfo->folder = new_item;
2162 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2163 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2164 compose->replyinfo->folder = new_item;
2167 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2168 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2169 compose->fwdinfo->folder = new_item;
2172 g_free(old_id);
2173 g_free(new_id);
2174 return FALSE;
2177 static void compose_colorize_signature(Compose *compose)
2179 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2180 GtkTextIter iter;
2181 GtkTextIter end_iter;
2182 gtk_text_buffer_get_start_iter(buffer, &iter);
2183 while (gtk_text_iter_forward_line(&iter))
2184 if (compose_is_sig_separator(compose, buffer, &iter)) {
2185 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2186 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2190 #define BLOCK_WRAP() { \
2191 prev_autowrap = compose->autowrap; \
2192 buffer = gtk_text_view_get_buffer( \
2193 GTK_TEXT_VIEW(compose->text)); \
2194 compose->autowrap = FALSE; \
2196 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2197 G_CALLBACK(compose_changed_cb), \
2198 compose); \
2199 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2200 G_CALLBACK(text_inserted), \
2201 compose); \
2203 #define UNBLOCK_WRAP() { \
2204 compose->autowrap = prev_autowrap; \
2205 if (compose->autowrap) { \
2206 gint old = compose->draft_timeout_tag; \
2207 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2208 compose_wrap_all(compose); \
2209 compose->draft_timeout_tag = old; \
2212 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2213 G_CALLBACK(compose_changed_cb), \
2214 compose); \
2215 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2216 G_CALLBACK(text_inserted), \
2217 compose); \
2220 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2222 Compose *compose = NULL;
2223 PrefsAccount *account = NULL;
2224 GtkTextView *textview;
2225 GtkTextBuffer *textbuf;
2226 GtkTextMark *mark;
2227 GtkTextIter iter;
2228 FILE *fp;
2229 gboolean use_signing = FALSE;
2230 gboolean use_encryption = FALSE;
2231 gchar *privacy_system = NULL;
2232 int priority = PRIORITY_NORMAL;
2233 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2234 gboolean autowrap = prefs_common.autowrap;
2235 gboolean autoindent = prefs_common.auto_indent;
2236 HeaderEntry *manual_headers = NULL;
2238 cm_return_val_if_fail(msginfo != NULL, NULL);
2239 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2241 if (compose_put_existing_to_front(msginfo)) {
2242 return NULL;
2245 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2246 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2247 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2248 gchar *queueheader_buf = NULL;
2249 gint id, param;
2251 /* Select Account from queue headers */
2252 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2253 "X-Claws-Account-Id:")) {
2254 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2255 account = account_find_from_id(id);
2256 g_free(queueheader_buf);
2258 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2259 "X-Sylpheed-Account-Id:")) {
2260 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2261 account = account_find_from_id(id);
2262 g_free(queueheader_buf);
2264 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2265 "NAID:")) {
2266 id = atoi(&queueheader_buf[strlen("NAID:")]);
2267 account = account_find_from_id(id);
2268 g_free(queueheader_buf);
2270 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2271 "MAID:")) {
2272 id = atoi(&queueheader_buf[strlen("MAID:")]);
2273 account = account_find_from_id(id);
2274 g_free(queueheader_buf);
2276 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2277 "S:")) {
2278 account = account_find_from_address(queueheader_buf, FALSE);
2279 g_free(queueheader_buf);
2281 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2282 "X-Claws-Sign:")) {
2283 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2284 use_signing = param;
2285 g_free(queueheader_buf);
2287 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2288 "X-Sylpheed-Sign:")) {
2289 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2290 use_signing = param;
2291 g_free(queueheader_buf);
2293 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2294 "X-Claws-Encrypt:")) {
2295 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2296 use_encryption = param;
2297 g_free(queueheader_buf);
2299 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2300 "X-Sylpheed-Encrypt:")) {
2301 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2302 use_encryption = param;
2303 g_free(queueheader_buf);
2305 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2306 "X-Claws-Auto-Wrapping:")) {
2307 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2308 autowrap = param;
2309 g_free(queueheader_buf);
2311 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2312 "X-Claws-Auto-Indent:")) {
2313 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2314 autoindent = param;
2315 g_free(queueheader_buf);
2317 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2318 "X-Claws-Privacy-System:")) {
2319 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2320 g_free(queueheader_buf);
2322 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2323 "X-Sylpheed-Privacy-System:")) {
2324 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2325 g_free(queueheader_buf);
2327 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2328 "X-Priority: ")) {
2329 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2330 priority = param;
2331 g_free(queueheader_buf);
2333 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2334 "RMID:")) {
2335 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2336 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2337 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2338 if (orig_item != NULL) {
2339 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2341 g_strfreev(tokens);
2343 g_free(queueheader_buf);
2345 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2346 "FMID:")) {
2347 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2348 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2349 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2350 if (orig_item != NULL) {
2351 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2353 g_strfreev(tokens);
2355 g_free(queueheader_buf);
2357 /* Get manual headers */
2358 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2359 "X-Claws-Manual-Headers:")) {
2360 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2361 if (listmh && *listmh != '\0') {
2362 debug_print("Got manual headers: %s\n", listmh);
2363 manual_headers = procheader_entries_from_str(listmh);
2364 g_free(listmh);
2366 g_free(queueheader_buf);
2368 } else {
2369 account = msginfo->folder->folder->account;
2372 if (!account && prefs_common.reedit_account_autosel) {
2373 gchar *from = NULL;
2374 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2375 extract_address(from);
2376 account = account_find_from_address(from, FALSE);
2377 g_free(from);
2380 if (!account) {
2381 account = cur_account;
2383 cm_return_val_if_fail(account != NULL, NULL);
2385 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2387 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
2388 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
2389 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2390 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2391 compose->autowrap = autowrap;
2392 compose->replyinfo = replyinfo;
2393 compose->fwdinfo = fwdinfo;
2395 compose->updating = TRUE;
2396 compose->priority = priority;
2398 if (privacy_system != NULL) {
2399 compose->privacy_system = privacy_system;
2400 compose_use_signing(compose, use_signing);
2401 compose_use_encryption(compose, use_encryption);
2402 compose_update_privacy_system_menu_item(compose, FALSE);
2403 } else {
2404 compose_activate_privacy_system(compose, account, FALSE);
2406 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2407 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
2408 (account->default_encrypt || account->default_sign))
2409 COMPOSE_PRIVACY_WARNING();
2411 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2412 compose->targetinfo->tags = g_slist_copy(msginfo->tags);
2414 compose_extract_original_charset(compose);
2416 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2417 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2418 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2419 gchar *queueheader_buf = NULL;
2421 /* Set message save folder */
2422 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2423 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2424 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2425 compose_set_save_to(compose, &queueheader_buf[4]);
2426 g_free(queueheader_buf);
2428 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2429 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2430 if (active) {
2431 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2433 g_free(queueheader_buf);
2437 if (compose_parse_header(compose, msginfo) < 0) {
2438 compose->updating = FALSE;
2439 compose_destroy(compose);
2440 return NULL;
2442 compose_reedit_set_entry(compose, msginfo);
2444 textview = GTK_TEXT_VIEW(compose->text);
2445 textbuf = gtk_text_view_get_buffer(textview);
2446 compose_create_tags(textview, compose);
2448 mark = gtk_text_buffer_get_insert(textbuf);
2449 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2451 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2452 G_CALLBACK(compose_changed_cb),
2453 compose);
2455 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2456 fp = procmime_get_first_encrypted_text_content(msginfo);
2457 if (fp) {
2458 compose_force_encryption(compose, account, TRUE, NULL);
2460 } else {
2461 fp = procmime_get_first_text_content(msginfo);
2463 if (fp == NULL) {
2464 g_warning("Can't get text part");
2467 if (fp != NULL) {
2468 gchar buf[BUFFSIZE];
2469 gboolean prev_autowrap;
2470 GtkTextBuffer *buffer;
2471 BLOCK_WRAP();
2472 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2473 strcrchomp(buf);
2474 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2476 UNBLOCK_WRAP();
2477 claws_fclose(fp);
2480 compose_attach_parts(compose, msginfo);
2482 compose_colorize_signature(compose);
2484 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2485 G_CALLBACK(compose_changed_cb),
2486 compose);
2488 if (manual_headers != NULL) {
2489 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2490 procheader_entries_free(manual_headers);
2491 compose->updating = FALSE;
2492 compose_destroy(compose);
2493 return NULL;
2495 procheader_entries_free(manual_headers);
2498 gtk_widget_grab_focus(compose->text);
2500 if (prefs_common.auto_exteditor) {
2501 compose_exec_ext_editor(compose);
2503 compose->modified = FALSE;
2504 compose_set_title(compose);
2506 compose->updating = FALSE;
2507 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2508 SCROLL_TO_CURSOR(compose);
2510 if (compose->deferred_destroy) {
2511 compose_destroy(compose);
2512 return NULL;
2515 compose->sig_str = account_get_signature_str(compose->account);
2517 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2519 return compose;
2522 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2523 gboolean batch)
2525 Compose *compose;
2526 gchar *filename;
2527 FolderItem *item;
2529 cm_return_val_if_fail(msginfo != NULL, NULL);
2531 if (!account)
2532 account = account_get_reply_account(msginfo,
2533 prefs_common.reply_account_autosel);
2534 cm_return_val_if_fail(account != NULL, NULL);
2536 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2538 compose->updating = TRUE;
2540 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2541 compose->replyinfo = NULL;
2542 compose->fwdinfo = NULL;
2544 compose_show_first_last_header(compose, TRUE);
2546 gtk_widget_grab_focus(compose->header_last->entry);
2548 filename = procmsg_get_message_file(msginfo);
2550 if (filename == NULL) {
2551 compose->updating = FALSE;
2552 compose_destroy(compose);
2554 return NULL;
2557 compose->redirect_filename = filename;
2559 /* Set save folder */
2560 item = msginfo->folder;
2561 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2562 gchar *folderidentifier;
2564 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2565 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2566 folderidentifier = folder_item_get_identifier(item);
2567 compose_set_save_to(compose, folderidentifier);
2568 g_free(folderidentifier);
2571 compose_attach_parts(compose, msginfo);
2573 if (msginfo->subject)
2574 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2575 msginfo->subject);
2576 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2578 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2579 _("The body of the \"Redirect\" template has an error at line %d."));
2580 quote_fmt_reset_vartable();
2581 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2583 compose_colorize_signature(compose);
2586 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2587 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2588 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2590 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2591 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2592 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2593 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2594 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2595 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2596 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2597 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2598 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2600 if (compose->toolbar->draft_btn)
2601 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2602 if (compose->toolbar->insert_btn)
2603 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2604 if (compose->toolbar->attach_btn)
2605 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2606 if (compose->toolbar->sig_btn)
2607 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2608 if (compose->toolbar->exteditor_btn)
2609 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2610 if (compose->toolbar->linewrap_current_btn)
2611 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2612 if (compose->toolbar->linewrap_all_btn)
2613 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2614 if (compose->toolbar->privacy_sign_btn)
2615 gtk_widget_set_sensitive(compose->toolbar->privacy_sign_btn, FALSE);
2616 if (compose->toolbar->privacy_encrypt_btn)
2617 gtk_widget_set_sensitive(compose->toolbar->privacy_encrypt_btn, FALSE);
2619 compose->modified = FALSE;
2620 compose_set_title(compose);
2621 compose->updating = FALSE;
2622 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2623 SCROLL_TO_CURSOR(compose);
2625 if (compose->deferred_destroy) {
2626 compose_destroy(compose);
2627 return NULL;
2630 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2632 return compose;
2635 const GList *compose_get_compose_list(void)
2637 return compose_list;
2640 void compose_entry_append(Compose *compose, const gchar *address,
2641 ComposeEntryType type, ComposePrefType pref_type)
2643 const gchar *header;
2644 gchar *cur, *begin;
2645 gboolean in_quote = FALSE;
2646 if (!address || *address == '\0') return;
2648 switch (type) {
2649 case COMPOSE_CC:
2650 header = N_("Cc:");
2651 break;
2652 case COMPOSE_BCC:
2653 header = N_("Bcc:");
2654 break;
2655 case COMPOSE_REPLYTO:
2656 header = N_("Reply-To:");
2657 break;
2658 case COMPOSE_NEWSGROUPS:
2659 header = N_("Newsgroups:");
2660 break;
2661 case COMPOSE_FOLLOWUPTO:
2662 header = N_( "Followup-To:");
2663 break;
2664 case COMPOSE_INREPLYTO:
2665 header = N_( "In-Reply-To:");
2666 break;
2667 case COMPOSE_TO:
2668 default:
2669 header = N_("To:");
2670 break;
2672 header = prefs_common_translated_header_name(header);
2674 cur = begin = (gchar *)address;
2676 /* we separate the line by commas, but not if we're inside a quoted
2677 * string */
2678 while (*cur != '\0') {
2679 if (*cur == '"')
2680 in_quote = !in_quote;
2681 if (*cur == ',' && !in_quote) {
2682 gchar *tmp = g_strdup(begin);
2683 gchar *o_tmp = tmp;
2684 tmp[cur-begin]='\0';
2685 cur++;
2686 begin = cur;
2687 while (*tmp == ' ' || *tmp == '\t')
2688 tmp++;
2689 compose_add_header_entry(compose, header, tmp, pref_type);
2690 compose_entry_indicate(compose, tmp);
2691 g_free(o_tmp);
2692 continue;
2694 cur++;
2696 if (begin < cur) {
2697 gchar *tmp = g_strdup(begin);
2698 gchar *o_tmp = tmp;
2699 tmp[cur-begin]='\0';
2700 while (*tmp == ' ' || *tmp == '\t')
2701 tmp++;
2702 compose_add_header_entry(compose, header, tmp, pref_type);
2703 compose_entry_indicate(compose, tmp);
2704 g_free(o_tmp);
2708 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2710 GSList *h_list;
2711 GtkEntry *entry;
2713 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2714 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2715 if (gtk_entry_get_text(entry) &&
2716 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2717 gtk_widget_modify_base(
2718 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2719 GTK_STATE_NORMAL, &default_header_bgcolor);
2720 gtk_widget_modify_text(
2721 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2722 GTK_STATE_NORMAL, &default_header_color);
2727 void compose_toolbar_cb(gint action, gpointer data)
2729 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2730 Compose *compose = (Compose*)toolbar_item->parent;
2732 cm_return_if_fail(compose != NULL);
2734 switch(action) {
2735 case A_SEND:
2736 compose_send_cb(NULL, compose);
2737 break;
2738 case A_SEND_LATER:
2739 compose_send_later_cb(NULL, compose);
2740 break;
2741 case A_DRAFT:
2742 compose_draft(compose, COMPOSE_QUIT_EDITING);
2743 break;
2744 case A_INSERT:
2745 compose_insert_file_cb(NULL, compose);
2746 break;
2747 case A_ATTACH:
2748 compose_attach_cb(NULL, compose);
2749 break;
2750 case A_SIG:
2751 compose_insert_sig(compose, FALSE);
2752 break;
2753 case A_REP_SIG:
2754 compose_insert_sig(compose, TRUE);
2755 break;
2756 case A_EXTEDITOR:
2757 compose_ext_editor_cb(NULL, compose);
2758 break;
2759 case A_LINEWRAP_CURRENT:
2760 compose_beautify_paragraph(compose, NULL, TRUE);
2761 break;
2762 case A_LINEWRAP_ALL:
2763 compose_wrap_all_full(compose, TRUE);
2764 break;
2765 case A_ADDRBOOK:
2766 compose_address_cb(NULL, compose);
2767 break;
2768 #ifdef USE_ENCHANT
2769 case A_CHECK_SPELLING:
2770 compose_check_all(NULL, compose);
2771 break;
2772 #endif
2773 case A_PRIVACY_SIGN:
2774 break;
2775 case A_PRIVACY_ENCRYPT:
2776 break;
2777 default:
2778 break;
2782 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2784 gchar *to = NULL;
2785 gchar *cc = NULL;
2786 gchar *bcc = NULL;
2787 gchar *subject = NULL;
2788 gchar *body = NULL;
2789 gchar *temp = NULL;
2790 gsize len = 0;
2791 gchar **attach = NULL;
2792 gchar *inreplyto = NULL;
2793 MailField mfield = NO_FIELD_PRESENT;
2795 /* get mailto parts but skip from */
2796 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2798 if (to) {
2799 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2800 mfield = TO_FIELD_PRESENT;
2802 if (cc)
2803 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2804 if (bcc)
2805 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2806 if (subject) {
2807 if (!g_utf8_validate (subject, -1, NULL)) {
2808 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2809 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2810 g_free(temp);
2811 } else {
2812 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2814 mfield = SUBJECT_FIELD_PRESENT;
2816 if (body) {
2817 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2818 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2819 GtkTextMark *mark;
2820 GtkTextIter iter;
2821 gboolean prev_autowrap = compose->autowrap;
2823 compose->autowrap = FALSE;
2825 mark = gtk_text_buffer_get_insert(buffer);
2826 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2828 if (!g_utf8_validate (body, -1, NULL)) {
2829 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2830 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2831 g_free(temp);
2832 } else {
2833 gtk_text_buffer_insert(buffer, &iter, body, -1);
2835 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2837 compose->autowrap = prev_autowrap;
2838 if (compose->autowrap)
2839 compose_wrap_all(compose);
2840 mfield = BODY_FIELD_PRESENT;
2843 if (attach) {
2844 gint i = 0, att = 0;
2845 gchar *warn_files = NULL;
2846 while (attach[i] != NULL) {
2847 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2848 if (utf8_filename) {
2849 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2850 gchar *tmp = g_strdup_printf("%s%s\n",
2851 warn_files?warn_files:"",
2852 utf8_filename);
2853 g_free(warn_files);
2854 warn_files = tmp;
2855 att++;
2857 g_free(utf8_filename);
2858 } else {
2859 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2861 i++;
2863 if (warn_files) {
2864 alertpanel_notice(ngettext(
2865 "The following file has been attached: \n%s",
2866 "The following files have been attached: \n%s", att), warn_files);
2867 g_free(warn_files);
2870 if (inreplyto)
2871 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2873 g_free(to);
2874 g_free(cc);
2875 g_free(bcc);
2876 g_free(subject);
2877 g_free(body);
2878 g_strfreev(attach);
2879 g_free(inreplyto);
2881 return mfield;
2884 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2886 static HeaderEntry hentry[] = {
2887 {"Reply-To:", NULL, TRUE },
2888 {"Cc:", NULL, TRUE },
2889 {"References:", NULL, FALSE },
2890 {"Bcc:", NULL, TRUE },
2891 {"Newsgroups:", NULL, TRUE },
2892 {"Followup-To:", NULL, TRUE },
2893 {"List-Post:", NULL, FALSE },
2894 {"X-Priority:", NULL, FALSE },
2895 {NULL, NULL, FALSE }
2898 enum
2900 H_REPLY_TO = 0,
2901 H_CC = 1,
2902 H_REFERENCES = 2,
2903 H_BCC = 3,
2904 H_NEWSGROUPS = 4,
2905 H_FOLLOWUP_TO = 5,
2906 H_LIST_POST = 6,
2907 H_X_PRIORITY = 7
2910 FILE *fp;
2912 cm_return_val_if_fail(msginfo != NULL, -1);
2914 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
2915 procheader_get_header_fields(fp, hentry);
2916 claws_fclose(fp);
2918 if (hentry[H_REPLY_TO].body != NULL) {
2919 if (hentry[H_REPLY_TO].body[0] != '\0') {
2920 compose->replyto =
2921 conv_unmime_header(hentry[H_REPLY_TO].body,
2922 NULL, TRUE);
2924 g_free(hentry[H_REPLY_TO].body);
2925 hentry[H_REPLY_TO].body = NULL;
2927 if (hentry[H_CC].body != NULL) {
2928 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2929 g_free(hentry[H_CC].body);
2930 hentry[H_CC].body = NULL;
2932 if (hentry[H_REFERENCES].body != NULL) {
2933 if (compose->mode == COMPOSE_REEDIT)
2934 compose->references = hentry[H_REFERENCES].body;
2935 else {
2936 compose->references = compose_parse_references
2937 (hentry[H_REFERENCES].body, msginfo->msgid);
2938 g_free(hentry[H_REFERENCES].body);
2940 hentry[H_REFERENCES].body = NULL;
2942 if (hentry[H_BCC].body != NULL) {
2943 if (compose->mode == COMPOSE_REEDIT)
2944 compose->bcc =
2945 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2946 g_free(hentry[H_BCC].body);
2947 hentry[H_BCC].body = NULL;
2949 if (hentry[H_NEWSGROUPS].body != NULL) {
2950 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2951 hentry[H_NEWSGROUPS].body = NULL;
2953 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2954 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2955 compose->followup_to =
2956 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2957 NULL, TRUE);
2959 g_free(hentry[H_FOLLOWUP_TO].body);
2960 hentry[H_FOLLOWUP_TO].body = NULL;
2962 if (hentry[H_LIST_POST].body != NULL) {
2963 gchar *to = NULL, *start = NULL;
2965 extract_address(hentry[H_LIST_POST].body);
2966 if (hentry[H_LIST_POST].body[0] != '\0') {
2967 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2969 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2970 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2972 if (to) {
2973 g_free(compose->ml_post);
2974 compose->ml_post = to;
2977 g_free(hentry[H_LIST_POST].body);
2978 hentry[H_LIST_POST].body = NULL;
2981 /* CLAWS - X-Priority */
2982 if (compose->mode == COMPOSE_REEDIT)
2983 if (hentry[H_X_PRIORITY].body != NULL) {
2984 gint priority;
2986 priority = atoi(hentry[H_X_PRIORITY].body);
2987 g_free(hentry[H_X_PRIORITY].body);
2989 hentry[H_X_PRIORITY].body = NULL;
2991 if (priority < PRIORITY_HIGHEST ||
2992 priority > PRIORITY_LOWEST)
2993 priority = PRIORITY_NORMAL;
2995 compose->priority = priority;
2998 if (compose->mode == COMPOSE_REEDIT) {
2999 if (msginfo->inreplyto && *msginfo->inreplyto)
3000 compose->inreplyto = g_strdup(msginfo->inreplyto);
3002 if (msginfo->msgid && *msginfo->msgid &&
3003 compose->folder != NULL &&
3004 compose->folder->stype == F_DRAFT)
3005 compose->msgid = g_strdup(msginfo->msgid);
3006 } else {
3007 if (msginfo->msgid && *msginfo->msgid)
3008 compose->inreplyto = g_strdup(msginfo->msgid);
3010 if (!compose->references) {
3011 if (msginfo->msgid && *msginfo->msgid) {
3012 if (msginfo->inreplyto && *msginfo->inreplyto)
3013 compose->references =
3014 g_strdup_printf("<%s>\n\t<%s>",
3015 msginfo->inreplyto,
3016 msginfo->msgid);
3017 else
3018 compose->references =
3019 g_strconcat("<", msginfo->msgid, ">",
3020 NULL);
3021 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3022 compose->references =
3023 g_strconcat("<", msginfo->inreplyto, ">",
3024 NULL);
3029 return 0;
3032 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3034 FILE *fp;
3035 HeaderEntry *he;
3037 cm_return_val_if_fail(msginfo != NULL, -1);
3039 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
3040 procheader_get_header_fields(fp, entries);
3041 claws_fclose(fp);
3043 he = entries;
3044 while (he != NULL && he->name != NULL) {
3045 GtkTreeIter iter;
3046 GtkListStore *model = NULL;
3048 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3049 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3050 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3051 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3052 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3053 ++he;
3056 return 0;
3059 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3061 GSList *ref_id_list, *cur;
3062 GString *new_ref;
3063 gchar *new_ref_str;
3065 ref_id_list = references_list_append(NULL, ref);
3066 if (!ref_id_list) return NULL;
3067 if (msgid && *msgid)
3068 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3070 for (;;) {
3071 gint len = 0;
3073 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3074 /* "<" + Message-ID + ">" + CR+LF+TAB */
3075 len += strlen((gchar *)cur->data) + 5;
3077 if (len > MAX_REFERENCES_LEN) {
3078 /* remove second message-ID */
3079 if (ref_id_list && ref_id_list->next &&
3080 ref_id_list->next->next) {
3081 g_free(ref_id_list->next->data);
3082 ref_id_list = g_slist_remove
3083 (ref_id_list, ref_id_list->next->data);
3084 } else {
3085 slist_free_strings_full(ref_id_list);
3086 return NULL;
3088 } else
3089 break;
3092 new_ref = g_string_new("");
3093 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3094 if (new_ref->len > 0)
3095 g_string_append(new_ref, "\n\t");
3096 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3099 slist_free_strings_full(ref_id_list);
3101 new_ref_str = new_ref->str;
3102 g_string_free(new_ref, FALSE);
3104 return new_ref_str;
3107 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3108 const gchar *fmt, const gchar *qmark,
3109 const gchar *body, gboolean rewrap,
3110 gboolean need_unescape,
3111 const gchar *err_msg)
3113 MsgInfo* dummyinfo = NULL;
3114 gchar *quote_str = NULL;
3115 gchar *buf;
3116 gboolean prev_autowrap;
3117 const gchar *trimmed_body = body;
3118 gint cursor_pos = -1;
3119 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3120 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3121 GtkTextIter iter;
3122 GtkTextMark *mark;
3125 SIGNAL_BLOCK(buffer);
3127 if (!msginfo) {
3128 dummyinfo = compose_msginfo_new_from_compose(compose);
3129 msginfo = dummyinfo;
3132 if (qmark != NULL) {
3133 #ifdef USE_ENCHANT
3134 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3135 compose->gtkaspell);
3136 #else
3137 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3138 #endif
3139 quote_fmt_scan_string(qmark);
3140 quote_fmt_parse();
3142 buf = quote_fmt_get_buffer();
3144 if (buf == NULL)
3145 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3146 else
3147 Xstrdup_a(quote_str, buf, goto error)
3150 if (fmt && *fmt != '\0') {
3152 if (trimmed_body)
3153 while (*trimmed_body == '\n')
3154 trimmed_body++;
3156 #ifdef USE_ENCHANT
3157 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3158 compose->gtkaspell);
3159 #else
3160 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3161 #endif
3162 if (need_unescape) {
3163 gchar *tmp = NULL;
3165 /* decode \-escape sequences in the internal representation of the quote format */
3166 tmp = g_malloc(strlen(fmt)+1);
3167 pref_get_unescaped_pref(tmp, fmt);
3168 quote_fmt_scan_string(tmp);
3169 quote_fmt_parse();
3170 g_free(tmp);
3171 } else {
3172 quote_fmt_scan_string(fmt);
3173 quote_fmt_parse();
3176 buf = quote_fmt_get_buffer();
3178 if (buf == NULL) {
3179 gint line = quote_fmt_get_line();
3180 alertpanel_error(err_msg, line);
3182 goto error;
3185 } else
3186 buf = "";
3188 prev_autowrap = compose->autowrap;
3189 compose->autowrap = FALSE;
3191 mark = gtk_text_buffer_get_insert(buffer);
3192 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3193 if (g_utf8_validate(buf, -1, NULL)) {
3194 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3195 } else {
3196 gchar *tmpout = NULL;
3197 tmpout = conv_codeset_strdup
3198 (buf, conv_get_locale_charset_str_no_utf8(),
3199 CS_INTERNAL);
3200 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3201 g_free(tmpout);
3202 tmpout = g_malloc(strlen(buf)*2+1);
3203 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3205 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3206 g_free(tmpout);
3209 cursor_pos = quote_fmt_get_cursor_pos();
3210 if (cursor_pos == -1)
3211 cursor_pos = gtk_text_iter_get_offset(&iter);
3212 compose->set_cursor_pos = cursor_pos;
3214 gtk_text_buffer_get_start_iter(buffer, &iter);
3215 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3216 gtk_text_buffer_place_cursor(buffer, &iter);
3218 compose->autowrap = prev_autowrap;
3219 if (compose->autowrap && rewrap)
3220 compose_wrap_all(compose);
3222 goto ok;
3224 error:
3225 buf = NULL;
3227 SIGNAL_UNBLOCK(buffer);
3229 procmsg_msginfo_free( &dummyinfo );
3231 return buf;
3234 /* if ml_post is of type addr@host and from is of type
3235 * addr-anything@host, return TRUE
3237 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3239 gchar *left_ml = NULL;
3240 gchar *right_ml = NULL;
3241 gchar *left_from = NULL;
3242 gchar *right_from = NULL;
3243 gboolean result = FALSE;
3245 if (!ml_post || !from)
3246 return FALSE;
3248 left_ml = g_strdup(ml_post);
3249 if (strstr(left_ml, "@")) {
3250 right_ml = strstr(left_ml, "@")+1;
3251 *(strstr(left_ml, "@")) = '\0';
3254 left_from = g_strdup(from);
3255 if (strstr(left_from, "@")) {
3256 right_from = strstr(left_from, "@")+1;
3257 *(strstr(left_from, "@")) = '\0';
3260 if (right_ml && right_from
3261 && !strncmp(left_from, left_ml, strlen(left_ml))
3262 && !strcmp(right_from, right_ml)) {
3263 result = TRUE;
3265 g_free(left_ml);
3266 g_free(left_from);
3268 return result;
3271 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3272 gboolean respect_default_to)
3274 if (!compose)
3275 return;
3276 if (!folder || !folder->prefs)
3277 return;
3279 if (respect_default_to && folder->prefs->enable_default_to) {
3280 compose_entry_append(compose, folder->prefs->default_to,
3281 COMPOSE_TO, PREF_FOLDER);
3282 compose_entry_indicate(compose, folder->prefs->default_to);
3284 if (folder->prefs->enable_default_cc) {
3285 compose_entry_append(compose, folder->prefs->default_cc,
3286 COMPOSE_CC, PREF_FOLDER);
3287 compose_entry_indicate(compose, folder->prefs->default_cc);
3289 if (folder->prefs->enable_default_bcc) {
3290 compose_entry_append(compose, folder->prefs->default_bcc,
3291 COMPOSE_BCC, PREF_FOLDER);
3292 compose_entry_indicate(compose, folder->prefs->default_bcc);
3294 if (folder->prefs->enable_default_replyto) {
3295 compose_entry_append(compose, folder->prefs->default_replyto,
3296 COMPOSE_REPLYTO, PREF_FOLDER);
3297 compose_entry_indicate(compose, folder->prefs->default_replyto);
3301 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3303 gchar *buf, *buf2;
3304 gchar *p;
3306 if (!compose || !msginfo)
3307 return;
3309 if (msginfo->subject && *msginfo->subject) {
3310 buf = p = g_strdup(msginfo->subject);
3311 p += subject_get_prefix_length(p);
3312 memmove(buf, p, strlen(p) + 1);
3314 buf2 = g_strdup_printf("Re: %s", buf);
3315 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3317 g_free(buf2);
3318 g_free(buf);
3319 } else
3320 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3323 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3324 gboolean to_all, gboolean to_ml,
3325 gboolean to_sender,
3326 gboolean followup_and_reply_to)
3328 GSList *cc_list = NULL;
3329 GSList *cur;
3330 gchar *from = NULL;
3331 gchar *replyto = NULL;
3332 gchar *ac_email = NULL;
3334 gboolean reply_to_ml = FALSE;
3335 gboolean default_reply_to = FALSE;
3337 cm_return_if_fail(compose->account != NULL);
3338 cm_return_if_fail(msginfo != NULL);
3340 reply_to_ml = to_ml && compose->ml_post;
3342 default_reply_to = msginfo->folder &&
3343 msginfo->folder->prefs->enable_default_reply_to;
3345 if (compose->account->protocol != A_NNTP) {
3346 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3348 if (reply_to_ml && !default_reply_to) {
3350 gboolean is_subscr = is_subscription(compose->ml_post,
3351 msginfo->from);
3352 if (!is_subscr) {
3353 /* normal answer to ml post with a reply-to */
3354 compose_entry_append(compose,
3355 compose->ml_post,
3356 COMPOSE_TO, PREF_ML);
3357 if (compose->replyto)
3358 compose_entry_append(compose,
3359 compose->replyto,
3360 COMPOSE_CC, PREF_ML);
3361 } else {
3362 /* answer to subscription confirmation */
3363 if (compose->replyto)
3364 compose_entry_append(compose,
3365 compose->replyto,
3366 COMPOSE_TO, PREF_ML);
3367 else if (msginfo->from)
3368 compose_entry_append(compose,
3369 msginfo->from,
3370 COMPOSE_TO, PREF_ML);
3373 else if (!(to_all || to_sender) && default_reply_to) {
3374 compose_entry_append(compose,
3375 msginfo->folder->prefs->default_reply_to,
3376 COMPOSE_TO, PREF_FOLDER);
3377 compose_entry_indicate(compose,
3378 msginfo->folder->prefs->default_reply_to);
3379 } else {
3380 gchar *tmp1 = NULL;
3381 if (!msginfo->from)
3382 return;
3383 if (to_sender)
3384 compose_entry_append(compose, msginfo->from,
3385 COMPOSE_TO, PREF_NONE);
3386 else if (to_all) {
3387 Xstrdup_a(tmp1, msginfo->from, return);
3388 extract_address(tmp1);
3389 compose_entry_append(compose,
3390 (!account_find_from_address(tmp1, FALSE))
3391 ? msginfo->from :
3392 msginfo->to,
3393 COMPOSE_TO, PREF_NONE);
3394 if (compose->replyto)
3395 compose_entry_append(compose,
3396 compose->replyto,
3397 COMPOSE_CC, PREF_NONE);
3398 } else {
3399 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3400 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3401 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3402 if (compose->replyto) {
3403 compose_entry_append(compose,
3404 compose->replyto,
3405 COMPOSE_TO, PREF_NONE);
3406 } else {
3407 compose_entry_append(compose,
3408 msginfo->from ? msginfo->from : "",
3409 COMPOSE_TO, PREF_NONE);
3411 } else {
3412 /* replying to own mail, use original recp */
3413 compose_entry_append(compose,
3414 msginfo->to ? msginfo->to : "",
3415 COMPOSE_TO, PREF_NONE);
3416 compose_entry_append(compose,
3417 msginfo->cc ? msginfo->cc : "",
3418 COMPOSE_CC, PREF_NONE);
3422 } else {
3423 if (to_sender || (compose->followup_to &&
3424 !strncmp(compose->followup_to, "poster", 6)))
3425 compose_entry_append
3426 (compose,
3427 (compose->replyto ? compose->replyto :
3428 msginfo->from ? msginfo->from : ""),
3429 COMPOSE_TO, PREF_NONE);
3431 else if (followup_and_reply_to || to_all) {
3432 compose_entry_append
3433 (compose,
3434 (compose->replyto ? compose->replyto :
3435 msginfo->from ? msginfo->from : ""),
3436 COMPOSE_TO, PREF_NONE);
3438 compose_entry_append
3439 (compose,
3440 compose->followup_to ? compose->followup_to :
3441 compose->newsgroups ? compose->newsgroups : "",
3442 COMPOSE_NEWSGROUPS, PREF_NONE);
3444 compose_entry_append
3445 (compose,
3446 msginfo->cc ? msginfo->cc : "",
3447 COMPOSE_CC, PREF_NONE);
3449 else
3450 compose_entry_append
3451 (compose,
3452 compose->followup_to ? compose->followup_to :
3453 compose->newsgroups ? compose->newsgroups : "",
3454 COMPOSE_NEWSGROUPS, PREF_NONE);
3456 compose_reply_set_subject(compose, msginfo);
3458 if (to_ml && compose->ml_post) return;
3459 if (!to_all || compose->account->protocol == A_NNTP) return;
3461 if (compose->replyto) {
3462 Xstrdup_a(replyto, compose->replyto, return);
3463 extract_address(replyto);
3465 if (msginfo->from) {
3466 Xstrdup_a(from, msginfo->from, return);
3467 extract_address(from);
3470 if (replyto && from)
3471 cc_list = address_list_append_with_comments(cc_list, from);
3472 if (to_all && msginfo->folder &&
3473 msginfo->folder->prefs->enable_default_reply_to)
3474 cc_list = address_list_append_with_comments(cc_list,
3475 msginfo->folder->prefs->default_reply_to);
3476 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3477 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3479 ac_email = g_utf8_strdown(compose->account->address, -1);
3481 if (cc_list) {
3482 for (cur = cc_list; cur != NULL; cur = cur->next) {
3483 gchar *addr = g_utf8_strdown(cur->data, -1);
3484 extract_address(addr);
3486 if (strcmp(ac_email, addr))
3487 compose_entry_append(compose, (gchar *)cur->data,
3488 COMPOSE_CC, PREF_NONE);
3489 else
3490 debug_print("Cc address same as compose account's, ignoring\n");
3492 g_free(addr);
3495 slist_free_strings_full(cc_list);
3498 g_free(ac_email);
3501 #define SET_ENTRY(entry, str) \
3503 if (str && *str) \
3504 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3507 #define SET_ADDRESS(type, str) \
3509 if (str && *str) \
3510 compose_entry_append(compose, str, type, PREF_NONE); \
3513 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3515 cm_return_if_fail(msginfo != NULL);
3517 SET_ENTRY(subject_entry, msginfo->subject);
3518 SET_ENTRY(from_name, msginfo->from);
3519 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3520 SET_ADDRESS(COMPOSE_CC, compose->cc);
3521 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3522 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3523 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3524 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3526 compose_update_priority_menu_item(compose);
3527 compose_update_privacy_system_menu_item(compose, FALSE);
3528 compose_show_first_last_header(compose, TRUE);
3531 #undef SET_ENTRY
3532 #undef SET_ADDRESS
3534 static void compose_insert_sig(Compose *compose, gboolean replace)
3536 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3537 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3538 GtkTextMark *mark;
3539 GtkTextIter iter, iter_end;
3540 gint cur_pos, ins_pos;
3541 gboolean prev_autowrap;
3542 gboolean found = FALSE;
3543 gboolean exists = FALSE;
3545 cm_return_if_fail(compose->account != NULL);
3547 BLOCK_WRAP();
3549 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3550 G_CALLBACK(compose_changed_cb),
3551 compose);
3553 mark = gtk_text_buffer_get_insert(buffer);
3554 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3555 cur_pos = gtk_text_iter_get_offset (&iter);
3556 ins_pos = cur_pos;
3558 gtk_text_buffer_get_end_iter(buffer, &iter);
3560 exists = (compose->sig_str != NULL);
3562 if (replace) {
3563 GtkTextIter first_iter, start_iter, end_iter;
3565 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3567 if (!exists || compose->sig_str[0] == '\0')
3568 found = FALSE;
3569 else
3570 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3571 compose->signature_tag);
3573 if (found) {
3574 /* include previous \n\n */
3575 gtk_text_iter_backward_chars(&first_iter, 1);
3576 start_iter = first_iter;
3577 end_iter = first_iter;
3578 /* skip re-start */
3579 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3580 compose->signature_tag);
3581 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3582 compose->signature_tag);
3583 if (found) {
3584 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3585 iter = start_iter;
3590 g_free(compose->sig_str);
3591 compose->sig_str = account_get_signature_str(compose->account);
3593 cur_pos = gtk_text_iter_get_offset(&iter);
3595 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3596 g_free(compose->sig_str);
3597 compose->sig_str = NULL;
3598 } else {
3599 if (compose->sig_inserted == FALSE)
3600 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3601 compose->sig_inserted = TRUE;
3603 cur_pos = gtk_text_iter_get_offset(&iter);
3604 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3605 /* remove \n\n */
3606 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3607 gtk_text_iter_forward_chars(&iter, 1);
3608 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3609 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3611 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3612 cur_pos = gtk_text_buffer_get_char_count (buffer);
3615 /* put the cursor where it should be
3616 * either where the quote_fmt says, either where it was */
3617 if (compose->set_cursor_pos < 0)
3618 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3619 else
3620 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3621 compose->set_cursor_pos);
3623 compose->set_cursor_pos = -1;
3624 gtk_text_buffer_place_cursor(buffer, &iter);
3625 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3626 G_CALLBACK(compose_changed_cb),
3627 compose);
3629 UNBLOCK_WRAP();
3632 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3634 GtkTextView *text;
3635 GtkTextBuffer *buffer;
3636 GtkTextMark *mark;
3637 GtkTextIter iter;
3638 const gchar *cur_encoding;
3639 gchar buf[BUFFSIZE];
3640 gint len;
3641 FILE *fp;
3642 gboolean prev_autowrap;
3643 #ifdef G_OS_WIN32
3644 GFile *f;
3645 GFileInfo *fi;
3646 GError *error = NULL;
3647 #else
3648 GStatBuf file_stat;
3649 #endif
3650 int ret;
3651 goffset size;
3652 GString *file_contents = NULL;
3653 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3655 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3657 /* get the size of the file we are about to insert */
3658 #ifdef G_OS_WIN32
3659 f = g_file_new_for_path(file);
3660 fi = g_file_query_info(f, "standard::size",
3661 G_FILE_QUERY_INFO_NONE, NULL, &error);
3662 ret = 0;
3663 if (error != NULL) {
3664 g_warning(error->message);
3665 ret = 1;
3666 g_error_free(error);
3667 g_object_unref(f);
3669 #else
3670 ret = g_stat(file, &file_stat);
3671 #endif
3672 if (ret != 0) {
3673 gchar *shortfile = g_path_get_basename(file);
3674 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3675 g_free(shortfile);
3676 return COMPOSE_INSERT_NO_FILE;
3677 } else if (prefs_common.warn_large_insert == TRUE) {
3678 #ifdef G_OS_WIN32
3679 size = g_file_info_get_size(fi);
3680 g_object_unref(fi);
3681 g_object_unref(f);
3682 #else
3683 size = file_stat.st_size;
3684 #endif
3686 /* ask user for confirmation if the file is large */
3687 if (prefs_common.warn_large_insert_size < 0 ||
3688 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3689 AlertValue aval;
3690 gchar *msg;
3692 msg = g_strdup_printf(_("You are about to insert a file of %s "
3693 "in the message body. Are you sure you want to do that?"),
3694 to_human_readable(size));
3695 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3696 _("_Insert"), NULL, ALERTFOCUS_SECOND, TRUE,
3697 NULL, ALERT_QUESTION);
3698 g_free(msg);
3700 /* do we ask for confirmation next time? */
3701 if (aval & G_ALERTDISABLE) {
3702 /* no confirmation next time, disable feature in preferences */
3703 aval &= ~G_ALERTDISABLE;
3704 prefs_common.warn_large_insert = FALSE;
3707 /* abort file insertion if user canceled action */
3708 if (aval != G_ALERTALTERNATE) {
3709 return COMPOSE_INSERT_NO_FILE;
3715 if ((fp = claws_fopen(file, "rb")) == NULL) {
3716 FILE_OP_ERROR(file, "claws_fopen");
3717 return COMPOSE_INSERT_READ_ERROR;
3720 prev_autowrap = compose->autowrap;
3721 compose->autowrap = FALSE;
3723 text = GTK_TEXT_VIEW(compose->text);
3724 buffer = gtk_text_view_get_buffer(text);
3725 mark = gtk_text_buffer_get_insert(buffer);
3726 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3728 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3729 G_CALLBACK(text_inserted),
3730 compose);
3732 cur_encoding = conv_get_locale_charset_str_no_utf8();
3734 file_contents = g_string_new("");
3735 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3736 gchar *str;
3738 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3739 str = g_strdup(buf);
3740 else {
3741 codeconv_set_strict(TRUE);
3742 str = conv_codeset_strdup
3743 (buf, cur_encoding, CS_INTERNAL);
3744 codeconv_set_strict(FALSE);
3746 if (!str) {
3747 result = COMPOSE_INSERT_INVALID_CHARACTER;
3748 break;
3751 if (!str) continue;
3753 /* strip <CR> if DOS/Windows file,
3754 replace <CR> with <LF> if Macintosh file. */
3755 strcrchomp(str);
3756 len = strlen(str);
3757 if (len > 0 && str[len - 1] != '\n') {
3758 while (--len >= 0)
3759 if (str[len] == '\r') str[len] = '\n';
3762 file_contents = g_string_append(file_contents, str);
3763 g_free(str);
3766 if (result == COMPOSE_INSERT_SUCCESS) {
3767 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3769 compose_changed_cb(NULL, compose);
3770 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3771 G_CALLBACK(text_inserted),
3772 compose);
3773 compose->autowrap = prev_autowrap;
3774 if (compose->autowrap)
3775 compose_wrap_all(compose);
3778 g_string_free(file_contents, TRUE);
3779 claws_fclose(fp);
3781 return result;
3784 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3785 const gchar *filename,
3786 const gchar *content_type,
3787 const gchar *charset)
3789 AttachInfo *ainfo;
3790 GtkTreeIter iter;
3791 FILE *fp;
3792 off_t size;
3793 GAuto *auto_ainfo;
3794 gchar *size_text;
3795 GtkListStore *store;
3796 gchar *name;
3797 gboolean has_binary = FALSE;
3799 if (!is_file_exist(file)) {
3800 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3801 gboolean result = FALSE;
3802 if (file_from_uri && is_file_exist(file_from_uri)) {
3803 result = compose_attach_append(
3804 compose, file_from_uri,
3805 filename, content_type,
3806 charset);
3808 g_free(file_from_uri);
3809 if (result)
3810 return TRUE;
3811 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3812 return FALSE;
3814 if ((size = get_file_size(file)) < 0) {
3815 alertpanel_error("Can't get file size of %s\n", filename);
3816 return FALSE;
3819 /* In batch mode, we allow 0-length files to be attached no questions asked */
3820 if (size == 0 && !compose->batch) {
3821 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3822 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3823 GTK_STOCK_CANCEL, _("_Attach anyway"), NULL,
3824 ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3825 g_free(msg);
3827 if (aval != G_ALERTALTERNATE) {
3828 return FALSE;
3831 if ((fp = claws_fopen(file, "rb")) == NULL) {
3832 alertpanel_error(_("Can't read %s."), filename);
3833 return FALSE;
3835 claws_fclose(fp);
3837 ainfo = g_new0(AttachInfo, 1);
3838 auto_ainfo = g_auto_pointer_new_with_free
3839 (ainfo, (GFreeFunc) compose_attach_info_free);
3840 ainfo->file = g_strdup(file);
3842 if (content_type) {
3843 ainfo->content_type = g_strdup(content_type);
3844 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3845 MsgInfo *msginfo;
3846 MsgFlags flags = {0, 0};
3848 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3849 ainfo->encoding = ENC_7BIT;
3850 else
3851 ainfo->encoding = ENC_8BIT;
3853 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3854 if (msginfo && msginfo->subject)
3855 name = g_strdup(msginfo->subject);
3856 else
3857 name = g_path_get_basename(filename ? filename : file);
3859 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3861 procmsg_msginfo_free(&msginfo);
3862 } else {
3863 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3864 ainfo->charset = g_strdup(charset);
3865 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3866 } else {
3867 ainfo->encoding = ENC_BASE64;
3869 name = g_path_get_basename(filename ? filename : file);
3870 ainfo->name = g_strdup(name);
3872 g_free(name);
3873 } else {
3874 ainfo->content_type = procmime_get_mime_type(file);
3875 if (!ainfo->content_type) {
3876 ainfo->content_type =
3877 g_strdup("application/octet-stream");
3878 ainfo->encoding = ENC_BASE64;
3879 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3880 ainfo->encoding =
3881 procmime_get_encoding_for_text_file(file, &has_binary);
3882 else
3883 ainfo->encoding = ENC_BASE64;
3884 name = g_path_get_basename(filename ? filename : file);
3885 ainfo->name = g_strdup(name);
3886 g_free(name);
3889 if (ainfo->name != NULL
3890 && !strcmp(ainfo->name, ".")) {
3891 g_free(ainfo->name);
3892 ainfo->name = NULL;
3895 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3896 g_free(ainfo->content_type);
3897 ainfo->content_type = g_strdup("application/octet-stream");
3898 g_free(ainfo->charset);
3899 ainfo->charset = NULL;
3902 ainfo->size = (goffset)size;
3903 size_text = to_human_readable((goffset)size);
3905 store = GTK_LIST_STORE(gtk_tree_view_get_model
3906 (GTK_TREE_VIEW(compose->attach_clist)));
3908 gtk_list_store_append(store, &iter);
3909 gtk_list_store_set(store, &iter,
3910 COL_MIMETYPE, ainfo->content_type,
3911 COL_SIZE, size_text,
3912 COL_NAME, ainfo->name,
3913 COL_CHARSET, ainfo->charset,
3914 COL_DATA, ainfo,
3915 COL_AUTODATA, auto_ainfo,
3916 -1);
3918 g_auto_pointer_free(auto_ainfo);
3919 compose_attach_update_label(compose);
3920 return TRUE;
3923 void compose_use_signing(Compose *compose, gboolean use_signing)
3925 compose->use_signing = use_signing;
3926 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3929 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3931 compose->use_encryption = use_encryption;
3932 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3935 #define NEXT_PART_NOT_CHILD(info) \
3937 node = info->node; \
3938 while (node->children) \
3939 node = g_node_last_child(node); \
3940 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3943 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3945 MimeInfo *mimeinfo;
3946 MimeInfo *child;
3947 MimeInfo *firsttext = NULL;
3948 MimeInfo *encrypted = NULL;
3949 GNode *node;
3950 gchar *outfile;
3951 const gchar *partname = NULL;
3953 mimeinfo = procmime_scan_message(msginfo);
3954 if (!mimeinfo) return;
3956 if (mimeinfo->node->children == NULL) {
3957 procmime_mimeinfo_free_all(&mimeinfo);
3958 return;
3961 /* find first content part */
3962 child = (MimeInfo *) mimeinfo->node->children->data;
3963 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3964 child = (MimeInfo *)child->node->children->data;
3966 if (child) {
3967 if (child->type == MIMETYPE_TEXT) {
3968 firsttext = child;
3969 debug_print("First text part found\n");
3970 } else if (compose->mode == COMPOSE_REEDIT &&
3971 child->type == MIMETYPE_APPLICATION &&
3972 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3973 encrypted = (MimeInfo *)child->node->parent->data;
3976 child = (MimeInfo *) mimeinfo->node->children->data;
3977 while (child != NULL) {
3978 gint err;
3980 if (child == encrypted) {
3981 /* skip this part of tree */
3982 NEXT_PART_NOT_CHILD(child);
3983 continue;
3986 if (child->type == MIMETYPE_MULTIPART) {
3987 /* get the actual content */
3988 child = procmime_mimeinfo_next(child);
3989 continue;
3992 if (child == firsttext) {
3993 child = procmime_mimeinfo_next(child);
3994 continue;
3997 outfile = procmime_get_tmp_file_name(child);
3998 if ((err = procmime_get_part(outfile, child)) < 0)
3999 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
4000 else {
4001 gchar *content_type;
4003 content_type = procmime_get_content_type_str(child->type, child->subtype);
4005 /* if we meet a pgp signature, we don't attach it, but
4006 * we force signing. */
4007 if ((strcmp(content_type, "application/pgp-signature") &&
4008 strcmp(content_type, "application/pkcs7-signature") &&
4009 strcmp(content_type, "application/x-pkcs7-signature"))
4010 || compose->mode == COMPOSE_REDIRECT) {
4011 partname = procmime_mimeinfo_get_parameter(child, "filename");
4012 if (partname == NULL)
4013 partname = procmime_mimeinfo_get_parameter(child, "name");
4014 if (partname == NULL)
4015 partname = "";
4016 compose_attach_append(compose, outfile,
4017 partname, content_type,
4018 procmime_mimeinfo_get_parameter(child, "charset"));
4019 } else {
4020 compose_force_signing(compose, compose->account, NULL);
4022 g_free(content_type);
4024 g_free(outfile);
4025 NEXT_PART_NOT_CHILD(child);
4027 procmime_mimeinfo_free_all(&mimeinfo);
4030 #undef NEXT_PART_NOT_CHILD
4034 typedef enum {
4035 WAIT_FOR_INDENT_CHAR,
4036 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4037 } IndentState;
4039 /* return indent length, we allow:
4040 indent characters followed by indent characters or spaces/tabs,
4041 alphabets and numbers immediately followed by indent characters,
4042 and the repeating sequences of the above
4043 If quote ends with multiple spaces, only the first one is included. */
4044 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4045 const GtkTextIter *start, gint *len)
4047 GtkTextIter iter = *start;
4048 gunichar wc;
4049 gchar ch[6];
4050 gint clen;
4051 IndentState state = WAIT_FOR_INDENT_CHAR;
4052 gboolean is_space;
4053 gboolean is_indent;
4054 gint alnum_count = 0;
4055 gint space_count = 0;
4056 gint quote_len = 0;
4058 if (prefs_common.quote_chars == NULL) {
4059 return 0 ;
4062 while (!gtk_text_iter_ends_line(&iter)) {
4063 wc = gtk_text_iter_get_char(&iter);
4064 if (g_unichar_iswide(wc))
4065 break;
4066 clen = g_unichar_to_utf8(wc, ch);
4067 if (clen != 1)
4068 break;
4070 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4071 is_space = g_unichar_isspace(wc);
4073 if (state == WAIT_FOR_INDENT_CHAR) {
4074 if (!is_indent && !g_unichar_isalnum(wc))
4075 break;
4076 if (is_indent) {
4077 quote_len += alnum_count + space_count + 1;
4078 alnum_count = space_count = 0;
4079 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4080 } else
4081 alnum_count++;
4082 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4083 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4084 break;
4085 if (is_space)
4086 space_count++;
4087 else if (is_indent) {
4088 quote_len += alnum_count + space_count + 1;
4089 alnum_count = space_count = 0;
4090 } else {
4091 alnum_count++;
4092 state = WAIT_FOR_INDENT_CHAR;
4096 gtk_text_iter_forward_char(&iter);
4099 if (quote_len > 0 && space_count > 0)
4100 quote_len++;
4102 if (len)
4103 *len = quote_len;
4105 if (quote_len > 0) {
4106 iter = *start;
4107 gtk_text_iter_forward_chars(&iter, quote_len);
4108 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4111 return NULL;
4114 /* return >0 if the line is itemized */
4115 static int compose_itemized_length(GtkTextBuffer *buffer,
4116 const GtkTextIter *start)
4118 GtkTextIter iter = *start;
4119 gunichar wc;
4120 gchar ch[6];
4121 gint clen;
4122 gint len = 0;
4123 if (gtk_text_iter_ends_line(&iter))
4124 return 0;
4126 while (1) {
4127 len++;
4128 wc = gtk_text_iter_get_char(&iter);
4129 if (!g_unichar_isspace(wc))
4130 break;
4131 gtk_text_iter_forward_char(&iter);
4132 if (gtk_text_iter_ends_line(&iter))
4133 return 0;
4136 clen = g_unichar_to_utf8(wc, ch);
4137 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4138 (clen == 3 && (
4139 wc == 0x2022 || /* BULLET */
4140 wc == 0x2023 || /* TRIANGULAR BULLET */
4141 wc == 0x2043 || /* HYPHEN BULLET */
4142 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4143 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4144 wc == 0x2219 || /* BULLET OPERATOR */
4145 wc == 0x25d8 || /* INVERSE BULLET */
4146 wc == 0x25e6 || /* WHITE BULLET */
4147 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4148 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4149 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4150 wc == 0x29be || /* CIRCLED WHITE BULLET */
4151 wc == 0x29bf /* CIRCLED BULLET */
4152 ))))
4153 return 0;
4155 gtk_text_iter_forward_char(&iter);
4156 if (gtk_text_iter_ends_line(&iter))
4157 return 0;
4158 wc = gtk_text_iter_get_char(&iter);
4159 if (g_unichar_isspace(wc)) {
4160 return len+1;
4162 return 0;
4165 /* return the string at the start of the itemization */
4166 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4167 const GtkTextIter *start)
4169 GtkTextIter iter = *start;
4170 gunichar wc;
4171 gint len = 0;
4172 GString *item_chars = g_string_new("");
4173 gchar *str = NULL;
4175 if (gtk_text_iter_ends_line(&iter))
4176 return NULL;
4178 while (1) {
4179 len++;
4180 wc = gtk_text_iter_get_char(&iter);
4181 if (!g_unichar_isspace(wc))
4182 break;
4183 gtk_text_iter_forward_char(&iter);
4184 if (gtk_text_iter_ends_line(&iter))
4185 break;
4186 g_string_append_unichar(item_chars, wc);
4189 str = item_chars->str;
4190 g_string_free(item_chars, FALSE);
4191 return str;
4194 /* return the number of spaces at a line's start */
4195 static int compose_left_offset_length(GtkTextBuffer *buffer,
4196 const GtkTextIter *start)
4198 GtkTextIter iter = *start;
4199 gunichar wc;
4200 gint len = 0;
4201 if (gtk_text_iter_ends_line(&iter))
4202 return 0;
4204 while (1) {
4205 wc = gtk_text_iter_get_char(&iter);
4206 if (!g_unichar_isspace(wc))
4207 break;
4208 len++;
4209 gtk_text_iter_forward_char(&iter);
4210 if (gtk_text_iter_ends_line(&iter))
4211 return 0;
4214 gtk_text_iter_forward_char(&iter);
4215 if (gtk_text_iter_ends_line(&iter))
4216 return 0;
4217 return len;
4220 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4221 const GtkTextIter *start,
4222 GtkTextIter *break_pos,
4223 gint max_col,
4224 gint quote_len)
4226 GtkTextIter iter = *start, line_end = *start;
4227 PangoLogAttr *attrs;
4228 gchar *str;
4229 gchar *p;
4230 gint len;
4231 gint i;
4232 gint col = 0;
4233 gint pos = 0;
4234 gboolean can_break = FALSE;
4235 gboolean do_break = FALSE;
4236 gboolean was_white = FALSE;
4237 gboolean prev_dont_break = FALSE;
4239 gtk_text_iter_forward_to_line_end(&line_end);
4240 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4241 len = g_utf8_strlen(str, -1);
4243 if (len == 0) {
4244 g_free(str);
4245 g_warning("compose_get_line_break_pos: len = 0!");
4246 return FALSE;
4249 /* g_print("breaking line: %d: %s (len = %d)\n",
4250 gtk_text_iter_get_line(&iter), str, len); */
4252 attrs = g_new(PangoLogAttr, len + 1);
4254 pango_default_break(str, -1, NULL, attrs, len + 1);
4256 p = str;
4258 /* skip quote and leading spaces */
4259 for (i = 0; *p != '\0' && i < len; i++) {
4260 gunichar wc;
4262 wc = g_utf8_get_char(p);
4263 if (i >= quote_len && !g_unichar_isspace(wc))
4264 break;
4265 if (g_unichar_iswide(wc))
4266 col += 2;
4267 else if (*p == '\t')
4268 col += 8;
4269 else
4270 col++;
4271 p = g_utf8_next_char(p);
4274 for (; *p != '\0' && i < len; i++) {
4275 PangoLogAttr *attr = attrs + i;
4276 gunichar wc = g_utf8_get_char(p);
4277 gint uri_len;
4279 /* attr->is_line_break will be false for some characters that
4280 * we want to break a line before, like '/' or ':', so we
4281 * also allow breaking on any non-wide character. The
4282 * mentioned pango attribute is still useful to decide on
4283 * line breaks when wide characters are involved. */
4284 if ((!g_unichar_iswide(wc) || attr->is_line_break)
4285 && can_break && was_white && !prev_dont_break)
4286 pos = i;
4288 was_white = attr->is_white;
4290 /* don't wrap URI */
4291 if ((uri_len = get_uri_len(p)) > 0) {
4292 col += uri_len;
4293 if (pos > 0 && col > max_col) {
4294 do_break = TRUE;
4295 break;
4297 i += uri_len - 1;
4298 p += uri_len;
4299 can_break = TRUE;
4300 continue;
4303 if (g_unichar_iswide(wc)) {
4304 col += 2;
4305 if (prev_dont_break && can_break && attr->is_line_break)
4306 pos = i;
4307 } else if (*p == '\t')
4308 col += 8;
4309 else
4310 col++;
4311 if (pos > 0 && col > max_col) {
4312 do_break = TRUE;
4313 break;
4316 if (*p == '-' || *p == '/')
4317 prev_dont_break = TRUE;
4318 else
4319 prev_dont_break = FALSE;
4321 p = g_utf8_next_char(p);
4322 can_break = TRUE;
4325 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4327 g_free(attrs);
4328 g_free(str);
4330 *break_pos = *start;
4331 gtk_text_iter_set_line_offset(break_pos, pos);
4333 return do_break;
4336 static gboolean compose_join_next_line(Compose *compose,
4337 GtkTextBuffer *buffer,
4338 GtkTextIter *iter,
4339 const gchar *quote_str)
4341 GtkTextIter iter_ = *iter, cur, prev, next, end;
4342 PangoLogAttr attrs[3];
4343 gchar *str;
4344 gchar *next_quote_str;
4345 gunichar wc1, wc2;
4346 gint quote_len;
4347 gboolean keep_cursor = FALSE;
4349 if (!gtk_text_iter_forward_line(&iter_) ||
4350 gtk_text_iter_ends_line(&iter_)) {
4351 return FALSE;
4353 next_quote_str = compose_get_quote_str(buffer, &iter_, &quote_len);
4355 if ((quote_str || next_quote_str) &&
4356 g_strcmp0(quote_str, next_quote_str) != 0) {
4357 g_free(next_quote_str);
4358 return FALSE;
4360 g_free(next_quote_str);
4362 end = iter_;
4363 if (quote_len > 0) {
4364 gtk_text_iter_forward_chars(&end, quote_len);
4365 if (gtk_text_iter_ends_line(&end)) {
4366 return FALSE;
4370 /* don't join itemized lines */
4371 if (compose_itemized_length(buffer, &end) > 0) {
4372 return FALSE;
4375 /* don't join signature separator */
4376 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4377 return FALSE;
4379 /* delete quote str */
4380 if (quote_len > 0)
4381 gtk_text_buffer_delete(buffer, &iter_, &end);
4383 /* don't join line breaks put by the user */
4384 prev = cur = iter_;
4385 gtk_text_iter_backward_char(&cur);
4386 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4387 gtk_text_iter_forward_char(&cur);
4388 *iter = cur;
4389 return FALSE;
4391 gtk_text_iter_forward_char(&cur);
4392 /* delete linebreak and extra spaces */
4393 while (gtk_text_iter_backward_char(&cur)) {
4394 wc1 = gtk_text_iter_get_char(&cur);
4395 if (!g_unichar_isspace(wc1))
4396 break;
4397 prev = cur;
4399 next = cur = iter_;
4400 while (!gtk_text_iter_ends_line(&cur)) {
4401 wc1 = gtk_text_iter_get_char(&cur);
4402 if (!g_unichar_isspace(wc1))
4403 break;
4404 gtk_text_iter_forward_char(&cur);
4405 next = cur;
4407 if (!gtk_text_iter_equal(&prev, &next)) {
4408 GtkTextMark *mark;
4410 mark = gtk_text_buffer_get_insert(buffer);
4411 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4412 if (gtk_text_iter_equal(&prev, &cur))
4413 keep_cursor = TRUE;
4414 gtk_text_buffer_delete(buffer, &prev, &next);
4416 iter_ = prev;
4418 /* insert space if required */
4419 gtk_text_iter_backward_char(&prev);
4420 wc1 = gtk_text_iter_get_char(&prev);
4421 wc2 = gtk_text_iter_get_char(&next);
4422 gtk_text_iter_forward_char(&next);
4423 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4424 pango_default_break(str, -1, NULL, attrs, 3);
4425 if (!attrs[1].is_line_break ||
4426 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4427 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4428 if (keep_cursor) {
4429 gtk_text_iter_backward_char(&iter_);
4430 gtk_text_buffer_place_cursor(buffer, &iter_);
4433 g_free(str);
4435 *iter = iter_;
4436 return TRUE;
4439 #define ADD_TXT_POS(bp_, ep_, pti_) \
4440 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4441 last = last->next; \
4442 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4443 last->next = NULL; \
4444 } else { \
4445 g_warning("alloc error scanning URIs"); \
4448 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4450 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4451 GtkTextBuffer *buffer;
4452 GtkTextIter iter, break_pos, end_of_line;
4453 gchar *quote_str = NULL;
4454 gint quote_len;
4455 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4456 gboolean prev_autowrap = compose->autowrap;
4457 gint startq_offset = -1, noq_offset = -1;
4458 gint uri_start = -1, uri_stop = -1;
4459 gint nouri_start = -1, nouri_stop = -1;
4460 gint num_blocks = 0;
4461 gint quotelevel = -1;
4462 gboolean modified = force;
4463 gboolean removed = FALSE;
4464 gboolean modified_before_remove = FALSE;
4465 gint lines = 0;
4466 gboolean start = TRUE;
4467 gint itemized_len = 0, rem_item_len = 0;
4468 gchar *itemized_chars = NULL;
4469 gboolean item_continuation = FALSE;
4471 if (force) {
4472 modified = TRUE;
4474 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4475 modified = TRUE;
4478 compose->autowrap = FALSE;
4480 buffer = gtk_text_view_get_buffer(text);
4481 undo_wrapping(compose->undostruct, TRUE);
4482 if (par_iter) {
4483 iter = *par_iter;
4484 } else {
4485 GtkTextMark *mark;
4486 mark = gtk_text_buffer_get_insert(buffer);
4487 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4491 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4492 if (gtk_text_iter_ends_line(&iter)) {
4493 while (gtk_text_iter_ends_line(&iter) &&
4494 gtk_text_iter_forward_line(&iter))
4496 } else {
4497 while (gtk_text_iter_backward_line(&iter)) {
4498 if (gtk_text_iter_ends_line(&iter)) {
4499 gtk_text_iter_forward_line(&iter);
4500 break;
4504 } else {
4505 /* move to line start */
4506 gtk_text_iter_set_line_offset(&iter, 0);
4509 itemized_len = compose_itemized_length(buffer, &iter);
4511 if (!itemized_len) {
4512 itemized_len = compose_left_offset_length(buffer, &iter);
4513 item_continuation = TRUE;
4516 if (itemized_len)
4517 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4519 /* go until paragraph end (empty line) */
4520 while (start || !gtk_text_iter_ends_line(&iter)) {
4521 gchar *scanpos = NULL;
4522 /* parse table - in order of priority */
4523 struct table {
4524 const gchar *needle; /* token */
4526 /* token search function */
4527 gchar *(*search) (const gchar *haystack,
4528 const gchar *needle);
4529 /* part parsing function */
4530 gboolean (*parse) (const gchar *start,
4531 const gchar *scanpos,
4532 const gchar **bp_,
4533 const gchar **ep_,
4534 gboolean hdr);
4535 /* part to URI function */
4536 gchar *(*build_uri) (const gchar *bp,
4537 const gchar *ep);
4540 static struct table parser[] = {
4541 {"http://", strcasestr, get_uri_part, make_uri_string},
4542 {"https://", strcasestr, get_uri_part, make_uri_string},
4543 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4544 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4545 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4546 {"www.", strcasestr, get_uri_part, make_http_string},
4547 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4548 {"@", strcasestr, get_email_part, make_email_string}
4550 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4551 gint last_index = PARSE_ELEMS;
4552 gint n;
4553 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4554 gint walk_pos;
4556 start = FALSE;
4557 if (!prev_autowrap && num_blocks == 0) {
4558 num_blocks++;
4559 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4560 G_CALLBACK(text_inserted),
4561 compose);
4563 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4564 goto colorize;
4566 uri_start = uri_stop = -1;
4567 quote_len = 0;
4568 quote_str = compose_get_quote_str(buffer, &iter, &quote_len);
4570 if (quote_str) {
4571 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4572 if (startq_offset == -1)
4573 startq_offset = gtk_text_iter_get_offset(&iter);
4574 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4575 if (quotelevel > 2) {
4576 /* recycle colors */
4577 if (prefs_common.recycle_quote_colors)
4578 quotelevel %= 3;
4579 else
4580 quotelevel = 2;
4582 if (!wrap_quote) {
4583 goto colorize;
4585 } else {
4586 if (startq_offset == -1)
4587 noq_offset = gtk_text_iter_get_offset(&iter);
4588 quotelevel = -1;
4591 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4592 goto colorize;
4594 if (gtk_text_iter_ends_line(&iter)) {
4595 goto colorize;
4596 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4597 prefs_common.linewrap_len,
4598 quote_len)) {
4599 GtkTextIter prev, next, cur;
4600 if (prev_autowrap != FALSE || force) {
4601 compose->automatic_break = TRUE;
4602 modified = TRUE;
4603 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4604 compose->automatic_break = FALSE;
4605 if (itemized_len && compose->autoindent) {
4606 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4607 if (!item_continuation)
4608 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4610 } else if (quote_str && wrap_quote) {
4611 compose->automatic_break = TRUE;
4612 modified = TRUE;
4613 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4614 compose->automatic_break = FALSE;
4615 if (itemized_len && compose->autoindent) {
4616 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4617 if (!item_continuation)
4618 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4620 } else
4621 goto colorize;
4622 /* remove trailing spaces */
4623 cur = break_pos;
4624 rem_item_len = itemized_len;
4625 while (compose->autoindent && rem_item_len-- > 0)
4626 gtk_text_iter_backward_char(&cur);
4627 gtk_text_iter_backward_char(&cur);
4629 prev = next = cur;
4630 while (!gtk_text_iter_starts_line(&cur)) {
4631 gunichar wc;
4633 gtk_text_iter_backward_char(&cur);
4634 wc = gtk_text_iter_get_char(&cur);
4635 if (!g_unichar_isspace(wc))
4636 break;
4637 prev = cur;
4639 if (!gtk_text_iter_equal(&prev, &next)) {
4640 gtk_text_buffer_delete(buffer, &prev, &next);
4641 break_pos = next;
4642 gtk_text_iter_forward_char(&break_pos);
4645 if (quote_str)
4646 gtk_text_buffer_insert(buffer, &break_pos,
4647 quote_str, -1);
4649 iter = break_pos;
4650 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4652 /* move iter to current line start */
4653 gtk_text_iter_set_line_offset(&iter, 0);
4654 if (quote_str) {
4655 g_free(quote_str);
4656 quote_str = NULL;
4658 continue;
4659 } else {
4660 /* move iter to next line start */
4661 iter = break_pos;
4662 lines++;
4665 colorize:
4666 if (!prev_autowrap && num_blocks > 0) {
4667 num_blocks--;
4668 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4669 G_CALLBACK(text_inserted),
4670 compose);
4672 end_of_line = iter;
4673 while (!gtk_text_iter_ends_line(&end_of_line)) {
4674 gtk_text_iter_forward_char(&end_of_line);
4676 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4678 nouri_start = gtk_text_iter_get_offset(&iter);
4679 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4681 walk_pos = gtk_text_iter_get_offset(&iter);
4682 /* FIXME: this looks phony. scanning for anything in the parse table */
4683 for (n = 0; n < PARSE_ELEMS; n++) {
4684 gchar *tmp;
4686 tmp = parser[n].search(walk, parser[n].needle);
4687 if (tmp) {
4688 if (scanpos == NULL || tmp < scanpos) {
4689 scanpos = tmp;
4690 last_index = n;
4695 bp = ep = 0;
4696 if (scanpos) {
4697 /* check if URI can be parsed */
4698 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4699 (const gchar **)&ep, FALSE)
4700 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4701 walk = ep;
4702 } else
4703 walk = scanpos +
4704 strlen(parser[last_index].needle);
4706 if (bp && ep) {
4707 uri_start = walk_pos + (bp - o_walk);
4708 uri_stop = walk_pos + (ep - o_walk);
4710 g_free(o_walk);
4711 o_walk = NULL;
4712 gtk_text_iter_forward_line(&iter);
4713 g_free(quote_str);
4714 quote_str = NULL;
4715 if (startq_offset != -1) {
4716 GtkTextIter startquote, endquote;
4717 gtk_text_buffer_get_iter_at_offset(
4718 buffer, &startquote, startq_offset);
4719 endquote = iter;
4721 switch (quotelevel) {
4722 case 0:
4723 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4724 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4725 gtk_text_buffer_apply_tag_by_name(
4726 buffer, "quote0", &startquote, &endquote);
4727 gtk_text_buffer_remove_tag_by_name(
4728 buffer, "quote1", &startquote, &endquote);
4729 gtk_text_buffer_remove_tag_by_name(
4730 buffer, "quote2", &startquote, &endquote);
4731 modified = TRUE;
4733 break;
4734 case 1:
4735 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4736 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4737 gtk_text_buffer_apply_tag_by_name(
4738 buffer, "quote1", &startquote, &endquote);
4739 gtk_text_buffer_remove_tag_by_name(
4740 buffer, "quote0", &startquote, &endquote);
4741 gtk_text_buffer_remove_tag_by_name(
4742 buffer, "quote2", &startquote, &endquote);
4743 modified = TRUE;
4745 break;
4746 case 2:
4747 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4748 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4749 gtk_text_buffer_apply_tag_by_name(
4750 buffer, "quote2", &startquote, &endquote);
4751 gtk_text_buffer_remove_tag_by_name(
4752 buffer, "quote0", &startquote, &endquote);
4753 gtk_text_buffer_remove_tag_by_name(
4754 buffer, "quote1", &startquote, &endquote);
4755 modified = TRUE;
4757 break;
4759 startq_offset = -1;
4760 } else if (noq_offset != -1) {
4761 GtkTextIter startnoquote, endnoquote;
4762 gtk_text_buffer_get_iter_at_offset(
4763 buffer, &startnoquote, noq_offset);
4764 endnoquote = iter;
4766 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4767 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4768 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4769 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4770 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4771 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4772 gtk_text_buffer_remove_tag_by_name(
4773 buffer, "quote0", &startnoquote, &endnoquote);
4774 gtk_text_buffer_remove_tag_by_name(
4775 buffer, "quote1", &startnoquote, &endnoquote);
4776 gtk_text_buffer_remove_tag_by_name(
4777 buffer, "quote2", &startnoquote, &endnoquote);
4778 modified = TRUE;
4780 noq_offset = -1;
4783 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4784 GtkTextIter nouri_start_iter, nouri_end_iter;
4785 gtk_text_buffer_get_iter_at_offset(
4786 buffer, &nouri_start_iter, nouri_start);
4787 gtk_text_buffer_get_iter_at_offset(
4788 buffer, &nouri_end_iter, nouri_stop);
4789 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4790 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4791 gtk_text_buffer_remove_tag_by_name(
4792 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4793 modified_before_remove = modified;
4794 modified = TRUE;
4795 removed = TRUE;
4798 if (uri_start >= 0 && uri_stop > 0) {
4799 GtkTextIter uri_start_iter, uri_end_iter, back;
4800 gtk_text_buffer_get_iter_at_offset(
4801 buffer, &uri_start_iter, uri_start);
4802 gtk_text_buffer_get_iter_at_offset(
4803 buffer, &uri_end_iter, uri_stop);
4804 back = uri_end_iter;
4805 gtk_text_iter_backward_char(&back);
4806 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4807 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4808 gtk_text_buffer_apply_tag_by_name(
4809 buffer, "link", &uri_start_iter, &uri_end_iter);
4810 modified = TRUE;
4811 if (removed && !modified_before_remove) {
4812 modified = FALSE;
4816 if (!modified) {
4817 /* debug_print("not modified, out after %d lines\n", lines); */
4818 goto end;
4821 /* debug_print("modified, out after %d lines\n", lines); */
4822 end:
4823 g_free(itemized_chars);
4824 if (par_iter)
4825 *par_iter = iter;
4826 undo_wrapping(compose->undostruct, FALSE);
4827 compose->autowrap = prev_autowrap;
4829 return modified;
4832 void compose_action_cb(void *data)
4834 Compose *compose = (Compose *)data;
4835 compose_wrap_all(compose);
4838 static void compose_wrap_all(Compose *compose)
4840 compose_wrap_all_full(compose, FALSE);
4843 static void compose_wrap_all_full(Compose *compose, gboolean force)
4845 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4846 GtkTextBuffer *buffer;
4847 GtkTextIter iter;
4848 gboolean modified = TRUE;
4850 buffer = gtk_text_view_get_buffer(text);
4852 gtk_text_buffer_get_start_iter(buffer, &iter);
4854 undo_wrapping(compose->undostruct, TRUE);
4856 while (!gtk_text_iter_is_end(&iter) && modified)
4857 modified = compose_beautify_paragraph(compose, &iter, force);
4859 undo_wrapping(compose->undostruct, FALSE);
4863 static void compose_set_title(Compose *compose)
4865 gchar *str;
4866 gchar *edited;
4867 gchar *subject;
4869 edited = compose->modified ? _(" [Edited]") : "";
4871 subject = gtk_editable_get_chars(
4872 GTK_EDITABLE(compose->subject_entry), 0, -1);
4874 #ifndef GENERIC_UMPC
4875 if (subject && strlen(subject))
4876 str = g_strdup_printf(_("%s - Compose message%s"),
4877 subject, edited);
4878 else
4879 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4880 #else
4881 str = g_strdup(_("Compose message"));
4882 #endif
4884 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4885 g_free(str);
4886 g_free(subject);
4890 * compose_current_mail_account:
4892 * Find a current mail account (the currently selected account, or the
4893 * default account, if a news account is currently selected). If a
4894 * mail account cannot be found, display an error message.
4896 * Return value: Mail account, or NULL if not found.
4898 static PrefsAccount *
4899 compose_current_mail_account(void)
4901 PrefsAccount *ac;
4903 if (cur_account && cur_account->protocol != A_NNTP)
4904 ac = cur_account;
4905 else {
4906 ac = account_get_default();
4907 if (!ac || ac->protocol == A_NNTP) {
4908 alertpanel_error(_("Account for sending mail is not specified.\n"
4909 "Please select a mail account before sending."));
4910 return NULL;
4913 return ac;
4916 #define QUOTE_IF_REQUIRED(out, str) \
4918 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4919 gchar *__tmp; \
4920 gint len; \
4922 len = strlen(str) + 3; \
4923 if ((__tmp = alloca(len)) == NULL) { \
4924 g_warning("can't allocate memory"); \
4925 g_string_free(header, TRUE); \
4926 return NULL; \
4928 g_snprintf(__tmp, len, "\"%s\"", str); \
4929 out = __tmp; \
4930 } else { \
4931 gchar *__tmp; \
4933 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4934 g_warning("can't allocate memory"); \
4935 g_string_free(header, TRUE); \
4936 return NULL; \
4937 } else \
4938 strcpy(__tmp, str); \
4940 out = __tmp; \
4944 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4946 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4947 gchar *__tmp; \
4948 gint len; \
4950 len = strlen(str) + 3; \
4951 if ((__tmp = alloca(len)) == NULL) { \
4952 g_warning("can't allocate memory"); \
4953 errret; \
4955 g_snprintf(__tmp, len, "\"%s\"", str); \
4956 out = __tmp; \
4957 } else { \
4958 gchar *__tmp; \
4960 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4961 g_warning("can't allocate memory"); \
4962 errret; \
4963 } else \
4964 strcpy(__tmp, str); \
4966 out = __tmp; \
4970 static void compose_select_account(Compose *compose, PrefsAccount *account,
4971 gboolean init)
4973 gchar *from = NULL, *header = NULL;
4974 ComposeHeaderEntry *header_entry;
4975 GtkTreeIter iter;
4977 cm_return_if_fail(account != NULL);
4979 compose->account = account;
4980 if (account->name && *account->name) {
4981 gchar *buf, *qbuf;
4982 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4983 qbuf = escape_internal_quotes(buf, '"');
4984 from = g_strdup_printf("%s <%s>",
4985 qbuf, account->address);
4986 if (qbuf != buf)
4987 g_free(qbuf);
4988 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4989 } else {
4990 from = g_strdup_printf("<%s>",
4991 account->address);
4992 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4995 g_free(from);
4997 compose_set_title(compose);
4999 compose_activate_privacy_system(compose, account, FALSE);
5001 if (account->default_sign && privacy_system_can_sign(compose->privacy_system) &&
5002 compose->mode != COMPOSE_REDIRECT)
5003 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
5004 else
5005 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
5006 if (account->default_encrypt && privacy_system_can_encrypt(compose->privacy_system) &&
5007 compose->mode != COMPOSE_REDIRECT)
5008 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
5009 else
5010 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
5012 if (!init && compose->mode != COMPOSE_REDIRECT) {
5013 undo_block(compose->undostruct);
5014 compose_insert_sig(compose, TRUE);
5015 undo_unblock(compose->undostruct);
5018 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
5019 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
5020 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
5021 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5023 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5024 if (account->protocol == A_NNTP) {
5025 if (!strcmp(header, _("To:")))
5026 combobox_select_by_text(
5027 GTK_COMBO_BOX(header_entry->combo),
5028 _("Newsgroups:"));
5029 } else {
5030 if (!strcmp(header, _("Newsgroups:")))
5031 combobox_select_by_text(
5032 GTK_COMBO_BOX(header_entry->combo),
5033 _("To:"));
5037 g_free(header);
5039 #ifdef USE_ENCHANT
5040 /* use account's dict info if set */
5041 if (compose->gtkaspell) {
5042 if (account->enable_default_dictionary)
5043 gtkaspell_change_dict(compose->gtkaspell,
5044 account->default_dictionary, FALSE);
5045 if (account->enable_default_alt_dictionary)
5046 gtkaspell_change_alt_dict(compose->gtkaspell,
5047 account->default_alt_dictionary);
5048 if (account->enable_default_dictionary
5049 || account->enable_default_alt_dictionary)
5050 compose_spell_menu_changed(compose);
5052 #endif
5055 gboolean compose_check_for_valid_recipient(Compose *compose) {
5056 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5057 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5058 gboolean recipient_found = FALSE;
5059 GSList *list;
5060 gchar **strptr;
5062 /* free to and newsgroup list */
5063 slist_free_strings_full(compose->to_list);
5064 compose->to_list = NULL;
5066 slist_free_strings_full(compose->newsgroup_list);
5067 compose->newsgroup_list = NULL;
5069 /* search header entries for to and newsgroup entries */
5070 for (list = compose->header_list; list; list = list->next) {
5071 gchar *header;
5072 gchar *entry;
5073 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5074 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5075 g_strstrip(entry);
5076 g_strstrip(header);
5077 if (entry[0] != '\0') {
5078 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5079 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5080 compose->to_list = address_list_append(compose->to_list, entry);
5081 recipient_found = TRUE;
5084 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5085 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5086 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5087 recipient_found = TRUE;
5091 g_free(header);
5092 g_free(entry);
5094 return recipient_found;
5097 static gboolean compose_check_for_set_recipients(Compose *compose)
5099 if (compose->account->set_autocc && compose->account->auto_cc) {
5100 gboolean found_other = FALSE;
5101 GSList *list;
5102 /* search header entries for to and newsgroup entries */
5103 for (list = compose->header_list; list; list = list->next) {
5104 gchar *entry;
5105 gchar *header;
5106 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5107 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5108 g_strstrip(entry);
5109 g_strstrip(header);
5110 if (strcmp(entry, compose->account->auto_cc)
5111 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5112 found_other = TRUE;
5113 g_free(entry);
5114 break;
5116 g_free(entry);
5117 g_free(header);
5119 if (!found_other) {
5120 AlertValue aval;
5121 gchar *text;
5122 if (compose->batch) {
5123 gtk_widget_show_all(compose->window);
5125 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5126 prefs_common_translated_header_name("Cc"));
5127 aval = alertpanel(_("Send"),
5128 text,
5129 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5130 g_free(text);
5131 if (aval != G_ALERTALTERNATE)
5132 return FALSE;
5135 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5136 gboolean found_other = FALSE;
5137 GSList *list;
5138 /* search header entries for to and newsgroup entries */
5139 for (list = compose->header_list; list; list = list->next) {
5140 gchar *entry;
5141 gchar *header;
5142 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5143 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5144 g_strstrip(entry);
5145 g_strstrip(header);
5146 if (strcmp(entry, compose->account->auto_bcc)
5147 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5148 found_other = TRUE;
5149 g_free(entry);
5150 g_free(header);
5151 break;
5153 g_free(entry);
5154 g_free(header);
5156 if (!found_other) {
5157 AlertValue aval;
5158 gchar *text;
5159 if (compose->batch) {
5160 gtk_widget_show_all(compose->window);
5162 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5163 prefs_common_translated_header_name("Bcc"));
5164 aval = alertpanel(_("Send"),
5165 text,
5166 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5167 g_free(text);
5168 if (aval != G_ALERTALTERNATE)
5169 return FALSE;
5172 return TRUE;
5175 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5177 const gchar *str;
5179 if (compose_check_for_valid_recipient(compose) == FALSE) {
5180 if (compose->batch) {
5181 gtk_widget_show_all(compose->window);
5183 alertpanel_error(_("Recipient is not specified."));
5184 return FALSE;
5187 if (compose_check_for_set_recipients(compose) == FALSE) {
5188 return FALSE;
5191 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5192 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5193 if (*str == '\0' && check_everything == TRUE &&
5194 compose->mode != COMPOSE_REDIRECT) {
5195 AlertValue aval;
5196 gchar *message;
5198 message = g_strdup_printf(_("Subject is empty. %s"),
5199 compose->sending?_("Send it anyway?"):
5200 _("Queue it anyway?"));
5202 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5203 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5204 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5205 g_free(message);
5206 if (aval & G_ALERTDISABLE) {
5207 aval &= ~G_ALERTDISABLE;
5208 prefs_common.warn_empty_subj = FALSE;
5210 if (aval != G_ALERTALTERNATE)
5211 return FALSE;
5215 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5216 && check_everything == TRUE) {
5217 GSList *list;
5218 gint cnt = 0;
5220 /* count To and Cc recipients */
5221 for (list = compose->header_list; list; list = list->next) {
5222 gchar *header;
5223 gchar *entry;
5225 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5226 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5227 g_strstrip(header);
5228 g_strstrip(entry);
5229 if ((entry[0] != '\0') &&
5230 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5231 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5232 cnt++;
5234 g_free(header);
5235 g_free(entry);
5237 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5238 AlertValue aval;
5239 gchar *message;
5241 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5242 compose->sending?_("Send it anyway?"):
5243 _("Queue it anyway?"));
5245 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5246 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5247 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5248 g_free(message);
5249 if (aval & G_ALERTDISABLE) {
5250 aval &= ~G_ALERTDISABLE;
5251 prefs_common.warn_sending_many_recipients_num = 0;
5253 if (aval != G_ALERTALTERNATE)
5254 return FALSE;
5258 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5259 return FALSE;
5261 return TRUE;
5264 static void _display_queue_error(ComposeQueueResult val)
5266 switch (val) {
5267 case COMPOSE_QUEUE_SUCCESS:
5268 break;
5269 case COMPOSE_QUEUE_ERROR_NO_MSG:
5270 alertpanel_error(_("Could not queue message."));
5271 break;
5272 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5273 alertpanel_error(_("Could not queue message:\n\n%s."),
5274 g_strerror(errno));
5275 break;
5276 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5277 alertpanel_error(_("Could not queue message for sending:\n\n"
5278 "Signature failed: %s"),
5279 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5280 break;
5281 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5282 alertpanel_error(_("Could not queue message for sending:\n\n"
5283 "Encryption failed: %s"),
5284 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5285 break;
5286 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5287 alertpanel_error(_("Could not queue message for sending:\n\n"
5288 "Charset conversion failed."));
5289 break;
5290 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5291 alertpanel_error(_("Could not queue message for sending:\n\n"
5292 "Couldn't get recipient encryption key."));
5293 break;
5294 case COMPOSE_QUEUE_SIGNING_CANCELLED:
5295 debug_print("signing cancelled\n");
5296 break;
5297 default:
5298 /* unhandled error */
5299 debug_print("oops, unhandled compose_queue() return value %d\n",
5300 val);
5301 break;
5305 gint compose_send(Compose *compose)
5307 gint msgnum;
5308 FolderItem *folder = NULL;
5309 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5310 gchar *msgpath = NULL;
5311 gboolean discard_window = FALSE;
5312 gchar *errstr = NULL;
5313 gchar *tmsgid = NULL;
5314 MainWindow *mainwin = mainwindow_get_mainwindow();
5315 gboolean queued_removed = FALSE;
5317 if (prefs_common.send_dialog_invisible
5318 || compose->batch == TRUE)
5319 discard_window = TRUE;
5321 compose_allow_user_actions (compose, FALSE);
5322 compose->sending = TRUE;
5324 if (compose_check_entries(compose, TRUE) == FALSE) {
5325 if (compose->batch) {
5326 gtk_widget_show_all(compose->window);
5328 goto bail;
5331 inc_lock();
5332 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5334 if (val != COMPOSE_QUEUE_SUCCESS) {
5335 if (compose->batch) {
5336 gtk_widget_show_all(compose->window);
5339 _display_queue_error(val);
5341 goto bail;
5344 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5345 if (discard_window) {
5346 compose->sending = FALSE;
5347 compose_close(compose);
5348 /* No more compose access in the normal codepath
5349 * after this point! */
5350 compose = NULL;
5353 if (msgnum == 0) {
5354 alertpanel_error(_("The message was queued but could not be "
5355 "sent.\nUse \"Send queued messages\" from "
5356 "the main window to retry."));
5357 if (!discard_window) {
5358 goto bail;
5360 inc_unlock();
5361 g_free(tmsgid);
5362 return -1;
5364 if (msgpath == NULL) {
5365 msgpath = folder_item_fetch_msg(folder, msgnum);
5366 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5367 g_free(msgpath);
5368 } else {
5369 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5370 claws_unlink(msgpath);
5371 g_free(msgpath);
5373 if (!discard_window) {
5374 if (val != 0) {
5375 if (!queued_removed)
5376 folder_item_remove_msg(folder, msgnum);
5377 folder_item_scan(folder);
5378 if (tmsgid) {
5379 /* make sure we delete that */
5380 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5381 if (tmp) {
5382 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5383 folder_item_remove_msg(folder, tmp->msgnum);
5384 procmsg_msginfo_free(&tmp);
5390 if (val == 0) {
5391 if (!queued_removed)
5392 folder_item_remove_msg(folder, msgnum);
5393 folder_item_scan(folder);
5394 if (tmsgid) {
5395 /* make sure we delete that */
5396 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5397 if (tmp) {
5398 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5399 folder_item_remove_msg(folder, tmp->msgnum);
5400 procmsg_msginfo_free(&tmp);
5403 if (!discard_window) {
5404 compose->sending = FALSE;
5405 compose_allow_user_actions (compose, TRUE);
5406 compose_close(compose);
5408 } else {
5409 if (errstr) {
5410 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5411 "the main window to retry."), errstr);
5412 g_free(errstr);
5413 } else {
5414 alertpanel_error_log(_("The message was queued but could not be "
5415 "sent.\nUse \"Send queued messages\" from "
5416 "the main window to retry."));
5418 if (!discard_window) {
5419 goto bail;
5421 inc_unlock();
5422 g_free(tmsgid);
5423 return -1;
5425 g_free(tmsgid);
5426 inc_unlock();
5427 toolbar_main_set_sensitive(mainwin);
5428 main_window_set_menu_sensitive(mainwin);
5429 return 0;
5431 bail:
5432 inc_unlock();
5433 g_free(tmsgid);
5434 compose_allow_user_actions (compose, TRUE);
5435 compose->sending = FALSE;
5436 compose->modified = TRUE;
5437 toolbar_main_set_sensitive(mainwin);
5438 main_window_set_menu_sensitive(mainwin);
5440 return -1;
5443 static gboolean compose_use_attach(Compose *compose)
5445 GtkTreeModel *model = gtk_tree_view_get_model
5446 (GTK_TREE_VIEW(compose->attach_clist));
5447 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5450 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5451 FILE *fp)
5453 gchar buf[BUFFSIZE];
5454 gchar *str;
5455 gboolean first_to_address;
5456 gboolean first_cc_address;
5457 GSList *list;
5458 ComposeHeaderEntry *headerentry;
5459 const gchar *headerentryname;
5460 const gchar *cc_hdr;
5461 const gchar *to_hdr;
5462 gboolean err = FALSE;
5464 debug_print("Writing redirect header\n");
5466 cc_hdr = prefs_common_translated_header_name("Cc:");
5467 to_hdr = prefs_common_translated_header_name("To:");
5469 first_to_address = TRUE;
5470 for (list = compose->header_list; list; list = list->next) {
5471 headerentry = ((ComposeHeaderEntry *)list->data);
5472 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5474 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5475 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5476 Xstrdup_a(str, entstr, return -1);
5477 g_strstrip(str);
5478 if (str[0] != '\0') {
5479 compose_convert_header
5480 (compose, buf, sizeof(buf), str,
5481 strlen("Resent-To") + 2, TRUE);
5483 if (first_to_address) {
5484 err |= (fprintf(fp, "Resent-To: ") < 0);
5485 first_to_address = FALSE;
5486 } else {
5487 err |= (fprintf(fp, ",") < 0);
5489 err |= (fprintf(fp, "%s", buf) < 0);
5493 if (!first_to_address) {
5494 err |= (fprintf(fp, "\n") < 0);
5497 first_cc_address = TRUE;
5498 for (list = compose->header_list; list; list = list->next) {
5499 headerentry = ((ComposeHeaderEntry *)list->data);
5500 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5502 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5503 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5504 Xstrdup_a(str, strg, return -1);
5505 g_strstrip(str);
5506 if (str[0] != '\0') {
5507 compose_convert_header
5508 (compose, buf, sizeof(buf), str,
5509 strlen("Resent-Cc") + 2, TRUE);
5511 if (first_cc_address) {
5512 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5513 first_cc_address = FALSE;
5514 } else {
5515 err |= (fprintf(fp, ",") < 0);
5517 err |= (fprintf(fp, "%s", buf) < 0);
5521 if (!first_cc_address) {
5522 err |= (fprintf(fp, "\n") < 0);
5525 return (err ? -1:0);
5528 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5530 gchar date[RFC822_DATE_BUFFSIZE];
5531 gchar buf[BUFFSIZE];
5532 gchar *str;
5533 const gchar *entstr;
5534 /* struct utsname utsbuf; */
5535 gboolean err = FALSE;
5537 cm_return_val_if_fail(fp != NULL, -1);
5538 cm_return_val_if_fail(compose->account != NULL, -1);
5539 cm_return_val_if_fail(compose->account->address != NULL, -1);
5541 /* Resent-Date */
5542 if (prefs_common.hide_timezone)
5543 get_rfc822_date_hide_tz(date, sizeof(date));
5544 else
5545 get_rfc822_date(date, sizeof(date));
5546 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5548 /* Resent-From */
5549 if (compose->account->name && *compose->account->name) {
5550 compose_convert_header
5551 (compose, buf, sizeof(buf), compose->account->name,
5552 strlen("From: "), TRUE);
5553 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5554 buf, compose->account->address) < 0);
5555 } else
5556 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5558 /* Subject */
5559 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5560 if (*entstr != '\0') {
5561 Xstrdup_a(str, entstr, return -1);
5562 g_strstrip(str);
5563 if (*str != '\0') {
5564 compose_convert_header(compose, buf, sizeof(buf), str,
5565 strlen("Subject: "), FALSE);
5566 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5570 /* Resent-Message-ID */
5571 if (compose->account->gen_msgid) {
5572 gchar *addr = prefs_account_generate_msgid(compose->account);
5573 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5574 if (compose->msgid)
5575 g_free(compose->msgid);
5576 compose->msgid = addr;
5577 } else {
5578 compose->msgid = NULL;
5581 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5582 return -1;
5584 /* separator between header and body */
5585 err |= (claws_fputs("\n", fp) == EOF);
5587 return (err ? -1:0);
5590 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5592 FILE *fp;
5593 size_t len;
5594 gchar *buf = NULL;
5595 gchar rewrite_buf[BUFFSIZE];
5596 int i = 0;
5597 gboolean skip = FALSE;
5598 gboolean err = FALSE;
5599 gchar *not_included[]={
5600 "Return-Path:", "Delivered-To:", "Received:",
5601 "Subject:", "X-UIDL:", "AF:",
5602 "NF:", "PS:", "SRH:",
5603 "SFN:", "DSR:", "MID:",
5604 "CFG:", "PT:", "S:",
5605 "RQ:", "SSV:", "NSV:",
5606 "SSH:", "R:", "MAID:",
5607 "NAID:", "RMID:", "FMID:",
5608 "SCF:", "RRCPT:", "NG:",
5609 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5610 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5611 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5612 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5613 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5614 NULL
5616 gint ret = 0;
5618 if ((fp = claws_fopen(compose->redirect_filename, "rb")) == NULL) {
5619 FILE_OP_ERROR(compose->redirect_filename, "claws_fopen");
5620 return -1;
5623 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5624 skip = FALSE;
5625 for (i = 0; not_included[i] != NULL; i++) {
5626 if (g_ascii_strncasecmp(buf, not_included[i],
5627 strlen(not_included[i])) == 0) {
5628 skip = TRUE;
5629 break;
5632 if (skip) {
5633 g_free(buf);
5634 buf = NULL;
5635 continue;
5637 if (claws_fputs(buf, fdest) == -1) {
5638 g_free(buf);
5639 buf = NULL;
5640 goto error;
5643 if (!prefs_common.redirect_keep_from) {
5644 if (g_ascii_strncasecmp(buf, "From:",
5645 strlen("From:")) == 0) {
5646 err |= (claws_fputs(" (by way of ", fdest) == EOF);
5647 if (compose->account->name
5648 && *compose->account->name) {
5649 gchar buffer[BUFFSIZE];
5651 compose_convert_header
5652 (compose, buffer, sizeof(buffer),
5653 compose->account->name,
5654 strlen("From: "),
5655 FALSE);
5656 err |= (fprintf(fdest, "%s <%s>",
5657 buffer,
5658 compose->account->address) < 0);
5659 } else
5660 err |= (fprintf(fdest, "%s",
5661 compose->account->address) < 0);
5662 err |= (claws_fputs(")", fdest) == EOF);
5666 g_free(buf);
5667 buf = NULL;
5668 if (claws_fputs("\n", fdest) == -1)
5669 goto error;
5672 if (err)
5673 goto error;
5675 if (compose_redirect_write_headers(compose, fdest))
5676 goto error;
5678 while ((len = claws_fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5679 if (claws_fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5680 goto error;
5683 claws_fclose(fp);
5685 return 0;
5687 error:
5688 claws_fclose(fp);
5690 return -1;
5693 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5695 GtkTextBuffer *buffer;
5696 GtkTextIter start, end, tmp;
5697 gchar *chars, *tmp_enc_file, *content;
5698 gchar *buf, *msg;
5699 const gchar *out_codeset;
5700 EncodingType encoding = ENC_UNKNOWN;
5701 MimeInfo *mimemsg, *mimetext;
5702 gint line;
5703 const gchar *src_codeset = CS_INTERNAL;
5704 gchar *from_addr = NULL;
5705 gchar *from_name = NULL;
5706 FolderItem *outbox;
5708 if (action == COMPOSE_WRITE_FOR_SEND) {
5709 attach_parts = TRUE;
5711 /* We're sending the message, generate a Message-ID
5712 * if necessary. */
5713 if (compose->msgid == NULL &&
5714 compose->account->gen_msgid) {
5715 compose->msgid = prefs_account_generate_msgid(compose->account);
5719 /* create message MimeInfo */
5720 mimemsg = procmime_mimeinfo_new();
5721 mimemsg->type = MIMETYPE_MESSAGE;
5722 mimemsg->subtype = g_strdup("rfc822");
5723 mimemsg->content = MIMECONTENT_MEM;
5724 mimemsg->tmp = TRUE; /* must free content later */
5725 mimemsg->data.mem = compose_get_header(compose);
5727 /* Create text part MimeInfo */
5728 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5729 gtk_text_buffer_get_end_iter(buffer, &end);
5730 tmp = end;
5732 /* We make sure that there is a newline at the end. */
5733 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5734 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5735 if (*chars != '\n') {
5736 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5738 g_free(chars);
5741 /* get all composed text */
5742 gtk_text_buffer_get_start_iter(buffer, &start);
5743 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5745 out_codeset = conv_get_charset_str(compose->out_encoding);
5747 if (!out_codeset && is_ascii_str(chars)) {
5748 out_codeset = CS_US_ASCII;
5749 } else if (prefs_common.outgoing_fallback_to_ascii &&
5750 is_ascii_str(chars)) {
5751 out_codeset = CS_US_ASCII;
5752 encoding = ENC_7BIT;
5755 if (!out_codeset) {
5756 gchar *test_conv_global_out = NULL;
5757 gchar *test_conv_reply = NULL;
5759 /* automatic mode. be automatic. */
5760 codeconv_set_strict(TRUE);
5762 out_codeset = conv_get_outgoing_charset_str();
5763 if (out_codeset) {
5764 debug_print("trying to convert to %s\n", out_codeset);
5765 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5768 if (!test_conv_global_out && compose->orig_charset
5769 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5770 out_codeset = compose->orig_charset;
5771 debug_print("failure; trying to convert to %s\n", out_codeset);
5772 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5775 if (!test_conv_global_out && !test_conv_reply) {
5776 /* we're lost */
5777 out_codeset = CS_INTERNAL;
5778 debug_print("failure; finally using %s\n", out_codeset);
5780 g_free(test_conv_global_out);
5781 g_free(test_conv_reply);
5782 codeconv_set_strict(FALSE);
5785 if (encoding == ENC_UNKNOWN) {
5786 if (prefs_common.encoding_method == CTE_BASE64)
5787 encoding = ENC_BASE64;
5788 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5789 encoding = ENC_QUOTED_PRINTABLE;
5790 else if (prefs_common.encoding_method == CTE_8BIT)
5791 encoding = ENC_8BIT;
5792 else
5793 encoding = procmime_get_encoding_for_charset(out_codeset);
5796 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5797 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5799 if (action == COMPOSE_WRITE_FOR_SEND) {
5800 codeconv_set_strict(TRUE);
5801 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5802 codeconv_set_strict(FALSE);
5804 if (!buf) {
5805 AlertValue aval;
5807 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5808 "to the specified %s charset.\n"
5809 "Send it as %s?"), out_codeset, src_codeset);
5810 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5811 _("_Send"), NULL, ALERTFOCUS_SECOND, FALSE,
5812 NULL, ALERT_ERROR);
5813 g_free(msg);
5815 if (aval != G_ALERTALTERNATE) {
5816 g_free(chars);
5817 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5818 } else {
5819 buf = chars;
5820 out_codeset = src_codeset;
5821 chars = NULL;
5824 } else {
5825 buf = chars;
5826 out_codeset = src_codeset;
5827 chars = NULL;
5829 g_free(chars);
5831 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5832 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5833 strstr(buf, "\nFrom ") != NULL) {
5834 encoding = ENC_QUOTED_PRINTABLE;
5838 mimetext = procmime_mimeinfo_new();
5839 mimetext->content = MIMECONTENT_MEM;
5840 mimetext->tmp = TRUE; /* must free content later */
5841 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5842 * and free the data, which we need later. */
5843 mimetext->data.mem = g_strdup(buf);
5844 mimetext->type = MIMETYPE_TEXT;
5845 mimetext->subtype = g_strdup("plain");
5846 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5847 g_strdup(out_codeset));
5849 /* protect trailing spaces when signing message */
5850 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5851 privacy_system_can_sign(compose->privacy_system)) {
5852 encoding = ENC_QUOTED_PRINTABLE;
5855 #ifdef G_OS_WIN32
5856 debug_print("main text: %Id bytes encoded as %s in %d\n",
5857 #else
5858 debug_print("main text: %zd bytes encoded as %s in %d\n",
5859 #endif
5860 strlen(buf), out_codeset, encoding);
5862 /* check for line length limit */
5863 if (action == COMPOSE_WRITE_FOR_SEND &&
5864 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5865 check_line_length(buf, 1000, &line) < 0) {
5866 AlertValue aval;
5868 msg = g_strdup_printf
5869 (_("Line %d exceeds the line length limit (998 bytes).\n"
5870 "The contents of the message might be broken on the way to the delivery.\n"
5871 "\n"
5872 "Send it anyway?"), line + 1);
5873 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL,
5874 ALERTFOCUS_FIRST);
5875 g_free(msg);
5876 if (aval != G_ALERTALTERNATE) {
5877 g_free(buf);
5878 return COMPOSE_QUEUE_ERROR_NO_MSG;
5882 if (encoding != ENC_UNKNOWN)
5883 procmime_encode_content(mimetext, encoding);
5885 /* append attachment parts */
5886 if (compose_use_attach(compose) && attach_parts) {
5887 MimeInfo *mimempart;
5888 gchar *boundary = NULL;
5889 mimempart = procmime_mimeinfo_new();
5890 mimempart->content = MIMECONTENT_EMPTY;
5891 mimempart->type = MIMETYPE_MULTIPART;
5892 mimempart->subtype = g_strdup("mixed");
5894 do {
5895 g_free(boundary);
5896 boundary = generate_mime_boundary(NULL);
5897 } while (strstr(buf, boundary) != NULL);
5899 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5900 boundary);
5902 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5904 g_node_append(mimempart->node, mimetext->node);
5905 g_node_append(mimemsg->node, mimempart->node);
5907 if (compose_add_attachments(compose, mimempart) < 0)
5908 return COMPOSE_QUEUE_ERROR_NO_MSG;
5909 } else
5910 g_node_append(mimemsg->node, mimetext->node);
5912 g_free(buf);
5914 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5915 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5916 /* extract name and address */
5917 if (strstr(spec, " <") && strstr(spec, ">")) {
5918 from_addr = g_strdup(strrchr(spec, '<')+1);
5919 *(strrchr(from_addr, '>')) = '\0';
5920 from_name = g_strdup(spec);
5921 *(strrchr(from_name, '<')) = '\0';
5922 } else {
5923 from_name = NULL;
5924 from_addr = NULL;
5926 g_free(spec);
5928 /* sign message if sending */
5929 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5930 privacy_system_can_sign(compose->privacy_system))
5931 if (!privacy_sign(compose->privacy_system, mimemsg,
5932 compose->account, from_addr)) {
5933 g_free(from_name);
5934 g_free(from_addr);
5935 if (!privacy_peek_error())
5936 return COMPOSE_QUEUE_SIGNING_CANCELLED;
5937 else
5938 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5940 g_free(from_name);
5941 g_free(from_addr);
5943 if (compose->use_encryption) {
5944 if (compose->encdata != NULL &&
5945 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5947 /* First, write an unencrypted copy and save it to outbox, if
5948 * user wants that. */
5949 if (compose->account->save_encrypted_as_clear_text) {
5950 debug_print("saving sent message unencrypted...\n");
5951 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5952 if (tmpfp) {
5953 claws_fclose(tmpfp);
5955 /* fp now points to a file with headers written,
5956 * let's make a copy. */
5957 rewind(fp);
5958 content = file_read_stream_to_str(fp);
5960 str_write_to_file(content, tmp_enc_file, TRUE);
5961 g_free(content);
5963 /* Now write the unencrypted body. */
5964 if ((tmpfp = claws_fopen(tmp_enc_file, "a")) != NULL) {
5965 procmime_write_mimeinfo(mimemsg, tmpfp);
5966 claws_fclose(tmpfp);
5968 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5969 if (!outbox)
5970 outbox = folder_get_default_outbox();
5972 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5973 claws_unlink(tmp_enc_file);
5974 } else {
5975 g_warning("Can't open file '%s'", tmp_enc_file);
5977 } else {
5978 g_warning("couldn't get tempfile");
5981 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5982 debug_print("Couldn't encrypt mime structure: %s.\n",
5983 privacy_get_error());
5984 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5989 procmime_write_mimeinfo(mimemsg, fp);
5991 procmime_mimeinfo_free_all(&mimemsg);
5993 return 0;
5996 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5998 GtkTextBuffer *buffer;
5999 GtkTextIter start, end;
6000 FILE *fp;
6001 size_t len;
6002 gchar *chars, *tmp;
6004 if ((fp = claws_fopen(file, "wb")) == NULL) {
6005 FILE_OP_ERROR(file, "claws_fopen");
6006 return -1;
6009 /* chmod for security */
6010 if (change_file_mode_rw(fp, file) < 0) {
6011 FILE_OP_ERROR(file, "chmod");
6012 g_warning("can't change file mode");
6015 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
6016 gtk_text_buffer_get_start_iter(buffer, &start);
6017 gtk_text_buffer_get_end_iter(buffer, &end);
6018 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
6020 chars = conv_codeset_strdup
6021 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6023 g_free(tmp);
6024 if (!chars) {
6025 claws_fclose(fp);
6026 claws_unlink(file);
6027 return -1;
6029 /* write body */
6030 len = strlen(chars);
6031 if (claws_fwrite(chars, sizeof(gchar), len, fp) != len) {
6032 FILE_OP_ERROR(file, "claws_fwrite");
6033 g_free(chars);
6034 claws_fclose(fp);
6035 claws_unlink(file);
6036 return -1;
6039 g_free(chars);
6041 if (claws_safe_fclose(fp) == EOF) {
6042 FILE_OP_ERROR(file, "claws_fclose");
6043 claws_unlink(file);
6044 return -1;
6046 return 0;
6049 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6051 FolderItem *item;
6052 MsgInfo *msginfo = compose->targetinfo;
6054 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6055 if (!msginfo) return -1;
6057 if (!force && MSG_IS_LOCKED(msginfo->flags))
6058 return 0;
6060 item = msginfo->folder;
6061 cm_return_val_if_fail(item != NULL, -1);
6063 if (procmsg_msg_exist(msginfo) &&
6064 (folder_has_parent_of_type(item, F_QUEUE) ||
6065 folder_has_parent_of_type(item, F_DRAFT)
6066 || msginfo == compose->autosaved_draft)) {
6067 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6068 g_warning("can't remove the old message");
6069 return -1;
6070 } else {
6071 debug_print("removed reedit target %d\n", msginfo->msgnum);
6075 return 0;
6078 static void compose_remove_draft(Compose *compose)
6080 FolderItem *drafts;
6081 MsgInfo *msginfo = compose->targetinfo;
6082 drafts = account_get_special_folder(compose->account, F_DRAFT);
6084 if (procmsg_msg_exist(msginfo)) {
6085 folder_item_remove_msg(drafts, msginfo->msgnum);
6090 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6091 gboolean remove_reedit_target)
6093 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6096 static gboolean compose_warn_encryption(Compose *compose)
6098 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6099 AlertValue val = G_ALERTALTERNATE;
6101 if (warning == NULL)
6102 return TRUE;
6104 val = alertpanel_full(_("Encryption warning"), warning,
6105 GTK_STOCK_CANCEL, _("C_ontinue"), NULL, ALERTFOCUS_SECOND,
6106 TRUE, NULL, ALERT_WARNING);
6107 if (val & G_ALERTDISABLE) {
6108 val &= ~G_ALERTDISABLE;
6109 if (val == G_ALERTALTERNATE)
6110 privacy_inhibit_encrypt_warning(compose->privacy_system,
6111 TRUE);
6114 if (val == G_ALERTALTERNATE) {
6115 return TRUE;
6116 } else {
6117 return FALSE;
6121 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6122 gchar **msgpath, gboolean perform_checks,
6123 gboolean remove_reedit_target)
6125 FolderItem *queue;
6126 gchar *tmp;
6127 FILE *fp;
6128 GSList *cur;
6129 gint num;
6130 PrefsAccount *mailac = NULL, *newsac = NULL;
6131 gboolean err = FALSE;
6133 debug_print("queueing message...\n");
6134 cm_return_val_if_fail(compose->account != NULL, -1);
6136 if (compose_check_entries(compose, perform_checks) == FALSE) {
6137 if (compose->batch) {
6138 gtk_widget_show_all(compose->window);
6140 return COMPOSE_QUEUE_ERROR_NO_MSG;
6143 if (!compose->to_list && !compose->newsgroup_list) {
6144 g_warning("can't get recipient list.");
6145 return COMPOSE_QUEUE_ERROR_NO_MSG;
6148 if (compose->to_list) {
6149 if (compose->account->protocol != A_NNTP)
6150 mailac = compose->account;
6151 else if (cur_account && cur_account->protocol != A_NNTP)
6152 mailac = cur_account;
6153 else if (!(mailac = compose_current_mail_account())) {
6154 alertpanel_error(_("No account for sending mails available!"));
6155 return COMPOSE_QUEUE_ERROR_NO_MSG;
6159 if (compose->newsgroup_list) {
6160 if (compose->account->protocol == A_NNTP)
6161 newsac = compose->account;
6162 else {
6163 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6164 return COMPOSE_QUEUE_ERROR_NO_MSG;
6168 /* write queue header */
6169 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6170 G_DIR_SEPARATOR, compose, (guint) rand());
6171 debug_print("queuing to %s\n", tmp);
6172 if ((fp = claws_fopen(tmp, "w+b")) == NULL) {
6173 FILE_OP_ERROR(tmp, "claws_fopen");
6174 g_free(tmp);
6175 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6178 if (change_file_mode_rw(fp, tmp) < 0) {
6179 FILE_OP_ERROR(tmp, "chmod");
6180 g_warning("can't change file mode");
6183 /* queueing variables */
6184 err |= (fprintf(fp, "AF:\n") < 0);
6185 err |= (fprintf(fp, "NF:0\n") < 0);
6186 err |= (fprintf(fp, "PS:10\n") < 0);
6187 err |= (fprintf(fp, "SRH:1\n") < 0);
6188 err |= (fprintf(fp, "SFN:\n") < 0);
6189 err |= (fprintf(fp, "DSR:\n") < 0);
6190 if (compose->msgid)
6191 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6192 else
6193 err |= (fprintf(fp, "MID:\n") < 0);
6194 err |= (fprintf(fp, "CFG:\n") < 0);
6195 err |= (fprintf(fp, "PT:0\n") < 0);
6196 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6197 err |= (fprintf(fp, "RQ:\n") < 0);
6198 if (mailac)
6199 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6200 else
6201 err |= (fprintf(fp, "SSV:\n") < 0);
6202 if (newsac)
6203 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6204 else
6205 err |= (fprintf(fp, "NSV:\n") < 0);
6206 err |= (fprintf(fp, "SSH:\n") < 0);
6207 /* write recipient list */
6208 if (compose->to_list) {
6209 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6210 for (cur = compose->to_list->next; cur != NULL;
6211 cur = cur->next)
6212 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6213 err |= (fprintf(fp, "\n") < 0);
6215 /* write newsgroup list */
6216 if (compose->newsgroup_list) {
6217 err |= (fprintf(fp, "NG:") < 0);
6218 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6219 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6220 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6221 err |= (fprintf(fp, "\n") < 0);
6223 /* account IDs */
6224 if (mailac)
6225 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6226 if (newsac)
6227 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6230 if (compose->privacy_system != NULL) {
6231 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6232 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6233 if (compose->use_encryption) {
6234 if (!compose_warn_encryption(compose)) {
6235 claws_fclose(fp);
6236 claws_unlink(tmp);
6237 g_free(tmp);
6238 return COMPOSE_QUEUE_ERROR_NO_MSG;
6240 if (mailac && mailac->encrypt_to_self) {
6241 GSList *tmp_list = g_slist_copy(compose->to_list);
6242 tmp_list = g_slist_append(tmp_list, compose->account->address);
6243 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6244 g_slist_free(tmp_list);
6245 } else {
6246 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6248 if (compose->encdata != NULL) {
6249 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6250 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6251 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6252 compose->encdata) < 0);
6253 } /* else we finally dont want to encrypt */
6254 } else {
6255 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6256 /* and if encdata was null, it means there's been a problem in
6257 * key selection */
6258 if (err == TRUE)
6259 g_warning("failed to write queue message");
6260 claws_fclose(fp);
6261 claws_unlink(tmp);
6262 g_free(tmp);
6263 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6268 /* Save copy folder */
6269 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6270 gchar *savefolderid;
6272 savefolderid = compose_get_save_to(compose);
6273 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6274 g_free(savefolderid);
6276 /* Save copy folder */
6277 if (compose->return_receipt) {
6278 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6280 /* Message-ID of message replying to */
6281 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6282 gchar *folderid = NULL;
6284 if (compose->replyinfo->folder)
6285 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6286 if (folderid == NULL)
6287 folderid = g_strdup("NULL");
6289 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6290 g_free(folderid);
6292 /* Message-ID of message forwarding to */
6293 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6294 gchar *folderid = NULL;
6296 if (compose->fwdinfo->folder)
6297 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6298 if (folderid == NULL)
6299 folderid = g_strdup("NULL");
6301 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6302 g_free(folderid);
6305 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6306 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6308 /* end of headers */
6309 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6311 if (compose->redirect_filename != NULL) {
6312 if (compose_redirect_write_to_file(compose, fp) < 0) {
6313 claws_fclose(fp);
6314 claws_unlink(tmp);
6315 g_free(tmp);
6316 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6318 } else {
6319 gint result = 0;
6320 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6321 claws_fclose(fp);
6322 claws_unlink(tmp);
6323 g_free(tmp);
6324 return result;
6327 if (err == TRUE) {
6328 g_warning("failed to write queue message");
6329 claws_fclose(fp);
6330 claws_unlink(tmp);
6331 g_free(tmp);
6332 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6334 if (claws_safe_fclose(fp) == EOF) {
6335 FILE_OP_ERROR(tmp, "claws_fclose");
6336 claws_unlink(tmp);
6337 g_free(tmp);
6338 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6341 if (item && *item) {
6342 queue = *item;
6343 } else {
6344 queue = account_get_special_folder(compose->account, F_QUEUE);
6346 if (!queue) {
6347 g_warning("can't find queue folder");
6348 claws_unlink(tmp);
6349 g_free(tmp);
6350 return COMPOSE_QUEUE_ERROR_NO_MSG;
6352 folder_item_scan(queue);
6353 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6354 g_warning("can't queue the message");
6355 claws_unlink(tmp);
6356 g_free(tmp);
6357 return COMPOSE_QUEUE_ERROR_NO_MSG;
6360 if (msgpath == NULL) {
6361 claws_unlink(tmp);
6362 g_free(tmp);
6363 } else
6364 *msgpath = tmp;
6366 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6367 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6368 if (mi) {
6369 procmsg_msginfo_change_flags(mi,
6370 compose->targetinfo->flags.perm_flags,
6371 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6372 0, 0);
6374 g_slist_free(mi->tags);
6375 mi->tags = g_slist_copy(compose->targetinfo->tags);
6376 procmsg_msginfo_free(&mi);
6380 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6381 compose_remove_reedit_target(compose, FALSE);
6384 if ((msgnum != NULL) && (item != NULL)) {
6385 *msgnum = num;
6386 *item = queue;
6389 return COMPOSE_QUEUE_SUCCESS;
6392 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6394 AttachInfo *ainfo;
6395 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6396 MimeInfo *mimepart;
6397 #ifdef G_OS_WIN32
6398 GFile *f;
6399 GFileInfo *fi;
6400 GError *error = NULL;
6401 #else
6402 GStatBuf statbuf;
6403 #endif
6404 goffset size;
6405 gchar *type, *subtype;
6406 GtkTreeModel *model;
6407 GtkTreeIter iter;
6409 model = gtk_tree_view_get_model(tree_view);
6411 if (!gtk_tree_model_get_iter_first(model, &iter))
6412 return 0;
6413 do {
6414 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6416 if (!is_file_exist(ainfo->file)) {
6417 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6418 AlertValue val = alertpanel_full(_("Warning"), msg,
6419 _("Cancel sending"), _("Ignore attachment"), NULL,
6420 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6421 g_free(msg);
6422 if (val == G_ALERTDEFAULT) {
6423 return -1;
6425 continue;
6427 #ifdef G_OS_WIN32
6428 f = g_file_new_for_path(ainfo->file);
6429 fi = g_file_query_info(f, "standard::size",
6430 G_FILE_QUERY_INFO_NONE, NULL, &error);
6431 if (error != NULL) {
6432 g_warning(error->message);
6433 g_error_free(error);
6434 g_object_unref(f);
6435 return -1;
6437 size = g_file_info_get_size(fi);
6438 g_object_unref(fi);
6439 g_object_unref(f);
6440 #else
6441 if (g_stat(ainfo->file, &statbuf) < 0)
6442 return -1;
6443 size = statbuf.st_size;
6444 #endif
6446 mimepart = procmime_mimeinfo_new();
6447 mimepart->content = MIMECONTENT_FILE;
6448 mimepart->data.filename = g_strdup(ainfo->file);
6449 mimepart->tmp = FALSE; /* or we destroy our attachment */
6450 mimepart->offset = 0;
6451 mimepart->length = size;
6453 type = g_strdup(ainfo->content_type);
6455 if (!strchr(type, '/')) {
6456 g_free(type);
6457 type = g_strdup("application/octet-stream");
6460 subtype = strchr(type, '/') + 1;
6461 *(subtype - 1) = '\0';
6462 mimepart->type = procmime_get_media_type(type);
6463 mimepart->subtype = g_strdup(subtype);
6464 g_free(type);
6466 if (mimepart->type == MIMETYPE_MESSAGE &&
6467 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6468 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6469 } else if (mimepart->type == MIMETYPE_TEXT) {
6470 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6471 /* Text parts with no name come from multipart/alternative
6472 * forwards. Make sure the recipient won't look at the
6473 * original HTML part by mistake. */
6474 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6475 ainfo->name = g_strdup_printf(_("Original %s part"),
6476 mimepart->subtype);
6478 if (ainfo->charset)
6479 g_hash_table_insert(mimepart->typeparameters,
6480 g_strdup("charset"), g_strdup(ainfo->charset));
6482 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6483 if (mimepart->type == MIMETYPE_APPLICATION &&
6484 !g_strcmp0(mimepart->subtype, "octet-stream"))
6485 g_hash_table_insert(mimepart->typeparameters,
6486 g_strdup("name"), g_strdup(ainfo->name));
6487 g_hash_table_insert(mimepart->dispositionparameters,
6488 g_strdup("filename"), g_strdup(ainfo->name));
6489 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6492 if (mimepart->type == MIMETYPE_MESSAGE
6493 || mimepart->type == MIMETYPE_MULTIPART)
6494 ainfo->encoding = ENC_BINARY;
6495 else if (compose->use_signing || compose->fwdinfo != NULL) {
6496 if (ainfo->encoding == ENC_7BIT)
6497 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6498 else if (ainfo->encoding == ENC_8BIT)
6499 ainfo->encoding = ENC_BASE64;
6502 procmime_encode_content(mimepart, ainfo->encoding);
6504 g_node_append(parent->node, mimepart->node);
6505 } while (gtk_tree_model_iter_next(model, &iter));
6507 return 0;
6510 static gchar *compose_quote_list_of_addresses(gchar *str)
6512 GSList *list = NULL, *item = NULL;
6513 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6515 list = address_list_append_with_comments(list, str);
6516 for (item = list; item != NULL; item = item->next) {
6517 gchar *spec = item->data;
6518 gchar *endofname = strstr(spec, " <");
6519 if (endofname != NULL) {
6520 gchar * qqname;
6521 *endofname = '\0';
6522 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6523 qqname = escape_internal_quotes(qname, '"');
6524 *endofname = ' ';
6525 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6526 gchar *addr = g_strdup(endofname);
6527 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6528 faddr = g_strconcat(name, addr, NULL);
6529 g_free(name);
6530 g_free(addr);
6531 debug_print("new auto-quoted address: '%s'\n", faddr);
6534 if (result == NULL)
6535 result = g_strdup((faddr != NULL)? faddr: spec);
6536 else {
6537 result = g_strconcat(result,
6538 ", ",
6539 (faddr != NULL)? faddr: spec,
6540 NULL);
6542 if (faddr != NULL) {
6543 g_free(faddr);
6544 faddr = NULL;
6547 slist_free_strings_full(list);
6549 return result;
6552 #define IS_IN_CUSTOM_HEADER(header) \
6553 (compose->account->add_customhdr && \
6554 custom_header_find(compose->account->customhdr_list, header) != NULL)
6556 static const gchar *compose_untranslated_header_name(gchar *header_name)
6558 /* return the untranslated header name, if header_name is a known
6559 header name, in either its translated or untranslated form, with
6560 or without trailing colon. otherwise, returns header_name. */
6561 gchar *translated_header_name;
6562 gchar *translated_header_name_wcolon;
6563 const gchar *untranslated_header_name;
6564 const gchar *untranslated_header_name_wcolon;
6565 gint i;
6567 cm_return_val_if_fail(header_name != NULL, NULL);
6569 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6570 untranslated_header_name = HEADERS[i].header_name;
6571 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6573 translated_header_name = gettext(untranslated_header_name);
6574 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6576 if (!strcmp(header_name, untranslated_header_name) ||
6577 !strcmp(header_name, translated_header_name)) {
6578 return untranslated_header_name;
6579 } else {
6580 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6581 !strcmp(header_name, translated_header_name_wcolon)) {
6582 return untranslated_header_name_wcolon;
6586 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6587 return header_name;
6590 static void compose_add_headerfield_from_headerlist(Compose *compose,
6591 GString *header,
6592 const gchar *fieldname,
6593 const gchar *seperator)
6595 gchar *str, *fieldname_w_colon;
6596 gboolean add_field = FALSE;
6597 GSList *list;
6598 ComposeHeaderEntry *headerentry;
6599 const gchar *headerentryname;
6600 const gchar *trans_fieldname;
6601 GString *fieldstr;
6603 if (IS_IN_CUSTOM_HEADER(fieldname))
6604 return;
6606 debug_print("Adding %s-fields\n", fieldname);
6608 fieldstr = g_string_sized_new(64);
6610 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6611 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6613 for (list = compose->header_list; list; list = list->next) {
6614 headerentry = ((ComposeHeaderEntry *)list->data);
6615 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6617 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6618 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6619 g_strstrip(ustr);
6620 str = compose_quote_list_of_addresses(ustr);
6621 g_free(ustr);
6622 if (str != NULL && str[0] != '\0') {
6623 if (add_field)
6624 g_string_append(fieldstr, seperator);
6625 g_string_append(fieldstr, str);
6626 add_field = TRUE;
6628 g_free(str);
6631 if (add_field) {
6632 gchar *buf;
6634 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6635 compose_convert_header
6636 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6637 strlen(fieldname) + 2, TRUE);
6638 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6639 g_free(buf);
6642 g_free(fieldname_w_colon);
6643 g_string_free(fieldstr, TRUE);
6645 return;
6648 static gchar *compose_get_manual_headers_info(Compose *compose)
6650 GString *sh_header = g_string_new(" ");
6651 GSList *list;
6652 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6654 for (list = compose->header_list; list; list = list->next) {
6655 ComposeHeaderEntry *headerentry;
6656 gchar *tmp;
6657 gchar *headername;
6658 gchar *headername_wcolon;
6659 const gchar *headername_trans;
6660 gchar **string;
6661 gboolean standard_header = FALSE;
6663 headerentry = ((ComposeHeaderEntry *)list->data);
6665 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6666 g_strstrip(tmp);
6667 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6668 g_free(tmp);
6669 continue;
6672 if (!strstr(tmp, ":")) {
6673 headername_wcolon = g_strconcat(tmp, ":", NULL);
6674 headername = g_strdup(tmp);
6675 } else {
6676 headername_wcolon = g_strdup(tmp);
6677 headername = g_strdup(strtok(tmp, ":"));
6679 g_free(tmp);
6681 string = std_headers;
6682 while (*string != NULL) {
6683 headername_trans = prefs_common_translated_header_name(*string);
6684 if (!strcmp(headername_trans, headername_wcolon))
6685 standard_header = TRUE;
6686 string++;
6688 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6689 g_string_append_printf(sh_header, "%s ", headername);
6690 g_free(headername);
6691 g_free(headername_wcolon);
6693 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6694 return g_string_free(sh_header, FALSE);
6697 static gchar *compose_get_header(Compose *compose)
6699 gchar date[RFC822_DATE_BUFFSIZE];
6700 gchar buf[BUFFSIZE];
6701 const gchar *entry_str;
6702 gchar *str;
6703 gchar *name;
6704 GSList *list;
6705 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6706 GString *header;
6707 gchar *from_name = NULL, *from_address = NULL;
6708 gchar *tmp;
6710 cm_return_val_if_fail(compose->account != NULL, NULL);
6711 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6713 header = g_string_sized_new(64);
6715 /* Date */
6716 if (prefs_common.hide_timezone)
6717 get_rfc822_date_hide_tz(date, sizeof(date));
6718 else
6719 get_rfc822_date(date, sizeof(date));
6720 g_string_append_printf(header, "Date: %s\n", date);
6722 /* From */
6724 if (compose->account->name && *compose->account->name) {
6725 gchar *buf;
6726 QUOTE_IF_REQUIRED(buf, compose->account->name);
6727 tmp = g_strdup_printf("%s <%s>",
6728 buf, compose->account->address);
6729 } else {
6730 tmp = g_strdup_printf("%s",
6731 compose->account->address);
6733 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6734 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6735 /* use default */
6736 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6737 from_address = g_strdup(compose->account->address);
6738 } else {
6739 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6740 /* extract name and address */
6741 if (strstr(spec, " <") && strstr(spec, ">")) {
6742 from_address = g_strdup(strrchr(spec, '<')+1);
6743 *(strrchr(from_address, '>')) = '\0';
6744 from_name = g_strdup(spec);
6745 *(strrchr(from_name, '<')) = '\0';
6746 } else {
6747 from_name = NULL;
6748 from_address = g_strdup(spec);
6750 g_free(spec);
6752 g_free(tmp);
6755 if (from_name && *from_name) {
6756 gchar *qname;
6757 compose_convert_header
6758 (compose, buf, sizeof(buf), from_name,
6759 strlen("From: "), TRUE);
6760 QUOTE_IF_REQUIRED(name, buf);
6761 qname = escape_internal_quotes(name, '"');
6763 g_string_append_printf(header, "From: %s <%s>\n",
6764 qname, from_address);
6765 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6766 compose->return_receipt) {
6767 compose_convert_header(compose, buf, sizeof(buf), from_name,
6768 strlen("Disposition-Notification-To: "),
6769 TRUE);
6770 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6772 if (qname != name)
6773 g_free(qname);
6774 } else {
6775 g_string_append_printf(header, "From: %s\n", from_address);
6776 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6777 compose->return_receipt)
6778 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6781 g_free(from_name);
6782 g_free(from_address);
6784 /* To */
6785 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6787 /* Newsgroups */
6788 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6790 /* Cc */
6791 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6793 /* Bcc */
6795 * If this account is a NNTP account remove Bcc header from
6796 * message body since it otherwise will be publicly shown
6798 if (compose->account->protocol != A_NNTP)
6799 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6801 /* Subject */
6802 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6804 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6805 g_strstrip(str);
6806 if (*str != '\0') {
6807 compose_convert_header(compose, buf, sizeof(buf), str,
6808 strlen("Subject: "), FALSE);
6809 g_string_append_printf(header, "Subject: %s\n", buf);
6812 g_free(str);
6814 /* Message-ID */
6815 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6816 g_string_append_printf(header, "Message-ID: <%s>\n",
6817 compose->msgid);
6820 if (compose->remove_references == FALSE) {
6821 /* In-Reply-To */
6822 if (compose->inreplyto && compose->to_list)
6823 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6825 /* References */
6826 if (compose->references)
6827 g_string_append_printf(header, "References: %s\n", compose->references);
6830 /* Followup-To */
6831 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6833 /* Reply-To */
6834 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6836 /* Organization */
6837 if (compose->account->organization &&
6838 strlen(compose->account->organization) &&
6839 !IS_IN_CUSTOM_HEADER("Organization")) {
6840 compose_convert_header(compose, buf, sizeof(buf),
6841 compose->account->organization,
6842 strlen("Organization: "), FALSE);
6843 g_string_append_printf(header, "Organization: %s\n", buf);
6846 /* Program version and system info */
6847 if (compose->account->gen_xmailer &&
6848 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6849 !compose->newsgroup_list) {
6850 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6851 prog_version,
6852 gtk_major_version, gtk_minor_version, gtk_micro_version,
6853 TARGET_ALIAS);
6855 if (compose->account->gen_xmailer &&
6856 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6857 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6858 prog_version,
6859 gtk_major_version, gtk_minor_version, gtk_micro_version,
6860 TARGET_ALIAS);
6863 /* custom headers */
6864 if (compose->account->add_customhdr) {
6865 GSList *cur;
6867 for (cur = compose->account->customhdr_list; cur != NULL;
6868 cur = cur->next) {
6869 CustomHeader *chdr = (CustomHeader *)cur->data;
6871 if (custom_header_is_allowed(chdr->name)
6872 && chdr->value != NULL
6873 && *(chdr->value) != '\0') {
6874 compose_convert_header
6875 (compose, buf, sizeof(buf),
6876 chdr->value,
6877 strlen(chdr->name) + 2, FALSE);
6878 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6883 /* Automatic Faces and X-Faces */
6884 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6885 g_string_append_printf(header, "X-Face: %s\n", buf);
6887 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6888 g_string_append_printf(header, "X-Face: %s\n", buf);
6890 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6891 g_string_append_printf(header, "Face: %s\n", buf);
6893 else if (get_default_face (buf, sizeof(buf)) == 0) {
6894 g_string_append_printf(header, "Face: %s\n", buf);
6897 /* PRIORITY */
6898 switch (compose->priority) {
6899 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6900 "X-Priority: 1 (Highest)\n");
6901 break;
6902 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6903 "X-Priority: 2 (High)\n");
6904 break;
6905 case PRIORITY_NORMAL: break;
6906 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6907 "X-Priority: 4 (Low)\n");
6908 break;
6909 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6910 "X-Priority: 5 (Lowest)\n");
6911 break;
6912 default: debug_print("compose: priority unknown : %d\n",
6913 compose->priority);
6916 /* get special headers */
6917 for (list = compose->header_list; list; list = list->next) {
6918 ComposeHeaderEntry *headerentry;
6919 gchar *tmp;
6920 gchar *headername;
6921 gchar *headername_wcolon;
6922 const gchar *headername_trans;
6923 gchar *headervalue;
6924 gchar **string;
6925 gboolean standard_header = FALSE;
6927 headerentry = ((ComposeHeaderEntry *)list->data);
6929 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6930 g_strstrip(tmp);
6931 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6932 g_free(tmp);
6933 continue;
6936 if (!strstr(tmp, ":")) {
6937 headername_wcolon = g_strconcat(tmp, ":", NULL);
6938 headername = g_strdup(tmp);
6939 } else {
6940 headername_wcolon = g_strdup(tmp);
6941 headername = g_strdup(strtok(tmp, ":"));
6943 g_free(tmp);
6945 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6946 Xstrdup_a(headervalue, entry_str, return NULL);
6947 subst_char(headervalue, '\r', ' ');
6948 subst_char(headervalue, '\n', ' ');
6949 g_strstrip(headervalue);
6950 if (*headervalue != '\0') {
6951 string = std_headers;
6952 while (*string != NULL && !standard_header) {
6953 headername_trans = prefs_common_translated_header_name(*string);
6954 /* support mixed translated and untranslated headers */
6955 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6956 standard_header = TRUE;
6957 string++;
6959 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6960 /* store untranslated header name */
6961 g_string_append_printf(header, "%s %s\n",
6962 compose_untranslated_header_name(headername_wcolon), headervalue);
6965 g_free(headername);
6966 g_free(headername_wcolon);
6969 str = header->str;
6970 g_string_free(header, FALSE);
6972 return str;
6975 #undef IS_IN_CUSTOM_HEADER
6977 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6978 gint header_len, gboolean addr_field)
6980 gchar *tmpstr = NULL;
6981 const gchar *out_codeset = NULL;
6983 cm_return_if_fail(src != NULL);
6984 cm_return_if_fail(dest != NULL);
6986 if (len < 1) return;
6988 tmpstr = g_strdup(src);
6990 subst_char(tmpstr, '\n', ' ');
6991 subst_char(tmpstr, '\r', ' ');
6992 g_strchomp(tmpstr);
6994 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6995 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6996 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6997 g_free(tmpstr);
6998 tmpstr = mybuf;
7001 codeconv_set_strict(TRUE);
7002 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7003 conv_get_charset_str(compose->out_encoding));
7004 codeconv_set_strict(FALSE);
7006 if (!dest || *dest == '\0') {
7007 gchar *test_conv_global_out = NULL;
7008 gchar *test_conv_reply = NULL;
7010 /* automatic mode. be automatic. */
7011 codeconv_set_strict(TRUE);
7013 out_codeset = conv_get_outgoing_charset_str();
7014 if (out_codeset) {
7015 debug_print("trying to convert to %s\n", out_codeset);
7016 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7019 if (!test_conv_global_out && compose->orig_charset
7020 && strcmp(compose->orig_charset, CS_US_ASCII)) {
7021 out_codeset = compose->orig_charset;
7022 debug_print("failure; trying to convert to %s\n", out_codeset);
7023 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7026 if (!test_conv_global_out && !test_conv_reply) {
7027 /* we're lost */
7028 out_codeset = CS_INTERNAL;
7029 debug_print("finally using %s\n", out_codeset);
7031 g_free(test_conv_global_out);
7032 g_free(test_conv_reply);
7033 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7034 out_codeset);
7035 codeconv_set_strict(FALSE);
7037 g_free(tmpstr);
7040 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7042 gchar *address;
7044 cm_return_if_fail(user_data != NULL);
7046 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7047 g_strstrip(address);
7048 if (*address != '\0') {
7049 gchar *name = procheader_get_fromname(address);
7050 extract_address(address);
7051 #ifndef USE_ALT_ADDRBOOK
7052 addressbook_add_contact(name, address, NULL, NULL);
7053 #else
7054 debug_print("%s: %s\n", name, address);
7055 if (addressadd_selection(name, address, NULL, NULL)) {
7056 debug_print( "addressbook_add_contact - added\n" );
7058 #endif
7060 g_free(address);
7063 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7065 GtkWidget *menuitem;
7066 gchar *address;
7068 cm_return_if_fail(menu != NULL);
7069 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7071 menuitem = gtk_separator_menu_item_new();
7072 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7073 gtk_widget_show(menuitem);
7075 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7076 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7078 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7079 g_strstrip(address);
7080 if (*address == '\0') {
7081 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7084 g_signal_connect(G_OBJECT(menuitem), "activate",
7085 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7086 gtk_widget_show(menuitem);
7089 void compose_add_extra_header(gchar *header, GtkListStore *model)
7091 GtkTreeIter iter;
7092 if (strcmp(header, "")) {
7093 COMBOBOX_ADD(model, header, COMPOSE_TO);
7097 void compose_add_extra_header_entries(GtkListStore *model)
7099 FILE *exh;
7100 gchar *exhrc;
7101 gchar buf[BUFFSIZE];
7102 gint lastc;
7104 if (extra_headers == NULL) {
7105 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7106 if ((exh = claws_fopen(exhrc, "rb")) == NULL) {
7107 debug_print("extra headers file not found\n");
7108 goto extra_headers_done;
7110 while (claws_fgets(buf, BUFFSIZE, exh) != NULL) {
7111 lastc = strlen(buf) - 1; /* remove trailing control chars */
7112 while (lastc >= 0 && buf[lastc] != ':')
7113 buf[lastc--] = '\0';
7114 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7115 buf[lastc] = '\0'; /* remove trailing : for comparison */
7116 if (custom_header_is_allowed(buf)) {
7117 buf[lastc] = ':';
7118 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7120 else
7121 g_message("disallowed extra header line: %s\n", buf);
7123 else {
7124 if (buf[0] != '#')
7125 g_message("invalid extra header line: %s\n", buf);
7128 claws_fclose(exh);
7129 extra_headers_done:
7130 g_free(exhrc);
7131 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7132 extra_headers = g_slist_reverse(extra_headers);
7134 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7137 #ifdef USE_LDAP
7138 static void _ldap_srv_func(gpointer data, gpointer user_data)
7140 LdapServer *server = (LdapServer *)data;
7141 gboolean *enable = (gboolean *)user_data;
7143 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7144 server->searchFlag = *enable;
7146 #endif
7148 static void compose_create_header_entry(Compose *compose)
7150 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7152 GtkWidget *combo;
7153 GtkWidget *entry;
7154 GtkWidget *button;
7155 GtkWidget *hbox;
7156 gchar **string;
7157 const gchar *header = NULL;
7158 ComposeHeaderEntry *headerentry;
7159 gboolean standard_header = FALSE;
7160 GtkListStore *model;
7161 GtkTreeIter iter;
7163 headerentry = g_new0(ComposeHeaderEntry, 1);
7165 /* Combo box model */
7166 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7167 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7168 COMPOSE_TO);
7169 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7170 COMPOSE_CC);
7171 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7172 COMPOSE_BCC);
7173 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7174 COMPOSE_NEWSGROUPS);
7175 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7176 COMPOSE_REPLYTO);
7177 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7178 COMPOSE_FOLLOWUPTO);
7179 compose_add_extra_header_entries(model);
7181 /* Combo box */
7182 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7183 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7184 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7185 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7186 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7187 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7188 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7189 G_CALLBACK(compose_grab_focus_cb), compose);
7190 gtk_widget_show(combo);
7192 /* Putting only the combobox child into focus chain of its parent causes
7193 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7194 * This eliminates need to pres Tab twice in order to really get from the
7195 * combobox to next widget. */
7196 GList *l = NULL;
7197 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7198 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7199 g_list_free(l);
7201 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7202 compose->header_nextrow, compose->header_nextrow+1,
7203 GTK_SHRINK, GTK_FILL, 0, 0);
7204 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7205 const gchar *last_header_entry = gtk_entry_get_text(
7206 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7207 string = headers;
7208 while (*string != NULL) {
7209 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7210 standard_header = TRUE;
7211 string++;
7213 if (standard_header)
7214 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7216 if (!compose->header_last || !standard_header) {
7217 switch(compose->account->protocol) {
7218 case A_NNTP:
7219 header = prefs_common_translated_header_name("Newsgroups:");
7220 break;
7221 default:
7222 header = prefs_common_translated_header_name("To:");
7223 break;
7226 if (header)
7227 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7229 gtk_editable_set_editable(
7230 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7231 prefs_common.type_any_header);
7233 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7234 G_CALLBACK(compose_grab_focus_cb), compose);
7236 /* Entry field with cleanup button */
7237 button = gtk_button_new();
7238 gtk_button_set_image(GTK_BUTTON(button),
7239 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7240 gtk_widget_show(button);
7241 CLAWS_SET_TIP(button,
7242 _("Delete entry contents"));
7243 entry = gtk_entry_new();
7244 gtk_widget_show(entry);
7245 CLAWS_SET_TIP(entry,
7246 _("Use <tab> to autocomplete from addressbook"));
7247 hbox = gtk_hbox_new (FALSE, 0);
7248 gtk_widget_show(hbox);
7249 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7250 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7251 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7252 compose->header_nextrow, compose->header_nextrow+1,
7253 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7255 g_signal_connect(G_OBJECT(entry), "key-press-event",
7256 G_CALLBACK(compose_headerentry_key_press_event_cb),
7257 headerentry);
7258 g_signal_connect(G_OBJECT(entry), "changed",
7259 G_CALLBACK(compose_headerentry_changed_cb),
7260 headerentry);
7261 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7262 G_CALLBACK(compose_grab_focus_cb), compose);
7264 g_signal_connect(G_OBJECT(button), "clicked",
7265 G_CALLBACK(compose_headerentry_button_clicked_cb),
7266 headerentry);
7268 /* email dnd */
7269 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7270 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7271 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7272 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7273 G_CALLBACK(compose_header_drag_received_cb),
7274 entry);
7275 g_signal_connect(G_OBJECT(entry), "drag-drop",
7276 G_CALLBACK(compose_drag_drop),
7277 compose);
7278 g_signal_connect(G_OBJECT(entry), "populate-popup",
7279 G_CALLBACK(compose_entry_popup_extend),
7280 NULL);
7282 #ifdef USE_LDAP
7283 #ifndef PASSWORD_CRYPTO_OLD
7284 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7285 if (pwd_servers != NULL && master_passphrase() == NULL) {
7286 gboolean enable = FALSE;
7287 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7288 /* Temporarily disable password-protected LDAP servers,
7289 * because user did not provide a master passphrase.
7290 * We can safely enable searchFlag on all servers in this list
7291 * later, since addrindex_get_password_protected_ldap_servers()
7292 * includes servers which have it enabled initially. */
7293 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7294 compose->passworded_ldap_servers = pwd_servers;
7296 #endif /* PASSWORD_CRYPTO_OLD */
7297 #endif /* USE_LDAP */
7299 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7301 headerentry->compose = compose;
7302 headerentry->combo = combo;
7303 headerentry->entry = entry;
7304 headerentry->button = button;
7305 headerentry->hbox = hbox;
7306 headerentry->headernum = compose->header_nextrow;
7307 headerentry->type = PREF_NONE;
7309 compose->header_nextrow++;
7310 compose->header_last = headerentry;
7311 compose->header_list =
7312 g_slist_append(compose->header_list,
7313 headerentry);
7316 static void compose_add_header_entry(Compose *compose, const gchar *header,
7317 gchar *text, ComposePrefType pref_type)
7319 ComposeHeaderEntry *last_header = compose->header_last;
7320 gchar *tmp = g_strdup(text), *email;
7321 gboolean replyto_hdr;
7323 replyto_hdr = (!strcasecmp(header,
7324 prefs_common_translated_header_name("Reply-To:")) ||
7325 !strcasecmp(header,
7326 prefs_common_translated_header_name("Followup-To:")) ||
7327 !strcasecmp(header,
7328 prefs_common_translated_header_name("In-Reply-To:")));
7330 extract_address(tmp);
7331 email = g_utf8_strdown(tmp, -1);
7333 if (replyto_hdr == FALSE &&
7334 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7336 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7337 header, text, (gint) pref_type);
7338 g_free(email);
7339 g_free(tmp);
7340 return;
7343 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7344 gtk_entry_set_text(GTK_ENTRY(
7345 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7346 else
7347 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7348 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7349 last_header->type = pref_type;
7351 if (replyto_hdr == FALSE)
7352 g_hash_table_insert(compose->email_hashtable, email,
7353 GUINT_TO_POINTER(1));
7354 else
7355 g_free(email);
7357 g_free(tmp);
7360 static void compose_destroy_headerentry(Compose *compose,
7361 ComposeHeaderEntry *headerentry)
7363 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7364 gchar *email;
7366 extract_address(text);
7367 email = g_utf8_strdown(text, -1);
7368 g_hash_table_remove(compose->email_hashtable, email);
7369 g_free(text);
7370 g_free(email);
7372 gtk_widget_destroy(headerentry->combo);
7373 gtk_widget_destroy(headerentry->entry);
7374 gtk_widget_destroy(headerentry->button);
7375 gtk_widget_destroy(headerentry->hbox);
7376 g_free(headerentry);
7379 static void compose_remove_header_entries(Compose *compose)
7381 GSList *list;
7382 for (list = compose->header_list; list; list = list->next)
7383 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7385 compose->header_last = NULL;
7386 g_slist_free(compose->header_list);
7387 compose->header_list = NULL;
7388 compose->header_nextrow = 1;
7389 compose_create_header_entry(compose);
7392 static GtkWidget *compose_create_header(Compose *compose)
7394 GtkWidget *from_optmenu_hbox;
7395 GtkWidget *header_table_main;
7396 GtkWidget *header_scrolledwin;
7397 GtkWidget *header_table;
7399 /* parent with account selection and from header */
7400 header_table_main = gtk_table_new(2, 2, FALSE);
7401 gtk_widget_show(header_table_main);
7402 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7404 from_optmenu_hbox = compose_account_option_menu_create(compose);
7405 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7406 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7408 /* child with header labels and entries */
7409 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7410 gtk_widget_show(header_scrolledwin);
7411 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7413 header_table = gtk_table_new(2, 2, FALSE);
7414 gtk_widget_show(header_table);
7415 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7416 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7417 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7418 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7419 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7421 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7422 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7424 compose->header_table = header_table;
7425 compose->header_list = NULL;
7426 compose->header_nextrow = 0;
7428 compose_create_header_entry(compose);
7430 compose->table = NULL;
7432 return header_table_main;
7435 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7437 Compose *compose = (Compose *)data;
7438 GdkEventButton event;
7440 event.button = 3;
7441 event.time = gtk_get_current_event_time();
7443 return attach_button_pressed(compose->attach_clist, &event, compose);
7446 static GtkWidget *compose_create_attach(Compose *compose)
7448 GtkWidget *attach_scrwin;
7449 GtkWidget *attach_clist;
7451 GtkListStore *store;
7452 GtkCellRenderer *renderer;
7453 GtkTreeViewColumn *column;
7454 GtkTreeSelection *selection;
7456 /* attachment list */
7457 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7458 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7459 GTK_POLICY_AUTOMATIC,
7460 GTK_POLICY_AUTOMATIC);
7461 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7463 store = gtk_list_store_new(N_ATTACH_COLS,
7464 G_TYPE_STRING,
7465 G_TYPE_STRING,
7466 G_TYPE_STRING,
7467 G_TYPE_STRING,
7468 G_TYPE_POINTER,
7469 G_TYPE_AUTO_POINTER,
7470 -1);
7471 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7472 (GTK_TREE_MODEL(store)));
7473 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7474 g_object_unref(store);
7476 renderer = gtk_cell_renderer_text_new();
7477 column = gtk_tree_view_column_new_with_attributes
7478 (_("Mime type"), renderer, "text",
7479 COL_MIMETYPE, NULL);
7480 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7482 renderer = gtk_cell_renderer_text_new();
7483 column = gtk_tree_view_column_new_with_attributes
7484 (_("Size"), renderer, "text",
7485 COL_SIZE, NULL);
7486 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7488 renderer = gtk_cell_renderer_text_new();
7489 column = gtk_tree_view_column_new_with_attributes
7490 (_("Name"), renderer, "text",
7491 COL_NAME, NULL);
7492 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7494 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7495 prefs_common.use_stripes_everywhere);
7496 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7497 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7499 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7500 G_CALLBACK(attach_selected), compose);
7501 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7502 G_CALLBACK(attach_button_pressed), compose);
7503 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7504 G_CALLBACK(popup_attach_button_pressed), compose);
7505 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7506 G_CALLBACK(attach_key_pressed), compose);
7508 /* drag and drop */
7509 gtk_drag_dest_set(attach_clist,
7510 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7511 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7512 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7513 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7514 G_CALLBACK(compose_attach_drag_received_cb),
7515 compose);
7516 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7517 G_CALLBACK(compose_drag_drop),
7518 compose);
7520 compose->attach_scrwin = attach_scrwin;
7521 compose->attach_clist = attach_clist;
7523 return attach_scrwin;
7526 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7528 static GtkWidget *compose_create_others(Compose *compose)
7530 GtkWidget *table;
7531 GtkWidget *savemsg_checkbtn;
7532 GtkWidget *savemsg_combo;
7533 GtkWidget *savemsg_select;
7535 guint rowcount = 0;
7536 gchar *folderidentifier;
7538 /* Table for settings */
7539 table = gtk_table_new(3, 1, FALSE);
7540 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7541 gtk_widget_show(table);
7542 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7543 rowcount = 0;
7545 /* Save Message to folder */
7546 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7547 gtk_widget_show(savemsg_checkbtn);
7548 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7549 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7550 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7553 savemsg_combo = gtk_combo_box_text_new_with_entry();
7554 compose->savemsg_checkbtn = savemsg_checkbtn;
7555 compose->savemsg_combo = savemsg_combo;
7556 gtk_widget_show(savemsg_combo);
7558 if (prefs_common.compose_save_to_history)
7559 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7560 prefs_common.compose_save_to_history);
7561 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7562 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7563 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7564 G_CALLBACK(compose_grab_focus_cb), compose);
7565 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7566 if (compose->account->set_sent_folder || prefs_common.savemsg)
7567 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), TRUE);
7568 else
7569 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), FALSE);
7570 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), TRUE);
7571 folderidentifier = folder_item_get_identifier(account_get_special_folder
7572 (compose->account, F_OUTBOX));
7573 compose_set_save_to(compose, folderidentifier);
7574 g_free(folderidentifier);
7577 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7578 gtk_widget_show(savemsg_select);
7579 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7580 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7581 G_CALLBACK(compose_savemsg_select_cb),
7582 compose);
7584 return table;
7587 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7589 FolderItem *dest;
7590 gchar * path;
7592 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7593 _("Select folder to save message to"));
7594 if (!dest) return;
7596 path = folder_item_get_identifier(dest);
7598 compose_set_save_to(compose, path);
7599 g_free(path);
7602 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7603 GdkAtom clip, GtkTextIter *insert_place);
7606 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7607 Compose *compose)
7609 gint prev_autowrap;
7610 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7611 #if USE_ENCHANT
7612 if (event->button == 3) {
7613 GtkTextIter iter;
7614 GtkTextIter sel_start, sel_end;
7615 gboolean stuff_selected;
7616 gint x, y;
7617 /* move the cursor to allow GtkAspell to check the word
7618 * under the mouse */
7619 if (event->x && event->y) {
7620 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7621 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7622 &x, &y);
7623 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7624 &iter, x, y);
7625 } else {
7626 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7627 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7629 /* get selection */
7630 stuff_selected = gtk_text_buffer_get_selection_bounds(
7631 buffer,
7632 &sel_start, &sel_end);
7634 gtk_text_buffer_place_cursor (buffer, &iter);
7635 /* reselect stuff */
7636 if (stuff_selected
7637 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7638 gtk_text_buffer_select_range(buffer,
7639 &sel_start, &sel_end);
7641 return FALSE; /* pass the event so that the right-click goes through */
7643 #endif
7644 if (event->button == 2) {
7645 GtkTextIter iter;
7646 gint x, y;
7647 BLOCK_WRAP();
7649 /* get the middle-click position to paste at the correct place */
7650 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7651 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7652 &x, &y);
7653 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7654 &iter, x, y);
7656 entry_paste_clipboard(compose, text,
7657 prefs_common.linewrap_pastes,
7658 GDK_SELECTION_PRIMARY, &iter);
7659 UNBLOCK_WRAP();
7660 return TRUE;
7662 return FALSE;
7665 #if USE_ENCHANT
7666 static void compose_spell_menu_changed(void *data)
7668 Compose *compose = (Compose *)data;
7669 GSList *items;
7670 GtkWidget *menuitem;
7671 GtkWidget *parent_item;
7672 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7673 GSList *spell_menu;
7675 if (compose->gtkaspell == NULL)
7676 return;
7678 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7679 "/Menu/Spelling/Options");
7681 /* setting the submenu removes /Spelling/Options from the factory
7682 * so we need to save it */
7684 if (parent_item == NULL) {
7685 parent_item = compose->aspell_options_menu;
7686 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7687 } else
7688 compose->aspell_options_menu = parent_item;
7690 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7692 spell_menu = g_slist_reverse(spell_menu);
7693 for (items = spell_menu;
7694 items; items = items->next) {
7695 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7696 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7697 gtk_widget_show(GTK_WIDGET(menuitem));
7699 g_slist_free(spell_menu);
7701 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7702 gtk_widget_show(parent_item);
7705 static void compose_dict_changed(void *data)
7707 Compose *compose = (Compose *) data;
7709 if(!compose->gtkaspell)
7710 return;
7711 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7712 return;
7714 gtkaspell_highlight_all(compose->gtkaspell);
7715 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7717 #endif
7719 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7721 Compose *compose = (Compose *)data;
7722 GdkEventButton event;
7724 event.button = 3;
7725 event.time = gtk_get_current_event_time();
7726 event.x = 0;
7727 event.y = 0;
7729 return text_clicked(compose->text, &event, compose);
7732 static gboolean compose_force_window_origin = TRUE;
7733 static Compose *compose_create(PrefsAccount *account,
7734 FolderItem *folder,
7735 ComposeMode mode,
7736 gboolean batch)
7738 Compose *compose;
7739 GtkWidget *window;
7740 GtkWidget *vbox;
7741 GtkWidget *menubar;
7742 GtkWidget *handlebox;
7744 GtkWidget *notebook;
7746 GtkWidget *attach_hbox;
7747 GtkWidget *attach_lab1;
7748 GtkWidget *attach_lab2;
7750 GtkWidget *vbox2;
7752 GtkWidget *label;
7753 GtkWidget *subject_hbox;
7754 GtkWidget *subject_frame;
7755 GtkWidget *subject_entry;
7756 GtkWidget *subject;
7757 GtkWidget *paned;
7759 GtkWidget *edit_vbox;
7760 GtkWidget *ruler_hbox;
7761 GtkWidget *ruler;
7762 GtkWidget *scrolledwin;
7763 GtkWidget *text;
7764 GtkTextBuffer *buffer;
7765 GtkClipboard *clipboard;
7767 UndoMain *undostruct;
7769 GtkWidget *popupmenu;
7770 GtkWidget *tmpl_menu;
7771 GtkActionGroup *action_group = NULL;
7773 #if USE_ENCHANT
7774 GtkAspell * gtkaspell = NULL;
7775 #endif
7777 static GdkGeometry geometry;
7779 cm_return_val_if_fail(account != NULL, NULL);
7781 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7782 &default_header_bgcolor);
7783 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7784 &default_header_color);
7786 debug_print("Creating compose window...\n");
7787 compose = g_new0(Compose, 1);
7789 compose->batch = batch;
7790 compose->account = account;
7791 compose->folder = folder;
7793 compose->mutex = cm_mutex_new();
7794 compose->set_cursor_pos = -1;
7796 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7798 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7799 gtk_widget_set_size_request(window, prefs_common.compose_width,
7800 prefs_common.compose_height);
7802 if (!geometry.max_width) {
7803 geometry.max_width = gdk_screen_width();
7804 geometry.max_height = gdk_screen_height();
7807 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7808 &geometry, GDK_HINT_MAX_SIZE);
7809 if (!geometry.min_width) {
7810 geometry.min_width = 600;
7811 geometry.min_height = 440;
7813 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7814 &geometry, GDK_HINT_MIN_SIZE);
7816 #ifndef GENERIC_UMPC
7817 if (compose_force_window_origin)
7818 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7819 prefs_common.compose_y);
7820 #endif
7821 g_signal_connect(G_OBJECT(window), "delete_event",
7822 G_CALLBACK(compose_delete_cb), compose);
7823 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7824 gtk_widget_realize(window);
7826 gtkut_widget_set_composer_icon(window);
7828 vbox = gtk_vbox_new(FALSE, 0);
7829 gtk_container_add(GTK_CONTAINER(window), vbox);
7831 compose->ui_manager = gtk_ui_manager_new();
7832 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7833 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7834 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7835 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7836 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7837 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7838 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7839 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7840 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7841 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7847 #ifdef USE_ENCHANT
7848 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7849 #endif
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7854 /* Compose menu */
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7869 /* Edit menu */
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7885 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7898 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7913 #if USE_ENCHANT
7914 /* Spelling menu */
7915 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7921 #endif
7923 /* Options menu */
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7937 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7938 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7940 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7941 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7942 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7943 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7946 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7947 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7948 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7949 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7951 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7953 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7954 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7955 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7956 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7957 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7959 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7960 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_ISO_8859_1, "Options/Encoding/Western/"CS_ISO_8859_1, GTK_UI_MANAGER_MENUITEM)
7961 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_ISO_8859_15, "Options/Encoding/Western/"CS_ISO_8859_15, GTK_UI_MANAGER_MENUITEM)
7962 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7964 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7966 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7967 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Baltic", CS_ISO_8859_13, "Options/Encoding/Baltic/"CS_ISO_8859_13, GTK_UI_MANAGER_MENUITEM)
7968 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Baltic", CS_ISO_8859_4, "Options/Encoding/Baltic/"CS_ISO_8859_4, GTK_UI_MANAGER_MENUITEM)
7970 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7972 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7973 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_ISO_8859_8, "Options/Encoding/Hebrew/"CS_ISO_8859_8, GTK_UI_MANAGER_MENUITEM)
7974 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7976 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7977 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_ISO_8859_6, "Options/Encoding/Arabic/"CS_ISO_8859_6, GTK_UI_MANAGER_MENUITEM)
7978 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7980 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7982 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7983 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_ISO_8859_5, "Options/Encoding/Cyrillic/"CS_ISO_8859_5, GTK_UI_MANAGER_MENUITEM)
7984 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7985 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7986 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7987 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7989 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7990 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_ISO_2022_JP, "Options/Encoding/Japanese/"CS_ISO_2022_JP, GTK_UI_MANAGER_MENUITEM)
7991 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_ISO_2022_JP_2, "Options/Encoding/Japanese/"CS_ISO_2022_JP_2, GTK_UI_MANAGER_MENUITEM)
7992 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7993 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7995 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7996 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7997 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7998 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7999 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
8000 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
8002 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
8003 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
8004 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_ISO_2022_KR, "Options/Encoding/Korean/"CS_ISO_2022_KR, GTK_UI_MANAGER_MENUITEM)
8006 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
8007 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
8008 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
8009 /* phew. */
8011 /* Tools menu */
8012 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
8013 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
8014 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
8015 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8016 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
8017 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8019 /* Help menu */
8020 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
8022 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
8023 gtk_widget_show_all(menubar);
8025 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
8026 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
8028 if (prefs_common.toolbar_detachable) {
8029 handlebox = gtk_handle_box_new();
8030 } else {
8031 handlebox = gtk_hbox_new(FALSE, 0);
8033 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8035 gtk_widget_realize(handlebox);
8036 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8037 (gpointer)compose);
8039 vbox2 = gtk_vbox_new(FALSE, 2);
8040 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8041 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8043 /* Notebook */
8044 notebook = gtk_notebook_new();
8045 gtk_widget_show(notebook);
8047 /* header labels and entries */
8048 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8049 compose_create_header(compose),
8050 gtk_label_new_with_mnemonic(_("Hea_der")));
8051 /* attachment list */
8052 attach_hbox = gtk_hbox_new(FALSE, 0);
8053 gtk_widget_show(attach_hbox);
8055 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8056 gtk_widget_show(attach_lab1);
8057 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8059 attach_lab2 = gtk_label_new("");
8060 gtk_widget_show(attach_lab2);
8061 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8063 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8064 compose_create_attach(compose),
8065 attach_hbox);
8066 /* Others Tab */
8067 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8068 compose_create_others(compose),
8069 gtk_label_new_with_mnemonic(_("Othe_rs")));
8071 /* Subject */
8072 subject_hbox = gtk_hbox_new(FALSE, 0);
8073 gtk_widget_show(subject_hbox);
8075 subject_frame = gtk_frame_new(NULL);
8076 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8077 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8078 gtk_widget_show(subject_frame);
8080 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8081 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8082 gtk_widget_show(subject);
8084 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8085 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8086 gtk_widget_show(label);
8088 #ifdef USE_ENCHANT
8089 subject_entry = claws_spell_entry_new();
8090 #else
8091 subject_entry = gtk_entry_new();
8092 #endif
8093 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8094 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8095 G_CALLBACK(compose_grab_focus_cb), compose);
8096 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8097 gtk_widget_show(subject_entry);
8098 compose->subject_entry = subject_entry;
8099 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8101 edit_vbox = gtk_vbox_new(FALSE, 0);
8103 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8105 /* ruler */
8106 ruler_hbox = gtk_hbox_new(FALSE, 0);
8107 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8109 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8110 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8111 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8112 BORDER_WIDTH);
8114 /* text widget */
8115 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8116 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8117 GTK_POLICY_AUTOMATIC,
8118 GTK_POLICY_AUTOMATIC);
8119 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8120 GTK_SHADOW_IN);
8121 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8123 text = gtk_text_view_new();
8124 if (prefs_common.show_compose_margin) {
8125 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8126 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8128 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8129 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8130 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8131 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8132 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8134 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8135 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8136 G_CALLBACK(compose_edit_size_alloc),
8137 ruler);
8138 g_signal_connect(G_OBJECT(buffer), "changed",
8139 G_CALLBACK(compose_changed_cb), compose);
8140 g_signal_connect(G_OBJECT(text), "grab_focus",
8141 G_CALLBACK(compose_grab_focus_cb), compose);
8142 g_signal_connect(G_OBJECT(buffer), "insert_text",
8143 G_CALLBACK(text_inserted), compose);
8144 g_signal_connect(G_OBJECT(text), "button_press_event",
8145 G_CALLBACK(text_clicked), compose);
8146 g_signal_connect(G_OBJECT(text), "popup-menu",
8147 G_CALLBACK(compose_popup_menu), compose);
8148 g_signal_connect(G_OBJECT(subject_entry), "changed",
8149 G_CALLBACK(compose_changed_cb), compose);
8150 g_signal_connect(G_OBJECT(subject_entry), "activate",
8151 G_CALLBACK(compose_subject_entry_activated), compose);
8153 /* drag and drop */
8154 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8155 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8156 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8157 g_signal_connect(G_OBJECT(text), "drag_data_received",
8158 G_CALLBACK(compose_insert_drag_received_cb),
8159 compose);
8160 g_signal_connect(G_OBJECT(text), "drag-drop",
8161 G_CALLBACK(compose_drag_drop),
8162 compose);
8163 g_signal_connect(G_OBJECT(text), "key-press-event",
8164 G_CALLBACK(completion_set_focus_to_subject),
8165 compose);
8166 gtk_widget_show_all(vbox);
8168 /* pane between attach clist and text */
8169 paned = gtk_vpaned_new();
8170 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8171 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8172 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8173 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8174 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8175 G_CALLBACK(compose_notebook_size_alloc), paned);
8177 gtk_widget_show_all(paned);
8180 if (prefs_common.textfont) {
8181 PangoFontDescription *font_desc;
8183 font_desc = pango_font_description_from_string
8184 (prefs_common.textfont);
8185 if (font_desc) {
8186 gtk_widget_modify_font(text, font_desc);
8187 pango_font_description_free(font_desc);
8191 gtk_action_group_add_actions(action_group, compose_popup_entries,
8192 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8193 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8194 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8195 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8196 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8197 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8198 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8200 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8202 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8203 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8204 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8206 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8208 undostruct = undo_init(text);
8209 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8210 compose);
8212 address_completion_start(window);
8214 compose->window = window;
8215 compose->vbox = vbox;
8216 compose->menubar = menubar;
8217 compose->handlebox = handlebox;
8219 compose->vbox2 = vbox2;
8221 compose->paned = paned;
8223 compose->attach_label = attach_lab2;
8225 compose->notebook = notebook;
8226 compose->edit_vbox = edit_vbox;
8227 compose->ruler_hbox = ruler_hbox;
8228 compose->ruler = ruler;
8229 compose->scrolledwin = scrolledwin;
8230 compose->text = text;
8232 compose->focused_editable = NULL;
8234 compose->popupmenu = popupmenu;
8236 compose->tmpl_menu = tmpl_menu;
8238 compose->mode = mode;
8239 compose->rmode = mode;
8241 compose->targetinfo = NULL;
8242 compose->replyinfo = NULL;
8243 compose->fwdinfo = NULL;
8245 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8246 g_str_equal, (GDestroyNotify) g_free, NULL);
8248 compose->replyto = NULL;
8249 compose->cc = NULL;
8250 compose->bcc = NULL;
8251 compose->followup_to = NULL;
8253 compose->ml_post = NULL;
8255 compose->inreplyto = NULL;
8256 compose->references = NULL;
8257 compose->msgid = NULL;
8258 compose->boundary = NULL;
8260 compose->autowrap = prefs_common.autowrap;
8261 compose->autoindent = prefs_common.auto_indent;
8262 compose->use_signing = FALSE;
8263 compose->use_encryption = FALSE;
8264 compose->privacy_system = NULL;
8265 compose->encdata = NULL;
8267 compose->modified = FALSE;
8269 compose->return_receipt = FALSE;
8271 compose->to_list = NULL;
8272 compose->newsgroup_list = NULL;
8274 compose->undostruct = undostruct;
8276 compose->sig_str = NULL;
8278 compose->exteditor_file = NULL;
8279 compose->exteditor_pid = -1;
8280 compose->exteditor_tag = -1;
8281 compose->exteditor_socket = NULL;
8282 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8284 compose->folder_update_callback_id =
8285 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8286 compose_update_folder_hook,
8287 (gpointer) compose);
8289 #if USE_ENCHANT
8290 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8291 if (mode != COMPOSE_REDIRECT) {
8292 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8293 strcmp(prefs_common.dictionary, "")) {
8294 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8295 prefs_common.alt_dictionary,
8296 conv_get_locale_charset_str(),
8297 prefs_common.color[COL_MISSPELLED],
8298 prefs_common.check_while_typing,
8299 prefs_common.recheck_when_changing_dict,
8300 prefs_common.use_alternate,
8301 prefs_common.use_both_dicts,
8302 GTK_TEXT_VIEW(text),
8303 GTK_WINDOW(compose->window),
8304 compose_dict_changed,
8305 compose_spell_menu_changed,
8306 compose);
8307 if (!gtkaspell) {
8308 alertpanel_error(_("Spell checker could not "
8309 "be started.\n%s"),
8310 gtkaspell_checkers_strerror());
8311 gtkaspell_checkers_reset_error();
8312 } else {
8313 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8317 compose->gtkaspell = gtkaspell;
8318 compose_spell_menu_changed(compose);
8319 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8320 #endif
8322 compose_select_account(compose, account, TRUE);
8324 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8325 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8327 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8328 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8330 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8331 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8333 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8334 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8336 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8337 if (account->protocol != A_NNTP)
8338 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8339 prefs_common_translated_header_name("To:"));
8340 else
8341 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8342 prefs_common_translated_header_name("Newsgroups:"));
8344 #ifndef USE_ALT_ADDRBOOK
8345 addressbook_set_target_compose(compose);
8346 #endif
8347 if (mode != COMPOSE_REDIRECT)
8348 compose_set_template_menu(compose);
8349 else {
8350 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8353 compose_list = g_list_append(compose_list, compose);
8355 if (!prefs_common.show_ruler)
8356 gtk_widget_hide(ruler_hbox);
8358 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8360 /* Priority */
8361 compose->priority = PRIORITY_NORMAL;
8362 compose_update_priority_menu_item(compose);
8364 compose_set_out_encoding(compose);
8366 /* Actions menu */
8367 compose_update_actions_menu(compose);
8369 /* Privacy Systems menu */
8370 compose_update_privacy_systems_menu(compose);
8371 compose_activate_privacy_system(compose, account, TRUE);
8373 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8374 if (batch) {
8375 gtk_widget_realize(window);
8376 } else {
8377 gtk_widget_show(window);
8380 return compose;
8383 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8385 GList *accounts;
8386 GtkWidget *hbox;
8387 GtkWidget *optmenu;
8388 GtkWidget *optmenubox;
8389 GtkWidget *fromlabel;
8390 GtkListStore *menu;
8391 GtkTreeIter iter;
8392 GtkWidget *from_name = NULL;
8394 gint num = 0, def_menu = 0;
8396 accounts = account_get_list();
8397 cm_return_val_if_fail(accounts != NULL, NULL);
8399 optmenubox = gtk_event_box_new();
8400 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8401 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8403 hbox = gtk_hbox_new(FALSE, 4);
8404 from_name = gtk_entry_new();
8406 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8407 G_CALLBACK(compose_grab_focus_cb), compose);
8408 g_signal_connect_after(G_OBJECT(from_name), "activate",
8409 G_CALLBACK(from_name_activate_cb), optmenu);
8411 for (; accounts != NULL; accounts = accounts->next, num++) {
8412 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8413 gchar *name, *from = NULL;
8415 if (ac == compose->account) def_menu = num;
8417 name = g_markup_printf_escaped("<i>%s</i>",
8418 ac->account_name);
8420 if (ac == compose->account) {
8421 if (ac->name && *ac->name) {
8422 gchar *buf;
8423 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8424 from = g_strdup_printf("%s <%s>",
8425 buf, ac->address);
8426 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8427 } else {
8428 from = g_strdup_printf("%s",
8429 ac->address);
8430 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8432 if (cur_account != compose->account) {
8433 gtk_widget_modify_base(
8434 GTK_WIDGET(from_name),
8435 GTK_STATE_NORMAL, &default_header_bgcolor);
8436 gtk_widget_modify_text(
8437 GTK_WIDGET(from_name),
8438 GTK_STATE_NORMAL, &default_header_color);
8441 COMBOBOX_ADD(menu, name, ac->account_id);
8442 g_free(name);
8443 g_free(from);
8446 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8448 g_signal_connect(G_OBJECT(optmenu), "changed",
8449 G_CALLBACK(account_activated),
8450 compose);
8451 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8452 G_CALLBACK(compose_entry_popup_extend),
8453 NULL);
8455 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8456 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8458 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8459 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8460 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8462 /* Putting only the GtkEntry into focus chain of parent hbox causes
8463 * the account selector combobox next to it to be unreachable when
8464 * navigating widgets in GtkTable with up/down arrow keys.
8465 * Note: gtk_widget_set_can_focus() was not enough. */
8466 GList *l = NULL;
8467 l = g_list_prepend(l, from_name);
8468 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8469 g_list_free(l);
8471 CLAWS_SET_TIP(optmenubox,
8472 _("Account to use for this email"));
8473 CLAWS_SET_TIP(from_name,
8474 _("Sender address to be used"));
8476 compose->account_combo = optmenu;
8477 compose->from_name = from_name;
8479 return hbox;
8482 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8484 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8485 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8486 Compose *compose = (Compose *) data;
8487 if (active) {
8488 compose->priority = value;
8492 static void compose_reply_change_mode(Compose *compose,
8493 ComposeMode action)
8495 gboolean was_modified = compose->modified;
8497 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8499 cm_return_if_fail(compose->replyinfo != NULL);
8501 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8502 ml = TRUE;
8503 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8504 followup = TRUE;
8505 if (action == COMPOSE_REPLY_TO_ALL)
8506 all = TRUE;
8507 if (action == COMPOSE_REPLY_TO_SENDER)
8508 sender = TRUE;
8509 if (action == COMPOSE_REPLY_TO_LIST)
8510 ml = TRUE;
8512 compose_remove_header_entries(compose);
8513 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8514 if (compose->account->set_autocc && compose->account->auto_cc)
8515 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8517 if (compose->account->set_autobcc && compose->account->auto_bcc)
8518 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8520 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8521 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8522 compose_show_first_last_header(compose, TRUE);
8523 compose->modified = was_modified;
8524 compose_set_title(compose);
8527 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8529 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8530 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8531 Compose *compose = (Compose *) data;
8533 if (active)
8534 compose_reply_change_mode(compose, value);
8537 static void compose_update_priority_menu_item(Compose * compose)
8539 GtkWidget *menuitem = NULL;
8540 switch (compose->priority) {
8541 case PRIORITY_HIGHEST:
8542 menuitem = gtk_ui_manager_get_widget
8543 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8544 break;
8545 case PRIORITY_HIGH:
8546 menuitem = gtk_ui_manager_get_widget
8547 (compose->ui_manager, "/Menu/Options/Priority/High");
8548 break;
8549 case PRIORITY_NORMAL:
8550 menuitem = gtk_ui_manager_get_widget
8551 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8552 break;
8553 case PRIORITY_LOW:
8554 menuitem = gtk_ui_manager_get_widget
8555 (compose->ui_manager, "/Menu/Options/Priority/Low");
8556 break;
8557 case PRIORITY_LOWEST:
8558 menuitem = gtk_ui_manager_get_widget
8559 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8560 break;
8562 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8565 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8567 Compose *compose = (Compose *) data;
8568 gchar *systemid;
8569 gboolean can_sign = FALSE, can_encrypt = FALSE;
8571 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8573 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8574 return;
8576 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8577 g_free(compose->privacy_system);
8578 compose->privacy_system = NULL;
8579 g_free(compose->encdata);
8580 compose->encdata = NULL;
8581 if (systemid != NULL) {
8582 compose->privacy_system = g_strdup(systemid);
8584 can_sign = privacy_system_can_sign(systemid);
8585 can_encrypt = privacy_system_can_encrypt(systemid);
8588 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8590 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8591 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8592 if (compose->toolbar->privacy_sign_btn != NULL) {
8593 gtk_widget_set_sensitive(
8594 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8595 can_sign);
8596 gtk_toggle_tool_button_set_active(
8597 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8598 can_sign ? compose->use_signing : FALSE);
8600 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8601 gtk_widget_set_sensitive(
8602 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8603 can_encrypt);
8604 gtk_toggle_tool_button_set_active(
8605 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8606 can_encrypt ? compose->use_encryption : FALSE);
8610 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8612 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8613 GtkWidget *menuitem = NULL;
8614 GList *children, *amenu;
8615 gboolean can_sign = FALSE, can_encrypt = FALSE;
8616 gboolean found = FALSE;
8618 if (compose->privacy_system != NULL) {
8619 gchar *systemid;
8620 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8621 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8622 cm_return_if_fail(menuitem != NULL);
8624 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8625 amenu = children;
8626 menuitem = NULL;
8627 while (amenu != NULL) {
8628 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8629 if (systemid != NULL) {
8630 if (strcmp(systemid, compose->privacy_system) == 0 &&
8631 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8632 menuitem = GTK_WIDGET(amenu->data);
8634 can_sign = privacy_system_can_sign(systemid);
8635 can_encrypt = privacy_system_can_encrypt(systemid);
8636 found = TRUE;
8637 break;
8639 } else if (strlen(compose->privacy_system) == 0 &&
8640 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8641 menuitem = GTK_WIDGET(amenu->data);
8643 can_sign = FALSE;
8644 can_encrypt = FALSE;
8645 found = TRUE;
8646 break;
8649 amenu = amenu->next;
8651 g_list_free(children);
8652 if (menuitem != NULL)
8653 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8655 if (warn && !found && strlen(compose->privacy_system)) {
8656 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8657 "will not be able to sign or encrypt this message."),
8658 compose->privacy_system);
8662 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8663 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8664 if (compose->toolbar->privacy_sign_btn != NULL) {
8665 gtk_widget_set_sensitive(
8666 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8667 can_sign);
8669 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8670 gtk_widget_set_sensitive(
8671 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8672 can_encrypt);
8676 static void compose_set_out_encoding(Compose *compose)
8678 CharSet out_encoding;
8679 const gchar *branch = NULL;
8680 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8682 switch(out_encoding) {
8683 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8684 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8685 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8686 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8687 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8688 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8689 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8690 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8691 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8692 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8693 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8694 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8695 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8696 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8697 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8698 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8699 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8700 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8701 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8702 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8703 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8704 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8705 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8706 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8707 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8708 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8709 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8710 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8711 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8712 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8713 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8714 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8715 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8716 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8718 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8721 static void compose_set_template_menu(Compose *compose)
8723 GSList *tmpl_list, *cur;
8724 GtkWidget *menu;
8725 GtkWidget *item;
8727 tmpl_list = template_get_config();
8729 menu = gtk_menu_new();
8731 gtk_menu_set_accel_group (GTK_MENU (menu),
8732 gtk_ui_manager_get_accel_group(compose->ui_manager));
8733 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8734 Template *tmpl = (Template *)cur->data;
8735 gchar *accel_path = NULL;
8736 item = gtk_menu_item_new_with_label(tmpl->name);
8737 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8738 g_signal_connect(G_OBJECT(item), "activate",
8739 G_CALLBACK(compose_template_activate_cb),
8740 compose);
8741 g_object_set_data(G_OBJECT(item), "template", tmpl);
8742 gtk_widget_show(item);
8743 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8744 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8745 g_free(accel_path);
8748 gtk_widget_show(menu);
8749 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8752 void compose_update_actions_menu(Compose *compose)
8754 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8757 static void compose_update_privacy_systems_menu(Compose *compose)
8759 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8760 GSList *systems, *cur;
8761 GtkWidget *widget;
8762 GtkWidget *system_none;
8763 GSList *group;
8764 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8765 GtkWidget *privacy_menu = gtk_menu_new();
8767 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8768 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8770 g_signal_connect(G_OBJECT(system_none), "activate",
8771 G_CALLBACK(compose_set_privacy_system_cb), compose);
8773 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8774 gtk_widget_show(system_none);
8776 systems = privacy_get_system_ids();
8777 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8778 gchar *systemid = cur->data;
8780 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8781 widget = gtk_radio_menu_item_new_with_label(group,
8782 privacy_system_get_name(systemid));
8783 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8784 g_strdup(systemid), g_free);
8785 g_signal_connect(G_OBJECT(widget), "activate",
8786 G_CALLBACK(compose_set_privacy_system_cb), compose);
8788 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8789 gtk_widget_show(widget);
8790 g_free(systemid);
8792 g_slist_free(systems);
8793 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8794 gtk_widget_show_all(privacy_menu);
8795 gtk_widget_show_all(privacy_menuitem);
8798 void compose_reflect_prefs_all(void)
8800 GList *cur;
8801 Compose *compose;
8803 for (cur = compose_list; cur != NULL; cur = cur->next) {
8804 compose = (Compose *)cur->data;
8805 compose_set_template_menu(compose);
8809 void compose_reflect_prefs_pixmap_theme(void)
8811 GList *cur;
8812 Compose *compose;
8814 for (cur = compose_list; cur != NULL; cur = cur->next) {
8815 compose = (Compose *)cur->data;
8816 toolbar_update(TOOLBAR_COMPOSE, compose);
8820 static const gchar *compose_quote_char_from_context(Compose *compose)
8822 const gchar *qmark = NULL;
8824 cm_return_val_if_fail(compose != NULL, NULL);
8826 switch (compose->mode) {
8827 /* use forward-specific quote char */
8828 case COMPOSE_FORWARD:
8829 case COMPOSE_FORWARD_AS_ATTACH:
8830 case COMPOSE_FORWARD_INLINE:
8831 if (compose->folder && compose->folder->prefs &&
8832 compose->folder->prefs->forward_with_format)
8833 qmark = compose->folder->prefs->forward_quotemark;
8834 else if (compose->account->forward_with_format)
8835 qmark = compose->account->forward_quotemark;
8836 else
8837 qmark = prefs_common.fw_quotemark;
8838 break;
8840 /* use reply-specific quote char in all other modes */
8841 default:
8842 if (compose->folder && compose->folder->prefs &&
8843 compose->folder->prefs->reply_with_format)
8844 qmark = compose->folder->prefs->reply_quotemark;
8845 else if (compose->account->reply_with_format)
8846 qmark = compose->account->reply_quotemark;
8847 else
8848 qmark = prefs_common.quotemark;
8849 break;
8852 if (qmark == NULL || *qmark == '\0')
8853 qmark = "> ";
8855 return qmark;
8858 static void compose_template_apply(Compose *compose, Template *tmpl,
8859 gboolean replace)
8861 GtkTextView *text;
8862 GtkTextBuffer *buffer;
8863 GtkTextMark *mark;
8864 GtkTextIter iter;
8865 const gchar *qmark;
8866 gchar *parsed_str = NULL;
8867 gint cursor_pos = 0;
8868 const gchar *err_msg = _("The body of the template has an error at line %d.");
8869 if (!tmpl) return;
8871 /* process the body */
8873 text = GTK_TEXT_VIEW(compose->text);
8874 buffer = gtk_text_view_get_buffer(text);
8876 if (tmpl->value) {
8877 qmark = compose_quote_char_from_context(compose);
8879 if (compose->replyinfo != NULL) {
8881 if (replace)
8882 gtk_text_buffer_set_text(buffer, "", -1);
8883 mark = gtk_text_buffer_get_insert(buffer);
8884 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8886 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8887 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8889 } else if (compose->fwdinfo != NULL) {
8891 if (replace)
8892 gtk_text_buffer_set_text(buffer, "", -1);
8893 mark = gtk_text_buffer_get_insert(buffer);
8894 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8896 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8897 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8899 } else {
8900 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8902 GtkTextIter start, end;
8903 gchar *tmp = NULL;
8905 gtk_text_buffer_get_start_iter(buffer, &start);
8906 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8907 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8909 /* clear the buffer now */
8910 if (replace)
8911 gtk_text_buffer_set_text(buffer, "", -1);
8913 parsed_str = compose_quote_fmt(compose, dummyinfo,
8914 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8915 procmsg_msginfo_free( &dummyinfo );
8917 g_free( tmp );
8919 } else {
8920 if (replace)
8921 gtk_text_buffer_set_text(buffer, "", -1);
8922 mark = gtk_text_buffer_get_insert(buffer);
8923 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8926 if (replace && parsed_str && compose->account->auto_sig)
8927 compose_insert_sig(compose, FALSE);
8929 if (replace && parsed_str) {
8930 gtk_text_buffer_get_start_iter(buffer, &iter);
8931 gtk_text_buffer_place_cursor(buffer, &iter);
8934 if (parsed_str) {
8935 cursor_pos = quote_fmt_get_cursor_pos();
8936 compose->set_cursor_pos = cursor_pos;
8937 if (cursor_pos == -1)
8938 cursor_pos = 0;
8939 gtk_text_buffer_get_start_iter(buffer, &iter);
8940 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8941 gtk_text_buffer_place_cursor(buffer, &iter);
8944 /* process the other fields */
8946 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8947 compose_template_apply_fields(compose, tmpl);
8948 quote_fmt_reset_vartable();
8949 quote_fmtlex_destroy();
8951 compose_changed_cb(NULL, compose);
8953 #ifdef USE_ENCHANT
8954 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8955 gtkaspell_highlight_all(compose->gtkaspell);
8956 #endif
8959 static void compose_template_apply_fields_error(const gchar *header)
8961 gchar *tr;
8962 gchar *text;
8964 tr = g_strdup(C_("'%s' stands for a header name",
8965 "Template '%s' format error."));
8966 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8967 alertpanel_error("%s", text);
8969 g_free(text);
8970 g_free(tr);
8973 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8975 MsgInfo* dummyinfo = NULL;
8976 MsgInfo *msginfo = NULL;
8977 gchar *buf = NULL;
8979 if (compose->replyinfo != NULL)
8980 msginfo = compose->replyinfo;
8981 else if (compose->fwdinfo != NULL)
8982 msginfo = compose->fwdinfo;
8983 else {
8984 dummyinfo = compose_msginfo_new_from_compose(compose);
8985 msginfo = dummyinfo;
8988 if (tmpl->from && *tmpl->from != '\0') {
8989 #ifdef USE_ENCHANT
8990 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8991 compose->gtkaspell);
8992 #else
8993 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8994 #endif
8995 quote_fmt_scan_string(tmpl->from);
8996 quote_fmt_parse();
8998 buf = quote_fmt_get_buffer();
8999 if (buf == NULL) {
9000 compose_template_apply_fields_error("From");
9001 } else {
9002 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
9005 quote_fmt_reset_vartable();
9006 quote_fmtlex_destroy();
9009 if (tmpl->to && *tmpl->to != '\0') {
9010 #ifdef USE_ENCHANT
9011 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9012 compose->gtkaspell);
9013 #else
9014 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9015 #endif
9016 quote_fmt_scan_string(tmpl->to);
9017 quote_fmt_parse();
9019 buf = quote_fmt_get_buffer();
9020 if (buf == NULL) {
9021 compose_template_apply_fields_error("To");
9022 } else {
9023 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
9026 quote_fmt_reset_vartable();
9027 quote_fmtlex_destroy();
9030 if (tmpl->cc && *tmpl->cc != '\0') {
9031 #ifdef USE_ENCHANT
9032 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9033 compose->gtkaspell);
9034 #else
9035 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9036 #endif
9037 quote_fmt_scan_string(tmpl->cc);
9038 quote_fmt_parse();
9040 buf = quote_fmt_get_buffer();
9041 if (buf == NULL) {
9042 compose_template_apply_fields_error("Cc");
9043 } else {
9044 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9047 quote_fmt_reset_vartable();
9048 quote_fmtlex_destroy();
9051 if (tmpl->bcc && *tmpl->bcc != '\0') {
9052 #ifdef USE_ENCHANT
9053 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9054 compose->gtkaspell);
9055 #else
9056 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9057 #endif
9058 quote_fmt_scan_string(tmpl->bcc);
9059 quote_fmt_parse();
9061 buf = quote_fmt_get_buffer();
9062 if (buf == NULL) {
9063 compose_template_apply_fields_error("Bcc");
9064 } else {
9065 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9068 quote_fmt_reset_vartable();
9069 quote_fmtlex_destroy();
9072 if (tmpl->replyto && *tmpl->replyto != '\0') {
9073 #ifdef USE_ENCHANT
9074 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9075 compose->gtkaspell);
9076 #else
9077 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9078 #endif
9079 quote_fmt_scan_string(tmpl->replyto);
9080 quote_fmt_parse();
9082 buf = quote_fmt_get_buffer();
9083 if (buf == NULL) {
9084 compose_template_apply_fields_error("Reply-To");
9085 } else {
9086 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9089 quote_fmt_reset_vartable();
9090 quote_fmtlex_destroy();
9093 /* process the subject */
9094 if (tmpl->subject && *tmpl->subject != '\0') {
9095 #ifdef USE_ENCHANT
9096 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9097 compose->gtkaspell);
9098 #else
9099 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9100 #endif
9101 quote_fmt_scan_string(tmpl->subject);
9102 quote_fmt_parse();
9104 buf = quote_fmt_get_buffer();
9105 if (buf == NULL) {
9106 compose_template_apply_fields_error("Subject");
9107 } else {
9108 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9111 quote_fmt_reset_vartable();
9112 quote_fmtlex_destroy();
9115 procmsg_msginfo_free( &dummyinfo );
9118 static void compose_destroy(Compose *compose)
9120 GtkAllocation allocation;
9121 GtkTextBuffer *buffer;
9122 GtkClipboard *clipboard;
9124 compose_list = g_list_remove(compose_list, compose);
9126 #ifdef USE_LDAP
9127 gboolean enable = TRUE;
9128 g_slist_foreach(compose->passworded_ldap_servers,
9129 _ldap_srv_func, &enable);
9130 g_slist_free(compose->passworded_ldap_servers);
9131 #endif
9133 if (compose->updating) {
9134 debug_print("danger, not destroying anything now\n");
9135 compose->deferred_destroy = TRUE;
9136 return;
9139 /* NOTE: address_completion_end() does nothing with the window
9140 * however this may change. */
9141 address_completion_end(compose->window);
9143 slist_free_strings_full(compose->to_list);
9144 slist_free_strings_full(compose->newsgroup_list);
9145 slist_free_strings_full(compose->header_list);
9147 slist_free_strings_full(extra_headers);
9148 extra_headers = NULL;
9150 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9152 g_hash_table_destroy(compose->email_hashtable);
9154 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9155 compose->folder_update_callback_id);
9157 procmsg_msginfo_free(&(compose->targetinfo));
9158 procmsg_msginfo_free(&(compose->replyinfo));
9159 procmsg_msginfo_free(&(compose->fwdinfo));
9161 g_free(compose->replyto);
9162 g_free(compose->cc);
9163 g_free(compose->bcc);
9164 g_free(compose->newsgroups);
9165 g_free(compose->followup_to);
9167 g_free(compose->ml_post);
9169 g_free(compose->inreplyto);
9170 g_free(compose->references);
9171 g_free(compose->msgid);
9172 g_free(compose->boundary);
9174 g_free(compose->redirect_filename);
9175 if (compose->undostruct)
9176 undo_destroy(compose->undostruct);
9178 g_free(compose->sig_str);
9180 g_free(compose->exteditor_file);
9182 g_free(compose->orig_charset);
9184 g_free(compose->privacy_system);
9185 g_free(compose->encdata);
9187 #ifndef USE_ALT_ADDRBOOK
9188 if (addressbook_get_target_compose() == compose)
9189 addressbook_set_target_compose(NULL);
9190 #endif
9191 #if USE_ENCHANT
9192 if (compose->gtkaspell) {
9193 gtkaspell_delete(compose->gtkaspell);
9194 compose->gtkaspell = NULL;
9196 #endif
9198 if (!compose->batch) {
9199 gtk_widget_get_allocation(compose->window, &allocation);
9200 prefs_common.compose_width = allocation.width;
9201 prefs_common.compose_height = allocation.height;
9204 if (!gtk_widget_get_parent(compose->paned))
9205 gtk_widget_destroy(compose->paned);
9206 gtk_widget_destroy(compose->popupmenu);
9208 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9209 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9210 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9212 message_search_close(compose);
9213 gtk_widget_destroy(compose->window);
9214 toolbar_destroy(compose->toolbar);
9215 g_free(compose->toolbar);
9216 cm_mutex_free(compose->mutex);
9217 g_free(compose);
9220 static void compose_attach_info_free(AttachInfo *ainfo)
9222 g_free(ainfo->file);
9223 g_free(ainfo->content_type);
9224 g_free(ainfo->name);
9225 g_free(ainfo->charset);
9226 g_free(ainfo);
9229 static void compose_attach_update_label(Compose *compose)
9231 GtkTreeIter iter;
9232 gint i = 1;
9233 gchar *text;
9234 GtkTreeModel *model;
9235 goffset total_size;
9236 AttachInfo *ainfo;
9238 if (compose == NULL)
9239 return;
9241 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9242 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9243 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9244 return;
9247 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9248 total_size = ainfo->size;
9249 while(gtk_tree_model_iter_next(model, &iter)) {
9250 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9251 total_size += ainfo->size;
9252 i++;
9254 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9255 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9256 g_free(text);
9259 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9261 Compose *compose = (Compose *)data;
9262 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9263 GtkTreeSelection *selection;
9264 GList *sel, *cur;
9265 GtkTreeModel *model;
9267 selection = gtk_tree_view_get_selection(tree_view);
9268 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9269 cm_return_if_fail(sel);
9271 for (cur = sel; cur != NULL; cur = cur->next) {
9272 GtkTreePath *path = cur->data;
9273 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9274 (model, cur->data);
9275 cur->data = ref;
9276 gtk_tree_path_free(path);
9279 for (cur = sel; cur != NULL; cur = cur->next) {
9280 GtkTreeRowReference *ref = cur->data;
9281 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9282 GtkTreeIter iter;
9284 if (gtk_tree_model_get_iter(model, &iter, path))
9285 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9287 gtk_tree_path_free(path);
9288 gtk_tree_row_reference_free(ref);
9291 g_list_free(sel);
9292 compose_attach_update_label(compose);
9295 static struct _AttachProperty
9297 GtkWidget *window;
9298 GtkWidget *mimetype_entry;
9299 GtkWidget *encoding_optmenu;
9300 GtkWidget *path_entry;
9301 GtkWidget *filename_entry;
9302 GtkWidget *ok_btn;
9303 GtkWidget *cancel_btn;
9304 } attach_prop;
9306 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9308 gtk_tree_path_free((GtkTreePath *)ptr);
9311 static void compose_attach_property(GtkAction *action, gpointer data)
9313 Compose *compose = (Compose *)data;
9314 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9315 AttachInfo *ainfo;
9316 GtkComboBox *optmenu;
9317 GtkTreeSelection *selection;
9318 GList *sel;
9319 GtkTreeModel *model;
9320 GtkTreeIter iter;
9321 GtkTreePath *path;
9322 static gboolean cancelled;
9324 /* only if one selected */
9325 selection = gtk_tree_view_get_selection(tree_view);
9326 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9327 return;
9329 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9330 cm_return_if_fail(sel);
9332 path = (GtkTreePath *) sel->data;
9333 gtk_tree_model_get_iter(model, &iter, path);
9334 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9336 if (!ainfo) {
9337 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9338 g_list_free(sel);
9339 return;
9341 g_list_free(sel);
9343 if (!attach_prop.window)
9344 compose_attach_property_create(&cancelled);
9345 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9346 gtk_widget_grab_focus(attach_prop.ok_btn);
9347 gtk_widget_show(attach_prop.window);
9348 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9349 GTK_WINDOW(compose->window));
9351 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9352 if (ainfo->encoding == ENC_UNKNOWN)
9353 combobox_select_by_data(optmenu, ENC_BASE64);
9354 else
9355 combobox_select_by_data(optmenu, ainfo->encoding);
9357 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9358 ainfo->content_type ? ainfo->content_type : "");
9359 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9360 ainfo->file ? ainfo->file : "");
9361 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9362 ainfo->name ? ainfo->name : "");
9364 for (;;) {
9365 const gchar *entry_text;
9366 gchar *text;
9367 gchar *cnttype = NULL;
9368 gchar *file = NULL;
9369 off_t size = 0;
9371 cancelled = FALSE;
9372 gtk_main();
9374 gtk_widget_hide(attach_prop.window);
9375 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9377 if (cancelled)
9378 break;
9380 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9381 if (*entry_text != '\0') {
9382 gchar *p;
9384 text = g_strstrip(g_strdup(entry_text));
9385 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9386 cnttype = g_strdup(text);
9387 g_free(text);
9388 } else {
9389 alertpanel_error(_("Invalid MIME type."));
9390 g_free(text);
9391 continue;
9395 ainfo->encoding = combobox_get_active_data(optmenu);
9397 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9398 if (*entry_text != '\0') {
9399 if (is_file_exist(entry_text) &&
9400 (size = get_file_size(entry_text)) > 0)
9401 file = g_strdup(entry_text);
9402 else {
9403 alertpanel_error
9404 (_("File doesn't exist or is empty."));
9405 g_free(cnttype);
9406 continue;
9410 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9411 if (*entry_text != '\0') {
9412 g_free(ainfo->name);
9413 ainfo->name = g_strdup(entry_text);
9416 if (cnttype) {
9417 g_free(ainfo->content_type);
9418 ainfo->content_type = cnttype;
9420 if (file) {
9421 g_free(ainfo->file);
9422 ainfo->file = file;
9424 if (size)
9425 ainfo->size = (goffset)size;
9427 /* update tree store */
9428 text = to_human_readable(ainfo->size);
9429 gtk_tree_model_get_iter(model, &iter, path);
9430 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9431 COL_MIMETYPE, ainfo->content_type,
9432 COL_SIZE, text,
9433 COL_NAME, ainfo->name,
9434 COL_CHARSET, ainfo->charset,
9435 -1);
9437 break;
9440 gtk_tree_path_free(path);
9443 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9445 label = gtk_label_new(str); \
9446 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9447 GTK_FILL, 0, 0, 0); \
9448 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9450 entry = gtk_entry_new(); \
9451 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9452 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9455 static void compose_attach_property_create(gboolean *cancelled)
9457 GtkWidget *window;
9458 GtkWidget *vbox;
9459 GtkWidget *table;
9460 GtkWidget *label;
9461 GtkWidget *mimetype_entry;
9462 GtkWidget *hbox;
9463 GtkWidget *optmenu;
9464 GtkListStore *optmenu_menu;
9465 GtkWidget *path_entry;
9466 GtkWidget *filename_entry;
9467 GtkWidget *hbbox;
9468 GtkWidget *ok_btn;
9469 GtkWidget *cancel_btn;
9470 GList *mime_type_list, *strlist;
9471 GtkTreeIter iter;
9473 debug_print("Creating attach_property window...\n");
9475 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9476 gtk_widget_set_size_request(window, 480, -1);
9477 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9478 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9479 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9480 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
9481 g_signal_connect(G_OBJECT(window), "delete_event",
9482 G_CALLBACK(attach_property_delete_event),
9483 cancelled);
9484 g_signal_connect(G_OBJECT(window), "key_press_event",
9485 G_CALLBACK(attach_property_key_pressed),
9486 cancelled);
9488 vbox = gtk_vbox_new(FALSE, 8);
9489 gtk_container_add(GTK_CONTAINER(window), vbox);
9491 table = gtk_table_new(4, 2, FALSE);
9492 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9493 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9494 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9496 label = gtk_label_new(_("MIME type"));
9497 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9498 GTK_FILL, 0, 0, 0);
9499 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9500 mimetype_entry = gtk_combo_box_text_new_with_entry();
9501 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9502 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9504 /* stuff with list */
9505 mime_type_list = procmime_get_mime_type_list();
9506 strlist = NULL;
9507 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9508 MimeType *type = (MimeType *) mime_type_list->data;
9509 gchar *tmp;
9511 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9513 if (g_list_find_custom(strlist, tmp, (GCompareFunc)g_strcmp0))
9514 g_free(tmp);
9515 else
9516 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9517 (GCompareFunc)g_strcmp0);
9520 for (mime_type_list = strlist; mime_type_list != NULL;
9521 mime_type_list = mime_type_list->next) {
9522 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9523 g_free(mime_type_list->data);
9525 g_list_free(strlist);
9526 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9527 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9529 label = gtk_label_new(_("Encoding"));
9530 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9531 GTK_FILL, 0, 0, 0);
9532 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9534 hbox = gtk_hbox_new(FALSE, 0);
9535 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9536 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9538 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9539 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9541 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9542 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9543 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9544 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9545 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9547 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9549 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9550 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9552 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9553 &ok_btn, GTK_STOCK_OK,
9554 NULL, NULL);
9555 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9556 gtk_widget_grab_default(ok_btn);
9558 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9559 G_CALLBACK(attach_property_ok),
9560 cancelled);
9561 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9562 G_CALLBACK(attach_property_cancel),
9563 cancelled);
9565 gtk_widget_show_all(vbox);
9567 attach_prop.window = window;
9568 attach_prop.mimetype_entry = mimetype_entry;
9569 attach_prop.encoding_optmenu = optmenu;
9570 attach_prop.path_entry = path_entry;
9571 attach_prop.filename_entry = filename_entry;
9572 attach_prop.ok_btn = ok_btn;
9573 attach_prop.cancel_btn = cancel_btn;
9576 #undef SET_LABEL_AND_ENTRY
9578 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9580 *cancelled = FALSE;
9581 gtk_main_quit();
9584 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9586 *cancelled = TRUE;
9587 gtk_main_quit();
9590 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9591 gboolean *cancelled)
9593 *cancelled = TRUE;
9594 gtk_main_quit();
9596 return TRUE;
9599 static gboolean attach_property_key_pressed(GtkWidget *widget,
9600 GdkEventKey *event,
9601 gboolean *cancelled)
9603 if (event && event->keyval == GDK_KEY_Escape) {
9604 *cancelled = TRUE;
9605 gtk_main_quit();
9607 if (event && event->keyval == GDK_KEY_Return) {
9608 *cancelled = FALSE;
9609 gtk_main_quit();
9610 return TRUE;
9612 return FALSE;
9615 static void compose_exec_ext_editor(Compose *compose)
9617 #ifdef G_OS_UNIX
9618 gchar *tmp;
9619 GtkWidget *socket;
9620 GdkNativeWindow socket_wid = 0;
9621 pid_t pid;
9622 gint pipe_fds[2];
9624 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9625 G_DIR_SEPARATOR, compose);
9627 if (compose_get_ext_editor_uses_socket()) {
9628 /* Only allow one socket */
9629 if (compose->exteditor_socket != NULL) {
9630 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9631 /* Move the focus off of the socket */
9632 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9634 g_free(tmp);
9635 return;
9637 /* Create the receiving GtkSocket */
9638 socket = gtk_socket_new ();
9639 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9640 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9641 compose);
9642 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9643 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9644 /* Realize the socket so that we can use its ID */
9645 gtk_widget_realize(socket);
9646 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9647 compose->exteditor_socket = socket;
9650 if (pipe(pipe_fds) < 0) {
9651 perror("pipe");
9652 g_free(tmp);
9653 return;
9656 if ((pid = fork()) < 0) {
9657 perror("fork");
9658 g_free(tmp);
9659 return;
9662 if (pid != 0) {
9663 /* close the write side of the pipe */
9664 close(pipe_fds[1]);
9666 compose->exteditor_file = g_strdup(tmp);
9667 compose->exteditor_pid = pid;
9669 compose_set_ext_editor_sensitive(compose, FALSE);
9671 #ifndef G_OS_WIN32
9672 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9673 #else
9674 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9675 #endif
9676 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9677 G_IO_IN,
9678 compose_input_cb,
9679 compose);
9680 } else { /* process-monitoring process */
9681 pid_t pid_ed;
9683 if (setpgid(0, 0))
9684 perror("setpgid");
9686 /* close the read side of the pipe */
9687 close(pipe_fds[0]);
9689 if (compose_write_body_to_file(compose, tmp) < 0) {
9690 fd_write_all(pipe_fds[1], "2\n", 2);
9691 _exit(1);
9694 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9695 if (pid_ed < 0) {
9696 fd_write_all(pipe_fds[1], "1\n", 2);
9697 _exit(1);
9700 /* wait until editor is terminated */
9701 waitpid(pid_ed, NULL, 0);
9703 fd_write_all(pipe_fds[1], "0\n", 2);
9705 close(pipe_fds[1]);
9706 _exit(0);
9709 g_free(tmp);
9710 #endif /* G_OS_UNIX */
9713 static gboolean compose_can_autosave(Compose *compose)
9715 if (compose->privacy_system && compose->use_encryption)
9716 return prefs_common.autosave && prefs_common.autosave_encrypted;
9717 else
9718 return prefs_common.autosave;
9721 #ifdef G_OS_UNIX
9722 static gboolean compose_get_ext_editor_cmd_valid()
9724 gboolean has_s = FALSE;
9725 gboolean has_w = FALSE;
9726 const gchar *p = prefs_common_get_ext_editor_cmd();
9727 if (!p)
9728 return FALSE;
9729 while ((p = strchr(p, '%'))) {
9730 p++;
9731 if (*p == 's') {
9732 if (has_s)
9733 return FALSE;
9734 has_s = TRUE;
9735 } else if (*p == 'w') {
9736 if (has_w)
9737 return FALSE;
9738 has_w = TRUE;
9739 } else {
9740 return FALSE;
9743 return TRUE;
9746 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9748 gchar *buf;
9749 gchar *p, *s;
9750 gchar **cmdline;
9751 pid_t pid;
9753 cm_return_val_if_fail(file != NULL, -1);
9755 if ((pid = fork()) < 0) {
9756 perror("fork");
9757 return -1;
9760 if (pid != 0) return pid;
9762 /* grandchild process */
9764 if (setpgid(0, getppid()))
9765 perror("setpgid");
9767 if (compose_get_ext_editor_cmd_valid()) {
9768 if (compose_get_ext_editor_uses_socket()) {
9769 p = g_strdup(prefs_common_get_ext_editor_cmd());
9770 s = strstr(p, "%w");
9771 s[1] = 'u';
9772 if (strstr(p, "%s") < s)
9773 buf = g_strdup_printf(p, file, socket_wid);
9774 else
9775 buf = g_strdup_printf(p, socket_wid, file);
9776 g_free(p);
9777 } else {
9778 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9780 } else {
9781 if (prefs_common_get_ext_editor_cmd())
9782 g_warning("External editor command-line is invalid: '%s'",
9783 prefs_common_get_ext_editor_cmd());
9784 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9787 cmdline = strsplit_with_quote(buf, " ", 0);
9788 g_free(buf);
9789 execvp(cmdline[0], cmdline);
9791 perror("execvp");
9792 g_strfreev(cmdline);
9794 _exit(1);
9797 static gboolean compose_ext_editor_kill(Compose *compose)
9799 pid_t pgid = compose->exteditor_pid * -1;
9800 gint ret;
9802 ret = kill(pgid, 0);
9804 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9805 AlertValue val;
9806 gchar *msg;
9808 msg = g_strdup_printf
9809 (_("The external editor is still working.\n"
9810 "Force terminating the process?\n"
9811 "process group id: %d"), -pgid);
9812 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9813 NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9814 ALERT_WARNING);
9816 g_free(msg);
9818 if (val == G_ALERTALTERNATE) {
9819 g_source_remove(compose->exteditor_tag);
9820 g_io_channel_shutdown(compose->exteditor_ch,
9821 FALSE, NULL);
9822 g_io_channel_unref(compose->exteditor_ch);
9824 if (kill(pgid, SIGTERM) < 0) perror("kill");
9825 waitpid(compose->exteditor_pid, NULL, 0);
9827 g_warning("Terminated process group id: %d. "
9828 "Temporary file: %s", -pgid, compose->exteditor_file);
9830 compose_set_ext_editor_sensitive(compose, TRUE);
9832 g_free(compose->exteditor_file);
9833 compose->exteditor_file = NULL;
9834 compose->exteditor_pid = -1;
9835 compose->exteditor_ch = NULL;
9836 compose->exteditor_tag = -1;
9837 } else
9838 return FALSE;
9841 return TRUE;
9844 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9845 gpointer data)
9847 gchar buf[3] = "3";
9848 Compose *compose = (Compose *)data;
9849 gsize bytes_read;
9851 debug_print("Compose: input from monitoring process\n");
9853 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9854 bytes_read = 0;
9855 buf[0] = '\0';
9858 g_io_channel_shutdown(source, FALSE, NULL);
9859 g_io_channel_unref(source);
9861 waitpid(compose->exteditor_pid, NULL, 0);
9863 if (buf[0] == '0') { /* success */
9864 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9865 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9866 GtkTextIter start, end;
9867 gchar *chars;
9869 gtk_text_buffer_set_text(buffer, "", -1);
9870 compose_insert_file(compose, compose->exteditor_file);
9871 compose_changed_cb(NULL, compose);
9873 /* Check if we should save the draft or not */
9874 if (compose_can_autosave(compose))
9875 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9877 if (claws_unlink(compose->exteditor_file) < 0)
9878 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9880 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9881 gtk_text_buffer_get_start_iter(buffer, &start);
9882 gtk_text_buffer_get_end_iter(buffer, &end);
9883 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9884 if (chars && strlen(chars) > 0)
9885 compose->modified = TRUE;
9886 g_free(chars);
9887 } else if (buf[0] == '1') { /* failed */
9888 g_warning("Couldn't exec external editor");
9889 if (claws_unlink(compose->exteditor_file) < 0)
9890 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9891 } else if (buf[0] == '2') {
9892 g_warning("Couldn't write to file");
9893 } else if (buf[0] == '3') {
9894 g_warning("Pipe read failed");
9897 compose_set_ext_editor_sensitive(compose, TRUE);
9899 g_free(compose->exteditor_file);
9900 compose->exteditor_file = NULL;
9901 compose->exteditor_pid = -1;
9902 compose->exteditor_ch = NULL;
9903 compose->exteditor_tag = -1;
9904 if (compose->exteditor_socket) {
9905 gtk_widget_destroy(compose->exteditor_socket);
9906 compose->exteditor_socket = NULL;
9910 return FALSE;
9913 static char *ext_editor_menu_entries[] = {
9914 "Menu/Message/Send",
9915 "Menu/Message/SendLater",
9916 "Menu/Message/InsertFile",
9917 "Menu/Message/InsertSig",
9918 "Menu/Message/ReplaceSig",
9919 "Menu/Message/Save",
9920 "Menu/Message/Print",
9921 "Menu/Edit",
9922 #if USE_ENCHANT
9923 "Menu/Spelling",
9924 #endif
9925 "Menu/Tools/ShowRuler",
9926 "Menu/Tools/Actions",
9927 "Menu/Help",
9928 NULL
9931 static void compose_set_ext_editor_sensitive(Compose *compose,
9932 gboolean sensitive)
9934 int i;
9936 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9937 cm_menu_set_sensitive_full(compose->ui_manager,
9938 ext_editor_menu_entries[i], sensitive);
9941 if (compose_get_ext_editor_uses_socket()) {
9942 if (sensitive) {
9943 if (compose->exteditor_socket)
9944 gtk_widget_hide(compose->exteditor_socket);
9945 gtk_widget_show(compose->scrolledwin);
9946 if (prefs_common.show_ruler)
9947 gtk_widget_show(compose->ruler_hbox);
9948 /* Fix the focus, as it doesn't go anywhere when the
9949 * socket is hidden or destroyed */
9950 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9951 } else {
9952 g_assert (compose->exteditor_socket != NULL);
9953 /* Fix the focus, as it doesn't go anywhere when the
9954 * edit box is hidden */
9955 if (gtk_widget_is_focus(compose->text))
9956 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9957 gtk_widget_hide(compose->scrolledwin);
9958 gtk_widget_hide(compose->ruler_hbox);
9959 gtk_widget_show(compose->exteditor_socket);
9961 } else {
9962 gtk_widget_set_sensitive(compose->text, sensitive);
9964 if (compose->toolbar->send_btn)
9965 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9966 if (compose->toolbar->sendl_btn)
9967 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9968 if (compose->toolbar->draft_btn)
9969 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9970 if (compose->toolbar->insert_btn)
9971 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9972 if (compose->toolbar->sig_btn)
9973 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9974 if (compose->toolbar->exteditor_btn)
9975 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9976 if (compose->toolbar->linewrap_current_btn)
9977 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9978 if (compose->toolbar->linewrap_all_btn)
9979 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9982 static gboolean compose_get_ext_editor_uses_socket()
9984 return (prefs_common_get_ext_editor_cmd() &&
9985 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9988 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9990 compose->exteditor_socket = NULL;
9991 /* returning FALSE allows destruction of the socket */
9992 return FALSE;
9994 #endif /* G_OS_UNIX */
9997 * compose_undo_state_changed:
9999 * Change the sensivity of the menuentries undo and redo
10001 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
10002 gint redo_state, gpointer data)
10004 Compose *compose = (Compose *)data;
10006 switch (undo_state) {
10007 case UNDO_STATE_TRUE:
10008 if (!undostruct->undo_state) {
10009 undostruct->undo_state = TRUE;
10010 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
10012 break;
10013 case UNDO_STATE_FALSE:
10014 if (undostruct->undo_state) {
10015 undostruct->undo_state = FALSE;
10016 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
10018 break;
10019 case UNDO_STATE_UNCHANGED:
10020 break;
10021 case UNDO_STATE_REFRESH:
10022 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
10023 break;
10024 default:
10025 g_warning("Undo state not recognized");
10026 break;
10029 switch (redo_state) {
10030 case UNDO_STATE_TRUE:
10031 if (!undostruct->redo_state) {
10032 undostruct->redo_state = TRUE;
10033 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
10035 break;
10036 case UNDO_STATE_FALSE:
10037 if (undostruct->redo_state) {
10038 undostruct->redo_state = FALSE;
10039 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
10041 break;
10042 case UNDO_STATE_UNCHANGED:
10043 break;
10044 case UNDO_STATE_REFRESH:
10045 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
10046 break;
10047 default:
10048 g_warning("Redo state not recognized");
10049 break;
10053 /* callback functions */
10055 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10056 GtkAllocation *allocation,
10057 GtkPaned *paned)
10059 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10062 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10063 * includes "non-client" (windows-izm) in calculation, so this calculation
10064 * may not be accurate.
10066 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10067 GtkAllocation *allocation,
10068 GtkSHRuler *shruler)
10070 if (prefs_common.show_ruler) {
10071 gint char_width = 0, char_height = 0;
10072 gint line_width_in_chars;
10074 gtkut_get_font_size(GTK_WIDGET(widget),
10075 &char_width, &char_height);
10076 line_width_in_chars =
10077 (allocation->width - allocation->x) / char_width;
10079 /* got the maximum */
10080 gtk_shruler_set_range(GTK_SHRULER(shruler),
10081 0.0, line_width_in_chars, 0);
10084 return TRUE;
10087 typedef struct {
10088 gchar *header;
10089 gchar *entry;
10090 ComposePrefType type;
10091 gboolean entry_marked;
10092 } HeaderEntryState;
10094 static void account_activated(GtkComboBox *optmenu, gpointer data)
10096 Compose *compose = (Compose *)data;
10098 PrefsAccount *ac;
10099 gchar *folderidentifier;
10100 gint account_id = 0;
10101 GtkTreeModel *menu;
10102 GtkTreeIter iter;
10103 GSList *list, *saved_list = NULL;
10104 HeaderEntryState *state;
10106 /* Get ID of active account in the combo box */
10107 menu = gtk_combo_box_get_model(optmenu);
10108 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10109 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10111 ac = account_find_from_id(account_id);
10112 cm_return_if_fail(ac != NULL);
10114 if (ac != compose->account) {
10115 compose_select_account(compose, ac, FALSE);
10117 for (list = compose->header_list; list; list = list->next) {
10118 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10120 if (hentry->type == PREF_ACCOUNT || !list->next) {
10121 compose_destroy_headerentry(compose, hentry);
10122 continue;
10124 state = g_malloc0(sizeof(HeaderEntryState));
10125 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10126 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10127 state->entry = gtk_editable_get_chars(
10128 GTK_EDITABLE(hentry->entry), 0, -1);
10129 state->type = hentry->type;
10131 saved_list = g_slist_append(saved_list, state);
10132 compose_destroy_headerentry(compose, hentry);
10135 compose->header_last = NULL;
10136 g_slist_free(compose->header_list);
10137 compose->header_list = NULL;
10138 compose->header_nextrow = 1;
10139 compose_create_header_entry(compose);
10141 if (ac->set_autocc && ac->auto_cc)
10142 compose_entry_append(compose, ac->auto_cc,
10143 COMPOSE_CC, PREF_ACCOUNT);
10144 if (ac->set_autobcc && ac->auto_bcc)
10145 compose_entry_append(compose, ac->auto_bcc,
10146 COMPOSE_BCC, PREF_ACCOUNT);
10147 if (ac->set_autoreplyto && ac->auto_replyto)
10148 compose_entry_append(compose, ac->auto_replyto,
10149 COMPOSE_REPLYTO, PREF_ACCOUNT);
10151 for (list = saved_list; list; list = list->next) {
10152 state = (HeaderEntryState *) list->data;
10154 compose_add_header_entry(compose, state->header,
10155 state->entry, state->type);
10157 g_free(state->header);
10158 g_free(state->entry);
10159 g_free(state);
10161 g_slist_free(saved_list);
10163 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10164 (ac->protocol == A_NNTP) ?
10165 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10168 /* Set message save folder */
10169 compose_set_save_to(compose, NULL);
10170 if (compose->folder && compose->folder->prefs && compose->folder->prefs->save_copy_to_folder) {
10171 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10172 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10173 folderidentifier = folder_item_get_identifier(compose->folder);
10174 compose_set_save_to(compose, folderidentifier);
10175 g_free(folderidentifier);
10176 } else if (account_get_special_folder(compose->account, F_OUTBOX)) {
10177 if (compose->account->set_sent_folder || prefs_common.savemsg)
10178 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10179 else
10180 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE);
10181 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10182 folderidentifier = folder_item_get_identifier(account_get_special_folder
10183 (compose->account, F_OUTBOX));
10184 compose_set_save_to(compose, folderidentifier);
10185 g_free(folderidentifier);
10189 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10190 GtkTreeViewColumn *column, Compose *compose)
10192 compose_attach_property(NULL, compose);
10195 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10196 gpointer data)
10198 Compose *compose = (Compose *)data;
10199 GtkTreeSelection *attach_selection;
10200 gint attach_nr_selected;
10201 GtkTreePath *path;
10203 if (!event) return FALSE;
10205 if (event->button == 3) {
10206 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10207 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10209 /* If no rows, or just one row is selected, right-click should
10210 * open menu relevant to the row being right-clicked on. We
10211 * achieve that by selecting the clicked row first. If more
10212 * than one row is selected, we shouldn't modify the selection,
10213 * as user may want to remove selected rows (attachments). */
10214 if (attach_nr_selected < 2) {
10215 gtk_tree_selection_unselect_all(attach_selection);
10216 attach_nr_selected = 0;
10217 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10218 event->x, event->y, &path, NULL, NULL, NULL);
10219 if (path != NULL) {
10220 gtk_tree_selection_select_path(attach_selection, path);
10221 gtk_tree_path_free(path);
10222 attach_nr_selected++;
10226 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10227 /* Properties menu item makes no sense with more than one row
10228 * selected, the properties dialog can only edit one attachment. */
10229 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10231 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10232 NULL, NULL, event->button, event->time);
10233 return TRUE;
10236 return FALSE;
10239 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10240 gpointer data)
10242 Compose *compose = (Compose *)data;
10244 if (!event) return FALSE;
10246 switch (event->keyval) {
10247 case GDK_KEY_Delete:
10248 compose_attach_remove_selected(NULL, compose);
10249 break;
10251 return FALSE;
10254 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10256 toolbar_comp_set_sensitive(compose, allow);
10257 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10258 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10259 #if USE_ENCHANT
10260 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10261 #endif
10262 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10263 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10264 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10266 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10270 static void compose_send_cb(GtkAction *action, gpointer data)
10272 Compose *compose = (Compose *)data;
10274 #ifdef G_OS_UNIX
10275 if (compose->exteditor_tag != -1) {
10276 debug_print("ignoring send: external editor still open\n");
10277 return;
10279 #endif
10280 if (prefs_common.work_offline &&
10281 !inc_offline_should_override(TRUE,
10282 _("Claws Mail needs network access in order "
10283 "to send this email.")))
10284 return;
10286 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10287 g_source_remove(compose->draft_timeout_tag);
10288 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10291 compose_send(compose);
10294 static void compose_send_later_cb(GtkAction *action, gpointer data)
10296 Compose *compose = (Compose *)data;
10297 ComposeQueueResult val;
10299 inc_lock();
10300 compose_allow_user_actions(compose, FALSE);
10301 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10302 compose_allow_user_actions(compose, TRUE);
10303 inc_unlock();
10305 if (val == COMPOSE_QUEUE_SUCCESS) {
10306 compose_close(compose);
10307 } else {
10308 _display_queue_error(val);
10311 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10314 #define DRAFTED_AT_EXIT "drafted_at_exit"
10315 static void compose_register_draft(MsgInfo *info)
10317 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10318 DRAFTED_AT_EXIT, NULL);
10319 FILE *fp = claws_fopen(filepath, "ab");
10321 if (fp) {
10322 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10323 info->msgnum);
10324 claws_fclose(fp);
10327 g_free(filepath);
10330 gboolean compose_draft (gpointer data, guint action)
10332 Compose *compose = (Compose *)data;
10333 FolderItem *draft;
10334 gchar *tmp;
10335 gchar *sheaders;
10336 gint msgnum;
10337 MsgFlags flag = {0, 0};
10338 static gboolean lock = FALSE;
10339 MsgInfo *newmsginfo;
10340 FILE *fp;
10341 gboolean target_locked = FALSE;
10342 gboolean err = FALSE;
10344 if (lock) return FALSE;
10346 if (compose->sending)
10347 return TRUE;
10349 draft = account_get_special_folder(compose->account, F_DRAFT);
10350 cm_return_val_if_fail(draft != NULL, FALSE);
10352 if (!g_mutex_trylock(compose->mutex)) {
10353 /* we don't want to lock the mutex once it's available,
10354 * because as the only other part of compose.c locking
10355 * it is compose_close - which means once unlocked,
10356 * the compose struct will be freed */
10357 debug_print("couldn't lock mutex, probably sending\n");
10358 return FALSE;
10361 lock = TRUE;
10363 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10364 G_DIR_SEPARATOR, compose);
10365 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10366 FILE_OP_ERROR(tmp, "claws_fopen");
10367 goto warn_err;
10370 /* chmod for security */
10371 if (change_file_mode_rw(fp, tmp) < 0) {
10372 FILE_OP_ERROR(tmp, "chmod");
10373 g_warning("can't change file mode");
10376 /* Save draft infos */
10377 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10378 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10380 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10381 gchar *savefolderid;
10383 savefolderid = compose_get_save_to(compose);
10384 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10385 g_free(savefolderid);
10387 if (compose->return_receipt) {
10388 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10390 if (compose->privacy_system) {
10391 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10392 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10393 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10396 /* Message-ID of message replying to */
10397 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10398 gchar *folderid = NULL;
10400 if (compose->replyinfo->folder)
10401 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10402 if (folderid == NULL)
10403 folderid = g_strdup("NULL");
10405 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10406 g_free(folderid);
10408 /* Message-ID of message forwarding to */
10409 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10410 gchar *folderid = NULL;
10412 if (compose->fwdinfo->folder)
10413 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10414 if (folderid == NULL)
10415 folderid = g_strdup("NULL");
10417 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10418 g_free(folderid);
10421 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10422 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10424 sheaders = compose_get_manual_headers_info(compose);
10425 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10426 g_free(sheaders);
10428 /* end of headers */
10429 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10431 if (err) {
10432 claws_fclose(fp);
10433 goto warn_err;
10436 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10437 claws_fclose(fp);
10438 goto warn_err;
10440 if (claws_safe_fclose(fp) == EOF) {
10441 goto warn_err;
10444 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10445 if (compose->targetinfo) {
10446 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10447 if (target_locked)
10448 flag.perm_flags |= MSG_LOCKED;
10450 flag.tmp_flags = MSG_DRAFT;
10452 folder_item_scan(draft);
10453 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10454 MsgInfo *tmpinfo = NULL;
10455 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10456 if (compose->msgid) {
10457 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10459 if (tmpinfo) {
10460 msgnum = tmpinfo->msgnum;
10461 procmsg_msginfo_free(&tmpinfo);
10462 debug_print("got draft msgnum %d from scanning\n", msgnum);
10463 } else {
10464 debug_print("didn't get draft msgnum after scanning\n");
10466 } else {
10467 debug_print("got draft msgnum %d from adding\n", msgnum);
10469 if (msgnum < 0) {
10470 warn_err:
10471 claws_unlink(tmp);
10472 g_free(tmp);
10473 if (action != COMPOSE_AUTO_SAVE) {
10474 if (action != COMPOSE_DRAFT_FOR_EXIT)
10475 alertpanel_error(_("Could not save draft."));
10476 else {
10477 AlertValue val;
10478 gtkut_window_popup(compose->window);
10479 val = alertpanel_full(_("Could not save draft"),
10480 _("Could not save draft.\n"
10481 "Do you want to cancel exit or discard this email?"),
10482 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10483 FALSE, NULL, ALERT_QUESTION);
10484 if (val == G_ALERTALTERNATE) {
10485 lock = FALSE;
10486 g_mutex_unlock(compose->mutex); /* must be done before closing */
10487 compose_close(compose);
10488 return TRUE;
10489 } else {
10490 lock = FALSE;
10491 g_mutex_unlock(compose->mutex); /* must be done before closing */
10492 return FALSE;
10496 goto unlock;
10498 g_free(tmp);
10500 if (compose->mode == COMPOSE_REEDIT) {
10501 compose_remove_reedit_target(compose, TRUE);
10504 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10506 if (newmsginfo) {
10507 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10508 if (target_locked)
10509 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10510 else
10511 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10512 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10513 procmsg_msginfo_set_flags(newmsginfo, 0,
10514 MSG_HAS_ATTACHMENT);
10516 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10517 compose_register_draft(newmsginfo);
10519 procmsg_msginfo_free(&newmsginfo);
10522 folder_item_scan(draft);
10524 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10525 lock = FALSE;
10526 g_mutex_unlock(compose->mutex); /* must be done before closing */
10527 compose_close(compose);
10528 return TRUE;
10529 } else {
10530 #ifdef G_OS_WIN32
10531 GFile *f;
10532 GFileInfo *fi;
10533 GTimeVal tv;
10534 GError *error = NULL;
10535 #else
10536 GStatBuf s;
10537 #endif
10538 gchar *path;
10539 goffset size, mtime;
10541 path = folder_item_fetch_msg(draft, msgnum);
10542 if (path == NULL) {
10543 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10544 goto unlock;
10546 #ifdef G_OS_WIN32
10547 f = g_file_new_for_path(path);
10548 fi = g_file_query_info(f, "standard::size,time::modified",
10549 G_FILE_QUERY_INFO_NONE, NULL, &error);
10550 if (error != NULL) {
10551 debug_print("couldn't query file info for '%s': %s\n",
10552 path, error->message);
10553 g_error_free(error);
10554 g_free(path);
10555 g_object_unref(f);
10556 goto unlock;
10558 size = g_file_info_get_size(fi);
10559 g_file_info_get_modification_time(fi, &tv);
10560 mtime = tv.tv_sec;
10561 g_object_unref(fi);
10562 g_object_unref(f);
10563 #else
10564 if (g_stat(path, &s) < 0) {
10565 FILE_OP_ERROR(path, "stat");
10566 g_free(path);
10567 goto unlock;
10569 size = s.st_size;
10570 mtime = s.st_mtime;
10571 #endif
10572 g_free(path);
10574 procmsg_msginfo_free(&(compose->targetinfo));
10575 compose->targetinfo = procmsg_msginfo_new();
10576 compose->targetinfo->msgnum = msgnum;
10577 compose->targetinfo->size = size;
10578 compose->targetinfo->mtime = mtime;
10579 compose->targetinfo->folder = draft;
10580 if (target_locked)
10581 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10582 compose->mode = COMPOSE_REEDIT;
10584 if (action == COMPOSE_AUTO_SAVE) {
10585 compose->autosaved_draft = compose->targetinfo;
10587 compose->modified = FALSE;
10588 compose_set_title(compose);
10590 unlock:
10591 lock = FALSE;
10592 g_mutex_unlock(compose->mutex);
10593 return TRUE;
10596 void compose_clear_exit_drafts(void)
10598 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10599 DRAFTED_AT_EXIT, NULL);
10600 if (is_file_exist(filepath))
10601 claws_unlink(filepath);
10603 g_free(filepath);
10606 void compose_reopen_exit_drafts(void)
10608 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10609 DRAFTED_AT_EXIT, NULL);
10610 FILE *fp = claws_fopen(filepath, "rb");
10611 gchar buf[1024];
10613 if (fp) {
10614 while (claws_fgets(buf, sizeof(buf), fp)) {
10615 gchar **parts = g_strsplit(buf, "\t", 2);
10616 const gchar *folder = parts[0];
10617 int msgnum = parts[1] ? atoi(parts[1]):-1;
10619 if (folder && *folder && msgnum > -1) {
10620 FolderItem *item = folder_find_item_from_identifier(folder);
10621 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10622 if (info)
10623 compose_reedit(info, FALSE);
10625 g_strfreev(parts);
10627 claws_fclose(fp);
10629 g_free(filepath);
10630 compose_clear_exit_drafts();
10633 static void compose_save_cb(GtkAction *action, gpointer data)
10635 Compose *compose = (Compose *)data;
10636 compose_draft(compose, COMPOSE_KEEP_EDITING);
10637 compose->rmode = COMPOSE_REEDIT;
10640 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10642 if (compose && file_list) {
10643 GList *tmp;
10645 for ( tmp = file_list; tmp; tmp = tmp->next) {
10646 gchar *file = (gchar *) tmp->data;
10647 gchar *utf8_filename = conv_filename_to_utf8(file);
10648 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10649 compose_changed_cb(NULL, compose);
10650 if (free_data) {
10651 g_free(file);
10652 tmp->data = NULL;
10654 g_free(utf8_filename);
10659 static void compose_attach_cb(GtkAction *action, gpointer data)
10661 Compose *compose = (Compose *)data;
10662 GList *file_list;
10664 if (compose->redirect_filename != NULL)
10665 return;
10667 /* Set focus_window properly, in case we were called via popup menu,
10668 * which unsets it (via focus_out_event callback on compose window). */
10669 manage_window_focus_in(compose->window, NULL, NULL);
10671 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10673 if (file_list) {
10674 compose_attach_from_list(compose, file_list, TRUE);
10675 g_list_free(file_list);
10679 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10681 Compose *compose = (Compose *)data;
10682 GList *file_list;
10683 gint files_inserted = 0;
10685 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10687 if (file_list) {
10688 GList *tmp;
10690 for ( tmp = file_list; tmp; tmp = tmp->next) {
10691 gchar *file = (gchar *) tmp->data;
10692 gchar *filedup = g_strdup(file);
10693 gchar *shortfile = g_path_get_basename(filedup);
10694 ComposeInsertResult res;
10695 /* insert the file if the file is short or if the user confirmed that
10696 he/she wants to insert the large file */
10697 res = compose_insert_file(compose, file);
10698 if (res == COMPOSE_INSERT_READ_ERROR) {
10699 alertpanel_error(_("File '%s' could not be read."), shortfile);
10700 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10701 alertpanel_error(_("File '%s' contained invalid characters\n"
10702 "for the current encoding, insertion may be incorrect."),
10703 shortfile);
10704 } else if (res == COMPOSE_INSERT_SUCCESS)
10705 files_inserted++;
10707 g_free(shortfile);
10708 g_free(filedup);
10709 g_free(file);
10711 g_list_free(file_list);
10714 #ifdef USE_ENCHANT
10715 if (files_inserted > 0 && compose->gtkaspell &&
10716 compose->gtkaspell->check_while_typing)
10717 gtkaspell_highlight_all(compose->gtkaspell);
10718 #endif
10721 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10723 Compose *compose = (Compose *)data;
10725 compose_insert_sig(compose, FALSE);
10728 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10730 Compose *compose = (Compose *)data;
10732 compose_insert_sig(compose, TRUE);
10735 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10736 gpointer data)
10738 gint x, y;
10739 Compose *compose = (Compose *)data;
10741 gtkut_widget_get_uposition(widget, &x, &y);
10742 if (!compose->batch) {
10743 prefs_common.compose_x = x;
10744 prefs_common.compose_y = y;
10746 if (compose->sending || compose->updating)
10747 return TRUE;
10748 compose_close_cb(NULL, compose);
10749 return TRUE;
10752 void compose_close_toolbar(Compose *compose)
10754 compose_close_cb(NULL, compose);
10757 static void compose_close_cb(GtkAction *action, gpointer data)
10759 Compose *compose = (Compose *)data;
10760 AlertValue val;
10762 #ifdef G_OS_UNIX
10763 if (compose->exteditor_tag != -1) {
10764 if (!compose_ext_editor_kill(compose))
10765 return;
10767 #endif
10769 if (compose->modified) {
10770 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10771 if (!g_mutex_trylock(compose->mutex)) {
10772 /* we don't want to lock the mutex once it's available,
10773 * because as the only other part of compose.c locking
10774 * it is compose_close - which means once unlocked,
10775 * the compose struct will be freed */
10776 debug_print("couldn't lock mutex, probably sending\n");
10777 return;
10779 if (!reedit || compose->folder->stype == F_DRAFT) {
10780 val = alertpanel(_("Discard message"),
10781 _("This message has been modified. Discard it?"),
10782 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10783 ALERTFOCUS_FIRST);
10784 } else {
10785 val = alertpanel(_("Save changes"),
10786 _("This message has been modified. Save the latest changes?"),
10787 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10788 ALERTFOCUS_SECOND);
10790 g_mutex_unlock(compose->mutex);
10791 switch (val) {
10792 case G_ALERTDEFAULT:
10793 if (compose_can_autosave(compose) && !reedit)
10794 compose_remove_draft(compose);
10795 break;
10796 case G_ALERTALTERNATE:
10797 compose_draft(data, COMPOSE_QUIT_EDITING);
10798 return;
10799 default:
10800 return;
10804 compose_close(compose);
10807 static void compose_print_cb(GtkAction *action, gpointer data)
10809 Compose *compose = (Compose *) data;
10811 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10812 if (compose->targetinfo)
10813 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10816 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10818 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10819 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10820 Compose *compose = (Compose *) data;
10822 if (active)
10823 compose->out_encoding = (CharSet)value;
10826 static void compose_address_cb(GtkAction *action, gpointer data)
10828 Compose *compose = (Compose *)data;
10830 #ifndef USE_ALT_ADDRBOOK
10831 addressbook_open(compose);
10832 #else
10833 GError* error = NULL;
10834 addressbook_connect_signals(compose);
10835 addressbook_dbus_open(TRUE, &error);
10836 if (error) {
10837 g_warning("%s", error->message);
10838 g_error_free(error);
10840 #endif
10843 static void about_show_cb(GtkAction *action, gpointer data)
10845 about_show();
10848 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10850 Compose *compose = (Compose *)data;
10851 Template *tmpl;
10852 gchar *msg;
10853 AlertValue val;
10855 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10856 cm_return_if_fail(tmpl != NULL);
10858 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10859 tmpl->name);
10860 val = alertpanel(_("Apply template"), msg,
10861 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10862 g_free(msg);
10864 if (val == G_ALERTDEFAULT)
10865 compose_template_apply(compose, tmpl, TRUE);
10866 else if (val == G_ALERTALTERNATE)
10867 compose_template_apply(compose, tmpl, FALSE);
10870 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10872 Compose *compose = (Compose *)data;
10874 #ifdef G_OS_UNIX
10875 if (compose->exteditor_tag != -1) {
10876 debug_print("ignoring open external editor: external editor still open\n");
10877 return;
10879 #endif
10880 compose_exec_ext_editor(compose);
10883 static void compose_undo_cb(GtkAction *action, gpointer data)
10885 Compose *compose = (Compose *)data;
10886 gboolean prev_autowrap = compose->autowrap;
10888 compose->autowrap = FALSE;
10889 undo_undo(compose->undostruct);
10890 compose->autowrap = prev_autowrap;
10893 static void compose_redo_cb(GtkAction *action, gpointer data)
10895 Compose *compose = (Compose *)data;
10896 gboolean prev_autowrap = compose->autowrap;
10898 compose->autowrap = FALSE;
10899 undo_redo(compose->undostruct);
10900 compose->autowrap = prev_autowrap;
10903 static void entry_cut_clipboard(GtkWidget *entry)
10905 if (GTK_IS_EDITABLE(entry))
10906 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10907 else if (GTK_IS_TEXT_VIEW(entry))
10908 gtk_text_buffer_cut_clipboard(
10909 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10910 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10911 TRUE);
10914 static void entry_copy_clipboard(GtkWidget *entry)
10916 if (GTK_IS_EDITABLE(entry))
10917 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10918 else if (GTK_IS_TEXT_VIEW(entry))
10919 gtk_text_buffer_copy_clipboard(
10920 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10921 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10924 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10925 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10927 if (GTK_IS_TEXT_VIEW(entry)) {
10928 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10929 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10930 GtkTextIter start_iter, end_iter;
10931 gint start, end;
10932 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10934 if (contents == NULL)
10935 return;
10937 /* we shouldn't delete the selection when middle-click-pasting, or we
10938 * can't mid-click-paste our own selection */
10939 if (clip != GDK_SELECTION_PRIMARY) {
10940 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10941 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10944 if (insert_place == NULL) {
10945 /* if insert_place isn't specified, insert at the cursor.
10946 * used for Ctrl-V pasting */
10947 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10948 start = gtk_text_iter_get_offset(&start_iter);
10949 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10950 } else {
10951 /* if insert_place is specified, paste here.
10952 * used for mid-click-pasting */
10953 start = gtk_text_iter_get_offset(insert_place);
10954 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10955 if (prefs_common.primary_paste_unselects)
10956 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10959 if (!wrap) {
10960 /* paste unwrapped: mark the paste so it's not wrapped later */
10961 end = start + strlen(contents);
10962 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10963 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10964 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10965 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10966 /* rewrap paragraph now (after a mid-click-paste) */
10967 mark_start = gtk_text_buffer_get_insert(buffer);
10968 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10969 gtk_text_iter_backward_char(&start_iter);
10970 compose_beautify_paragraph(compose, &start_iter, TRUE);
10972 } else if (GTK_IS_EDITABLE(entry))
10973 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10975 compose->modified = TRUE;
10978 static void entry_allsel(GtkWidget *entry)
10980 if (GTK_IS_EDITABLE(entry))
10981 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10982 else if (GTK_IS_TEXT_VIEW(entry)) {
10983 GtkTextIter startiter, enditer;
10984 GtkTextBuffer *textbuf;
10986 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10987 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10988 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10990 gtk_text_buffer_move_mark_by_name(textbuf,
10991 "selection_bound", &startiter);
10992 gtk_text_buffer_move_mark_by_name(textbuf,
10993 "insert", &enditer);
10997 static void compose_cut_cb(GtkAction *action, gpointer data)
10999 Compose *compose = (Compose *)data;
11000 if (compose->focused_editable
11001 #ifndef GENERIC_UMPC
11002 && gtk_widget_has_focus(compose->focused_editable)
11003 #endif
11005 entry_cut_clipboard(compose->focused_editable);
11008 static void compose_copy_cb(GtkAction *action, gpointer data)
11010 Compose *compose = (Compose *)data;
11011 if (compose->focused_editable
11012 #ifndef GENERIC_UMPC
11013 && gtk_widget_has_focus(compose->focused_editable)
11014 #endif
11016 entry_copy_clipboard(compose->focused_editable);
11019 static void compose_paste_cb(GtkAction *action, gpointer data)
11021 Compose *compose = (Compose *)data;
11022 gint prev_autowrap;
11023 GtkTextBuffer *buffer;
11024 BLOCK_WRAP();
11025 if (compose->focused_editable
11026 #ifndef GENERIC_UMPC
11027 && gtk_widget_has_focus(compose->focused_editable)
11028 #endif
11030 entry_paste_clipboard(compose, compose->focused_editable,
11031 prefs_common.linewrap_pastes,
11032 GDK_SELECTION_CLIPBOARD, NULL);
11033 UNBLOCK_WRAP();
11035 #ifdef USE_ENCHANT
11036 if (
11037 #ifndef GENERIC_UMPC
11038 gtk_widget_has_focus(compose->text) &&
11039 #endif
11040 compose->gtkaspell &&
11041 compose->gtkaspell->check_while_typing)
11042 gtkaspell_highlight_all(compose->gtkaspell);
11043 #endif
11046 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11048 Compose *compose = (Compose *)data;
11049 gint wrap_quote = prefs_common.linewrap_quote;
11050 if (compose->focused_editable
11051 #ifndef GENERIC_UMPC
11052 && gtk_widget_has_focus(compose->focused_editable)
11053 #endif
11055 /* let text_insert() (called directly or at a later time
11056 * after the gtk_editable_paste_clipboard) know that
11057 * text is to be inserted as a quotation. implemented
11058 * by using a simple refcount... */
11059 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11060 G_OBJECT(compose->focused_editable),
11061 "paste_as_quotation"));
11062 g_object_set_data(G_OBJECT(compose->focused_editable),
11063 "paste_as_quotation",
11064 GINT_TO_POINTER(paste_as_quotation + 1));
11065 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11066 entry_paste_clipboard(compose, compose->focused_editable,
11067 prefs_common.linewrap_pastes,
11068 GDK_SELECTION_CLIPBOARD, NULL);
11069 prefs_common.linewrap_quote = wrap_quote;
11073 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11075 Compose *compose = (Compose *)data;
11076 gint prev_autowrap;
11077 GtkTextBuffer *buffer;
11078 BLOCK_WRAP();
11079 if (compose->focused_editable
11080 #ifndef GENERIC_UMPC
11081 && gtk_widget_has_focus(compose->focused_editable)
11082 #endif
11084 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11085 GDK_SELECTION_CLIPBOARD, NULL);
11086 UNBLOCK_WRAP();
11088 #ifdef USE_ENCHANT
11089 if (
11090 #ifndef GENERIC_UMPC
11091 gtk_widget_has_focus(compose->text) &&
11092 #endif
11093 compose->gtkaspell &&
11094 compose->gtkaspell->check_while_typing)
11095 gtkaspell_highlight_all(compose->gtkaspell);
11096 #endif
11099 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11101 Compose *compose = (Compose *)data;
11102 gint prev_autowrap;
11103 GtkTextBuffer *buffer;
11104 BLOCK_WRAP();
11105 if (compose->focused_editable
11106 #ifndef GENERIC_UMPC
11107 && gtk_widget_has_focus(compose->focused_editable)
11108 #endif
11110 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11111 GDK_SELECTION_CLIPBOARD, NULL);
11112 UNBLOCK_WRAP();
11114 #ifdef USE_ENCHANT
11115 if (
11116 #ifndef GENERIC_UMPC
11117 gtk_widget_has_focus(compose->text) &&
11118 #endif
11119 compose->gtkaspell &&
11120 compose->gtkaspell->check_while_typing)
11121 gtkaspell_highlight_all(compose->gtkaspell);
11122 #endif
11125 static void compose_allsel_cb(GtkAction *action, gpointer data)
11127 Compose *compose = (Compose *)data;
11128 if (compose->focused_editable
11129 #ifndef GENERIC_UMPC
11130 && gtk_widget_has_focus(compose->focused_editable)
11131 #endif
11133 entry_allsel(compose->focused_editable);
11136 static void textview_move_beginning_of_line (GtkTextView *text)
11138 GtkTextBuffer *buffer;
11139 GtkTextMark *mark;
11140 GtkTextIter ins;
11142 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11144 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11145 mark = gtk_text_buffer_get_insert(buffer);
11146 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11147 gtk_text_iter_set_line_offset(&ins, 0);
11148 gtk_text_buffer_place_cursor(buffer, &ins);
11151 static void textview_move_forward_character (GtkTextView *text)
11153 GtkTextBuffer *buffer;
11154 GtkTextMark *mark;
11155 GtkTextIter ins;
11157 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11159 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11160 mark = gtk_text_buffer_get_insert(buffer);
11161 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11162 if (gtk_text_iter_forward_cursor_position(&ins))
11163 gtk_text_buffer_place_cursor(buffer, &ins);
11166 static void textview_move_backward_character (GtkTextView *text)
11168 GtkTextBuffer *buffer;
11169 GtkTextMark *mark;
11170 GtkTextIter ins;
11172 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11174 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11175 mark = gtk_text_buffer_get_insert(buffer);
11176 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11177 if (gtk_text_iter_backward_cursor_position(&ins))
11178 gtk_text_buffer_place_cursor(buffer, &ins);
11181 static void textview_move_forward_word (GtkTextView *text)
11183 GtkTextBuffer *buffer;
11184 GtkTextMark *mark;
11185 GtkTextIter ins;
11186 gint count;
11188 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11190 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11191 mark = gtk_text_buffer_get_insert(buffer);
11192 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11193 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11194 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11195 gtk_text_iter_backward_word_start(&ins);
11196 gtk_text_buffer_place_cursor(buffer, &ins);
11200 static void textview_move_backward_word (GtkTextView *text)
11202 GtkTextBuffer *buffer;
11203 GtkTextMark *mark;
11204 GtkTextIter ins;
11206 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11208 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11209 mark = gtk_text_buffer_get_insert(buffer);
11210 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11211 if (gtk_text_iter_backward_word_starts(&ins, 1))
11212 gtk_text_buffer_place_cursor(buffer, &ins);
11215 static void textview_move_end_of_line (GtkTextView *text)
11217 GtkTextBuffer *buffer;
11218 GtkTextMark *mark;
11219 GtkTextIter ins;
11221 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11223 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11224 mark = gtk_text_buffer_get_insert(buffer);
11225 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11226 if (gtk_text_iter_forward_to_line_end(&ins))
11227 gtk_text_buffer_place_cursor(buffer, &ins);
11230 static void textview_move_next_line (GtkTextView *text)
11232 GtkTextBuffer *buffer;
11233 GtkTextMark *mark;
11234 GtkTextIter ins;
11235 gint offset;
11237 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11239 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11240 mark = gtk_text_buffer_get_insert(buffer);
11241 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11242 offset = gtk_text_iter_get_line_offset(&ins);
11243 if (gtk_text_iter_forward_line(&ins)) {
11244 gtk_text_iter_set_line_offset(&ins, offset);
11245 gtk_text_buffer_place_cursor(buffer, &ins);
11249 static void textview_move_previous_line (GtkTextView *text)
11251 GtkTextBuffer *buffer;
11252 GtkTextMark *mark;
11253 GtkTextIter ins;
11254 gint offset;
11256 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11258 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11259 mark = gtk_text_buffer_get_insert(buffer);
11260 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11261 offset = gtk_text_iter_get_line_offset(&ins);
11262 if (gtk_text_iter_backward_line(&ins)) {
11263 gtk_text_iter_set_line_offset(&ins, offset);
11264 gtk_text_buffer_place_cursor(buffer, &ins);
11268 static void textview_delete_forward_character (GtkTextView *text)
11270 GtkTextBuffer *buffer;
11271 GtkTextMark *mark;
11272 GtkTextIter ins, end_iter;
11274 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11276 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11277 mark = gtk_text_buffer_get_insert(buffer);
11278 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11279 end_iter = ins;
11280 if (gtk_text_iter_forward_char(&end_iter)) {
11281 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11285 static void textview_delete_backward_character (GtkTextView *text)
11287 GtkTextBuffer *buffer;
11288 GtkTextMark *mark;
11289 GtkTextIter ins, end_iter;
11291 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11293 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11294 mark = gtk_text_buffer_get_insert(buffer);
11295 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11296 end_iter = ins;
11297 if (gtk_text_iter_backward_char(&end_iter)) {
11298 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11302 static void textview_delete_forward_word (GtkTextView *text)
11304 GtkTextBuffer *buffer;
11305 GtkTextMark *mark;
11306 GtkTextIter ins, end_iter;
11308 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11310 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11311 mark = gtk_text_buffer_get_insert(buffer);
11312 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11313 end_iter = ins;
11314 if (gtk_text_iter_forward_word_end(&end_iter)) {
11315 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11319 static void textview_delete_backward_word (GtkTextView *text)
11321 GtkTextBuffer *buffer;
11322 GtkTextMark *mark;
11323 GtkTextIter ins, end_iter;
11325 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11327 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11328 mark = gtk_text_buffer_get_insert(buffer);
11329 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11330 end_iter = ins;
11331 if (gtk_text_iter_backward_word_start(&end_iter)) {
11332 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11336 static void textview_delete_line (GtkTextView *text)
11338 GtkTextBuffer *buffer;
11339 GtkTextMark *mark;
11340 GtkTextIter ins, start_iter, end_iter;
11342 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11344 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11345 mark = gtk_text_buffer_get_insert(buffer);
11346 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11348 start_iter = ins;
11349 gtk_text_iter_set_line_offset(&start_iter, 0);
11351 end_iter = ins;
11352 if (gtk_text_iter_ends_line(&end_iter)){
11353 if (!gtk_text_iter_forward_char(&end_iter))
11354 gtk_text_iter_backward_char(&start_iter);
11356 else
11357 gtk_text_iter_forward_to_line_end(&end_iter);
11358 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11361 static void textview_delete_to_line_end (GtkTextView *text)
11363 GtkTextBuffer *buffer;
11364 GtkTextMark *mark;
11365 GtkTextIter ins, end_iter;
11367 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11369 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11370 mark = gtk_text_buffer_get_insert(buffer);
11371 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11372 end_iter = ins;
11373 if (gtk_text_iter_ends_line(&end_iter))
11374 gtk_text_iter_forward_char(&end_iter);
11375 else
11376 gtk_text_iter_forward_to_line_end(&end_iter);
11377 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11380 #define DO_ACTION(name, act) { \
11381 if(!strcmp(name, a_name)) { \
11382 return act; \
11385 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11387 const gchar *a_name = gtk_action_get_name(action);
11388 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11389 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11390 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11391 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11392 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11393 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11394 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11395 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11396 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11397 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11398 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11399 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11400 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11401 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11402 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11405 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11407 Compose *compose = (Compose *)data;
11408 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11409 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11411 action = compose_call_advanced_action_from_path(gaction);
11413 static struct {
11414 void (*do_action) (GtkTextView *text);
11415 } action_table[] = {
11416 {textview_move_beginning_of_line},
11417 {textview_move_forward_character},
11418 {textview_move_backward_character},
11419 {textview_move_forward_word},
11420 {textview_move_backward_word},
11421 {textview_move_end_of_line},
11422 {textview_move_next_line},
11423 {textview_move_previous_line},
11424 {textview_delete_forward_character},
11425 {textview_delete_backward_character},
11426 {textview_delete_forward_word},
11427 {textview_delete_backward_word},
11428 {textview_delete_line},
11429 {textview_delete_to_line_end}
11432 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11434 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11435 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11436 if (action_table[action].do_action)
11437 action_table[action].do_action(text);
11438 else
11439 g_warning("Not implemented yet.");
11443 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11445 GtkAllocation allocation;
11446 GtkWidget *parent;
11447 gchar *str = NULL;
11449 if (GTK_IS_EDITABLE(widget)) {
11450 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11451 gtk_editable_set_position(GTK_EDITABLE(widget),
11452 strlen(str));
11453 g_free(str);
11454 if ((parent = gtk_widget_get_parent(widget))
11455 && (parent = gtk_widget_get_parent(parent))
11456 && (parent = gtk_widget_get_parent(parent))) {
11457 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11458 gtk_widget_get_allocation(widget, &allocation);
11459 gint y = allocation.y;
11460 gint height = allocation.height;
11461 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11462 (GTK_SCROLLED_WINDOW(parent));
11464 gfloat value = gtk_adjustment_get_value(shown);
11465 gfloat upper = gtk_adjustment_get_upper(shown);
11466 gfloat page_size = gtk_adjustment_get_page_size(shown);
11467 if (y < (int)value) {
11468 gtk_adjustment_set_value(shown, y - 1);
11470 if ((y + height) > ((int)value + (int)page_size)) {
11471 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11472 gtk_adjustment_set_value(shown,
11473 y + height - (int)page_size - 1);
11474 } else {
11475 gtk_adjustment_set_value(shown,
11476 (int)upper - (int)page_size - 1);
11483 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11484 compose->focused_editable = widget;
11486 #ifdef GENERIC_UMPC
11487 if (GTK_IS_TEXT_VIEW(widget)
11488 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11489 g_object_ref(compose->notebook);
11490 g_object_ref(compose->edit_vbox);
11491 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11492 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11493 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11494 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11495 g_object_unref(compose->notebook);
11496 g_object_unref(compose->edit_vbox);
11497 g_signal_handlers_block_by_func(G_OBJECT(widget),
11498 G_CALLBACK(compose_grab_focus_cb),
11499 compose);
11500 gtk_widget_grab_focus(widget);
11501 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11502 G_CALLBACK(compose_grab_focus_cb),
11503 compose);
11504 } else if (!GTK_IS_TEXT_VIEW(widget)
11505 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11506 g_object_ref(compose->notebook);
11507 g_object_ref(compose->edit_vbox);
11508 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11509 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11510 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11511 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11512 g_object_unref(compose->notebook);
11513 g_object_unref(compose->edit_vbox);
11514 g_signal_handlers_block_by_func(G_OBJECT(widget),
11515 G_CALLBACK(compose_grab_focus_cb),
11516 compose);
11517 gtk_widget_grab_focus(widget);
11518 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11519 G_CALLBACK(compose_grab_focus_cb),
11520 compose);
11522 #endif
11525 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11527 compose->modified = TRUE;
11528 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11529 #ifndef GENERIC_UMPC
11530 compose_set_title(compose);
11531 #endif
11534 static void compose_wrap_cb(GtkAction *action, gpointer data)
11536 Compose *compose = (Compose *)data;
11537 compose_beautify_paragraph(compose, NULL, TRUE);
11540 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11542 Compose *compose = (Compose *)data;
11543 compose_wrap_all_full(compose, TRUE);
11546 static void compose_find_cb(GtkAction *action, gpointer data)
11548 Compose *compose = (Compose *)data;
11550 message_search_compose(compose);
11553 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11554 gpointer data)
11556 Compose *compose = (Compose *)data;
11557 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11558 if (compose->autowrap)
11559 compose_wrap_all_full(compose, TRUE);
11560 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11563 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11564 gpointer data)
11566 Compose *compose = (Compose *)data;
11567 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11570 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11572 Compose *compose = (Compose *)data;
11574 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11575 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11578 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11580 Compose *compose = (Compose *)data;
11582 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11583 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11586 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11588 g_free(compose->privacy_system);
11589 g_free(compose->encdata);
11591 compose->privacy_system = g_strdup(account->default_privacy_system);
11592 compose_update_privacy_system_menu_item(compose, warn);
11595 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11597 if (folder_item != NULL) {
11598 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT &&
11599 privacy_system_can_sign(compose->privacy_system)) {
11600 compose_use_signing(compose,
11601 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11603 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT &&
11604 privacy_system_can_encrypt(compose->privacy_system)) {
11605 compose_use_encryption(compose,
11606 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11611 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11613 Compose *compose = (Compose *)data;
11615 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11616 gtk_widget_show(compose->ruler_hbox);
11617 prefs_common.show_ruler = TRUE;
11618 } else {
11619 gtk_widget_hide(compose->ruler_hbox);
11620 gtk_widget_queue_resize(compose->edit_vbox);
11621 prefs_common.show_ruler = FALSE;
11625 static void compose_attach_drag_received_cb (GtkWidget *widget,
11626 GdkDragContext *context,
11627 gint x,
11628 gint y,
11629 GtkSelectionData *data,
11630 guint info,
11631 guint time,
11632 gpointer user_data)
11634 Compose *compose = (Compose *)user_data;
11635 GList *list, *tmp;
11636 GdkAtom type;
11638 type = gtk_selection_data_get_data_type(data);
11639 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11640 && gtk_drag_get_source_widget(context) !=
11641 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11642 list = uri_list_extract_filenames(
11643 (const gchar *)gtk_selection_data_get_data(data));
11644 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11645 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11646 compose_attach_append
11647 (compose, (const gchar *)tmp->data,
11648 utf8_filename, NULL, NULL);
11649 g_free(utf8_filename);
11651 if (list)
11652 compose_changed_cb(NULL, compose);
11653 list_free_strings_full(list);
11654 } else if (gtk_drag_get_source_widget(context)
11655 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11656 /* comes from our summaryview */
11657 SummaryView * summaryview = NULL;
11658 GSList * list = NULL, *cur = NULL;
11660 if (mainwindow_get_mainwindow())
11661 summaryview = mainwindow_get_mainwindow()->summaryview;
11663 if (summaryview)
11664 list = summary_get_selected_msg_list(summaryview);
11666 for (cur = list; cur; cur = cur->next) {
11667 MsgInfo *msginfo = (MsgInfo *)cur->data;
11668 gchar *file = NULL;
11669 if (msginfo)
11670 file = procmsg_get_message_file_full(msginfo,
11671 TRUE, TRUE);
11672 if (file) {
11673 compose_attach_append(compose, (const gchar *)file,
11674 (const gchar *)file, "message/rfc822", NULL);
11675 g_free(file);
11678 g_slist_free(list);
11682 static gboolean compose_drag_drop(GtkWidget *widget,
11683 GdkDragContext *drag_context,
11684 gint x, gint y,
11685 guint time, gpointer user_data)
11687 /* not handling this signal makes compose_insert_drag_received_cb
11688 * called twice */
11689 return TRUE;
11692 static gboolean completion_set_focus_to_subject
11693 (GtkWidget *widget,
11694 GdkEventKey *event,
11695 Compose *compose)
11697 cm_return_val_if_fail(compose != NULL, FALSE);
11699 /* make backtab move to subject field */
11700 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11701 gtk_widget_grab_focus(compose->subject_entry);
11702 return TRUE;
11704 return FALSE;
11707 static void compose_insert_drag_received_cb (GtkWidget *widget,
11708 GdkDragContext *drag_context,
11709 gint x,
11710 gint y,
11711 GtkSelectionData *data,
11712 guint info,
11713 guint time,
11714 gpointer user_data)
11716 Compose *compose = (Compose *)user_data;
11717 GList *list, *tmp;
11718 GdkAtom type;
11719 guint num_files;
11720 gchar *msg;
11722 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11723 * does not work */
11724 type = gtk_selection_data_get_data_type(data);
11725 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11726 AlertValue val = G_ALERTDEFAULT;
11727 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11729 list = uri_list_extract_filenames(ddata);
11730 num_files = g_list_length(list);
11731 if (list == NULL && strstr(ddata, "://")) {
11732 /* Assume a list of no files, and data has ://, is a remote link */
11733 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11734 gchar *tmpfile = get_tmp_file();
11735 str_write_to_file(tmpdata, tmpfile, TRUE);
11736 g_free(tmpdata);
11737 compose_insert_file(compose, tmpfile);
11738 claws_unlink(tmpfile);
11739 g_free(tmpfile);
11740 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11741 compose_beautify_paragraph(compose, NULL, TRUE);
11742 return;
11744 switch (prefs_common.compose_dnd_mode) {
11745 case COMPOSE_DND_ASK:
11746 msg = g_strdup_printf(
11747 ngettext(
11748 "Do you want to insert the contents of the file "
11749 "into the message body, or attach it to the email?",
11750 "Do you want to insert the contents of the %d files "
11751 "into the message body, or attach them to the email?",
11752 num_files),
11753 num_files);
11754 val = alertpanel_full(_("Insert or attach?"), msg,
11755 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11756 ALERTFOCUS_SECOND,
11757 TRUE, NULL, ALERT_QUESTION);
11758 g_free(msg);
11759 break;
11760 case COMPOSE_DND_INSERT:
11761 val = G_ALERTALTERNATE;
11762 break;
11763 case COMPOSE_DND_ATTACH:
11764 val = G_ALERTOTHER;
11765 break;
11766 default:
11767 /* unexpected case */
11768 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11771 if (val & G_ALERTDISABLE) {
11772 val &= ~G_ALERTDISABLE;
11773 /* remember what action to perform by default, only if we don't click Cancel */
11774 if (val == G_ALERTALTERNATE)
11775 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11776 else if (val == G_ALERTOTHER)
11777 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11780 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11781 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11782 list_free_strings_full(list);
11783 return;
11784 } else if (val == G_ALERTOTHER) {
11785 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11786 list_free_strings_full(list);
11787 return;
11790 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11791 compose_insert_file(compose, (const gchar *)tmp->data);
11793 list_free_strings_full(list);
11794 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11795 return;
11799 static void compose_header_drag_received_cb (GtkWidget *widget,
11800 GdkDragContext *drag_context,
11801 gint x,
11802 gint y,
11803 GtkSelectionData *data,
11804 guint info,
11805 guint time,
11806 gpointer user_data)
11808 GtkEditable *entry = (GtkEditable *)user_data;
11809 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11811 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11812 * does not work */
11814 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11815 gchar *decoded=g_new(gchar, strlen(email));
11816 int start = 0;
11818 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11819 gtk_editable_delete_text(entry, 0, -1);
11820 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11821 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11822 g_free(decoded);
11823 return;
11825 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11828 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11830 Compose *compose = (Compose *)data;
11832 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11833 compose->return_receipt = TRUE;
11834 else
11835 compose->return_receipt = FALSE;
11838 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11840 Compose *compose = (Compose *)data;
11842 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11843 compose->remove_references = TRUE;
11844 else
11845 compose->remove_references = FALSE;
11848 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11849 ComposeHeaderEntry *headerentry)
11851 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11852 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11853 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11854 return FALSE;
11857 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11858 GdkEventKey *event,
11859 ComposeHeaderEntry *headerentry)
11861 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11862 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11863 !(event->state & GDK_MODIFIER_MASK) &&
11864 (event->keyval == GDK_KEY_BackSpace) &&
11865 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11866 gtk_container_remove
11867 (GTK_CONTAINER(headerentry->compose->header_table),
11868 headerentry->combo);
11869 gtk_container_remove
11870 (GTK_CONTAINER(headerentry->compose->header_table),
11871 headerentry->entry);
11872 headerentry->compose->header_list =
11873 g_slist_remove(headerentry->compose->header_list,
11874 headerentry);
11875 g_free(headerentry);
11876 } else if (event->keyval == GDK_KEY_Tab) {
11877 if (headerentry->compose->header_last == headerentry) {
11878 /* Override default next focus, and give it to subject_entry
11879 * instead of notebook tabs
11881 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11882 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11883 return TRUE;
11886 return FALSE;
11889 static gboolean scroll_postpone(gpointer data)
11891 Compose *compose = (Compose *)data;
11893 if (compose->batch)
11894 return FALSE;
11896 GTK_EVENTS_FLUSH();
11897 compose_show_first_last_header(compose, FALSE);
11898 return FALSE;
11901 static void compose_headerentry_changed_cb(GtkWidget *entry,
11902 ComposeHeaderEntry *headerentry)
11904 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11905 compose_create_header_entry(headerentry->compose);
11906 g_signal_handlers_disconnect_matched
11907 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11908 0, 0, NULL, NULL, headerentry);
11910 if (!headerentry->compose->batch)
11911 g_timeout_add(0, scroll_postpone, headerentry->compose);
11915 static gboolean compose_defer_auto_save_draft(Compose *compose)
11917 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11918 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11919 return FALSE;
11922 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11924 GtkAdjustment *vadj;
11926 cm_return_if_fail(compose);
11928 if(compose->batch)
11929 return;
11931 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11932 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11933 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11934 gtk_widget_get_parent(compose->header_table)));
11935 gtk_adjustment_set_value(vadj, (show_first ?
11936 gtk_adjustment_get_lower(vadj) :
11937 (gtk_adjustment_get_upper(vadj) -
11938 gtk_adjustment_get_page_size(vadj))));
11939 gtk_adjustment_changed(vadj);
11942 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11943 const gchar *text, gint len, Compose *compose)
11945 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11946 (G_OBJECT(compose->text), "paste_as_quotation"));
11947 GtkTextMark *mark;
11949 cm_return_if_fail(text != NULL);
11951 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11952 G_CALLBACK(text_inserted),
11953 compose);
11954 if (paste_as_quotation) {
11955 gchar *new_text;
11956 const gchar *qmark;
11957 guint pos = 0;
11958 GtkTextIter start_iter;
11960 if (len < 0)
11961 len = strlen(text);
11963 new_text = g_strndup(text, len);
11965 qmark = compose_quote_char_from_context(compose);
11967 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11968 gtk_text_buffer_place_cursor(buffer, iter);
11970 pos = gtk_text_iter_get_offset(iter);
11972 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11973 _("Quote format error at line %d."));
11974 quote_fmt_reset_vartable();
11975 g_free(new_text);
11976 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11977 GINT_TO_POINTER(paste_as_quotation - 1));
11979 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11980 gtk_text_buffer_place_cursor(buffer, iter);
11981 gtk_text_buffer_delete_mark(buffer, mark);
11983 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11984 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11985 compose_beautify_paragraph(compose, &start_iter, FALSE);
11986 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11987 gtk_text_buffer_delete_mark(buffer, mark);
11988 } else {
11989 if (strcmp(text, "\n") || compose->automatic_break
11990 || gtk_text_iter_starts_line(iter)) {
11991 GtkTextIter before_ins;
11992 gtk_text_buffer_insert(buffer, iter, text, len);
11993 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11994 before_ins = *iter;
11995 gtk_text_iter_backward_chars(&before_ins, len);
11996 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11998 } else {
11999 /* check if the preceding is just whitespace or quote */
12000 GtkTextIter start_line;
12001 gchar *tmp = NULL, *quote = NULL;
12002 gint quote_len = 0, is_normal = 0;
12003 start_line = *iter;
12004 gtk_text_iter_set_line_offset(&start_line, 0);
12005 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
12006 g_strstrip(tmp);
12008 if (*tmp == '\0') {
12009 is_normal = 1;
12010 } else {
12011 quote = compose_get_quote_str(buffer, &start_line, &quote_len);
12012 if (quote)
12013 is_normal = 1;
12014 g_free(quote);
12016 g_free(tmp);
12018 if (is_normal) {
12019 gtk_text_buffer_insert(buffer, iter, text, len);
12020 } else {
12021 gtk_text_buffer_insert_with_tags_by_name(buffer,
12022 iter, text, len, "no_join", NULL);
12027 if (!paste_as_quotation) {
12028 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
12029 compose_beautify_paragraph(compose, iter, FALSE);
12030 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12031 gtk_text_buffer_delete_mark(buffer, mark);
12034 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
12035 G_CALLBACK(text_inserted),
12036 compose);
12037 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
12039 if (compose_can_autosave(compose) &&
12040 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
12041 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
12042 compose->draft_timeout_tag = g_timeout_add
12043 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
12046 #if USE_ENCHANT
12047 static void compose_check_all(GtkAction *action, gpointer data)
12049 Compose *compose = (Compose *)data;
12050 if (!compose->gtkaspell)
12051 return;
12053 if (gtk_widget_has_focus(compose->subject_entry))
12054 claws_spell_entry_check_all(
12055 CLAWS_SPELL_ENTRY(compose->subject_entry));
12056 else
12057 gtkaspell_check_all(compose->gtkaspell);
12060 static void compose_highlight_all(GtkAction *action, gpointer data)
12062 Compose *compose = (Compose *)data;
12063 if (compose->gtkaspell) {
12064 claws_spell_entry_recheck_all(
12065 CLAWS_SPELL_ENTRY(compose->subject_entry));
12066 gtkaspell_highlight_all(compose->gtkaspell);
12070 static void compose_check_backwards(GtkAction *action, gpointer data)
12072 Compose *compose = (Compose *)data;
12073 if (!compose->gtkaspell) {
12074 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12075 return;
12078 if (gtk_widget_has_focus(compose->subject_entry))
12079 claws_spell_entry_check_backwards(
12080 CLAWS_SPELL_ENTRY(compose->subject_entry));
12081 else
12082 gtkaspell_check_backwards(compose->gtkaspell);
12085 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12087 Compose *compose = (Compose *)data;
12088 if (!compose->gtkaspell) {
12089 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12090 return;
12093 if (gtk_widget_has_focus(compose->subject_entry))
12094 claws_spell_entry_check_forwards_go(
12095 CLAWS_SPELL_ENTRY(compose->subject_entry));
12096 else
12097 gtkaspell_check_forwards_go(compose->gtkaspell);
12099 #endif
12102 *\brief Guess originating forward account from MsgInfo and several
12103 * "common preference" settings. Return NULL if no guess.
12105 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12107 PrefsAccount *account = NULL;
12109 cm_return_val_if_fail(msginfo, NULL);
12110 cm_return_val_if_fail(msginfo->folder, NULL);
12111 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12113 if (msginfo->folder->prefs->enable_default_account)
12114 account = account_find_from_id(msginfo->folder->prefs->default_account);
12116 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12117 gchar *to;
12118 Xstrdup_a(to, msginfo->to, return NULL);
12119 extract_address(to);
12120 account = account_find_from_address(to, FALSE);
12123 if (!account && prefs_common.forward_account_autosel) {
12124 gchar *cc = NULL;
12125 if (!procheader_get_header_from_msginfo
12126 (msginfo, &cc, "Cc:")) {
12127 gchar *buf = cc + strlen("Cc:");
12128 extract_address(buf);
12129 account = account_find_from_address(buf, FALSE);
12130 g_free(cc);
12134 if (!account && prefs_common.forward_account_autosel) {
12135 gchar *deliveredto = NULL;
12136 if (!procheader_get_header_from_msginfo
12137 (msginfo, &deliveredto, "Delivered-To:")) {
12138 gchar *buf = deliveredto + strlen("Delivered-To:");
12139 extract_address(buf);
12140 account = account_find_from_address(buf, FALSE);
12141 g_free(deliveredto);
12145 if (!account)
12146 account = msginfo->folder->folder->account;
12148 return account;
12151 gboolean compose_close(Compose *compose)
12153 gint x, y;
12155 cm_return_val_if_fail(compose, FALSE);
12157 if (!g_mutex_trylock(compose->mutex)) {
12158 /* we have to wait for the (possibly deferred by auto-save)
12159 * drafting to be done, before destroying the compose under
12160 * it. */
12161 debug_print("waiting for drafting to finish...\n");
12162 compose_allow_user_actions(compose, FALSE);
12163 if (compose->close_timeout_tag == 0) {
12164 compose->close_timeout_tag =
12165 g_timeout_add (500, (GSourceFunc) compose_close,
12166 compose);
12168 return TRUE;
12171 if (compose->draft_timeout_tag >= 0) {
12172 g_source_remove(compose->draft_timeout_tag);
12173 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12176 gtkut_widget_get_uposition(compose->window, &x, &y);
12177 if (!compose->batch) {
12178 prefs_common.compose_x = x;
12179 prefs_common.compose_y = y;
12181 g_mutex_unlock(compose->mutex);
12182 compose_destroy(compose);
12183 return FALSE;
12187 * Add entry field for each address in list.
12188 * \param compose E-Mail composition object.
12189 * \param listAddress List of (formatted) E-Mail addresses.
12191 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12192 GList *node;
12193 gchar *addr;
12194 node = listAddress;
12195 while( node ) {
12196 addr = ( gchar * ) node->data;
12197 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12198 node = g_list_next( node );
12202 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12203 guint action, gboolean opening_multiple)
12205 gchar *body = NULL;
12206 GSList *new_msglist = NULL;
12207 MsgInfo *tmp_msginfo = NULL;
12208 gboolean originally_enc = FALSE;
12209 gboolean originally_sig = FALSE;
12210 Compose *compose = NULL;
12211 gchar *s_system = NULL;
12213 cm_return_if_fail(msginfo_list != NULL);
12215 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12216 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12217 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12219 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12220 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12221 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12222 orig_msginfo, mimeinfo);
12223 if (tmp_msginfo != NULL) {
12224 new_msglist = g_slist_append(NULL, tmp_msginfo);
12226 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12227 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12228 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12230 tmp_msginfo->folder = orig_msginfo->folder;
12231 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12232 if (orig_msginfo->tags) {
12233 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12234 tmp_msginfo->folder->tags_dirty = TRUE;
12240 if (!opening_multiple && msgview != NULL)
12241 body = messageview_get_selection(msgview);
12243 if (new_msglist) {
12244 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12245 procmsg_msginfo_free(&tmp_msginfo);
12246 g_slist_free(new_msglist);
12247 } else
12248 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12250 if (compose && originally_enc) {
12251 compose_force_encryption(compose, compose->account, FALSE, s_system);
12254 if (compose && originally_sig && compose->account->default_sign_reply) {
12255 compose_force_signing(compose, compose->account, s_system);
12257 g_free(s_system);
12258 g_free(body);
12259 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12262 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12263 guint action)
12265 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12266 && msginfo_list != NULL
12267 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12268 GSList *cur = msginfo_list;
12269 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12270 "messages. Opening the windows "
12271 "could take some time. Do you "
12272 "want to continue?"),
12273 g_slist_length(msginfo_list));
12274 if (g_slist_length(msginfo_list) > 9
12275 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12276 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12277 g_free(msg);
12278 return;
12280 g_free(msg);
12281 /* We'll open multiple compose windows */
12282 /* let the WM place the next windows */
12283 compose_force_window_origin = FALSE;
12284 for (; cur; cur = cur->next) {
12285 GSList tmplist;
12286 tmplist.data = cur->data;
12287 tmplist.next = NULL;
12288 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12290 compose_force_window_origin = TRUE;
12291 } else {
12292 /* forwarding multiple mails as attachments is done via a
12293 * single compose window */
12294 if (msginfo_list != NULL) {
12295 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12296 } else if (msgview != NULL) {
12297 GSList tmplist;
12298 tmplist.data = msgview->msginfo;
12299 tmplist.next = NULL;
12300 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12301 } else {
12302 debug_print("Nothing to reply to\n");
12307 void compose_check_for_email_account(Compose *compose)
12309 PrefsAccount *ac = NULL, *curr = NULL;
12310 GList *list;
12312 if (!compose)
12313 return;
12315 if (compose->account && compose->account->protocol == A_NNTP) {
12316 ac = account_get_cur_account();
12317 if (ac->protocol == A_NNTP) {
12318 list = account_get_list();
12320 for( ; list != NULL ; list = g_list_next(list)) {
12321 curr = (PrefsAccount *) list->data;
12322 if (curr->protocol != A_NNTP) {
12323 ac = curr;
12324 break;
12328 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12329 ac->account_id);
12333 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12334 const gchar *address)
12336 GSList *msginfo_list = NULL;
12337 gchar *body = messageview_get_selection(msgview);
12338 Compose *compose;
12340 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12342 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12343 compose_check_for_email_account(compose);
12344 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12345 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12346 compose_reply_set_subject(compose, msginfo);
12348 g_free(body);
12349 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12352 void compose_set_position(Compose *compose, gint pos)
12354 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12356 gtkut_text_view_set_position(text, pos);
12359 gboolean compose_search_string(Compose *compose,
12360 const gchar *str, gboolean case_sens)
12362 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12364 return gtkut_text_view_search_string(text, str, case_sens);
12367 gboolean compose_search_string_backward(Compose *compose,
12368 const gchar *str, gboolean case_sens)
12370 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12372 return gtkut_text_view_search_string_backward(text, str, case_sens);
12375 /* allocate a msginfo structure and populate its data from a compose data structure */
12376 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12378 MsgInfo *newmsginfo;
12379 GSList *list;
12380 gchar date[RFC822_DATE_BUFFSIZE];
12382 cm_return_val_if_fail( compose != NULL, NULL );
12384 newmsginfo = procmsg_msginfo_new();
12386 /* date is now */
12387 get_rfc822_date(date, sizeof(date));
12388 newmsginfo->date = g_strdup(date);
12390 /* from */
12391 if (compose->from_name) {
12392 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12393 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12396 /* subject */
12397 if (compose->subject_entry)
12398 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12400 /* to, cc, reply-to, newsgroups */
12401 for (list = compose->header_list; list; list = list->next) {
12402 gchar *header = gtk_editable_get_chars(
12403 GTK_EDITABLE(
12404 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12405 gchar *entry = gtk_editable_get_chars(
12406 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12408 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12409 if ( newmsginfo->to == NULL ) {
12410 newmsginfo->to = g_strdup(entry);
12411 } else if (entry && *entry) {
12412 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12413 g_free(newmsginfo->to);
12414 newmsginfo->to = tmp;
12416 } else
12417 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12418 if ( newmsginfo->cc == NULL ) {
12419 newmsginfo->cc = g_strdup(entry);
12420 } else if (entry && *entry) {
12421 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12422 g_free(newmsginfo->cc);
12423 newmsginfo->cc = tmp;
12425 } else
12426 if ( strcasecmp(header,
12427 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12428 if ( newmsginfo->newsgroups == NULL ) {
12429 newmsginfo->newsgroups = g_strdup(entry);
12430 } else if (entry && *entry) {
12431 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12432 g_free(newmsginfo->newsgroups);
12433 newmsginfo->newsgroups = tmp;
12437 g_free(header);
12438 g_free(entry);
12441 /* other data is unset */
12443 return newmsginfo;
12446 #ifdef USE_ENCHANT
12447 /* update compose's dictionaries from folder dict settings */
12448 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12449 FolderItem *folder_item)
12451 cm_return_if_fail(compose != NULL);
12453 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12454 FolderItemPrefs *prefs = folder_item->prefs;
12456 if (prefs->enable_default_dictionary)
12457 gtkaspell_change_dict(compose->gtkaspell,
12458 prefs->default_dictionary, FALSE);
12459 if (folder_item->prefs->enable_default_alt_dictionary)
12460 gtkaspell_change_alt_dict(compose->gtkaspell,
12461 prefs->default_alt_dictionary);
12462 if (prefs->enable_default_dictionary
12463 || prefs->enable_default_alt_dictionary)
12464 compose_spell_menu_changed(compose);
12467 #endif
12469 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12471 Compose *compose = (Compose *)data;
12473 cm_return_if_fail(compose != NULL);
12475 gtk_widget_grab_focus(compose->text);
12478 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12480 gtk_combo_box_popup(GTK_COMBO_BOX(data));
12485 * End of Source.