change labels, remove empty lines, show phishing warning on copy
[claws.git] / src / compose.c
blob27a8f388f422ede9f81d7510eb6a46f6e498de72
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
53 #ifdef G_OS_WIN32
54 #include <windows.h>
55 #endif
57 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
58 # include <wchar.h>
59 # include <wctype.h>
60 #endif
62 #include "claws.h"
63 #include "main.h"
64 #include "mainwindow.h"
65 #include "compose.h"
66 #ifndef USE_ALT_ADDRBOOK
67 #include "addressbook.h"
68 #else
69 #include "addressbook-dbus.h"
70 #include "addressadd.h"
71 #endif
72 #include "folderview.h"
73 #include "procmsg.h"
74 #include "menu.h"
75 #include "stock_pixmap.h"
76 #include "send_message.h"
77 #include "imap.h"
78 #include "news.h"
79 #include "customheader.h"
80 #include "prefs_common.h"
81 #include "prefs_account.h"
82 #include "action.h"
83 #include "account.h"
84 #include "filesel.h"
85 #include "procheader.h"
86 #include "procmime.h"
87 #include "statusbar.h"
88 #include "about.h"
89 #include "quoted-printable.h"
90 #include "codeconv.h"
91 #include "utils.h"
92 #include "gtkutils.h"
93 #include "gtkshruler.h"
94 #include "socket.h"
95 #include "alertpanel.h"
96 #include "manage_window.h"
97 #include "folder.h"
98 #include "folder_item_prefs.h"
99 #include "addr_compl.h"
100 #include "quote_fmt.h"
101 #include "undo.h"
102 #include "foldersel.h"
103 #include "toolbar.h"
104 #include "inc.h"
105 #include "message_search.h"
106 #include "combobox.h"
107 #include "hooks.h"
108 #include "privacy.h"
109 #include "timing.h"
110 #include "autofaces.h"
111 #include "spell_entry.h"
112 #include "headers.h"
113 #include "file-utils.h"
115 #ifdef USE_LDAP
116 #include "password.h"
117 #include "ldapserver.h"
118 #endif
120 enum
122 COL_MIMETYPE = 0,
123 COL_SIZE = 1,
124 COL_NAME = 2,
125 COL_CHARSET = 3,
126 COL_DATA = 4,
127 COL_AUTODATA = 5,
128 N_COL_COLUMNS
131 #define N_ATTACH_COLS (N_COL_COLUMNS)
133 typedef enum
135 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
136 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
137 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
138 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
139 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
140 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
141 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
142 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
143 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
144 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
145 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
146 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
147 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
148 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
149 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
150 } ComposeCallAdvancedAction;
152 typedef enum
154 PRIORITY_HIGHEST = 1,
155 PRIORITY_HIGH,
156 PRIORITY_NORMAL,
157 PRIORITY_LOW,
158 PRIORITY_LOWEST
159 } PriorityLevel;
161 typedef enum
163 COMPOSE_INSERT_SUCCESS,
164 COMPOSE_INSERT_READ_ERROR,
165 COMPOSE_INSERT_INVALID_CHARACTER,
166 COMPOSE_INSERT_NO_FILE
167 } ComposeInsertResult;
169 typedef enum
171 COMPOSE_WRITE_FOR_SEND,
172 COMPOSE_WRITE_FOR_STORE
173 } ComposeWriteType;
175 typedef enum
177 COMPOSE_QUOTE_FORCED,
178 COMPOSE_QUOTE_CHECK,
179 COMPOSE_QUOTE_SKIP
180 } ComposeQuoteMode;
182 typedef enum {
183 TO_FIELD_PRESENT,
184 SUBJECT_FIELD_PRESENT,
185 BODY_FIELD_PRESENT,
186 NO_FIELD_PRESENT
187 } MailField;
189 #define B64_LINE_SIZE 57
190 #define B64_BUFFSIZE 77
192 #define MAX_REFERENCES_LEN 999
194 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
195 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
197 #define COMPOSE_PRIVACY_WARNING() { \
198 alertpanel_error(_("You have opted to sign and/or encrypt this " \
199 "message but have not selected a privacy system.\n\n" \
200 "Signing and encrypting have been disabled for this " \
201 "message.")); \
204 static GdkColor default_header_bgcolor = {
205 (gulong)0,
206 (gushort)0,
207 (gushort)0,
208 (gushort)0
211 static GdkColor default_header_color = {
212 (gulong)0,
213 (gushort)0,
214 (gushort)0,
215 (gushort)0
218 static GList *compose_list = NULL;
219 static GSList *extra_headers = NULL;
221 static Compose *compose_generic_new (PrefsAccount *account,
222 const gchar *to,
223 FolderItem *item,
224 GList *attach_files,
225 GList *listAddress );
227 static Compose *compose_create (PrefsAccount *account,
228 FolderItem *item,
229 ComposeMode mode,
230 gboolean batch);
232 static void compose_entry_indicate (Compose *compose,
233 const gchar *address);
234 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
235 ComposeQuoteMode quote_mode,
236 gboolean to_all,
237 gboolean to_sender,
238 const gchar *body);
239 static Compose *compose_forward_multiple (PrefsAccount *account,
240 GSList *msginfo_list);
241 static Compose *compose_reply (MsgInfo *msginfo,
242 ComposeQuoteMode quote_mode,
243 gboolean to_all,
244 gboolean to_ml,
245 gboolean to_sender,
246 const gchar *body);
247 static Compose *compose_reply_mode (ComposeMode mode,
248 GSList *msginfo_list,
249 gchar *body);
250 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
251 static void compose_update_privacy_systems_menu(Compose *compose);
253 static GtkWidget *compose_account_option_menu_create
254 (Compose *compose);
255 static void compose_set_out_encoding (Compose *compose);
256 static void compose_set_template_menu (Compose *compose);
257 static void compose_destroy (Compose *compose);
259 static MailField compose_entries_set (Compose *compose,
260 const gchar *mailto,
261 ComposeEntryType to_type);
262 static gint compose_parse_header (Compose *compose,
263 MsgInfo *msginfo);
264 static gint compose_parse_manual_headers (Compose *compose,
265 MsgInfo *msginfo,
266 HeaderEntry *entries);
267 static gchar *compose_parse_references (const gchar *ref,
268 const gchar *msgid);
270 static gchar *compose_quote_fmt (Compose *compose,
271 MsgInfo *msginfo,
272 const gchar *fmt,
273 const gchar *qmark,
274 const gchar *body,
275 gboolean rewrap,
276 gboolean need_unescape,
277 const gchar *err_msg);
279 static void compose_reply_set_entry (Compose *compose,
280 MsgInfo *msginfo,
281 gboolean to_all,
282 gboolean to_ml,
283 gboolean to_sender,
284 gboolean
285 followup_and_reply_to);
286 static void compose_reedit_set_entry (Compose *compose,
287 MsgInfo *msginfo);
289 static void compose_insert_sig (Compose *compose,
290 gboolean replace);
291 static ComposeInsertResult compose_insert_file (Compose *compose,
292 const gchar *file);
294 static gboolean compose_attach_append (Compose *compose,
295 const gchar *file,
296 const gchar *type,
297 const gchar *content_type,
298 const gchar *charset);
299 static void compose_attach_parts (Compose *compose,
300 MsgInfo *msginfo);
302 static gboolean compose_beautify_paragraph (Compose *compose,
303 GtkTextIter *par_iter,
304 gboolean force);
305 static void compose_wrap_all (Compose *compose);
306 static void compose_wrap_all_full (Compose *compose,
307 gboolean autowrap);
309 static void compose_set_title (Compose *compose);
310 static void compose_select_account (Compose *compose,
311 PrefsAccount *account,
312 gboolean init);
314 static PrefsAccount *compose_current_mail_account(void);
315 /* static gint compose_send (Compose *compose); */
316 static gboolean compose_check_for_valid_recipient
317 (Compose *compose);
318 static gboolean compose_check_entries (Compose *compose,
319 gboolean check_everything);
320 static gint compose_write_to_file (Compose *compose,
321 FILE *fp,
322 gint action,
323 gboolean attach_parts);
324 static gint compose_write_body_to_file (Compose *compose,
325 const gchar *file);
326 static gint compose_remove_reedit_target (Compose *compose,
327 gboolean force);
328 static void compose_remove_draft (Compose *compose);
329 static ComposeQueueResult compose_queue_sub (Compose *compose,
330 gint *msgnum,
331 FolderItem **item,
332 gchar **msgpath,
333 gboolean perform_checks,
334 gboolean remove_reedit_target);
335 static int compose_add_attachments (Compose *compose,
336 MimeInfo *parent);
337 static gchar *compose_get_header (Compose *compose);
338 static gchar *compose_get_manual_headers_info (Compose *compose);
340 static void compose_convert_header (Compose *compose,
341 gchar *dest,
342 gint len,
343 gchar *src,
344 gint header_len,
345 gboolean addr_field);
347 static void compose_attach_info_free (AttachInfo *ainfo);
348 static void compose_attach_remove_selected (GtkAction *action,
349 gpointer data);
351 static void compose_template_apply (Compose *compose,
352 Template *tmpl,
353 gboolean replace);
354 static void compose_attach_property (GtkAction *action,
355 gpointer data);
356 static void compose_attach_property_create (gboolean *cancelled);
357 static void attach_property_ok (GtkWidget *widget,
358 gboolean *cancelled);
359 static void attach_property_cancel (GtkWidget *widget,
360 gboolean *cancelled);
361 static gint attach_property_delete_event (GtkWidget *widget,
362 GdkEventAny *event,
363 gboolean *cancelled);
364 static gboolean attach_property_key_pressed (GtkWidget *widget,
365 GdkEventKey *event,
366 gboolean *cancelled);
368 static void compose_exec_ext_editor (Compose *compose);
369 static gboolean compose_ext_editor_kill (Compose *compose);
370 static void compose_ext_editor_closed_cb (GPid pid,
371 gint exit_status,
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 #ifndef G_OS_WIN32
378 static gboolean compose_ext_editor_plug_removed_cb
379 (GtkSocket *socket,
380 Compose *compose);
381 #endif /* G_OS_WIN32 */
383 static void compose_undo_state_changed (UndoMain *undostruct,
384 gint undo_state,
385 gint redo_state,
386 gpointer data);
388 static void compose_create_header_entry (Compose *compose);
389 static void compose_add_header_entry (Compose *compose, const gchar *header,
390 gchar *text, ComposePrefType pref_type);
391 static void compose_remove_header_entries(Compose *compose);
393 static void compose_update_priority_menu_item(Compose * compose);
394 #if USE_ENCHANT
395 static void compose_spell_menu_changed (void *data);
396 static void compose_dict_changed (void *data);
397 #endif
398 static void compose_add_field_list ( Compose *compose,
399 GList *listAddress );
401 /* callback functions */
403 static void compose_notebook_size_alloc (GtkNotebook *notebook,
404 GtkAllocation *allocation,
405 GtkPaned *paned);
406 static gboolean compose_edit_size_alloc (GtkEditable *widget,
407 GtkAllocation *allocation,
408 GtkSHRuler *shruler);
409 static void account_activated (GtkComboBox *optmenu,
410 gpointer data);
411 static void attach_selected (GtkTreeView *tree_view,
412 GtkTreePath *tree_path,
413 GtkTreeViewColumn *column,
414 Compose *compose);
415 static gboolean attach_button_pressed (GtkWidget *widget,
416 GdkEventButton *event,
417 gpointer data);
418 static gboolean attach_key_pressed (GtkWidget *widget,
419 GdkEventKey *event,
420 gpointer data);
421 static void compose_send_cb (GtkAction *action, gpointer data);
422 static void compose_send_later_cb (GtkAction *action, gpointer data);
424 static void compose_save_cb (GtkAction *action,
425 gpointer data);
427 static void compose_attach_cb (GtkAction *action,
428 gpointer data);
429 static void compose_insert_file_cb (GtkAction *action,
430 gpointer data);
431 static void compose_insert_sig_cb (GtkAction *action,
432 gpointer data);
433 static void compose_replace_sig_cb (GtkAction *action,
434 gpointer data);
436 static void compose_close_cb (GtkAction *action,
437 gpointer data);
438 static void compose_print_cb (GtkAction *action,
439 gpointer data);
441 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
443 static void compose_address_cb (GtkAction *action,
444 gpointer data);
445 static void about_show_cb (GtkAction *action,
446 gpointer data);
447 static void compose_template_activate_cb(GtkWidget *widget,
448 gpointer data);
450 static void compose_ext_editor_cb (GtkAction *action,
451 gpointer data);
453 static gint compose_delete_cb (GtkWidget *widget,
454 GdkEventAny *event,
455 gpointer data);
457 static void compose_undo_cb (GtkAction *action,
458 gpointer data);
459 static void compose_redo_cb (GtkAction *action,
460 gpointer data);
461 static void compose_cut_cb (GtkAction *action,
462 gpointer data);
463 static void compose_copy_cb (GtkAction *action,
464 gpointer data);
465 static void compose_paste_cb (GtkAction *action,
466 gpointer data);
467 static void compose_paste_as_quote_cb (GtkAction *action,
468 gpointer data);
469 static void compose_paste_no_wrap_cb (GtkAction *action,
470 gpointer data);
471 static void compose_paste_wrap_cb (GtkAction *action,
472 gpointer data);
473 static void compose_allsel_cb (GtkAction *action,
474 gpointer data);
476 static void compose_advanced_action_cb (GtkAction *action,
477 gpointer data);
479 static void compose_grab_focus_cb (GtkWidget *widget,
480 Compose *compose);
482 static void compose_changed_cb (GtkTextBuffer *textbuf,
483 Compose *compose);
485 static void compose_wrap_cb (GtkAction *action,
486 gpointer data);
487 static void compose_wrap_all_cb (GtkAction *action,
488 gpointer data);
489 static void compose_find_cb (GtkAction *action,
490 gpointer data);
491 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
492 gpointer data);
493 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
494 gpointer data);
496 static void compose_toggle_ruler_cb (GtkToggleAction *action,
497 gpointer data);
498 static void compose_toggle_sign_cb (GtkToggleAction *action,
499 gpointer data);
500 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
501 gpointer data);
502 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
503 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
504 static void compose_activate_privacy_system (Compose *compose,
505 PrefsAccount *account,
506 gboolean warn);
507 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item);
508 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
509 gpointer data);
510 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
511 gpointer data);
512 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
513 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
514 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
516 static void compose_attach_drag_received_cb (GtkWidget *widget,
517 GdkDragContext *drag_context,
518 gint x,
519 gint y,
520 GtkSelectionData *data,
521 guint info,
522 guint time,
523 gpointer user_data);
524 static void compose_insert_drag_received_cb (GtkWidget *widget,
525 GdkDragContext *drag_context,
526 gint x,
527 gint y,
528 GtkSelectionData *data,
529 guint info,
530 guint time,
531 gpointer user_data);
532 static void compose_header_drag_received_cb (GtkWidget *widget,
533 GdkDragContext *drag_context,
534 gint x,
535 gint y,
536 GtkSelectionData *data,
537 guint info,
538 guint time,
539 gpointer user_data);
541 static gboolean compose_drag_drop (GtkWidget *widget,
542 GdkDragContext *drag_context,
543 gint x, gint y,
544 guint time, gpointer user_data);
545 static gboolean completion_set_focus_to_subject
546 (GtkWidget *widget,
547 GdkEventKey *event,
548 Compose *user_data);
550 static void text_inserted (GtkTextBuffer *buffer,
551 GtkTextIter *iter,
552 const gchar *text,
553 gint len,
554 Compose *compose);
555 static Compose *compose_generic_reply(MsgInfo *msginfo,
556 ComposeQuoteMode quote_mode,
557 gboolean to_all,
558 gboolean to_ml,
559 gboolean to_sender,
560 gboolean followup_and_reply_to,
561 const gchar *body);
563 static void compose_headerentry_changed_cb (GtkWidget *entry,
564 ComposeHeaderEntry *headerentry);
565 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
566 GdkEventKey *event,
567 ComposeHeaderEntry *headerentry);
568 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
569 ComposeHeaderEntry *headerentry);
571 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
573 static void compose_allow_user_actions (Compose *compose, gboolean allow);
575 static void compose_nothing_cb (GtkAction *action, gpointer data)
580 #if USE_ENCHANT
581 static void compose_check_all (GtkAction *action, gpointer data);
582 static void compose_highlight_all (GtkAction *action, gpointer data);
583 static void compose_check_backwards (GtkAction *action, gpointer data);
584 static void compose_check_forwards_go (GtkAction *action, gpointer data);
585 #endif
587 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
589 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
591 #ifdef USE_ENCHANT
592 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
593 FolderItem *folder_item);
594 #endif
595 static void compose_attach_update_label(Compose *compose);
596 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
597 gboolean respect_default_to);
598 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
599 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
601 static GtkActionEntry compose_popup_entries[] =
603 {"Compose", NULL, "Compose", NULL, NULL, NULL },
604 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
605 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
606 {"Compose/---", NULL, "---", NULL, NULL, NULL },
607 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
610 static GtkActionEntry compose_entries[] =
612 {"Menu", NULL, "Menu", NULL, NULL, NULL },
613 /* menus */
614 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
615 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
616 #if USE_ENCHANT
617 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
618 #endif
619 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
620 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
621 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
622 /* Message menu */
623 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
624 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
625 {"Message/---", NULL, "---", NULL, NULL, NULL },
627 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
628 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
629 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
630 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
631 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
632 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
633 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
634 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
635 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
636 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
638 /* Edit menu */
639 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
640 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
641 {"Edit/---", NULL, "---", NULL, NULL, NULL },
643 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
644 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
645 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
647 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
648 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
649 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
650 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
652 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
654 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
655 {"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*/
656 {"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*/
657 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
658 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
659 {"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*/
660 {"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*/
661 {"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*/
662 {"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*/
663 {"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*/
664 {"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*/
665 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
666 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
667 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
668 {"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*/
670 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
671 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
673 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
674 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
675 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
676 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
677 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
678 #if USE_ENCHANT
679 /* Spelling menu */
680 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
681 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
682 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
683 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
685 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
686 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
687 #endif
689 /* Options menu */
690 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
691 {"Options/---", NULL, "---", NULL, NULL, NULL },
692 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
693 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
695 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
696 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
698 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
699 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
700 #define ENC_ACTION(cs_char,c_char,string) \
701 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
703 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
704 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
705 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
706 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
707 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
708 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
709 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
710 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
711 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
713 /* Tools menu */
714 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
716 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
717 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
718 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
719 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
721 /* Help menu */
722 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
725 static GtkToggleActionEntry compose_toggle_entries[] =
727 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
728 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
729 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
730 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
731 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
732 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
733 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
736 static GtkRadioActionEntry compose_radio_rm_entries[] =
738 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
739 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
740 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
741 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
744 static GtkRadioActionEntry compose_radio_prio_entries[] =
746 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
747 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
748 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
749 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
750 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
753 static GtkRadioActionEntry compose_radio_enc_entries[] =
755 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
777 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
778 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
779 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
780 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
781 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
782 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
783 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
784 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
785 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
786 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
787 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
790 static GtkTargetEntry compose_mime_types[] =
792 {"text/uri-list", 0, 0},
793 {"UTF8_STRING", 0, 0},
794 {"text/plain", 0, 0}
797 static gboolean compose_put_existing_to_front(MsgInfo *info)
799 const GList *compose_list = compose_get_compose_list();
800 const GList *elem = NULL;
802 if (compose_list) {
803 for (elem = compose_list; elem != NULL && elem->data != NULL;
804 elem = elem->next) {
805 Compose *c = (Compose*)elem->data;
807 if (!c->targetinfo || !c->targetinfo->msgid ||
808 !info->msgid)
809 continue;
811 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
812 gtkut_window_popup(c->window);
813 return TRUE;
817 return FALSE;
820 static GdkColor quote_color1 =
821 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
822 static GdkColor quote_color2 =
823 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
824 static GdkColor quote_color3 =
825 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
827 static GdkColor quote_bgcolor1 =
828 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
829 static GdkColor quote_bgcolor2 =
830 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
831 static GdkColor quote_bgcolor3 =
832 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
834 static GdkColor signature_color = {
835 (gulong)0,
836 (gushort)0x7fff,
837 (gushort)0x7fff,
838 (gushort)0x7fff
841 static GdkColor uri_color = {
842 (gulong)0,
843 (gushort)0,
844 (gushort)0,
845 (gushort)0
848 static void compose_create_tags(GtkTextView *text, Compose *compose)
850 GtkTextBuffer *buffer;
851 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
853 buffer = gtk_text_view_get_buffer(text);
855 if (prefs_common.enable_color) {
856 /* grab the quote colors, converting from an int to a GdkColor */
857 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1],
858 &quote_color1);
859 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2],
860 &quote_color2);
861 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3],
862 &quote_color3);
863 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1_BG],
864 &quote_bgcolor1);
865 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2_BG],
866 &quote_bgcolor2);
867 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3_BG],
868 &quote_bgcolor3);
869 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_SIGNATURE],
870 &signature_color);
871 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_URI],
872 &uri_color);
873 } else {
874 signature_color = quote_color1 = quote_color2 = quote_color3 =
875 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
878 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
879 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
880 "foreground-gdk", &quote_color1,
881 "paragraph-background-gdk", &quote_bgcolor1,
882 NULL);
883 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
884 "foreground-gdk", &quote_color2,
885 "paragraph-background-gdk", &quote_bgcolor2,
886 NULL);
887 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
888 "foreground-gdk", &quote_color3,
889 "paragraph-background-gdk", &quote_bgcolor3,
890 NULL);
891 } else {
892 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
893 "foreground-gdk", &quote_color1,
894 NULL);
895 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
896 "foreground-gdk", &quote_color2,
897 NULL);
898 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
899 "foreground-gdk", &quote_color3,
900 NULL);
903 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
904 "foreground-gdk", &signature_color,
905 NULL);
907 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
908 "foreground-gdk", &uri_color,
909 NULL);
910 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
911 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
914 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
915 GList *attach_files)
917 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
920 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
922 return compose_generic_new(account, mailto, item, NULL, NULL);
925 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
927 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
930 #define SCROLL_TO_CURSOR(compose) { \
931 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
932 gtk_text_view_get_buffer( \
933 GTK_TEXT_VIEW(compose->text))); \
934 gtk_text_view_scroll_mark_onscreen( \
935 GTK_TEXT_VIEW(compose->text), \
936 cmark); \
939 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
941 GtkEditable *entry;
942 if (folderidentifier) {
943 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
944 prefs_common.compose_save_to_history = add_history(
945 prefs_common.compose_save_to_history, folderidentifier);
946 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
947 prefs_common.compose_save_to_history);
950 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
951 if (folderidentifier)
952 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
953 else
954 gtk_entry_set_text(GTK_ENTRY(entry), "");
957 static gchar *compose_get_save_to(Compose *compose)
959 GtkEditable *entry;
960 gchar *result = NULL;
961 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
962 result = gtk_editable_get_chars(entry, 0, -1);
964 if (result) {
965 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
966 prefs_common.compose_save_to_history = add_history(
967 prefs_common.compose_save_to_history, result);
968 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
969 prefs_common.compose_save_to_history);
971 return result;
974 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
975 GList *attach_files, GList *listAddress )
977 Compose *compose;
978 GtkTextView *textview;
979 GtkTextBuffer *textbuf;
980 GtkTextIter iter;
981 const gchar *subject_format = NULL;
982 const gchar *body_format = NULL;
983 gchar *mailto_from = NULL;
984 PrefsAccount *mailto_account = NULL;
985 MsgInfo* dummyinfo = NULL;
986 gint cursor_pos = -1;
987 MailField mfield = NO_FIELD_PRESENT;
988 gchar* buf;
989 GtkTextMark *mark;
991 /* check if mailto defines a from */
992 if (mailto && *mailto != '\0') {
993 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
994 /* mailto defines a from, check if we can get account prefs from it,
995 if not, the account prefs will be guessed using other ways, but we'll keep
996 the from anyway */
997 if (mailto_from) {
998 mailto_account = account_find_from_address(mailto_from, TRUE);
999 if (mailto_account == NULL) {
1000 gchar *tmp_from;
1001 Xstrdup_a(tmp_from, mailto_from, return NULL);
1002 extract_address(tmp_from);
1003 mailto_account = account_find_from_address(tmp_from, TRUE);
1006 if (mailto_account)
1007 account = mailto_account;
1010 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1011 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1012 account = account_find_from_id(item->prefs->default_account);
1014 /* if no account prefs set, fallback to the current one */
1015 if (!account) account = cur_account;
1016 cm_return_val_if_fail(account != NULL, NULL);
1018 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1019 compose_apply_folder_privacy_settings(compose, item);
1021 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1022 (account->default_encrypt || account->default_sign))
1023 COMPOSE_PRIVACY_WARNING();
1025 /* override from name if mailto asked for it */
1026 if (mailto_from) {
1027 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1028 g_free(mailto_from);
1029 } else
1030 /* override from name according to folder properties */
1031 if (item && item->prefs &&
1032 item->prefs->compose_with_format &&
1033 item->prefs->compose_override_from_format &&
1034 *item->prefs->compose_override_from_format != '\0') {
1036 gchar *tmp = NULL;
1037 gchar *buf = NULL;
1039 dummyinfo = compose_msginfo_new_from_compose(compose);
1041 /* decode \-escape sequences in the internal representation of the quote format */
1042 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1043 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1045 #ifdef USE_ENCHANT
1046 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1047 compose->gtkaspell);
1048 #else
1049 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1050 #endif
1051 quote_fmt_scan_string(tmp);
1052 quote_fmt_parse();
1054 buf = quote_fmt_get_buffer();
1055 if (buf == NULL)
1056 alertpanel_error(_("New message From format error."));
1057 else
1058 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1059 quote_fmt_reset_vartable();
1060 quote_fmtlex_destroy();
1062 g_free(tmp);
1065 compose->replyinfo = NULL;
1066 compose->fwdinfo = NULL;
1068 textview = GTK_TEXT_VIEW(compose->text);
1069 textbuf = gtk_text_view_get_buffer(textview);
1070 compose_create_tags(textview, compose);
1072 undo_block(compose->undostruct);
1073 #ifdef USE_ENCHANT
1074 compose_set_dictionaries_from_folder_prefs(compose, item);
1075 #endif
1077 if (account->auto_sig)
1078 compose_insert_sig(compose, FALSE);
1079 gtk_text_buffer_get_start_iter(textbuf, &iter);
1080 gtk_text_buffer_place_cursor(textbuf, &iter);
1082 if (account->protocol != A_NNTP) {
1083 if (mailto && *mailto != '\0') {
1084 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1086 } else {
1087 compose_set_folder_prefs(compose, item, TRUE);
1089 if (item && item->ret_rcpt) {
1090 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1092 } else {
1093 if (mailto && *mailto != '\0') {
1094 if (!strchr(mailto, '@'))
1095 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1096 else
1097 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1098 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1099 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1100 mfield = TO_FIELD_PRESENT;
1103 * CLAWS: just don't allow return receipt request, even if the user
1104 * may want to send an email. simple but foolproof.
1106 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1108 compose_add_field_list( compose, listAddress );
1110 if (item && item->prefs && item->prefs->compose_with_format) {
1111 subject_format = item->prefs->compose_subject_format;
1112 body_format = item->prefs->compose_body_format;
1113 } else if (account->compose_with_format) {
1114 subject_format = account->compose_subject_format;
1115 body_format = account->compose_body_format;
1116 } else if (prefs_common.compose_with_format) {
1117 subject_format = prefs_common.compose_subject_format;
1118 body_format = prefs_common.compose_body_format;
1121 if (subject_format || body_format) {
1123 if ( subject_format
1124 && *subject_format != '\0' )
1126 gchar *subject = NULL;
1127 gchar *tmp = NULL;
1128 gchar *buf = NULL;
1130 if (!dummyinfo)
1131 dummyinfo = compose_msginfo_new_from_compose(compose);
1133 /* decode \-escape sequences in the internal representation of the quote format */
1134 tmp = g_malloc(strlen(subject_format)+1);
1135 pref_get_unescaped_pref(tmp, subject_format);
1137 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1138 #ifdef USE_ENCHANT
1139 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1140 compose->gtkaspell);
1141 #else
1142 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1143 #endif
1144 quote_fmt_scan_string(tmp);
1145 quote_fmt_parse();
1147 buf = quote_fmt_get_buffer();
1148 if (buf == NULL)
1149 alertpanel_error(_("New message subject format error."));
1150 else
1151 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1152 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1153 quote_fmt_reset_vartable();
1154 quote_fmtlex_destroy();
1156 g_free(subject);
1157 g_free(tmp);
1158 mfield = SUBJECT_FIELD_PRESENT;
1161 if ( body_format
1162 && *body_format != '\0' )
1164 GtkTextView *text;
1165 GtkTextBuffer *buffer;
1166 GtkTextIter start, end;
1167 gchar *tmp = NULL;
1169 if (!dummyinfo)
1170 dummyinfo = compose_msginfo_new_from_compose(compose);
1172 text = GTK_TEXT_VIEW(compose->text);
1173 buffer = gtk_text_view_get_buffer(text);
1174 gtk_text_buffer_get_start_iter(buffer, &start);
1175 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1176 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1178 compose_quote_fmt(compose, dummyinfo,
1179 body_format,
1180 NULL, tmp, FALSE, TRUE,
1181 _("The body of the \"New message\" template has an error at line %d."));
1182 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1183 quote_fmt_reset_vartable();
1185 g_free(tmp);
1186 #ifdef USE_ENCHANT
1187 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1188 gtkaspell_highlight_all(compose->gtkaspell);
1189 #endif
1190 mfield = BODY_FIELD_PRESENT;
1194 procmsg_msginfo_free( &dummyinfo );
1196 if (attach_files) {
1197 GList *curr;
1198 AttachInfo *ainfo;
1200 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1201 ainfo = (AttachInfo *) curr->data;
1202 if (ainfo->insert)
1203 compose_insert_file(compose, ainfo->file);
1204 else
1205 compose_attach_append(compose, ainfo->file, ainfo->file,
1206 ainfo->content_type, ainfo->charset);
1210 compose_show_first_last_header(compose, TRUE);
1212 /* Set save folder */
1213 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1214 gchar *folderidentifier;
1216 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1217 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1218 folderidentifier = folder_item_get_identifier(item);
1219 compose_set_save_to(compose, folderidentifier);
1220 g_free(folderidentifier);
1223 /* Place cursor according to provided input (mfield) */
1224 switch (mfield) {
1225 case NO_FIELD_PRESENT:
1226 if (compose->header_last)
1227 gtk_widget_grab_focus(compose->header_last->entry);
1228 break;
1229 case TO_FIELD_PRESENT:
1230 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1231 if (buf) {
1232 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1233 g_free(buf);
1235 gtk_widget_grab_focus(compose->subject_entry);
1236 break;
1237 case SUBJECT_FIELD_PRESENT:
1238 textview = GTK_TEXT_VIEW(compose->text);
1239 if (!textview)
1240 break;
1241 textbuf = gtk_text_view_get_buffer(textview);
1242 if (!textbuf)
1243 break;
1244 mark = gtk_text_buffer_get_insert(textbuf);
1245 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1246 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1248 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1249 * only defers where it comes to the variable body
1250 * is not null. If no body is present compose->text
1251 * will be null in which case you cannot place the
1252 * cursor inside the component so. An empty component
1253 * is therefore created before placing the cursor
1255 case BODY_FIELD_PRESENT:
1256 cursor_pos = quote_fmt_get_cursor_pos();
1257 if (cursor_pos == -1)
1258 gtk_widget_grab_focus(compose->header_last->entry);
1259 else
1260 gtk_widget_grab_focus(compose->text);
1261 break;
1264 undo_unblock(compose->undostruct);
1266 if (prefs_common.auto_exteditor)
1267 compose_exec_ext_editor(compose);
1269 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1271 SCROLL_TO_CURSOR(compose);
1273 compose->modified = FALSE;
1274 compose_set_title(compose);
1276 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1278 return compose;
1281 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1282 gboolean override_pref, const gchar *system)
1284 const gchar *privacy = NULL;
1286 cm_return_if_fail(compose != NULL);
1287 cm_return_if_fail(account != NULL);
1289 if (privacy_system_can_encrypt(compose->privacy_system) == FALSE ||
1290 (override_pref == FALSE && account->default_encrypt_reply == FALSE))
1291 return;
1293 if (account->default_privacy_system && strlen(account->default_privacy_system))
1294 privacy = account->default_privacy_system;
1295 else if (system)
1296 privacy = system;
1297 else {
1298 GSList *privacy_avail = privacy_get_system_ids();
1299 if (privacy_avail && g_slist_length(privacy_avail)) {
1300 privacy = (gchar *)(privacy_avail->data);
1302 g_slist_free_full(privacy_avail, g_free);
1304 if (privacy != NULL) {
1305 if (system) {
1306 g_free(compose->privacy_system);
1307 compose->privacy_system = NULL;
1308 g_free(compose->encdata);
1309 compose->encdata = NULL;
1311 if (compose->privacy_system == NULL)
1312 compose->privacy_system = g_strdup(privacy);
1313 else if (*(compose->privacy_system) == '\0') {
1314 g_free(compose->privacy_system);
1315 g_free(compose->encdata);
1316 compose->encdata = NULL;
1317 compose->privacy_system = g_strdup(privacy);
1319 compose_update_privacy_system_menu_item(compose, FALSE);
1320 compose_use_encryption(compose, TRUE);
1324 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1326 const gchar *privacy = NULL;
1327 if (privacy_system_can_sign(compose->privacy_system) == FALSE)
1328 return;
1330 if (account->default_privacy_system && strlen(account->default_privacy_system))
1331 privacy = account->default_privacy_system;
1332 else if (system)
1333 privacy = system;
1334 else {
1335 GSList *privacy_avail = privacy_get_system_ids();
1336 if (privacy_avail && g_slist_length(privacy_avail)) {
1337 privacy = (gchar *)(privacy_avail->data);
1341 if (privacy != NULL) {
1342 if (system) {
1343 g_free(compose->privacy_system);
1344 compose->privacy_system = NULL;
1345 g_free(compose->encdata);
1346 compose->encdata = NULL;
1348 if (compose->privacy_system == NULL)
1349 compose->privacy_system = g_strdup(privacy);
1350 compose_update_privacy_system_menu_item(compose, FALSE);
1351 compose_use_signing(compose, TRUE);
1355 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1357 MsgInfo *msginfo;
1358 guint list_len;
1359 Compose *compose = NULL;
1361 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1363 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1364 cm_return_val_if_fail(msginfo != NULL, NULL);
1366 list_len = g_slist_length(msginfo_list);
1368 switch (mode) {
1369 case COMPOSE_REPLY:
1370 case COMPOSE_REPLY_TO_ADDRESS:
1371 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1372 FALSE, prefs_common.default_reply_list, FALSE, body);
1373 break;
1374 case COMPOSE_REPLY_WITH_QUOTE:
1375 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1376 FALSE, prefs_common.default_reply_list, FALSE, body);
1377 break;
1378 case COMPOSE_REPLY_WITHOUT_QUOTE:
1379 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1380 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1381 break;
1382 case COMPOSE_REPLY_TO_SENDER:
1383 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1384 FALSE, FALSE, TRUE, body);
1385 break;
1386 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1387 compose = compose_followup_and_reply_to(msginfo,
1388 COMPOSE_QUOTE_CHECK,
1389 FALSE, FALSE, body);
1390 break;
1391 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1392 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1393 FALSE, FALSE, TRUE, body);
1394 break;
1395 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1396 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1397 FALSE, FALSE, TRUE, NULL);
1398 break;
1399 case COMPOSE_REPLY_TO_ALL:
1400 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1401 TRUE, FALSE, FALSE, body);
1402 break;
1403 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1404 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1405 TRUE, FALSE, FALSE, body);
1406 break;
1407 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1408 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1409 TRUE, FALSE, FALSE, NULL);
1410 break;
1411 case COMPOSE_REPLY_TO_LIST:
1412 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1413 FALSE, TRUE, FALSE, body);
1414 break;
1415 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1416 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1417 FALSE, TRUE, FALSE, body);
1418 break;
1419 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1420 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1421 FALSE, TRUE, FALSE, NULL);
1422 break;
1423 case COMPOSE_FORWARD:
1424 if (prefs_common.forward_as_attachment) {
1425 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1426 return compose;
1427 } else {
1428 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1429 return compose;
1431 break;
1432 case COMPOSE_FORWARD_INLINE:
1433 /* check if we reply to more than one Message */
1434 if (list_len == 1) {
1435 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1436 break;
1438 /* more messages FALL THROUGH */
1439 case COMPOSE_FORWARD_AS_ATTACH:
1440 compose = compose_forward_multiple(NULL, msginfo_list);
1441 break;
1442 case COMPOSE_REDIRECT:
1443 compose = compose_redirect(NULL, msginfo, FALSE);
1444 break;
1445 default:
1446 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1449 if (compose == NULL) {
1450 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1451 return NULL;
1454 compose->rmode = mode;
1455 switch (compose->rmode) {
1456 case COMPOSE_REPLY:
1457 case COMPOSE_REPLY_WITH_QUOTE:
1458 case COMPOSE_REPLY_WITHOUT_QUOTE:
1459 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1460 debug_print("reply mode Normal\n");
1461 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1462 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1463 break;
1464 case COMPOSE_REPLY_TO_SENDER:
1465 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1466 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1467 debug_print("reply mode Sender\n");
1468 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1469 break;
1470 case COMPOSE_REPLY_TO_ALL:
1471 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1472 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1473 debug_print("reply mode All\n");
1474 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1475 break;
1476 case COMPOSE_REPLY_TO_LIST:
1477 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1478 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1479 debug_print("reply mode List\n");
1480 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1481 break;
1482 case COMPOSE_REPLY_TO_ADDRESS:
1483 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1484 break;
1485 default:
1486 break;
1488 return compose;
1491 static Compose *compose_reply(MsgInfo *msginfo,
1492 ComposeQuoteMode quote_mode,
1493 gboolean to_all,
1494 gboolean to_ml,
1495 gboolean to_sender,
1496 const gchar *body)
1498 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1499 to_sender, FALSE, body);
1502 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1503 ComposeQuoteMode quote_mode,
1504 gboolean to_all,
1505 gboolean to_sender,
1506 const gchar *body)
1508 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1509 to_sender, TRUE, body);
1512 static void compose_extract_original_charset(Compose *compose)
1514 MsgInfo *info = NULL;
1515 if (compose->replyinfo) {
1516 info = compose->replyinfo;
1517 } else if (compose->fwdinfo) {
1518 info = compose->fwdinfo;
1519 } else if (compose->targetinfo) {
1520 info = compose->targetinfo;
1522 if (info) {
1523 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1524 MimeInfo *partinfo = mimeinfo;
1525 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1526 partinfo = procmime_mimeinfo_next(partinfo);
1527 if (partinfo) {
1528 compose->orig_charset =
1529 g_strdup(procmime_mimeinfo_get_parameter(
1530 partinfo, "charset"));
1532 procmime_mimeinfo_free_all(&mimeinfo);
1536 #define SIGNAL_BLOCK(buffer) { \
1537 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1538 G_CALLBACK(compose_changed_cb), \
1539 compose); \
1540 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1541 G_CALLBACK(text_inserted), \
1542 compose); \
1545 #define SIGNAL_UNBLOCK(buffer) { \
1546 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1547 G_CALLBACK(compose_changed_cb), \
1548 compose); \
1549 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1550 G_CALLBACK(text_inserted), \
1551 compose); \
1554 static Compose *compose_generic_reply(MsgInfo *msginfo,
1555 ComposeQuoteMode quote_mode,
1556 gboolean to_all, gboolean to_ml,
1557 gboolean to_sender,
1558 gboolean followup_and_reply_to,
1559 const gchar *body)
1561 Compose *compose;
1562 PrefsAccount *account = NULL;
1563 GtkTextView *textview;
1564 GtkTextBuffer *textbuf;
1565 gboolean quote = FALSE;
1566 const gchar *qmark = NULL;
1567 const gchar *body_fmt = NULL;
1568 gchar *s_system = NULL;
1569 START_TIMING("");
1570 cm_return_val_if_fail(msginfo != NULL, NULL);
1571 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1573 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1575 cm_return_val_if_fail(account != NULL, NULL);
1577 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1578 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1580 compose->updating = TRUE;
1582 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1583 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1585 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1586 if (!compose->replyinfo)
1587 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1589 compose_extract_original_charset(compose);
1591 if (msginfo->folder && msginfo->folder->ret_rcpt)
1592 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1594 /* Set save folder */
1595 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1596 gchar *folderidentifier;
1598 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1599 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1600 folderidentifier = folder_item_get_identifier(msginfo->folder);
1601 compose_set_save_to(compose, folderidentifier);
1602 g_free(folderidentifier);
1605 if (compose_parse_header(compose, msginfo) < 0) {
1606 compose->updating = FALSE;
1607 compose_destroy(compose);
1608 return NULL;
1611 /* override from name according to folder properties */
1612 if (msginfo->folder && msginfo->folder->prefs &&
1613 msginfo->folder->prefs->reply_with_format &&
1614 msginfo->folder->prefs->reply_override_from_format &&
1615 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1617 gchar *tmp = NULL;
1618 gchar *buf = NULL;
1620 /* decode \-escape sequences in the internal representation of the quote format */
1621 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1622 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1624 #ifdef USE_ENCHANT
1625 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1626 compose->gtkaspell);
1627 #else
1628 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1629 #endif
1630 quote_fmt_scan_string(tmp);
1631 quote_fmt_parse();
1633 buf = quote_fmt_get_buffer();
1634 if (buf == NULL)
1635 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1636 else
1637 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1638 quote_fmt_reset_vartable();
1639 quote_fmtlex_destroy();
1641 g_free(tmp);
1644 textview = (GTK_TEXT_VIEW(compose->text));
1645 textbuf = gtk_text_view_get_buffer(textview);
1646 compose_create_tags(textview, compose);
1648 undo_block(compose->undostruct);
1649 #ifdef USE_ENCHANT
1650 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1651 gtkaspell_block_check(compose->gtkaspell);
1652 #endif
1654 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1655 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1656 /* use the reply format of folder (if enabled), or the account's one
1657 (if enabled) or fallback to the global reply format, which is always
1658 enabled (even if empty), and use the relevant quotemark */
1659 quote = TRUE;
1660 if (msginfo->folder && msginfo->folder->prefs &&
1661 msginfo->folder->prefs->reply_with_format) {
1662 qmark = msginfo->folder->prefs->reply_quotemark;
1663 body_fmt = msginfo->folder->prefs->reply_body_format;
1665 } else if (account->reply_with_format) {
1666 qmark = account->reply_quotemark;
1667 body_fmt = account->reply_body_format;
1669 } else {
1670 qmark = prefs_common.quotemark;
1671 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1672 body_fmt = gettext(prefs_common.quotefmt);
1673 else
1674 body_fmt = "";
1678 if (quote) {
1679 /* empty quotemark is not allowed */
1680 if (qmark == NULL || *qmark == '\0')
1681 qmark = "> ";
1682 compose_quote_fmt(compose, compose->replyinfo,
1683 body_fmt, qmark, body, FALSE, TRUE,
1684 _("The body of the \"Reply\" template has an error at line %d."));
1685 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1686 quote_fmt_reset_vartable();
1689 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1690 compose_force_encryption(compose, account, FALSE, s_system);
1693 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1694 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1695 compose_force_signing(compose, account, s_system);
1697 g_free(s_system);
1699 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1700 ((account->default_encrypt || account->default_sign) ||
1701 (account->default_encrypt_reply && MSG_IS_ENCRYPTED(compose->replyinfo->flags)) ||
1702 (account->default_sign_reply && MSG_IS_SIGNED(compose->replyinfo->flags))))
1703 COMPOSE_PRIVACY_WARNING();
1705 SIGNAL_BLOCK(textbuf);
1707 if (account->auto_sig)
1708 compose_insert_sig(compose, FALSE);
1710 compose_wrap_all(compose);
1712 #ifdef USE_ENCHANT
1713 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1714 gtkaspell_highlight_all(compose->gtkaspell);
1715 gtkaspell_unblock_check(compose->gtkaspell);
1716 #endif
1717 SIGNAL_UNBLOCK(textbuf);
1719 gtk_widget_grab_focus(compose->text);
1721 undo_unblock(compose->undostruct);
1723 if (prefs_common.auto_exteditor)
1724 compose_exec_ext_editor(compose);
1726 compose->modified = FALSE;
1727 compose_set_title(compose);
1729 compose->updating = FALSE;
1730 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1731 SCROLL_TO_CURSOR(compose);
1733 if (compose->deferred_destroy) {
1734 compose_destroy(compose);
1735 return NULL;
1737 END_TIMING();
1739 return compose;
1742 #define INSERT_FW_HEADER(var, hdr) \
1743 if (msginfo->var && *msginfo->var) { \
1744 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1745 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1746 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1749 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1750 gboolean as_attach, const gchar *body,
1751 gboolean no_extedit,
1752 gboolean batch)
1754 Compose *compose;
1755 GtkTextView *textview;
1756 GtkTextBuffer *textbuf;
1757 gint cursor_pos = -1;
1758 ComposeMode mode;
1760 cm_return_val_if_fail(msginfo != NULL, NULL);
1761 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1763 if (!account && !(account = compose_find_account(msginfo)))
1764 account = cur_account;
1766 if (!prefs_common.forward_as_attachment)
1767 mode = COMPOSE_FORWARD_INLINE;
1768 else
1769 mode = COMPOSE_FORWARD;
1770 compose = compose_create(account, msginfo->folder, mode, batch);
1771 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1773 compose->updating = TRUE;
1774 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1775 if (!compose->fwdinfo)
1776 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1778 compose_extract_original_charset(compose);
1780 if (msginfo->subject && *msginfo->subject) {
1781 gchar *buf, *buf2, *p;
1783 buf = p = g_strdup(msginfo->subject);
1784 p += subject_get_prefix_length(p);
1785 memmove(buf, p, strlen(p) + 1);
1787 buf2 = g_strdup_printf("Fw: %s", buf);
1788 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1790 g_free(buf);
1791 g_free(buf2);
1794 /* override from name according to folder properties */
1795 if (msginfo->folder && msginfo->folder->prefs &&
1796 msginfo->folder->prefs->forward_with_format &&
1797 msginfo->folder->prefs->forward_override_from_format &&
1798 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1800 gchar *tmp = NULL;
1801 gchar *buf = NULL;
1802 MsgInfo *full_msginfo = NULL;
1804 if (!as_attach)
1805 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1806 if (!full_msginfo)
1807 full_msginfo = procmsg_msginfo_copy(msginfo);
1809 /* decode \-escape sequences in the internal representation of the quote format */
1810 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1811 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1813 #ifdef USE_ENCHANT
1814 gtkaspell_block_check(compose->gtkaspell);
1815 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1816 compose->gtkaspell);
1817 #else
1818 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1819 #endif
1820 quote_fmt_scan_string(tmp);
1821 quote_fmt_parse();
1823 buf = quote_fmt_get_buffer();
1824 if (buf == NULL)
1825 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1826 else
1827 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1828 quote_fmt_reset_vartable();
1829 quote_fmtlex_destroy();
1831 g_free(tmp);
1832 procmsg_msginfo_free(&full_msginfo);
1835 textview = GTK_TEXT_VIEW(compose->text);
1836 textbuf = gtk_text_view_get_buffer(textview);
1837 compose_create_tags(textview, compose);
1839 undo_block(compose->undostruct);
1840 if (as_attach) {
1841 gchar *msgfile;
1843 msgfile = procmsg_get_message_file(msginfo);
1844 if (!is_file_exist(msgfile))
1845 g_warning("%s: file does not exist", msgfile);
1846 else
1847 compose_attach_append(compose, msgfile, msgfile,
1848 "message/rfc822", NULL);
1850 g_free(msgfile);
1851 } else {
1852 const gchar *qmark = NULL;
1853 const gchar *body_fmt = NULL;
1854 MsgInfo *full_msginfo;
1856 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1857 if (!full_msginfo)
1858 full_msginfo = procmsg_msginfo_copy(msginfo);
1860 /* use the forward format of folder (if enabled), or the account's one
1861 (if enabled) or fallback to the global forward format, which is always
1862 enabled (even if empty), and use the relevant quotemark */
1863 if (msginfo->folder && msginfo->folder->prefs &&
1864 msginfo->folder->prefs->forward_with_format) {
1865 qmark = msginfo->folder->prefs->forward_quotemark;
1866 body_fmt = msginfo->folder->prefs->forward_body_format;
1868 } else if (account->forward_with_format) {
1869 qmark = account->forward_quotemark;
1870 body_fmt = account->forward_body_format;
1872 } else {
1873 qmark = prefs_common.fw_quotemark;
1874 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1875 body_fmt = gettext(prefs_common.fw_quotefmt);
1876 else
1877 body_fmt = "";
1880 /* empty quotemark is not allowed */
1881 if (qmark == NULL || *qmark == '\0')
1882 qmark = "> ";
1884 compose_quote_fmt(compose, full_msginfo,
1885 body_fmt, qmark, body, FALSE, TRUE,
1886 _("The body of the \"Forward\" template has an error at line %d."));
1887 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1888 quote_fmt_reset_vartable();
1889 compose_attach_parts(compose, msginfo);
1891 procmsg_msginfo_free(&full_msginfo);
1894 SIGNAL_BLOCK(textbuf);
1896 if (account->auto_sig)
1897 compose_insert_sig(compose, FALSE);
1899 compose_wrap_all(compose);
1901 #ifdef USE_ENCHANT
1902 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1903 gtkaspell_highlight_all(compose->gtkaspell);
1904 gtkaspell_unblock_check(compose->gtkaspell);
1905 #endif
1906 SIGNAL_UNBLOCK(textbuf);
1908 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1909 (account->default_encrypt || account->default_sign))
1910 COMPOSE_PRIVACY_WARNING();
1912 cursor_pos = quote_fmt_get_cursor_pos();
1913 if (cursor_pos == -1)
1914 gtk_widget_grab_focus(compose->header_last->entry);
1915 else
1916 gtk_widget_grab_focus(compose->text);
1918 if (!no_extedit && prefs_common.auto_exteditor)
1919 compose_exec_ext_editor(compose);
1921 /*save folder*/
1922 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1923 gchar *folderidentifier;
1925 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1926 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1927 folderidentifier = folder_item_get_identifier(msginfo->folder);
1928 compose_set_save_to(compose, folderidentifier);
1929 g_free(folderidentifier);
1932 undo_unblock(compose->undostruct);
1934 compose->modified = FALSE;
1935 compose_set_title(compose);
1937 compose->updating = FALSE;
1938 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1939 SCROLL_TO_CURSOR(compose);
1941 if (compose->deferred_destroy) {
1942 compose_destroy(compose);
1943 return NULL;
1946 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1948 return compose;
1951 #undef INSERT_FW_HEADER
1953 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1955 Compose *compose;
1956 GtkTextView *textview;
1957 GtkTextBuffer *textbuf;
1958 GtkTextIter iter;
1959 GSList *msginfo;
1960 gchar *msgfile;
1961 gboolean single_mail = TRUE;
1963 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1965 if (g_slist_length(msginfo_list) > 1)
1966 single_mail = FALSE;
1968 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1969 if (((MsgInfo *)msginfo->data)->folder == NULL)
1970 return NULL;
1972 /* guess account from first selected message */
1973 if (!account &&
1974 !(account = compose_find_account(msginfo_list->data)))
1975 account = cur_account;
1977 cm_return_val_if_fail(account != NULL, NULL);
1979 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1980 if (msginfo->data) {
1981 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1982 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1986 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1987 g_warning("no msginfo_list");
1988 return NULL;
1991 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1992 compose_apply_folder_privacy_settings(compose, ((MsgInfo *)msginfo_list->data)->folder);
1993 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1994 (account->default_encrypt || account->default_sign))
1995 COMPOSE_PRIVACY_WARNING();
1997 compose->updating = TRUE;
1999 /* override from name according to folder properties */
2000 if (msginfo_list->data) {
2001 MsgInfo *msginfo = msginfo_list->data;
2003 if (msginfo->folder && msginfo->folder->prefs &&
2004 msginfo->folder->prefs->forward_with_format &&
2005 msginfo->folder->prefs->forward_override_from_format &&
2006 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2008 gchar *tmp = NULL;
2009 gchar *buf = NULL;
2011 /* decode \-escape sequences in the internal representation of the quote format */
2012 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2013 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2015 #ifdef USE_ENCHANT
2016 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2017 compose->gtkaspell);
2018 #else
2019 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2020 #endif
2021 quote_fmt_scan_string(tmp);
2022 quote_fmt_parse();
2024 buf = quote_fmt_get_buffer();
2025 if (buf == NULL)
2026 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2027 else
2028 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2029 quote_fmt_reset_vartable();
2030 quote_fmtlex_destroy();
2032 g_free(tmp);
2036 textview = GTK_TEXT_VIEW(compose->text);
2037 textbuf = gtk_text_view_get_buffer(textview);
2038 compose_create_tags(textview, compose);
2040 undo_block(compose->undostruct);
2041 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2042 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2044 if (!is_file_exist(msgfile))
2045 g_warning("%s: file does not exist", msgfile);
2046 else
2047 compose_attach_append(compose, msgfile, msgfile,
2048 "message/rfc822", NULL);
2049 g_free(msgfile);
2052 if (single_mail) {
2053 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2054 if (info->subject && *info->subject) {
2055 gchar *buf, *buf2, *p;
2057 buf = p = g_strdup(info->subject);
2058 p += subject_get_prefix_length(p);
2059 memmove(buf, p, strlen(p) + 1);
2061 buf2 = g_strdup_printf("Fw: %s", buf);
2062 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2064 g_free(buf);
2065 g_free(buf2);
2067 } else {
2068 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2069 _("Fw: multiple emails"));
2072 SIGNAL_BLOCK(textbuf);
2074 if (account->auto_sig)
2075 compose_insert_sig(compose, FALSE);
2077 compose_wrap_all(compose);
2079 SIGNAL_UNBLOCK(textbuf);
2081 gtk_text_buffer_get_start_iter(textbuf, &iter);
2082 gtk_text_buffer_place_cursor(textbuf, &iter);
2084 if (prefs_common.auto_exteditor)
2085 compose_exec_ext_editor(compose);
2087 gtk_widget_grab_focus(compose->header_last->entry);
2088 undo_unblock(compose->undostruct);
2089 compose->modified = FALSE;
2090 compose_set_title(compose);
2092 compose->updating = FALSE;
2093 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2094 SCROLL_TO_CURSOR(compose);
2096 if (compose->deferred_destroy) {
2097 compose_destroy(compose);
2098 return NULL;
2101 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2103 return compose;
2106 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2108 GtkTextIter start = *iter;
2109 GtkTextIter end_iter;
2110 int start_pos = gtk_text_iter_get_offset(&start);
2111 gchar *str = NULL;
2112 if (!compose->account->sig_sep)
2113 return FALSE;
2115 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2116 start_pos+strlen(compose->account->sig_sep));
2118 /* check sig separator */
2119 str = gtk_text_iter_get_text(&start, &end_iter);
2120 if (!strcmp(str, compose->account->sig_sep)) {
2121 gchar *tmp = NULL;
2122 /* check end of line (\n) */
2123 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2124 start_pos+strlen(compose->account->sig_sep));
2125 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2126 start_pos+strlen(compose->account->sig_sep)+1);
2127 tmp = gtk_text_iter_get_text(&start, &end_iter);
2128 if (!strcmp(tmp,"\n")) {
2129 g_free(str);
2130 g_free(tmp);
2131 return TRUE;
2133 g_free(tmp);
2135 g_free(str);
2137 return FALSE;
2140 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2142 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2143 Compose *compose = (Compose *)data;
2144 FolderItem *old_item = NULL;
2145 FolderItem *new_item = NULL;
2146 gchar *old_id, *new_id;
2148 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2149 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2150 return FALSE;
2152 old_item = hookdata->item;
2153 new_item = hookdata->item2;
2155 old_id = folder_item_get_identifier(old_item);
2156 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2158 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2159 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2160 compose->targetinfo->folder = new_item;
2163 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2164 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2165 compose->replyinfo->folder = new_item;
2168 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2169 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2170 compose->fwdinfo->folder = new_item;
2173 g_free(old_id);
2174 g_free(new_id);
2175 return FALSE;
2178 static void compose_colorize_signature(Compose *compose)
2180 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2181 GtkTextIter iter;
2182 GtkTextIter end_iter;
2183 gtk_text_buffer_get_start_iter(buffer, &iter);
2184 while (gtk_text_iter_forward_line(&iter))
2185 if (compose_is_sig_separator(compose, buffer, &iter)) {
2186 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2187 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2191 #define BLOCK_WRAP() { \
2192 prev_autowrap = compose->autowrap; \
2193 buffer = gtk_text_view_get_buffer( \
2194 GTK_TEXT_VIEW(compose->text)); \
2195 compose->autowrap = FALSE; \
2197 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2198 G_CALLBACK(compose_changed_cb), \
2199 compose); \
2200 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2201 G_CALLBACK(text_inserted), \
2202 compose); \
2204 #define UNBLOCK_WRAP() { \
2205 compose->autowrap = prev_autowrap; \
2206 if (compose->autowrap) { \
2207 gint old = compose->draft_timeout_tag; \
2208 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2209 compose_wrap_all(compose); \
2210 compose->draft_timeout_tag = old; \
2213 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2214 G_CALLBACK(compose_changed_cb), \
2215 compose); \
2216 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2217 G_CALLBACK(text_inserted), \
2218 compose); \
2221 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2223 Compose *compose = NULL;
2224 PrefsAccount *account = NULL;
2225 GtkTextView *textview;
2226 GtkTextBuffer *textbuf;
2227 GtkTextMark *mark;
2228 GtkTextIter iter;
2229 FILE *fp;
2230 gboolean use_signing = FALSE;
2231 gboolean use_encryption = FALSE;
2232 gchar *privacy_system = NULL;
2233 int priority = PRIORITY_NORMAL;
2234 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2235 gboolean autowrap = prefs_common.autowrap;
2236 gboolean autoindent = prefs_common.auto_indent;
2237 HeaderEntry *manual_headers = NULL;
2239 cm_return_val_if_fail(msginfo != NULL, NULL);
2240 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2242 if (compose_put_existing_to_front(msginfo)) {
2243 return NULL;
2246 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2247 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2248 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2249 gchar *queueheader_buf = NULL;
2250 gint id, param;
2252 /* Select Account from queue headers */
2253 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2254 "X-Claws-Account-Id:")) {
2255 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2256 account = account_find_from_id(id);
2257 g_free(queueheader_buf);
2259 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2260 "X-Sylpheed-Account-Id:")) {
2261 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2262 account = account_find_from_id(id);
2263 g_free(queueheader_buf);
2265 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2266 "NAID:")) {
2267 id = atoi(&queueheader_buf[strlen("NAID:")]);
2268 account = account_find_from_id(id);
2269 g_free(queueheader_buf);
2271 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2272 "MAID:")) {
2273 id = atoi(&queueheader_buf[strlen("MAID:")]);
2274 account = account_find_from_id(id);
2275 g_free(queueheader_buf);
2277 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2278 "S:")) {
2279 account = account_find_from_address(queueheader_buf, FALSE);
2280 g_free(queueheader_buf);
2282 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2283 "X-Claws-Sign:")) {
2284 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2285 use_signing = param;
2286 g_free(queueheader_buf);
2288 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2289 "X-Sylpheed-Sign:")) {
2290 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2291 use_signing = param;
2292 g_free(queueheader_buf);
2294 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2295 "X-Claws-Encrypt:")) {
2296 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2297 use_encryption = param;
2298 g_free(queueheader_buf);
2300 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2301 "X-Sylpheed-Encrypt:")) {
2302 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2303 use_encryption = param;
2304 g_free(queueheader_buf);
2306 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2307 "X-Claws-Auto-Wrapping:")) {
2308 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2309 autowrap = param;
2310 g_free(queueheader_buf);
2312 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2313 "X-Claws-Auto-Indent:")) {
2314 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2315 autoindent = param;
2316 g_free(queueheader_buf);
2318 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2319 "X-Claws-Privacy-System:")) {
2320 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2321 g_free(queueheader_buf);
2323 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2324 "X-Sylpheed-Privacy-System:")) {
2325 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2326 g_free(queueheader_buf);
2328 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2329 "X-Priority: ")) {
2330 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2331 priority = param;
2332 g_free(queueheader_buf);
2334 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2335 "RMID:")) {
2336 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2337 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2338 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2339 if (orig_item != NULL) {
2340 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2342 g_strfreev(tokens);
2344 g_free(queueheader_buf);
2346 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2347 "FMID:")) {
2348 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2349 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2350 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2351 if (orig_item != NULL) {
2352 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2354 g_strfreev(tokens);
2356 g_free(queueheader_buf);
2358 /* Get manual headers */
2359 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2360 "X-Claws-Manual-Headers:")) {
2361 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2362 if (listmh && *listmh != '\0') {
2363 debug_print("Got manual headers: %s\n", listmh);
2364 manual_headers = procheader_entries_from_str(listmh);
2365 g_free(listmh);
2367 g_free(queueheader_buf);
2369 } else {
2370 account = msginfo->folder->folder->account;
2373 if (!account && prefs_common.reedit_account_autosel) {
2374 gchar *from = NULL;
2375 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2376 extract_address(from);
2377 account = account_find_from_address(from, FALSE);
2378 g_free(from);
2381 if (!account) {
2382 account = cur_account;
2384 cm_return_val_if_fail(account != NULL, NULL);
2386 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2388 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
2389 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
2390 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2391 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2392 compose->autowrap = autowrap;
2393 compose->replyinfo = replyinfo;
2394 compose->fwdinfo = fwdinfo;
2396 compose->updating = TRUE;
2397 compose->priority = priority;
2399 if (privacy_system != NULL) {
2400 compose->privacy_system = privacy_system;
2401 compose_use_signing(compose, use_signing);
2402 compose_use_encryption(compose, use_encryption);
2403 compose_update_privacy_system_menu_item(compose, FALSE);
2404 } else {
2405 compose_activate_privacy_system(compose, account, FALSE);
2407 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2408 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
2409 (account->default_encrypt || account->default_sign))
2410 COMPOSE_PRIVACY_WARNING();
2412 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2413 compose->targetinfo->tags = g_slist_copy(msginfo->tags);
2415 compose_extract_original_charset(compose);
2417 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2418 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2419 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2420 gchar *queueheader_buf = NULL;
2422 /* Set message save folder */
2423 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2424 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2425 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2426 compose_set_save_to(compose, &queueheader_buf[4]);
2427 g_free(queueheader_buf);
2429 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2430 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2431 if (active) {
2432 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2434 g_free(queueheader_buf);
2438 if (compose_parse_header(compose, msginfo) < 0) {
2439 compose->updating = FALSE;
2440 compose_destroy(compose);
2441 return NULL;
2443 compose_reedit_set_entry(compose, msginfo);
2445 textview = GTK_TEXT_VIEW(compose->text);
2446 textbuf = gtk_text_view_get_buffer(textview);
2447 compose_create_tags(textview, compose);
2449 mark = gtk_text_buffer_get_insert(textbuf);
2450 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2452 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2453 G_CALLBACK(compose_changed_cb),
2454 compose);
2456 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2457 fp = procmime_get_first_encrypted_text_content(msginfo);
2458 if (fp) {
2459 compose_force_encryption(compose, account, TRUE, NULL);
2461 } else {
2462 fp = procmime_get_first_text_content(msginfo);
2464 if (fp == NULL) {
2465 g_warning("Can't get text part");
2468 if (fp != NULL) {
2469 gchar buf[BUFFSIZE];
2470 gboolean prev_autowrap;
2471 GtkTextBuffer *buffer;
2472 BLOCK_WRAP();
2473 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2474 strcrchomp(buf);
2475 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2477 UNBLOCK_WRAP();
2478 claws_fclose(fp);
2481 compose_attach_parts(compose, msginfo);
2483 compose_colorize_signature(compose);
2485 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2486 G_CALLBACK(compose_changed_cb),
2487 compose);
2489 if (manual_headers != NULL) {
2490 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2491 procheader_entries_free(manual_headers);
2492 compose->updating = FALSE;
2493 compose_destroy(compose);
2494 return NULL;
2496 procheader_entries_free(manual_headers);
2499 gtk_widget_grab_focus(compose->text);
2501 if (prefs_common.auto_exteditor) {
2502 compose_exec_ext_editor(compose);
2504 compose->modified = FALSE;
2505 compose_set_title(compose);
2507 compose->updating = FALSE;
2508 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2509 SCROLL_TO_CURSOR(compose);
2511 if (compose->deferred_destroy) {
2512 compose_destroy(compose);
2513 return NULL;
2516 compose->sig_str = account_get_signature_str(compose->account);
2518 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2520 return compose;
2523 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2524 gboolean batch)
2526 Compose *compose;
2527 gchar *filename;
2528 FolderItem *item;
2530 cm_return_val_if_fail(msginfo != NULL, NULL);
2532 if (!account)
2533 account = account_get_reply_account(msginfo,
2534 prefs_common.reply_account_autosel);
2535 cm_return_val_if_fail(account != NULL, NULL);
2537 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2539 compose->updating = TRUE;
2541 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2542 compose->replyinfo = NULL;
2543 compose->fwdinfo = NULL;
2545 compose_show_first_last_header(compose, TRUE);
2547 gtk_widget_grab_focus(compose->header_last->entry);
2549 filename = procmsg_get_message_file(msginfo);
2551 if (filename == NULL) {
2552 compose->updating = FALSE;
2553 compose_destroy(compose);
2555 return NULL;
2558 compose->redirect_filename = filename;
2560 /* Set save folder */
2561 item = msginfo->folder;
2562 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2563 gchar *folderidentifier;
2565 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2566 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2567 folderidentifier = folder_item_get_identifier(item);
2568 compose_set_save_to(compose, folderidentifier);
2569 g_free(folderidentifier);
2572 compose_attach_parts(compose, msginfo);
2574 if (msginfo->subject)
2575 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2576 msginfo->subject);
2577 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2579 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2580 _("The body of the \"Redirect\" template has an error at line %d."));
2581 quote_fmt_reset_vartable();
2582 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2584 compose_colorize_signature(compose);
2587 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2588 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2589 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2591 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2592 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2593 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2594 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2595 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2596 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2597 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2598 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2599 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2601 if (compose->toolbar->draft_btn)
2602 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2603 if (compose->toolbar->insert_btn)
2604 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2605 if (compose->toolbar->attach_btn)
2606 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2607 if (compose->toolbar->sig_btn)
2608 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2609 if (compose->toolbar->exteditor_btn)
2610 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2611 if (compose->toolbar->linewrap_current_btn)
2612 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2613 if (compose->toolbar->linewrap_all_btn)
2614 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2615 if (compose->toolbar->privacy_sign_btn)
2616 gtk_widget_set_sensitive(compose->toolbar->privacy_sign_btn, FALSE);
2617 if (compose->toolbar->privacy_encrypt_btn)
2618 gtk_widget_set_sensitive(compose->toolbar->privacy_encrypt_btn, FALSE);
2620 compose->modified = FALSE;
2621 compose_set_title(compose);
2622 compose->updating = FALSE;
2623 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2624 SCROLL_TO_CURSOR(compose);
2626 if (compose->deferred_destroy) {
2627 compose_destroy(compose);
2628 return NULL;
2631 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2633 return compose;
2636 const GList *compose_get_compose_list(void)
2638 return compose_list;
2641 void compose_entry_append(Compose *compose, const gchar *address,
2642 ComposeEntryType type, ComposePrefType pref_type)
2644 const gchar *header;
2645 gchar *cur, *begin;
2646 gboolean in_quote = FALSE;
2647 if (!address || *address == '\0') return;
2649 switch (type) {
2650 case COMPOSE_CC:
2651 header = N_("Cc:");
2652 break;
2653 case COMPOSE_BCC:
2654 header = N_("Bcc:");
2655 break;
2656 case COMPOSE_REPLYTO:
2657 header = N_("Reply-To:");
2658 break;
2659 case COMPOSE_NEWSGROUPS:
2660 header = N_("Newsgroups:");
2661 break;
2662 case COMPOSE_FOLLOWUPTO:
2663 header = N_( "Followup-To:");
2664 break;
2665 case COMPOSE_INREPLYTO:
2666 header = N_( "In-Reply-To:");
2667 break;
2668 case COMPOSE_TO:
2669 default:
2670 header = N_("To:");
2671 break;
2673 header = prefs_common_translated_header_name(header);
2675 cur = begin = (gchar *)address;
2677 /* we separate the line by commas, but not if we're inside a quoted
2678 * string */
2679 while (*cur != '\0') {
2680 if (*cur == '"')
2681 in_quote = !in_quote;
2682 if (*cur == ',' && !in_quote) {
2683 gchar *tmp = g_strdup(begin);
2684 gchar *o_tmp = tmp;
2685 tmp[cur-begin]='\0';
2686 cur++;
2687 begin = cur;
2688 while (*tmp == ' ' || *tmp == '\t')
2689 tmp++;
2690 compose_add_header_entry(compose, header, tmp, pref_type);
2691 compose_entry_indicate(compose, tmp);
2692 g_free(o_tmp);
2693 continue;
2695 cur++;
2697 if (begin < cur) {
2698 gchar *tmp = g_strdup(begin);
2699 gchar *o_tmp = tmp;
2700 tmp[cur-begin]='\0';
2701 while (*tmp == ' ' || *tmp == '\t')
2702 tmp++;
2703 compose_add_header_entry(compose, header, tmp, pref_type);
2704 compose_entry_indicate(compose, tmp);
2705 g_free(o_tmp);
2709 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2711 GSList *h_list;
2712 GtkEntry *entry;
2714 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2715 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2716 if (gtk_entry_get_text(entry) &&
2717 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2718 gtk_widget_modify_base(
2719 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2720 GTK_STATE_NORMAL, &default_header_bgcolor);
2721 gtk_widget_modify_text(
2722 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2723 GTK_STATE_NORMAL, &default_header_color);
2728 void compose_toolbar_cb(gint action, gpointer data)
2730 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2731 Compose *compose = (Compose*)toolbar_item->parent;
2733 cm_return_if_fail(compose != NULL);
2735 switch(action) {
2736 case A_SEND:
2737 compose_send_cb(NULL, compose);
2738 break;
2739 case A_SEND_LATER:
2740 compose_send_later_cb(NULL, compose);
2741 break;
2742 case A_DRAFT:
2743 compose_draft(compose, COMPOSE_QUIT_EDITING);
2744 break;
2745 case A_INSERT:
2746 compose_insert_file_cb(NULL, compose);
2747 break;
2748 case A_ATTACH:
2749 compose_attach_cb(NULL, compose);
2750 break;
2751 case A_SIG:
2752 compose_insert_sig(compose, FALSE);
2753 break;
2754 case A_REP_SIG:
2755 compose_insert_sig(compose, TRUE);
2756 break;
2757 case A_EXTEDITOR:
2758 compose_ext_editor_cb(NULL, compose);
2759 break;
2760 case A_LINEWRAP_CURRENT:
2761 compose_beautify_paragraph(compose, NULL, TRUE);
2762 break;
2763 case A_LINEWRAP_ALL:
2764 compose_wrap_all_full(compose, TRUE);
2765 break;
2766 case A_ADDRBOOK:
2767 compose_address_cb(NULL, compose);
2768 break;
2769 #ifdef USE_ENCHANT
2770 case A_CHECK_SPELLING:
2771 compose_check_all(NULL, compose);
2772 break;
2773 #endif
2774 case A_PRIVACY_SIGN:
2775 break;
2776 case A_PRIVACY_ENCRYPT:
2777 break;
2778 default:
2779 break;
2783 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2785 gchar *to = NULL;
2786 gchar *cc = NULL;
2787 gchar *bcc = NULL;
2788 gchar *subject = NULL;
2789 gchar *body = NULL;
2790 gchar *temp = NULL;
2791 gsize len = 0;
2792 gchar **attach = NULL;
2793 gchar *inreplyto = NULL;
2794 MailField mfield = NO_FIELD_PRESENT;
2796 /* get mailto parts but skip from */
2797 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2799 if (to) {
2800 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2801 mfield = TO_FIELD_PRESENT;
2803 if (cc)
2804 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2805 if (bcc)
2806 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2807 if (subject) {
2808 if (!g_utf8_validate (subject, -1, NULL)) {
2809 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2810 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2811 g_free(temp);
2812 } else {
2813 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2815 mfield = SUBJECT_FIELD_PRESENT;
2817 if (body) {
2818 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2819 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2820 GtkTextMark *mark;
2821 GtkTextIter iter;
2822 gboolean prev_autowrap = compose->autowrap;
2824 compose->autowrap = FALSE;
2826 mark = gtk_text_buffer_get_insert(buffer);
2827 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2829 if (!g_utf8_validate (body, -1, NULL)) {
2830 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2831 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2832 g_free(temp);
2833 } else {
2834 gtk_text_buffer_insert(buffer, &iter, body, -1);
2836 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2838 compose->autowrap = prev_autowrap;
2839 if (compose->autowrap)
2840 compose_wrap_all(compose);
2841 mfield = BODY_FIELD_PRESENT;
2844 if (attach) {
2845 gint i = 0, att = 0;
2846 gchar *warn_files = NULL;
2847 while (attach[i] != NULL) {
2848 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2849 if (utf8_filename) {
2850 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2851 gchar *tmp = g_strdup_printf("%s%s\n",
2852 warn_files?warn_files:"",
2853 utf8_filename);
2854 g_free(warn_files);
2855 warn_files = tmp;
2856 att++;
2858 g_free(utf8_filename);
2859 } else {
2860 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2862 i++;
2864 if (warn_files) {
2865 alertpanel_notice(ngettext(
2866 "The following file has been attached: \n%s",
2867 "The following files have been attached: \n%s", att), warn_files);
2868 g_free(warn_files);
2871 if (inreplyto)
2872 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2874 g_free(to);
2875 g_free(cc);
2876 g_free(bcc);
2877 g_free(subject);
2878 g_free(body);
2879 g_strfreev(attach);
2880 g_free(inreplyto);
2882 return mfield;
2885 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2887 static HeaderEntry hentry[] = {
2888 {"Reply-To:", NULL, TRUE },
2889 {"Cc:", NULL, TRUE },
2890 {"References:", NULL, FALSE },
2891 {"Bcc:", NULL, TRUE },
2892 {"Newsgroups:", NULL, TRUE },
2893 {"Followup-To:", NULL, TRUE },
2894 {"List-Post:", NULL, FALSE },
2895 {"X-Priority:", NULL, FALSE },
2896 {NULL, NULL, FALSE }
2899 enum
2901 H_REPLY_TO = 0,
2902 H_CC = 1,
2903 H_REFERENCES = 2,
2904 H_BCC = 3,
2905 H_NEWSGROUPS = 4,
2906 H_FOLLOWUP_TO = 5,
2907 H_LIST_POST = 6,
2908 H_X_PRIORITY = 7
2911 FILE *fp;
2913 cm_return_val_if_fail(msginfo != NULL, -1);
2915 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
2916 procheader_get_header_fields(fp, hentry);
2917 claws_fclose(fp);
2919 if (hentry[H_REPLY_TO].body != NULL) {
2920 if (hentry[H_REPLY_TO].body[0] != '\0') {
2921 compose->replyto =
2922 conv_unmime_header(hentry[H_REPLY_TO].body,
2923 NULL, TRUE);
2925 g_free(hentry[H_REPLY_TO].body);
2926 hentry[H_REPLY_TO].body = NULL;
2928 if (hentry[H_CC].body != NULL) {
2929 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2930 g_free(hentry[H_CC].body);
2931 hentry[H_CC].body = NULL;
2933 if (hentry[H_REFERENCES].body != NULL) {
2934 if (compose->mode == COMPOSE_REEDIT)
2935 compose->references = hentry[H_REFERENCES].body;
2936 else {
2937 compose->references = compose_parse_references
2938 (hentry[H_REFERENCES].body, msginfo->msgid);
2939 g_free(hentry[H_REFERENCES].body);
2941 hentry[H_REFERENCES].body = NULL;
2943 if (hentry[H_BCC].body != NULL) {
2944 if (compose->mode == COMPOSE_REEDIT)
2945 compose->bcc =
2946 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2947 g_free(hentry[H_BCC].body);
2948 hentry[H_BCC].body = NULL;
2950 if (hentry[H_NEWSGROUPS].body != NULL) {
2951 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2952 hentry[H_NEWSGROUPS].body = NULL;
2954 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2955 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2956 compose->followup_to =
2957 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2958 NULL, TRUE);
2960 g_free(hentry[H_FOLLOWUP_TO].body);
2961 hentry[H_FOLLOWUP_TO].body = NULL;
2963 if (hentry[H_LIST_POST].body != NULL) {
2964 gchar *to = NULL, *start = NULL;
2966 extract_address(hentry[H_LIST_POST].body);
2967 if (hentry[H_LIST_POST].body[0] != '\0') {
2968 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2970 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2971 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2973 if (to) {
2974 g_free(compose->ml_post);
2975 compose->ml_post = to;
2978 g_free(hentry[H_LIST_POST].body);
2979 hentry[H_LIST_POST].body = NULL;
2982 /* CLAWS - X-Priority */
2983 if (compose->mode == COMPOSE_REEDIT)
2984 if (hentry[H_X_PRIORITY].body != NULL) {
2985 gint priority;
2987 priority = atoi(hentry[H_X_PRIORITY].body);
2988 g_free(hentry[H_X_PRIORITY].body);
2990 hentry[H_X_PRIORITY].body = NULL;
2992 if (priority < PRIORITY_HIGHEST ||
2993 priority > PRIORITY_LOWEST)
2994 priority = PRIORITY_NORMAL;
2996 compose->priority = priority;
2999 if (compose->mode == COMPOSE_REEDIT) {
3000 if (msginfo->inreplyto && *msginfo->inreplyto)
3001 compose->inreplyto = g_strdup(msginfo->inreplyto);
3003 if (msginfo->msgid && *msginfo->msgid &&
3004 compose->folder != NULL &&
3005 compose->folder->stype == F_DRAFT)
3006 compose->msgid = g_strdup(msginfo->msgid);
3007 } else {
3008 if (msginfo->msgid && *msginfo->msgid)
3009 compose->inreplyto = g_strdup(msginfo->msgid);
3011 if (!compose->references) {
3012 if (msginfo->msgid && *msginfo->msgid) {
3013 if (msginfo->inreplyto && *msginfo->inreplyto)
3014 compose->references =
3015 g_strdup_printf("<%s>\n\t<%s>",
3016 msginfo->inreplyto,
3017 msginfo->msgid);
3018 else
3019 compose->references =
3020 g_strconcat("<", msginfo->msgid, ">",
3021 NULL);
3022 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3023 compose->references =
3024 g_strconcat("<", msginfo->inreplyto, ">",
3025 NULL);
3030 return 0;
3033 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3035 FILE *fp;
3036 HeaderEntry *he;
3038 cm_return_val_if_fail(msginfo != NULL, -1);
3040 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
3041 procheader_get_header_fields(fp, entries);
3042 claws_fclose(fp);
3044 he = entries;
3045 while (he != NULL && he->name != NULL) {
3046 GtkTreeIter iter;
3047 GtkListStore *model = NULL;
3049 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3050 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3051 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3052 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3053 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3054 ++he;
3057 return 0;
3060 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3062 GSList *ref_id_list, *cur;
3063 GString *new_ref;
3064 gchar *new_ref_str;
3066 ref_id_list = references_list_append(NULL, ref);
3067 if (!ref_id_list) return NULL;
3068 if (msgid && *msgid)
3069 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3071 for (;;) {
3072 gint len = 0;
3074 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3075 /* "<" + Message-ID + ">" + CR+LF+TAB */
3076 len += strlen((gchar *)cur->data) + 5;
3078 if (len > MAX_REFERENCES_LEN) {
3079 /* remove second message-ID */
3080 if (ref_id_list && ref_id_list->next &&
3081 ref_id_list->next->next) {
3082 g_free(ref_id_list->next->data);
3083 ref_id_list = g_slist_remove
3084 (ref_id_list, ref_id_list->next->data);
3085 } else {
3086 slist_free_strings_full(ref_id_list);
3087 return NULL;
3089 } else
3090 break;
3093 new_ref = g_string_new("");
3094 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3095 if (new_ref->len > 0)
3096 g_string_append(new_ref, "\n\t");
3097 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3100 slist_free_strings_full(ref_id_list);
3102 new_ref_str = new_ref->str;
3103 g_string_free(new_ref, FALSE);
3105 return new_ref_str;
3108 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3109 const gchar *fmt, const gchar *qmark,
3110 const gchar *body, gboolean rewrap,
3111 gboolean need_unescape,
3112 const gchar *err_msg)
3114 MsgInfo* dummyinfo = NULL;
3115 gchar *quote_str = NULL;
3116 gchar *buf;
3117 gboolean prev_autowrap;
3118 const gchar *trimmed_body = body;
3119 gint cursor_pos = -1;
3120 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3121 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3122 GtkTextIter iter;
3123 GtkTextMark *mark;
3126 SIGNAL_BLOCK(buffer);
3128 if (!msginfo) {
3129 dummyinfo = compose_msginfo_new_from_compose(compose);
3130 msginfo = dummyinfo;
3133 if (qmark != NULL) {
3134 #ifdef USE_ENCHANT
3135 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3136 compose->gtkaspell);
3137 #else
3138 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3139 #endif
3140 quote_fmt_scan_string(qmark);
3141 quote_fmt_parse();
3143 buf = quote_fmt_get_buffer();
3145 if (buf == NULL)
3146 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3147 else
3148 Xstrdup_a(quote_str, buf, goto error)
3151 if (fmt && *fmt != '\0') {
3153 if (trimmed_body)
3154 while (*trimmed_body == '\n')
3155 trimmed_body++;
3157 #ifdef USE_ENCHANT
3158 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3159 compose->gtkaspell);
3160 #else
3161 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3162 #endif
3163 if (need_unescape) {
3164 gchar *tmp = NULL;
3166 /* decode \-escape sequences in the internal representation of the quote format */
3167 tmp = g_malloc(strlen(fmt)+1);
3168 pref_get_unescaped_pref(tmp, fmt);
3169 quote_fmt_scan_string(tmp);
3170 quote_fmt_parse();
3171 g_free(tmp);
3172 } else {
3173 quote_fmt_scan_string(fmt);
3174 quote_fmt_parse();
3177 buf = quote_fmt_get_buffer();
3179 if (buf == NULL) {
3180 gint line = quote_fmt_get_line();
3181 alertpanel_error(err_msg, line);
3183 goto error;
3186 } else
3187 buf = "";
3189 prev_autowrap = compose->autowrap;
3190 compose->autowrap = FALSE;
3192 mark = gtk_text_buffer_get_insert(buffer);
3193 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3194 if (g_utf8_validate(buf, -1, NULL)) {
3195 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3196 } else {
3197 gchar *tmpout = NULL;
3198 tmpout = conv_codeset_strdup
3199 (buf, conv_get_locale_charset_str_no_utf8(),
3200 CS_INTERNAL);
3201 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3202 g_free(tmpout);
3203 tmpout = g_malloc(strlen(buf)*2+1);
3204 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3206 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3207 g_free(tmpout);
3210 cursor_pos = quote_fmt_get_cursor_pos();
3211 if (cursor_pos == -1)
3212 cursor_pos = gtk_text_iter_get_offset(&iter);
3213 compose->set_cursor_pos = cursor_pos;
3215 gtk_text_buffer_get_start_iter(buffer, &iter);
3216 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3217 gtk_text_buffer_place_cursor(buffer, &iter);
3219 compose->autowrap = prev_autowrap;
3220 if (compose->autowrap && rewrap)
3221 compose_wrap_all(compose);
3223 goto ok;
3225 error:
3226 buf = NULL;
3228 SIGNAL_UNBLOCK(buffer);
3230 procmsg_msginfo_free( &dummyinfo );
3232 return buf;
3235 /* if ml_post is of type addr@host and from is of type
3236 * addr-anything@host, return TRUE
3238 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3240 gchar *left_ml = NULL;
3241 gchar *right_ml = NULL;
3242 gchar *left_from = NULL;
3243 gchar *right_from = NULL;
3244 gboolean result = FALSE;
3246 if (!ml_post || !from)
3247 return FALSE;
3249 left_ml = g_strdup(ml_post);
3250 if (strstr(left_ml, "@")) {
3251 right_ml = strstr(left_ml, "@")+1;
3252 *(strstr(left_ml, "@")) = '\0';
3255 left_from = g_strdup(from);
3256 if (strstr(left_from, "@")) {
3257 right_from = strstr(left_from, "@")+1;
3258 *(strstr(left_from, "@")) = '\0';
3261 if (right_ml && right_from
3262 && !strncmp(left_from, left_ml, strlen(left_ml))
3263 && !strcmp(right_from, right_ml)) {
3264 result = TRUE;
3266 g_free(left_ml);
3267 g_free(left_from);
3269 return result;
3272 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3273 gboolean respect_default_to)
3275 if (!compose)
3276 return;
3277 if (!folder || !folder->prefs)
3278 return;
3280 if (respect_default_to && folder->prefs->enable_default_to) {
3281 compose_entry_append(compose, folder->prefs->default_to,
3282 COMPOSE_TO, PREF_FOLDER);
3283 compose_entry_indicate(compose, folder->prefs->default_to);
3285 if (folder->prefs->enable_default_cc) {
3286 compose_entry_append(compose, folder->prefs->default_cc,
3287 COMPOSE_CC, PREF_FOLDER);
3288 compose_entry_indicate(compose, folder->prefs->default_cc);
3290 if (folder->prefs->enable_default_bcc) {
3291 compose_entry_append(compose, folder->prefs->default_bcc,
3292 COMPOSE_BCC, PREF_FOLDER);
3293 compose_entry_indicate(compose, folder->prefs->default_bcc);
3295 if (folder->prefs->enable_default_replyto) {
3296 compose_entry_append(compose, folder->prefs->default_replyto,
3297 COMPOSE_REPLYTO, PREF_FOLDER);
3298 compose_entry_indicate(compose, folder->prefs->default_replyto);
3302 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3304 gchar *buf, *buf2;
3305 gchar *p;
3307 if (!compose || !msginfo)
3308 return;
3310 if (msginfo->subject && *msginfo->subject) {
3311 buf = p = g_strdup(msginfo->subject);
3312 p += subject_get_prefix_length(p);
3313 memmove(buf, p, strlen(p) + 1);
3315 buf2 = g_strdup_printf("Re: %s", buf);
3316 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3318 g_free(buf2);
3319 g_free(buf);
3320 } else
3321 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3324 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3325 gboolean to_all, gboolean to_ml,
3326 gboolean to_sender,
3327 gboolean followup_and_reply_to)
3329 GSList *cc_list = NULL;
3330 GSList *cur;
3331 gchar *from = NULL;
3332 gchar *replyto = NULL;
3333 gchar *ac_email = NULL;
3335 gboolean reply_to_ml = FALSE;
3336 gboolean default_reply_to = FALSE;
3338 cm_return_if_fail(compose->account != NULL);
3339 cm_return_if_fail(msginfo != NULL);
3341 reply_to_ml = to_ml && compose->ml_post;
3343 default_reply_to = msginfo->folder &&
3344 msginfo->folder->prefs->enable_default_reply_to;
3346 if (compose->account->protocol != A_NNTP) {
3347 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3349 if (reply_to_ml && !default_reply_to) {
3351 gboolean is_subscr = is_subscription(compose->ml_post,
3352 msginfo->from);
3353 if (!is_subscr) {
3354 /* normal answer to ml post with a reply-to */
3355 compose_entry_append(compose,
3356 compose->ml_post,
3357 COMPOSE_TO, PREF_ML);
3358 if (compose->replyto)
3359 compose_entry_append(compose,
3360 compose->replyto,
3361 COMPOSE_CC, PREF_ML);
3362 } else {
3363 /* answer to subscription confirmation */
3364 if (compose->replyto)
3365 compose_entry_append(compose,
3366 compose->replyto,
3367 COMPOSE_TO, PREF_ML);
3368 else if (msginfo->from)
3369 compose_entry_append(compose,
3370 msginfo->from,
3371 COMPOSE_TO, PREF_ML);
3374 else if (!(to_all || to_sender) && default_reply_to) {
3375 compose_entry_append(compose,
3376 msginfo->folder->prefs->default_reply_to,
3377 COMPOSE_TO, PREF_FOLDER);
3378 compose_entry_indicate(compose,
3379 msginfo->folder->prefs->default_reply_to);
3380 } else {
3381 gchar *tmp1 = NULL;
3382 if (!msginfo->from)
3383 return;
3384 if (to_sender)
3385 compose_entry_append(compose, msginfo->from,
3386 COMPOSE_TO, PREF_NONE);
3387 else if (to_all) {
3388 Xstrdup_a(tmp1, msginfo->from, return);
3389 extract_address(tmp1);
3390 compose_entry_append(compose,
3391 (!account_find_from_address(tmp1, FALSE))
3392 ? msginfo->from :
3393 msginfo->to,
3394 COMPOSE_TO, PREF_NONE);
3395 if (compose->replyto)
3396 compose_entry_append(compose,
3397 compose->replyto,
3398 COMPOSE_CC, PREF_NONE);
3399 } else {
3400 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3401 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3402 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3403 if (compose->replyto) {
3404 compose_entry_append(compose,
3405 compose->replyto,
3406 COMPOSE_TO, PREF_NONE);
3407 } else {
3408 compose_entry_append(compose,
3409 msginfo->from ? msginfo->from : "",
3410 COMPOSE_TO, PREF_NONE);
3412 } else {
3413 /* replying to own mail, use original recp */
3414 compose_entry_append(compose,
3415 msginfo->to ? msginfo->to : "",
3416 COMPOSE_TO, PREF_NONE);
3417 compose_entry_append(compose,
3418 msginfo->cc ? msginfo->cc : "",
3419 COMPOSE_CC, PREF_NONE);
3423 } else {
3424 if (to_sender || (compose->followup_to &&
3425 !strncmp(compose->followup_to, "poster", 6)))
3426 compose_entry_append
3427 (compose,
3428 (compose->replyto ? compose->replyto :
3429 msginfo->from ? msginfo->from : ""),
3430 COMPOSE_TO, PREF_NONE);
3432 else if (followup_and_reply_to || to_all) {
3433 compose_entry_append
3434 (compose,
3435 (compose->replyto ? compose->replyto :
3436 msginfo->from ? msginfo->from : ""),
3437 COMPOSE_TO, PREF_NONE);
3439 compose_entry_append
3440 (compose,
3441 compose->followup_to ? compose->followup_to :
3442 compose->newsgroups ? compose->newsgroups : "",
3443 COMPOSE_NEWSGROUPS, PREF_NONE);
3445 compose_entry_append
3446 (compose,
3447 msginfo->cc ? msginfo->cc : "",
3448 COMPOSE_CC, PREF_NONE);
3450 else
3451 compose_entry_append
3452 (compose,
3453 compose->followup_to ? compose->followup_to :
3454 compose->newsgroups ? compose->newsgroups : "",
3455 COMPOSE_NEWSGROUPS, PREF_NONE);
3457 compose_reply_set_subject(compose, msginfo);
3459 if (to_ml && compose->ml_post) return;
3460 if (!to_all || compose->account->protocol == A_NNTP) return;
3462 if (compose->replyto) {
3463 Xstrdup_a(replyto, compose->replyto, return);
3464 extract_address(replyto);
3466 if (msginfo->from) {
3467 Xstrdup_a(from, msginfo->from, return);
3468 extract_address(from);
3471 if (replyto && from)
3472 cc_list = address_list_append_with_comments(cc_list, from);
3473 if (to_all && msginfo->folder &&
3474 msginfo->folder->prefs->enable_default_reply_to)
3475 cc_list = address_list_append_with_comments(cc_list,
3476 msginfo->folder->prefs->default_reply_to);
3477 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3478 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3480 ac_email = g_utf8_strdown(compose->account->address, -1);
3482 if (cc_list) {
3483 for (cur = cc_list; cur != NULL; cur = cur->next) {
3484 gchar *addr = g_utf8_strdown(cur->data, -1);
3485 extract_address(addr);
3487 if (strcmp(ac_email, addr))
3488 compose_entry_append(compose, (gchar *)cur->data,
3489 COMPOSE_CC, PREF_NONE);
3490 else
3491 debug_print("Cc address same as compose account's, ignoring\n");
3493 g_free(addr);
3496 slist_free_strings_full(cc_list);
3499 g_free(ac_email);
3502 #define SET_ENTRY(entry, str) \
3504 if (str && *str) \
3505 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3508 #define SET_ADDRESS(type, str) \
3510 if (str && *str) \
3511 compose_entry_append(compose, str, type, PREF_NONE); \
3514 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3516 cm_return_if_fail(msginfo != NULL);
3518 SET_ENTRY(subject_entry, msginfo->subject);
3519 SET_ENTRY(from_name, msginfo->from);
3520 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3521 SET_ADDRESS(COMPOSE_CC, compose->cc);
3522 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3523 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3524 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3525 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3527 compose_update_priority_menu_item(compose);
3528 compose_update_privacy_system_menu_item(compose, FALSE);
3529 compose_show_first_last_header(compose, TRUE);
3532 #undef SET_ENTRY
3533 #undef SET_ADDRESS
3535 static void compose_insert_sig(Compose *compose, gboolean replace)
3537 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3538 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3539 GtkTextMark *mark;
3540 GtkTextIter iter, iter_end;
3541 gint cur_pos, ins_pos;
3542 gboolean prev_autowrap;
3543 gboolean found = FALSE;
3544 gboolean exists = FALSE;
3546 cm_return_if_fail(compose->account != NULL);
3548 BLOCK_WRAP();
3550 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3551 G_CALLBACK(compose_changed_cb),
3552 compose);
3554 mark = gtk_text_buffer_get_insert(buffer);
3555 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3556 cur_pos = gtk_text_iter_get_offset (&iter);
3557 ins_pos = cur_pos;
3559 gtk_text_buffer_get_end_iter(buffer, &iter);
3561 exists = (compose->sig_str != NULL);
3563 if (replace) {
3564 GtkTextIter first_iter, start_iter, end_iter;
3566 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3568 if (!exists || compose->sig_str[0] == '\0')
3569 found = FALSE;
3570 else
3571 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3572 compose->signature_tag);
3574 if (found) {
3575 /* include previous \n\n */
3576 gtk_text_iter_backward_chars(&first_iter, 1);
3577 start_iter = first_iter;
3578 end_iter = first_iter;
3579 /* skip re-start */
3580 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3581 compose->signature_tag);
3582 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3583 compose->signature_tag);
3584 if (found) {
3585 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3586 iter = start_iter;
3591 g_free(compose->sig_str);
3592 compose->sig_str = account_get_signature_str(compose->account);
3594 cur_pos = gtk_text_iter_get_offset(&iter);
3596 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3597 g_free(compose->sig_str);
3598 compose->sig_str = NULL;
3599 } else {
3600 if (compose->sig_inserted == FALSE)
3601 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3602 compose->sig_inserted = TRUE;
3604 cur_pos = gtk_text_iter_get_offset(&iter);
3605 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3606 /* remove \n\n */
3607 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3608 gtk_text_iter_forward_chars(&iter, 1);
3609 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3610 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3612 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3613 cur_pos = gtk_text_buffer_get_char_count (buffer);
3616 /* put the cursor where it should be
3617 * either where the quote_fmt says, either where it was */
3618 if (compose->set_cursor_pos < 0)
3619 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3620 else
3621 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3622 compose->set_cursor_pos);
3624 compose->set_cursor_pos = -1;
3625 gtk_text_buffer_place_cursor(buffer, &iter);
3626 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3627 G_CALLBACK(compose_changed_cb),
3628 compose);
3630 UNBLOCK_WRAP();
3633 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3635 GtkTextView *text;
3636 GtkTextBuffer *buffer;
3637 GtkTextMark *mark;
3638 GtkTextIter iter;
3639 const gchar *cur_encoding;
3640 gchar buf[BUFFSIZE];
3641 gint len;
3642 FILE *fp;
3643 gboolean prev_autowrap;
3644 #ifdef G_OS_WIN32
3645 GFile *f;
3646 GFileInfo *fi;
3647 GError *error = NULL;
3648 #else
3649 GStatBuf file_stat;
3650 #endif
3651 int ret;
3652 goffset size;
3653 GString *file_contents = NULL;
3654 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3656 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3658 /* get the size of the file we are about to insert */
3659 #ifdef G_OS_WIN32
3660 f = g_file_new_for_path(file);
3661 fi = g_file_query_info(f, "standard::size",
3662 G_FILE_QUERY_INFO_NONE, NULL, &error);
3663 ret = 0;
3664 if (error != NULL) {
3665 g_warning(error->message);
3666 ret = 1;
3667 g_error_free(error);
3668 g_object_unref(f);
3670 #else
3671 ret = g_stat(file, &file_stat);
3672 #endif
3673 if (ret != 0) {
3674 gchar *shortfile = g_path_get_basename(file);
3675 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3676 g_free(shortfile);
3677 return COMPOSE_INSERT_NO_FILE;
3678 } else if (prefs_common.warn_large_insert == TRUE) {
3679 #ifdef G_OS_WIN32
3680 size = g_file_info_get_size(fi);
3681 g_object_unref(fi);
3682 g_object_unref(f);
3683 #else
3684 size = file_stat.st_size;
3685 #endif
3687 /* ask user for confirmation if the file is large */
3688 if (prefs_common.warn_large_insert_size < 0 ||
3689 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3690 AlertValue aval;
3691 gchar *msg;
3693 msg = g_strdup_printf(_("You are about to insert a file of %s "
3694 "in the message body. Are you sure you want to do that?"),
3695 to_human_readable(size));
3696 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3697 _("_Insert"), NULL, ALERTFOCUS_SECOND, TRUE,
3698 NULL, ALERT_QUESTION);
3699 g_free(msg);
3701 /* do we ask for confirmation next time? */
3702 if (aval & G_ALERTDISABLE) {
3703 /* no confirmation next time, disable feature in preferences */
3704 aval &= ~G_ALERTDISABLE;
3705 prefs_common.warn_large_insert = FALSE;
3708 /* abort file insertion if user canceled action */
3709 if (aval != G_ALERTALTERNATE) {
3710 return COMPOSE_INSERT_NO_FILE;
3716 if ((fp = claws_fopen(file, "rb")) == NULL) {
3717 FILE_OP_ERROR(file, "claws_fopen");
3718 return COMPOSE_INSERT_READ_ERROR;
3721 prev_autowrap = compose->autowrap;
3722 compose->autowrap = FALSE;
3724 text = GTK_TEXT_VIEW(compose->text);
3725 buffer = gtk_text_view_get_buffer(text);
3726 mark = gtk_text_buffer_get_insert(buffer);
3727 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3729 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3730 G_CALLBACK(text_inserted),
3731 compose);
3733 cur_encoding = conv_get_locale_charset_str_no_utf8();
3735 file_contents = g_string_new("");
3736 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3737 gchar *str;
3739 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3740 str = g_strdup(buf);
3741 else {
3742 codeconv_set_strict(TRUE);
3743 str = conv_codeset_strdup
3744 (buf, cur_encoding, CS_INTERNAL);
3745 codeconv_set_strict(FALSE);
3747 if (!str) {
3748 result = COMPOSE_INSERT_INVALID_CHARACTER;
3749 break;
3752 if (!str) continue;
3754 /* strip <CR> if DOS/Windows file,
3755 replace <CR> with <LF> if Macintosh file. */
3756 strcrchomp(str);
3757 len = strlen(str);
3758 if (len > 0 && str[len - 1] != '\n') {
3759 while (--len >= 0)
3760 if (str[len] == '\r') str[len] = '\n';
3763 file_contents = g_string_append(file_contents, str);
3764 g_free(str);
3767 if (result == COMPOSE_INSERT_SUCCESS) {
3768 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3770 compose_changed_cb(NULL, compose);
3771 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3772 G_CALLBACK(text_inserted),
3773 compose);
3774 compose->autowrap = prev_autowrap;
3775 if (compose->autowrap)
3776 compose_wrap_all(compose);
3779 g_string_free(file_contents, TRUE);
3780 claws_fclose(fp);
3782 return result;
3785 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3786 const gchar *filename,
3787 const gchar *content_type,
3788 const gchar *charset)
3790 AttachInfo *ainfo;
3791 GtkTreeIter iter;
3792 FILE *fp;
3793 off_t size;
3794 GAuto *auto_ainfo;
3795 gchar *size_text;
3796 GtkListStore *store;
3797 gchar *name;
3798 gboolean has_binary = FALSE;
3800 if (!is_file_exist(file)) {
3801 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3802 gboolean result = FALSE;
3803 if (file_from_uri && is_file_exist(file_from_uri)) {
3804 result = compose_attach_append(
3805 compose, file_from_uri,
3806 filename, content_type,
3807 charset);
3809 g_free(file_from_uri);
3810 if (result)
3811 return TRUE;
3812 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3813 return FALSE;
3815 if ((size = get_file_size(file)) < 0) {
3816 alertpanel_error("Can't get file size of %s\n", filename);
3817 return FALSE;
3820 /* In batch mode, we allow 0-length files to be attached no questions asked */
3821 if (size == 0 && !compose->batch) {
3822 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3823 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3824 GTK_STOCK_CANCEL, _("_Attach anyway"), NULL,
3825 ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3826 g_free(msg);
3828 if (aval != G_ALERTALTERNATE) {
3829 return FALSE;
3832 if ((fp = claws_fopen(file, "rb")) == NULL) {
3833 alertpanel_error(_("Can't read %s."), filename);
3834 return FALSE;
3836 claws_fclose(fp);
3838 ainfo = g_new0(AttachInfo, 1);
3839 auto_ainfo = g_auto_pointer_new_with_free
3840 (ainfo, (GFreeFunc) compose_attach_info_free);
3841 ainfo->file = g_strdup(file);
3843 if (content_type) {
3844 ainfo->content_type = g_strdup(content_type);
3845 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3846 MsgInfo *msginfo;
3847 MsgFlags flags = {0, 0};
3849 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3850 ainfo->encoding = ENC_7BIT;
3851 else
3852 ainfo->encoding = ENC_8BIT;
3854 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3855 if (msginfo && msginfo->subject)
3856 name = g_strdup(msginfo->subject);
3857 else
3858 name = g_path_get_basename(filename ? filename : file);
3860 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3862 procmsg_msginfo_free(&msginfo);
3863 } else {
3864 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3865 ainfo->charset = g_strdup(charset);
3866 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3867 } else {
3868 ainfo->encoding = ENC_BASE64;
3870 name = g_path_get_basename(filename ? filename : file);
3871 ainfo->name = g_strdup(name);
3873 g_free(name);
3874 } else {
3875 ainfo->content_type = procmime_get_mime_type(file);
3876 if (!ainfo->content_type) {
3877 ainfo->content_type =
3878 g_strdup("application/octet-stream");
3879 ainfo->encoding = ENC_BASE64;
3880 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3881 ainfo->encoding =
3882 procmime_get_encoding_for_text_file(file, &has_binary);
3883 else
3884 ainfo->encoding = ENC_BASE64;
3885 name = g_path_get_basename(filename ? filename : file);
3886 ainfo->name = g_strdup(name);
3887 g_free(name);
3890 if (ainfo->name != NULL
3891 && !strcmp(ainfo->name, ".")) {
3892 g_free(ainfo->name);
3893 ainfo->name = NULL;
3896 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3897 g_free(ainfo->content_type);
3898 ainfo->content_type = g_strdup("application/octet-stream");
3899 g_free(ainfo->charset);
3900 ainfo->charset = NULL;
3903 ainfo->size = (goffset)size;
3904 size_text = to_human_readable((goffset)size);
3906 store = GTK_LIST_STORE(gtk_tree_view_get_model
3907 (GTK_TREE_VIEW(compose->attach_clist)));
3909 gtk_list_store_append(store, &iter);
3910 gtk_list_store_set(store, &iter,
3911 COL_MIMETYPE, ainfo->content_type,
3912 COL_SIZE, size_text,
3913 COL_NAME, ainfo->name,
3914 COL_CHARSET, ainfo->charset,
3915 COL_DATA, ainfo,
3916 COL_AUTODATA, auto_ainfo,
3917 -1);
3919 g_auto_pointer_free(auto_ainfo);
3920 compose_attach_update_label(compose);
3921 return TRUE;
3924 void compose_use_signing(Compose *compose, gboolean use_signing)
3926 compose->use_signing = use_signing;
3927 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3930 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3932 compose->use_encryption = use_encryption;
3933 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3936 #define NEXT_PART_NOT_CHILD(info) \
3938 node = info->node; \
3939 while (node->children) \
3940 node = g_node_last_child(node); \
3941 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3944 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3946 MimeInfo *mimeinfo;
3947 MimeInfo *child;
3948 MimeInfo *firsttext = NULL;
3949 MimeInfo *encrypted = NULL;
3950 GNode *node;
3951 gchar *outfile;
3952 const gchar *partname = NULL;
3954 mimeinfo = procmime_scan_message(msginfo);
3955 if (!mimeinfo) return;
3957 if (mimeinfo->node->children == NULL) {
3958 procmime_mimeinfo_free_all(&mimeinfo);
3959 return;
3962 /* find first content part */
3963 child = (MimeInfo *) mimeinfo->node->children->data;
3964 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3965 child = (MimeInfo *)child->node->children->data;
3967 if (child) {
3968 if (child->type == MIMETYPE_TEXT) {
3969 firsttext = child;
3970 debug_print("First text part found\n");
3971 } else if (compose->mode == COMPOSE_REEDIT &&
3972 child->type == MIMETYPE_APPLICATION &&
3973 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3974 encrypted = (MimeInfo *)child->node->parent->data;
3977 child = (MimeInfo *) mimeinfo->node->children->data;
3978 while (child != NULL) {
3979 gint err;
3981 if (child == encrypted) {
3982 /* skip this part of tree */
3983 NEXT_PART_NOT_CHILD(child);
3984 continue;
3987 if (child->type == MIMETYPE_MULTIPART) {
3988 /* get the actual content */
3989 child = procmime_mimeinfo_next(child);
3990 continue;
3993 if (child == firsttext) {
3994 child = procmime_mimeinfo_next(child);
3995 continue;
3998 outfile = procmime_get_tmp_file_name(child);
3999 if ((err = procmime_get_part(outfile, child)) < 0)
4000 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
4001 else {
4002 gchar *content_type;
4004 content_type = procmime_get_content_type_str(child->type, child->subtype);
4006 /* if we meet a pgp signature, we don't attach it, but
4007 * we force signing. */
4008 if ((strcmp(content_type, "application/pgp-signature") &&
4009 strcmp(content_type, "application/pkcs7-signature") &&
4010 strcmp(content_type, "application/x-pkcs7-signature"))
4011 || compose->mode == COMPOSE_REDIRECT) {
4012 partname = procmime_mimeinfo_get_parameter(child, "filename");
4013 if (partname == NULL)
4014 partname = procmime_mimeinfo_get_parameter(child, "name");
4015 if (partname == NULL)
4016 partname = "";
4017 compose_attach_append(compose, outfile,
4018 partname, content_type,
4019 procmime_mimeinfo_get_parameter(child, "charset"));
4020 } else {
4021 compose_force_signing(compose, compose->account, NULL);
4023 g_free(content_type);
4025 g_free(outfile);
4026 NEXT_PART_NOT_CHILD(child);
4028 procmime_mimeinfo_free_all(&mimeinfo);
4031 #undef NEXT_PART_NOT_CHILD
4035 typedef enum {
4036 WAIT_FOR_INDENT_CHAR,
4037 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4038 } IndentState;
4040 /* return indent length, we allow:
4041 indent characters followed by indent characters or spaces/tabs,
4042 alphabets and numbers immediately followed by indent characters,
4043 and the repeating sequences of the above
4044 If quote ends with multiple spaces, only the first one is included. */
4045 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4046 const GtkTextIter *start, gint *len)
4048 GtkTextIter iter = *start;
4049 gunichar wc;
4050 gchar ch[6];
4051 gint clen;
4052 IndentState state = WAIT_FOR_INDENT_CHAR;
4053 gboolean is_space;
4054 gboolean is_indent;
4055 gint alnum_count = 0;
4056 gint space_count = 0;
4057 gint quote_len = 0;
4059 if (prefs_common.quote_chars == NULL) {
4060 return 0 ;
4063 while (!gtk_text_iter_ends_line(&iter)) {
4064 wc = gtk_text_iter_get_char(&iter);
4065 if (g_unichar_iswide(wc))
4066 break;
4067 clen = g_unichar_to_utf8(wc, ch);
4068 if (clen != 1)
4069 break;
4071 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4072 is_space = g_unichar_isspace(wc);
4074 if (state == WAIT_FOR_INDENT_CHAR) {
4075 if (!is_indent && !g_unichar_isalnum(wc))
4076 break;
4077 if (is_indent) {
4078 quote_len += alnum_count + space_count + 1;
4079 alnum_count = space_count = 0;
4080 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4081 } else
4082 alnum_count++;
4083 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4084 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4085 break;
4086 if (is_space)
4087 space_count++;
4088 else if (is_indent) {
4089 quote_len += alnum_count + space_count + 1;
4090 alnum_count = space_count = 0;
4091 } else {
4092 alnum_count++;
4093 state = WAIT_FOR_INDENT_CHAR;
4097 gtk_text_iter_forward_char(&iter);
4100 if (quote_len > 0 && space_count > 0)
4101 quote_len++;
4103 if (len)
4104 *len = quote_len;
4106 if (quote_len > 0) {
4107 iter = *start;
4108 gtk_text_iter_forward_chars(&iter, quote_len);
4109 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4112 return NULL;
4115 /* return >0 if the line is itemized */
4116 static int compose_itemized_length(GtkTextBuffer *buffer,
4117 const GtkTextIter *start)
4119 GtkTextIter iter = *start;
4120 gunichar wc;
4121 gchar ch[6];
4122 gint clen;
4123 gint len = 0;
4124 if (gtk_text_iter_ends_line(&iter))
4125 return 0;
4127 while (1) {
4128 len++;
4129 wc = gtk_text_iter_get_char(&iter);
4130 if (!g_unichar_isspace(wc))
4131 break;
4132 gtk_text_iter_forward_char(&iter);
4133 if (gtk_text_iter_ends_line(&iter))
4134 return 0;
4137 clen = g_unichar_to_utf8(wc, ch);
4138 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4139 (clen == 3 && (
4140 wc == 0x2022 || /* BULLET */
4141 wc == 0x2023 || /* TRIANGULAR BULLET */
4142 wc == 0x2043 || /* HYPHEN BULLET */
4143 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4144 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4145 wc == 0x2219 || /* BULLET OPERATOR */
4146 wc == 0x25d8 || /* INVERSE BULLET */
4147 wc == 0x25e6 || /* WHITE BULLET */
4148 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4149 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4150 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4151 wc == 0x29be || /* CIRCLED WHITE BULLET */
4152 wc == 0x29bf /* CIRCLED BULLET */
4153 ))))
4154 return 0;
4156 gtk_text_iter_forward_char(&iter);
4157 if (gtk_text_iter_ends_line(&iter))
4158 return 0;
4159 wc = gtk_text_iter_get_char(&iter);
4160 if (g_unichar_isspace(wc)) {
4161 return len+1;
4163 return 0;
4166 /* return the string at the start of the itemization */
4167 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4168 const GtkTextIter *start)
4170 GtkTextIter iter = *start;
4171 gunichar wc;
4172 gint len = 0;
4173 GString *item_chars = g_string_new("");
4174 gchar *str = NULL;
4176 if (gtk_text_iter_ends_line(&iter))
4177 return NULL;
4179 while (1) {
4180 len++;
4181 wc = gtk_text_iter_get_char(&iter);
4182 if (!g_unichar_isspace(wc))
4183 break;
4184 gtk_text_iter_forward_char(&iter);
4185 if (gtk_text_iter_ends_line(&iter))
4186 break;
4187 g_string_append_unichar(item_chars, wc);
4190 str = item_chars->str;
4191 g_string_free(item_chars, FALSE);
4192 return str;
4195 /* return the number of spaces at a line's start */
4196 static int compose_left_offset_length(GtkTextBuffer *buffer,
4197 const GtkTextIter *start)
4199 GtkTextIter iter = *start;
4200 gunichar wc;
4201 gint len = 0;
4202 if (gtk_text_iter_ends_line(&iter))
4203 return 0;
4205 while (1) {
4206 wc = gtk_text_iter_get_char(&iter);
4207 if (!g_unichar_isspace(wc))
4208 break;
4209 len++;
4210 gtk_text_iter_forward_char(&iter);
4211 if (gtk_text_iter_ends_line(&iter))
4212 return 0;
4215 gtk_text_iter_forward_char(&iter);
4216 if (gtk_text_iter_ends_line(&iter))
4217 return 0;
4218 return len;
4221 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4222 const GtkTextIter *start,
4223 GtkTextIter *break_pos,
4224 gint max_col,
4225 gint quote_len)
4227 GtkTextIter iter = *start, line_end = *start;
4228 PangoLogAttr *attrs;
4229 gchar *str;
4230 gchar *p;
4231 gint len;
4232 gint i;
4233 gint col = 0;
4234 gint pos = 0;
4235 gboolean can_break = FALSE;
4236 gboolean do_break = FALSE;
4237 gboolean was_white = FALSE;
4238 gboolean prev_dont_break = FALSE;
4240 gtk_text_iter_forward_to_line_end(&line_end);
4241 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4242 len = g_utf8_strlen(str, -1);
4244 if (len == 0) {
4245 g_free(str);
4246 g_warning("compose_get_line_break_pos: len = 0!");
4247 return FALSE;
4250 /* g_print("breaking line: %d: %s (len = %d)\n",
4251 gtk_text_iter_get_line(&iter), str, len); */
4253 attrs = g_new(PangoLogAttr, len + 1);
4255 pango_default_break(str, -1, NULL, attrs, len + 1);
4257 p = str;
4259 /* skip quote and leading spaces */
4260 for (i = 0; *p != '\0' && i < len; i++) {
4261 gunichar wc;
4263 wc = g_utf8_get_char(p);
4264 if (i >= quote_len && !g_unichar_isspace(wc))
4265 break;
4266 if (g_unichar_iswide(wc))
4267 col += 2;
4268 else if (*p == '\t')
4269 col += 8;
4270 else
4271 col++;
4272 p = g_utf8_next_char(p);
4275 for (; *p != '\0' && i < len; i++) {
4276 PangoLogAttr *attr = attrs + i;
4277 gunichar wc = g_utf8_get_char(p);
4278 gint uri_len;
4280 /* attr->is_line_break will be false for some characters that
4281 * we want to break a line before, like '/' or ':', so we
4282 * also allow breaking on any non-wide character. The
4283 * mentioned pango attribute is still useful to decide on
4284 * line breaks when wide characters are involved. */
4285 if ((!g_unichar_iswide(wc) || attr->is_line_break)
4286 && can_break && was_white && !prev_dont_break)
4287 pos = i;
4289 was_white = attr->is_white;
4291 /* don't wrap URI */
4292 if ((uri_len = get_uri_len(p)) > 0) {
4293 col += uri_len;
4294 if (pos > 0 && col > max_col) {
4295 do_break = TRUE;
4296 break;
4298 i += uri_len - 1;
4299 p += uri_len;
4300 can_break = TRUE;
4301 continue;
4304 if (g_unichar_iswide(wc)) {
4305 col += 2;
4306 if (prev_dont_break && can_break && attr->is_line_break)
4307 pos = i;
4308 } else if (*p == '\t')
4309 col += 8;
4310 else
4311 col++;
4312 if (pos > 0 && col > max_col) {
4313 do_break = TRUE;
4314 break;
4317 if (*p == '-' || *p == '/')
4318 prev_dont_break = TRUE;
4319 else
4320 prev_dont_break = FALSE;
4322 p = g_utf8_next_char(p);
4323 can_break = TRUE;
4326 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4328 g_free(attrs);
4329 g_free(str);
4331 *break_pos = *start;
4332 gtk_text_iter_set_line_offset(break_pos, pos);
4334 return do_break;
4337 static gboolean compose_join_next_line(Compose *compose,
4338 GtkTextBuffer *buffer,
4339 GtkTextIter *iter,
4340 const gchar *quote_str)
4342 GtkTextIter iter_ = *iter, cur, prev, next, end;
4343 PangoLogAttr attrs[3];
4344 gchar *str;
4345 gchar *next_quote_str;
4346 gunichar wc1, wc2;
4347 gint quote_len;
4348 gboolean keep_cursor = FALSE;
4350 if (!gtk_text_iter_forward_line(&iter_) ||
4351 gtk_text_iter_ends_line(&iter_)) {
4352 return FALSE;
4354 next_quote_str = compose_get_quote_str(buffer, &iter_, &quote_len);
4356 if ((quote_str || next_quote_str) &&
4357 g_strcmp0(quote_str, next_quote_str) != 0) {
4358 g_free(next_quote_str);
4359 return FALSE;
4361 g_free(next_quote_str);
4363 end = iter_;
4364 if (quote_len > 0) {
4365 gtk_text_iter_forward_chars(&end, quote_len);
4366 if (gtk_text_iter_ends_line(&end)) {
4367 return FALSE;
4371 /* don't join itemized lines */
4372 if (compose_itemized_length(buffer, &end) > 0) {
4373 return FALSE;
4376 /* don't join signature separator */
4377 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4378 return FALSE;
4380 /* delete quote str */
4381 if (quote_len > 0)
4382 gtk_text_buffer_delete(buffer, &iter_, &end);
4384 /* don't join line breaks put by the user */
4385 prev = cur = iter_;
4386 gtk_text_iter_backward_char(&cur);
4387 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4388 gtk_text_iter_forward_char(&cur);
4389 *iter = cur;
4390 return FALSE;
4392 gtk_text_iter_forward_char(&cur);
4393 /* delete linebreak and extra spaces */
4394 while (gtk_text_iter_backward_char(&cur)) {
4395 wc1 = gtk_text_iter_get_char(&cur);
4396 if (!g_unichar_isspace(wc1))
4397 break;
4398 prev = cur;
4400 next = cur = iter_;
4401 while (!gtk_text_iter_ends_line(&cur)) {
4402 wc1 = gtk_text_iter_get_char(&cur);
4403 if (!g_unichar_isspace(wc1))
4404 break;
4405 gtk_text_iter_forward_char(&cur);
4406 next = cur;
4408 if (!gtk_text_iter_equal(&prev, &next)) {
4409 GtkTextMark *mark;
4411 mark = gtk_text_buffer_get_insert(buffer);
4412 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4413 if (gtk_text_iter_equal(&prev, &cur))
4414 keep_cursor = TRUE;
4415 gtk_text_buffer_delete(buffer, &prev, &next);
4417 iter_ = prev;
4419 /* insert space if required */
4420 gtk_text_iter_backward_char(&prev);
4421 wc1 = gtk_text_iter_get_char(&prev);
4422 wc2 = gtk_text_iter_get_char(&next);
4423 gtk_text_iter_forward_char(&next);
4424 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4425 pango_default_break(str, -1, NULL, attrs, 3);
4426 if (!attrs[1].is_line_break ||
4427 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4428 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4429 if (keep_cursor) {
4430 gtk_text_iter_backward_char(&iter_);
4431 gtk_text_buffer_place_cursor(buffer, &iter_);
4434 g_free(str);
4436 *iter = iter_;
4437 return TRUE;
4440 #define ADD_TXT_POS(bp_, ep_, pti_) \
4441 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4442 last = last->next; \
4443 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4444 last->next = NULL; \
4445 } else { \
4446 g_warning("alloc error scanning URIs"); \
4449 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4451 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4452 GtkTextBuffer *buffer;
4453 GtkTextIter iter, break_pos, end_of_line;
4454 gchar *quote_str = NULL;
4455 gint quote_len;
4456 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4457 gboolean prev_autowrap = compose->autowrap;
4458 gint startq_offset = -1, noq_offset = -1;
4459 gint uri_start = -1, uri_stop = -1;
4460 gint nouri_start = -1, nouri_stop = -1;
4461 gint num_blocks = 0;
4462 gint quotelevel = -1;
4463 gboolean modified = force;
4464 gboolean removed = FALSE;
4465 gboolean modified_before_remove = FALSE;
4466 gint lines = 0;
4467 gboolean start = TRUE;
4468 gint itemized_len = 0, rem_item_len = 0;
4469 gchar *itemized_chars = NULL;
4470 gboolean item_continuation = FALSE;
4472 if (force) {
4473 modified = TRUE;
4475 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4476 modified = TRUE;
4479 compose->autowrap = FALSE;
4481 buffer = gtk_text_view_get_buffer(text);
4482 undo_wrapping(compose->undostruct, TRUE);
4483 if (par_iter) {
4484 iter = *par_iter;
4485 } else {
4486 GtkTextMark *mark;
4487 mark = gtk_text_buffer_get_insert(buffer);
4488 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4492 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4493 if (gtk_text_iter_ends_line(&iter)) {
4494 while (gtk_text_iter_ends_line(&iter) &&
4495 gtk_text_iter_forward_line(&iter))
4497 } else {
4498 while (gtk_text_iter_backward_line(&iter)) {
4499 if (gtk_text_iter_ends_line(&iter)) {
4500 gtk_text_iter_forward_line(&iter);
4501 break;
4505 } else {
4506 /* move to line start */
4507 gtk_text_iter_set_line_offset(&iter, 0);
4510 itemized_len = compose_itemized_length(buffer, &iter);
4512 if (!itemized_len) {
4513 itemized_len = compose_left_offset_length(buffer, &iter);
4514 item_continuation = TRUE;
4517 if (itemized_len)
4518 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4520 /* go until paragraph end (empty line) */
4521 while (start || !gtk_text_iter_ends_line(&iter)) {
4522 gchar *scanpos = NULL;
4523 /* parse table - in order of priority */
4524 struct table {
4525 const gchar *needle; /* token */
4527 /* token search function */
4528 gchar *(*search) (const gchar *haystack,
4529 const gchar *needle);
4530 /* part parsing function */
4531 gboolean (*parse) (const gchar *start,
4532 const gchar *scanpos,
4533 const gchar **bp_,
4534 const gchar **ep_,
4535 gboolean hdr);
4536 /* part to URI function */
4537 gchar *(*build_uri) (const gchar *bp,
4538 const gchar *ep);
4541 static struct table parser[] = {
4542 {"http://", strcasestr, get_uri_part, make_uri_string},
4543 {"https://", strcasestr, get_uri_part, make_uri_string},
4544 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4545 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4546 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4547 {"www.", strcasestr, get_uri_part, make_http_string},
4548 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4549 {"@", strcasestr, get_email_part, make_email_string}
4551 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4552 gint last_index = PARSE_ELEMS;
4553 gint n;
4554 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4555 gint walk_pos;
4557 start = FALSE;
4558 if (!prev_autowrap && num_blocks == 0) {
4559 num_blocks++;
4560 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4561 G_CALLBACK(text_inserted),
4562 compose);
4564 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4565 goto colorize;
4567 uri_start = uri_stop = -1;
4568 quote_len = 0;
4569 quote_str = compose_get_quote_str(buffer, &iter, &quote_len);
4571 if (quote_str) {
4572 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4573 if (startq_offset == -1)
4574 startq_offset = gtk_text_iter_get_offset(&iter);
4575 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4576 if (quotelevel > 2) {
4577 /* recycle colors */
4578 if (prefs_common.recycle_quote_colors)
4579 quotelevel %= 3;
4580 else
4581 quotelevel = 2;
4583 if (!wrap_quote) {
4584 goto colorize;
4586 } else {
4587 if (startq_offset == -1)
4588 noq_offset = gtk_text_iter_get_offset(&iter);
4589 quotelevel = -1;
4592 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4593 goto colorize;
4595 if (gtk_text_iter_ends_line(&iter)) {
4596 goto colorize;
4597 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4598 prefs_common.linewrap_len,
4599 quote_len)) {
4600 GtkTextIter prev, next, cur;
4601 if (prev_autowrap != FALSE || force) {
4602 compose->automatic_break = TRUE;
4603 modified = TRUE;
4604 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4605 compose->automatic_break = FALSE;
4606 if (itemized_len && compose->autoindent) {
4607 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4608 if (!item_continuation)
4609 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4611 } else if (quote_str && wrap_quote) {
4612 compose->automatic_break = TRUE;
4613 modified = TRUE;
4614 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4615 compose->automatic_break = FALSE;
4616 if (itemized_len && compose->autoindent) {
4617 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4618 if (!item_continuation)
4619 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4621 } else
4622 goto colorize;
4623 /* remove trailing spaces */
4624 cur = break_pos;
4625 rem_item_len = itemized_len;
4626 while (compose->autoindent && rem_item_len-- > 0)
4627 gtk_text_iter_backward_char(&cur);
4628 gtk_text_iter_backward_char(&cur);
4630 prev = next = cur;
4631 while (!gtk_text_iter_starts_line(&cur)) {
4632 gunichar wc;
4634 gtk_text_iter_backward_char(&cur);
4635 wc = gtk_text_iter_get_char(&cur);
4636 if (!g_unichar_isspace(wc))
4637 break;
4638 prev = cur;
4640 if (!gtk_text_iter_equal(&prev, &next)) {
4641 gtk_text_buffer_delete(buffer, &prev, &next);
4642 break_pos = next;
4643 gtk_text_iter_forward_char(&break_pos);
4646 if (quote_str)
4647 gtk_text_buffer_insert(buffer, &break_pos,
4648 quote_str, -1);
4650 iter = break_pos;
4651 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4653 /* move iter to current line start */
4654 gtk_text_iter_set_line_offset(&iter, 0);
4655 if (quote_str) {
4656 g_free(quote_str);
4657 quote_str = NULL;
4659 continue;
4660 } else {
4661 /* move iter to next line start */
4662 iter = break_pos;
4663 lines++;
4666 colorize:
4667 if (!prev_autowrap && num_blocks > 0) {
4668 num_blocks--;
4669 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4670 G_CALLBACK(text_inserted),
4671 compose);
4673 end_of_line = iter;
4674 while (!gtk_text_iter_ends_line(&end_of_line)) {
4675 gtk_text_iter_forward_char(&end_of_line);
4677 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4679 nouri_start = gtk_text_iter_get_offset(&iter);
4680 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4682 walk_pos = gtk_text_iter_get_offset(&iter);
4683 /* FIXME: this looks phony. scanning for anything in the parse table */
4684 for (n = 0; n < PARSE_ELEMS; n++) {
4685 gchar *tmp;
4687 tmp = parser[n].search(walk, parser[n].needle);
4688 if (tmp) {
4689 if (scanpos == NULL || tmp < scanpos) {
4690 scanpos = tmp;
4691 last_index = n;
4696 bp = ep = 0;
4697 if (scanpos) {
4698 /* check if URI can be parsed */
4699 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4700 (const gchar **)&ep, FALSE)
4701 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4702 walk = ep;
4703 } else
4704 walk = scanpos +
4705 strlen(parser[last_index].needle);
4707 if (bp && ep) {
4708 uri_start = walk_pos + (bp - o_walk);
4709 uri_stop = walk_pos + (ep - o_walk);
4711 g_free(o_walk);
4712 o_walk = NULL;
4713 gtk_text_iter_forward_line(&iter);
4714 g_free(quote_str);
4715 quote_str = NULL;
4716 if (startq_offset != -1) {
4717 GtkTextIter startquote, endquote;
4718 gtk_text_buffer_get_iter_at_offset(
4719 buffer, &startquote, startq_offset);
4720 endquote = iter;
4722 switch (quotelevel) {
4723 case 0:
4724 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4725 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4726 gtk_text_buffer_apply_tag_by_name(
4727 buffer, "quote0", &startquote, &endquote);
4728 gtk_text_buffer_remove_tag_by_name(
4729 buffer, "quote1", &startquote, &endquote);
4730 gtk_text_buffer_remove_tag_by_name(
4731 buffer, "quote2", &startquote, &endquote);
4732 modified = TRUE;
4734 break;
4735 case 1:
4736 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4737 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4738 gtk_text_buffer_apply_tag_by_name(
4739 buffer, "quote1", &startquote, &endquote);
4740 gtk_text_buffer_remove_tag_by_name(
4741 buffer, "quote0", &startquote, &endquote);
4742 gtk_text_buffer_remove_tag_by_name(
4743 buffer, "quote2", &startquote, &endquote);
4744 modified = TRUE;
4746 break;
4747 case 2:
4748 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4749 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4750 gtk_text_buffer_apply_tag_by_name(
4751 buffer, "quote2", &startquote, &endquote);
4752 gtk_text_buffer_remove_tag_by_name(
4753 buffer, "quote0", &startquote, &endquote);
4754 gtk_text_buffer_remove_tag_by_name(
4755 buffer, "quote1", &startquote, &endquote);
4756 modified = TRUE;
4758 break;
4760 startq_offset = -1;
4761 } else if (noq_offset != -1) {
4762 GtkTextIter startnoquote, endnoquote;
4763 gtk_text_buffer_get_iter_at_offset(
4764 buffer, &startnoquote, noq_offset);
4765 endnoquote = iter;
4767 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4768 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4769 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4770 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4771 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4772 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4773 gtk_text_buffer_remove_tag_by_name(
4774 buffer, "quote0", &startnoquote, &endnoquote);
4775 gtk_text_buffer_remove_tag_by_name(
4776 buffer, "quote1", &startnoquote, &endnoquote);
4777 gtk_text_buffer_remove_tag_by_name(
4778 buffer, "quote2", &startnoquote, &endnoquote);
4779 modified = TRUE;
4781 noq_offset = -1;
4784 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4785 GtkTextIter nouri_start_iter, nouri_end_iter;
4786 gtk_text_buffer_get_iter_at_offset(
4787 buffer, &nouri_start_iter, nouri_start);
4788 gtk_text_buffer_get_iter_at_offset(
4789 buffer, &nouri_end_iter, nouri_stop);
4790 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4791 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4792 gtk_text_buffer_remove_tag_by_name(
4793 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4794 modified_before_remove = modified;
4795 modified = TRUE;
4796 removed = TRUE;
4799 if (uri_start >= 0 && uri_stop > 0) {
4800 GtkTextIter uri_start_iter, uri_end_iter, back;
4801 gtk_text_buffer_get_iter_at_offset(
4802 buffer, &uri_start_iter, uri_start);
4803 gtk_text_buffer_get_iter_at_offset(
4804 buffer, &uri_end_iter, uri_stop);
4805 back = uri_end_iter;
4806 gtk_text_iter_backward_char(&back);
4807 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4808 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4809 gtk_text_buffer_apply_tag_by_name(
4810 buffer, "link", &uri_start_iter, &uri_end_iter);
4811 modified = TRUE;
4812 if (removed && !modified_before_remove) {
4813 modified = FALSE;
4817 if (!modified) {
4818 /* debug_print("not modified, out after %d lines\n", lines); */
4819 goto end;
4822 /* debug_print("modified, out after %d lines\n", lines); */
4823 end:
4824 g_free(itemized_chars);
4825 if (par_iter)
4826 *par_iter = iter;
4827 undo_wrapping(compose->undostruct, FALSE);
4828 compose->autowrap = prev_autowrap;
4830 return modified;
4833 void compose_action_cb(void *data)
4835 Compose *compose = (Compose *)data;
4836 compose_wrap_all(compose);
4839 static void compose_wrap_all(Compose *compose)
4841 compose_wrap_all_full(compose, FALSE);
4844 static void compose_wrap_all_full(Compose *compose, gboolean force)
4846 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4847 GtkTextBuffer *buffer;
4848 GtkTextIter iter;
4849 gboolean modified = TRUE;
4851 buffer = gtk_text_view_get_buffer(text);
4853 gtk_text_buffer_get_start_iter(buffer, &iter);
4855 undo_wrapping(compose->undostruct, TRUE);
4857 while (!gtk_text_iter_is_end(&iter) && modified)
4858 modified = compose_beautify_paragraph(compose, &iter, force);
4860 undo_wrapping(compose->undostruct, FALSE);
4864 static void compose_set_title(Compose *compose)
4866 gchar *str;
4867 gchar *edited;
4868 gchar *subject;
4870 edited = compose->modified ? _(" [Edited]") : "";
4872 subject = gtk_editable_get_chars(
4873 GTK_EDITABLE(compose->subject_entry), 0, -1);
4875 #ifndef GENERIC_UMPC
4876 if (subject && strlen(subject))
4877 str = g_strdup_printf(_("%s - Compose message%s"),
4878 subject, edited);
4879 else
4880 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4881 #else
4882 str = g_strdup(_("Compose message"));
4883 #endif
4885 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4886 g_free(str);
4887 g_free(subject);
4891 * compose_current_mail_account:
4893 * Find a current mail account (the currently selected account, or the
4894 * default account, if a news account is currently selected). If a
4895 * mail account cannot be found, display an error message.
4897 * Return value: Mail account, or NULL if not found.
4899 static PrefsAccount *
4900 compose_current_mail_account(void)
4902 PrefsAccount *ac;
4904 if (cur_account && cur_account->protocol != A_NNTP)
4905 ac = cur_account;
4906 else {
4907 ac = account_get_default();
4908 if (!ac || ac->protocol == A_NNTP) {
4909 alertpanel_error(_("Account for sending mail is not specified.\n"
4910 "Please select a mail account before sending."));
4911 return NULL;
4914 return ac;
4917 #define QUOTE_IF_REQUIRED(out, str) \
4919 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4920 gchar *__tmp; \
4921 gint len; \
4923 len = strlen(str) + 3; \
4924 if ((__tmp = alloca(len)) == NULL) { \
4925 g_warning("can't allocate memory"); \
4926 g_string_free(header, TRUE); \
4927 return NULL; \
4929 g_snprintf(__tmp, len, "\"%s\"", str); \
4930 out = __tmp; \
4931 } else { \
4932 gchar *__tmp; \
4934 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4935 g_warning("can't allocate memory"); \
4936 g_string_free(header, TRUE); \
4937 return NULL; \
4938 } else \
4939 strcpy(__tmp, str); \
4941 out = __tmp; \
4945 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4947 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4948 gchar *__tmp; \
4949 gint len; \
4951 len = strlen(str) + 3; \
4952 if ((__tmp = alloca(len)) == NULL) { \
4953 g_warning("can't allocate memory"); \
4954 errret; \
4956 g_snprintf(__tmp, len, "\"%s\"", str); \
4957 out = __tmp; \
4958 } else { \
4959 gchar *__tmp; \
4961 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4962 g_warning("can't allocate memory"); \
4963 errret; \
4964 } else \
4965 strcpy(__tmp, str); \
4967 out = __tmp; \
4971 static void compose_select_account(Compose *compose, PrefsAccount *account,
4972 gboolean init)
4974 gchar *from = NULL, *header = NULL;
4975 ComposeHeaderEntry *header_entry;
4976 GtkTreeIter iter;
4978 cm_return_if_fail(account != NULL);
4980 compose->account = account;
4981 if (account->name && *account->name) {
4982 gchar *buf, *qbuf;
4983 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4984 qbuf = escape_internal_quotes(buf, '"');
4985 from = g_strdup_printf("%s <%s>",
4986 qbuf, account->address);
4987 if (qbuf != buf)
4988 g_free(qbuf);
4989 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4990 } else {
4991 from = g_strdup_printf("<%s>",
4992 account->address);
4993 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4996 g_free(from);
4998 compose_set_title(compose);
5000 compose_activate_privacy_system(compose, account, FALSE);
5002 if (account->default_sign && privacy_system_can_sign(compose->privacy_system) &&
5003 compose->mode != COMPOSE_REDIRECT)
5004 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
5005 else
5006 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
5007 if (account->default_encrypt && privacy_system_can_encrypt(compose->privacy_system) &&
5008 compose->mode != COMPOSE_REDIRECT)
5009 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
5010 else
5011 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
5013 if (!init && compose->mode != COMPOSE_REDIRECT) {
5014 undo_block(compose->undostruct);
5015 compose_insert_sig(compose, TRUE);
5016 undo_unblock(compose->undostruct);
5019 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
5020 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
5021 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
5022 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5024 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5025 if (account->protocol == A_NNTP) {
5026 if (!strcmp(header, _("To:")))
5027 combobox_select_by_text(
5028 GTK_COMBO_BOX(header_entry->combo),
5029 _("Newsgroups:"));
5030 } else {
5031 if (!strcmp(header, _("Newsgroups:")))
5032 combobox_select_by_text(
5033 GTK_COMBO_BOX(header_entry->combo),
5034 _("To:"));
5038 g_free(header);
5040 #ifdef USE_ENCHANT
5041 /* use account's dict info if set */
5042 if (compose->gtkaspell) {
5043 if (account->enable_default_dictionary)
5044 gtkaspell_change_dict(compose->gtkaspell,
5045 account->default_dictionary, FALSE);
5046 if (account->enable_default_alt_dictionary)
5047 gtkaspell_change_alt_dict(compose->gtkaspell,
5048 account->default_alt_dictionary);
5049 if (account->enable_default_dictionary
5050 || account->enable_default_alt_dictionary)
5051 compose_spell_menu_changed(compose);
5053 #endif
5056 gboolean compose_check_for_valid_recipient(Compose *compose) {
5057 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5058 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5059 gboolean recipient_found = FALSE;
5060 GSList *list;
5061 gchar **strptr;
5063 /* free to and newsgroup list */
5064 slist_free_strings_full(compose->to_list);
5065 compose->to_list = NULL;
5067 slist_free_strings_full(compose->newsgroup_list);
5068 compose->newsgroup_list = NULL;
5070 /* search header entries for to and newsgroup entries */
5071 for (list = compose->header_list; list; list = list->next) {
5072 gchar *header;
5073 gchar *entry;
5074 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5075 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5076 g_strstrip(entry);
5077 g_strstrip(header);
5078 if (entry[0] != '\0') {
5079 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5080 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5081 compose->to_list = address_list_append(compose->to_list, entry);
5082 recipient_found = TRUE;
5085 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5086 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5087 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5088 recipient_found = TRUE;
5092 g_free(header);
5093 g_free(entry);
5095 return recipient_found;
5098 static gboolean compose_check_for_set_recipients(Compose *compose)
5100 if (compose->account->set_autocc && compose->account->auto_cc) {
5101 gboolean found_other = FALSE;
5102 GSList *list;
5103 /* search header entries for to and newsgroup entries */
5104 for (list = compose->header_list; list; list = list->next) {
5105 gchar *entry;
5106 gchar *header;
5107 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5108 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5109 g_strstrip(entry);
5110 g_strstrip(header);
5111 if (strcmp(entry, compose->account->auto_cc)
5112 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5113 found_other = TRUE;
5114 g_free(entry);
5115 break;
5117 g_free(entry);
5118 g_free(header);
5120 if (!found_other) {
5121 AlertValue aval;
5122 gchar *text;
5123 if (compose->batch) {
5124 gtk_widget_show_all(compose->window);
5126 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5127 prefs_common_translated_header_name("Cc"));
5128 aval = alertpanel(_("Send"),
5129 text,
5130 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5131 g_free(text);
5132 if (aval != G_ALERTALTERNATE)
5133 return FALSE;
5136 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5137 gboolean found_other = FALSE;
5138 GSList *list;
5139 /* search header entries for to and newsgroup entries */
5140 for (list = compose->header_list; list; list = list->next) {
5141 gchar *entry;
5142 gchar *header;
5143 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5144 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5145 g_strstrip(entry);
5146 g_strstrip(header);
5147 if (strcmp(entry, compose->account->auto_bcc)
5148 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5149 found_other = TRUE;
5150 g_free(entry);
5151 g_free(header);
5152 break;
5154 g_free(entry);
5155 g_free(header);
5157 if (!found_other) {
5158 AlertValue aval;
5159 gchar *text;
5160 if (compose->batch) {
5161 gtk_widget_show_all(compose->window);
5163 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5164 prefs_common_translated_header_name("Bcc"));
5165 aval = alertpanel(_("Send"),
5166 text,
5167 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5168 g_free(text);
5169 if (aval != G_ALERTALTERNATE)
5170 return FALSE;
5173 return TRUE;
5176 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5178 const gchar *str;
5180 if (compose_check_for_valid_recipient(compose) == FALSE) {
5181 if (compose->batch) {
5182 gtk_widget_show_all(compose->window);
5184 alertpanel_error(_("Recipient is not specified."));
5185 return FALSE;
5188 if (compose_check_for_set_recipients(compose) == FALSE) {
5189 return FALSE;
5192 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5193 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5194 if (*str == '\0' && check_everything == TRUE &&
5195 compose->mode != COMPOSE_REDIRECT) {
5196 AlertValue aval;
5197 gchar *message;
5199 message = g_strdup_printf(_("Subject is empty. %s"),
5200 compose->sending?_("Send it anyway?"):
5201 _("Queue it anyway?"));
5203 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5204 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5205 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5206 g_free(message);
5207 if (aval & G_ALERTDISABLE) {
5208 aval &= ~G_ALERTDISABLE;
5209 prefs_common.warn_empty_subj = FALSE;
5211 if (aval != G_ALERTALTERNATE)
5212 return FALSE;
5216 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5217 && check_everything == TRUE) {
5218 GSList *list;
5219 gint cnt = 0;
5221 /* count To and Cc recipients */
5222 for (list = compose->header_list; list; list = list->next) {
5223 gchar *header;
5224 gchar *entry;
5226 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5227 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5228 g_strstrip(header);
5229 g_strstrip(entry);
5230 if ((entry[0] != '\0') &&
5231 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5232 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5233 cnt++;
5235 g_free(header);
5236 g_free(entry);
5238 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5239 AlertValue aval;
5240 gchar *message;
5242 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5243 compose->sending?_("Send it anyway?"):
5244 _("Queue it anyway?"));
5246 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5247 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5248 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5249 g_free(message);
5250 if (aval & G_ALERTDISABLE) {
5251 aval &= ~G_ALERTDISABLE;
5252 prefs_common.warn_sending_many_recipients_num = 0;
5254 if (aval != G_ALERTALTERNATE)
5255 return FALSE;
5259 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5260 return FALSE;
5262 return TRUE;
5265 static void _display_queue_error(ComposeQueueResult val)
5267 switch (val) {
5268 case COMPOSE_QUEUE_SUCCESS:
5269 break;
5270 case COMPOSE_QUEUE_ERROR_NO_MSG:
5271 alertpanel_error(_("Could not queue message."));
5272 break;
5273 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5274 alertpanel_error(_("Could not queue message:\n\n%s."),
5275 g_strerror(errno));
5276 break;
5277 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5278 alertpanel_error(_("Could not queue message for sending:\n\n"
5279 "Signature failed: %s"),
5280 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5281 break;
5282 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5283 alertpanel_error(_("Could not queue message for sending:\n\n"
5284 "Encryption failed: %s"),
5285 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5286 break;
5287 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5288 alertpanel_error(_("Could not queue message for sending:\n\n"
5289 "Charset conversion failed."));
5290 break;
5291 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5292 alertpanel_error(_("Could not queue message for sending:\n\n"
5293 "Couldn't get recipient encryption key."));
5294 break;
5295 case COMPOSE_QUEUE_SIGNING_CANCELLED:
5296 debug_print("signing cancelled\n");
5297 break;
5298 default:
5299 /* unhandled error */
5300 debug_print("oops, unhandled compose_queue() return value %d\n",
5301 val);
5302 break;
5306 gint compose_send(Compose *compose)
5308 gint msgnum;
5309 FolderItem *folder = NULL;
5310 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5311 gchar *msgpath = NULL;
5312 gboolean discard_window = FALSE;
5313 gchar *errstr = NULL;
5314 gchar *tmsgid = NULL;
5315 MainWindow *mainwin = mainwindow_get_mainwindow();
5316 gboolean queued_removed = FALSE;
5318 if (prefs_common.send_dialog_invisible
5319 || compose->batch == TRUE)
5320 discard_window = TRUE;
5322 compose_allow_user_actions (compose, FALSE);
5323 compose->sending = TRUE;
5325 if (compose_check_entries(compose, TRUE) == FALSE) {
5326 if (compose->batch) {
5327 gtk_widget_show_all(compose->window);
5329 goto bail;
5332 inc_lock();
5333 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5335 if (val != COMPOSE_QUEUE_SUCCESS) {
5336 if (compose->batch) {
5337 gtk_widget_show_all(compose->window);
5340 _display_queue_error(val);
5342 goto bail;
5345 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5346 if (discard_window) {
5347 compose->sending = FALSE;
5348 compose_close(compose);
5349 /* No more compose access in the normal codepath
5350 * after this point! */
5351 compose = NULL;
5354 if (msgnum == 0) {
5355 alertpanel_error(_("The message was queued but could not be "
5356 "sent.\nUse \"Send queued messages\" from "
5357 "the main window to retry."));
5358 if (!discard_window) {
5359 goto bail;
5361 inc_unlock();
5362 g_free(tmsgid);
5363 return -1;
5365 if (msgpath == NULL) {
5366 msgpath = folder_item_fetch_msg(folder, msgnum);
5367 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5368 g_free(msgpath);
5369 } else {
5370 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5371 claws_unlink(msgpath);
5372 g_free(msgpath);
5374 if (!discard_window) {
5375 if (val != 0) {
5376 if (!queued_removed)
5377 folder_item_remove_msg(folder, msgnum);
5378 folder_item_scan(folder);
5379 if (tmsgid) {
5380 /* make sure we delete that */
5381 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5382 if (tmp) {
5383 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5384 folder_item_remove_msg(folder, tmp->msgnum);
5385 procmsg_msginfo_free(&tmp);
5391 if (val == 0) {
5392 if (!queued_removed)
5393 folder_item_remove_msg(folder, msgnum);
5394 folder_item_scan(folder);
5395 if (tmsgid) {
5396 /* make sure we delete that */
5397 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5398 if (tmp) {
5399 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5400 folder_item_remove_msg(folder, tmp->msgnum);
5401 procmsg_msginfo_free(&tmp);
5404 if (!discard_window) {
5405 compose->sending = FALSE;
5406 compose_allow_user_actions (compose, TRUE);
5407 compose_close(compose);
5409 } else {
5410 if (errstr) {
5411 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5412 "the main window to retry."), errstr);
5413 g_free(errstr);
5414 } else {
5415 alertpanel_error_log(_("The message was queued but could not be "
5416 "sent.\nUse \"Send queued messages\" from "
5417 "the main window to retry."));
5419 if (!discard_window) {
5420 goto bail;
5422 inc_unlock();
5423 g_free(tmsgid);
5424 return -1;
5426 g_free(tmsgid);
5427 inc_unlock();
5428 toolbar_main_set_sensitive(mainwin);
5429 main_window_set_menu_sensitive(mainwin);
5430 return 0;
5432 bail:
5433 inc_unlock();
5434 g_free(tmsgid);
5435 compose_allow_user_actions (compose, TRUE);
5436 compose->sending = FALSE;
5437 compose->modified = TRUE;
5438 toolbar_main_set_sensitive(mainwin);
5439 main_window_set_menu_sensitive(mainwin);
5441 return -1;
5444 static gboolean compose_use_attach(Compose *compose)
5446 GtkTreeModel *model = gtk_tree_view_get_model
5447 (GTK_TREE_VIEW(compose->attach_clist));
5448 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5451 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5452 FILE *fp)
5454 gchar buf[BUFFSIZE];
5455 gchar *str;
5456 gboolean first_to_address;
5457 gboolean first_cc_address;
5458 GSList *list;
5459 ComposeHeaderEntry *headerentry;
5460 const gchar *headerentryname;
5461 const gchar *cc_hdr;
5462 const gchar *to_hdr;
5463 gboolean err = FALSE;
5465 debug_print("Writing redirect header\n");
5467 cc_hdr = prefs_common_translated_header_name("Cc:");
5468 to_hdr = prefs_common_translated_header_name("To:");
5470 first_to_address = TRUE;
5471 for (list = compose->header_list; list; list = list->next) {
5472 headerentry = ((ComposeHeaderEntry *)list->data);
5473 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5475 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5476 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5477 Xstrdup_a(str, entstr, return -1);
5478 g_strstrip(str);
5479 if (str[0] != '\0') {
5480 compose_convert_header
5481 (compose, buf, sizeof(buf), str,
5482 strlen("Resent-To") + 2, TRUE);
5484 if (first_to_address) {
5485 err |= (fprintf(fp, "Resent-To: ") < 0);
5486 first_to_address = FALSE;
5487 } else {
5488 err |= (fprintf(fp, ",") < 0);
5490 err |= (fprintf(fp, "%s", buf) < 0);
5494 if (!first_to_address) {
5495 err |= (fprintf(fp, "\n") < 0);
5498 first_cc_address = TRUE;
5499 for (list = compose->header_list; list; list = list->next) {
5500 headerentry = ((ComposeHeaderEntry *)list->data);
5501 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5503 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5504 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5505 Xstrdup_a(str, strg, return -1);
5506 g_strstrip(str);
5507 if (str[0] != '\0') {
5508 compose_convert_header
5509 (compose, buf, sizeof(buf), str,
5510 strlen("Resent-Cc") + 2, TRUE);
5512 if (first_cc_address) {
5513 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5514 first_cc_address = FALSE;
5515 } else {
5516 err |= (fprintf(fp, ",") < 0);
5518 err |= (fprintf(fp, "%s", buf) < 0);
5522 if (!first_cc_address) {
5523 err |= (fprintf(fp, "\n") < 0);
5526 return (err ? -1:0);
5529 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5531 gchar date[RFC822_DATE_BUFFSIZE];
5532 gchar buf[BUFFSIZE];
5533 gchar *str;
5534 const gchar *entstr;
5535 /* struct utsname utsbuf; */
5536 gboolean err = FALSE;
5538 cm_return_val_if_fail(fp != NULL, -1);
5539 cm_return_val_if_fail(compose->account != NULL, -1);
5540 cm_return_val_if_fail(compose->account->address != NULL, -1);
5542 /* Resent-Date */
5543 if (prefs_common.hide_timezone)
5544 get_rfc822_date_hide_tz(date, sizeof(date));
5545 else
5546 get_rfc822_date(date, sizeof(date));
5547 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5549 /* Resent-From */
5550 if (compose->account->name && *compose->account->name) {
5551 compose_convert_header
5552 (compose, buf, sizeof(buf), compose->account->name,
5553 strlen("From: "), TRUE);
5554 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5555 buf, compose->account->address) < 0);
5556 } else
5557 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5559 /* Subject */
5560 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5561 if (*entstr != '\0') {
5562 Xstrdup_a(str, entstr, return -1);
5563 g_strstrip(str);
5564 if (*str != '\0') {
5565 compose_convert_header(compose, buf, sizeof(buf), str,
5566 strlen("Subject: "), FALSE);
5567 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5571 /* Resent-Message-ID */
5572 if (compose->account->gen_msgid) {
5573 gchar *addr = prefs_account_generate_msgid(compose->account);
5574 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5575 if (compose->msgid)
5576 g_free(compose->msgid);
5577 compose->msgid = addr;
5578 } else {
5579 compose->msgid = NULL;
5582 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5583 return -1;
5585 /* separator between header and body */
5586 err |= (claws_fputs("\n", fp) == EOF);
5588 return (err ? -1:0);
5591 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5593 FILE *fp;
5594 size_t len;
5595 gchar *buf = NULL;
5596 gchar rewrite_buf[BUFFSIZE];
5597 int i = 0;
5598 gboolean skip = FALSE;
5599 gboolean err = FALSE;
5600 gchar *not_included[]={
5601 "Return-Path:", "Delivered-To:", "Received:",
5602 "Subject:", "X-UIDL:", "AF:",
5603 "NF:", "PS:", "SRH:",
5604 "SFN:", "DSR:", "MID:",
5605 "CFG:", "PT:", "S:",
5606 "RQ:", "SSV:", "NSV:",
5607 "SSH:", "R:", "MAID:",
5608 "NAID:", "RMID:", "FMID:",
5609 "SCF:", "RRCPT:", "NG:",
5610 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5611 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5612 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5613 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5614 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5615 NULL
5617 gint ret = 0;
5619 if ((fp = claws_fopen(compose->redirect_filename, "rb")) == NULL) {
5620 FILE_OP_ERROR(compose->redirect_filename, "claws_fopen");
5621 return -1;
5624 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5625 skip = FALSE;
5626 for (i = 0; not_included[i] != NULL; i++) {
5627 if (g_ascii_strncasecmp(buf, not_included[i],
5628 strlen(not_included[i])) == 0) {
5629 skip = TRUE;
5630 break;
5633 if (skip) {
5634 g_free(buf);
5635 buf = NULL;
5636 continue;
5638 if (claws_fputs(buf, fdest) == -1) {
5639 g_free(buf);
5640 buf = NULL;
5641 goto error;
5644 if (!prefs_common.redirect_keep_from) {
5645 if (g_ascii_strncasecmp(buf, "From:",
5646 strlen("From:")) == 0) {
5647 err |= (claws_fputs(" (by way of ", fdest) == EOF);
5648 if (compose->account->name
5649 && *compose->account->name) {
5650 gchar buffer[BUFFSIZE];
5652 compose_convert_header
5653 (compose, buffer, sizeof(buffer),
5654 compose->account->name,
5655 strlen("From: "),
5656 FALSE);
5657 err |= (fprintf(fdest, "%s <%s>",
5658 buffer,
5659 compose->account->address) < 0);
5660 } else
5661 err |= (fprintf(fdest, "%s",
5662 compose->account->address) < 0);
5663 err |= (claws_fputs(")", fdest) == EOF);
5667 g_free(buf);
5668 buf = NULL;
5669 if (claws_fputs("\n", fdest) == -1)
5670 goto error;
5673 if (err)
5674 goto error;
5676 if (compose_redirect_write_headers(compose, fdest))
5677 goto error;
5679 while ((len = claws_fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5680 if (claws_fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5681 goto error;
5684 claws_fclose(fp);
5686 return 0;
5688 error:
5689 claws_fclose(fp);
5691 return -1;
5694 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5696 GtkTextBuffer *buffer;
5697 GtkTextIter start, end, tmp;
5698 gchar *chars, *tmp_enc_file, *content;
5699 gchar *buf, *msg;
5700 const gchar *out_codeset;
5701 EncodingType encoding = ENC_UNKNOWN;
5702 MimeInfo *mimemsg, *mimetext;
5703 gint line;
5704 const gchar *src_codeset = CS_INTERNAL;
5705 gchar *from_addr = NULL;
5706 gchar *from_name = NULL;
5707 FolderItem *outbox;
5709 if (action == COMPOSE_WRITE_FOR_SEND) {
5710 attach_parts = TRUE;
5712 /* We're sending the message, generate a Message-ID
5713 * if necessary. */
5714 if (compose->msgid == NULL &&
5715 compose->account->gen_msgid) {
5716 compose->msgid = prefs_account_generate_msgid(compose->account);
5720 /* create message MimeInfo */
5721 mimemsg = procmime_mimeinfo_new();
5722 mimemsg->type = MIMETYPE_MESSAGE;
5723 mimemsg->subtype = g_strdup("rfc822");
5724 mimemsg->content = MIMECONTENT_MEM;
5725 mimemsg->tmp = TRUE; /* must free content later */
5726 mimemsg->data.mem = compose_get_header(compose);
5728 /* Create text part MimeInfo */
5729 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5730 gtk_text_buffer_get_end_iter(buffer, &end);
5731 tmp = end;
5733 /* We make sure that there is a newline at the end. */
5734 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5735 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5736 if (*chars != '\n') {
5737 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5739 g_free(chars);
5742 /* get all composed text */
5743 gtk_text_buffer_get_start_iter(buffer, &start);
5744 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5746 out_codeset = conv_get_charset_str(compose->out_encoding);
5748 if (!out_codeset && is_ascii_str(chars)) {
5749 out_codeset = CS_US_ASCII;
5750 } else if (prefs_common.outgoing_fallback_to_ascii &&
5751 is_ascii_str(chars)) {
5752 out_codeset = CS_US_ASCII;
5753 encoding = ENC_7BIT;
5756 if (!out_codeset) {
5757 gchar *test_conv_global_out = NULL;
5758 gchar *test_conv_reply = NULL;
5760 /* automatic mode. be automatic. */
5761 codeconv_set_strict(TRUE);
5763 out_codeset = conv_get_outgoing_charset_str();
5764 if (out_codeset) {
5765 debug_print("trying to convert to %s\n", out_codeset);
5766 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5769 if (!test_conv_global_out && compose->orig_charset
5770 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5771 out_codeset = compose->orig_charset;
5772 debug_print("failure; trying to convert to %s\n", out_codeset);
5773 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5776 if (!test_conv_global_out && !test_conv_reply) {
5777 /* we're lost */
5778 out_codeset = CS_INTERNAL;
5779 debug_print("failure; finally using %s\n", out_codeset);
5781 g_free(test_conv_global_out);
5782 g_free(test_conv_reply);
5783 codeconv_set_strict(FALSE);
5786 if (encoding == ENC_UNKNOWN) {
5787 if (prefs_common.encoding_method == CTE_BASE64)
5788 encoding = ENC_BASE64;
5789 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5790 encoding = ENC_QUOTED_PRINTABLE;
5791 else if (prefs_common.encoding_method == CTE_8BIT)
5792 encoding = ENC_8BIT;
5793 else
5794 encoding = procmime_get_encoding_for_charset(out_codeset);
5797 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5798 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5800 if (action == COMPOSE_WRITE_FOR_SEND) {
5801 codeconv_set_strict(TRUE);
5802 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5803 codeconv_set_strict(FALSE);
5805 if (!buf) {
5806 AlertValue aval;
5808 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5809 "to the specified %s charset.\n"
5810 "Send it as %s?"), out_codeset, src_codeset);
5811 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5812 _("_Send"), NULL, ALERTFOCUS_SECOND, FALSE,
5813 NULL, ALERT_ERROR);
5814 g_free(msg);
5816 if (aval != G_ALERTALTERNATE) {
5817 g_free(chars);
5818 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5819 } else {
5820 buf = chars;
5821 out_codeset = src_codeset;
5822 chars = NULL;
5825 } else {
5826 buf = chars;
5827 out_codeset = src_codeset;
5828 chars = NULL;
5830 g_free(chars);
5832 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5833 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5834 strstr(buf, "\nFrom ") != NULL) {
5835 encoding = ENC_QUOTED_PRINTABLE;
5839 mimetext = procmime_mimeinfo_new();
5840 mimetext->content = MIMECONTENT_MEM;
5841 mimetext->tmp = TRUE; /* must free content later */
5842 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5843 * and free the data, which we need later. */
5844 mimetext->data.mem = g_strdup(buf);
5845 mimetext->type = MIMETYPE_TEXT;
5846 mimetext->subtype = g_strdup("plain");
5847 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5848 g_strdup(out_codeset));
5850 /* protect trailing spaces when signing message */
5851 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5852 privacy_system_can_sign(compose->privacy_system)) {
5853 encoding = ENC_QUOTED_PRINTABLE;
5856 #ifdef G_OS_WIN32
5857 debug_print("main text: %Id bytes encoded as %s in %d\n",
5858 #else
5859 debug_print("main text: %zd bytes encoded as %s in %d\n",
5860 #endif
5861 strlen(buf), out_codeset, encoding);
5863 /* check for line length limit */
5864 if (action == COMPOSE_WRITE_FOR_SEND &&
5865 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5866 check_line_length(buf, 1000, &line) < 0) {
5867 AlertValue aval;
5869 msg = g_strdup_printf
5870 (_("Line %d exceeds the line length limit (998 bytes).\n"
5871 "The contents of the message might be broken on the way to the delivery.\n"
5872 "\n"
5873 "Send it anyway?"), line + 1);
5874 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL,
5875 ALERTFOCUS_FIRST);
5876 g_free(msg);
5877 if (aval != G_ALERTALTERNATE) {
5878 g_free(buf);
5879 return COMPOSE_QUEUE_ERROR_NO_MSG;
5883 if (encoding != ENC_UNKNOWN)
5884 procmime_encode_content(mimetext, encoding);
5886 /* append attachment parts */
5887 if (compose_use_attach(compose) && attach_parts) {
5888 MimeInfo *mimempart;
5889 gchar *boundary = NULL;
5890 mimempart = procmime_mimeinfo_new();
5891 mimempart->content = MIMECONTENT_EMPTY;
5892 mimempart->type = MIMETYPE_MULTIPART;
5893 mimempart->subtype = g_strdup("mixed");
5895 do {
5896 g_free(boundary);
5897 boundary = generate_mime_boundary(NULL);
5898 } while (strstr(buf, boundary) != NULL);
5900 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5901 boundary);
5903 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5905 g_node_append(mimempart->node, mimetext->node);
5906 g_node_append(mimemsg->node, mimempart->node);
5908 if (compose_add_attachments(compose, mimempart) < 0)
5909 return COMPOSE_QUEUE_ERROR_NO_MSG;
5910 } else
5911 g_node_append(mimemsg->node, mimetext->node);
5913 g_free(buf);
5915 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5916 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5917 /* extract name and address */
5918 if (strstr(spec, " <") && strstr(spec, ">")) {
5919 from_addr = g_strdup(strrchr(spec, '<')+1);
5920 *(strrchr(from_addr, '>')) = '\0';
5921 from_name = g_strdup(spec);
5922 *(strrchr(from_name, '<')) = '\0';
5923 } else {
5924 from_name = NULL;
5925 from_addr = NULL;
5927 g_free(spec);
5929 /* sign message if sending */
5930 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5931 privacy_system_can_sign(compose->privacy_system))
5932 if (!privacy_sign(compose->privacy_system, mimemsg,
5933 compose->account, from_addr)) {
5934 g_free(from_name);
5935 g_free(from_addr);
5936 if (!privacy_peek_error())
5937 return COMPOSE_QUEUE_SIGNING_CANCELLED;
5938 else
5939 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5941 g_free(from_name);
5942 g_free(from_addr);
5944 if (compose->use_encryption) {
5945 if (compose->encdata != NULL &&
5946 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5948 /* First, write an unencrypted copy and save it to outbox, if
5949 * user wants that. */
5950 if (compose->account->save_encrypted_as_clear_text) {
5951 debug_print("saving sent message unencrypted...\n");
5952 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5953 if (tmpfp) {
5954 claws_fclose(tmpfp);
5956 /* fp now points to a file with headers written,
5957 * let's make a copy. */
5958 rewind(fp);
5959 content = file_read_stream_to_str(fp);
5961 str_write_to_file(content, tmp_enc_file, TRUE);
5962 g_free(content);
5964 /* Now write the unencrypted body. */
5965 if ((tmpfp = claws_fopen(tmp_enc_file, "a")) != NULL) {
5966 procmime_write_mimeinfo(mimemsg, tmpfp);
5967 claws_fclose(tmpfp);
5969 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5970 if (!outbox)
5971 outbox = folder_get_default_outbox();
5973 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5974 claws_unlink(tmp_enc_file);
5975 } else {
5976 g_warning("Can't open file '%s'", tmp_enc_file);
5978 } else {
5979 g_warning("couldn't get tempfile");
5982 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5983 debug_print("Couldn't encrypt mime structure: %s.\n",
5984 privacy_get_error());
5985 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5990 procmime_write_mimeinfo(mimemsg, fp);
5992 procmime_mimeinfo_free_all(&mimemsg);
5994 return 0;
5997 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5999 GtkTextBuffer *buffer;
6000 GtkTextIter start, end;
6001 FILE *fp;
6002 size_t len;
6003 gchar *chars, *tmp;
6005 if ((fp = claws_fopen(file, "wb")) == NULL) {
6006 FILE_OP_ERROR(file, "claws_fopen");
6007 return -1;
6010 /* chmod for security */
6011 if (change_file_mode_rw(fp, file) < 0) {
6012 FILE_OP_ERROR(file, "chmod");
6013 g_warning("can't change file mode");
6016 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
6017 gtk_text_buffer_get_start_iter(buffer, &start);
6018 gtk_text_buffer_get_end_iter(buffer, &end);
6019 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
6021 chars = conv_codeset_strdup
6022 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6024 g_free(tmp);
6025 if (!chars) {
6026 claws_fclose(fp);
6027 claws_unlink(file);
6028 return -1;
6030 /* write body */
6031 len = strlen(chars);
6032 if (claws_fwrite(chars, sizeof(gchar), len, fp) != len) {
6033 FILE_OP_ERROR(file, "claws_fwrite");
6034 g_free(chars);
6035 claws_fclose(fp);
6036 claws_unlink(file);
6037 return -1;
6040 g_free(chars);
6042 if (claws_safe_fclose(fp) == EOF) {
6043 FILE_OP_ERROR(file, "claws_fclose");
6044 claws_unlink(file);
6045 return -1;
6047 return 0;
6050 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6052 FolderItem *item;
6053 MsgInfo *msginfo = compose->targetinfo;
6055 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6056 if (!msginfo) return -1;
6058 if (!force && MSG_IS_LOCKED(msginfo->flags))
6059 return 0;
6061 item = msginfo->folder;
6062 cm_return_val_if_fail(item != NULL, -1);
6064 if (procmsg_msg_exist(msginfo) &&
6065 (folder_has_parent_of_type(item, F_QUEUE) ||
6066 folder_has_parent_of_type(item, F_DRAFT)
6067 || msginfo == compose->autosaved_draft)) {
6068 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6069 g_warning("can't remove the old message");
6070 return -1;
6071 } else {
6072 debug_print("removed reedit target %d\n", msginfo->msgnum);
6076 return 0;
6079 static void compose_remove_draft(Compose *compose)
6081 FolderItem *drafts;
6082 MsgInfo *msginfo = compose->targetinfo;
6083 drafts = account_get_special_folder(compose->account, F_DRAFT);
6085 if (procmsg_msg_exist(msginfo)) {
6086 folder_item_remove_msg(drafts, msginfo->msgnum);
6091 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6092 gboolean remove_reedit_target)
6094 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6097 static gboolean compose_warn_encryption(Compose *compose)
6099 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6100 AlertValue val = G_ALERTALTERNATE;
6102 if (warning == NULL)
6103 return TRUE;
6105 val = alertpanel_full(_("Encryption warning"), warning,
6106 GTK_STOCK_CANCEL, _("C_ontinue"), NULL, ALERTFOCUS_SECOND,
6107 TRUE, NULL, ALERT_WARNING);
6108 if (val & G_ALERTDISABLE) {
6109 val &= ~G_ALERTDISABLE;
6110 if (val == G_ALERTALTERNATE)
6111 privacy_inhibit_encrypt_warning(compose->privacy_system,
6112 TRUE);
6115 if (val == G_ALERTALTERNATE) {
6116 return TRUE;
6117 } else {
6118 return FALSE;
6122 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6123 gchar **msgpath, gboolean perform_checks,
6124 gboolean remove_reedit_target)
6126 FolderItem *queue;
6127 gchar *tmp;
6128 FILE *fp;
6129 GSList *cur;
6130 gint num;
6131 PrefsAccount *mailac = NULL, *newsac = NULL;
6132 gboolean err = FALSE;
6134 debug_print("queueing message...\n");
6135 cm_return_val_if_fail(compose->account != NULL, -1);
6137 if (compose_check_entries(compose, perform_checks) == FALSE) {
6138 if (compose->batch) {
6139 gtk_widget_show_all(compose->window);
6141 return COMPOSE_QUEUE_ERROR_NO_MSG;
6144 if (!compose->to_list && !compose->newsgroup_list) {
6145 g_warning("can't get recipient list.");
6146 return COMPOSE_QUEUE_ERROR_NO_MSG;
6149 if (compose->to_list) {
6150 if (compose->account->protocol != A_NNTP)
6151 mailac = compose->account;
6152 else if (cur_account && cur_account->protocol != A_NNTP)
6153 mailac = cur_account;
6154 else if (!(mailac = compose_current_mail_account())) {
6155 alertpanel_error(_("No account for sending mails available!"));
6156 return COMPOSE_QUEUE_ERROR_NO_MSG;
6160 if (compose->newsgroup_list) {
6161 if (compose->account->protocol == A_NNTP)
6162 newsac = compose->account;
6163 else {
6164 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6165 return COMPOSE_QUEUE_ERROR_NO_MSG;
6169 /* write queue header */
6170 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6171 G_DIR_SEPARATOR, compose, (guint) rand());
6172 debug_print("queuing to %s\n", tmp);
6173 if ((fp = claws_fopen(tmp, "w+b")) == NULL) {
6174 FILE_OP_ERROR(tmp, "claws_fopen");
6175 g_free(tmp);
6176 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6179 if (change_file_mode_rw(fp, tmp) < 0) {
6180 FILE_OP_ERROR(tmp, "chmod");
6181 g_warning("can't change file mode");
6184 /* queueing variables */
6185 err |= (fprintf(fp, "AF:\n") < 0);
6186 err |= (fprintf(fp, "NF:0\n") < 0);
6187 err |= (fprintf(fp, "PS:10\n") < 0);
6188 err |= (fprintf(fp, "SRH:1\n") < 0);
6189 err |= (fprintf(fp, "SFN:\n") < 0);
6190 err |= (fprintf(fp, "DSR:\n") < 0);
6191 if (compose->msgid)
6192 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6193 else
6194 err |= (fprintf(fp, "MID:\n") < 0);
6195 err |= (fprintf(fp, "CFG:\n") < 0);
6196 err |= (fprintf(fp, "PT:0\n") < 0);
6197 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6198 err |= (fprintf(fp, "RQ:\n") < 0);
6199 if (mailac)
6200 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6201 else
6202 err |= (fprintf(fp, "SSV:\n") < 0);
6203 if (newsac)
6204 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6205 else
6206 err |= (fprintf(fp, "NSV:\n") < 0);
6207 err |= (fprintf(fp, "SSH:\n") < 0);
6208 /* write recipient list */
6209 if (compose->to_list) {
6210 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6211 for (cur = compose->to_list->next; cur != NULL;
6212 cur = cur->next)
6213 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6214 err |= (fprintf(fp, "\n") < 0);
6216 /* write newsgroup list */
6217 if (compose->newsgroup_list) {
6218 err |= (fprintf(fp, "NG:") < 0);
6219 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6220 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6221 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6222 err |= (fprintf(fp, "\n") < 0);
6224 /* account IDs */
6225 if (mailac)
6226 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6227 if (newsac)
6228 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6231 if (compose->privacy_system != NULL) {
6232 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6233 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6234 if (compose->use_encryption) {
6235 if (!compose_warn_encryption(compose)) {
6236 claws_fclose(fp);
6237 claws_unlink(tmp);
6238 g_free(tmp);
6239 return COMPOSE_QUEUE_ERROR_NO_MSG;
6241 if (mailac && mailac->encrypt_to_self) {
6242 GSList *tmp_list = g_slist_copy(compose->to_list);
6243 tmp_list = g_slist_append(tmp_list, compose->account->address);
6244 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6245 g_slist_free(tmp_list);
6246 } else {
6247 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6249 if (compose->encdata != NULL) {
6250 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6251 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6252 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6253 compose->encdata) < 0);
6254 } /* else we finally dont want to encrypt */
6255 } else {
6256 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6257 /* and if encdata was null, it means there's been a problem in
6258 * key selection */
6259 if (err == TRUE)
6260 g_warning("failed to write queue message");
6261 claws_fclose(fp);
6262 claws_unlink(tmp);
6263 g_free(tmp);
6264 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6269 /* Save copy folder */
6270 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6271 gchar *savefolderid;
6273 savefolderid = compose_get_save_to(compose);
6274 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6275 g_free(savefolderid);
6277 /* Save copy folder */
6278 if (compose->return_receipt) {
6279 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6281 /* Message-ID of message replying to */
6282 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6283 gchar *folderid = NULL;
6285 if (compose->replyinfo->folder)
6286 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6287 if (folderid == NULL)
6288 folderid = g_strdup("NULL");
6290 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6291 g_free(folderid);
6293 /* Message-ID of message forwarding to */
6294 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6295 gchar *folderid = NULL;
6297 if (compose->fwdinfo->folder)
6298 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6299 if (folderid == NULL)
6300 folderid = g_strdup("NULL");
6302 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6303 g_free(folderid);
6306 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6307 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6309 /* end of headers */
6310 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6312 if (compose->redirect_filename != NULL) {
6313 if (compose_redirect_write_to_file(compose, fp) < 0) {
6314 claws_fclose(fp);
6315 claws_unlink(tmp);
6316 g_free(tmp);
6317 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6319 } else {
6320 gint result = 0;
6321 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6322 claws_fclose(fp);
6323 claws_unlink(tmp);
6324 g_free(tmp);
6325 return result;
6328 if (err == TRUE) {
6329 g_warning("failed to write queue message");
6330 claws_fclose(fp);
6331 claws_unlink(tmp);
6332 g_free(tmp);
6333 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6335 if (claws_safe_fclose(fp) == EOF) {
6336 FILE_OP_ERROR(tmp, "claws_fclose");
6337 claws_unlink(tmp);
6338 g_free(tmp);
6339 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6342 if (item && *item) {
6343 queue = *item;
6344 } else {
6345 queue = account_get_special_folder(compose->account, F_QUEUE);
6347 if (!queue) {
6348 g_warning("can't find queue folder");
6349 claws_unlink(tmp);
6350 g_free(tmp);
6351 return COMPOSE_QUEUE_ERROR_NO_MSG;
6353 folder_item_scan(queue);
6354 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6355 g_warning("can't queue the message");
6356 claws_unlink(tmp);
6357 g_free(tmp);
6358 return COMPOSE_QUEUE_ERROR_NO_MSG;
6361 if (msgpath == NULL) {
6362 claws_unlink(tmp);
6363 g_free(tmp);
6364 } else
6365 *msgpath = tmp;
6367 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6368 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6369 if (mi) {
6370 procmsg_msginfo_change_flags(mi,
6371 compose->targetinfo->flags.perm_flags,
6372 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6373 0, 0);
6375 g_slist_free(mi->tags);
6376 mi->tags = g_slist_copy(compose->targetinfo->tags);
6377 procmsg_msginfo_free(&mi);
6381 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6382 compose_remove_reedit_target(compose, FALSE);
6385 if ((msgnum != NULL) && (item != NULL)) {
6386 *msgnum = num;
6387 *item = queue;
6390 return COMPOSE_QUEUE_SUCCESS;
6393 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6395 AttachInfo *ainfo;
6396 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6397 MimeInfo *mimepart;
6398 #ifdef G_OS_WIN32
6399 GFile *f;
6400 GFileInfo *fi;
6401 GError *error = NULL;
6402 #else
6403 GStatBuf statbuf;
6404 #endif
6405 goffset size;
6406 gchar *type, *subtype;
6407 GtkTreeModel *model;
6408 GtkTreeIter iter;
6410 model = gtk_tree_view_get_model(tree_view);
6412 if (!gtk_tree_model_get_iter_first(model, &iter))
6413 return 0;
6414 do {
6415 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6417 if (!is_file_exist(ainfo->file)) {
6418 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6419 AlertValue val = alertpanel_full(_("Warning"), msg,
6420 _("Cancel sending"), _("Ignore attachment"), NULL,
6421 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6422 g_free(msg);
6423 if (val == G_ALERTDEFAULT) {
6424 return -1;
6426 continue;
6428 #ifdef G_OS_WIN32
6429 f = g_file_new_for_path(ainfo->file);
6430 fi = g_file_query_info(f, "standard::size",
6431 G_FILE_QUERY_INFO_NONE, NULL, &error);
6432 if (error != NULL) {
6433 g_warning(error->message);
6434 g_error_free(error);
6435 g_object_unref(f);
6436 return -1;
6438 size = g_file_info_get_size(fi);
6439 g_object_unref(fi);
6440 g_object_unref(f);
6441 #else
6442 if (g_stat(ainfo->file, &statbuf) < 0)
6443 return -1;
6444 size = statbuf.st_size;
6445 #endif
6447 mimepart = procmime_mimeinfo_new();
6448 mimepart->content = MIMECONTENT_FILE;
6449 mimepart->data.filename = g_strdup(ainfo->file);
6450 mimepart->tmp = FALSE; /* or we destroy our attachment */
6451 mimepart->offset = 0;
6452 mimepart->length = size;
6454 type = g_strdup(ainfo->content_type);
6456 if (!strchr(type, '/')) {
6457 g_free(type);
6458 type = g_strdup("application/octet-stream");
6461 subtype = strchr(type, '/') + 1;
6462 *(subtype - 1) = '\0';
6463 mimepart->type = procmime_get_media_type(type);
6464 mimepart->subtype = g_strdup(subtype);
6465 g_free(type);
6467 if (mimepart->type == MIMETYPE_MESSAGE &&
6468 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6469 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6470 } else if (mimepart->type == MIMETYPE_TEXT) {
6471 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6472 /* Text parts with no name come from multipart/alternative
6473 * forwards. Make sure the recipient won't look at the
6474 * original HTML part by mistake. */
6475 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6476 ainfo->name = g_strdup_printf(_("Original %s part"),
6477 mimepart->subtype);
6479 if (ainfo->charset)
6480 g_hash_table_insert(mimepart->typeparameters,
6481 g_strdup("charset"), g_strdup(ainfo->charset));
6483 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6484 if (mimepart->type == MIMETYPE_APPLICATION &&
6485 !g_strcmp0(mimepart->subtype, "octet-stream"))
6486 g_hash_table_insert(mimepart->typeparameters,
6487 g_strdup("name"), g_strdup(ainfo->name));
6488 g_hash_table_insert(mimepart->dispositionparameters,
6489 g_strdup("filename"), g_strdup(ainfo->name));
6490 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6493 if (mimepart->type == MIMETYPE_MESSAGE
6494 || mimepart->type == MIMETYPE_MULTIPART)
6495 ainfo->encoding = ENC_BINARY;
6496 else if (compose->use_signing || compose->fwdinfo != NULL) {
6497 if (ainfo->encoding == ENC_7BIT)
6498 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6499 else if (ainfo->encoding == ENC_8BIT)
6500 ainfo->encoding = ENC_BASE64;
6503 procmime_encode_content(mimepart, ainfo->encoding);
6505 g_node_append(parent->node, mimepart->node);
6506 } while (gtk_tree_model_iter_next(model, &iter));
6508 return 0;
6511 static gchar *compose_quote_list_of_addresses(gchar *str)
6513 GSList *list = NULL, *item = NULL;
6514 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6516 list = address_list_append_with_comments(list, str);
6517 for (item = list; item != NULL; item = item->next) {
6518 gchar *spec = item->data;
6519 gchar *endofname = strstr(spec, " <");
6520 if (endofname != NULL) {
6521 gchar * qqname;
6522 *endofname = '\0';
6523 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6524 qqname = escape_internal_quotes(qname, '"');
6525 *endofname = ' ';
6526 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6527 gchar *addr = g_strdup(endofname);
6528 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6529 faddr = g_strconcat(name, addr, NULL);
6530 g_free(name);
6531 g_free(addr);
6532 debug_print("new auto-quoted address: '%s'\n", faddr);
6535 if (result == NULL)
6536 result = g_strdup((faddr != NULL)? faddr: spec);
6537 else {
6538 result = g_strconcat(result,
6539 ", ",
6540 (faddr != NULL)? faddr: spec,
6541 NULL);
6543 if (faddr != NULL) {
6544 g_free(faddr);
6545 faddr = NULL;
6548 slist_free_strings_full(list);
6550 return result;
6553 #define IS_IN_CUSTOM_HEADER(header) \
6554 (compose->account->add_customhdr && \
6555 custom_header_find(compose->account->customhdr_list, header) != NULL)
6557 static const gchar *compose_untranslated_header_name(gchar *header_name)
6559 /* return the untranslated header name, if header_name is a known
6560 header name, in either its translated or untranslated form, with
6561 or without trailing colon. otherwise, returns header_name. */
6562 gchar *translated_header_name;
6563 gchar *translated_header_name_wcolon;
6564 const gchar *untranslated_header_name;
6565 const gchar *untranslated_header_name_wcolon;
6566 gint i;
6568 cm_return_val_if_fail(header_name != NULL, NULL);
6570 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6571 untranslated_header_name = HEADERS[i].header_name;
6572 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6574 translated_header_name = gettext(untranslated_header_name);
6575 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6577 if (!strcmp(header_name, untranslated_header_name) ||
6578 !strcmp(header_name, translated_header_name)) {
6579 return untranslated_header_name;
6580 } else {
6581 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6582 !strcmp(header_name, translated_header_name_wcolon)) {
6583 return untranslated_header_name_wcolon;
6587 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6588 return header_name;
6591 static void compose_add_headerfield_from_headerlist(Compose *compose,
6592 GString *header,
6593 const gchar *fieldname,
6594 const gchar *seperator)
6596 gchar *str, *fieldname_w_colon;
6597 gboolean add_field = FALSE;
6598 GSList *list;
6599 ComposeHeaderEntry *headerentry;
6600 const gchar *headerentryname;
6601 const gchar *trans_fieldname;
6602 GString *fieldstr;
6604 if (IS_IN_CUSTOM_HEADER(fieldname))
6605 return;
6607 debug_print("Adding %s-fields\n", fieldname);
6609 fieldstr = g_string_sized_new(64);
6611 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6612 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6614 for (list = compose->header_list; list; list = list->next) {
6615 headerentry = ((ComposeHeaderEntry *)list->data);
6616 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6618 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6619 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6620 g_strstrip(ustr);
6621 str = compose_quote_list_of_addresses(ustr);
6622 g_free(ustr);
6623 if (str != NULL && str[0] != '\0') {
6624 if (add_field)
6625 g_string_append(fieldstr, seperator);
6626 g_string_append(fieldstr, str);
6627 add_field = TRUE;
6629 g_free(str);
6632 if (add_field) {
6633 gchar *buf;
6635 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6636 compose_convert_header
6637 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6638 strlen(fieldname) + 2, TRUE);
6639 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6640 g_free(buf);
6643 g_free(fieldname_w_colon);
6644 g_string_free(fieldstr, TRUE);
6646 return;
6649 static gchar *compose_get_manual_headers_info(Compose *compose)
6651 GString *sh_header = g_string_new(" ");
6652 GSList *list;
6653 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6655 for (list = compose->header_list; list; list = list->next) {
6656 ComposeHeaderEntry *headerentry;
6657 gchar *tmp;
6658 gchar *headername;
6659 gchar *headername_wcolon;
6660 const gchar *headername_trans;
6661 gchar **string;
6662 gboolean standard_header = FALSE;
6664 headerentry = ((ComposeHeaderEntry *)list->data);
6666 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6667 g_strstrip(tmp);
6668 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6669 g_free(tmp);
6670 continue;
6673 if (!strstr(tmp, ":")) {
6674 headername_wcolon = g_strconcat(tmp, ":", NULL);
6675 headername = g_strdup(tmp);
6676 } else {
6677 headername_wcolon = g_strdup(tmp);
6678 headername = g_strdup(strtok(tmp, ":"));
6680 g_free(tmp);
6682 string = std_headers;
6683 while (*string != NULL) {
6684 headername_trans = prefs_common_translated_header_name(*string);
6685 if (!strcmp(headername_trans, headername_wcolon))
6686 standard_header = TRUE;
6687 string++;
6689 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6690 g_string_append_printf(sh_header, "%s ", headername);
6691 g_free(headername);
6692 g_free(headername_wcolon);
6694 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6695 return g_string_free(sh_header, FALSE);
6698 static gchar *compose_get_header(Compose *compose)
6700 gchar date[RFC822_DATE_BUFFSIZE];
6701 gchar buf[BUFFSIZE];
6702 const gchar *entry_str;
6703 gchar *str;
6704 gchar *name;
6705 GSList *list;
6706 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6707 GString *header;
6708 gchar *from_name = NULL, *from_address = NULL;
6709 gchar *tmp;
6711 cm_return_val_if_fail(compose->account != NULL, NULL);
6712 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6714 header = g_string_sized_new(64);
6716 /* Date */
6717 if (prefs_common.hide_timezone)
6718 get_rfc822_date_hide_tz(date, sizeof(date));
6719 else
6720 get_rfc822_date(date, sizeof(date));
6721 g_string_append_printf(header, "Date: %s\n", date);
6723 /* From */
6725 if (compose->account->name && *compose->account->name) {
6726 gchar *buf;
6727 QUOTE_IF_REQUIRED(buf, compose->account->name);
6728 tmp = g_strdup_printf("%s <%s>",
6729 buf, compose->account->address);
6730 } else {
6731 tmp = g_strdup_printf("%s",
6732 compose->account->address);
6734 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6735 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6736 /* use default */
6737 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6738 from_address = g_strdup(compose->account->address);
6739 } else {
6740 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6741 /* extract name and address */
6742 if (strstr(spec, " <") && strstr(spec, ">")) {
6743 from_address = g_strdup(strrchr(spec, '<')+1);
6744 *(strrchr(from_address, '>')) = '\0';
6745 from_name = g_strdup(spec);
6746 *(strrchr(from_name, '<')) = '\0';
6747 } else {
6748 from_name = NULL;
6749 from_address = g_strdup(spec);
6751 g_free(spec);
6753 g_free(tmp);
6756 if (from_name && *from_name) {
6757 gchar *qname;
6758 compose_convert_header
6759 (compose, buf, sizeof(buf), from_name,
6760 strlen("From: "), TRUE);
6761 QUOTE_IF_REQUIRED(name, buf);
6762 qname = escape_internal_quotes(name, '"');
6764 g_string_append_printf(header, "From: %s <%s>\n",
6765 qname, from_address);
6766 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6767 compose->return_receipt) {
6768 compose_convert_header(compose, buf, sizeof(buf), from_name,
6769 strlen("Disposition-Notification-To: "),
6770 TRUE);
6771 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6773 if (qname != name)
6774 g_free(qname);
6775 } else {
6776 g_string_append_printf(header, "From: %s\n", from_address);
6777 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6778 compose->return_receipt)
6779 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6782 g_free(from_name);
6783 g_free(from_address);
6785 /* To */
6786 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6788 /* Newsgroups */
6789 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6791 /* Cc */
6792 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6794 /* Bcc */
6796 * If this account is a NNTP account remove Bcc header from
6797 * message body since it otherwise will be publicly shown
6799 if (compose->account->protocol != A_NNTP)
6800 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6802 /* Subject */
6803 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6805 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6806 g_strstrip(str);
6807 if (*str != '\0') {
6808 compose_convert_header(compose, buf, sizeof(buf), str,
6809 strlen("Subject: "), FALSE);
6810 g_string_append_printf(header, "Subject: %s\n", buf);
6813 g_free(str);
6815 /* Message-ID */
6816 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6817 g_string_append_printf(header, "Message-ID: <%s>\n",
6818 compose->msgid);
6821 if (compose->remove_references == FALSE) {
6822 /* In-Reply-To */
6823 if (compose->inreplyto && compose->to_list)
6824 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6826 /* References */
6827 if (compose->references)
6828 g_string_append_printf(header, "References: %s\n", compose->references);
6831 /* Followup-To */
6832 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6834 /* Reply-To */
6835 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6837 /* Organization */
6838 if (compose->account->organization &&
6839 strlen(compose->account->organization) &&
6840 !IS_IN_CUSTOM_HEADER("Organization")) {
6841 compose_convert_header(compose, buf, sizeof(buf),
6842 compose->account->organization,
6843 strlen("Organization: "), FALSE);
6844 g_string_append_printf(header, "Organization: %s\n", buf);
6847 /* Program version and system info */
6848 if (compose->account->gen_xmailer &&
6849 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6850 !compose->newsgroup_list) {
6851 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6852 prog_version,
6853 gtk_major_version, gtk_minor_version, gtk_micro_version,
6854 TARGET_ALIAS);
6856 if (compose->account->gen_xmailer &&
6857 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6858 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6859 prog_version,
6860 gtk_major_version, gtk_minor_version, gtk_micro_version,
6861 TARGET_ALIAS);
6864 /* custom headers */
6865 if (compose->account->add_customhdr) {
6866 GSList *cur;
6868 for (cur = compose->account->customhdr_list; cur != NULL;
6869 cur = cur->next) {
6870 CustomHeader *chdr = (CustomHeader *)cur->data;
6872 if (custom_header_is_allowed(chdr->name)
6873 && chdr->value != NULL
6874 && *(chdr->value) != '\0') {
6875 compose_convert_header
6876 (compose, buf, sizeof(buf),
6877 chdr->value,
6878 strlen(chdr->name) + 2, FALSE);
6879 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6884 /* Automatic Faces and X-Faces */
6885 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6886 g_string_append_printf(header, "X-Face: %s\n", buf);
6888 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6889 g_string_append_printf(header, "X-Face: %s\n", buf);
6891 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6892 g_string_append_printf(header, "Face: %s\n", buf);
6894 else if (get_default_face (buf, sizeof(buf)) == 0) {
6895 g_string_append_printf(header, "Face: %s\n", buf);
6898 /* PRIORITY */
6899 switch (compose->priority) {
6900 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6901 "X-Priority: 1 (Highest)\n");
6902 break;
6903 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6904 "X-Priority: 2 (High)\n");
6905 break;
6906 case PRIORITY_NORMAL: break;
6907 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6908 "X-Priority: 4 (Low)\n");
6909 break;
6910 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6911 "X-Priority: 5 (Lowest)\n");
6912 break;
6913 default: debug_print("compose: priority unknown : %d\n",
6914 compose->priority);
6917 /* get special headers */
6918 for (list = compose->header_list; list; list = list->next) {
6919 ComposeHeaderEntry *headerentry;
6920 gchar *tmp;
6921 gchar *headername;
6922 gchar *headername_wcolon;
6923 const gchar *headername_trans;
6924 gchar *headervalue;
6925 gchar **string;
6926 gboolean standard_header = FALSE;
6928 headerentry = ((ComposeHeaderEntry *)list->data);
6930 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6931 g_strstrip(tmp);
6932 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6933 g_free(tmp);
6934 continue;
6937 if (!strstr(tmp, ":")) {
6938 headername_wcolon = g_strconcat(tmp, ":", NULL);
6939 headername = g_strdup(tmp);
6940 } else {
6941 headername_wcolon = g_strdup(tmp);
6942 headername = g_strdup(strtok(tmp, ":"));
6944 g_free(tmp);
6946 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6947 Xstrdup_a(headervalue, entry_str, return NULL);
6948 subst_char(headervalue, '\r', ' ');
6949 subst_char(headervalue, '\n', ' ');
6950 g_strstrip(headervalue);
6951 if (*headervalue != '\0') {
6952 string = std_headers;
6953 while (*string != NULL && !standard_header) {
6954 headername_trans = prefs_common_translated_header_name(*string);
6955 /* support mixed translated and untranslated headers */
6956 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6957 standard_header = TRUE;
6958 string++;
6960 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6961 /* store untranslated header name */
6962 g_string_append_printf(header, "%s %s\n",
6963 compose_untranslated_header_name(headername_wcolon), headervalue);
6966 g_free(headername);
6967 g_free(headername_wcolon);
6970 str = header->str;
6971 g_string_free(header, FALSE);
6973 return str;
6976 #undef IS_IN_CUSTOM_HEADER
6978 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6979 gint header_len, gboolean addr_field)
6981 gchar *tmpstr = NULL;
6982 const gchar *out_codeset = NULL;
6984 cm_return_if_fail(src != NULL);
6985 cm_return_if_fail(dest != NULL);
6987 if (len < 1) return;
6989 tmpstr = g_strdup(src);
6991 subst_char(tmpstr, '\n', ' ');
6992 subst_char(tmpstr, '\r', ' ');
6993 g_strchomp(tmpstr);
6995 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6996 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6997 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6998 g_free(tmpstr);
6999 tmpstr = mybuf;
7002 codeconv_set_strict(TRUE);
7003 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7004 conv_get_charset_str(compose->out_encoding));
7005 codeconv_set_strict(FALSE);
7007 if (!dest || *dest == '\0') {
7008 gchar *test_conv_global_out = NULL;
7009 gchar *test_conv_reply = NULL;
7011 /* automatic mode. be automatic. */
7012 codeconv_set_strict(TRUE);
7014 out_codeset = conv_get_outgoing_charset_str();
7015 if (out_codeset) {
7016 debug_print("trying to convert to %s\n", out_codeset);
7017 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7020 if (!test_conv_global_out && compose->orig_charset
7021 && strcmp(compose->orig_charset, CS_US_ASCII)) {
7022 out_codeset = compose->orig_charset;
7023 debug_print("failure; trying to convert to %s\n", out_codeset);
7024 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7027 if (!test_conv_global_out && !test_conv_reply) {
7028 /* we're lost */
7029 out_codeset = CS_INTERNAL;
7030 debug_print("finally using %s\n", out_codeset);
7032 g_free(test_conv_global_out);
7033 g_free(test_conv_reply);
7034 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7035 out_codeset);
7036 codeconv_set_strict(FALSE);
7038 g_free(tmpstr);
7041 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7043 gchar *address;
7045 cm_return_if_fail(user_data != NULL);
7047 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7048 g_strstrip(address);
7049 if (*address != '\0') {
7050 gchar *name = procheader_get_fromname(address);
7051 extract_address(address);
7052 #ifndef USE_ALT_ADDRBOOK
7053 addressbook_add_contact(name, address, NULL, NULL);
7054 #else
7055 debug_print("%s: %s\n", name, address);
7056 if (addressadd_selection(name, address, NULL, NULL)) {
7057 debug_print( "addressbook_add_contact - added\n" );
7059 #endif
7061 g_free(address);
7064 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7066 GtkWidget *menuitem;
7067 gchar *address;
7069 cm_return_if_fail(menu != NULL);
7070 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7072 menuitem = gtk_separator_menu_item_new();
7073 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7074 gtk_widget_show(menuitem);
7076 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7077 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7079 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7080 g_strstrip(address);
7081 if (*address == '\0') {
7082 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7085 g_signal_connect(G_OBJECT(menuitem), "activate",
7086 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7087 gtk_widget_show(menuitem);
7090 void compose_add_extra_header(gchar *header, GtkListStore *model)
7092 GtkTreeIter iter;
7093 if (strcmp(header, "")) {
7094 COMBOBOX_ADD(model, header, COMPOSE_TO);
7098 void compose_add_extra_header_entries(GtkListStore *model)
7100 FILE *exh;
7101 gchar *exhrc;
7102 gchar buf[BUFFSIZE];
7103 gint lastc;
7105 if (extra_headers == NULL) {
7106 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7107 if ((exh = claws_fopen(exhrc, "rb")) == NULL) {
7108 debug_print("extra headers file not found\n");
7109 goto extra_headers_done;
7111 while (claws_fgets(buf, BUFFSIZE, exh) != NULL) {
7112 lastc = strlen(buf) - 1; /* remove trailing control chars */
7113 while (lastc >= 0 && buf[lastc] != ':')
7114 buf[lastc--] = '\0';
7115 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7116 buf[lastc] = '\0'; /* remove trailing : for comparison */
7117 if (custom_header_is_allowed(buf)) {
7118 buf[lastc] = ':';
7119 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7121 else
7122 g_message("disallowed extra header line: %s\n", buf);
7124 else {
7125 if (buf[0] != '#')
7126 g_message("invalid extra header line: %s\n", buf);
7129 claws_fclose(exh);
7130 extra_headers_done:
7131 g_free(exhrc);
7132 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7133 extra_headers = g_slist_reverse(extra_headers);
7135 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7138 #ifdef USE_LDAP
7139 static void _ldap_srv_func(gpointer data, gpointer user_data)
7141 LdapServer *server = (LdapServer *)data;
7142 gboolean *enable = (gboolean *)user_data;
7144 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7145 server->searchFlag = *enable;
7147 #endif
7149 static void compose_create_header_entry(Compose *compose)
7151 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7153 GtkWidget *combo;
7154 GtkWidget *entry;
7155 GtkWidget *button;
7156 GtkWidget *hbox;
7157 gchar **string;
7158 const gchar *header = NULL;
7159 ComposeHeaderEntry *headerentry;
7160 gboolean standard_header = FALSE;
7161 GtkListStore *model;
7162 GtkTreeIter iter;
7164 headerentry = g_new0(ComposeHeaderEntry, 1);
7166 /* Combo box model */
7167 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7168 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7169 COMPOSE_TO);
7170 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7171 COMPOSE_CC);
7172 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7173 COMPOSE_BCC);
7174 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7175 COMPOSE_NEWSGROUPS);
7176 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7177 COMPOSE_REPLYTO);
7178 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7179 COMPOSE_FOLLOWUPTO);
7180 compose_add_extra_header_entries(model);
7182 /* Combo box */
7183 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7184 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7185 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7186 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7187 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7188 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7189 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7190 G_CALLBACK(compose_grab_focus_cb), compose);
7191 gtk_widget_show(combo);
7193 /* Putting only the combobox child into focus chain of its parent causes
7194 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7195 * This eliminates need to pres Tab twice in order to really get from the
7196 * combobox to next widget. */
7197 GList *l = NULL;
7198 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7199 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7200 g_list_free(l);
7202 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7203 compose->header_nextrow, compose->header_nextrow+1,
7204 GTK_SHRINK, GTK_FILL, 0, 0);
7205 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7206 const gchar *last_header_entry = gtk_entry_get_text(
7207 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7208 string = headers;
7209 while (*string != NULL) {
7210 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7211 standard_header = TRUE;
7212 string++;
7214 if (standard_header)
7215 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7217 if (!compose->header_last || !standard_header) {
7218 switch(compose->account->protocol) {
7219 case A_NNTP:
7220 header = prefs_common_translated_header_name("Newsgroups:");
7221 break;
7222 default:
7223 header = prefs_common_translated_header_name("To:");
7224 break;
7227 if (header)
7228 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7230 gtk_editable_set_editable(
7231 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7232 prefs_common.type_any_header);
7234 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7235 G_CALLBACK(compose_grab_focus_cb), compose);
7237 /* Entry field with cleanup button */
7238 button = gtk_button_new();
7239 gtk_button_set_image(GTK_BUTTON(button),
7240 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7241 gtk_widget_show(button);
7242 CLAWS_SET_TIP(button,
7243 _("Delete entry contents"));
7244 entry = gtk_entry_new();
7245 gtk_widget_show(entry);
7246 CLAWS_SET_TIP(entry,
7247 _("Use <tab> to autocomplete from addressbook"));
7248 hbox = gtk_hbox_new (FALSE, 0);
7249 gtk_widget_show(hbox);
7250 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7251 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7252 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7253 compose->header_nextrow, compose->header_nextrow+1,
7254 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7256 g_signal_connect(G_OBJECT(entry), "key-press-event",
7257 G_CALLBACK(compose_headerentry_key_press_event_cb),
7258 headerentry);
7259 g_signal_connect(G_OBJECT(entry), "changed",
7260 G_CALLBACK(compose_headerentry_changed_cb),
7261 headerentry);
7262 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7263 G_CALLBACK(compose_grab_focus_cb), compose);
7265 g_signal_connect(G_OBJECT(button), "clicked",
7266 G_CALLBACK(compose_headerentry_button_clicked_cb),
7267 headerentry);
7269 /* email dnd */
7270 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7271 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7272 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7273 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7274 G_CALLBACK(compose_header_drag_received_cb),
7275 entry);
7276 g_signal_connect(G_OBJECT(entry), "drag-drop",
7277 G_CALLBACK(compose_drag_drop),
7278 compose);
7279 g_signal_connect(G_OBJECT(entry), "populate-popup",
7280 G_CALLBACK(compose_entry_popup_extend),
7281 NULL);
7283 #ifdef USE_LDAP
7284 #ifndef PASSWORD_CRYPTO_OLD
7285 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7286 if (pwd_servers != NULL && master_passphrase() == NULL) {
7287 gboolean enable = FALSE;
7288 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7289 /* Temporarily disable password-protected LDAP servers,
7290 * because user did not provide a master passphrase.
7291 * We can safely enable searchFlag on all servers in this list
7292 * later, since addrindex_get_password_protected_ldap_servers()
7293 * includes servers which have it enabled initially. */
7294 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7295 compose->passworded_ldap_servers = pwd_servers;
7297 #endif /* PASSWORD_CRYPTO_OLD */
7298 #endif /* USE_LDAP */
7300 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7302 headerentry->compose = compose;
7303 headerentry->combo = combo;
7304 headerentry->entry = entry;
7305 headerentry->button = button;
7306 headerentry->hbox = hbox;
7307 headerentry->headernum = compose->header_nextrow;
7308 headerentry->type = PREF_NONE;
7310 compose->header_nextrow++;
7311 compose->header_last = headerentry;
7312 compose->header_list =
7313 g_slist_append(compose->header_list,
7314 headerentry);
7317 static void compose_add_header_entry(Compose *compose, const gchar *header,
7318 gchar *text, ComposePrefType pref_type)
7320 ComposeHeaderEntry *last_header = compose->header_last;
7321 gchar *tmp = g_strdup(text), *email;
7322 gboolean replyto_hdr;
7324 replyto_hdr = (!strcasecmp(header,
7325 prefs_common_translated_header_name("Reply-To:")) ||
7326 !strcasecmp(header,
7327 prefs_common_translated_header_name("Followup-To:")) ||
7328 !strcasecmp(header,
7329 prefs_common_translated_header_name("In-Reply-To:")));
7331 extract_address(tmp);
7332 email = g_utf8_strdown(tmp, -1);
7334 if (replyto_hdr == FALSE &&
7335 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7337 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7338 header, text, (gint) pref_type);
7339 g_free(email);
7340 g_free(tmp);
7341 return;
7344 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7345 gtk_entry_set_text(GTK_ENTRY(
7346 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7347 else
7348 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7349 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7350 last_header->type = pref_type;
7352 if (replyto_hdr == FALSE)
7353 g_hash_table_insert(compose->email_hashtable, email,
7354 GUINT_TO_POINTER(1));
7355 else
7356 g_free(email);
7358 g_free(tmp);
7361 static void compose_destroy_headerentry(Compose *compose,
7362 ComposeHeaderEntry *headerentry)
7364 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7365 gchar *email;
7367 extract_address(text);
7368 email = g_utf8_strdown(text, -1);
7369 g_hash_table_remove(compose->email_hashtable, email);
7370 g_free(text);
7371 g_free(email);
7373 gtk_widget_destroy(headerentry->combo);
7374 gtk_widget_destroy(headerentry->entry);
7375 gtk_widget_destroy(headerentry->button);
7376 gtk_widget_destroy(headerentry->hbox);
7377 g_free(headerentry);
7380 static void compose_remove_header_entries(Compose *compose)
7382 GSList *list;
7383 for (list = compose->header_list; list; list = list->next)
7384 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7386 compose->header_last = NULL;
7387 g_slist_free(compose->header_list);
7388 compose->header_list = NULL;
7389 compose->header_nextrow = 1;
7390 compose_create_header_entry(compose);
7393 static GtkWidget *compose_create_header(Compose *compose)
7395 GtkWidget *from_optmenu_hbox;
7396 GtkWidget *header_table_main;
7397 GtkWidget *header_scrolledwin;
7398 GtkWidget *header_table;
7400 /* parent with account selection and from header */
7401 header_table_main = gtk_table_new(2, 2, FALSE);
7402 gtk_widget_show(header_table_main);
7403 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7405 from_optmenu_hbox = compose_account_option_menu_create(compose);
7406 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7407 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7409 /* child with header labels and entries */
7410 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7411 gtk_widget_show(header_scrolledwin);
7412 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7414 header_table = gtk_table_new(2, 2, FALSE);
7415 gtk_widget_show(header_table);
7416 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7417 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7418 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7419 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7420 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7422 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7423 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7425 compose->header_table = header_table;
7426 compose->header_list = NULL;
7427 compose->header_nextrow = 0;
7429 compose_create_header_entry(compose);
7431 compose->table = NULL;
7433 return header_table_main;
7436 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7438 Compose *compose = (Compose *)data;
7439 GdkEventButton event;
7441 event.button = 3;
7442 event.time = gtk_get_current_event_time();
7444 return attach_button_pressed(compose->attach_clist, &event, compose);
7447 static GtkWidget *compose_create_attach(Compose *compose)
7449 GtkWidget *attach_scrwin;
7450 GtkWidget *attach_clist;
7452 GtkListStore *store;
7453 GtkCellRenderer *renderer;
7454 GtkTreeViewColumn *column;
7455 GtkTreeSelection *selection;
7457 /* attachment list */
7458 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7459 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7460 GTK_POLICY_AUTOMATIC,
7461 GTK_POLICY_AUTOMATIC);
7462 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7464 store = gtk_list_store_new(N_ATTACH_COLS,
7465 G_TYPE_STRING,
7466 G_TYPE_STRING,
7467 G_TYPE_STRING,
7468 G_TYPE_STRING,
7469 G_TYPE_POINTER,
7470 G_TYPE_AUTO_POINTER,
7471 -1);
7472 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7473 (GTK_TREE_MODEL(store)));
7474 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7475 g_object_unref(store);
7477 renderer = gtk_cell_renderer_text_new();
7478 column = gtk_tree_view_column_new_with_attributes
7479 (_("Mime type"), renderer, "text",
7480 COL_MIMETYPE, NULL);
7481 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7483 renderer = gtk_cell_renderer_text_new();
7484 column = gtk_tree_view_column_new_with_attributes
7485 (_("Size"), renderer, "text",
7486 COL_SIZE, NULL);
7487 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7489 renderer = gtk_cell_renderer_text_new();
7490 column = gtk_tree_view_column_new_with_attributes
7491 (_("Name"), renderer, "text",
7492 COL_NAME, NULL);
7493 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7495 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7496 prefs_common.use_stripes_everywhere);
7497 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7498 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7500 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7501 G_CALLBACK(attach_selected), compose);
7502 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7503 G_CALLBACK(attach_button_pressed), compose);
7504 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7505 G_CALLBACK(popup_attach_button_pressed), compose);
7506 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7507 G_CALLBACK(attach_key_pressed), compose);
7509 /* drag and drop */
7510 gtk_drag_dest_set(attach_clist,
7511 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7512 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7513 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7514 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7515 G_CALLBACK(compose_attach_drag_received_cb),
7516 compose);
7517 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7518 G_CALLBACK(compose_drag_drop),
7519 compose);
7521 compose->attach_scrwin = attach_scrwin;
7522 compose->attach_clist = attach_clist;
7524 return attach_scrwin;
7527 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7529 static GtkWidget *compose_create_others(Compose *compose)
7531 GtkWidget *table;
7532 GtkWidget *savemsg_checkbtn;
7533 GtkWidget *savemsg_combo;
7534 GtkWidget *savemsg_select;
7536 guint rowcount = 0;
7537 gchar *folderidentifier;
7539 /* Table for settings */
7540 table = gtk_table_new(3, 1, FALSE);
7541 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7542 gtk_widget_show(table);
7543 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7544 rowcount = 0;
7546 /* Save Message to folder */
7547 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7548 gtk_widget_show(savemsg_checkbtn);
7549 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7550 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7551 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7554 savemsg_combo = gtk_combo_box_text_new_with_entry();
7555 compose->savemsg_checkbtn = savemsg_checkbtn;
7556 compose->savemsg_combo = savemsg_combo;
7557 gtk_widget_show(savemsg_combo);
7559 if (prefs_common.compose_save_to_history)
7560 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7561 prefs_common.compose_save_to_history);
7562 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7563 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7564 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7565 G_CALLBACK(compose_grab_focus_cb), compose);
7566 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7567 if (compose->account->set_sent_folder || prefs_common.savemsg)
7568 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), TRUE);
7569 else
7570 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), FALSE);
7571 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), TRUE);
7572 folderidentifier = folder_item_get_identifier(account_get_special_folder
7573 (compose->account, F_OUTBOX));
7574 compose_set_save_to(compose, folderidentifier);
7575 g_free(folderidentifier);
7578 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7579 gtk_widget_show(savemsg_select);
7580 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7581 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7582 G_CALLBACK(compose_savemsg_select_cb),
7583 compose);
7585 return table;
7588 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7590 FolderItem *dest;
7591 gchar * path;
7593 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7594 _("Select folder to save message to"));
7595 if (!dest) return;
7597 path = folder_item_get_identifier(dest);
7599 compose_set_save_to(compose, path);
7600 g_free(path);
7603 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7604 GdkAtom clip, GtkTextIter *insert_place);
7607 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7608 Compose *compose)
7610 gint prev_autowrap;
7611 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7612 #if USE_ENCHANT
7613 if (event->button == 3) {
7614 GtkTextIter iter;
7615 GtkTextIter sel_start, sel_end;
7616 gboolean stuff_selected;
7617 gint x, y;
7618 /* move the cursor to allow GtkAspell to check the word
7619 * under the mouse */
7620 if (event->x && event->y) {
7621 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7622 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7623 &x, &y);
7624 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7625 &iter, x, y);
7626 } else {
7627 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7628 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7630 /* get selection */
7631 stuff_selected = gtk_text_buffer_get_selection_bounds(
7632 buffer,
7633 &sel_start, &sel_end);
7635 gtk_text_buffer_place_cursor (buffer, &iter);
7636 /* reselect stuff */
7637 if (stuff_selected
7638 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7639 gtk_text_buffer_select_range(buffer,
7640 &sel_start, &sel_end);
7642 return FALSE; /* pass the event so that the right-click goes through */
7644 #endif
7645 if (event->button == 2) {
7646 GtkTextIter iter;
7647 gint x, y;
7648 BLOCK_WRAP();
7650 /* get the middle-click position to paste at the correct place */
7651 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7652 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7653 &x, &y);
7654 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7655 &iter, x, y);
7657 entry_paste_clipboard(compose, text,
7658 prefs_common.linewrap_pastes,
7659 GDK_SELECTION_PRIMARY, &iter);
7660 UNBLOCK_WRAP();
7661 return TRUE;
7663 return FALSE;
7666 #if USE_ENCHANT
7667 static void compose_spell_menu_changed(void *data)
7669 Compose *compose = (Compose *)data;
7670 GSList *items;
7671 GtkWidget *menuitem;
7672 GtkWidget *parent_item;
7673 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7674 GSList *spell_menu;
7676 if (compose->gtkaspell == NULL)
7677 return;
7679 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7680 "/Menu/Spelling/Options");
7682 /* setting the submenu removes /Spelling/Options from the factory
7683 * so we need to save it */
7685 if (parent_item == NULL) {
7686 parent_item = compose->aspell_options_menu;
7687 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7688 } else
7689 compose->aspell_options_menu = parent_item;
7691 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7693 spell_menu = g_slist_reverse(spell_menu);
7694 for (items = spell_menu;
7695 items; items = items->next) {
7696 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7697 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7698 gtk_widget_show(GTK_WIDGET(menuitem));
7700 g_slist_free(spell_menu);
7702 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7703 gtk_widget_show(parent_item);
7706 static void compose_dict_changed(void *data)
7708 Compose *compose = (Compose *) data;
7710 if(!compose->gtkaspell)
7711 return;
7712 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7713 return;
7715 gtkaspell_highlight_all(compose->gtkaspell);
7716 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7718 #endif
7720 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7722 Compose *compose = (Compose *)data;
7723 GdkEventButton event;
7725 event.button = 3;
7726 event.time = gtk_get_current_event_time();
7727 event.x = 0;
7728 event.y = 0;
7730 return text_clicked(compose->text, &event, compose);
7733 static gboolean compose_force_window_origin = TRUE;
7734 static Compose *compose_create(PrefsAccount *account,
7735 FolderItem *folder,
7736 ComposeMode mode,
7737 gboolean batch)
7739 Compose *compose;
7740 GtkWidget *window;
7741 GtkWidget *vbox;
7742 GtkWidget *menubar;
7743 GtkWidget *handlebox;
7745 GtkWidget *notebook;
7747 GtkWidget *attach_hbox;
7748 GtkWidget *attach_lab1;
7749 GtkWidget *attach_lab2;
7751 GtkWidget *vbox2;
7753 GtkWidget *label;
7754 GtkWidget *subject_hbox;
7755 GtkWidget *subject_frame;
7756 GtkWidget *subject_entry;
7757 GtkWidget *subject;
7758 GtkWidget *paned;
7760 GtkWidget *edit_vbox;
7761 GtkWidget *ruler_hbox;
7762 GtkWidget *ruler;
7763 GtkWidget *scrolledwin;
7764 GtkWidget *text;
7765 GtkTextBuffer *buffer;
7766 GtkClipboard *clipboard;
7768 UndoMain *undostruct;
7770 GtkWidget *popupmenu;
7771 GtkWidget *tmpl_menu;
7772 GtkActionGroup *action_group = NULL;
7774 #if USE_ENCHANT
7775 GtkAspell * gtkaspell = NULL;
7776 #endif
7778 static GdkGeometry geometry;
7780 cm_return_val_if_fail(account != NULL, NULL);
7782 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7783 &default_header_bgcolor);
7784 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7785 &default_header_color);
7787 debug_print("Creating compose window...\n");
7788 compose = g_new0(Compose, 1);
7790 compose->batch = batch;
7791 compose->account = account;
7792 compose->folder = folder;
7794 compose->mutex = cm_mutex_new();
7795 compose->set_cursor_pos = -1;
7797 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7799 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7800 gtk_widget_set_size_request(window, prefs_common.compose_width,
7801 prefs_common.compose_height);
7803 if (!geometry.max_width) {
7804 geometry.max_width = gdk_screen_width();
7805 geometry.max_height = gdk_screen_height();
7808 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7809 &geometry, GDK_HINT_MAX_SIZE);
7810 if (!geometry.min_width) {
7811 geometry.min_width = 600;
7812 geometry.min_height = 440;
7814 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7815 &geometry, GDK_HINT_MIN_SIZE);
7817 #ifndef GENERIC_UMPC
7818 if (compose_force_window_origin)
7819 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7820 prefs_common.compose_y);
7821 #endif
7822 g_signal_connect(G_OBJECT(window), "delete_event",
7823 G_CALLBACK(compose_delete_cb), compose);
7824 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7825 gtk_widget_realize(window);
7827 gtkut_widget_set_composer_icon(window);
7829 vbox = gtk_vbox_new(FALSE, 0);
7830 gtk_container_add(GTK_CONTAINER(window), vbox);
7832 compose->ui_manager = gtk_ui_manager_new();
7833 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7834 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7835 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7836 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7837 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7838 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7839 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7840 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7841 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7842 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7847 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7848 #ifdef USE_ENCHANT
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7850 #endif
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7855 /* Compose menu */
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7870 /* Edit menu */
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7898 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7908 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7914 #if USE_ENCHANT
7915 /* Spelling menu */
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7922 #endif
7924 /* Options menu */
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7935 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7938 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7940 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7941 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7942 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7943 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7944 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7946 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7947 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7948 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7949 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7950 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7952 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7954 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7955 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7956 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7957 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7958 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7960 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7961 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)
7962 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)
7963 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7965 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7967 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7968 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)
7969 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)
7971 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7973 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7974 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)
7975 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7977 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7978 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)
7979 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7981 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7983 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7984 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)
7985 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7986 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7987 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7988 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7990 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7991 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)
7992 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)
7993 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7994 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7996 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7997 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7998 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7999 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
8000 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
8001 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
8003 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
8004 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
8005 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)
8007 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
8008 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
8009 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
8010 /* phew. */
8012 /* Tools menu */
8013 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
8014 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
8015 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
8016 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8017 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
8018 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8020 /* Help menu */
8021 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
8023 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
8024 gtk_widget_show_all(menubar);
8026 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
8027 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
8029 if (prefs_common.toolbar_detachable) {
8030 handlebox = gtk_handle_box_new();
8031 } else {
8032 handlebox = gtk_hbox_new(FALSE, 0);
8034 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8036 gtk_widget_realize(handlebox);
8037 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8038 (gpointer)compose);
8040 vbox2 = gtk_vbox_new(FALSE, 2);
8041 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8042 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8044 /* Notebook */
8045 notebook = gtk_notebook_new();
8046 gtk_widget_show(notebook);
8048 /* header labels and entries */
8049 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8050 compose_create_header(compose),
8051 gtk_label_new_with_mnemonic(_("Hea_der")));
8052 /* attachment list */
8053 attach_hbox = gtk_hbox_new(FALSE, 0);
8054 gtk_widget_show(attach_hbox);
8056 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8057 gtk_widget_show(attach_lab1);
8058 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8060 attach_lab2 = gtk_label_new("");
8061 gtk_widget_show(attach_lab2);
8062 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8064 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8065 compose_create_attach(compose),
8066 attach_hbox);
8067 /* Others Tab */
8068 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8069 compose_create_others(compose),
8070 gtk_label_new_with_mnemonic(_("Othe_rs")));
8072 /* Subject */
8073 subject_hbox = gtk_hbox_new(FALSE, 0);
8074 gtk_widget_show(subject_hbox);
8076 subject_frame = gtk_frame_new(NULL);
8077 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8078 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8079 gtk_widget_show(subject_frame);
8081 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8082 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8083 gtk_widget_show(subject);
8085 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8086 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8087 gtk_widget_show(label);
8089 #ifdef USE_ENCHANT
8090 subject_entry = claws_spell_entry_new();
8091 #else
8092 subject_entry = gtk_entry_new();
8093 #endif
8094 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8095 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8096 G_CALLBACK(compose_grab_focus_cb), compose);
8097 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8098 gtk_widget_show(subject_entry);
8099 compose->subject_entry = subject_entry;
8100 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8102 edit_vbox = gtk_vbox_new(FALSE, 0);
8104 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8106 /* ruler */
8107 ruler_hbox = gtk_hbox_new(FALSE, 0);
8108 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8110 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8111 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8112 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8113 BORDER_WIDTH);
8115 /* text widget */
8116 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8117 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8118 GTK_POLICY_AUTOMATIC,
8119 GTK_POLICY_AUTOMATIC);
8120 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8121 GTK_SHADOW_IN);
8122 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8124 text = gtk_text_view_new();
8125 if (prefs_common.show_compose_margin) {
8126 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8127 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8129 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8130 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8131 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8132 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8133 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8135 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8136 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8137 G_CALLBACK(compose_edit_size_alloc),
8138 ruler);
8139 g_signal_connect(G_OBJECT(buffer), "changed",
8140 G_CALLBACK(compose_changed_cb), compose);
8141 g_signal_connect(G_OBJECT(text), "grab_focus",
8142 G_CALLBACK(compose_grab_focus_cb), compose);
8143 g_signal_connect(G_OBJECT(buffer), "insert_text",
8144 G_CALLBACK(text_inserted), compose);
8145 g_signal_connect(G_OBJECT(text), "button_press_event",
8146 G_CALLBACK(text_clicked), compose);
8147 g_signal_connect(G_OBJECT(text), "popup-menu",
8148 G_CALLBACK(compose_popup_menu), compose);
8149 g_signal_connect(G_OBJECT(subject_entry), "changed",
8150 G_CALLBACK(compose_changed_cb), compose);
8151 g_signal_connect(G_OBJECT(subject_entry), "activate",
8152 G_CALLBACK(compose_subject_entry_activated), compose);
8154 /* drag and drop */
8155 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8156 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8157 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8158 g_signal_connect(G_OBJECT(text), "drag_data_received",
8159 G_CALLBACK(compose_insert_drag_received_cb),
8160 compose);
8161 g_signal_connect(G_OBJECT(text), "drag-drop",
8162 G_CALLBACK(compose_drag_drop),
8163 compose);
8164 g_signal_connect(G_OBJECT(text), "key-press-event",
8165 G_CALLBACK(completion_set_focus_to_subject),
8166 compose);
8167 gtk_widget_show_all(vbox);
8169 /* pane between attach clist and text */
8170 paned = gtk_vpaned_new();
8171 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8172 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8173 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8174 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8175 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8176 G_CALLBACK(compose_notebook_size_alloc), paned);
8178 gtk_widget_show_all(paned);
8181 if (prefs_common.textfont) {
8182 PangoFontDescription *font_desc;
8184 font_desc = pango_font_description_from_string
8185 (prefs_common.textfont);
8186 if (font_desc) {
8187 gtk_widget_modify_font(text, font_desc);
8188 pango_font_description_free(font_desc);
8192 gtk_action_group_add_actions(action_group, compose_popup_entries,
8193 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8194 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8195 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8196 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8197 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8198 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8199 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8201 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8203 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8204 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8205 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8207 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8209 undostruct = undo_init(text);
8210 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8211 compose);
8213 address_completion_start(window);
8215 compose->window = window;
8216 compose->vbox = vbox;
8217 compose->menubar = menubar;
8218 compose->handlebox = handlebox;
8220 compose->vbox2 = vbox2;
8222 compose->paned = paned;
8224 compose->attach_label = attach_lab2;
8226 compose->notebook = notebook;
8227 compose->edit_vbox = edit_vbox;
8228 compose->ruler_hbox = ruler_hbox;
8229 compose->ruler = ruler;
8230 compose->scrolledwin = scrolledwin;
8231 compose->text = text;
8233 compose->focused_editable = NULL;
8235 compose->popupmenu = popupmenu;
8237 compose->tmpl_menu = tmpl_menu;
8239 compose->mode = mode;
8240 compose->rmode = mode;
8242 compose->targetinfo = NULL;
8243 compose->replyinfo = NULL;
8244 compose->fwdinfo = NULL;
8246 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8247 g_str_equal, (GDestroyNotify) g_free, NULL);
8249 compose->replyto = NULL;
8250 compose->cc = NULL;
8251 compose->bcc = NULL;
8252 compose->followup_to = NULL;
8254 compose->ml_post = NULL;
8256 compose->inreplyto = NULL;
8257 compose->references = NULL;
8258 compose->msgid = NULL;
8259 compose->boundary = NULL;
8261 compose->autowrap = prefs_common.autowrap;
8262 compose->autoindent = prefs_common.auto_indent;
8263 compose->use_signing = FALSE;
8264 compose->use_encryption = FALSE;
8265 compose->privacy_system = NULL;
8266 compose->encdata = NULL;
8268 compose->modified = FALSE;
8270 compose->return_receipt = FALSE;
8272 compose->to_list = NULL;
8273 compose->newsgroup_list = NULL;
8275 compose->undostruct = undostruct;
8277 compose->sig_str = NULL;
8279 compose->exteditor_file = NULL;
8280 compose->exteditor_pid = -1;
8281 compose->exteditor_tag = -1;
8282 compose->exteditor_socket = NULL;
8283 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8285 compose->folder_update_callback_id =
8286 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8287 compose_update_folder_hook,
8288 (gpointer) compose);
8290 #if USE_ENCHANT
8291 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8292 if (mode != COMPOSE_REDIRECT) {
8293 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8294 strcmp(prefs_common.dictionary, "")) {
8295 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8296 prefs_common.alt_dictionary,
8297 conv_get_locale_charset_str(),
8298 prefs_common.color[COL_MISSPELLED],
8299 prefs_common.check_while_typing,
8300 prefs_common.recheck_when_changing_dict,
8301 prefs_common.use_alternate,
8302 prefs_common.use_both_dicts,
8303 GTK_TEXT_VIEW(text),
8304 GTK_WINDOW(compose->window),
8305 compose_dict_changed,
8306 compose_spell_menu_changed,
8307 compose);
8308 if (!gtkaspell) {
8309 alertpanel_error(_("Spell checker could not "
8310 "be started.\n%s"),
8311 gtkaspell_checkers_strerror());
8312 gtkaspell_checkers_reset_error();
8313 } else {
8314 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8318 compose->gtkaspell = gtkaspell;
8319 compose_spell_menu_changed(compose);
8320 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8321 #endif
8323 compose_select_account(compose, account, TRUE);
8325 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8326 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8328 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8329 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8331 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8332 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8334 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8335 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8337 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8338 if (account->protocol != A_NNTP)
8339 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8340 prefs_common_translated_header_name("To:"));
8341 else
8342 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8343 prefs_common_translated_header_name("Newsgroups:"));
8345 #ifndef USE_ALT_ADDRBOOK
8346 addressbook_set_target_compose(compose);
8347 #endif
8348 if (mode != COMPOSE_REDIRECT)
8349 compose_set_template_menu(compose);
8350 else {
8351 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8354 compose_list = g_list_append(compose_list, compose);
8356 if (!prefs_common.show_ruler)
8357 gtk_widget_hide(ruler_hbox);
8359 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8361 /* Priority */
8362 compose->priority = PRIORITY_NORMAL;
8363 compose_update_priority_menu_item(compose);
8365 compose_set_out_encoding(compose);
8367 /* Actions menu */
8368 compose_update_actions_menu(compose);
8370 /* Privacy Systems menu */
8371 compose_update_privacy_systems_menu(compose);
8372 compose_activate_privacy_system(compose, account, TRUE);
8374 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8375 if (batch) {
8376 gtk_widget_realize(window);
8377 } else {
8378 gtk_widget_show(window);
8381 return compose;
8384 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8386 GList *accounts;
8387 GtkWidget *hbox;
8388 GtkWidget *optmenu;
8389 GtkWidget *optmenubox;
8390 GtkWidget *fromlabel;
8391 GtkListStore *menu;
8392 GtkTreeIter iter;
8393 GtkWidget *from_name = NULL;
8395 gint num = 0, def_menu = 0;
8397 accounts = account_get_list();
8398 cm_return_val_if_fail(accounts != NULL, NULL);
8400 optmenubox = gtk_event_box_new();
8401 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8402 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8404 hbox = gtk_hbox_new(FALSE, 4);
8405 from_name = gtk_entry_new();
8407 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8408 G_CALLBACK(compose_grab_focus_cb), compose);
8409 g_signal_connect_after(G_OBJECT(from_name), "activate",
8410 G_CALLBACK(from_name_activate_cb), optmenu);
8412 for (; accounts != NULL; accounts = accounts->next, num++) {
8413 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8414 gchar *name, *from = NULL;
8416 if (ac == compose->account) def_menu = num;
8418 name = g_markup_printf_escaped("<i>%s</i>",
8419 ac->account_name);
8421 if (ac == compose->account) {
8422 if (ac->name && *ac->name) {
8423 gchar *buf;
8424 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8425 from = g_strdup_printf("%s <%s>",
8426 buf, ac->address);
8427 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8428 } else {
8429 from = g_strdup_printf("%s",
8430 ac->address);
8431 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8433 if (cur_account != compose->account) {
8434 gtk_widget_modify_base(
8435 GTK_WIDGET(from_name),
8436 GTK_STATE_NORMAL, &default_header_bgcolor);
8437 gtk_widget_modify_text(
8438 GTK_WIDGET(from_name),
8439 GTK_STATE_NORMAL, &default_header_color);
8442 COMBOBOX_ADD(menu, name, ac->account_id);
8443 g_free(name);
8444 g_free(from);
8447 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8449 g_signal_connect(G_OBJECT(optmenu), "changed",
8450 G_CALLBACK(account_activated),
8451 compose);
8452 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8453 G_CALLBACK(compose_entry_popup_extend),
8454 NULL);
8456 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8457 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8459 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8460 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8461 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8463 /* Putting only the GtkEntry into focus chain of parent hbox causes
8464 * the account selector combobox next to it to be unreachable when
8465 * navigating widgets in GtkTable with up/down arrow keys.
8466 * Note: gtk_widget_set_can_focus() was not enough. */
8467 GList *l = NULL;
8468 l = g_list_prepend(l, from_name);
8469 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8470 g_list_free(l);
8472 CLAWS_SET_TIP(optmenubox,
8473 _("Account to use for this email"));
8474 CLAWS_SET_TIP(from_name,
8475 _("Sender address to be used"));
8477 compose->account_combo = optmenu;
8478 compose->from_name = from_name;
8480 return hbox;
8483 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8485 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8486 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8487 Compose *compose = (Compose *) data;
8488 if (active) {
8489 compose->priority = value;
8493 static void compose_reply_change_mode(Compose *compose,
8494 ComposeMode action)
8496 gboolean was_modified = compose->modified;
8498 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8500 cm_return_if_fail(compose->replyinfo != NULL);
8502 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8503 ml = TRUE;
8504 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8505 followup = TRUE;
8506 if (action == COMPOSE_REPLY_TO_ALL)
8507 all = TRUE;
8508 if (action == COMPOSE_REPLY_TO_SENDER)
8509 sender = TRUE;
8510 if (action == COMPOSE_REPLY_TO_LIST)
8511 ml = TRUE;
8513 compose_remove_header_entries(compose);
8514 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8515 if (compose->account->set_autocc && compose->account->auto_cc)
8516 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8518 if (compose->account->set_autobcc && compose->account->auto_bcc)
8519 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8521 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8522 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8523 compose_show_first_last_header(compose, TRUE);
8524 compose->modified = was_modified;
8525 compose_set_title(compose);
8528 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8530 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8531 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8532 Compose *compose = (Compose *) data;
8534 if (active)
8535 compose_reply_change_mode(compose, value);
8538 static void compose_update_priority_menu_item(Compose * compose)
8540 GtkWidget *menuitem = NULL;
8541 switch (compose->priority) {
8542 case PRIORITY_HIGHEST:
8543 menuitem = gtk_ui_manager_get_widget
8544 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8545 break;
8546 case PRIORITY_HIGH:
8547 menuitem = gtk_ui_manager_get_widget
8548 (compose->ui_manager, "/Menu/Options/Priority/High");
8549 break;
8550 case PRIORITY_NORMAL:
8551 menuitem = gtk_ui_manager_get_widget
8552 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8553 break;
8554 case PRIORITY_LOW:
8555 menuitem = gtk_ui_manager_get_widget
8556 (compose->ui_manager, "/Menu/Options/Priority/Low");
8557 break;
8558 case PRIORITY_LOWEST:
8559 menuitem = gtk_ui_manager_get_widget
8560 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8561 break;
8563 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8566 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8568 Compose *compose = (Compose *) data;
8569 gchar *systemid;
8570 gboolean can_sign = FALSE, can_encrypt = FALSE;
8572 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8574 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8575 return;
8577 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8578 g_free(compose->privacy_system);
8579 compose->privacy_system = NULL;
8580 g_free(compose->encdata);
8581 compose->encdata = NULL;
8582 if (systemid != NULL) {
8583 compose->privacy_system = g_strdup(systemid);
8585 can_sign = privacy_system_can_sign(systemid);
8586 can_encrypt = privacy_system_can_encrypt(systemid);
8589 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8591 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8592 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8593 if (compose->toolbar->privacy_sign_btn != NULL) {
8594 gtk_widget_set_sensitive(
8595 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8596 can_sign);
8597 gtk_toggle_tool_button_set_active(
8598 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8599 can_sign ? compose->use_signing : FALSE);
8601 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8602 gtk_widget_set_sensitive(
8603 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8604 can_encrypt);
8605 gtk_toggle_tool_button_set_active(
8606 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8607 can_encrypt ? compose->use_encryption : FALSE);
8611 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8613 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8614 GtkWidget *menuitem = NULL;
8615 GList *children, *amenu;
8616 gboolean can_sign = FALSE, can_encrypt = FALSE;
8617 gboolean found = FALSE;
8619 if (compose->privacy_system != NULL) {
8620 gchar *systemid;
8621 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8622 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8623 cm_return_if_fail(menuitem != NULL);
8625 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8626 amenu = children;
8627 menuitem = NULL;
8628 while (amenu != NULL) {
8629 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8630 if (systemid != NULL) {
8631 if (strcmp(systemid, compose->privacy_system) == 0 &&
8632 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8633 menuitem = GTK_WIDGET(amenu->data);
8635 can_sign = privacy_system_can_sign(systemid);
8636 can_encrypt = privacy_system_can_encrypt(systemid);
8637 found = TRUE;
8638 break;
8640 } else if (strlen(compose->privacy_system) == 0 &&
8641 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8642 menuitem = GTK_WIDGET(amenu->data);
8644 can_sign = FALSE;
8645 can_encrypt = FALSE;
8646 found = TRUE;
8647 break;
8650 amenu = amenu->next;
8652 g_list_free(children);
8653 if (menuitem != NULL)
8654 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8656 if (warn && !found && strlen(compose->privacy_system)) {
8657 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8658 "will not be able to sign or encrypt this message."),
8659 compose->privacy_system);
8663 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8664 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8665 if (compose->toolbar->privacy_sign_btn != NULL) {
8666 gtk_widget_set_sensitive(
8667 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8668 can_sign);
8670 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8671 gtk_widget_set_sensitive(
8672 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8673 can_encrypt);
8677 static void compose_set_out_encoding(Compose *compose)
8679 CharSet out_encoding;
8680 const gchar *branch = NULL;
8681 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8683 switch(out_encoding) {
8684 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8685 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8686 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8687 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8688 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8689 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8690 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8691 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8692 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8693 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8694 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8695 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8696 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8697 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8698 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8699 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8700 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8701 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8702 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8703 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8704 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8705 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8706 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8707 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8708 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8709 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8710 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8711 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8712 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8713 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8714 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8715 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8716 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8717 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8719 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8722 static void compose_set_template_menu(Compose *compose)
8724 GSList *tmpl_list, *cur;
8725 GtkWidget *menu;
8726 GtkWidget *item;
8728 tmpl_list = template_get_config();
8730 menu = gtk_menu_new();
8732 gtk_menu_set_accel_group (GTK_MENU (menu),
8733 gtk_ui_manager_get_accel_group(compose->ui_manager));
8734 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8735 Template *tmpl = (Template *)cur->data;
8736 gchar *accel_path = NULL;
8737 item = gtk_menu_item_new_with_label(tmpl->name);
8738 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8739 g_signal_connect(G_OBJECT(item), "activate",
8740 G_CALLBACK(compose_template_activate_cb),
8741 compose);
8742 g_object_set_data(G_OBJECT(item), "template", tmpl);
8743 gtk_widget_show(item);
8744 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8745 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8746 g_free(accel_path);
8749 gtk_widget_show(menu);
8750 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8753 void compose_update_actions_menu(Compose *compose)
8755 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8758 static void compose_update_privacy_systems_menu(Compose *compose)
8760 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8761 GSList *systems, *cur;
8762 GtkWidget *widget;
8763 GtkWidget *system_none;
8764 GSList *group;
8765 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8766 GtkWidget *privacy_menu = gtk_menu_new();
8768 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8769 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8771 g_signal_connect(G_OBJECT(system_none), "activate",
8772 G_CALLBACK(compose_set_privacy_system_cb), compose);
8774 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8775 gtk_widget_show(system_none);
8777 systems = privacy_get_system_ids();
8778 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8779 gchar *systemid = cur->data;
8781 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8782 widget = gtk_radio_menu_item_new_with_label(group,
8783 privacy_system_get_name(systemid));
8784 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8785 g_strdup(systemid), g_free);
8786 g_signal_connect(G_OBJECT(widget), "activate",
8787 G_CALLBACK(compose_set_privacy_system_cb), compose);
8789 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8790 gtk_widget_show(widget);
8791 g_free(systemid);
8793 g_slist_free(systems);
8794 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8795 gtk_widget_show_all(privacy_menu);
8796 gtk_widget_show_all(privacy_menuitem);
8799 void compose_reflect_prefs_all(void)
8801 GList *cur;
8802 Compose *compose;
8804 for (cur = compose_list; cur != NULL; cur = cur->next) {
8805 compose = (Compose *)cur->data;
8806 compose_set_template_menu(compose);
8810 void compose_reflect_prefs_pixmap_theme(void)
8812 GList *cur;
8813 Compose *compose;
8815 for (cur = compose_list; cur != NULL; cur = cur->next) {
8816 compose = (Compose *)cur->data;
8817 toolbar_update(TOOLBAR_COMPOSE, compose);
8821 static const gchar *compose_quote_char_from_context(Compose *compose)
8823 const gchar *qmark = NULL;
8825 cm_return_val_if_fail(compose != NULL, NULL);
8827 switch (compose->mode) {
8828 /* use forward-specific quote char */
8829 case COMPOSE_FORWARD:
8830 case COMPOSE_FORWARD_AS_ATTACH:
8831 case COMPOSE_FORWARD_INLINE:
8832 if (compose->folder && compose->folder->prefs &&
8833 compose->folder->prefs->forward_with_format)
8834 qmark = compose->folder->prefs->forward_quotemark;
8835 else if (compose->account->forward_with_format)
8836 qmark = compose->account->forward_quotemark;
8837 else
8838 qmark = prefs_common.fw_quotemark;
8839 break;
8841 /* use reply-specific quote char in all other modes */
8842 default:
8843 if (compose->folder && compose->folder->prefs &&
8844 compose->folder->prefs->reply_with_format)
8845 qmark = compose->folder->prefs->reply_quotemark;
8846 else if (compose->account->reply_with_format)
8847 qmark = compose->account->reply_quotemark;
8848 else
8849 qmark = prefs_common.quotemark;
8850 break;
8853 if (qmark == NULL || *qmark == '\0')
8854 qmark = "> ";
8856 return qmark;
8859 static void compose_template_apply(Compose *compose, Template *tmpl,
8860 gboolean replace)
8862 GtkTextView *text;
8863 GtkTextBuffer *buffer;
8864 GtkTextMark *mark;
8865 GtkTextIter iter;
8866 const gchar *qmark;
8867 gchar *parsed_str = NULL;
8868 gint cursor_pos = 0;
8869 const gchar *err_msg = _("The body of the template has an error at line %d.");
8870 if (!tmpl) return;
8872 /* process the body */
8874 text = GTK_TEXT_VIEW(compose->text);
8875 buffer = gtk_text_view_get_buffer(text);
8877 if (tmpl->value) {
8878 qmark = compose_quote_char_from_context(compose);
8880 if (compose->replyinfo != NULL) {
8882 if (replace)
8883 gtk_text_buffer_set_text(buffer, "", -1);
8884 mark = gtk_text_buffer_get_insert(buffer);
8885 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8887 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8888 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8890 } else if (compose->fwdinfo != NULL) {
8892 if (replace)
8893 gtk_text_buffer_set_text(buffer, "", -1);
8894 mark = gtk_text_buffer_get_insert(buffer);
8895 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8897 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8898 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8900 } else {
8901 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8903 GtkTextIter start, end;
8904 gchar *tmp = NULL;
8906 gtk_text_buffer_get_start_iter(buffer, &start);
8907 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8908 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8910 /* clear the buffer now */
8911 if (replace)
8912 gtk_text_buffer_set_text(buffer, "", -1);
8914 parsed_str = compose_quote_fmt(compose, dummyinfo,
8915 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8916 procmsg_msginfo_free( &dummyinfo );
8918 g_free( tmp );
8920 } else {
8921 if (replace)
8922 gtk_text_buffer_set_text(buffer, "", -1);
8923 mark = gtk_text_buffer_get_insert(buffer);
8924 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8927 if (replace && parsed_str && compose->account->auto_sig)
8928 compose_insert_sig(compose, FALSE);
8930 if (replace && parsed_str) {
8931 gtk_text_buffer_get_start_iter(buffer, &iter);
8932 gtk_text_buffer_place_cursor(buffer, &iter);
8935 if (parsed_str) {
8936 cursor_pos = quote_fmt_get_cursor_pos();
8937 compose->set_cursor_pos = cursor_pos;
8938 if (cursor_pos == -1)
8939 cursor_pos = 0;
8940 gtk_text_buffer_get_start_iter(buffer, &iter);
8941 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8942 gtk_text_buffer_place_cursor(buffer, &iter);
8945 /* process the other fields */
8947 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8948 compose_template_apply_fields(compose, tmpl);
8949 quote_fmt_reset_vartable();
8950 quote_fmtlex_destroy();
8952 compose_changed_cb(NULL, compose);
8954 #ifdef USE_ENCHANT
8955 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8956 gtkaspell_highlight_all(compose->gtkaspell);
8957 #endif
8960 static void compose_template_apply_fields_error(const gchar *header)
8962 gchar *tr;
8963 gchar *text;
8965 tr = g_strdup(C_("'%s' stands for a header name",
8966 "Template '%s' format error."));
8967 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8968 alertpanel_error("%s", text);
8970 g_free(text);
8971 g_free(tr);
8974 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8976 MsgInfo* dummyinfo = NULL;
8977 MsgInfo *msginfo = NULL;
8978 gchar *buf = NULL;
8980 if (compose->replyinfo != NULL)
8981 msginfo = compose->replyinfo;
8982 else if (compose->fwdinfo != NULL)
8983 msginfo = compose->fwdinfo;
8984 else {
8985 dummyinfo = compose_msginfo_new_from_compose(compose);
8986 msginfo = dummyinfo;
8989 if (tmpl->from && *tmpl->from != '\0') {
8990 #ifdef USE_ENCHANT
8991 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8992 compose->gtkaspell);
8993 #else
8994 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8995 #endif
8996 quote_fmt_scan_string(tmpl->from);
8997 quote_fmt_parse();
8999 buf = quote_fmt_get_buffer();
9000 if (buf == NULL) {
9001 compose_template_apply_fields_error("From");
9002 } else {
9003 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
9006 quote_fmt_reset_vartable();
9007 quote_fmtlex_destroy();
9010 if (tmpl->to && *tmpl->to != '\0') {
9011 #ifdef USE_ENCHANT
9012 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9013 compose->gtkaspell);
9014 #else
9015 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9016 #endif
9017 quote_fmt_scan_string(tmpl->to);
9018 quote_fmt_parse();
9020 buf = quote_fmt_get_buffer();
9021 if (buf == NULL) {
9022 compose_template_apply_fields_error("To");
9023 } else {
9024 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
9027 quote_fmt_reset_vartable();
9028 quote_fmtlex_destroy();
9031 if (tmpl->cc && *tmpl->cc != '\0') {
9032 #ifdef USE_ENCHANT
9033 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9034 compose->gtkaspell);
9035 #else
9036 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9037 #endif
9038 quote_fmt_scan_string(tmpl->cc);
9039 quote_fmt_parse();
9041 buf = quote_fmt_get_buffer();
9042 if (buf == NULL) {
9043 compose_template_apply_fields_error("Cc");
9044 } else {
9045 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9048 quote_fmt_reset_vartable();
9049 quote_fmtlex_destroy();
9052 if (tmpl->bcc && *tmpl->bcc != '\0') {
9053 #ifdef USE_ENCHANT
9054 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9055 compose->gtkaspell);
9056 #else
9057 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9058 #endif
9059 quote_fmt_scan_string(tmpl->bcc);
9060 quote_fmt_parse();
9062 buf = quote_fmt_get_buffer();
9063 if (buf == NULL) {
9064 compose_template_apply_fields_error("Bcc");
9065 } else {
9066 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9069 quote_fmt_reset_vartable();
9070 quote_fmtlex_destroy();
9073 if (tmpl->replyto && *tmpl->replyto != '\0') {
9074 #ifdef USE_ENCHANT
9075 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9076 compose->gtkaspell);
9077 #else
9078 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9079 #endif
9080 quote_fmt_scan_string(tmpl->replyto);
9081 quote_fmt_parse();
9083 buf = quote_fmt_get_buffer();
9084 if (buf == NULL) {
9085 compose_template_apply_fields_error("Reply-To");
9086 } else {
9087 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9090 quote_fmt_reset_vartable();
9091 quote_fmtlex_destroy();
9094 /* process the subject */
9095 if (tmpl->subject && *tmpl->subject != '\0') {
9096 #ifdef USE_ENCHANT
9097 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9098 compose->gtkaspell);
9099 #else
9100 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9101 #endif
9102 quote_fmt_scan_string(tmpl->subject);
9103 quote_fmt_parse();
9105 buf = quote_fmt_get_buffer();
9106 if (buf == NULL) {
9107 compose_template_apply_fields_error("Subject");
9108 } else {
9109 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9112 quote_fmt_reset_vartable();
9113 quote_fmtlex_destroy();
9116 procmsg_msginfo_free( &dummyinfo );
9119 static void compose_destroy(Compose *compose)
9121 GtkAllocation allocation;
9122 GtkTextBuffer *buffer;
9123 GtkClipboard *clipboard;
9125 compose_list = g_list_remove(compose_list, compose);
9127 #ifdef USE_LDAP
9128 gboolean enable = TRUE;
9129 g_slist_foreach(compose->passworded_ldap_servers,
9130 _ldap_srv_func, &enable);
9131 g_slist_free(compose->passworded_ldap_servers);
9132 #endif
9134 if (compose->updating) {
9135 debug_print("danger, not destroying anything now\n");
9136 compose->deferred_destroy = TRUE;
9137 return;
9140 /* NOTE: address_completion_end() does nothing with the window
9141 * however this may change. */
9142 address_completion_end(compose->window);
9144 slist_free_strings_full(compose->to_list);
9145 slist_free_strings_full(compose->newsgroup_list);
9146 slist_free_strings_full(compose->header_list);
9148 slist_free_strings_full(extra_headers);
9149 extra_headers = NULL;
9151 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9153 g_hash_table_destroy(compose->email_hashtable);
9155 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9156 compose->folder_update_callback_id);
9158 procmsg_msginfo_free(&(compose->targetinfo));
9159 procmsg_msginfo_free(&(compose->replyinfo));
9160 procmsg_msginfo_free(&(compose->fwdinfo));
9162 g_free(compose->replyto);
9163 g_free(compose->cc);
9164 g_free(compose->bcc);
9165 g_free(compose->newsgroups);
9166 g_free(compose->followup_to);
9168 g_free(compose->ml_post);
9170 g_free(compose->inreplyto);
9171 g_free(compose->references);
9172 g_free(compose->msgid);
9173 g_free(compose->boundary);
9175 g_free(compose->redirect_filename);
9176 if (compose->undostruct)
9177 undo_destroy(compose->undostruct);
9179 g_free(compose->sig_str);
9181 g_free(compose->exteditor_file);
9183 g_free(compose->orig_charset);
9185 g_free(compose->privacy_system);
9186 g_free(compose->encdata);
9188 #ifndef USE_ALT_ADDRBOOK
9189 if (addressbook_get_target_compose() == compose)
9190 addressbook_set_target_compose(NULL);
9191 #endif
9192 #if USE_ENCHANT
9193 if (compose->gtkaspell) {
9194 gtkaspell_delete(compose->gtkaspell);
9195 compose->gtkaspell = NULL;
9197 #endif
9199 if (!compose->batch) {
9200 gtk_widget_get_allocation(compose->window, &allocation);
9201 prefs_common.compose_width = allocation.width;
9202 prefs_common.compose_height = allocation.height;
9205 if (!gtk_widget_get_parent(compose->paned))
9206 gtk_widget_destroy(compose->paned);
9207 gtk_widget_destroy(compose->popupmenu);
9209 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9210 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9211 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9213 message_search_close(compose);
9214 gtk_widget_destroy(compose->window);
9215 toolbar_destroy(compose->toolbar);
9216 g_free(compose->toolbar);
9217 cm_mutex_free(compose->mutex);
9218 g_free(compose);
9221 static void compose_attach_info_free(AttachInfo *ainfo)
9223 g_free(ainfo->file);
9224 g_free(ainfo->content_type);
9225 g_free(ainfo->name);
9226 g_free(ainfo->charset);
9227 g_free(ainfo);
9230 static void compose_attach_update_label(Compose *compose)
9232 GtkTreeIter iter;
9233 gint i = 1;
9234 gchar *text;
9235 GtkTreeModel *model;
9236 goffset total_size;
9237 AttachInfo *ainfo;
9239 if (compose == NULL)
9240 return;
9242 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9243 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9244 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9245 return;
9248 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9249 total_size = ainfo->size;
9250 while(gtk_tree_model_iter_next(model, &iter)) {
9251 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9252 total_size += ainfo->size;
9253 i++;
9255 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9256 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9257 g_free(text);
9260 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9262 Compose *compose = (Compose *)data;
9263 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9264 GtkTreeSelection *selection;
9265 GList *sel, *cur;
9266 GtkTreeModel *model;
9268 selection = gtk_tree_view_get_selection(tree_view);
9269 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9270 cm_return_if_fail(sel);
9272 for (cur = sel; cur != NULL; cur = cur->next) {
9273 GtkTreePath *path = cur->data;
9274 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9275 (model, cur->data);
9276 cur->data = ref;
9277 gtk_tree_path_free(path);
9280 for (cur = sel; cur != NULL; cur = cur->next) {
9281 GtkTreeRowReference *ref = cur->data;
9282 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9283 GtkTreeIter iter;
9285 if (gtk_tree_model_get_iter(model, &iter, path))
9286 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9288 gtk_tree_path_free(path);
9289 gtk_tree_row_reference_free(ref);
9292 g_list_free(sel);
9293 compose_attach_update_label(compose);
9296 static struct _AttachProperty
9298 GtkWidget *window;
9299 GtkWidget *mimetype_entry;
9300 GtkWidget *encoding_optmenu;
9301 GtkWidget *path_entry;
9302 GtkWidget *filename_entry;
9303 GtkWidget *ok_btn;
9304 GtkWidget *cancel_btn;
9305 } attach_prop;
9307 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9309 gtk_tree_path_free((GtkTreePath *)ptr);
9312 static void compose_attach_property(GtkAction *action, gpointer data)
9314 Compose *compose = (Compose *)data;
9315 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9316 AttachInfo *ainfo;
9317 GtkComboBox *optmenu;
9318 GtkTreeSelection *selection;
9319 GList *sel;
9320 GtkTreeModel *model;
9321 GtkTreeIter iter;
9322 GtkTreePath *path;
9323 static gboolean cancelled;
9325 /* only if one selected */
9326 selection = gtk_tree_view_get_selection(tree_view);
9327 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9328 return;
9330 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9331 cm_return_if_fail(sel);
9333 path = (GtkTreePath *) sel->data;
9334 gtk_tree_model_get_iter(model, &iter, path);
9335 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9337 if (!ainfo) {
9338 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9339 g_list_free(sel);
9340 return;
9342 g_list_free(sel);
9344 if (!attach_prop.window)
9345 compose_attach_property_create(&cancelled);
9346 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9347 gtk_widget_grab_focus(attach_prop.ok_btn);
9348 gtk_widget_show(attach_prop.window);
9349 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9350 GTK_WINDOW(compose->window));
9352 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9353 if (ainfo->encoding == ENC_UNKNOWN)
9354 combobox_select_by_data(optmenu, ENC_BASE64);
9355 else
9356 combobox_select_by_data(optmenu, ainfo->encoding);
9358 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9359 ainfo->content_type ? ainfo->content_type : "");
9360 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9361 ainfo->file ? ainfo->file : "");
9362 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9363 ainfo->name ? ainfo->name : "");
9365 for (;;) {
9366 const gchar *entry_text;
9367 gchar *text;
9368 gchar *cnttype = NULL;
9369 gchar *file = NULL;
9370 off_t size = 0;
9372 cancelled = FALSE;
9373 gtk_main();
9375 gtk_widget_hide(attach_prop.window);
9376 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9378 if (cancelled)
9379 break;
9381 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9382 if (*entry_text != '\0') {
9383 gchar *p;
9385 text = g_strstrip(g_strdup(entry_text));
9386 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9387 cnttype = g_strdup(text);
9388 g_free(text);
9389 } else {
9390 alertpanel_error(_("Invalid MIME type."));
9391 g_free(text);
9392 continue;
9396 ainfo->encoding = combobox_get_active_data(optmenu);
9398 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9399 if (*entry_text != '\0') {
9400 if (is_file_exist(entry_text) &&
9401 (size = get_file_size(entry_text)) > 0)
9402 file = g_strdup(entry_text);
9403 else {
9404 alertpanel_error
9405 (_("File doesn't exist or is empty."));
9406 g_free(cnttype);
9407 continue;
9411 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9412 if (*entry_text != '\0') {
9413 g_free(ainfo->name);
9414 ainfo->name = g_strdup(entry_text);
9417 if (cnttype) {
9418 g_free(ainfo->content_type);
9419 ainfo->content_type = cnttype;
9421 if (file) {
9422 g_free(ainfo->file);
9423 ainfo->file = file;
9425 if (size)
9426 ainfo->size = (goffset)size;
9428 /* update tree store */
9429 text = to_human_readable(ainfo->size);
9430 gtk_tree_model_get_iter(model, &iter, path);
9431 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9432 COL_MIMETYPE, ainfo->content_type,
9433 COL_SIZE, text,
9434 COL_NAME, ainfo->name,
9435 COL_CHARSET, ainfo->charset,
9436 -1);
9438 break;
9441 gtk_tree_path_free(path);
9444 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9446 label = gtk_label_new(str); \
9447 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9448 GTK_FILL, 0, 0, 0); \
9449 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9451 entry = gtk_entry_new(); \
9452 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9453 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9456 static void compose_attach_property_create(gboolean *cancelled)
9458 GtkWidget *window;
9459 GtkWidget *vbox;
9460 GtkWidget *table;
9461 GtkWidget *label;
9462 GtkWidget *mimetype_entry;
9463 GtkWidget *hbox;
9464 GtkWidget *optmenu;
9465 GtkListStore *optmenu_menu;
9466 GtkWidget *path_entry;
9467 GtkWidget *filename_entry;
9468 GtkWidget *hbbox;
9469 GtkWidget *ok_btn;
9470 GtkWidget *cancel_btn;
9471 GList *mime_type_list, *strlist;
9472 GtkTreeIter iter;
9474 debug_print("Creating attach_property window...\n");
9476 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9477 gtk_widget_set_size_request(window, 480, -1);
9478 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9479 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9480 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9481 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
9482 g_signal_connect(G_OBJECT(window), "delete_event",
9483 G_CALLBACK(attach_property_delete_event),
9484 cancelled);
9485 g_signal_connect(G_OBJECT(window), "key_press_event",
9486 G_CALLBACK(attach_property_key_pressed),
9487 cancelled);
9489 vbox = gtk_vbox_new(FALSE, 8);
9490 gtk_container_add(GTK_CONTAINER(window), vbox);
9492 table = gtk_table_new(4, 2, FALSE);
9493 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9494 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9495 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9497 label = gtk_label_new(_("MIME type"));
9498 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9499 GTK_FILL, 0, 0, 0);
9500 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9501 mimetype_entry = gtk_combo_box_text_new_with_entry();
9502 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9503 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9505 /* stuff with list */
9506 mime_type_list = procmime_get_mime_type_list();
9507 strlist = NULL;
9508 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9509 MimeType *type = (MimeType *) mime_type_list->data;
9510 gchar *tmp;
9512 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9514 if (g_list_find_custom(strlist, tmp, (GCompareFunc)g_strcmp0))
9515 g_free(tmp);
9516 else
9517 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9518 (GCompareFunc)g_strcmp0);
9521 for (mime_type_list = strlist; mime_type_list != NULL;
9522 mime_type_list = mime_type_list->next) {
9523 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9524 g_free(mime_type_list->data);
9526 g_list_free(strlist);
9527 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9528 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9530 label = gtk_label_new(_("Encoding"));
9531 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9532 GTK_FILL, 0, 0, 0);
9533 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9535 hbox = gtk_hbox_new(FALSE, 0);
9536 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9537 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9539 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9540 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9542 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9543 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9544 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9545 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9546 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9548 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9550 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9551 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9553 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9554 &ok_btn, GTK_STOCK_OK,
9555 NULL, NULL);
9556 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9557 gtk_widget_grab_default(ok_btn);
9559 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9560 G_CALLBACK(attach_property_ok),
9561 cancelled);
9562 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9563 G_CALLBACK(attach_property_cancel),
9564 cancelled);
9566 gtk_widget_show_all(vbox);
9568 attach_prop.window = window;
9569 attach_prop.mimetype_entry = mimetype_entry;
9570 attach_prop.encoding_optmenu = optmenu;
9571 attach_prop.path_entry = path_entry;
9572 attach_prop.filename_entry = filename_entry;
9573 attach_prop.ok_btn = ok_btn;
9574 attach_prop.cancel_btn = cancel_btn;
9577 #undef SET_LABEL_AND_ENTRY
9579 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9581 *cancelled = FALSE;
9582 gtk_main_quit();
9585 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9587 *cancelled = TRUE;
9588 gtk_main_quit();
9591 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9592 gboolean *cancelled)
9594 *cancelled = TRUE;
9595 gtk_main_quit();
9597 return TRUE;
9600 static gboolean attach_property_key_pressed(GtkWidget *widget,
9601 GdkEventKey *event,
9602 gboolean *cancelled)
9604 if (event && event->keyval == GDK_KEY_Escape) {
9605 *cancelled = TRUE;
9606 gtk_main_quit();
9608 if (event && event->keyval == GDK_KEY_Return) {
9609 *cancelled = FALSE;
9610 gtk_main_quit();
9611 return TRUE;
9613 return FALSE;
9616 static gboolean compose_can_autosave(Compose *compose)
9618 if (compose->privacy_system && compose->use_encryption)
9619 return prefs_common.autosave && prefs_common.autosave_encrypted;
9620 else
9621 return prefs_common.autosave;
9625 * compose_exec_ext_editor:
9627 * Open (and optionally embed) external editor
9629 static void compose_exec_ext_editor(Compose *compose)
9631 gchar *tmp;
9632 GtkWidget *socket;
9633 #ifndef G_OS_WIN32
9634 GdkNativeWindow socket_wid = 0;
9635 #endif /* G_OS_WIN32 */
9636 GPid pid;
9637 GError *error = NULL;
9638 gchar *cmd;
9639 gchar *p, *s;
9640 gchar **argv;
9642 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9643 G_DIR_SEPARATOR, compose);
9645 if (compose_write_body_to_file(compose, tmp) < 0) {
9646 alertpanel_error(_("Could not write the body to file:\n%s"),
9647 tmp);
9648 g_free(tmp);
9649 return;
9652 if (compose_get_ext_editor_uses_socket()) {
9653 #ifndef G_OS_WIN32
9654 /* Only allow one socket */
9655 if (compose->exteditor_socket != NULL) {
9656 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9657 /* Move the focus off of the socket */
9658 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9660 g_free(tmp);
9661 return;
9663 /* Create the receiving GtkSocket */
9664 socket = gtk_socket_new ();
9665 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9666 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9667 compose);
9668 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9669 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9670 /* Realize the socket so that we can use its ID */
9671 gtk_widget_realize(socket);
9672 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9673 compose->exteditor_socket = socket;
9674 #endif /* G_OS_WIN32 */
9677 if (compose_get_ext_editor_cmd_valid()) {
9678 if (compose_get_ext_editor_uses_socket()) {
9679 #ifndef G_OS_WIN32
9680 p = g_strdup(prefs_common_get_ext_editor_cmd());
9681 s = strstr(p, "%w");
9682 s[1] = 'u';
9683 if (strstr(p, "%s") < s)
9684 cmd = g_strdup_printf(p, tmp, socket_wid);
9685 else
9686 cmd = g_strdup_printf(p, socket_wid, tmp);
9687 g_free(p);
9688 #endif /* G_OS_WIN32 */
9689 } else {
9690 cmd = g_strdup_printf(prefs_common_get_ext_editor_cmd(), tmp);
9692 } else {
9693 if (prefs_common_get_ext_editor_cmd())
9694 g_warning("External editor command-line is invalid: '%s'",
9695 prefs_common_get_ext_editor_cmd());
9696 cmd = g_strdup_printf(DEFAULT_EDITOR_CMD, tmp);
9699 argv = strsplit_with_quote(cmd, " ", 0);
9701 if (!g_spawn_async(NULL, argv, NULL,
9702 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
9703 NULL, NULL, &pid, &error)) {
9704 alertpanel_error(_("Could not spawn the following "
9705 "command:\n%s\n%s"),
9706 cmd, error ? error->message : _("Unknown error"));
9707 if (error)
9708 g_error_free(error);
9709 g_free(tmp);
9710 g_free(cmd);
9711 g_strfreev(argv);
9712 return;
9714 g_free(cmd);
9715 g_strfreev(argv);
9717 compose->exteditor_file = g_strdup(tmp);
9718 compose->exteditor_pid = pid;
9719 compose->exteditor_tag = g_child_watch_add(pid,
9720 compose_ext_editor_closed_cb,
9721 compose);
9723 compose_set_ext_editor_sensitive(compose, FALSE);
9725 g_free(tmp);
9729 * compose_ext_editor_cb:
9731 * External editor has closed (called by g_child_watch)
9733 static void compose_ext_editor_closed_cb(GPid pid, gint exit_status, gpointer data)
9735 Compose *compose = (Compose *)data;
9736 GError *error = NULL;
9737 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9738 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9739 GtkTextIter start, end;
9740 gchar *chars;
9742 if (!g_spawn_check_exit_status(exit_status, &error)) {
9743 alertpanel_error(
9744 _("External editor stopped with an error: %s"),
9745 error ? error->message : _("Unknown error"));
9746 if (error)
9747 g_error_free(error);
9749 g_spawn_close_pid(compose->exteditor_pid);
9751 gtk_text_buffer_set_text(buffer, "", -1);
9752 compose_insert_file(compose, compose->exteditor_file);
9753 compose_changed_cb(NULL, compose);
9755 /* Check if we should save the draft or not */
9756 if (compose_can_autosave(compose))
9757 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9759 if (claws_unlink(compose->exteditor_file) < 0)
9760 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9762 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9763 gtk_text_buffer_get_start_iter(buffer, &start);
9764 gtk_text_buffer_get_end_iter(buffer, &end);
9765 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9766 if (chars && strlen(chars) > 0)
9767 compose->modified = TRUE;
9768 g_free(chars);
9770 compose_set_ext_editor_sensitive(compose, TRUE);
9772 g_free(compose->exteditor_file);
9773 compose->exteditor_file = NULL;
9774 compose->exteditor_pid = -1;
9775 compose->exteditor_tag = -1;
9776 if (compose->exteditor_socket) {
9777 gtk_widget_destroy(compose->exteditor_socket);
9778 compose->exteditor_socket = NULL;
9783 static gboolean compose_get_ext_editor_cmd_valid()
9785 gboolean has_s = FALSE;
9786 gboolean has_w = FALSE;
9787 const gchar *p = prefs_common_get_ext_editor_cmd();
9788 if (!p)
9789 return FALSE;
9790 while ((p = strchr(p, '%'))) {
9791 p++;
9792 if (*p == 's') {
9793 if (has_s)
9794 return FALSE;
9795 has_s = TRUE;
9796 } else if (*p == 'w') {
9797 if (has_w)
9798 return FALSE;
9799 has_w = TRUE;
9800 } else {
9801 return FALSE;
9804 return TRUE;
9807 static gboolean compose_ext_editor_kill(Compose *compose)
9809 GPid pid = compose->exteditor_pid;
9810 gint ret;
9812 if (pid > 0) {
9813 AlertValue val;
9814 gchar *msg;
9816 msg = g_strdup_printf
9817 (_("The external editor is still working.\n"
9818 "Force terminating the process?\n"
9819 "process id: %d"), pid);
9820 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO,
9821 GTK_STOCK_YES, NULL, ALERTFOCUS_FIRST,
9822 FALSE, NULL, ALERT_WARNING);
9823 g_free(msg);
9825 if (val == G_ALERTALTERNATE) {
9826 g_source_remove(compose->exteditor_tag);
9828 #ifdef G_OS_WIN32
9829 if (!TerminateProcess(compose->exteditor_pid, 0))
9830 perror("TerminateProcess");
9831 #else
9832 if (kill(pid, SIGTERM) < 0) perror("kill");
9833 waitpid(compose->exteditor_pid, NULL, 0);
9834 #endif /* G_OS_WIN32 */
9836 g_warning("Terminated process id: %d. "
9837 "Temporary file: %s", pid, compose->exteditor_file);
9838 g_spawn_close_pid(compose->exteditor_pid);
9840 compose_set_ext_editor_sensitive(compose, TRUE);
9842 g_free(compose->exteditor_file);
9843 compose->exteditor_file = NULL;
9844 compose->exteditor_pid = -1;
9845 compose->exteditor_tag = -1;
9846 } else
9847 return FALSE;
9850 return TRUE;
9853 static char *ext_editor_menu_entries[] = {
9854 "Menu/Message/Send",
9855 "Menu/Message/SendLater",
9856 "Menu/Message/InsertFile",
9857 "Menu/Message/InsertSig",
9858 "Menu/Message/ReplaceSig",
9859 "Menu/Message/Save",
9860 "Menu/Message/Print",
9861 "Menu/Edit",
9862 #if USE_ENCHANT
9863 "Menu/Spelling",
9864 #endif
9865 "Menu/Tools/ShowRuler",
9866 "Menu/Tools/Actions",
9867 "Menu/Help",
9868 NULL
9871 static void compose_set_ext_editor_sensitive(Compose *compose,
9872 gboolean sensitive)
9874 int i;
9876 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9877 cm_menu_set_sensitive_full(compose->ui_manager,
9878 ext_editor_menu_entries[i], sensitive);
9881 if (compose_get_ext_editor_uses_socket()) {
9882 if (sensitive) {
9883 if (compose->exteditor_socket)
9884 gtk_widget_hide(compose->exteditor_socket);
9885 gtk_widget_show(compose->scrolledwin);
9886 if (prefs_common.show_ruler)
9887 gtk_widget_show(compose->ruler_hbox);
9888 /* Fix the focus, as it doesn't go anywhere when the
9889 * socket is hidden or destroyed */
9890 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9891 } else {
9892 g_assert (compose->exteditor_socket != NULL);
9893 /* Fix the focus, as it doesn't go anywhere when the
9894 * edit box is hidden */
9895 if (gtk_widget_is_focus(compose->text))
9896 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9897 gtk_widget_hide(compose->scrolledwin);
9898 gtk_widget_hide(compose->ruler_hbox);
9899 gtk_widget_show(compose->exteditor_socket);
9901 } else {
9902 gtk_widget_set_sensitive(compose->text, sensitive);
9904 if (compose->toolbar->send_btn)
9905 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9906 if (compose->toolbar->sendl_btn)
9907 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9908 if (compose->toolbar->draft_btn)
9909 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9910 if (compose->toolbar->insert_btn)
9911 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9912 if (compose->toolbar->sig_btn)
9913 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9914 if (compose->toolbar->exteditor_btn)
9915 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9916 if (compose->toolbar->linewrap_current_btn)
9917 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9918 if (compose->toolbar->linewrap_all_btn)
9919 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9922 static gboolean compose_get_ext_editor_uses_socket()
9924 return (prefs_common_get_ext_editor_cmd() &&
9925 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9928 #ifndef G_OS_WIN32
9929 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9931 compose->exteditor_socket = NULL;
9932 /* returning FALSE allows destruction of the socket */
9933 return FALSE;
9935 #endif /* G_OS_WIN32 */
9938 * compose_undo_state_changed:
9940 * Change the sensivity of the menuentries undo and redo
9942 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9943 gint redo_state, gpointer data)
9945 Compose *compose = (Compose *)data;
9947 switch (undo_state) {
9948 case UNDO_STATE_TRUE:
9949 if (!undostruct->undo_state) {
9950 undostruct->undo_state = TRUE;
9951 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9953 break;
9954 case UNDO_STATE_FALSE:
9955 if (undostruct->undo_state) {
9956 undostruct->undo_state = FALSE;
9957 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9959 break;
9960 case UNDO_STATE_UNCHANGED:
9961 break;
9962 case UNDO_STATE_REFRESH:
9963 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9964 break;
9965 default:
9966 g_warning("Undo state not recognized");
9967 break;
9970 switch (redo_state) {
9971 case UNDO_STATE_TRUE:
9972 if (!undostruct->redo_state) {
9973 undostruct->redo_state = TRUE;
9974 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9976 break;
9977 case UNDO_STATE_FALSE:
9978 if (undostruct->redo_state) {
9979 undostruct->redo_state = FALSE;
9980 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9982 break;
9983 case UNDO_STATE_UNCHANGED:
9984 break;
9985 case UNDO_STATE_REFRESH:
9986 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9987 break;
9988 default:
9989 g_warning("Redo state not recognized");
9990 break;
9994 /* callback functions */
9996 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9997 GtkAllocation *allocation,
9998 GtkPaned *paned)
10000 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10003 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10004 * includes "non-client" (windows-izm) in calculation, so this calculation
10005 * may not be accurate.
10007 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10008 GtkAllocation *allocation,
10009 GtkSHRuler *shruler)
10011 if (prefs_common.show_ruler) {
10012 gint char_width = 0, char_height = 0;
10013 gint line_width_in_chars;
10015 gtkut_get_font_size(GTK_WIDGET(widget),
10016 &char_width, &char_height);
10017 line_width_in_chars =
10018 (allocation->width - allocation->x) / char_width;
10020 /* got the maximum */
10021 gtk_shruler_set_range(GTK_SHRULER(shruler),
10022 0.0, line_width_in_chars, 0);
10025 return TRUE;
10028 typedef struct {
10029 gchar *header;
10030 gchar *entry;
10031 ComposePrefType type;
10032 gboolean entry_marked;
10033 } HeaderEntryState;
10035 static void account_activated(GtkComboBox *optmenu, gpointer data)
10037 Compose *compose = (Compose *)data;
10039 PrefsAccount *ac;
10040 gchar *folderidentifier;
10041 gint account_id = 0;
10042 GtkTreeModel *menu;
10043 GtkTreeIter iter;
10044 GSList *list, *saved_list = NULL;
10045 HeaderEntryState *state;
10047 /* Get ID of active account in the combo box */
10048 menu = gtk_combo_box_get_model(optmenu);
10049 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10050 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10052 ac = account_find_from_id(account_id);
10053 cm_return_if_fail(ac != NULL);
10055 if (ac != compose->account) {
10056 compose_select_account(compose, ac, FALSE);
10058 for (list = compose->header_list; list; list = list->next) {
10059 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10061 if (hentry->type == PREF_ACCOUNT || !list->next) {
10062 compose_destroy_headerentry(compose, hentry);
10063 continue;
10065 state = g_malloc0(sizeof(HeaderEntryState));
10066 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10067 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10068 state->entry = gtk_editable_get_chars(
10069 GTK_EDITABLE(hentry->entry), 0, -1);
10070 state->type = hentry->type;
10072 saved_list = g_slist_append(saved_list, state);
10073 compose_destroy_headerentry(compose, hentry);
10076 compose->header_last = NULL;
10077 g_slist_free(compose->header_list);
10078 compose->header_list = NULL;
10079 compose->header_nextrow = 1;
10080 compose_create_header_entry(compose);
10082 if (ac->set_autocc && ac->auto_cc)
10083 compose_entry_append(compose, ac->auto_cc,
10084 COMPOSE_CC, PREF_ACCOUNT);
10085 if (ac->set_autobcc && ac->auto_bcc)
10086 compose_entry_append(compose, ac->auto_bcc,
10087 COMPOSE_BCC, PREF_ACCOUNT);
10088 if (ac->set_autoreplyto && ac->auto_replyto)
10089 compose_entry_append(compose, ac->auto_replyto,
10090 COMPOSE_REPLYTO, PREF_ACCOUNT);
10092 for (list = saved_list; list; list = list->next) {
10093 state = (HeaderEntryState *) list->data;
10095 compose_add_header_entry(compose, state->header,
10096 state->entry, state->type);
10098 g_free(state->header);
10099 g_free(state->entry);
10100 g_free(state);
10102 g_slist_free(saved_list);
10104 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10105 (ac->protocol == A_NNTP) ?
10106 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10109 /* Set message save folder */
10110 compose_set_save_to(compose, NULL);
10111 if (compose->folder && compose->folder->prefs && compose->folder->prefs->save_copy_to_folder) {
10112 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10113 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10114 folderidentifier = folder_item_get_identifier(compose->folder);
10115 compose_set_save_to(compose, folderidentifier);
10116 g_free(folderidentifier);
10117 } else if (account_get_special_folder(compose->account, F_OUTBOX)) {
10118 if (compose->account->set_sent_folder || prefs_common.savemsg)
10119 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10120 else
10121 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE);
10122 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10123 folderidentifier = folder_item_get_identifier(account_get_special_folder
10124 (compose->account, F_OUTBOX));
10125 compose_set_save_to(compose, folderidentifier);
10126 g_free(folderidentifier);
10130 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10131 GtkTreeViewColumn *column, Compose *compose)
10133 compose_attach_property(NULL, compose);
10136 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10137 gpointer data)
10139 Compose *compose = (Compose *)data;
10140 GtkTreeSelection *attach_selection;
10141 gint attach_nr_selected;
10142 GtkTreePath *path;
10144 if (!event) return FALSE;
10146 if (event->button == 3) {
10147 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10148 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10150 /* If no rows, or just one row is selected, right-click should
10151 * open menu relevant to the row being right-clicked on. We
10152 * achieve that by selecting the clicked row first. If more
10153 * than one row is selected, we shouldn't modify the selection,
10154 * as user may want to remove selected rows (attachments). */
10155 if (attach_nr_selected < 2) {
10156 gtk_tree_selection_unselect_all(attach_selection);
10157 attach_nr_selected = 0;
10158 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10159 event->x, event->y, &path, NULL, NULL, NULL);
10160 if (path != NULL) {
10161 gtk_tree_selection_select_path(attach_selection, path);
10162 gtk_tree_path_free(path);
10163 attach_nr_selected++;
10167 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10168 /* Properties menu item makes no sense with more than one row
10169 * selected, the properties dialog can only edit one attachment. */
10170 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10172 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10173 NULL, NULL, event->button, event->time);
10174 return TRUE;
10177 return FALSE;
10180 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10181 gpointer data)
10183 Compose *compose = (Compose *)data;
10185 if (!event) return FALSE;
10187 switch (event->keyval) {
10188 case GDK_KEY_Delete:
10189 compose_attach_remove_selected(NULL, compose);
10190 break;
10192 return FALSE;
10195 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10197 toolbar_comp_set_sensitive(compose, allow);
10198 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10199 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10200 #if USE_ENCHANT
10201 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10202 #endif
10203 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10204 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10205 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10207 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10211 static void compose_send_cb(GtkAction *action, gpointer data)
10213 Compose *compose = (Compose *)data;
10215 #ifdef G_OS_UNIX
10216 if (compose->exteditor_tag != -1) {
10217 debug_print("ignoring send: external editor still open\n");
10218 return;
10220 #endif
10221 if (prefs_common.work_offline &&
10222 !inc_offline_should_override(TRUE,
10223 _("Claws Mail needs network access in order "
10224 "to send this email.")))
10225 return;
10227 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10228 g_source_remove(compose->draft_timeout_tag);
10229 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10232 compose_send(compose);
10235 static void compose_send_later_cb(GtkAction *action, gpointer data)
10237 Compose *compose = (Compose *)data;
10238 ComposeQueueResult val;
10240 inc_lock();
10241 compose_allow_user_actions(compose, FALSE);
10242 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10243 compose_allow_user_actions(compose, TRUE);
10244 inc_unlock();
10246 if (val == COMPOSE_QUEUE_SUCCESS) {
10247 compose_close(compose);
10248 } else {
10249 _display_queue_error(val);
10252 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10255 #define DRAFTED_AT_EXIT "drafted_at_exit"
10256 static void compose_register_draft(MsgInfo *info)
10258 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10259 DRAFTED_AT_EXIT, NULL);
10260 FILE *fp = claws_fopen(filepath, "ab");
10262 if (fp) {
10263 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10264 info->msgnum);
10265 claws_fclose(fp);
10268 g_free(filepath);
10271 gboolean compose_draft (gpointer data, guint action)
10273 Compose *compose = (Compose *)data;
10274 FolderItem *draft;
10275 FolderItemPrefs *prefs;
10276 gchar *tmp;
10277 gchar *sheaders;
10278 gint msgnum;
10279 MsgFlags flag = {0, 0};
10280 static gboolean lock = FALSE;
10281 MsgInfo *newmsginfo;
10282 FILE *fp;
10283 gboolean target_locked = FALSE;
10284 gboolean err = FALSE;
10285 gint filemode = 0;
10287 if (lock) return FALSE;
10289 if (compose->sending)
10290 return TRUE;
10292 draft = account_get_special_folder(compose->account, F_DRAFT);
10293 cm_return_val_if_fail(draft != NULL, FALSE);
10295 if (!g_mutex_trylock(compose->mutex)) {
10296 /* we don't want to lock the mutex once it's available,
10297 * because as the only other part of compose.c locking
10298 * it is compose_close - which means once unlocked,
10299 * the compose struct will be freed */
10300 debug_print("couldn't lock mutex, probably sending\n");
10301 return FALSE;
10304 lock = TRUE;
10306 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10307 G_DIR_SEPARATOR, compose);
10308 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10309 FILE_OP_ERROR(tmp, "claws_fopen");
10310 goto warn_err;
10313 /* chmod for security unless folder chmod is set */
10314 prefs = draft->prefs;
10315 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) {
10316 filemode = prefs->folder_chmod;
10317 if (filemode & S_IRGRP) filemode |= S_IWGRP;
10318 if (filemode & S_IROTH) filemode |= S_IWOTH;
10319 if (chmod(tmp, filemode) < 0)
10320 FILE_OP_ERROR(tmp, "chmod");
10321 } else if (change_file_mode_rw(fp, tmp) < 0) {
10322 FILE_OP_ERROR(tmp, "chmod");
10323 g_warning("can't change file mode");
10326 /* Save draft infos */
10327 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10328 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10330 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10331 gchar *savefolderid;
10333 savefolderid = compose_get_save_to(compose);
10334 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10335 g_free(savefolderid);
10337 if (compose->return_receipt) {
10338 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10340 if (compose->privacy_system) {
10341 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10342 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10343 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10346 /* Message-ID of message replying to */
10347 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10348 gchar *folderid = NULL;
10350 if (compose->replyinfo->folder)
10351 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10352 if (folderid == NULL)
10353 folderid = g_strdup("NULL");
10355 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10356 g_free(folderid);
10358 /* Message-ID of message forwarding to */
10359 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10360 gchar *folderid = NULL;
10362 if (compose->fwdinfo->folder)
10363 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10364 if (folderid == NULL)
10365 folderid = g_strdup("NULL");
10367 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10368 g_free(folderid);
10371 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10372 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10374 sheaders = compose_get_manual_headers_info(compose);
10375 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10376 g_free(sheaders);
10378 /* end of headers */
10379 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10381 if (err) {
10382 claws_fclose(fp);
10383 goto warn_err;
10386 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10387 claws_fclose(fp);
10388 goto warn_err;
10390 if (claws_safe_fclose(fp) == EOF) {
10391 goto warn_err;
10394 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10395 if (compose->targetinfo) {
10396 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10397 if (target_locked)
10398 flag.perm_flags |= MSG_LOCKED;
10400 flag.tmp_flags = MSG_DRAFT;
10402 folder_item_scan(draft);
10403 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10404 MsgInfo *tmpinfo = NULL;
10405 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10406 if (compose->msgid) {
10407 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10409 if (tmpinfo) {
10410 msgnum = tmpinfo->msgnum;
10411 procmsg_msginfo_free(&tmpinfo);
10412 debug_print("got draft msgnum %d from scanning\n", msgnum);
10413 } else {
10414 debug_print("didn't get draft msgnum after scanning\n");
10416 } else {
10417 debug_print("got draft msgnum %d from adding\n", msgnum);
10419 if (msgnum < 0) {
10420 warn_err:
10421 claws_unlink(tmp);
10422 g_free(tmp);
10423 if (action != COMPOSE_AUTO_SAVE) {
10424 if (action != COMPOSE_DRAFT_FOR_EXIT)
10425 alertpanel_error(_("Could not save draft."));
10426 else {
10427 AlertValue val;
10428 gtkut_window_popup(compose->window);
10429 val = alertpanel_full(_("Could not save draft"),
10430 _("Could not save draft.\n"
10431 "Do you want to cancel exit or discard this email?"),
10432 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10433 FALSE, NULL, ALERT_QUESTION);
10434 if (val == G_ALERTALTERNATE) {
10435 lock = FALSE;
10436 g_mutex_unlock(compose->mutex); /* must be done before closing */
10437 compose_close(compose);
10438 return TRUE;
10439 } else {
10440 lock = FALSE;
10441 g_mutex_unlock(compose->mutex); /* must be done before closing */
10442 return FALSE;
10446 goto unlock;
10448 g_free(tmp);
10450 if (compose->mode == COMPOSE_REEDIT) {
10451 compose_remove_reedit_target(compose, TRUE);
10454 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10456 if (newmsginfo) {
10457 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10458 if (target_locked)
10459 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10460 else
10461 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10462 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10463 procmsg_msginfo_set_flags(newmsginfo, 0,
10464 MSG_HAS_ATTACHMENT);
10466 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10467 compose_register_draft(newmsginfo);
10469 procmsg_msginfo_free(&newmsginfo);
10472 folder_item_scan(draft);
10474 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10475 lock = FALSE;
10476 g_mutex_unlock(compose->mutex); /* must be done before closing */
10477 compose_close(compose);
10478 return TRUE;
10479 } else {
10480 #ifdef G_OS_WIN32
10481 GFile *f;
10482 GFileInfo *fi;
10483 GTimeVal tv;
10484 GError *error = NULL;
10485 #else
10486 GStatBuf s;
10487 #endif
10488 gchar *path;
10489 goffset size, mtime;
10491 path = folder_item_fetch_msg(draft, msgnum);
10492 if (path == NULL) {
10493 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10494 goto unlock;
10496 #ifdef G_OS_WIN32
10497 f = g_file_new_for_path(path);
10498 fi = g_file_query_info(f, "standard::size,time::modified",
10499 G_FILE_QUERY_INFO_NONE, NULL, &error);
10500 if (error != NULL) {
10501 debug_print("couldn't query file info for '%s': %s\n",
10502 path, error->message);
10503 g_error_free(error);
10504 g_free(path);
10505 g_object_unref(f);
10506 goto unlock;
10508 size = g_file_info_get_size(fi);
10509 g_file_info_get_modification_time(fi, &tv);
10510 mtime = tv.tv_sec;
10511 g_object_unref(fi);
10512 g_object_unref(f);
10513 #else
10514 if (g_stat(path, &s) < 0) {
10515 FILE_OP_ERROR(path, "stat");
10516 g_free(path);
10517 goto unlock;
10519 size = s.st_size;
10520 mtime = s.st_mtime;
10521 #endif
10522 g_free(path);
10524 procmsg_msginfo_free(&(compose->targetinfo));
10525 compose->targetinfo = procmsg_msginfo_new();
10526 compose->targetinfo->msgnum = msgnum;
10527 compose->targetinfo->size = size;
10528 compose->targetinfo->mtime = mtime;
10529 compose->targetinfo->folder = draft;
10530 if (target_locked)
10531 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10532 compose->mode = COMPOSE_REEDIT;
10534 if (action == COMPOSE_AUTO_SAVE) {
10535 compose->autosaved_draft = compose->targetinfo;
10537 compose->modified = FALSE;
10538 compose_set_title(compose);
10540 unlock:
10541 lock = FALSE;
10542 g_mutex_unlock(compose->mutex);
10543 return TRUE;
10546 void compose_clear_exit_drafts(void)
10548 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10549 DRAFTED_AT_EXIT, NULL);
10550 if (is_file_exist(filepath))
10551 claws_unlink(filepath);
10553 g_free(filepath);
10556 void compose_reopen_exit_drafts(void)
10558 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10559 DRAFTED_AT_EXIT, NULL);
10560 FILE *fp = claws_fopen(filepath, "rb");
10561 gchar buf[1024];
10563 if (fp) {
10564 while (claws_fgets(buf, sizeof(buf), fp)) {
10565 gchar **parts = g_strsplit(buf, "\t", 2);
10566 const gchar *folder = parts[0];
10567 int msgnum = parts[1] ? atoi(parts[1]):-1;
10569 if (folder && *folder && msgnum > -1) {
10570 FolderItem *item = folder_find_item_from_identifier(folder);
10571 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10572 if (info)
10573 compose_reedit(info, FALSE);
10575 g_strfreev(parts);
10577 claws_fclose(fp);
10579 g_free(filepath);
10580 compose_clear_exit_drafts();
10583 static void compose_save_cb(GtkAction *action, gpointer data)
10585 Compose *compose = (Compose *)data;
10586 compose_draft(compose, COMPOSE_KEEP_EDITING);
10587 compose->rmode = COMPOSE_REEDIT;
10590 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10592 if (compose && file_list) {
10593 GList *tmp;
10595 for ( tmp = file_list; tmp; tmp = tmp->next) {
10596 gchar *file = (gchar *) tmp->data;
10597 gchar *utf8_filename = conv_filename_to_utf8(file);
10598 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10599 compose_changed_cb(NULL, compose);
10600 if (free_data) {
10601 g_free(file);
10602 tmp->data = NULL;
10604 g_free(utf8_filename);
10609 static void compose_attach_cb(GtkAction *action, gpointer data)
10611 Compose *compose = (Compose *)data;
10612 GList *file_list;
10614 if (compose->redirect_filename != NULL)
10615 return;
10617 /* Set focus_window properly, in case we were called via popup menu,
10618 * which unsets it (via focus_out_event callback on compose window). */
10619 manage_window_focus_in(compose->window, NULL, NULL);
10621 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10623 if (file_list) {
10624 compose_attach_from_list(compose, file_list, TRUE);
10625 g_list_free(file_list);
10629 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10631 Compose *compose = (Compose *)data;
10632 GList *file_list;
10633 gint files_inserted = 0;
10635 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10637 if (file_list) {
10638 GList *tmp;
10640 for ( tmp = file_list; tmp; tmp = tmp->next) {
10641 gchar *file = (gchar *) tmp->data;
10642 gchar *filedup = g_strdup(file);
10643 gchar *shortfile = g_path_get_basename(filedup);
10644 ComposeInsertResult res;
10645 /* insert the file if the file is short or if the user confirmed that
10646 he/she wants to insert the large file */
10647 res = compose_insert_file(compose, file);
10648 if (res == COMPOSE_INSERT_READ_ERROR) {
10649 alertpanel_error(_("File '%s' could not be read."), shortfile);
10650 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10651 alertpanel_error(_("File '%s' contained invalid characters\n"
10652 "for the current encoding, insertion may be incorrect."),
10653 shortfile);
10654 } else if (res == COMPOSE_INSERT_SUCCESS)
10655 files_inserted++;
10657 g_free(shortfile);
10658 g_free(filedup);
10659 g_free(file);
10661 g_list_free(file_list);
10664 #ifdef USE_ENCHANT
10665 if (files_inserted > 0 && compose->gtkaspell &&
10666 compose->gtkaspell->check_while_typing)
10667 gtkaspell_highlight_all(compose->gtkaspell);
10668 #endif
10671 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10673 Compose *compose = (Compose *)data;
10675 compose_insert_sig(compose, FALSE);
10678 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10680 Compose *compose = (Compose *)data;
10682 compose_insert_sig(compose, TRUE);
10685 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10686 gpointer data)
10688 gint x, y;
10689 Compose *compose = (Compose *)data;
10691 gtkut_widget_get_uposition(widget, &x, &y);
10692 if (!compose->batch) {
10693 prefs_common.compose_x = x;
10694 prefs_common.compose_y = y;
10696 if (compose->sending || compose->updating)
10697 return TRUE;
10698 compose_close_cb(NULL, compose);
10699 return TRUE;
10702 void compose_close_toolbar(Compose *compose)
10704 compose_close_cb(NULL, compose);
10707 static void compose_close_cb(GtkAction *action, gpointer data)
10709 Compose *compose = (Compose *)data;
10710 AlertValue val;
10712 if (compose->exteditor_tag != -1) {
10713 if (!compose_ext_editor_kill(compose))
10714 return;
10717 if (compose->modified) {
10718 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10719 if (!g_mutex_trylock(compose->mutex)) {
10720 /* we don't want to lock the mutex once it's available,
10721 * because as the only other part of compose.c locking
10722 * it is compose_close - which means once unlocked,
10723 * the compose struct will be freed */
10724 debug_print("couldn't lock mutex, probably sending\n");
10725 return;
10727 if (!reedit || compose->folder->stype == F_DRAFT) {
10728 val = alertpanel(_("Discard message"),
10729 _("This message has been modified. Discard it?"),
10730 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10731 ALERTFOCUS_FIRST);
10732 } else {
10733 val = alertpanel(_("Save changes"),
10734 _("This message has been modified. Save the latest changes?"),
10735 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10736 ALERTFOCUS_SECOND);
10738 g_mutex_unlock(compose->mutex);
10739 switch (val) {
10740 case G_ALERTDEFAULT:
10741 if (compose_can_autosave(compose) && !reedit)
10742 compose_remove_draft(compose);
10743 break;
10744 case G_ALERTALTERNATE:
10745 compose_draft(data, COMPOSE_QUIT_EDITING);
10746 return;
10747 default:
10748 return;
10752 compose_close(compose);
10755 static void compose_print_cb(GtkAction *action, gpointer data)
10757 Compose *compose = (Compose *) data;
10759 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10760 if (compose->targetinfo)
10761 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10764 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10766 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10767 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10768 Compose *compose = (Compose *) data;
10770 if (active)
10771 compose->out_encoding = (CharSet)value;
10774 static void compose_address_cb(GtkAction *action, gpointer data)
10776 Compose *compose = (Compose *)data;
10778 #ifndef USE_ALT_ADDRBOOK
10779 addressbook_open(compose);
10780 #else
10781 GError* error = NULL;
10782 addressbook_connect_signals(compose);
10783 addressbook_dbus_open(TRUE, &error);
10784 if (error) {
10785 g_warning("%s", error->message);
10786 g_error_free(error);
10788 #endif
10791 static void about_show_cb(GtkAction *action, gpointer data)
10793 about_show();
10796 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10798 Compose *compose = (Compose *)data;
10799 Template *tmpl;
10800 gchar *msg;
10801 AlertValue val;
10803 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10804 cm_return_if_fail(tmpl != NULL);
10806 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10807 tmpl->name);
10808 val = alertpanel(_("Apply template"), msg,
10809 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10810 g_free(msg);
10812 if (val == G_ALERTDEFAULT)
10813 compose_template_apply(compose, tmpl, TRUE);
10814 else if (val == G_ALERTALTERNATE)
10815 compose_template_apply(compose, tmpl, FALSE);
10818 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10820 Compose *compose = (Compose *)data;
10822 if (compose->exteditor_tag != -1) {
10823 debug_print("ignoring open external editor: external editor still open\n");
10824 return;
10826 compose_exec_ext_editor(compose);
10829 static void compose_undo_cb(GtkAction *action, gpointer data)
10831 Compose *compose = (Compose *)data;
10832 gboolean prev_autowrap = compose->autowrap;
10834 compose->autowrap = FALSE;
10835 undo_undo(compose->undostruct);
10836 compose->autowrap = prev_autowrap;
10839 static void compose_redo_cb(GtkAction *action, gpointer data)
10841 Compose *compose = (Compose *)data;
10842 gboolean prev_autowrap = compose->autowrap;
10844 compose->autowrap = FALSE;
10845 undo_redo(compose->undostruct);
10846 compose->autowrap = prev_autowrap;
10849 static void entry_cut_clipboard(GtkWidget *entry)
10851 if (GTK_IS_EDITABLE(entry))
10852 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10853 else if (GTK_IS_TEXT_VIEW(entry))
10854 gtk_text_buffer_cut_clipboard(
10855 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10856 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10857 TRUE);
10860 static void entry_copy_clipboard(GtkWidget *entry)
10862 if (GTK_IS_EDITABLE(entry))
10863 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10864 else if (GTK_IS_TEXT_VIEW(entry))
10865 gtk_text_buffer_copy_clipboard(
10866 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10867 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10870 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10871 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10873 if (GTK_IS_TEXT_VIEW(entry)) {
10874 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10875 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10876 GtkTextIter start_iter, end_iter;
10877 gint start, end;
10878 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10880 if (contents == NULL)
10881 return;
10883 /* we shouldn't delete the selection when middle-click-pasting, or we
10884 * can't mid-click-paste our own selection */
10885 if (clip != GDK_SELECTION_PRIMARY) {
10886 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10887 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10890 if (insert_place == NULL) {
10891 /* if insert_place isn't specified, insert at the cursor.
10892 * used for Ctrl-V pasting */
10893 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10894 start = gtk_text_iter_get_offset(&start_iter);
10895 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10896 } else {
10897 /* if insert_place is specified, paste here.
10898 * used for mid-click-pasting */
10899 start = gtk_text_iter_get_offset(insert_place);
10900 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10901 if (prefs_common.primary_paste_unselects)
10902 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10905 if (!wrap) {
10906 /* paste unwrapped: mark the paste so it's not wrapped later */
10907 end = start + strlen(contents);
10908 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10909 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10910 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10911 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10912 /* rewrap paragraph now (after a mid-click-paste) */
10913 mark_start = gtk_text_buffer_get_insert(buffer);
10914 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10915 gtk_text_iter_backward_char(&start_iter);
10916 compose_beautify_paragraph(compose, &start_iter, TRUE);
10918 } else if (GTK_IS_EDITABLE(entry))
10919 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10921 compose->modified = TRUE;
10924 static void entry_allsel(GtkWidget *entry)
10926 if (GTK_IS_EDITABLE(entry))
10927 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10928 else if (GTK_IS_TEXT_VIEW(entry)) {
10929 GtkTextIter startiter, enditer;
10930 GtkTextBuffer *textbuf;
10932 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10933 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10934 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10936 gtk_text_buffer_move_mark_by_name(textbuf,
10937 "selection_bound", &startiter);
10938 gtk_text_buffer_move_mark_by_name(textbuf,
10939 "insert", &enditer);
10943 static void compose_cut_cb(GtkAction *action, gpointer data)
10945 Compose *compose = (Compose *)data;
10946 if (compose->focused_editable
10947 #ifndef GENERIC_UMPC
10948 && gtk_widget_has_focus(compose->focused_editable)
10949 #endif
10951 entry_cut_clipboard(compose->focused_editable);
10954 static void compose_copy_cb(GtkAction *action, gpointer data)
10956 Compose *compose = (Compose *)data;
10957 if (compose->focused_editable
10958 #ifndef GENERIC_UMPC
10959 && gtk_widget_has_focus(compose->focused_editable)
10960 #endif
10962 entry_copy_clipboard(compose->focused_editable);
10965 static void compose_paste_cb(GtkAction *action, gpointer data)
10967 Compose *compose = (Compose *)data;
10968 gint prev_autowrap;
10969 GtkTextBuffer *buffer;
10970 BLOCK_WRAP();
10971 if (compose->focused_editable
10972 #ifndef GENERIC_UMPC
10973 && gtk_widget_has_focus(compose->focused_editable)
10974 #endif
10976 entry_paste_clipboard(compose, compose->focused_editable,
10977 prefs_common.linewrap_pastes,
10978 GDK_SELECTION_CLIPBOARD, NULL);
10979 UNBLOCK_WRAP();
10981 #ifdef USE_ENCHANT
10982 if (
10983 #ifndef GENERIC_UMPC
10984 gtk_widget_has_focus(compose->text) &&
10985 #endif
10986 compose->gtkaspell &&
10987 compose->gtkaspell->check_while_typing)
10988 gtkaspell_highlight_all(compose->gtkaspell);
10989 #endif
10992 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10994 Compose *compose = (Compose *)data;
10995 gint wrap_quote = prefs_common.linewrap_quote;
10996 if (compose->focused_editable
10997 #ifndef GENERIC_UMPC
10998 && gtk_widget_has_focus(compose->focused_editable)
10999 #endif
11001 /* let text_insert() (called directly or at a later time
11002 * after the gtk_editable_paste_clipboard) know that
11003 * text is to be inserted as a quotation. implemented
11004 * by using a simple refcount... */
11005 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11006 G_OBJECT(compose->focused_editable),
11007 "paste_as_quotation"));
11008 g_object_set_data(G_OBJECT(compose->focused_editable),
11009 "paste_as_quotation",
11010 GINT_TO_POINTER(paste_as_quotation + 1));
11011 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11012 entry_paste_clipboard(compose, compose->focused_editable,
11013 prefs_common.linewrap_pastes,
11014 GDK_SELECTION_CLIPBOARD, NULL);
11015 prefs_common.linewrap_quote = wrap_quote;
11019 static void compose_paste_no_wrap_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, FALSE,
11031 GDK_SELECTION_CLIPBOARD, NULL);
11032 UNBLOCK_WRAP();
11034 #ifdef USE_ENCHANT
11035 if (
11036 #ifndef GENERIC_UMPC
11037 gtk_widget_has_focus(compose->text) &&
11038 #endif
11039 compose->gtkaspell &&
11040 compose->gtkaspell->check_while_typing)
11041 gtkaspell_highlight_all(compose->gtkaspell);
11042 #endif
11045 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11047 Compose *compose = (Compose *)data;
11048 gint prev_autowrap;
11049 GtkTextBuffer *buffer;
11050 BLOCK_WRAP();
11051 if (compose->focused_editable
11052 #ifndef GENERIC_UMPC
11053 && gtk_widget_has_focus(compose->focused_editable)
11054 #endif
11056 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11057 GDK_SELECTION_CLIPBOARD, NULL);
11058 UNBLOCK_WRAP();
11060 #ifdef USE_ENCHANT
11061 if (
11062 #ifndef GENERIC_UMPC
11063 gtk_widget_has_focus(compose->text) &&
11064 #endif
11065 compose->gtkaspell &&
11066 compose->gtkaspell->check_while_typing)
11067 gtkaspell_highlight_all(compose->gtkaspell);
11068 #endif
11071 static void compose_allsel_cb(GtkAction *action, gpointer data)
11073 Compose *compose = (Compose *)data;
11074 if (compose->focused_editable
11075 #ifndef GENERIC_UMPC
11076 && gtk_widget_has_focus(compose->focused_editable)
11077 #endif
11079 entry_allsel(compose->focused_editable);
11082 static void textview_move_beginning_of_line (GtkTextView *text)
11084 GtkTextBuffer *buffer;
11085 GtkTextMark *mark;
11086 GtkTextIter ins;
11088 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11090 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11091 mark = gtk_text_buffer_get_insert(buffer);
11092 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11093 gtk_text_iter_set_line_offset(&ins, 0);
11094 gtk_text_buffer_place_cursor(buffer, &ins);
11097 static void textview_move_forward_character (GtkTextView *text)
11099 GtkTextBuffer *buffer;
11100 GtkTextMark *mark;
11101 GtkTextIter ins;
11103 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11105 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11106 mark = gtk_text_buffer_get_insert(buffer);
11107 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11108 if (gtk_text_iter_forward_cursor_position(&ins))
11109 gtk_text_buffer_place_cursor(buffer, &ins);
11112 static void textview_move_backward_character (GtkTextView *text)
11114 GtkTextBuffer *buffer;
11115 GtkTextMark *mark;
11116 GtkTextIter ins;
11118 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11120 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11121 mark = gtk_text_buffer_get_insert(buffer);
11122 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11123 if (gtk_text_iter_backward_cursor_position(&ins))
11124 gtk_text_buffer_place_cursor(buffer, &ins);
11127 static void textview_move_forward_word (GtkTextView *text)
11129 GtkTextBuffer *buffer;
11130 GtkTextMark *mark;
11131 GtkTextIter ins;
11132 gint count;
11134 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11136 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11137 mark = gtk_text_buffer_get_insert(buffer);
11138 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11139 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11140 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11141 gtk_text_iter_backward_word_start(&ins);
11142 gtk_text_buffer_place_cursor(buffer, &ins);
11146 static void textview_move_backward_word (GtkTextView *text)
11148 GtkTextBuffer *buffer;
11149 GtkTextMark *mark;
11150 GtkTextIter ins;
11152 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11154 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11155 mark = gtk_text_buffer_get_insert(buffer);
11156 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11157 if (gtk_text_iter_backward_word_starts(&ins, 1))
11158 gtk_text_buffer_place_cursor(buffer, &ins);
11161 static void textview_move_end_of_line (GtkTextView *text)
11163 GtkTextBuffer *buffer;
11164 GtkTextMark *mark;
11165 GtkTextIter ins;
11167 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11169 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11170 mark = gtk_text_buffer_get_insert(buffer);
11171 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11172 if (gtk_text_iter_forward_to_line_end(&ins))
11173 gtk_text_buffer_place_cursor(buffer, &ins);
11176 static void textview_move_next_line (GtkTextView *text)
11178 GtkTextBuffer *buffer;
11179 GtkTextMark *mark;
11180 GtkTextIter ins;
11181 gint offset;
11183 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11185 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11186 mark = gtk_text_buffer_get_insert(buffer);
11187 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11188 offset = gtk_text_iter_get_line_offset(&ins);
11189 if (gtk_text_iter_forward_line(&ins)) {
11190 gtk_text_iter_set_line_offset(&ins, offset);
11191 gtk_text_buffer_place_cursor(buffer, &ins);
11195 static void textview_move_previous_line (GtkTextView *text)
11197 GtkTextBuffer *buffer;
11198 GtkTextMark *mark;
11199 GtkTextIter ins;
11200 gint offset;
11202 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11204 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11205 mark = gtk_text_buffer_get_insert(buffer);
11206 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11207 offset = gtk_text_iter_get_line_offset(&ins);
11208 if (gtk_text_iter_backward_line(&ins)) {
11209 gtk_text_iter_set_line_offset(&ins, offset);
11210 gtk_text_buffer_place_cursor(buffer, &ins);
11214 static void textview_delete_forward_character (GtkTextView *text)
11216 GtkTextBuffer *buffer;
11217 GtkTextMark *mark;
11218 GtkTextIter ins, end_iter;
11220 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11222 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11223 mark = gtk_text_buffer_get_insert(buffer);
11224 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11225 end_iter = ins;
11226 if (gtk_text_iter_forward_char(&end_iter)) {
11227 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11231 static void textview_delete_backward_character (GtkTextView *text)
11233 GtkTextBuffer *buffer;
11234 GtkTextMark *mark;
11235 GtkTextIter ins, end_iter;
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 end_iter = ins;
11243 if (gtk_text_iter_backward_char(&end_iter)) {
11244 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11248 static void textview_delete_forward_word (GtkTextView *text)
11250 GtkTextBuffer *buffer;
11251 GtkTextMark *mark;
11252 GtkTextIter ins, end_iter;
11254 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11256 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11257 mark = gtk_text_buffer_get_insert(buffer);
11258 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11259 end_iter = ins;
11260 if (gtk_text_iter_forward_word_end(&end_iter)) {
11261 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11265 static void textview_delete_backward_word (GtkTextView *text)
11267 GtkTextBuffer *buffer;
11268 GtkTextMark *mark;
11269 GtkTextIter ins, end_iter;
11271 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11273 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11274 mark = gtk_text_buffer_get_insert(buffer);
11275 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11276 end_iter = ins;
11277 if (gtk_text_iter_backward_word_start(&end_iter)) {
11278 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11282 static void textview_delete_line (GtkTextView *text)
11284 GtkTextBuffer *buffer;
11285 GtkTextMark *mark;
11286 GtkTextIter ins, start_iter, end_iter;
11288 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11290 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11291 mark = gtk_text_buffer_get_insert(buffer);
11292 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11294 start_iter = ins;
11295 gtk_text_iter_set_line_offset(&start_iter, 0);
11297 end_iter = ins;
11298 if (gtk_text_iter_ends_line(&end_iter)){
11299 if (!gtk_text_iter_forward_char(&end_iter))
11300 gtk_text_iter_backward_char(&start_iter);
11302 else
11303 gtk_text_iter_forward_to_line_end(&end_iter);
11304 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11307 static void textview_delete_to_line_end (GtkTextView *text)
11309 GtkTextBuffer *buffer;
11310 GtkTextMark *mark;
11311 GtkTextIter ins, end_iter;
11313 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11315 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11316 mark = gtk_text_buffer_get_insert(buffer);
11317 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11318 end_iter = ins;
11319 if (gtk_text_iter_ends_line(&end_iter))
11320 gtk_text_iter_forward_char(&end_iter);
11321 else
11322 gtk_text_iter_forward_to_line_end(&end_iter);
11323 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11326 #define DO_ACTION(name, act) { \
11327 if(!strcmp(name, a_name)) { \
11328 return act; \
11331 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11333 const gchar *a_name = gtk_action_get_name(action);
11334 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11335 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11336 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11337 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11338 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11339 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11340 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11341 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11342 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11343 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11344 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11345 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11346 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11347 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11348 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11351 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11353 Compose *compose = (Compose *)data;
11354 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11355 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11357 action = compose_call_advanced_action_from_path(gaction);
11359 static struct {
11360 void (*do_action) (GtkTextView *text);
11361 } action_table[] = {
11362 {textview_move_beginning_of_line},
11363 {textview_move_forward_character},
11364 {textview_move_backward_character},
11365 {textview_move_forward_word},
11366 {textview_move_backward_word},
11367 {textview_move_end_of_line},
11368 {textview_move_next_line},
11369 {textview_move_previous_line},
11370 {textview_delete_forward_character},
11371 {textview_delete_backward_character},
11372 {textview_delete_forward_word},
11373 {textview_delete_backward_word},
11374 {textview_delete_line},
11375 {textview_delete_to_line_end}
11378 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11380 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11381 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11382 if (action_table[action].do_action)
11383 action_table[action].do_action(text);
11384 else
11385 g_warning("Not implemented yet.");
11389 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11391 GtkAllocation allocation;
11392 GtkWidget *parent;
11393 gchar *str = NULL;
11395 if (GTK_IS_EDITABLE(widget)) {
11396 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11397 gtk_editable_set_position(GTK_EDITABLE(widget),
11398 strlen(str));
11399 g_free(str);
11400 if ((parent = gtk_widget_get_parent(widget))
11401 && (parent = gtk_widget_get_parent(parent))
11402 && (parent = gtk_widget_get_parent(parent))) {
11403 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11404 gtk_widget_get_allocation(widget, &allocation);
11405 gint y = allocation.y;
11406 gint height = allocation.height;
11407 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11408 (GTK_SCROLLED_WINDOW(parent));
11410 gfloat value = gtk_adjustment_get_value(shown);
11411 gfloat upper = gtk_adjustment_get_upper(shown);
11412 gfloat page_size = gtk_adjustment_get_page_size(shown);
11413 if (y < (int)value) {
11414 gtk_adjustment_set_value(shown, y - 1);
11416 if ((y + height) > ((int)value + (int)page_size)) {
11417 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11418 gtk_adjustment_set_value(shown,
11419 y + height - (int)page_size - 1);
11420 } else {
11421 gtk_adjustment_set_value(shown,
11422 (int)upper - (int)page_size - 1);
11429 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11430 compose->focused_editable = widget;
11432 #ifdef GENERIC_UMPC
11433 if (GTK_IS_TEXT_VIEW(widget)
11434 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11435 g_object_ref(compose->notebook);
11436 g_object_ref(compose->edit_vbox);
11437 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11438 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11439 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11440 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11441 g_object_unref(compose->notebook);
11442 g_object_unref(compose->edit_vbox);
11443 g_signal_handlers_block_by_func(G_OBJECT(widget),
11444 G_CALLBACK(compose_grab_focus_cb),
11445 compose);
11446 gtk_widget_grab_focus(widget);
11447 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11448 G_CALLBACK(compose_grab_focus_cb),
11449 compose);
11450 } else if (!GTK_IS_TEXT_VIEW(widget)
11451 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11452 g_object_ref(compose->notebook);
11453 g_object_ref(compose->edit_vbox);
11454 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11455 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11456 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11457 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11458 g_object_unref(compose->notebook);
11459 g_object_unref(compose->edit_vbox);
11460 g_signal_handlers_block_by_func(G_OBJECT(widget),
11461 G_CALLBACK(compose_grab_focus_cb),
11462 compose);
11463 gtk_widget_grab_focus(widget);
11464 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11465 G_CALLBACK(compose_grab_focus_cb),
11466 compose);
11468 #endif
11471 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11473 compose->modified = TRUE;
11474 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11475 #ifndef GENERIC_UMPC
11476 compose_set_title(compose);
11477 #endif
11480 static void compose_wrap_cb(GtkAction *action, gpointer data)
11482 Compose *compose = (Compose *)data;
11483 compose_beautify_paragraph(compose, NULL, TRUE);
11486 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11488 Compose *compose = (Compose *)data;
11489 compose_wrap_all_full(compose, TRUE);
11492 static void compose_find_cb(GtkAction *action, gpointer data)
11494 Compose *compose = (Compose *)data;
11496 message_search_compose(compose);
11499 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11500 gpointer data)
11502 Compose *compose = (Compose *)data;
11503 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11504 if (compose->autowrap)
11505 compose_wrap_all_full(compose, TRUE);
11506 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11509 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11510 gpointer data)
11512 Compose *compose = (Compose *)data;
11513 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11516 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11518 Compose *compose = (Compose *)data;
11520 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11521 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11524 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11526 Compose *compose = (Compose *)data;
11528 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11529 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11532 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11534 g_free(compose->privacy_system);
11535 g_free(compose->encdata);
11537 compose->privacy_system = g_strdup(account->default_privacy_system);
11538 compose_update_privacy_system_menu_item(compose, warn);
11541 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11543 if (folder_item != NULL) {
11544 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT &&
11545 privacy_system_can_sign(compose->privacy_system)) {
11546 compose_use_signing(compose,
11547 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11549 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT &&
11550 privacy_system_can_encrypt(compose->privacy_system)) {
11551 compose_use_encryption(compose,
11552 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11557 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11559 Compose *compose = (Compose *)data;
11561 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11562 gtk_widget_show(compose->ruler_hbox);
11563 prefs_common.show_ruler = TRUE;
11564 } else {
11565 gtk_widget_hide(compose->ruler_hbox);
11566 gtk_widget_queue_resize(compose->edit_vbox);
11567 prefs_common.show_ruler = FALSE;
11571 static void compose_attach_drag_received_cb (GtkWidget *widget,
11572 GdkDragContext *context,
11573 gint x,
11574 gint y,
11575 GtkSelectionData *data,
11576 guint info,
11577 guint time,
11578 gpointer user_data)
11580 Compose *compose = (Compose *)user_data;
11581 GList *list, *tmp;
11582 GdkAtom type;
11584 type = gtk_selection_data_get_data_type(data);
11585 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11586 && gtk_drag_get_source_widget(context) !=
11587 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11588 list = uri_list_extract_filenames(
11589 (const gchar *)gtk_selection_data_get_data(data));
11590 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11591 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11592 compose_attach_append
11593 (compose, (const gchar *)tmp->data,
11594 utf8_filename, NULL, NULL);
11595 g_free(utf8_filename);
11597 if (list)
11598 compose_changed_cb(NULL, compose);
11599 list_free_strings_full(list);
11600 } else if (gtk_drag_get_source_widget(context)
11601 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11602 /* comes from our summaryview */
11603 SummaryView * summaryview = NULL;
11604 GSList * list = NULL, *cur = NULL;
11606 if (mainwindow_get_mainwindow())
11607 summaryview = mainwindow_get_mainwindow()->summaryview;
11609 if (summaryview)
11610 list = summary_get_selected_msg_list(summaryview);
11612 for (cur = list; cur; cur = cur->next) {
11613 MsgInfo *msginfo = (MsgInfo *)cur->data;
11614 gchar *file = NULL;
11615 if (msginfo)
11616 file = procmsg_get_message_file_full(msginfo,
11617 TRUE, TRUE);
11618 if (file) {
11619 compose_attach_append(compose, (const gchar *)file,
11620 (const gchar *)file, "message/rfc822", NULL);
11621 g_free(file);
11624 g_slist_free(list);
11628 static gboolean compose_drag_drop(GtkWidget *widget,
11629 GdkDragContext *drag_context,
11630 gint x, gint y,
11631 guint time, gpointer user_data)
11633 /* not handling this signal makes compose_insert_drag_received_cb
11634 * called twice */
11635 return TRUE;
11638 static gboolean completion_set_focus_to_subject
11639 (GtkWidget *widget,
11640 GdkEventKey *event,
11641 Compose *compose)
11643 cm_return_val_if_fail(compose != NULL, FALSE);
11645 /* make backtab move to subject field */
11646 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11647 gtk_widget_grab_focus(compose->subject_entry);
11648 return TRUE;
11650 return FALSE;
11653 static void compose_insert_drag_received_cb (GtkWidget *widget,
11654 GdkDragContext *drag_context,
11655 gint x,
11656 gint y,
11657 GtkSelectionData *data,
11658 guint info,
11659 guint time,
11660 gpointer user_data)
11662 Compose *compose = (Compose *)user_data;
11663 GList *list, *tmp;
11664 GdkAtom type;
11665 guint num_files;
11666 gchar *msg;
11668 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11669 * does not work */
11670 type = gtk_selection_data_get_data_type(data);
11671 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11672 AlertValue val = G_ALERTDEFAULT;
11673 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11675 list = uri_list_extract_filenames(ddata);
11676 num_files = g_list_length(list);
11677 if (list == NULL && strstr(ddata, "://")) {
11678 /* Assume a list of no files, and data has ://, is a remote link */
11679 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11680 gchar *tmpfile = get_tmp_file();
11681 str_write_to_file(tmpdata, tmpfile, TRUE);
11682 g_free(tmpdata);
11683 compose_insert_file(compose, tmpfile);
11684 claws_unlink(tmpfile);
11685 g_free(tmpfile);
11686 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11687 compose_beautify_paragraph(compose, NULL, TRUE);
11688 return;
11690 switch (prefs_common.compose_dnd_mode) {
11691 case COMPOSE_DND_ASK:
11692 msg = g_strdup_printf(
11693 ngettext(
11694 "Do you want to insert the contents of the file "
11695 "into the message body, or attach it to the email?",
11696 "Do you want to insert the contents of the %d files "
11697 "into the message body, or attach them to the email?",
11698 num_files),
11699 num_files);
11700 val = alertpanel_full(_("Insert or attach?"), msg,
11701 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11702 ALERTFOCUS_SECOND,
11703 TRUE, NULL, ALERT_QUESTION);
11704 g_free(msg);
11705 break;
11706 case COMPOSE_DND_INSERT:
11707 val = G_ALERTALTERNATE;
11708 break;
11709 case COMPOSE_DND_ATTACH:
11710 val = G_ALERTOTHER;
11711 break;
11712 default:
11713 /* unexpected case */
11714 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11717 if (val & G_ALERTDISABLE) {
11718 val &= ~G_ALERTDISABLE;
11719 /* remember what action to perform by default, only if we don't click Cancel */
11720 if (val == G_ALERTALTERNATE)
11721 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11722 else if (val == G_ALERTOTHER)
11723 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11726 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11727 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11728 list_free_strings_full(list);
11729 return;
11730 } else if (val == G_ALERTOTHER) {
11731 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11732 list_free_strings_full(list);
11733 return;
11736 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11737 compose_insert_file(compose, (const gchar *)tmp->data);
11739 list_free_strings_full(list);
11740 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11741 return;
11745 static void compose_header_drag_received_cb (GtkWidget *widget,
11746 GdkDragContext *drag_context,
11747 gint x,
11748 gint y,
11749 GtkSelectionData *data,
11750 guint info,
11751 guint time,
11752 gpointer user_data)
11754 GtkEditable *entry = (GtkEditable *)user_data;
11755 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11757 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11758 * does not work */
11760 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11761 gchar *decoded=g_new(gchar, strlen(email));
11762 int start = 0;
11764 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11765 gtk_editable_delete_text(entry, 0, -1);
11766 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11767 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11768 g_free(decoded);
11769 return;
11771 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11774 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11776 Compose *compose = (Compose *)data;
11778 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11779 compose->return_receipt = TRUE;
11780 else
11781 compose->return_receipt = FALSE;
11784 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11786 Compose *compose = (Compose *)data;
11788 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11789 compose->remove_references = TRUE;
11790 else
11791 compose->remove_references = FALSE;
11794 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11795 ComposeHeaderEntry *headerentry)
11797 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11798 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11799 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11800 return FALSE;
11803 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11804 GdkEventKey *event,
11805 ComposeHeaderEntry *headerentry)
11807 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11808 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11809 !(event->state & GDK_MODIFIER_MASK) &&
11810 (event->keyval == GDK_KEY_BackSpace) &&
11811 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11812 gtk_container_remove
11813 (GTK_CONTAINER(headerentry->compose->header_table),
11814 headerentry->combo);
11815 gtk_container_remove
11816 (GTK_CONTAINER(headerentry->compose->header_table),
11817 headerentry->entry);
11818 headerentry->compose->header_list =
11819 g_slist_remove(headerentry->compose->header_list,
11820 headerentry);
11821 g_free(headerentry);
11822 } else if (event->keyval == GDK_KEY_Tab) {
11823 if (headerentry->compose->header_last == headerentry) {
11824 /* Override default next focus, and give it to subject_entry
11825 * instead of notebook tabs
11827 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11828 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11829 return TRUE;
11832 return FALSE;
11835 static gboolean scroll_postpone(gpointer data)
11837 Compose *compose = (Compose *)data;
11839 if (compose->batch)
11840 return FALSE;
11842 GTK_EVENTS_FLUSH();
11843 compose_show_first_last_header(compose, FALSE);
11844 return FALSE;
11847 static void compose_headerentry_changed_cb(GtkWidget *entry,
11848 ComposeHeaderEntry *headerentry)
11850 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11851 compose_create_header_entry(headerentry->compose);
11852 g_signal_handlers_disconnect_matched
11853 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11854 0, 0, NULL, NULL, headerentry);
11856 if (!headerentry->compose->batch)
11857 g_timeout_add(0, scroll_postpone, headerentry->compose);
11861 static gboolean compose_defer_auto_save_draft(Compose *compose)
11863 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11864 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11865 return FALSE;
11868 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11870 GtkAdjustment *vadj;
11872 cm_return_if_fail(compose);
11874 if(compose->batch)
11875 return;
11877 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11878 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11879 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11880 gtk_widget_get_parent(compose->header_table)));
11881 gtk_adjustment_set_value(vadj, (show_first ?
11882 gtk_adjustment_get_lower(vadj) :
11883 (gtk_adjustment_get_upper(vadj) -
11884 gtk_adjustment_get_page_size(vadj))));
11885 gtk_adjustment_changed(vadj);
11888 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11889 const gchar *text, gint len, Compose *compose)
11891 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11892 (G_OBJECT(compose->text), "paste_as_quotation"));
11893 GtkTextMark *mark;
11895 cm_return_if_fail(text != NULL);
11897 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11898 G_CALLBACK(text_inserted),
11899 compose);
11900 if (paste_as_quotation) {
11901 gchar *new_text;
11902 const gchar *qmark;
11903 guint pos = 0;
11904 GtkTextIter start_iter;
11906 if (len < 0)
11907 len = strlen(text);
11909 new_text = g_strndup(text, len);
11911 qmark = compose_quote_char_from_context(compose);
11913 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11914 gtk_text_buffer_place_cursor(buffer, iter);
11916 pos = gtk_text_iter_get_offset(iter);
11918 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11919 _("Quote format error at line %d."));
11920 quote_fmt_reset_vartable();
11921 g_free(new_text);
11922 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11923 GINT_TO_POINTER(paste_as_quotation - 1));
11925 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11926 gtk_text_buffer_place_cursor(buffer, iter);
11927 gtk_text_buffer_delete_mark(buffer, mark);
11929 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11930 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11931 compose_beautify_paragraph(compose, &start_iter, FALSE);
11932 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11933 gtk_text_buffer_delete_mark(buffer, mark);
11934 } else {
11935 if (strcmp(text, "\n") || compose->automatic_break
11936 || gtk_text_iter_starts_line(iter)) {
11937 GtkTextIter before_ins;
11938 gtk_text_buffer_insert(buffer, iter, text, len);
11939 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11940 before_ins = *iter;
11941 gtk_text_iter_backward_chars(&before_ins, len);
11942 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11944 } else {
11945 /* check if the preceding is just whitespace or quote */
11946 GtkTextIter start_line;
11947 gchar *tmp = NULL, *quote = NULL;
11948 gint quote_len = 0, is_normal = 0;
11949 start_line = *iter;
11950 gtk_text_iter_set_line_offset(&start_line, 0);
11951 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11952 g_strstrip(tmp);
11954 if (*tmp == '\0') {
11955 is_normal = 1;
11956 } else {
11957 quote = compose_get_quote_str(buffer, &start_line, &quote_len);
11958 if (quote)
11959 is_normal = 1;
11960 g_free(quote);
11962 g_free(tmp);
11964 if (is_normal) {
11965 gtk_text_buffer_insert(buffer, iter, text, len);
11966 } else {
11967 gtk_text_buffer_insert_with_tags_by_name(buffer,
11968 iter, text, len, "no_join", NULL);
11973 if (!paste_as_quotation) {
11974 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11975 compose_beautify_paragraph(compose, iter, FALSE);
11976 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11977 gtk_text_buffer_delete_mark(buffer, mark);
11980 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11981 G_CALLBACK(text_inserted),
11982 compose);
11983 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11985 if (compose_can_autosave(compose) &&
11986 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11987 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11988 compose->draft_timeout_tag = g_timeout_add
11989 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11992 #if USE_ENCHANT
11993 static void compose_check_all(GtkAction *action, gpointer data)
11995 Compose *compose = (Compose *)data;
11996 if (!compose->gtkaspell)
11997 return;
11999 if (gtk_widget_has_focus(compose->subject_entry))
12000 claws_spell_entry_check_all(
12001 CLAWS_SPELL_ENTRY(compose->subject_entry));
12002 else
12003 gtkaspell_check_all(compose->gtkaspell);
12006 static void compose_highlight_all(GtkAction *action, gpointer data)
12008 Compose *compose = (Compose *)data;
12009 if (compose->gtkaspell) {
12010 claws_spell_entry_recheck_all(
12011 CLAWS_SPELL_ENTRY(compose->subject_entry));
12012 gtkaspell_highlight_all(compose->gtkaspell);
12016 static void compose_check_backwards(GtkAction *action, gpointer data)
12018 Compose *compose = (Compose *)data;
12019 if (!compose->gtkaspell) {
12020 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12021 return;
12024 if (gtk_widget_has_focus(compose->subject_entry))
12025 claws_spell_entry_check_backwards(
12026 CLAWS_SPELL_ENTRY(compose->subject_entry));
12027 else
12028 gtkaspell_check_backwards(compose->gtkaspell);
12031 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12033 Compose *compose = (Compose *)data;
12034 if (!compose->gtkaspell) {
12035 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12036 return;
12039 if (gtk_widget_has_focus(compose->subject_entry))
12040 claws_spell_entry_check_forwards_go(
12041 CLAWS_SPELL_ENTRY(compose->subject_entry));
12042 else
12043 gtkaspell_check_forwards_go(compose->gtkaspell);
12045 #endif
12048 *\brief Guess originating forward account from MsgInfo and several
12049 * "common preference" settings. Return NULL if no guess.
12051 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12053 PrefsAccount *account = NULL;
12055 cm_return_val_if_fail(msginfo, NULL);
12056 cm_return_val_if_fail(msginfo->folder, NULL);
12057 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12059 if (msginfo->folder->prefs->enable_default_account)
12060 account = account_find_from_id(msginfo->folder->prefs->default_account);
12062 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12063 gchar *to;
12064 Xstrdup_a(to, msginfo->to, return NULL);
12065 extract_address(to);
12066 account = account_find_from_address(to, FALSE);
12069 if (!account && prefs_common.forward_account_autosel) {
12070 gchar *cc = NULL;
12071 if (!procheader_get_header_from_msginfo
12072 (msginfo, &cc, "Cc:")) {
12073 gchar *buf = cc + strlen("Cc:");
12074 extract_address(buf);
12075 account = account_find_from_address(buf, FALSE);
12076 g_free(cc);
12080 if (!account && prefs_common.forward_account_autosel) {
12081 gchar *deliveredto = NULL;
12082 if (!procheader_get_header_from_msginfo
12083 (msginfo, &deliveredto, "Delivered-To:")) {
12084 gchar *buf = deliveredto + strlen("Delivered-To:");
12085 extract_address(buf);
12086 account = account_find_from_address(buf, FALSE);
12087 g_free(deliveredto);
12091 if (!account)
12092 account = msginfo->folder->folder->account;
12094 return account;
12097 gboolean compose_close(Compose *compose)
12099 gint x, y;
12101 cm_return_val_if_fail(compose, FALSE);
12103 if (!g_mutex_trylock(compose->mutex)) {
12104 /* we have to wait for the (possibly deferred by auto-save)
12105 * drafting to be done, before destroying the compose under
12106 * it. */
12107 debug_print("waiting for drafting to finish...\n");
12108 compose_allow_user_actions(compose, FALSE);
12109 if (compose->close_timeout_tag == 0) {
12110 compose->close_timeout_tag =
12111 g_timeout_add (500, (GSourceFunc) compose_close,
12112 compose);
12114 return TRUE;
12117 if (compose->draft_timeout_tag >= 0) {
12118 g_source_remove(compose->draft_timeout_tag);
12119 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12122 gtkut_widget_get_uposition(compose->window, &x, &y);
12123 if (!compose->batch) {
12124 prefs_common.compose_x = x;
12125 prefs_common.compose_y = y;
12127 g_mutex_unlock(compose->mutex);
12128 compose_destroy(compose);
12129 return FALSE;
12133 * Add entry field for each address in list.
12134 * \param compose E-Mail composition object.
12135 * \param listAddress List of (formatted) E-Mail addresses.
12137 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12138 GList *node;
12139 gchar *addr;
12140 node = listAddress;
12141 while( node ) {
12142 addr = ( gchar * ) node->data;
12143 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12144 node = g_list_next( node );
12148 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12149 guint action, gboolean opening_multiple)
12151 gchar *body = NULL;
12152 GSList *new_msglist = NULL;
12153 MsgInfo *tmp_msginfo = NULL;
12154 gboolean originally_enc = FALSE;
12155 gboolean originally_sig = FALSE;
12156 Compose *compose = NULL;
12157 gchar *s_system = NULL;
12159 cm_return_if_fail(msginfo_list != NULL);
12161 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12162 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12163 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12165 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12166 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12167 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12168 orig_msginfo, mimeinfo);
12169 if (tmp_msginfo != NULL) {
12170 new_msglist = g_slist_append(NULL, tmp_msginfo);
12172 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12173 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12174 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12176 tmp_msginfo->folder = orig_msginfo->folder;
12177 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12178 if (orig_msginfo->tags) {
12179 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12180 tmp_msginfo->folder->tags_dirty = TRUE;
12186 if (!opening_multiple && msgview != NULL)
12187 body = messageview_get_selection(msgview);
12189 if (new_msglist) {
12190 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12191 procmsg_msginfo_free(&tmp_msginfo);
12192 g_slist_free(new_msglist);
12193 } else
12194 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12196 if (compose && originally_enc) {
12197 compose_force_encryption(compose, compose->account, FALSE, s_system);
12200 if (compose && originally_sig && compose->account->default_sign_reply) {
12201 compose_force_signing(compose, compose->account, s_system);
12203 g_free(s_system);
12204 g_free(body);
12205 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12208 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12209 guint action)
12211 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12212 && msginfo_list != NULL
12213 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12214 GSList *cur = msginfo_list;
12215 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12216 "messages. Opening the windows "
12217 "could take some time. Do you "
12218 "want to continue?"),
12219 g_slist_length(msginfo_list));
12220 if (g_slist_length(msginfo_list) > 9
12221 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12222 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12223 g_free(msg);
12224 return;
12226 g_free(msg);
12227 /* We'll open multiple compose windows */
12228 /* let the WM place the next windows */
12229 compose_force_window_origin = FALSE;
12230 for (; cur; cur = cur->next) {
12231 GSList tmplist;
12232 tmplist.data = cur->data;
12233 tmplist.next = NULL;
12234 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12236 compose_force_window_origin = TRUE;
12237 } else {
12238 /* forwarding multiple mails as attachments is done via a
12239 * single compose window */
12240 if (msginfo_list != NULL) {
12241 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12242 } else if (msgview != NULL) {
12243 GSList tmplist;
12244 tmplist.data = msgview->msginfo;
12245 tmplist.next = NULL;
12246 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12247 } else {
12248 debug_print("Nothing to reply to\n");
12253 void compose_check_for_email_account(Compose *compose)
12255 PrefsAccount *ac = NULL, *curr = NULL;
12256 GList *list;
12258 if (!compose)
12259 return;
12261 if (compose->account && compose->account->protocol == A_NNTP) {
12262 ac = account_get_cur_account();
12263 if (ac->protocol == A_NNTP) {
12264 list = account_get_list();
12266 for( ; list != NULL ; list = g_list_next(list)) {
12267 curr = (PrefsAccount *) list->data;
12268 if (curr->protocol != A_NNTP) {
12269 ac = curr;
12270 break;
12274 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12275 ac->account_id);
12279 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12280 const gchar *address)
12282 GSList *msginfo_list = NULL;
12283 gchar *body = messageview_get_selection(msgview);
12284 Compose *compose;
12286 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12288 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12289 compose_check_for_email_account(compose);
12290 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12291 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12292 compose_reply_set_subject(compose, msginfo);
12294 g_free(body);
12295 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12298 void compose_set_position(Compose *compose, gint pos)
12300 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12302 gtkut_text_view_set_position(text, pos);
12305 gboolean compose_search_string(Compose *compose,
12306 const gchar *str, gboolean case_sens)
12308 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12310 return gtkut_text_view_search_string(text, str, case_sens);
12313 gboolean compose_search_string_backward(Compose *compose,
12314 const gchar *str, gboolean case_sens)
12316 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12318 return gtkut_text_view_search_string_backward(text, str, case_sens);
12321 /* allocate a msginfo structure and populate its data from a compose data structure */
12322 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12324 MsgInfo *newmsginfo;
12325 GSList *list;
12326 gchar date[RFC822_DATE_BUFFSIZE];
12328 cm_return_val_if_fail( compose != NULL, NULL );
12330 newmsginfo = procmsg_msginfo_new();
12332 /* date is now */
12333 get_rfc822_date(date, sizeof(date));
12334 newmsginfo->date = g_strdup(date);
12336 /* from */
12337 if (compose->from_name) {
12338 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12339 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12342 /* subject */
12343 if (compose->subject_entry)
12344 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12346 /* to, cc, reply-to, newsgroups */
12347 for (list = compose->header_list; list; list = list->next) {
12348 gchar *header = gtk_editable_get_chars(
12349 GTK_EDITABLE(
12350 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12351 gchar *entry = gtk_editable_get_chars(
12352 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12354 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12355 if ( newmsginfo->to == NULL ) {
12356 newmsginfo->to = g_strdup(entry);
12357 } else if (entry && *entry) {
12358 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12359 g_free(newmsginfo->to);
12360 newmsginfo->to = tmp;
12362 } else
12363 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12364 if ( newmsginfo->cc == NULL ) {
12365 newmsginfo->cc = g_strdup(entry);
12366 } else if (entry && *entry) {
12367 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12368 g_free(newmsginfo->cc);
12369 newmsginfo->cc = tmp;
12371 } else
12372 if ( strcasecmp(header,
12373 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12374 if ( newmsginfo->newsgroups == NULL ) {
12375 newmsginfo->newsgroups = g_strdup(entry);
12376 } else if (entry && *entry) {
12377 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12378 g_free(newmsginfo->newsgroups);
12379 newmsginfo->newsgroups = tmp;
12383 g_free(header);
12384 g_free(entry);
12387 /* other data is unset */
12389 return newmsginfo;
12392 #ifdef USE_ENCHANT
12393 /* update compose's dictionaries from folder dict settings */
12394 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12395 FolderItem *folder_item)
12397 cm_return_if_fail(compose != NULL);
12399 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12400 FolderItemPrefs *prefs = folder_item->prefs;
12402 if (prefs->enable_default_dictionary)
12403 gtkaspell_change_dict(compose->gtkaspell,
12404 prefs->default_dictionary, FALSE);
12405 if (folder_item->prefs->enable_default_alt_dictionary)
12406 gtkaspell_change_alt_dict(compose->gtkaspell,
12407 prefs->default_alt_dictionary);
12408 if (prefs->enable_default_dictionary
12409 || prefs->enable_default_alt_dictionary)
12410 compose_spell_menu_changed(compose);
12413 #endif
12415 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12417 Compose *compose = (Compose *)data;
12419 cm_return_if_fail(compose != NULL);
12421 gtk_widget_grab_focus(compose->text);
12424 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12426 gtk_combo_box_popup(GTK_COMBO_BOX(data));
12431 * End of Source.