correctly set modified flag after auto-save
[claws.git] / src / compose.c
blob88d21c47c82648c537529394a58fda73e675872b
1 /*
2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2022 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>
34 #ifdef GDK_WINDOWING_X11
35 #include <gtk/gtkx.h>
36 #endif
38 #include <pango/pango-break.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <ctype.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <unistd.h>
46 #include <time.h>
47 #if HAVE_SYS_WAIT_H
48 # include <sys/wait.h>
49 #endif
50 #include <signal.h>
51 #include <errno.h>
52 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
53 #include <libgen.h>
54 #endif
55 #ifdef G_OS_WIN32
56 #include <windows.h>
57 #endif
59 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
60 # include <wchar.h>
61 # include <wctype.h>
62 #endif
64 #include "claws.h"
65 #include "main.h"
66 #include "mainwindow.h"
67 #include "compose.h"
68 #ifndef USE_ALT_ADDRBOOK
69 #include "addressbook.h"
70 #else
71 #include "addressbook-dbus.h"
72 #include "addressadd.h"
73 #endif
74 #include "folderview.h"
75 #include "procmsg.h"
76 #include "menu.h"
77 #include "stock_pixmap.h"
78 #include "send_message.h"
79 #include "imap.h"
80 #include "news.h"
81 #include "customheader.h"
82 #include "prefs_common.h"
83 #include "prefs_account.h"
84 #include "action.h"
85 #include "account.h"
86 #include "filesel.h"
87 #include "procheader.h"
88 #include "procmime.h"
89 #include "statusbar.h"
90 #include "about.h"
91 #include "quoted-printable.h"
92 #include "codeconv.h"
93 #include "utils.h"
94 #include "gtkutils.h"
95 #include "gtkshruler.h"
96 #include "socket.h"
97 #include "alertpanel.h"
98 #include "manage_window.h"
99 #include "folder.h"
100 #include "folder_item_prefs.h"
101 #include "addr_compl.h"
102 #include "quote_fmt.h"
103 #include "undo.h"
104 #include "foldersel.h"
105 #include "toolbar.h"
106 #include "inc.h"
107 #include "message_search.h"
108 #include "combobox.h"
109 #include "hooks.h"
110 #include "privacy.h"
111 #include "timing.h"
112 #include "autofaces.h"
113 #include "spell_entry.h"
114 #include "headers.h"
115 #include "file-utils.h"
117 #ifdef USE_LDAP
118 #include "password.h"
119 #include "ldapserver.h"
120 #endif
122 enum
124 COL_MIMETYPE = 0,
125 COL_SIZE = 1,
126 COL_NAME = 2,
127 COL_CHARSET = 3,
128 COL_DATA = 4,
129 COL_AUTODATA = 5,
130 N_COL_COLUMNS
133 #define N_ATTACH_COLS (N_COL_COLUMNS)
135 typedef enum
137 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
138 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
139 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
140 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
141 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
142 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
143 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
144 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
145 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
146 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
147 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
148 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
149 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
150 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
151 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
152 } ComposeCallAdvancedAction;
154 typedef enum
156 PRIORITY_HIGHEST = 1,
157 PRIORITY_HIGH,
158 PRIORITY_NORMAL,
159 PRIORITY_LOW,
160 PRIORITY_LOWEST
161 } PriorityLevel;
163 typedef enum
165 COMPOSE_INSERT_SUCCESS,
166 COMPOSE_INSERT_READ_ERROR,
167 COMPOSE_INSERT_INVALID_CHARACTER,
168 COMPOSE_INSERT_NO_FILE
169 } ComposeInsertResult;
171 typedef enum
173 COMPOSE_WRITE_FOR_SEND,
174 COMPOSE_WRITE_FOR_STORE
175 } ComposeWriteType;
177 typedef enum
179 COMPOSE_QUOTE_FORCED,
180 COMPOSE_QUOTE_CHECK,
181 COMPOSE_QUOTE_SKIP
182 } ComposeQuoteMode;
184 typedef enum {
185 TO_FIELD_PRESENT,
186 SUBJECT_FIELD_PRESENT,
187 BODY_FIELD_PRESENT,
188 NO_FIELD_PRESENT
189 } MailField;
191 #define B64_LINE_SIZE 57
192 #define B64_BUFFSIZE 77
194 #define MAX_REFERENCES_LEN 999
196 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
197 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
199 #define COMPOSE_PRIVACY_WARNING() { \
200 alertpanel_error(_("You have opted to sign and/or encrypt this " \
201 "message but have not selected a privacy system.\n\n" \
202 "Signing and encrypting have been disabled for this " \
203 "message.")); \
206 #ifdef G_OS_WIN32
207 #define INVALID_PID INVALID_HANDLE_VALUE
208 #else
209 #define INVALID_PID -1
210 #endif
212 static GdkRGBA default_header_bgcolor =
213 {0, 0, 0, 1};
215 static GdkRGBA default_header_color =
216 {0, 0, 0, 1};
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 GdkRGBA quote_color1 =
821 {0, 0, 0, 1};
822 static GdkRGBA quote_color2 =
823 {0, 0, 0, 1};
824 static GdkRGBA quote_color3 =
825 {0, 0, 0, 1};
827 static GdkRGBA quote_bgcolor1 =
828 {0, 0, 0, 1};
829 static GdkRGBA quote_bgcolor2 =
830 {0, 0, 0, 1};
831 static GdkRGBA quote_bgcolor3 =
832 {0, 0, 0, 1};
834 static GdkRGBA signature_color =
835 {0.5, 0.5, 0.5, 1};
837 static GdkRGBA uri_color =
838 {0, 0, 0, 1};
840 static void compose_create_tags(GtkTextView *text, Compose *compose)
842 GtkTextBuffer *buffer;
843 GdkRGBA black = { 0, 0, 0, 1 };
845 buffer = gtk_text_view_get_buffer(text);
847 if (prefs_common.enable_color) {
848 /* grab the quote colors, converting from an int to a GdkColor */
849 quote_color1 = prefs_common.color[COL_QUOTE_LEVEL1];
850 quote_color2 = prefs_common.color[COL_QUOTE_LEVEL2];
851 quote_color3 = prefs_common.color[COL_QUOTE_LEVEL3];
852 quote_bgcolor1 = prefs_common.color[COL_QUOTE_LEVEL1_BG];
853 quote_bgcolor2 = prefs_common.color[COL_QUOTE_LEVEL2_BG];
854 quote_bgcolor3 = prefs_common.color[COL_QUOTE_LEVEL3_BG];
855 signature_color = prefs_common.color[COL_SIGNATURE];
856 uri_color = prefs_common.color[COL_URI];
857 } else {
858 signature_color = quote_color1 = quote_color2 = quote_color3 =
859 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
862 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
863 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
864 "foreground-rgba", &quote_color1,
865 "paragraph-background-rgba", &quote_bgcolor1,
866 NULL);
867 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
868 "foreground-rgba", &quote_color2,
869 "paragraph-background-rgba", &quote_bgcolor2,
870 NULL);
871 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
872 "foreground-rgba", &quote_color3,
873 "paragraph-background-rgba", &quote_bgcolor3,
874 NULL);
875 } else {
876 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
877 "foreground-rgba", &quote_color1,
878 NULL);
879 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
880 "foreground-rgba", &quote_color2,
881 NULL);
882 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
883 "foreground-rgba", &quote_color3,
884 NULL);
887 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
888 "foreground-rgba", &signature_color,
889 NULL);
891 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
892 "foreground-rgba", &uri_color,
893 NULL);
894 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
895 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
898 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
899 GList *attach_files)
901 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
904 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
906 return compose_generic_new(account, mailto, item, NULL, NULL);
909 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
911 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
914 #define SCROLL_TO_CURSOR(compose) { \
915 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
916 gtk_text_view_get_buffer( \
917 GTK_TEXT_VIEW(compose->text))); \
918 gtk_text_view_scroll_mark_onscreen( \
919 GTK_TEXT_VIEW(compose->text), \
920 cmark); \
923 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
925 GtkEditable *entry;
926 if (folderidentifier) {
927 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
928 prefs_common.compose_save_to_history = add_history(
929 prefs_common.compose_save_to_history, folderidentifier);
930 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
931 prefs_common.compose_save_to_history);
934 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
935 if (folderidentifier)
936 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
937 else
938 gtk_entry_set_text(GTK_ENTRY(entry), "");
941 static gchar *compose_get_save_to(Compose *compose)
943 GtkEditable *entry;
944 gchar *result = NULL;
945 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
946 result = gtk_editable_get_chars(entry, 0, -1);
948 if (result) {
949 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
950 prefs_common.compose_save_to_history = add_history(
951 prefs_common.compose_save_to_history, result);
952 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
953 prefs_common.compose_save_to_history);
955 return result;
958 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
959 GList *attach_files, GList *listAddress )
961 Compose *compose;
962 GtkTextView *textview;
963 GtkTextBuffer *textbuf;
964 GtkTextIter iter;
965 const gchar *subject_format = NULL;
966 const gchar *body_format = NULL;
967 gchar *mailto_from = NULL;
968 PrefsAccount *mailto_account = NULL;
969 MsgInfo* dummyinfo = NULL;
970 gint cursor_pos = -1;
971 MailField mfield = NO_FIELD_PRESENT;
972 gchar* buf;
973 GtkTextMark *mark;
975 /* check if mailto defines a from */
976 if (mailto && *mailto != '\0') {
977 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
978 /* mailto defines a from, check if we can get account prefs from it,
979 if not, the account prefs will be guessed using other ways, but we'll keep
980 the from anyway */
981 if (mailto_from) {
982 mailto_account = account_find_from_address(mailto_from, TRUE);
983 if (mailto_account == NULL) {
984 gchar *tmp_from;
985 Xstrdup_a(tmp_from, mailto_from, return NULL);
986 extract_address(tmp_from);
987 mailto_account = account_find_from_address(tmp_from, TRUE);
990 if (mailto_account)
991 account = mailto_account;
994 /* if no account prefs set from mailto, set if from folder prefs (if any) */
995 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
996 account = account_find_from_id(item->prefs->default_account);
998 /* if no account prefs set, fallback to the current one */
999 if (!account) account = cur_account;
1000 cm_return_val_if_fail(account != NULL, NULL);
1002 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1003 compose_apply_folder_privacy_settings(compose, item);
1005 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1006 (account->default_encrypt || account->default_sign))
1007 COMPOSE_PRIVACY_WARNING();
1009 /* override from name if mailto asked for it */
1010 if (mailto_from) {
1011 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1012 g_free(mailto_from);
1013 } else
1014 /* override from name according to folder properties */
1015 if (item && item->prefs &&
1016 item->prefs->compose_with_format &&
1017 item->prefs->compose_override_from_format &&
1018 *item->prefs->compose_override_from_format != '\0') {
1020 gchar *tmp = NULL;
1021 gchar *buf = NULL;
1023 dummyinfo = compose_msginfo_new_from_compose(compose);
1025 /* decode \-escape sequences in the internal representation of the quote format */
1026 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1027 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1029 #ifdef USE_ENCHANT
1030 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1031 compose->gtkaspell);
1032 #else
1033 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1034 #endif
1035 quote_fmt_scan_string(tmp);
1036 quote_fmt_parse();
1038 buf = quote_fmt_get_buffer();
1039 if (buf == NULL)
1040 alertpanel_error(_("New message From format error."));
1041 else
1042 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1043 quote_fmt_reset_vartable();
1044 quote_fmtlex_destroy();
1046 g_free(tmp);
1049 compose->replyinfo = NULL;
1050 compose->fwdinfo = NULL;
1052 textview = GTK_TEXT_VIEW(compose->text);
1053 textbuf = gtk_text_view_get_buffer(textview);
1054 compose_create_tags(textview, compose);
1056 undo_block(compose->undostruct);
1057 #ifdef USE_ENCHANT
1058 compose_set_dictionaries_from_folder_prefs(compose, item);
1059 #endif
1061 if (account->auto_sig)
1062 compose_insert_sig(compose, FALSE);
1063 gtk_text_buffer_get_start_iter(textbuf, &iter);
1064 gtk_text_buffer_place_cursor(textbuf, &iter);
1066 if (account->protocol != A_NNTP) {
1067 if (mailto && *mailto != '\0') {
1068 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1070 } else {
1071 compose_set_folder_prefs(compose, item, TRUE);
1073 if (item && item->ret_rcpt) {
1074 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1076 } else {
1077 if (mailto && *mailto != '\0') {
1078 if (!strchr(mailto, '@'))
1079 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1080 else
1081 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1082 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1083 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1084 mfield = TO_FIELD_PRESENT;
1087 * CLAWS: just don't allow return receipt request, even if the user
1088 * may want to send an email. simple but foolproof.
1090 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1092 compose_add_field_list( compose, listAddress );
1094 if (item && item->prefs && item->prefs->compose_with_format) {
1095 subject_format = item->prefs->compose_subject_format;
1096 body_format = item->prefs->compose_body_format;
1097 } else if (account->compose_with_format) {
1098 subject_format = account->compose_subject_format;
1099 body_format = account->compose_body_format;
1100 } else if (prefs_common.compose_with_format) {
1101 subject_format = prefs_common.compose_subject_format;
1102 body_format = prefs_common.compose_body_format;
1105 if (subject_format || body_format) {
1107 if ( subject_format
1108 && *subject_format != '\0' )
1110 gchar *subject = NULL;
1111 gchar *tmp = NULL;
1112 gchar *buf = NULL;
1114 if (!dummyinfo)
1115 dummyinfo = compose_msginfo_new_from_compose(compose);
1117 /* decode \-escape sequences in the internal representation of the quote format */
1118 tmp = g_malloc(strlen(subject_format)+1);
1119 pref_get_unescaped_pref(tmp, subject_format);
1121 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1122 #ifdef USE_ENCHANT
1123 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1124 compose->gtkaspell);
1125 #else
1126 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1127 #endif
1128 quote_fmt_scan_string(tmp);
1129 quote_fmt_parse();
1131 buf = quote_fmt_get_buffer();
1132 if (buf == NULL)
1133 alertpanel_error(_("New message subject format error."));
1134 else
1135 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1136 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1137 quote_fmt_reset_vartable();
1138 quote_fmtlex_destroy();
1140 g_free(subject);
1141 g_free(tmp);
1142 mfield = SUBJECT_FIELD_PRESENT;
1145 if ( body_format
1146 && *body_format != '\0' )
1148 GtkTextView *text;
1149 GtkTextBuffer *buffer;
1150 GtkTextIter start, end;
1151 gchar *tmp = NULL;
1153 if (!dummyinfo)
1154 dummyinfo = compose_msginfo_new_from_compose(compose);
1156 text = GTK_TEXT_VIEW(compose->text);
1157 buffer = gtk_text_view_get_buffer(text);
1158 gtk_text_buffer_get_start_iter(buffer, &start);
1159 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1160 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1162 compose_quote_fmt(compose, dummyinfo,
1163 body_format,
1164 NULL, tmp, FALSE, TRUE,
1165 _("The body of the \"New message\" template has an error at line %d."));
1166 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1167 quote_fmt_reset_vartable();
1169 g_free(tmp);
1170 #ifdef USE_ENCHANT
1171 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1172 gtkaspell_highlight_all(compose->gtkaspell);
1173 #endif
1174 mfield = BODY_FIELD_PRESENT;
1178 procmsg_msginfo_free( &dummyinfo );
1180 if (attach_files) {
1181 GList *curr;
1182 AttachInfo *ainfo;
1184 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1185 ainfo = (AttachInfo *) curr->data;
1186 if (ainfo->insert)
1187 compose_insert_file(compose, ainfo->file);
1188 else
1189 compose_attach_append(compose, ainfo->file, ainfo->file,
1190 ainfo->content_type, ainfo->charset);
1194 compose_show_first_last_header(compose, TRUE);
1196 /* Set save folder */
1197 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1198 gchar *folderidentifier;
1200 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1201 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1202 folderidentifier = folder_item_get_identifier(item);
1203 compose_set_save_to(compose, folderidentifier);
1204 g_free(folderidentifier);
1207 /* Place cursor according to provided input (mfield) */
1208 switch (mfield) {
1209 case NO_FIELD_PRESENT:
1210 if (compose->header_last)
1211 gtk_widget_grab_focus(compose->header_last->entry);
1212 break;
1213 case TO_FIELD_PRESENT:
1214 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1215 if (buf) {
1216 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1217 g_free(buf);
1219 gtk_widget_grab_focus(compose->subject_entry);
1220 break;
1221 case SUBJECT_FIELD_PRESENT:
1222 textview = GTK_TEXT_VIEW(compose->text);
1223 if (!textview)
1224 break;
1225 textbuf = gtk_text_view_get_buffer(textview);
1226 if (!textbuf)
1227 break;
1228 mark = gtk_text_buffer_get_insert(textbuf);
1229 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1230 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1232 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1233 * only defers where it comes to the variable body
1234 * is not null. If no body is present compose->text
1235 * will be null in which case you cannot place the
1236 * cursor inside the component so. An empty component
1237 * is therefore created before placing the cursor
1239 case BODY_FIELD_PRESENT:
1240 cursor_pos = quote_fmt_get_cursor_pos();
1241 if (cursor_pos == -1)
1242 gtk_widget_grab_focus(compose->header_last->entry);
1243 else
1244 gtk_widget_grab_focus(compose->text);
1245 break;
1248 undo_unblock(compose->undostruct);
1250 if (prefs_common.auto_exteditor)
1251 compose_exec_ext_editor(compose);
1253 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1255 SCROLL_TO_CURSOR(compose);
1257 compose->modified = FALSE;
1258 compose_set_title(compose);
1260 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1262 return compose;
1265 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1266 gboolean override_pref, const gchar *system)
1268 const gchar *privacy = NULL;
1270 cm_return_if_fail(compose != NULL);
1271 cm_return_if_fail(account != NULL);
1273 if (privacy_system_can_encrypt(compose->privacy_system) == FALSE ||
1274 (override_pref == FALSE && account->default_encrypt_reply == FALSE))
1275 return;
1277 if (account->default_privacy_system && strlen(account->default_privacy_system))
1278 privacy = account->default_privacy_system;
1279 else if (system)
1280 privacy = system;
1281 else {
1282 GSList *privacy_avail = privacy_get_system_ids();
1283 if (privacy_avail && g_slist_length(privacy_avail)) {
1284 privacy = (gchar *)(privacy_avail->data);
1286 g_slist_free_full(privacy_avail, g_free);
1288 if (privacy != NULL) {
1289 if (system) {
1290 g_free(compose->privacy_system);
1291 compose->privacy_system = NULL;
1292 g_free(compose->encdata);
1293 compose->encdata = NULL;
1295 if (compose->privacy_system == NULL)
1296 compose->privacy_system = g_strdup(privacy);
1297 else if (*(compose->privacy_system) == '\0') {
1298 g_free(compose->privacy_system);
1299 g_free(compose->encdata);
1300 compose->encdata = NULL;
1301 compose->privacy_system = g_strdup(privacy);
1303 compose_update_privacy_system_menu_item(compose, FALSE);
1304 compose_use_encryption(compose, TRUE);
1308 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1310 const gchar *privacy = NULL;
1311 if (privacy_system_can_sign(compose->privacy_system) == FALSE)
1312 return;
1314 if (account->default_privacy_system && strlen(account->default_privacy_system))
1315 privacy = account->default_privacy_system;
1316 else if (system)
1317 privacy = system;
1318 else {
1319 GSList *privacy_avail = privacy_get_system_ids();
1320 if (privacy_avail && g_slist_length(privacy_avail)) {
1321 privacy = (gchar *)(privacy_avail->data);
1325 if (privacy != NULL) {
1326 if (system) {
1327 g_free(compose->privacy_system);
1328 compose->privacy_system = NULL;
1329 g_free(compose->encdata);
1330 compose->encdata = NULL;
1332 if (compose->privacy_system == NULL)
1333 compose->privacy_system = g_strdup(privacy);
1334 compose_update_privacy_system_menu_item(compose, FALSE);
1335 compose_use_signing(compose, TRUE);
1339 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1341 MsgInfo *msginfo;
1342 guint list_len;
1343 Compose *compose = NULL;
1345 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1347 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1348 cm_return_val_if_fail(msginfo != NULL, NULL);
1350 list_len = g_slist_length(msginfo_list);
1352 switch (mode) {
1353 case COMPOSE_REPLY:
1354 case COMPOSE_REPLY_TO_ADDRESS:
1355 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1356 FALSE, prefs_common.default_reply_list, FALSE, body);
1357 break;
1358 case COMPOSE_REPLY_WITH_QUOTE:
1359 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1360 FALSE, prefs_common.default_reply_list, FALSE, body);
1361 break;
1362 case COMPOSE_REPLY_WITHOUT_QUOTE:
1363 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1364 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1365 break;
1366 case COMPOSE_REPLY_TO_SENDER:
1367 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1368 FALSE, FALSE, TRUE, body);
1369 break;
1370 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1371 compose = compose_followup_and_reply_to(msginfo,
1372 COMPOSE_QUOTE_CHECK,
1373 FALSE, FALSE, body);
1374 break;
1375 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1376 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1377 FALSE, FALSE, TRUE, body);
1378 break;
1379 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1380 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1381 FALSE, FALSE, TRUE, NULL);
1382 break;
1383 case COMPOSE_REPLY_TO_ALL:
1384 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1385 TRUE, FALSE, FALSE, body);
1386 break;
1387 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1388 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1389 TRUE, FALSE, FALSE, body);
1390 break;
1391 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1392 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1393 TRUE, FALSE, FALSE, NULL);
1394 break;
1395 case COMPOSE_REPLY_TO_LIST:
1396 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1397 FALSE, TRUE, FALSE, body);
1398 break;
1399 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1400 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1401 FALSE, TRUE, FALSE, body);
1402 break;
1403 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1404 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1405 FALSE, TRUE, FALSE, NULL);
1406 break;
1407 case COMPOSE_FORWARD:
1408 if (prefs_common.forward_as_attachment) {
1409 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1410 return compose;
1411 } else {
1412 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1413 return compose;
1415 break;
1416 case COMPOSE_FORWARD_INLINE:
1417 /* check if we reply to more than one Message */
1418 if (list_len == 1) {
1419 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1420 break;
1422 /* more messages FALL THROUGH */
1423 case COMPOSE_FORWARD_AS_ATTACH:
1424 compose = compose_forward_multiple(NULL, msginfo_list);
1425 break;
1426 case COMPOSE_REDIRECT:
1427 compose = compose_redirect(NULL, msginfo, FALSE);
1428 break;
1429 default:
1430 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1433 if (compose == NULL) {
1434 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1435 return NULL;
1438 compose->rmode = mode;
1439 switch (compose->rmode) {
1440 case COMPOSE_REPLY:
1441 case COMPOSE_REPLY_WITH_QUOTE:
1442 case COMPOSE_REPLY_WITHOUT_QUOTE:
1443 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1444 debug_print("reply mode Normal\n");
1445 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1446 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1447 break;
1448 case COMPOSE_REPLY_TO_SENDER:
1449 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1450 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1451 debug_print("reply mode Sender\n");
1452 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1453 break;
1454 case COMPOSE_REPLY_TO_ALL:
1455 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1456 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1457 debug_print("reply mode All\n");
1458 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1459 break;
1460 case COMPOSE_REPLY_TO_LIST:
1461 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1462 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1463 debug_print("reply mode List\n");
1464 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1465 break;
1466 case COMPOSE_REPLY_TO_ADDRESS:
1467 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1468 break;
1469 default:
1470 break;
1472 return compose;
1475 static Compose *compose_reply(MsgInfo *msginfo,
1476 ComposeQuoteMode quote_mode,
1477 gboolean to_all,
1478 gboolean to_ml,
1479 gboolean to_sender,
1480 const gchar *body)
1482 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1483 to_sender, FALSE, body);
1486 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1487 ComposeQuoteMode quote_mode,
1488 gboolean to_all,
1489 gboolean to_sender,
1490 const gchar *body)
1492 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1493 to_sender, TRUE, body);
1496 static void compose_extract_original_charset(Compose *compose)
1498 MsgInfo *info = NULL;
1499 if (compose->replyinfo) {
1500 info = compose->replyinfo;
1501 } else if (compose->fwdinfo) {
1502 info = compose->fwdinfo;
1503 } else if (compose->targetinfo) {
1504 info = compose->targetinfo;
1506 if (info) {
1507 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1508 MimeInfo *partinfo = mimeinfo;
1509 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1510 partinfo = procmime_mimeinfo_next(partinfo);
1511 if (partinfo) {
1512 compose->orig_charset =
1513 g_strdup(procmime_mimeinfo_get_parameter(
1514 partinfo, "charset"));
1516 procmime_mimeinfo_free_all(&mimeinfo);
1520 #define SIGNAL_BLOCK(buffer) { \
1521 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1522 G_CALLBACK(compose_changed_cb), \
1523 compose); \
1524 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1525 G_CALLBACK(text_inserted), \
1526 compose); \
1529 #define SIGNAL_UNBLOCK(buffer) { \
1530 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1531 G_CALLBACK(compose_changed_cb), \
1532 compose); \
1533 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1534 G_CALLBACK(text_inserted), \
1535 compose); \
1538 static Compose *compose_generic_reply(MsgInfo *msginfo,
1539 ComposeQuoteMode quote_mode,
1540 gboolean to_all, gboolean to_ml,
1541 gboolean to_sender,
1542 gboolean followup_and_reply_to,
1543 const gchar *body)
1545 Compose *compose;
1546 PrefsAccount *account = NULL;
1547 GtkTextView *textview;
1548 GtkTextBuffer *textbuf;
1549 gboolean quote = FALSE;
1550 const gchar *qmark = NULL;
1551 const gchar *body_fmt = NULL;
1552 gchar *s_system = NULL;
1553 START_TIMING("");
1554 cm_return_val_if_fail(msginfo != NULL, NULL);
1555 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1557 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1559 cm_return_val_if_fail(account != NULL, NULL);
1561 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1562 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1564 compose->updating = TRUE;
1566 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1567 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1569 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1570 if (!compose->replyinfo)
1571 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1573 compose_extract_original_charset(compose);
1575 if (msginfo->folder && msginfo->folder->ret_rcpt)
1576 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1578 /* Set save folder */
1579 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1580 gchar *folderidentifier;
1582 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1583 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1584 folderidentifier = folder_item_get_identifier(msginfo->folder);
1585 compose_set_save_to(compose, folderidentifier);
1586 g_free(folderidentifier);
1589 if (compose_parse_header(compose, msginfo) < 0) {
1590 compose->updating = FALSE;
1591 compose_destroy(compose);
1592 return NULL;
1595 /* override from name according to folder properties */
1596 if (msginfo->folder && msginfo->folder->prefs &&
1597 msginfo->folder->prefs->reply_with_format &&
1598 msginfo->folder->prefs->reply_override_from_format &&
1599 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1601 gchar *tmp = NULL;
1602 gchar *buf = NULL;
1604 /* decode \-escape sequences in the internal representation of the quote format */
1605 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1606 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1608 #ifdef USE_ENCHANT
1609 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1610 compose->gtkaspell);
1611 #else
1612 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1613 #endif
1614 quote_fmt_scan_string(tmp);
1615 quote_fmt_parse();
1617 buf = quote_fmt_get_buffer();
1618 if (buf == NULL)
1619 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1620 else
1621 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1622 quote_fmt_reset_vartable();
1623 quote_fmtlex_destroy();
1625 g_free(tmp);
1628 textview = (GTK_TEXT_VIEW(compose->text));
1629 textbuf = gtk_text_view_get_buffer(textview);
1630 compose_create_tags(textview, compose);
1632 undo_block(compose->undostruct);
1633 #ifdef USE_ENCHANT
1634 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1635 gtkaspell_block_check(compose->gtkaspell);
1636 #endif
1638 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1639 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1640 /* use the reply format of folder (if enabled), or the account's one
1641 (if enabled) or fallback to the global reply format, which is always
1642 enabled (even if empty), and use the relevant quotemark */
1643 quote = TRUE;
1644 if (msginfo->folder && msginfo->folder->prefs &&
1645 msginfo->folder->prefs->reply_with_format) {
1646 qmark = msginfo->folder->prefs->reply_quotemark;
1647 body_fmt = msginfo->folder->prefs->reply_body_format;
1649 } else if (account->reply_with_format) {
1650 qmark = account->reply_quotemark;
1651 body_fmt = account->reply_body_format;
1653 } else {
1654 qmark = prefs_common.quotemark;
1655 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1656 body_fmt = gettext(prefs_common.quotefmt);
1657 else
1658 body_fmt = "";
1662 if (quote) {
1663 /* empty quotemark is not allowed */
1664 if (qmark == NULL || *qmark == '\0')
1665 qmark = "> ";
1666 compose_quote_fmt(compose, compose->replyinfo,
1667 body_fmt, qmark, body, FALSE, TRUE,
1668 _("The body of the \"Reply\" template has an error at line %d."));
1669 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1670 quote_fmt_reset_vartable();
1673 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1674 compose_force_encryption(compose, account, FALSE, s_system);
1677 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1678 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1679 compose_force_signing(compose, account, s_system);
1681 g_free(s_system);
1683 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1684 ((account->default_encrypt || account->default_sign) ||
1685 (account->default_encrypt_reply && MSG_IS_ENCRYPTED(compose->replyinfo->flags)) ||
1686 (account->default_sign_reply && MSG_IS_SIGNED(compose->replyinfo->flags))))
1687 COMPOSE_PRIVACY_WARNING();
1689 SIGNAL_BLOCK(textbuf);
1691 if (account->auto_sig)
1692 compose_insert_sig(compose, FALSE);
1694 compose_wrap_all(compose);
1696 #ifdef USE_ENCHANT
1697 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1698 gtkaspell_highlight_all(compose->gtkaspell);
1699 gtkaspell_unblock_check(compose->gtkaspell);
1700 #endif
1701 SIGNAL_UNBLOCK(textbuf);
1703 gtk_widget_grab_focus(compose->text);
1705 undo_unblock(compose->undostruct);
1707 if (prefs_common.auto_exteditor)
1708 compose_exec_ext_editor(compose);
1710 compose->modified = FALSE;
1711 compose_set_title(compose);
1713 compose->updating = FALSE;
1714 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1715 SCROLL_TO_CURSOR(compose);
1717 if (compose->deferred_destroy) {
1718 compose_destroy(compose);
1719 return NULL;
1721 END_TIMING();
1723 return compose;
1726 #define INSERT_FW_HEADER(var, hdr) \
1727 if (msginfo->var && *msginfo->var) { \
1728 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1729 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1730 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1733 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1734 gboolean as_attach, const gchar *body,
1735 gboolean no_extedit,
1736 gboolean batch)
1738 Compose *compose;
1739 GtkTextView *textview;
1740 GtkTextBuffer *textbuf;
1741 gint cursor_pos = -1;
1742 ComposeMode mode;
1744 cm_return_val_if_fail(msginfo != NULL, NULL);
1745 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1747 if (!account && !(account = compose_find_account(msginfo)))
1748 account = cur_account;
1750 if (!prefs_common.forward_as_attachment)
1751 mode = COMPOSE_FORWARD_INLINE;
1752 else
1753 mode = COMPOSE_FORWARD;
1754 compose = compose_create(account, msginfo->folder, mode, batch);
1755 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1757 compose->updating = TRUE;
1758 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1759 if (!compose->fwdinfo)
1760 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1762 compose_extract_original_charset(compose);
1764 if (msginfo->subject && *msginfo->subject) {
1765 gchar *buf, *buf2, *p;
1767 buf = p = g_strdup(msginfo->subject);
1768 p += subject_get_prefix_length(p);
1769 memmove(buf, p, strlen(p) + 1);
1771 buf2 = g_strdup_printf("Fw: %s", buf);
1772 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1774 g_free(buf);
1775 g_free(buf2);
1778 /* override from name according to folder properties */
1779 if (msginfo->folder && msginfo->folder->prefs &&
1780 msginfo->folder->prefs->forward_with_format &&
1781 msginfo->folder->prefs->forward_override_from_format &&
1782 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1784 gchar *tmp = NULL;
1785 gchar *buf = NULL;
1786 MsgInfo *full_msginfo = NULL;
1788 if (!as_attach)
1789 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1790 if (!full_msginfo)
1791 full_msginfo = procmsg_msginfo_copy(msginfo);
1793 /* decode \-escape sequences in the internal representation of the quote format */
1794 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1795 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1797 #ifdef USE_ENCHANT
1798 gtkaspell_block_check(compose->gtkaspell);
1799 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1800 compose->gtkaspell);
1801 #else
1802 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1803 #endif
1804 quote_fmt_scan_string(tmp);
1805 quote_fmt_parse();
1807 buf = quote_fmt_get_buffer();
1808 if (buf == NULL)
1809 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1810 else
1811 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1812 quote_fmt_reset_vartable();
1813 quote_fmtlex_destroy();
1815 g_free(tmp);
1816 procmsg_msginfo_free(&full_msginfo);
1819 textview = GTK_TEXT_VIEW(compose->text);
1820 textbuf = gtk_text_view_get_buffer(textview);
1821 compose_create_tags(textview, compose);
1823 undo_block(compose->undostruct);
1824 if (as_attach) {
1825 gchar *msgfile;
1827 msgfile = procmsg_get_message_file(msginfo);
1828 if (!is_file_exist(msgfile))
1829 g_warning("%s: file does not exist", msgfile);
1830 else
1831 compose_attach_append(compose, msgfile, msgfile,
1832 "message/rfc822", NULL);
1834 g_free(msgfile);
1835 } else {
1836 const gchar *qmark = NULL;
1837 const gchar *body_fmt = NULL;
1838 MsgInfo *full_msginfo;
1840 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1841 if (!full_msginfo)
1842 full_msginfo = procmsg_msginfo_copy(msginfo);
1844 /* use the forward format of folder (if enabled), or the account's one
1845 (if enabled) or fallback to the global forward format, which is always
1846 enabled (even if empty), and use the relevant quotemark */
1847 if (msginfo->folder && msginfo->folder->prefs &&
1848 msginfo->folder->prefs->forward_with_format) {
1849 qmark = msginfo->folder->prefs->forward_quotemark;
1850 body_fmt = msginfo->folder->prefs->forward_body_format;
1852 } else if (account->forward_with_format) {
1853 qmark = account->forward_quotemark;
1854 body_fmt = account->forward_body_format;
1856 } else {
1857 qmark = prefs_common.fw_quotemark;
1858 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1859 body_fmt = gettext(prefs_common.fw_quotefmt);
1860 else
1861 body_fmt = "";
1864 /* empty quotemark is not allowed */
1865 if (qmark == NULL || *qmark == '\0')
1866 qmark = "> ";
1868 compose_quote_fmt(compose, full_msginfo,
1869 body_fmt, qmark, body, FALSE, TRUE,
1870 _("The body of the \"Forward\" template has an error at line %d."));
1871 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1872 quote_fmt_reset_vartable();
1873 compose_attach_parts(compose, msginfo);
1875 procmsg_msginfo_free(&full_msginfo);
1878 SIGNAL_BLOCK(textbuf);
1880 if (account->auto_sig)
1881 compose_insert_sig(compose, FALSE);
1883 compose_wrap_all(compose);
1885 #ifdef USE_ENCHANT
1886 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1887 gtkaspell_highlight_all(compose->gtkaspell);
1888 gtkaspell_unblock_check(compose->gtkaspell);
1889 #endif
1890 SIGNAL_UNBLOCK(textbuf);
1892 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1893 (account->default_encrypt || account->default_sign))
1894 COMPOSE_PRIVACY_WARNING();
1896 cursor_pos = quote_fmt_get_cursor_pos();
1897 if (cursor_pos == -1)
1898 gtk_widget_grab_focus(compose->header_last->entry);
1899 else
1900 gtk_widget_grab_focus(compose->text);
1902 if (!no_extedit && prefs_common.auto_exteditor)
1903 compose_exec_ext_editor(compose);
1905 /*save folder*/
1906 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1907 gchar *folderidentifier;
1909 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1910 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1911 folderidentifier = folder_item_get_identifier(msginfo->folder);
1912 compose_set_save_to(compose, folderidentifier);
1913 g_free(folderidentifier);
1916 undo_unblock(compose->undostruct);
1918 compose->modified = FALSE;
1919 compose_set_title(compose);
1921 compose->updating = FALSE;
1922 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1923 SCROLL_TO_CURSOR(compose);
1925 if (compose->deferred_destroy) {
1926 compose_destroy(compose);
1927 return NULL;
1930 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1932 return compose;
1935 #undef INSERT_FW_HEADER
1937 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1939 Compose *compose;
1940 GtkTextView *textview;
1941 GtkTextBuffer *textbuf;
1942 GtkTextIter iter;
1943 GSList *msginfo;
1944 gchar *msgfile;
1945 gboolean single_mail = TRUE;
1947 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1949 if (g_slist_length(msginfo_list) > 1)
1950 single_mail = FALSE;
1952 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1953 if (((MsgInfo *)msginfo->data)->folder == NULL)
1954 return NULL;
1956 /* guess account from first selected message */
1957 if (!account &&
1958 !(account = compose_find_account(msginfo_list->data)))
1959 account = cur_account;
1961 cm_return_val_if_fail(account != NULL, NULL);
1963 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1964 if (msginfo->data) {
1965 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1966 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1970 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1971 g_warning("no msginfo_list");
1972 return NULL;
1975 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1976 compose_apply_folder_privacy_settings(compose, ((MsgInfo *)msginfo_list->data)->folder);
1977 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1978 (account->default_encrypt || account->default_sign))
1979 COMPOSE_PRIVACY_WARNING();
1981 compose->updating = TRUE;
1983 /* override from name according to folder properties */
1984 if (msginfo_list->data) {
1985 MsgInfo *msginfo = msginfo_list->data;
1987 if (msginfo->folder && msginfo->folder->prefs &&
1988 msginfo->folder->prefs->forward_with_format &&
1989 msginfo->folder->prefs->forward_override_from_format &&
1990 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1992 gchar *tmp = NULL;
1993 gchar *buf = NULL;
1995 /* decode \-escape sequences in the internal representation of the quote format */
1996 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1997 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1999 #ifdef USE_ENCHANT
2000 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2001 compose->gtkaspell);
2002 #else
2003 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2004 #endif
2005 quote_fmt_scan_string(tmp);
2006 quote_fmt_parse();
2008 buf = quote_fmt_get_buffer();
2009 if (buf == NULL)
2010 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2011 else
2012 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2013 quote_fmt_reset_vartable();
2014 quote_fmtlex_destroy();
2016 g_free(tmp);
2020 textview = GTK_TEXT_VIEW(compose->text);
2021 textbuf = gtk_text_view_get_buffer(textview);
2022 compose_create_tags(textview, compose);
2024 undo_block(compose->undostruct);
2025 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2026 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2028 if (!is_file_exist(msgfile))
2029 g_warning("%s: file does not exist", msgfile);
2030 else
2031 compose_attach_append(compose, msgfile, msgfile,
2032 "message/rfc822", NULL);
2033 g_free(msgfile);
2036 if (single_mail) {
2037 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2038 if (info->subject && *info->subject) {
2039 gchar *buf, *buf2, *p;
2041 buf = p = g_strdup(info->subject);
2042 p += subject_get_prefix_length(p);
2043 memmove(buf, p, strlen(p) + 1);
2045 buf2 = g_strdup_printf("Fw: %s", buf);
2046 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2048 g_free(buf);
2049 g_free(buf2);
2051 } else {
2052 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2053 _("Fw: multiple emails"));
2056 SIGNAL_BLOCK(textbuf);
2058 if (account->auto_sig)
2059 compose_insert_sig(compose, FALSE);
2061 compose_wrap_all(compose);
2063 SIGNAL_UNBLOCK(textbuf);
2065 gtk_text_buffer_get_start_iter(textbuf, &iter);
2066 gtk_text_buffer_place_cursor(textbuf, &iter);
2068 if (prefs_common.auto_exteditor)
2069 compose_exec_ext_editor(compose);
2071 gtk_widget_grab_focus(compose->header_last->entry);
2072 undo_unblock(compose->undostruct);
2073 compose->modified = FALSE;
2074 compose_set_title(compose);
2076 compose->updating = FALSE;
2077 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2078 SCROLL_TO_CURSOR(compose);
2080 if (compose->deferred_destroy) {
2081 compose_destroy(compose);
2082 return NULL;
2085 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2087 return compose;
2090 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2092 GtkTextIter start = *iter;
2093 GtkTextIter end_iter;
2094 int start_pos = gtk_text_iter_get_offset(&start);
2095 gchar *str = NULL;
2096 if (!compose->account->sig_sep)
2097 return FALSE;
2099 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2100 start_pos+strlen(compose->account->sig_sep));
2102 /* check sig separator */
2103 str = gtk_text_iter_get_text(&start, &end_iter);
2104 if (!strcmp(str, compose->account->sig_sep)) {
2105 gchar *tmp = NULL;
2106 /* check end of line (\n) */
2107 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2108 start_pos+strlen(compose->account->sig_sep));
2109 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2110 start_pos+strlen(compose->account->sig_sep)+1);
2111 tmp = gtk_text_iter_get_text(&start, &end_iter);
2112 if (!strcmp(tmp,"\n")) {
2113 g_free(str);
2114 g_free(tmp);
2115 return TRUE;
2117 g_free(tmp);
2119 g_free(str);
2121 return FALSE;
2124 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2126 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2127 Compose *compose = (Compose *)data;
2128 FolderItem *old_item = NULL;
2129 FolderItem *new_item = NULL;
2130 gchar *old_id, *new_id;
2132 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2133 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2134 return FALSE;
2136 old_item = hookdata->item;
2137 new_item = hookdata->item2;
2139 old_id = folder_item_get_identifier(old_item);
2140 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2142 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2143 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2144 compose->targetinfo->folder = new_item;
2147 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2148 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2149 compose->replyinfo->folder = new_item;
2152 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2153 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2154 compose->fwdinfo->folder = new_item;
2157 g_free(old_id);
2158 g_free(new_id);
2159 return FALSE;
2162 static void compose_colorize_signature(Compose *compose)
2164 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2165 GtkTextIter iter;
2166 GtkTextIter end_iter;
2167 gtk_text_buffer_get_start_iter(buffer, &iter);
2168 while (gtk_text_iter_forward_line(&iter))
2169 if (compose_is_sig_separator(compose, buffer, &iter)) {
2170 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2171 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2175 #define BLOCK_WRAP() { \
2176 prev_autowrap = compose->autowrap; \
2177 buffer = gtk_text_view_get_buffer( \
2178 GTK_TEXT_VIEW(compose->text)); \
2179 compose->autowrap = FALSE; \
2181 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2182 G_CALLBACK(compose_changed_cb), \
2183 compose); \
2184 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2185 G_CALLBACK(text_inserted), \
2186 compose); \
2188 #define UNBLOCK_WRAP() { \
2189 compose->autowrap = prev_autowrap; \
2190 if (compose->autowrap) { \
2191 gint old = compose->draft_timeout_tag; \
2192 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2193 compose_wrap_all(compose); \
2194 compose->draft_timeout_tag = old; \
2197 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2198 G_CALLBACK(compose_changed_cb), \
2199 compose); \
2200 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2201 G_CALLBACK(text_inserted), \
2202 compose); \
2205 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2207 Compose *compose = NULL;
2208 PrefsAccount *account = NULL;
2209 GtkTextView *textview;
2210 GtkTextBuffer *textbuf;
2211 GtkTextMark *mark;
2212 GtkTextIter iter;
2213 FILE *fp;
2214 gboolean use_signing = FALSE;
2215 gboolean use_encryption = FALSE;
2216 gchar *privacy_system = NULL;
2217 int priority = PRIORITY_NORMAL;
2218 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2219 gboolean autowrap = prefs_common.autowrap;
2220 gboolean autoindent = prefs_common.auto_indent;
2221 HeaderEntry *manual_headers = NULL;
2223 cm_return_val_if_fail(msginfo != NULL, NULL);
2224 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2226 if (compose_put_existing_to_front(msginfo)) {
2227 return NULL;
2230 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2231 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2232 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2233 gchar *queueheader_buf = NULL;
2234 gint id, param;
2236 /* Select Account from queue headers */
2237 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2238 "X-Claws-Account-Id:")) {
2239 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2240 account = account_find_from_id(id);
2241 g_free(queueheader_buf);
2243 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2244 "X-Sylpheed-Account-Id:")) {
2245 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2246 account = account_find_from_id(id);
2247 g_free(queueheader_buf);
2249 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2250 "NAID:")) {
2251 id = atoi(&queueheader_buf[strlen("NAID:")]);
2252 account = account_find_from_id(id);
2253 g_free(queueheader_buf);
2255 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2256 "MAID:")) {
2257 id = atoi(&queueheader_buf[strlen("MAID:")]);
2258 account = account_find_from_id(id);
2259 g_free(queueheader_buf);
2261 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2262 "S:")) {
2263 account = account_find_from_address(queueheader_buf, FALSE);
2264 g_free(queueheader_buf);
2266 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2267 "X-Claws-Sign:")) {
2268 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2269 use_signing = param;
2270 g_free(queueheader_buf);
2272 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2273 "X-Sylpheed-Sign:")) {
2274 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2275 use_signing = param;
2276 g_free(queueheader_buf);
2278 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2279 "X-Claws-Encrypt:")) {
2280 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2281 use_encryption = param;
2282 g_free(queueheader_buf);
2284 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2285 "X-Sylpheed-Encrypt:")) {
2286 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2287 use_encryption = param;
2288 g_free(queueheader_buf);
2290 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2291 "X-Claws-Auto-Wrapping:")) {
2292 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2293 autowrap = param;
2294 g_free(queueheader_buf);
2296 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2297 "X-Claws-Auto-Indent:")) {
2298 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2299 autoindent = param;
2300 g_free(queueheader_buf);
2302 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2303 "X-Claws-Privacy-System:")) {
2304 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2305 g_free(queueheader_buf);
2307 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2308 "X-Sylpheed-Privacy-System:")) {
2309 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2310 g_free(queueheader_buf);
2312 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2313 "X-Priority: ")) {
2314 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2315 priority = param;
2316 g_free(queueheader_buf);
2318 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2319 "RMID:")) {
2320 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2321 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2322 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2323 if (orig_item != NULL) {
2324 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2327 if (tokens)
2328 g_strfreev(tokens);
2329 g_free(queueheader_buf);
2331 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2332 "FMID:")) {
2333 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2334 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2335 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2336 if (orig_item != NULL) {
2337 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2340 if (tokens)
2341 g_strfreev(tokens);
2342 g_free(queueheader_buf);
2344 /* Get manual headers */
2345 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2346 "X-Claws-Manual-Headers:")) {
2347 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2348 if (listmh && *listmh != '\0') {
2349 debug_print("Got manual headers: %s\n", listmh);
2350 manual_headers = procheader_entries_from_str(listmh);
2352 if (listmh)
2353 g_free(listmh);
2354 g_free(queueheader_buf);
2356 } else {
2357 account = msginfo->folder->folder->account;
2360 if (!account && prefs_common.reedit_account_autosel) {
2361 gchar *from = NULL;
2362 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2363 extract_address(from);
2364 account = account_find_from_address(from, FALSE);
2366 if (from)
2367 g_free(from);
2369 if (!account) {
2370 account = cur_account;
2372 if (!account) {
2373 g_warning("can't select account");
2374 if (manual_headers)
2375 procheader_entries_free(manual_headers);
2376 return NULL;
2379 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2381 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
2382 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
2383 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2384 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2385 compose->autowrap = autowrap;
2386 compose->replyinfo = replyinfo;
2387 compose->fwdinfo = fwdinfo;
2389 compose->updating = TRUE;
2390 compose->priority = priority;
2392 if (privacy_system != NULL) {
2393 compose->privacy_system = privacy_system;
2394 compose_use_signing(compose, use_signing);
2395 compose_use_encryption(compose, use_encryption);
2396 compose_update_privacy_system_menu_item(compose, FALSE);
2397 } else {
2398 compose_activate_privacy_system(compose, account, FALSE);
2400 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2401 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
2402 (account->default_encrypt || account->default_sign))
2403 COMPOSE_PRIVACY_WARNING();
2405 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2406 compose->targetinfo->tags = g_slist_copy(msginfo->tags);
2408 compose_extract_original_charset(compose);
2410 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2411 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2412 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2413 gchar *queueheader_buf = NULL;
2415 /* Set message save folder */
2416 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2417 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2418 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2419 compose_set_save_to(compose, &queueheader_buf[4]);
2420 g_free(queueheader_buf);
2422 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2423 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2424 if (active) {
2425 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2427 g_free(queueheader_buf);
2431 if (compose_parse_header(compose, msginfo) < 0) {
2432 compose->updating = FALSE;
2433 compose_destroy(compose);
2434 if (manual_headers)
2435 procheader_entries_free(manual_headers);
2436 return NULL;
2438 compose_reedit_set_entry(compose, msginfo);
2440 textview = GTK_TEXT_VIEW(compose->text);
2441 textbuf = gtk_text_view_get_buffer(textview);
2442 compose_create_tags(textview, compose);
2444 mark = gtk_text_buffer_get_insert(textbuf);
2445 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2447 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2448 G_CALLBACK(compose_changed_cb),
2449 compose);
2451 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2452 fp = procmime_get_first_encrypted_text_content(msginfo);
2453 if (fp) {
2454 compose_force_encryption(compose, account, TRUE, NULL);
2456 } else {
2457 fp = procmime_get_first_text_content(msginfo);
2459 if (fp == NULL) {
2460 g_warning("can't get text part");
2463 if (fp != NULL) {
2464 gchar buf[BUFFSIZE];
2465 gboolean prev_autowrap;
2466 GtkTextBuffer *buffer;
2467 BLOCK_WRAP();
2468 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2469 strcrchomp(buf);
2470 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2472 UNBLOCK_WRAP();
2473 claws_fclose(fp);
2476 compose_attach_parts(compose, msginfo);
2478 compose_colorize_signature(compose);
2480 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2481 G_CALLBACK(compose_changed_cb),
2482 compose);
2484 if (manual_headers != NULL) {
2485 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2486 procheader_entries_free(manual_headers);
2487 compose->updating = FALSE;
2488 compose_destroy(compose);
2489 return NULL;
2491 procheader_entries_free(manual_headers);
2494 gtk_widget_grab_focus(compose->text);
2496 if (prefs_common.auto_exteditor) {
2497 compose_exec_ext_editor(compose);
2499 compose->modified = FALSE;
2500 compose_set_title(compose);
2502 compose->updating = FALSE;
2503 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2504 SCROLL_TO_CURSOR(compose);
2506 if (compose->deferred_destroy) {
2507 compose_destroy(compose);
2508 return NULL;
2511 compose->sig_str = account_get_signature_str(compose->account);
2513 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2515 return compose;
2518 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2519 gboolean batch)
2521 Compose *compose;
2522 gchar *filename;
2523 FolderItem *item;
2525 cm_return_val_if_fail(msginfo != NULL, NULL);
2527 if (!account)
2528 account = account_get_reply_account(msginfo,
2529 prefs_common.reply_account_autosel);
2530 cm_return_val_if_fail(account != NULL, NULL);
2532 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2534 compose->updating = TRUE;
2536 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2537 compose->replyinfo = NULL;
2538 compose->fwdinfo = NULL;
2540 compose_show_first_last_header(compose, TRUE);
2542 gtk_widget_grab_focus(compose->header_last->entry);
2544 filename = procmsg_get_message_file(msginfo);
2546 if (filename == NULL) {
2547 compose->updating = FALSE;
2548 compose_destroy(compose);
2550 return NULL;
2553 compose->redirect_filename = filename;
2555 /* Set save folder */
2556 item = msginfo->folder;
2557 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2558 gchar *folderidentifier;
2560 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2561 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2562 folderidentifier = folder_item_get_identifier(item);
2563 compose_set_save_to(compose, folderidentifier);
2564 g_free(folderidentifier);
2567 compose_attach_parts(compose, msginfo);
2569 if (msginfo->subject)
2570 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2571 msginfo->subject);
2572 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2574 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2575 _("The body of the \"Redirect\" template has an error at line %d."));
2576 quote_fmt_reset_vartable();
2577 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2579 compose_colorize_signature(compose);
2582 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2583 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2584 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2586 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2587 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2588 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2589 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2590 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2591 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2592 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2593 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2594 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2596 if (compose->toolbar->draft_btn)
2597 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2598 if (compose->toolbar->insert_btn)
2599 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2600 if (compose->toolbar->attach_btn)
2601 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2602 if (compose->toolbar->sig_btn)
2603 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2604 if (compose->toolbar->exteditor_btn)
2605 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2606 if (compose->toolbar->linewrap_current_btn)
2607 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2608 if (compose->toolbar->linewrap_all_btn)
2609 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2610 if (compose->toolbar->privacy_sign_btn)
2611 gtk_widget_set_sensitive(compose->toolbar->privacy_sign_btn, FALSE);
2612 if (compose->toolbar->privacy_encrypt_btn)
2613 gtk_widget_set_sensitive(compose->toolbar->privacy_encrypt_btn, FALSE);
2615 compose->modified = FALSE;
2616 compose_set_title(compose);
2617 compose->updating = FALSE;
2618 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2619 SCROLL_TO_CURSOR(compose);
2621 if (compose->deferred_destroy) {
2622 compose_destroy(compose);
2623 return NULL;
2626 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2628 return compose;
2631 const GList *compose_get_compose_list(void)
2633 return compose_list;
2636 void compose_entry_append(Compose *compose, const gchar *address,
2637 ComposeEntryType type, ComposePrefType pref_type)
2639 const gchar *header;
2640 gchar *cur, *begin;
2641 gboolean in_quote = FALSE;
2642 if (!address || *address == '\0') return;
2644 switch (type) {
2645 case COMPOSE_CC:
2646 header = N_("Cc:");
2647 break;
2648 case COMPOSE_BCC:
2649 header = N_("Bcc:");
2650 break;
2651 case COMPOSE_REPLYTO:
2652 header = N_("Reply-To:");
2653 break;
2654 case COMPOSE_NEWSGROUPS:
2655 header = N_("Newsgroups:");
2656 break;
2657 case COMPOSE_FOLLOWUPTO:
2658 header = N_( "Followup-To:");
2659 break;
2660 case COMPOSE_INREPLYTO:
2661 header = N_( "In-Reply-To:");
2662 break;
2663 case COMPOSE_TO:
2664 default:
2665 header = N_("To:");
2666 break;
2668 header = prefs_common_translated_header_name(header);
2670 cur = begin = (gchar *)address;
2672 /* we separate the line by commas, but not if we're inside a quoted
2673 * string */
2674 while (*cur != '\0') {
2675 if (*cur == '"')
2676 in_quote = !in_quote;
2677 if (*cur == ',' && !in_quote) {
2678 gchar *tmp = g_strdup(begin);
2679 gchar *o_tmp = tmp;
2680 tmp[cur-begin]='\0';
2681 cur++;
2682 begin = cur;
2683 while (*tmp == ' ' || *tmp == '\t')
2684 tmp++;
2685 compose_add_header_entry(compose, header, tmp, pref_type);
2686 compose_entry_indicate(compose, tmp);
2687 g_free(o_tmp);
2688 continue;
2690 cur++;
2692 if (begin < cur) {
2693 gchar *tmp = g_strdup(begin);
2694 gchar *o_tmp = tmp;
2695 tmp[cur-begin]='\0';
2696 while (*tmp == ' ' || *tmp == '\t')
2697 tmp++;
2698 compose_add_header_entry(compose, header, tmp, pref_type);
2699 compose_entry_indicate(compose, tmp);
2700 g_free(o_tmp);
2704 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2706 GSList *h_list;
2707 GtkEntry *entry;
2708 GdkColor color;
2710 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2711 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2712 if (gtk_entry_get_text(entry) &&
2713 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2714 /* Modify background color */
2715 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_bgcolor, color);
2716 gtk_widget_modify_base(
2717 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2718 GTK_STATE_NORMAL, &color);
2720 /* Modify foreground color */
2721 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_color, color);
2722 gtk_widget_modify_text(
2723 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2724 GTK_STATE_NORMAL, &color);
2729 void compose_toolbar_cb(gint action, gpointer data)
2731 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2732 Compose *compose = (Compose*)toolbar_item->parent;
2734 cm_return_if_fail(compose != NULL);
2736 switch(action) {
2737 case A_SEND:
2738 compose_send_cb(NULL, compose);
2739 break;
2740 case A_SEND_LATER:
2741 compose_send_later_cb(NULL, compose);
2742 break;
2743 case A_DRAFT:
2744 compose_draft(compose, COMPOSE_QUIT_EDITING);
2745 break;
2746 case A_INSERT:
2747 compose_insert_file_cb(NULL, compose);
2748 break;
2749 case A_ATTACH:
2750 compose_attach_cb(NULL, compose);
2751 break;
2752 case A_SIG:
2753 compose_insert_sig(compose, FALSE);
2754 break;
2755 case A_REP_SIG:
2756 compose_insert_sig(compose, TRUE);
2757 break;
2758 case A_EXTEDITOR:
2759 compose_ext_editor_cb(NULL, compose);
2760 break;
2761 case A_LINEWRAP_CURRENT:
2762 compose_beautify_paragraph(compose, NULL, TRUE);
2763 break;
2764 case A_LINEWRAP_ALL:
2765 compose_wrap_all_full(compose, TRUE);
2766 break;
2767 case A_ADDRBOOK:
2768 compose_address_cb(NULL, compose);
2769 break;
2770 #ifdef USE_ENCHANT
2771 case A_CHECK_SPELLING:
2772 compose_check_all(NULL, compose);
2773 break;
2774 #endif
2775 case A_PRIVACY_SIGN:
2776 break;
2777 case A_PRIVACY_ENCRYPT:
2778 break;
2779 default:
2780 break;
2784 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2786 gchar *to = NULL;
2787 gchar *cc = NULL;
2788 gchar *bcc = NULL;
2789 gchar *subject = NULL;
2790 gchar *body = NULL;
2791 gchar *temp = NULL;
2792 gsize len = 0;
2793 gchar **attach = NULL;
2794 gchar *inreplyto = NULL;
2795 MailField mfield = NO_FIELD_PRESENT;
2797 /* get mailto parts but skip from */
2798 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2800 if (to) {
2801 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2802 mfield = TO_FIELD_PRESENT;
2804 if (cc)
2805 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2806 if (bcc)
2807 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2808 if (subject) {
2809 if (!g_utf8_validate (subject, -1, NULL)) {
2810 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2811 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2812 g_free(temp);
2813 } else {
2814 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2816 mfield = SUBJECT_FIELD_PRESENT;
2818 if (body) {
2819 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2820 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2821 GtkTextMark *mark;
2822 GtkTextIter iter;
2823 gboolean prev_autowrap = compose->autowrap;
2825 compose->autowrap = FALSE;
2827 mark = gtk_text_buffer_get_insert(buffer);
2828 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2830 if (!g_utf8_validate (body, -1, NULL)) {
2831 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2832 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2833 g_free(temp);
2834 } else {
2835 gtk_text_buffer_insert(buffer, &iter, body, -1);
2837 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2839 compose->autowrap = prev_autowrap;
2840 if (compose->autowrap)
2841 compose_wrap_all(compose);
2842 mfield = BODY_FIELD_PRESENT;
2845 if (attach) {
2846 gint i = 0, att = 0;
2847 gchar *warn_files = NULL;
2848 while (attach[i] != NULL) {
2849 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2850 if (utf8_filename) {
2851 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2852 gchar *tmp = g_strdup_printf("%s%s\n",
2853 warn_files?warn_files:"",
2854 utf8_filename);
2855 g_free(warn_files);
2856 warn_files = tmp;
2857 att++;
2859 g_free(utf8_filename);
2860 } else {
2861 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2863 i++;
2865 if (warn_files) {
2866 alertpanel_notice(ngettext(
2867 "The following file has been attached: \n%s",
2868 "The following files have been attached: \n%s", att), warn_files);
2869 g_free(warn_files);
2872 if (inreplyto)
2873 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2875 g_free(to);
2876 g_free(cc);
2877 g_free(bcc);
2878 g_free(subject);
2879 g_free(body);
2880 g_strfreev(attach);
2881 g_free(inreplyto);
2883 return mfield;
2886 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2888 static HeaderEntry hentry[] = {
2889 {"Reply-To:", NULL, TRUE },
2890 {"Cc:", NULL, TRUE },
2891 {"References:", NULL, FALSE },
2892 {"Bcc:", NULL, TRUE },
2893 {"Newsgroups:", NULL, TRUE },
2894 {"Followup-To:", NULL, TRUE },
2895 {"List-Post:", NULL, FALSE },
2896 {"X-Priority:", NULL, FALSE },
2897 {NULL, NULL, FALSE }
2900 enum
2902 H_REPLY_TO = 0,
2903 H_CC = 1,
2904 H_REFERENCES = 2,
2905 H_BCC = 3,
2906 H_NEWSGROUPS = 4,
2907 H_FOLLOWUP_TO = 5,
2908 H_LIST_POST = 6,
2909 H_X_PRIORITY = 7
2912 FILE *fp;
2914 cm_return_val_if_fail(msginfo != NULL, -1);
2916 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
2917 procheader_get_header_fields(fp, hentry);
2918 claws_fclose(fp);
2920 if (hentry[H_REPLY_TO].body != NULL) {
2921 if (hentry[H_REPLY_TO].body[0] != '\0') {
2922 compose->replyto =
2923 conv_unmime_header(hentry[H_REPLY_TO].body,
2924 NULL, TRUE);
2926 g_free(hentry[H_REPLY_TO].body);
2927 hentry[H_REPLY_TO].body = NULL;
2929 if (hentry[H_CC].body != NULL) {
2930 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2931 g_free(hentry[H_CC].body);
2932 hentry[H_CC].body = NULL;
2934 if (hentry[H_REFERENCES].body != NULL) {
2935 if (compose->mode == COMPOSE_REEDIT)
2936 compose->references = hentry[H_REFERENCES].body;
2937 else {
2938 compose->references = compose_parse_references
2939 (hentry[H_REFERENCES].body, msginfo->msgid);
2940 g_free(hentry[H_REFERENCES].body);
2942 hentry[H_REFERENCES].body = NULL;
2944 if (hentry[H_BCC].body != NULL) {
2945 if (compose->mode == COMPOSE_REEDIT)
2946 compose->bcc =
2947 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2948 g_free(hentry[H_BCC].body);
2949 hentry[H_BCC].body = NULL;
2951 if (hentry[H_NEWSGROUPS].body != NULL) {
2952 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2953 hentry[H_NEWSGROUPS].body = NULL;
2955 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2956 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2957 compose->followup_to =
2958 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2959 NULL, TRUE);
2961 g_free(hentry[H_FOLLOWUP_TO].body);
2962 hentry[H_FOLLOWUP_TO].body = NULL;
2964 if (hentry[H_LIST_POST].body != NULL) {
2965 gchar *to = NULL, *start = NULL;
2967 extract_address(hentry[H_LIST_POST].body);
2968 if (hentry[H_LIST_POST].body[0] != '\0') {
2969 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2971 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2972 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2974 if (to) {
2975 g_free(compose->ml_post);
2976 compose->ml_post = to;
2979 g_free(hentry[H_LIST_POST].body);
2980 hentry[H_LIST_POST].body = NULL;
2983 /* CLAWS - X-Priority */
2984 if (compose->mode == COMPOSE_REEDIT)
2985 if (hentry[H_X_PRIORITY].body != NULL) {
2986 gint priority;
2988 priority = atoi(hentry[H_X_PRIORITY].body);
2989 g_free(hentry[H_X_PRIORITY].body);
2991 hentry[H_X_PRIORITY].body = NULL;
2993 if (priority < PRIORITY_HIGHEST ||
2994 priority > PRIORITY_LOWEST)
2995 priority = PRIORITY_NORMAL;
2997 compose->priority = priority;
3000 if (compose->mode == COMPOSE_REEDIT) {
3001 if (msginfo->inreplyto && *msginfo->inreplyto)
3002 compose->inreplyto = g_strdup(msginfo->inreplyto);
3004 if (msginfo->msgid && *msginfo->msgid &&
3005 compose->folder != NULL &&
3006 compose->folder->stype == F_DRAFT)
3007 compose->msgid = g_strdup(msginfo->msgid);
3008 } else {
3009 if (msginfo->msgid && *msginfo->msgid)
3010 compose->inreplyto = g_strdup(msginfo->msgid);
3012 if (!compose->references) {
3013 if (msginfo->msgid && *msginfo->msgid) {
3014 if (msginfo->inreplyto && *msginfo->inreplyto)
3015 compose->references =
3016 g_strdup_printf("<%s>\n\t<%s>",
3017 msginfo->inreplyto,
3018 msginfo->msgid);
3019 else
3020 compose->references =
3021 g_strconcat("<", msginfo->msgid, ">",
3022 NULL);
3023 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3024 compose->references =
3025 g_strconcat("<", msginfo->inreplyto, ">",
3026 NULL);
3031 return 0;
3034 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3036 FILE *fp;
3037 HeaderEntry *he;
3039 cm_return_val_if_fail(msginfo != NULL, -1);
3041 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
3042 procheader_get_header_fields(fp, entries);
3043 claws_fclose(fp);
3045 he = entries;
3046 while (he != NULL && he->name != NULL) {
3047 GtkTreeIter iter;
3048 GtkListStore *model = NULL;
3050 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3051 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3052 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3053 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3054 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3055 ++he;
3058 return 0;
3061 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3063 GSList *ref_id_list, *cur;
3064 GString *new_ref;
3065 gchar *new_ref_str;
3067 ref_id_list = references_list_append(NULL, ref);
3068 if (!ref_id_list) return NULL;
3069 if (msgid && *msgid)
3070 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3072 for (;;) {
3073 gint len = 0;
3075 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3076 /* "<" + Message-ID + ">" + CR+LF+TAB */
3077 len += strlen((gchar *)cur->data) + 5;
3079 if (len > MAX_REFERENCES_LEN) {
3080 /* remove second message-ID */
3081 if (ref_id_list && ref_id_list->next &&
3082 ref_id_list->next->next) {
3083 g_free(ref_id_list->next->data);
3084 ref_id_list = g_slist_remove
3085 (ref_id_list, ref_id_list->next->data);
3086 } else {
3087 slist_free_strings_full(ref_id_list);
3088 return NULL;
3090 } else
3091 break;
3094 new_ref = g_string_new("");
3095 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3096 if (new_ref->len > 0)
3097 g_string_append(new_ref, "\n\t");
3098 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3101 slist_free_strings_full(ref_id_list);
3103 new_ref_str = new_ref->str;
3104 g_string_free(new_ref, FALSE);
3106 return new_ref_str;
3109 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3110 const gchar *fmt, const gchar *qmark,
3111 const gchar *body, gboolean rewrap,
3112 gboolean need_unescape,
3113 const gchar *err_msg)
3115 MsgInfo* dummyinfo = NULL;
3116 gchar *quote_str = NULL;
3117 gchar *buf;
3118 gboolean prev_autowrap;
3119 const gchar *trimmed_body = body;
3120 gint cursor_pos = -1;
3121 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3122 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3123 GtkTextIter iter;
3124 GtkTextMark *mark;
3127 SIGNAL_BLOCK(buffer);
3129 if (!msginfo) {
3130 dummyinfo = compose_msginfo_new_from_compose(compose);
3131 msginfo = dummyinfo;
3134 if (qmark != NULL) {
3135 #ifdef USE_ENCHANT
3136 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3137 compose->gtkaspell);
3138 #else
3139 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3140 #endif
3141 quote_fmt_scan_string(qmark);
3142 quote_fmt_parse();
3144 buf = quote_fmt_get_buffer();
3146 if (buf == NULL)
3147 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3148 else
3149 Xstrdup_a(quote_str, buf, goto error)
3152 if (fmt && *fmt != '\0') {
3154 if (trimmed_body)
3155 while (*trimmed_body == '\n')
3156 trimmed_body++;
3158 #ifdef USE_ENCHANT
3159 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3160 compose->gtkaspell);
3161 #else
3162 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3163 #endif
3164 if (need_unescape) {
3165 gchar *tmp = NULL;
3167 /* decode \-escape sequences in the internal representation of the quote format */
3168 tmp = g_malloc(strlen(fmt)+1);
3169 pref_get_unescaped_pref(tmp, fmt);
3170 quote_fmt_scan_string(tmp);
3171 quote_fmt_parse();
3172 g_free(tmp);
3173 } else {
3174 quote_fmt_scan_string(fmt);
3175 quote_fmt_parse();
3178 buf = quote_fmt_get_buffer();
3180 if (buf == NULL) {
3181 gint line = quote_fmt_get_line();
3182 alertpanel_error(err_msg, line);
3184 goto error;
3187 } else
3188 buf = "";
3190 prev_autowrap = compose->autowrap;
3191 compose->autowrap = FALSE;
3193 mark = gtk_text_buffer_get_insert(buffer);
3194 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3195 if (g_utf8_validate(buf, -1, NULL)) {
3196 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3197 } else {
3198 gchar *tmpout = NULL;
3199 tmpout = conv_codeset_strdup
3200 (buf, conv_get_locale_charset_str_no_utf8(),
3201 CS_INTERNAL);
3202 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3203 g_free(tmpout);
3204 tmpout = g_malloc(strlen(buf)*2+1);
3205 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3207 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3208 g_free(tmpout);
3211 cursor_pos = quote_fmt_get_cursor_pos();
3212 if (cursor_pos == -1)
3213 cursor_pos = gtk_text_iter_get_offset(&iter);
3214 compose->set_cursor_pos = cursor_pos;
3216 gtk_text_buffer_get_start_iter(buffer, &iter);
3217 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3218 gtk_text_buffer_place_cursor(buffer, &iter);
3220 compose->autowrap = prev_autowrap;
3221 if (compose->autowrap && rewrap)
3222 compose_wrap_all(compose);
3224 goto ok;
3226 error:
3227 buf = NULL;
3229 SIGNAL_UNBLOCK(buffer);
3231 procmsg_msginfo_free( &dummyinfo );
3233 return buf;
3236 /* if ml_post is of type addr@host and from is of type
3237 * addr-anything@host, return TRUE
3239 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3241 gchar *left_ml = NULL;
3242 gchar *right_ml = NULL;
3243 gchar *left_from = NULL;
3244 gchar *right_from = NULL;
3245 gboolean result = FALSE;
3247 if (!ml_post || !from)
3248 return FALSE;
3250 left_ml = g_strdup(ml_post);
3251 if (strstr(left_ml, "@")) {
3252 right_ml = strstr(left_ml, "@")+1;
3253 *(strstr(left_ml, "@")) = '\0';
3256 left_from = g_strdup(from);
3257 if (strstr(left_from, "@")) {
3258 right_from = strstr(left_from, "@")+1;
3259 *(strstr(left_from, "@")) = '\0';
3262 if (right_ml && right_from
3263 && !strncmp(left_from, left_ml, strlen(left_ml))
3264 && !strcmp(right_from, right_ml)) {
3265 result = TRUE;
3267 g_free(left_ml);
3268 g_free(left_from);
3270 return result;
3273 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3274 gboolean respect_default_to)
3276 if (!compose)
3277 return;
3278 if (!folder || !folder->prefs)
3279 return;
3281 if (folder->prefs->enable_default_from) {
3282 gtk_entry_set_text(GTK_ENTRY(compose->from_name), folder->prefs->default_from);
3283 compose_entry_indicate(compose, folder->prefs->default_from);
3285 if (respect_default_to && folder->prefs->enable_default_to) {
3286 compose_entry_append(compose, folder->prefs->default_to,
3287 COMPOSE_TO, PREF_FOLDER);
3288 compose_entry_indicate(compose, folder->prefs->default_to);
3290 if (folder->prefs->enable_default_cc) {
3291 compose_entry_append(compose, folder->prefs->default_cc,
3292 COMPOSE_CC, PREF_FOLDER);
3293 compose_entry_indicate(compose, folder->prefs->default_cc);
3295 if (folder->prefs->enable_default_bcc) {
3296 compose_entry_append(compose, folder->prefs->default_bcc,
3297 COMPOSE_BCC, PREF_FOLDER);
3298 compose_entry_indicate(compose, folder->prefs->default_bcc);
3300 if (folder->prefs->enable_default_replyto) {
3301 compose_entry_append(compose, folder->prefs->default_replyto,
3302 COMPOSE_REPLYTO, PREF_FOLDER);
3303 compose_entry_indicate(compose, folder->prefs->default_replyto);
3307 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3309 gchar *buf, *buf2;
3310 gchar *p;
3312 if (!compose || !msginfo)
3313 return;
3315 if (msginfo->subject && *msginfo->subject) {
3316 buf = p = g_strdup(msginfo->subject);
3317 p += subject_get_prefix_length(p);
3318 memmove(buf, p, strlen(p) + 1);
3320 buf2 = g_strdup_printf("Re: %s", buf);
3321 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3323 g_free(buf2);
3324 g_free(buf);
3325 } else
3326 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3329 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3330 gboolean to_all, gboolean to_ml,
3331 gboolean to_sender,
3332 gboolean followup_and_reply_to)
3334 GSList *cc_list = NULL;
3335 GSList *cur;
3336 gchar *from = NULL;
3337 gchar *replyto = NULL;
3338 gchar *ac_email = NULL;
3340 gboolean reply_to_ml = FALSE;
3341 gboolean default_reply_to = FALSE;
3343 cm_return_if_fail(compose->account != NULL);
3344 cm_return_if_fail(msginfo != NULL);
3346 reply_to_ml = to_ml && compose->ml_post;
3348 default_reply_to = msginfo->folder &&
3349 msginfo->folder->prefs->enable_default_reply_to;
3351 if (compose->account->protocol != A_NNTP) {
3352 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3354 if (reply_to_ml && !default_reply_to) {
3356 gboolean is_subscr = is_subscription(compose->ml_post,
3357 msginfo->from);
3358 if (!is_subscr) {
3359 /* normal answer to ml post with a reply-to */
3360 compose_entry_append(compose,
3361 compose->ml_post,
3362 COMPOSE_TO, PREF_ML);
3363 if (compose->replyto)
3364 compose_entry_append(compose,
3365 compose->replyto,
3366 COMPOSE_CC, PREF_ML);
3367 } else {
3368 /* answer to subscription confirmation */
3369 if (compose->replyto)
3370 compose_entry_append(compose,
3371 compose->replyto,
3372 COMPOSE_TO, PREF_ML);
3373 else if (msginfo->from)
3374 compose_entry_append(compose,
3375 msginfo->from,
3376 COMPOSE_TO, PREF_ML);
3379 else if (!(to_all || to_sender) && default_reply_to) {
3380 compose_entry_append(compose,
3381 msginfo->folder->prefs->default_reply_to,
3382 COMPOSE_TO, PREF_FOLDER);
3383 compose_entry_indicate(compose,
3384 msginfo->folder->prefs->default_reply_to);
3385 } else {
3386 gchar *tmp1 = NULL;
3387 if (!msginfo->from)
3388 return;
3389 if (to_sender)
3390 compose_entry_append(compose, msginfo->from,
3391 COMPOSE_TO, PREF_NONE);
3392 else if (to_all) {
3393 Xstrdup_a(tmp1, msginfo->from, return);
3394 extract_address(tmp1);
3395 compose_entry_append(compose,
3396 (!account_find_from_address(tmp1, FALSE))
3397 ? msginfo->from :
3398 msginfo->to,
3399 COMPOSE_TO, PREF_NONE);
3400 if (compose->replyto)
3401 compose_entry_append(compose,
3402 compose->replyto,
3403 COMPOSE_CC, PREF_NONE);
3404 } else {
3405 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3406 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3407 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3408 if (compose->replyto) {
3409 compose_entry_append(compose,
3410 compose->replyto,
3411 COMPOSE_TO, PREF_NONE);
3412 } else {
3413 compose_entry_append(compose,
3414 msginfo->from ? msginfo->from : "",
3415 COMPOSE_TO, PREF_NONE);
3417 } else {
3418 /* replying to own mail, use original recp */
3419 compose_entry_append(compose,
3420 msginfo->to ? msginfo->to : "",
3421 COMPOSE_TO, PREF_NONE);
3422 compose_entry_append(compose,
3423 msginfo->cc ? msginfo->cc : "",
3424 COMPOSE_CC, PREF_NONE);
3428 } else {
3429 if (to_sender || (compose->followup_to &&
3430 !strncmp(compose->followup_to, "poster", 6)))
3431 compose_entry_append
3432 (compose,
3433 (compose->replyto ? compose->replyto :
3434 msginfo->from ? msginfo->from : ""),
3435 COMPOSE_TO, PREF_NONE);
3437 else if (followup_and_reply_to || to_all) {
3438 compose_entry_append
3439 (compose,
3440 (compose->replyto ? compose->replyto :
3441 msginfo->from ? msginfo->from : ""),
3442 COMPOSE_TO, PREF_NONE);
3444 compose_entry_append
3445 (compose,
3446 compose->followup_to ? compose->followup_to :
3447 compose->newsgroups ? compose->newsgroups : "",
3448 COMPOSE_NEWSGROUPS, PREF_NONE);
3450 compose_entry_append
3451 (compose,
3452 msginfo->cc ? msginfo->cc : "",
3453 COMPOSE_CC, PREF_NONE);
3455 else
3456 compose_entry_append
3457 (compose,
3458 compose->followup_to ? compose->followup_to :
3459 compose->newsgroups ? compose->newsgroups : "",
3460 COMPOSE_NEWSGROUPS, PREF_NONE);
3462 compose_reply_set_subject(compose, msginfo);
3464 if (to_ml && compose->ml_post) return;
3465 if (!to_all || compose->account->protocol == A_NNTP) return;
3467 if (compose->replyto) {
3468 Xstrdup_a(replyto, compose->replyto, return);
3469 extract_address(replyto);
3471 if (msginfo->from) {
3472 Xstrdup_a(from, msginfo->from, return);
3473 extract_address(from);
3476 if (replyto && from)
3477 cc_list = address_list_append_with_comments(cc_list, from);
3478 if (to_all && msginfo->folder &&
3479 msginfo->folder->prefs->enable_default_reply_to)
3480 cc_list = address_list_append_with_comments(cc_list,
3481 msginfo->folder->prefs->default_reply_to);
3482 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3483 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3485 ac_email = g_utf8_strdown(compose->account->address, -1);
3487 if (cc_list) {
3488 for (cur = cc_list; cur != NULL; cur = cur->next) {
3489 gchar *addr = g_utf8_strdown(cur->data, -1);
3490 extract_address(addr);
3492 if (strcmp(ac_email, addr))
3493 compose_entry_append(compose, (gchar *)cur->data,
3494 COMPOSE_CC, PREF_NONE);
3495 else
3496 debug_print("Cc address same as compose account's, ignoring\n");
3498 g_free(addr);
3501 slist_free_strings_full(cc_list);
3504 g_free(ac_email);
3507 #define SET_ENTRY(entry, str) \
3509 if (str && *str) \
3510 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3513 #define SET_ADDRESS(type, str) \
3515 if (str && *str) \
3516 compose_entry_append(compose, str, type, PREF_NONE); \
3519 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3521 cm_return_if_fail(msginfo != NULL);
3523 SET_ENTRY(subject_entry, msginfo->subject);
3524 SET_ENTRY(from_name, msginfo->from);
3525 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3526 SET_ADDRESS(COMPOSE_CC, compose->cc);
3527 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3528 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3529 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3530 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3532 compose_update_priority_menu_item(compose);
3533 compose_update_privacy_system_menu_item(compose, FALSE);
3534 compose_show_first_last_header(compose, TRUE);
3537 #undef SET_ENTRY
3538 #undef SET_ADDRESS
3540 static void compose_insert_sig(Compose *compose, gboolean replace)
3542 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3543 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3544 GtkTextMark *mark;
3545 GtkTextIter iter, iter_end;
3546 gint cur_pos, ins_pos;
3547 gboolean prev_autowrap;
3548 gboolean found = FALSE;
3549 gboolean exists = FALSE;
3551 cm_return_if_fail(compose->account != NULL);
3553 BLOCK_WRAP();
3555 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3556 G_CALLBACK(compose_changed_cb),
3557 compose);
3559 mark = gtk_text_buffer_get_insert(buffer);
3560 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3561 cur_pos = gtk_text_iter_get_offset (&iter);
3562 ins_pos = cur_pos;
3564 gtk_text_buffer_get_end_iter(buffer, &iter);
3566 exists = (compose->sig_str != NULL);
3568 if (replace) {
3569 GtkTextIter first_iter, start_iter, end_iter;
3571 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3573 if (!exists || compose->sig_str[0] == '\0')
3574 found = FALSE;
3575 else
3576 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3577 compose->signature_tag);
3579 if (found) {
3580 /* include previous \n\n */
3581 gtk_text_iter_backward_chars(&first_iter, 1);
3582 start_iter = first_iter;
3583 end_iter = first_iter;
3584 /* skip re-start */
3585 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3586 compose->signature_tag);
3587 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3588 compose->signature_tag);
3589 if (found) {
3590 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3591 iter = start_iter;
3596 g_free(compose->sig_str);
3597 compose->sig_str = account_get_signature_str(compose->account);
3599 cur_pos = gtk_text_iter_get_offset(&iter);
3601 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3602 g_free(compose->sig_str);
3603 compose->sig_str = NULL;
3604 } else {
3605 if (compose->sig_inserted == FALSE)
3606 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3607 compose->sig_inserted = TRUE;
3609 cur_pos = gtk_text_iter_get_offset(&iter);
3610 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3611 /* remove \n\n */
3612 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3613 gtk_text_iter_forward_chars(&iter, 1);
3614 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3615 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3617 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3618 cur_pos = gtk_text_buffer_get_char_count (buffer);
3621 /* put the cursor where it should be
3622 * either where the quote_fmt says, either where it was */
3623 if (compose->set_cursor_pos < 0)
3624 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3625 else
3626 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3627 compose->set_cursor_pos);
3629 compose->set_cursor_pos = -1;
3630 gtk_text_buffer_place_cursor(buffer, &iter);
3631 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3632 G_CALLBACK(compose_changed_cb),
3633 compose);
3635 UNBLOCK_WRAP();
3638 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3640 GtkTextView *text;
3641 GtkTextBuffer *buffer;
3642 GtkTextMark *mark;
3643 GtkTextIter iter;
3644 const gchar *cur_encoding;
3645 gchar buf[BUFFSIZE];
3646 gint len;
3647 FILE *fp;
3648 gboolean prev_autowrap;
3649 #ifdef G_OS_WIN32
3650 GFile *f;
3651 GFileInfo *fi;
3652 GError *error = NULL;
3653 #else
3654 GStatBuf file_stat;
3655 #endif
3656 int ret;
3657 goffset size;
3658 GString *file_contents = NULL;
3659 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3661 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3663 /* get the size of the file we are about to insert */
3664 #ifdef G_OS_WIN32
3665 f = g_file_new_for_path(file);
3666 fi = g_file_query_info(f, "standard::size",
3667 G_FILE_QUERY_INFO_NONE, NULL, &error);
3668 ret = 0;
3669 if (error != NULL) {
3670 g_warning(error->message);
3671 ret = 1;
3672 g_error_free(error);
3673 g_object_unref(f);
3675 #else
3676 ret = g_stat(file, &file_stat);
3677 #endif
3678 if (ret != 0) {
3679 gchar *shortfile = g_path_get_basename(file);
3680 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3681 g_free(shortfile);
3682 return COMPOSE_INSERT_NO_FILE;
3683 } else if (prefs_common.warn_large_insert == TRUE) {
3684 #ifdef G_OS_WIN32
3685 size = g_file_info_get_size(fi);
3686 g_object_unref(fi);
3687 g_object_unref(f);
3688 #else
3689 size = file_stat.st_size;
3690 #endif
3692 /* ask user for confirmation if the file is large */
3693 if (prefs_common.warn_large_insert_size < 0 ||
3694 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3695 AlertValue aval;
3696 gchar *msg;
3698 msg = g_strdup_printf(_("You are about to insert a file of %s "
3699 "in the message body. Are you sure you want to do that?"),
3700 to_human_readable(size));
3701 aval = alertpanel_full(_("Are you sure?"), msg, NULL, _("_Cancel"),
3702 NULL, _("_Insert"), NULL, NULL, ALERTFOCUS_SECOND, TRUE,
3703 NULL, ALERT_QUESTION);
3704 g_free(msg);
3706 /* do we ask for confirmation next time? */
3707 if (aval & G_ALERTDISABLE) {
3708 /* no confirmation next time, disable feature in preferences */
3709 aval &= ~G_ALERTDISABLE;
3710 prefs_common.warn_large_insert = FALSE;
3713 /* abort file insertion if user canceled action */
3714 if (aval != G_ALERTALTERNATE) {
3715 return COMPOSE_INSERT_NO_FILE;
3721 if ((fp = claws_fopen(file, "rb")) == NULL) {
3722 FILE_OP_ERROR(file, "claws_fopen");
3723 return COMPOSE_INSERT_READ_ERROR;
3726 prev_autowrap = compose->autowrap;
3727 compose->autowrap = FALSE;
3729 text = GTK_TEXT_VIEW(compose->text);
3730 buffer = gtk_text_view_get_buffer(text);
3731 mark = gtk_text_buffer_get_insert(buffer);
3732 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3734 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3735 G_CALLBACK(text_inserted),
3736 compose);
3738 cur_encoding = conv_get_locale_charset_str_no_utf8();
3740 file_contents = g_string_new("");
3741 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3742 gchar *str;
3744 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3745 str = g_strdup(buf);
3746 else {
3747 codeconv_set_strict(TRUE);
3748 str = conv_codeset_strdup
3749 (buf, cur_encoding, CS_INTERNAL);
3750 codeconv_set_strict(FALSE);
3752 if (!str) {
3753 result = COMPOSE_INSERT_INVALID_CHARACTER;
3754 break;
3757 if (!str) continue;
3759 /* strip <CR> if DOS/Windows file,
3760 replace <CR> with <LF> if Macintosh file. */
3761 strcrchomp(str);
3762 len = strlen(str);
3763 if (len > 0 && str[len - 1] != '\n') {
3764 while (--len >= 0)
3765 if (str[len] == '\r') str[len] = '\n';
3768 file_contents = g_string_append(file_contents, str);
3769 g_free(str);
3772 if (result == COMPOSE_INSERT_SUCCESS) {
3773 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3775 compose_changed_cb(NULL, compose);
3776 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3777 G_CALLBACK(text_inserted),
3778 compose);
3779 compose->autowrap = prev_autowrap;
3780 if (compose->autowrap)
3781 compose_wrap_all(compose);
3784 g_string_free(file_contents, TRUE);
3785 claws_fclose(fp);
3787 return result;
3790 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3791 const gchar *filename,
3792 const gchar *content_type,
3793 const gchar *charset)
3795 AttachInfo *ainfo;
3796 GtkTreeIter iter;
3797 FILE *fp;
3798 off_t size;
3799 GAuto *auto_ainfo;
3800 gchar *size_text;
3801 GtkListStore *store;
3802 gchar *name;
3803 gboolean has_binary = FALSE;
3805 if (!is_file_exist(file)) {
3806 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3807 gboolean result = FALSE;
3808 if (file_from_uri && is_file_exist(file_from_uri)) {
3809 result = compose_attach_append(
3810 compose, file_from_uri,
3811 filename, content_type,
3812 charset);
3814 g_free(file_from_uri);
3815 if (result)
3816 return TRUE;
3817 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3818 return FALSE;
3820 if ((size = get_file_size(file)) < 0) {
3821 alertpanel_error("Can't get file size of %s\n", filename);
3822 return FALSE;
3825 /* In batch mode, we allow 0-length files to be attached no questions asked */
3826 if (size == 0 && !compose->batch) {
3827 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3828 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3829 NULL, _("_Cancel"), NULL, _("_Attach anyway"),
3830 NULL, NULL, ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3831 g_free(msg);
3833 if (aval != G_ALERTALTERNATE) {
3834 return FALSE;
3837 if ((fp = claws_fopen(file, "rb")) == NULL) {
3838 alertpanel_error(_("Can't read %s."), filename);
3839 return FALSE;
3841 claws_fclose(fp);
3843 ainfo = g_new0(AttachInfo, 1);
3844 auto_ainfo = g_auto_pointer_new_with_free
3845 (ainfo, (GFreeFunc) compose_attach_info_free);
3846 ainfo->file = g_strdup(file);
3848 if (content_type) {
3849 ainfo->content_type = g_strdup(content_type);
3850 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3851 MsgInfo *msginfo;
3852 MsgFlags flags = {0, 0};
3854 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3855 ainfo->encoding = ENC_7BIT;
3856 else
3857 ainfo->encoding = ENC_8BIT;
3859 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3860 if (msginfo && msginfo->subject)
3861 name = g_strdup(msginfo->subject);
3862 else
3863 name = g_path_get_basename(filename ? filename : file);
3865 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3867 procmsg_msginfo_free(&msginfo);
3868 } else {
3869 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3870 ainfo->charset = g_strdup(charset);
3871 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3872 } else {
3873 ainfo->encoding = ENC_BASE64;
3875 name = g_path_get_basename(filename ? filename : file);
3876 ainfo->name = g_strdup(name);
3878 g_free(name);
3879 } else {
3880 ainfo->content_type = procmime_get_mime_type(file);
3881 if (!ainfo->content_type) {
3882 ainfo->content_type =
3883 g_strdup("application/octet-stream");
3884 ainfo->encoding = ENC_BASE64;
3885 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3886 ainfo->encoding =
3887 procmime_get_encoding_for_text_file(file, &has_binary);
3888 else
3889 ainfo->encoding = ENC_BASE64;
3890 name = g_path_get_basename(filename ? filename : file);
3891 ainfo->name = g_strdup(name);
3892 g_free(name);
3895 if (ainfo->name != NULL
3896 && !strcmp(ainfo->name, ".")) {
3897 g_free(ainfo->name);
3898 ainfo->name = NULL;
3901 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3902 g_free(ainfo->content_type);
3903 ainfo->content_type = g_strdup("application/octet-stream");
3904 g_free(ainfo->charset);
3905 ainfo->charset = NULL;
3908 ainfo->size = (goffset)size;
3909 size_text = to_human_readable((goffset)size);
3911 store = GTK_LIST_STORE(gtk_tree_view_get_model
3912 (GTK_TREE_VIEW(compose->attach_clist)));
3914 gtk_list_store_append(store, &iter);
3915 gtk_list_store_set(store, &iter,
3916 COL_MIMETYPE, ainfo->content_type,
3917 COL_SIZE, size_text,
3918 COL_NAME, ainfo->name,
3919 COL_CHARSET, ainfo->charset,
3920 COL_DATA, ainfo,
3921 COL_AUTODATA, auto_ainfo,
3922 -1);
3924 g_auto_pointer_free(auto_ainfo);
3925 compose_attach_update_label(compose);
3926 return TRUE;
3929 void compose_use_signing(Compose *compose, gboolean use_signing)
3931 compose->use_signing = use_signing;
3932 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3935 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3937 compose->use_encryption = use_encryption;
3938 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3941 #define NEXT_PART_NOT_CHILD(info) \
3943 node = info->node; \
3944 while (node->children) \
3945 node = g_node_last_child(node); \
3946 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3949 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3951 MimeInfo *mimeinfo;
3952 MimeInfo *child;
3953 MimeInfo *firsttext = NULL;
3954 MimeInfo *encrypted = NULL;
3955 GNode *node;
3956 gchar *outfile;
3957 const gchar *partname = NULL;
3959 mimeinfo = procmime_scan_message(msginfo);
3960 if (!mimeinfo) return;
3962 if (mimeinfo->node->children == NULL) {
3963 procmime_mimeinfo_free_all(&mimeinfo);
3964 return;
3967 /* find first content part */
3968 child = (MimeInfo *) mimeinfo->node->children->data;
3969 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3970 child = (MimeInfo *)child->node->children->data;
3972 if (child) {
3973 if (child->type == MIMETYPE_TEXT) {
3974 firsttext = child;
3975 debug_print("First text part found\n");
3976 } else if (compose->mode == COMPOSE_REEDIT &&
3977 child->type == MIMETYPE_APPLICATION &&
3978 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3979 encrypted = (MimeInfo *)child->node->parent->data;
3982 child = (MimeInfo *) mimeinfo->node->children->data;
3983 while (child != NULL) {
3984 gint err;
3986 if (child == encrypted) {
3987 /* skip this part of tree */
3988 NEXT_PART_NOT_CHILD(child);
3989 continue;
3992 if (child->type == MIMETYPE_MULTIPART) {
3993 /* get the actual content */
3994 child = procmime_mimeinfo_next(child);
3995 continue;
3998 if (child == firsttext) {
3999 child = procmime_mimeinfo_next(child);
4000 continue;
4003 outfile = procmime_get_tmp_file_name(child);
4004 if ((err = procmime_get_part(outfile, child)) < 0)
4005 g_warning("can't get the part of multipart message. (%s)", g_strerror(-err));
4006 else {
4007 gchar *content_type;
4009 content_type = procmime_get_content_type_str(child->type, child->subtype);
4011 /* if we meet a pgp signature, we don't attach it, but
4012 * we force signing. */
4013 if ((strcmp(content_type, "application/pgp-signature") &&
4014 strcmp(content_type, "application/pkcs7-signature") &&
4015 strcmp(content_type, "application/x-pkcs7-signature"))
4016 || compose->mode == COMPOSE_REDIRECT) {
4017 partname = procmime_mimeinfo_get_parameter(child, "filename");
4018 if (partname == NULL)
4019 partname = procmime_mimeinfo_get_parameter(child, "name");
4020 if (partname == NULL)
4021 partname = "";
4022 compose_attach_append(compose, outfile,
4023 partname, content_type,
4024 procmime_mimeinfo_get_parameter(child, "charset"));
4025 } else {
4026 compose_force_signing(compose, compose->account, NULL);
4028 g_free(content_type);
4030 g_free(outfile);
4031 NEXT_PART_NOT_CHILD(child);
4033 procmime_mimeinfo_free_all(&mimeinfo);
4036 #undef NEXT_PART_NOT_CHILD
4040 typedef enum {
4041 WAIT_FOR_INDENT_CHAR,
4042 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4043 } IndentState;
4045 /* return indent length, we allow:
4046 indent characters followed by indent characters or spaces/tabs,
4047 alphabets and numbers immediately followed by indent characters,
4048 and the repeating sequences of the above
4049 If quote ends with multiple spaces, only the first one is included. */
4050 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4051 const GtkTextIter *start, gint *len)
4053 GtkTextIter iter = *start;
4054 gunichar wc;
4055 gchar ch[6];
4056 gint clen;
4057 IndentState state = WAIT_FOR_INDENT_CHAR;
4058 gboolean is_space;
4059 gboolean is_indent;
4060 gint alnum_count = 0;
4061 gint space_count = 0;
4062 gint quote_len = 0;
4064 if (prefs_common.quote_chars == NULL) {
4065 return 0 ;
4068 while (!gtk_text_iter_ends_line(&iter)) {
4069 wc = gtk_text_iter_get_char(&iter);
4070 if (g_unichar_iswide(wc))
4071 break;
4072 clen = g_unichar_to_utf8(wc, ch);
4073 if (clen != 1)
4074 break;
4076 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4077 is_space = g_unichar_isspace(wc);
4079 if (state == WAIT_FOR_INDENT_CHAR) {
4080 if (!is_indent && !g_unichar_isalnum(wc))
4081 break;
4082 if (is_indent) {
4083 quote_len += alnum_count + space_count + 1;
4084 alnum_count = space_count = 0;
4085 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4086 } else
4087 alnum_count++;
4088 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4089 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4090 break;
4091 if (is_space)
4092 space_count++;
4093 else if (is_indent) {
4094 quote_len += alnum_count + space_count + 1;
4095 alnum_count = space_count = 0;
4096 } else {
4097 alnum_count++;
4098 state = WAIT_FOR_INDENT_CHAR;
4102 gtk_text_iter_forward_char(&iter);
4105 if (quote_len > 0 && space_count > 0)
4106 quote_len++;
4108 if (len)
4109 *len = quote_len;
4111 if (quote_len > 0) {
4112 iter = *start;
4113 gtk_text_iter_forward_chars(&iter, quote_len);
4114 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4117 return NULL;
4120 /* return >0 if the line is itemized */
4121 static int compose_itemized_length(GtkTextBuffer *buffer,
4122 const GtkTextIter *start)
4124 GtkTextIter iter = *start;
4125 gunichar wc;
4126 gchar ch[6];
4127 gint clen;
4128 gint len = 0;
4129 if (gtk_text_iter_ends_line(&iter))
4130 return 0;
4132 while (1) {
4133 len++;
4134 wc = gtk_text_iter_get_char(&iter);
4135 if (!g_unichar_isspace(wc))
4136 break;
4137 gtk_text_iter_forward_char(&iter);
4138 if (gtk_text_iter_ends_line(&iter))
4139 return 0;
4142 clen = g_unichar_to_utf8(wc, ch);
4143 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4144 (clen == 3 && (
4145 wc == 0x2022 || /* BULLET */
4146 wc == 0x2023 || /* TRIANGULAR BULLET */
4147 wc == 0x2043 || /* HYPHEN BULLET */
4148 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4149 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4150 wc == 0x2219 || /* BULLET OPERATOR */
4151 wc == 0x25d8 || /* INVERSE BULLET */
4152 wc == 0x25e6 || /* WHITE BULLET */
4153 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4154 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4155 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4156 wc == 0x29be || /* CIRCLED WHITE BULLET */
4157 wc == 0x29bf /* CIRCLED BULLET */
4158 ))))
4159 return 0;
4161 gtk_text_iter_forward_char(&iter);
4162 if (gtk_text_iter_ends_line(&iter))
4163 return 0;
4164 wc = gtk_text_iter_get_char(&iter);
4165 if (g_unichar_isspace(wc)) {
4166 return len+1;
4168 return 0;
4171 /* return the string at the start of the itemization */
4172 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4173 const GtkTextIter *start)
4175 GtkTextIter iter = *start;
4176 gunichar wc;
4177 gint len = 0;
4178 GString *item_chars = g_string_new("");
4179 gchar *str = NULL;
4181 if (gtk_text_iter_ends_line(&iter)) {
4182 g_string_free(item_chars, FALSE);
4183 return NULL;
4186 while (1) {
4187 len++;
4188 wc = gtk_text_iter_get_char(&iter);
4189 if (!g_unichar_isspace(wc))
4190 break;
4191 gtk_text_iter_forward_char(&iter);
4192 if (gtk_text_iter_ends_line(&iter))
4193 break;
4194 g_string_append_unichar(item_chars, wc);
4197 str = item_chars->str;
4198 g_string_free(item_chars, FALSE);
4199 return str;
4202 /* return the number of spaces at a line's start */
4203 static int compose_left_offset_length(GtkTextBuffer *buffer,
4204 const GtkTextIter *start)
4206 GtkTextIter iter = *start;
4207 gunichar wc;
4208 gint len = 0;
4209 if (gtk_text_iter_ends_line(&iter))
4210 return 0;
4212 while (1) {
4213 wc = gtk_text_iter_get_char(&iter);
4214 if (!g_unichar_isspace(wc))
4215 break;
4216 len++;
4217 gtk_text_iter_forward_char(&iter);
4218 if (gtk_text_iter_ends_line(&iter))
4219 return 0;
4222 gtk_text_iter_forward_char(&iter);
4223 if (gtk_text_iter_ends_line(&iter))
4224 return 0;
4225 return len;
4228 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4229 const GtkTextIter *start,
4230 GtkTextIter *break_pos,
4231 gint max_col,
4232 gint quote_len)
4234 GtkTextIter iter = *start, line_end = *start;
4235 PangoLogAttr *attrs;
4236 gchar *str;
4237 gchar *p;
4238 gint len;
4239 gint i;
4240 gint col = 0;
4241 gint pos = 0;
4242 gboolean can_break = FALSE;
4243 gboolean do_break = FALSE;
4244 gboolean was_white = FALSE;
4245 gboolean prev_dont_break = FALSE;
4247 gtk_text_iter_forward_to_line_end(&line_end);
4248 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4249 len = g_utf8_strlen(str, -1);
4251 if (len == 0) {
4252 g_free(str);
4253 g_warning("compose_get_line_break_pos: len = 0!");
4254 return FALSE;
4257 /* g_print("breaking line: %d: %s (len = %d)\n",
4258 gtk_text_iter_get_line(&iter), str, len); */
4260 attrs = g_new(PangoLogAttr, len + 1);
4262 pango_default_break(str, -1, NULL, attrs, len + 1);
4264 p = str;
4266 /* skip quote and leading spaces */
4267 for (i = 0; *p != '\0' && i < len; i++) {
4268 gunichar wc;
4270 wc = g_utf8_get_char(p);
4271 if (i >= quote_len && !g_unichar_isspace(wc))
4272 break;
4273 if (g_unichar_iswide(wc))
4274 col += 2;
4275 else if (*p == '\t')
4276 col += 8;
4277 else
4278 col++;
4279 p = g_utf8_next_char(p);
4282 for (; *p != '\0' && i < len; i++) {
4283 PangoLogAttr *attr = attrs + i;
4284 gunichar wc = g_utf8_get_char(p);
4285 gint uri_len;
4287 /* attr->is_line_break will be false for some characters that
4288 * we want to break a line before, like '/' or ':', so we
4289 * also allow breaking on any non-wide character. The
4290 * mentioned pango attribute is still useful to decide on
4291 * line breaks when wide characters are involved. */
4292 if ((!g_unichar_iswide(wc) || attr->is_line_break)
4293 && can_break && was_white && !prev_dont_break)
4294 pos = i;
4296 was_white = attr->is_white;
4298 /* don't wrap URI */
4299 if ((uri_len = get_uri_len(p)) > 0) {
4300 col += uri_len;
4301 if (pos > 0 && col > max_col) {
4302 do_break = TRUE;
4303 break;
4305 i += uri_len - 1;
4306 p += uri_len;
4307 can_break = TRUE;
4308 continue;
4311 if (g_unichar_iswide(wc)) {
4312 col += 2;
4313 if (prev_dont_break && can_break && attr->is_line_break)
4314 pos = i;
4315 } else if (*p == '\t')
4316 col += 8;
4317 else
4318 col++;
4319 if (pos > 0 && col > max_col) {
4320 do_break = TRUE;
4321 break;
4324 if (*p == '-' || *p == '/')
4325 prev_dont_break = TRUE;
4326 else
4327 prev_dont_break = FALSE;
4329 p = g_utf8_next_char(p);
4330 can_break = TRUE;
4333 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4335 g_free(attrs);
4336 g_free(str);
4338 *break_pos = *start;
4339 gtk_text_iter_set_line_offset(break_pos, pos);
4341 return do_break;
4344 static gboolean compose_join_next_line(Compose *compose,
4345 GtkTextBuffer *buffer,
4346 GtkTextIter *iter,
4347 const gchar *quote_str)
4349 GtkTextIter iter_ = *iter, cur, prev, next, end;
4350 PangoLogAttr attrs[3];
4351 gchar *str;
4352 gchar *next_quote_str;
4353 gunichar wc1, wc2;
4354 gint quote_len;
4355 gboolean keep_cursor = FALSE;
4357 if (!gtk_text_iter_forward_line(&iter_) ||
4358 gtk_text_iter_ends_line(&iter_)) {
4359 return FALSE;
4361 next_quote_str = compose_get_quote_str(buffer, &iter_, &quote_len);
4363 if ((quote_str || next_quote_str) &&
4364 g_strcmp0(quote_str, next_quote_str) != 0) {
4365 g_free(next_quote_str);
4366 return FALSE;
4368 g_free(next_quote_str);
4370 end = iter_;
4371 if (quote_len > 0) {
4372 gtk_text_iter_forward_chars(&end, quote_len);
4373 if (gtk_text_iter_ends_line(&end)) {
4374 return FALSE;
4378 /* don't join itemized lines */
4379 if (compose_itemized_length(buffer, &end) > 0) {
4380 return FALSE;
4383 /* don't join signature separator */
4384 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4385 return FALSE;
4387 /* delete quote str */
4388 if (quote_len > 0)
4389 gtk_text_buffer_delete(buffer, &iter_, &end);
4391 /* don't join line breaks put by the user */
4392 prev = cur = iter_;
4393 gtk_text_iter_backward_char(&cur);
4394 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4395 gtk_text_iter_forward_char(&cur);
4396 *iter = cur;
4397 return FALSE;
4399 gtk_text_iter_forward_char(&cur);
4400 /* delete linebreak and extra spaces */
4401 while (gtk_text_iter_backward_char(&cur)) {
4402 wc1 = gtk_text_iter_get_char(&cur);
4403 if (!g_unichar_isspace(wc1))
4404 break;
4405 prev = cur;
4407 next = cur = iter_;
4408 while (!gtk_text_iter_ends_line(&cur)) {
4409 wc1 = gtk_text_iter_get_char(&cur);
4410 if (!g_unichar_isspace(wc1))
4411 break;
4412 gtk_text_iter_forward_char(&cur);
4413 next = cur;
4415 if (!gtk_text_iter_equal(&prev, &next)) {
4416 GtkTextMark *mark;
4418 mark = gtk_text_buffer_get_insert(buffer);
4419 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4420 if (gtk_text_iter_equal(&prev, &cur))
4421 keep_cursor = TRUE;
4422 gtk_text_buffer_delete(buffer, &prev, &next);
4424 iter_ = prev;
4426 /* insert space if required */
4427 gtk_text_iter_backward_char(&prev);
4428 wc1 = gtk_text_iter_get_char(&prev);
4429 wc2 = gtk_text_iter_get_char(&next);
4430 gtk_text_iter_forward_char(&next);
4431 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4432 pango_default_break(str, -1, NULL, attrs, 3);
4433 if (!attrs[1].is_line_break ||
4434 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4435 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4436 if (keep_cursor) {
4437 gtk_text_iter_backward_char(&iter_);
4438 gtk_text_buffer_place_cursor(buffer, &iter_);
4441 g_free(str);
4443 *iter = iter_;
4444 return TRUE;
4447 #define ADD_TXT_POS(bp_, ep_, pti_) \
4448 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4449 last = last->next; \
4450 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4451 last->next = NULL; \
4452 } else { \
4453 g_warning("alloc error scanning URIs"); \
4456 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4458 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4459 GtkTextBuffer *buffer;
4460 GtkTextIter iter, break_pos, end_of_line;
4461 gchar *quote_str = NULL;
4462 gint quote_len;
4463 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4464 gboolean prev_autowrap = compose->autowrap;
4465 gint startq_offset = -1, noq_offset = -1;
4466 gint uri_start = -1, uri_stop = -1;
4467 gint nouri_start = -1, nouri_stop = -1;
4468 gint num_blocks = 0;
4469 gint quotelevel = -1;
4470 gboolean modified = force;
4471 gboolean removed = FALSE;
4472 gboolean modified_before_remove = FALSE;
4473 gint lines = 0;
4474 gboolean start = TRUE;
4475 gint itemized_len = 0, rem_item_len = 0;
4476 gchar *itemized_chars = NULL;
4477 gboolean item_continuation = FALSE;
4479 if (force) {
4480 modified = TRUE;
4482 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4483 modified = TRUE;
4486 compose->autowrap = FALSE;
4488 buffer = gtk_text_view_get_buffer(text);
4489 undo_wrapping(compose->undostruct, TRUE);
4490 if (par_iter) {
4491 iter = *par_iter;
4492 } else {
4493 GtkTextMark *mark;
4494 mark = gtk_text_buffer_get_insert(buffer);
4495 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4499 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4500 if (gtk_text_iter_ends_line(&iter)) {
4501 while (gtk_text_iter_ends_line(&iter) &&
4502 gtk_text_iter_forward_line(&iter))
4504 } else {
4505 while (gtk_text_iter_backward_line(&iter)) {
4506 if (gtk_text_iter_ends_line(&iter)) {
4507 gtk_text_iter_forward_line(&iter);
4508 break;
4512 } else {
4513 /* move to line start */
4514 gtk_text_iter_set_line_offset(&iter, 0);
4517 itemized_len = compose_itemized_length(buffer, &iter);
4519 if (!itemized_len) {
4520 itemized_len = compose_left_offset_length(buffer, &iter);
4521 item_continuation = TRUE;
4524 if (itemized_len)
4525 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4527 /* go until paragraph end (empty line) */
4528 while (start || !gtk_text_iter_ends_line(&iter)) {
4529 gchar *scanpos = NULL;
4530 /* parse table - in order of priority */
4531 struct table {
4532 const gchar *needle; /* token */
4534 /* token search function */
4535 gchar *(*search) (const gchar *haystack,
4536 const gchar *needle);
4537 /* part parsing function */
4538 gboolean (*parse) (const gchar *start,
4539 const gchar *scanpos,
4540 const gchar **bp_,
4541 const gchar **ep_,
4542 gboolean hdr);
4543 /* part to URI function */
4544 gchar *(*build_uri) (const gchar *bp,
4545 const gchar *ep);
4548 static struct table parser[] = {
4549 {"http://", strcasestr, get_uri_part, make_uri_string},
4550 {"https://", strcasestr, get_uri_part, make_uri_string},
4551 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4552 {"ftps://", strcasestr, get_uri_part, make_uri_string},
4553 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4554 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4555 {"www.", strcasestr, get_uri_part, make_http_string},
4556 {"webcal://",strcasestr, get_uri_part, make_uri_string},
4557 {"webcals://",strcasestr, get_uri_part, make_uri_string},
4558 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4559 {"@", strcasestr, get_email_part, make_email_string}
4561 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4562 gint last_index = PARSE_ELEMS;
4563 gint n;
4564 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4565 gint walk_pos;
4567 start = FALSE;
4568 if (!prev_autowrap && num_blocks == 0) {
4569 num_blocks++;
4570 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4571 G_CALLBACK(text_inserted),
4572 compose);
4574 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4575 goto colorize;
4577 uri_start = uri_stop = -1;
4578 quote_len = 0;
4579 quote_str = compose_get_quote_str(buffer, &iter, &quote_len);
4581 if (quote_str) {
4582 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4583 if (startq_offset == -1)
4584 startq_offset = gtk_text_iter_get_offset(&iter);
4585 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4586 if (quotelevel > 2) {
4587 /* recycle colors */
4588 if (prefs_common.recycle_quote_colors)
4589 quotelevel %= 3;
4590 else
4591 quotelevel = 2;
4593 if (!wrap_quote) {
4594 goto colorize;
4596 } else {
4597 if (startq_offset == -1)
4598 noq_offset = gtk_text_iter_get_offset(&iter);
4599 quotelevel = -1;
4602 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4603 goto colorize;
4605 if (gtk_text_iter_ends_line(&iter)) {
4606 goto colorize;
4607 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4608 prefs_common.linewrap_len,
4609 quote_len)) {
4610 GtkTextIter prev, next, cur;
4611 if (prev_autowrap != FALSE || force) {
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 if (quote_str && wrap_quote) {
4622 compose->automatic_break = TRUE;
4623 modified = TRUE;
4624 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4625 compose->automatic_break = FALSE;
4626 if (itemized_len && compose->autoindent) {
4627 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4628 if (!item_continuation)
4629 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4631 } else
4632 goto colorize;
4633 /* remove trailing spaces */
4634 cur = break_pos;
4635 rem_item_len = itemized_len;
4636 while (compose->autoindent && rem_item_len-- > 0)
4637 gtk_text_iter_backward_char(&cur);
4638 gtk_text_iter_backward_char(&cur);
4640 prev = next = cur;
4641 while (!gtk_text_iter_starts_line(&cur)) {
4642 gunichar wc;
4644 gtk_text_iter_backward_char(&cur);
4645 wc = gtk_text_iter_get_char(&cur);
4646 if (!g_unichar_isspace(wc))
4647 break;
4648 prev = cur;
4650 if (!gtk_text_iter_equal(&prev, &next)) {
4651 gtk_text_buffer_delete(buffer, &prev, &next);
4652 break_pos = next;
4653 gtk_text_iter_forward_char(&break_pos);
4656 if (quote_str)
4657 gtk_text_buffer_insert(buffer, &break_pos,
4658 quote_str, -1);
4660 iter = break_pos;
4661 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4663 /* move iter to current line start */
4664 gtk_text_iter_set_line_offset(&iter, 0);
4665 if (quote_str) {
4666 g_free(quote_str);
4667 quote_str = NULL;
4669 continue;
4670 } else {
4671 /* move iter to next line start */
4672 iter = break_pos;
4673 lines++;
4676 colorize:
4677 if (!prev_autowrap && num_blocks > 0) {
4678 num_blocks--;
4679 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4680 G_CALLBACK(text_inserted),
4681 compose);
4683 end_of_line = iter;
4684 while (!gtk_text_iter_ends_line(&end_of_line)) {
4685 gtk_text_iter_forward_char(&end_of_line);
4687 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4689 nouri_start = gtk_text_iter_get_offset(&iter);
4690 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4692 walk_pos = gtk_text_iter_get_offset(&iter);
4693 /* FIXME: this looks phony. scanning for anything in the parse table */
4694 for (n = 0; n < PARSE_ELEMS; n++) {
4695 gchar *tmp;
4697 tmp = parser[n].search(walk, parser[n].needle);
4698 if (tmp) {
4699 if (scanpos == NULL || tmp < scanpos) {
4700 scanpos = tmp;
4701 last_index = n;
4706 bp = ep = 0;
4707 if (scanpos) {
4708 /* check if URI can be parsed */
4709 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4710 (const gchar **)&ep, FALSE)
4711 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4712 walk = ep;
4713 } else
4714 walk = scanpos +
4715 strlen(parser[last_index].needle);
4717 if (bp && ep) {
4718 uri_start = walk_pos + (bp - o_walk);
4719 uri_stop = walk_pos + (ep - o_walk);
4721 g_free(o_walk);
4722 o_walk = NULL;
4723 gtk_text_iter_forward_line(&iter);
4724 g_free(quote_str);
4725 quote_str = NULL;
4726 if (startq_offset != -1) {
4727 GtkTextIter startquote, endquote;
4728 gtk_text_buffer_get_iter_at_offset(
4729 buffer, &startquote, startq_offset);
4730 endquote = iter;
4732 switch (quotelevel) {
4733 case 0:
4734 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4735 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4736 gtk_text_buffer_apply_tag_by_name(
4737 buffer, "quote0", &startquote, &endquote);
4738 gtk_text_buffer_remove_tag_by_name(
4739 buffer, "quote1", &startquote, &endquote);
4740 gtk_text_buffer_remove_tag_by_name(
4741 buffer, "quote2", &startquote, &endquote);
4742 modified = TRUE;
4744 break;
4745 case 1:
4746 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4747 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4748 gtk_text_buffer_apply_tag_by_name(
4749 buffer, "quote1", &startquote, &endquote);
4750 gtk_text_buffer_remove_tag_by_name(
4751 buffer, "quote0", &startquote, &endquote);
4752 gtk_text_buffer_remove_tag_by_name(
4753 buffer, "quote2", &startquote, &endquote);
4754 modified = TRUE;
4756 break;
4757 case 2:
4758 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4759 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4760 gtk_text_buffer_apply_tag_by_name(
4761 buffer, "quote2", &startquote, &endquote);
4762 gtk_text_buffer_remove_tag_by_name(
4763 buffer, "quote0", &startquote, &endquote);
4764 gtk_text_buffer_remove_tag_by_name(
4765 buffer, "quote1", &startquote, &endquote);
4766 modified = TRUE;
4768 break;
4770 startq_offset = -1;
4771 } else if (noq_offset != -1) {
4772 GtkTextIter startnoquote, endnoquote;
4773 gtk_text_buffer_get_iter_at_offset(
4774 buffer, &startnoquote, noq_offset);
4775 endnoquote = iter;
4777 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4778 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4779 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4780 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4781 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4782 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4783 gtk_text_buffer_remove_tag_by_name(
4784 buffer, "quote0", &startnoquote, &endnoquote);
4785 gtk_text_buffer_remove_tag_by_name(
4786 buffer, "quote1", &startnoquote, &endnoquote);
4787 gtk_text_buffer_remove_tag_by_name(
4788 buffer, "quote2", &startnoquote, &endnoquote);
4789 modified = TRUE;
4791 noq_offset = -1;
4794 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4795 GtkTextIter nouri_start_iter, nouri_end_iter;
4796 gtk_text_buffer_get_iter_at_offset(
4797 buffer, &nouri_start_iter, nouri_start);
4798 gtk_text_buffer_get_iter_at_offset(
4799 buffer, &nouri_end_iter, nouri_stop);
4800 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4801 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4802 gtk_text_buffer_remove_tag_by_name(
4803 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4804 modified_before_remove = modified;
4805 modified = TRUE;
4806 removed = TRUE;
4809 if (uri_start >= 0 && uri_stop > 0) {
4810 GtkTextIter uri_start_iter, uri_end_iter, back;
4811 gtk_text_buffer_get_iter_at_offset(
4812 buffer, &uri_start_iter, uri_start);
4813 gtk_text_buffer_get_iter_at_offset(
4814 buffer, &uri_end_iter, uri_stop);
4815 back = uri_end_iter;
4816 gtk_text_iter_backward_char(&back);
4817 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4818 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4819 gtk_text_buffer_apply_tag_by_name(
4820 buffer, "link", &uri_start_iter, &uri_end_iter);
4821 modified = TRUE;
4822 if (removed && !modified_before_remove) {
4823 modified = FALSE;
4827 if (!modified) {
4828 /* debug_print("not modified, out after %d lines\n", lines); */
4829 goto end;
4832 /* debug_print("modified, out after %d lines\n", lines); */
4833 end:
4834 g_free(itemized_chars);
4835 if (par_iter)
4836 *par_iter = iter;
4837 undo_wrapping(compose->undostruct, FALSE);
4838 compose->autowrap = prev_autowrap;
4840 return modified;
4843 void compose_action_cb(void *data)
4845 Compose *compose = (Compose *)data;
4846 compose_wrap_all(compose);
4849 static void compose_wrap_all(Compose *compose)
4851 compose_wrap_all_full(compose, FALSE);
4854 static void compose_wrap_all_full(Compose *compose, gboolean force)
4856 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4857 GtkTextBuffer *buffer;
4858 GtkTextIter iter;
4859 gboolean modified = TRUE;
4861 buffer = gtk_text_view_get_buffer(text);
4863 gtk_text_buffer_get_start_iter(buffer, &iter);
4865 undo_wrapping(compose->undostruct, TRUE);
4867 while (!gtk_text_iter_is_end(&iter) && modified)
4868 modified = compose_beautify_paragraph(compose, &iter, force);
4870 undo_wrapping(compose->undostruct, FALSE);
4874 static void compose_set_title(Compose *compose)
4876 gchar *str;
4877 gchar *edited;
4878 gchar *subject;
4880 edited = compose->modified ? _(" [Edited]") : "";
4882 subject = gtk_editable_get_chars(
4883 GTK_EDITABLE(compose->subject_entry), 0, -1);
4885 #ifndef GENERIC_UMPC
4886 if (subject && strlen(subject))
4887 str = g_strdup_printf(_("%s - Compose message%s"),
4888 subject, edited);
4889 else
4890 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4891 #else
4892 str = g_strdup(_("Compose message"));
4893 #endif
4895 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4896 g_free(str);
4897 g_free(subject);
4901 * compose_current_mail_account:
4903 * Find a current mail account (the currently selected account, or the
4904 * default account, if a news account is currently selected). If a
4905 * mail account cannot be found, display an error message.
4907 * Return value: Mail account, or NULL if not found.
4909 static PrefsAccount *
4910 compose_current_mail_account(void)
4912 PrefsAccount *ac;
4914 if (cur_account && cur_account->protocol != A_NNTP)
4915 ac = cur_account;
4916 else {
4917 ac = account_get_default();
4918 if (!ac || ac->protocol == A_NNTP) {
4919 alertpanel_error(_("Account for sending mail is not specified.\n"
4920 "Please select a mail account before sending."));
4921 return NULL;
4924 return ac;
4927 #define QUOTE_IF_REQUIRED(out, str) \
4929 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4930 gchar *__tmp; \
4931 gint len; \
4933 len = strlen(str) + 3; \
4934 if ((__tmp = alloca(len)) == NULL) { \
4935 g_warning("can't allocate memory"); \
4936 g_string_free(header, TRUE); \
4937 return NULL; \
4939 g_snprintf(__tmp, len, "\"%s\"", str); \
4940 out = __tmp; \
4941 } else { \
4942 gchar *__tmp; \
4944 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4945 g_warning("can't allocate memory"); \
4946 g_string_free(header, TRUE); \
4947 return NULL; \
4948 } else \
4949 strcpy(__tmp, str); \
4951 out = __tmp; \
4955 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4957 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4958 gchar *__tmp; \
4959 gint len; \
4961 len = strlen(str) + 3; \
4962 if ((__tmp = alloca(len)) == NULL) { \
4963 g_warning("can't allocate memory"); \
4964 errret; \
4966 g_snprintf(__tmp, len, "\"%s\"", str); \
4967 out = __tmp; \
4968 } else { \
4969 gchar *__tmp; \
4971 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4972 g_warning("can't allocate memory"); \
4973 errret; \
4974 } else \
4975 strcpy(__tmp, str); \
4977 out = __tmp; \
4981 static void compose_select_account(Compose *compose, PrefsAccount *account,
4982 gboolean init)
4984 gchar *from = NULL, *header = NULL;
4985 ComposeHeaderEntry *header_entry;
4986 GtkTreeIter iter;
4988 cm_return_if_fail(account != NULL);
4990 compose->account = account;
4991 if (account->name && *account->name) {
4992 gchar *buf, *qbuf;
4993 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4994 qbuf = escape_internal_quotes(buf, '"');
4995 from = g_strdup_printf("%s <%s>",
4996 qbuf, account->address);
4997 if (qbuf != buf)
4998 g_free(qbuf);
4999 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
5000 } else {
5001 from = g_strdup_printf("<%s>",
5002 account->address);
5003 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
5006 g_free(from);
5008 compose_set_title(compose);
5010 compose_activate_privacy_system(compose, account, FALSE);
5012 if (account->default_sign && privacy_system_can_sign(compose->privacy_system) &&
5013 compose->mode != COMPOSE_REDIRECT)
5014 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
5015 else
5016 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
5017 if (account->default_encrypt && privacy_system_can_encrypt(compose->privacy_system) &&
5018 compose->mode != COMPOSE_REDIRECT)
5019 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
5020 else
5021 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
5023 if (!init && compose->mode != COMPOSE_REDIRECT) {
5024 undo_block(compose->undostruct);
5025 compose_insert_sig(compose, TRUE);
5026 undo_unblock(compose->undostruct);
5029 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
5030 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
5031 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
5032 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5034 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5035 if (account->protocol == A_NNTP) {
5036 if (!strcmp(header, _("To:")))
5037 combobox_select_by_text(
5038 GTK_COMBO_BOX(header_entry->combo),
5039 _("Newsgroups:"));
5040 } else {
5041 if (!strcmp(header, _("Newsgroups:")))
5042 combobox_select_by_text(
5043 GTK_COMBO_BOX(header_entry->combo),
5044 _("To:"));
5048 g_free(header);
5050 #ifdef USE_ENCHANT
5051 /* use account's dict info if set */
5052 if (compose->gtkaspell) {
5053 if (account->enable_default_dictionary)
5054 gtkaspell_change_dict(compose->gtkaspell,
5055 account->default_dictionary, FALSE);
5056 if (account->enable_default_alt_dictionary)
5057 gtkaspell_change_alt_dict(compose->gtkaspell,
5058 account->default_alt_dictionary);
5059 if (account->enable_default_dictionary
5060 || account->enable_default_alt_dictionary)
5061 compose_spell_menu_changed(compose);
5063 #endif
5066 gboolean compose_check_for_valid_recipient(Compose *compose) {
5067 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5068 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5069 gboolean recipient_found = FALSE;
5070 GSList *list;
5071 gchar **strptr;
5073 /* free to and newsgroup list */
5074 slist_free_strings_full(compose->to_list);
5075 compose->to_list = NULL;
5077 slist_free_strings_full(compose->newsgroup_list);
5078 compose->newsgroup_list = NULL;
5080 /* search header entries for to and newsgroup entries */
5081 for (list = compose->header_list; list; list = list->next) {
5082 gchar *header;
5083 gchar *entry;
5084 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5085 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5086 g_strstrip(entry);
5087 g_strstrip(header);
5088 if (entry[0] != '\0') {
5089 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5090 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5091 compose->to_list = address_list_append(compose->to_list, entry);
5092 recipient_found = TRUE;
5095 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5096 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5097 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5098 recipient_found = TRUE;
5102 g_free(header);
5103 g_free(entry);
5105 return recipient_found;
5108 static gboolean compose_check_for_set_recipients(Compose *compose)
5110 if (compose->account->set_autocc && compose->account->auto_cc) {
5111 gboolean found_other = FALSE;
5112 GSList *list;
5113 /* search header entries for to and newsgroup entries */
5114 for (list = compose->header_list; list; list = list->next) {
5115 gchar *entry;
5116 gchar *header;
5117 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5118 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5119 g_strstrip(entry);
5120 g_strstrip(header);
5121 if (strcmp(entry, compose->account->auto_cc)
5122 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5123 found_other = TRUE;
5124 g_free(entry);
5125 break;
5127 g_free(entry);
5128 g_free(header);
5130 if (!found_other) {
5131 AlertValue aval;
5132 gchar *text;
5133 if (compose->batch) {
5134 gtk_widget_show_all(compose->window);
5136 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5137 prefs_common_translated_header_name("Cc"));
5138 aval = alertpanel(_("Send"),
5139 text,
5140 NULL, _("_Cancel"), NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND);
5141 g_free(text);
5142 if (aval != G_ALERTALTERNATE)
5143 return FALSE;
5146 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5147 gboolean found_other = FALSE;
5148 GSList *list;
5149 /* search header entries for to and newsgroup entries */
5150 for (list = compose->header_list; list; list = list->next) {
5151 gchar *entry;
5152 gchar *header;
5153 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5154 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5155 g_strstrip(entry);
5156 g_strstrip(header);
5157 if (strcmp(entry, compose->account->auto_bcc)
5158 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5159 found_other = TRUE;
5160 g_free(entry);
5161 g_free(header);
5162 break;
5164 g_free(entry);
5165 g_free(header);
5167 if (!found_other) {
5168 AlertValue aval;
5169 gchar *text;
5170 if (compose->batch) {
5171 gtk_widget_show_all(compose->window);
5173 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5174 prefs_common_translated_header_name("Bcc"));
5175 aval = alertpanel(_("Send"),
5176 text,
5177 NULL, _("_Cancel"), NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND);
5178 g_free(text);
5179 if (aval != G_ALERTALTERNATE)
5180 return FALSE;
5183 return TRUE;
5186 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5188 const gchar *str;
5190 if (compose_check_for_valid_recipient(compose) == FALSE) {
5191 if (compose->batch) {
5192 gtk_widget_show_all(compose->window);
5194 alertpanel_error(_("Recipient is not specified."));
5195 return FALSE;
5198 if (compose_check_for_set_recipients(compose) == FALSE) {
5199 return FALSE;
5202 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5203 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5204 if (*str == '\0' && check_everything == TRUE &&
5205 compose->mode != COMPOSE_REDIRECT) {
5206 AlertValue aval;
5207 gchar *message;
5209 message = g_strdup_printf(_("Subject is empty. %s"),
5210 compose->sending?_("Send it anyway?"):
5211 _("Queue it anyway?"));
5213 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5214 NULL, _("_Cancel"), NULL, compose->sending?_("_Send"):_("_Queue"),
5215 NULL, NULL, ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5216 g_free(message);
5217 if (aval & G_ALERTDISABLE) {
5218 aval &= ~G_ALERTDISABLE;
5219 prefs_common.warn_empty_subj = FALSE;
5221 if (aval != G_ALERTALTERNATE)
5222 return FALSE;
5226 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5227 && check_everything == TRUE) {
5228 GSList *list;
5229 gint cnt = 0;
5231 /* count To and Cc recipients */
5232 for (list = compose->header_list; list; list = list->next) {
5233 gchar *header;
5234 gchar *entry;
5236 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5237 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5238 g_strstrip(header);
5239 g_strstrip(entry);
5240 if ((entry[0] != '\0') &&
5241 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5242 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5243 cnt++;
5245 g_free(header);
5246 g_free(entry);
5248 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5249 AlertValue aval;
5250 gchar *message;
5252 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5253 compose->sending?_("Send it anyway?"):
5254 _("Queue it anyway?"));
5256 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5257 NULL, _("_Cancel"), NULL, compose->sending?_("_Send"):_("_Queue"),
5258 NULL, NULL, ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5259 g_free(message);
5260 if (aval & G_ALERTDISABLE) {
5261 aval &= ~G_ALERTDISABLE;
5262 prefs_common.warn_sending_many_recipients_num = 0;
5264 if (aval != G_ALERTALTERNATE)
5265 return FALSE;
5269 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5270 return FALSE;
5272 return TRUE;
5275 static void _display_queue_error(ComposeQueueResult val)
5277 switch (val) {
5278 case COMPOSE_QUEUE_SUCCESS:
5279 break;
5280 case COMPOSE_QUEUE_ERROR_NO_MSG:
5281 alertpanel_error(_("Could not queue message."));
5282 break;
5283 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5284 alertpanel_error(_("Could not queue message:\n\n%s."),
5285 g_strerror(errno));
5286 break;
5287 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5288 alertpanel_error(_("Could not queue message for sending:\n\n"
5289 "Signature failed: %s"),
5290 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5291 break;
5292 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5293 alertpanel_error(_("Could not queue message for sending:\n\n"
5294 "Encryption failed: %s"),
5295 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5296 break;
5297 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5298 alertpanel_error(_("Could not queue message for sending:\n\n"
5299 "Charset conversion failed."));
5300 break;
5301 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5302 alertpanel_error(_("Could not queue message for sending:\n\n"
5303 "Couldn't get recipient encryption key."));
5304 break;
5305 case COMPOSE_QUEUE_SIGNING_CANCELLED:
5306 debug_print("signing cancelled\n");
5307 break;
5308 default:
5309 /* unhandled error */
5310 debug_print("oops, unhandled compose_queue() return value %d\n",
5311 val);
5312 break;
5316 gint compose_send(Compose *compose)
5318 gint msgnum;
5319 FolderItem *folder = NULL;
5320 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5321 gchar *msgpath = NULL;
5322 gboolean discard_window = FALSE;
5323 gchar *errstr = NULL;
5324 gchar *tmsgid = NULL;
5325 MainWindow *mainwin = mainwindow_get_mainwindow();
5326 gboolean queued_removed = FALSE;
5328 if (prefs_common.send_dialog_invisible
5329 || compose->batch == TRUE)
5330 discard_window = TRUE;
5332 compose_allow_user_actions (compose, FALSE);
5333 compose->sending = TRUE;
5335 if (compose_check_entries(compose, TRUE) == FALSE) {
5336 if (compose->batch) {
5337 gtk_widget_show_all(compose->window);
5339 goto bail;
5342 inc_lock();
5343 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5345 if (val != COMPOSE_QUEUE_SUCCESS) {
5346 if (compose->batch) {
5347 gtk_widget_show_all(compose->window);
5350 _display_queue_error(val);
5352 goto bail;
5355 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5356 if (discard_window) {
5357 compose->sending = FALSE;
5358 compose_close(compose);
5359 /* No more compose access in the normal codepath
5360 * after this point! */
5361 compose = NULL;
5364 if (msgnum == 0) {
5365 alertpanel_error(_("The message was queued but could not be "
5366 "sent.\nUse \"Send queued messages\" from "
5367 "the main window to retry."));
5368 if (!discard_window) {
5369 goto bail;
5371 inc_unlock();
5372 g_free(tmsgid);
5373 return -1;
5375 if (msgpath == NULL) {
5376 msgpath = folder_item_fetch_msg(folder, msgnum);
5377 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5378 g_free(msgpath);
5379 } else {
5380 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5381 claws_unlink(msgpath);
5382 g_free(msgpath);
5384 if (!discard_window) {
5385 if (val != 0) {
5386 if (!queued_removed)
5387 folder_item_remove_msg(folder, msgnum);
5388 folder_item_scan(folder);
5389 if (tmsgid) {
5390 /* make sure we delete that */
5391 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5392 if (tmp) {
5393 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5394 folder_item_remove_msg(folder, tmp->msgnum);
5395 procmsg_msginfo_free(&tmp);
5401 if (val == 0) {
5402 if (!queued_removed)
5403 folder_item_remove_msg(folder, msgnum);
5404 folder_item_scan(folder);
5405 if (tmsgid) {
5406 /* make sure we delete that */
5407 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5408 if (tmp) {
5409 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5410 folder_item_remove_msg(folder, tmp->msgnum);
5411 procmsg_msginfo_free(&tmp);
5414 if (!discard_window) {
5415 compose->sending = FALSE;
5416 compose_allow_user_actions (compose, TRUE);
5417 compose_close(compose);
5419 } else {
5420 if (errstr) {
5421 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5422 "the main window to retry."), errstr);
5423 g_free(errstr);
5424 } else {
5425 alertpanel_error_log(_("The message was queued but could not be "
5426 "sent.\nUse \"Send queued messages\" from "
5427 "the main window to retry."));
5429 if (!discard_window) {
5430 goto bail;
5432 inc_unlock();
5433 g_free(tmsgid);
5434 return -1;
5436 g_free(tmsgid);
5437 inc_unlock();
5438 toolbar_main_set_sensitive(mainwin);
5439 main_window_set_menu_sensitive(mainwin);
5440 return 0;
5442 bail:
5443 inc_unlock();
5444 g_free(tmsgid);
5445 compose_allow_user_actions (compose, TRUE);
5446 compose->sending = FALSE;
5447 compose->modified = TRUE;
5448 toolbar_main_set_sensitive(mainwin);
5449 main_window_set_menu_sensitive(mainwin);
5451 return -1;
5454 static gboolean compose_use_attach(Compose *compose)
5456 GtkTreeModel *model = gtk_tree_view_get_model
5457 (GTK_TREE_VIEW(compose->attach_clist));
5458 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5461 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5462 FILE *fp)
5464 gchar buf[BUFFSIZE];
5465 gchar *str;
5466 gboolean first_to_address;
5467 gboolean first_cc_address;
5468 GSList *list;
5469 ComposeHeaderEntry *headerentry;
5470 const gchar *headerentryname;
5471 const gchar *cc_hdr;
5472 const gchar *to_hdr;
5473 gboolean err = FALSE;
5475 debug_print("Writing redirect header\n");
5477 cc_hdr = prefs_common_translated_header_name("Cc:");
5478 to_hdr = prefs_common_translated_header_name("To:");
5480 first_to_address = TRUE;
5481 for (list = compose->header_list; list; list = list->next) {
5482 headerentry = ((ComposeHeaderEntry *)list->data);
5483 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5485 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5486 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5487 Xstrdup_a(str, entstr, return -1);
5488 g_strstrip(str);
5489 if (str[0] != '\0') {
5490 compose_convert_header
5491 (compose, buf, sizeof(buf), str,
5492 strlen("Resent-To") + 2, TRUE);
5494 if (first_to_address) {
5495 err |= (fprintf(fp, "Resent-To: ") < 0);
5496 first_to_address = FALSE;
5497 } else {
5498 err |= (fprintf(fp, ",") < 0);
5500 err |= (fprintf(fp, "%s", buf) < 0);
5504 if (!first_to_address) {
5505 err |= (fprintf(fp, "\n") < 0);
5508 first_cc_address = TRUE;
5509 for (list = compose->header_list; list; list = list->next) {
5510 headerentry = ((ComposeHeaderEntry *)list->data);
5511 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5513 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5514 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5515 Xstrdup_a(str, strg, return -1);
5516 g_strstrip(str);
5517 if (str[0] != '\0') {
5518 compose_convert_header
5519 (compose, buf, sizeof(buf), str,
5520 strlen("Resent-Cc") + 2, TRUE);
5522 if (first_cc_address) {
5523 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5524 first_cc_address = FALSE;
5525 } else {
5526 err |= (fprintf(fp, ",") < 0);
5528 err |= (fprintf(fp, "%s", buf) < 0);
5532 if (!first_cc_address) {
5533 err |= (fprintf(fp, "\n") < 0);
5536 return (err ? -1:0);
5539 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5541 gchar date[RFC822_DATE_BUFFSIZE];
5542 gchar buf[BUFFSIZE];
5543 gchar *str;
5544 const gchar *entstr;
5545 /* struct utsname utsbuf; */
5546 gboolean err = FALSE;
5548 cm_return_val_if_fail(fp != NULL, -1);
5549 cm_return_val_if_fail(compose->account != NULL, -1);
5550 cm_return_val_if_fail(compose->account->address != NULL, -1);
5552 /* Resent-Date */
5553 if (prefs_common.hide_timezone)
5554 get_rfc822_date_hide_tz(date, sizeof(date));
5555 else
5556 get_rfc822_date(date, sizeof(date));
5557 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5559 /* Resent-From */
5560 if (compose->account->name && *compose->account->name) {
5561 compose_convert_header
5562 (compose, buf, sizeof(buf), compose->account->name,
5563 strlen("From: "), TRUE);
5564 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5565 buf, compose->account->address) < 0);
5566 } else
5567 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5569 /* Subject */
5570 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5571 if (*entstr != '\0') {
5572 Xstrdup_a(str, entstr, return -1);
5573 g_strstrip(str);
5574 if (*str != '\0') {
5575 compose_convert_header(compose, buf, sizeof(buf), str,
5576 strlen("Subject: "), FALSE);
5577 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5581 /* Resent-Message-ID */
5582 if (compose->account->gen_msgid) {
5583 gchar *addr = prefs_account_generate_msgid(compose->account);
5584 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5585 if (compose->msgid)
5586 g_free(compose->msgid);
5587 compose->msgid = addr;
5588 } else {
5589 compose->msgid = NULL;
5592 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5593 return -1;
5595 /* separator between header and body */
5596 err |= (claws_fputs("\n", fp) == EOF);
5598 return (err ? -1:0);
5601 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5603 FILE *fp;
5604 size_t len;
5605 gchar *buf = NULL;
5606 gchar rewrite_buf[BUFFSIZE];
5607 int i = 0;
5608 gboolean skip = FALSE;
5609 gboolean err = FALSE;
5610 gchar *not_included[]={
5611 "Return-Path:", "Delivered-To:", "Received:",
5612 "Subject:", "X-UIDL:", "AF:",
5613 "NF:", "PS:", "SRH:",
5614 "SFN:", "DSR:", "MID:",
5615 "CFG:", "PT:", "S:",
5616 "RQ:", "SSV:", "NSV:",
5617 "SSH:", "R:", "MAID:",
5618 "NAID:", "RMID:", "FMID:",
5619 "SCF:", "RRCPT:", "NG:",
5620 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5621 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5622 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5623 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5624 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5625 NULL
5627 gint ret = 0;
5629 if ((fp = claws_fopen(compose->redirect_filename, "rb")) == NULL) {
5630 FILE_OP_ERROR(compose->redirect_filename, "claws_fopen");
5631 return -1;
5634 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5635 skip = FALSE;
5636 for (i = 0; not_included[i] != NULL; i++) {
5637 if (g_ascii_strncasecmp(buf, not_included[i],
5638 strlen(not_included[i])) == 0) {
5639 skip = TRUE;
5640 break;
5643 if (skip) {
5644 g_free(buf);
5645 buf = NULL;
5646 continue;
5648 if (claws_fputs(buf, fdest) == -1) {
5649 g_free(buf);
5650 buf = NULL;
5651 goto error;
5654 if (!prefs_common.redirect_keep_from) {
5655 if (g_ascii_strncasecmp(buf, "From:",
5656 strlen("From:")) == 0) {
5657 err |= (claws_fputs(" (by way of ", fdest) == EOF);
5658 if (compose->account->name
5659 && *compose->account->name) {
5660 gchar buffer[BUFFSIZE];
5662 compose_convert_header
5663 (compose, buffer, sizeof(buffer),
5664 compose->account->name,
5665 strlen("From: "),
5666 FALSE);
5667 err |= (fprintf(fdest, "%s <%s>",
5668 buffer,
5669 compose->account->address) < 0);
5670 } else
5671 err |= (fprintf(fdest, "%s",
5672 compose->account->address) < 0);
5673 err |= (claws_fputs(")", fdest) == EOF);
5677 g_free(buf);
5678 buf = NULL;
5679 if (claws_fputs("\n", fdest) == -1)
5680 goto error;
5683 if (err)
5684 goto error;
5686 if (compose_redirect_write_headers(compose, fdest))
5687 goto error;
5689 while ((len = claws_fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5690 if (claws_fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5691 goto error;
5694 claws_fclose(fp);
5696 return 0;
5698 error:
5699 claws_fclose(fp);
5701 return -1;
5704 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5706 GtkTextBuffer *buffer;
5707 GtkTextIter start, end, tmp;
5708 gchar *chars, *tmp_enc_file = NULL, *content;
5709 gchar *buf, *msg;
5710 const gchar *out_codeset;
5711 EncodingType encoding = ENC_UNKNOWN;
5712 MimeInfo *mimemsg, *mimetext;
5713 gint line;
5714 const gchar *src_codeset = CS_INTERNAL;
5715 gchar *from_addr = NULL;
5716 gchar *from_name = NULL;
5717 FolderItem *outbox;
5719 if (action == COMPOSE_WRITE_FOR_SEND) {
5720 attach_parts = TRUE;
5722 /* We're sending the message, generate a Message-ID
5723 * if necessary. */
5724 if (compose->msgid == NULL &&
5725 compose->account->gen_msgid) {
5726 compose->msgid = prefs_account_generate_msgid(compose->account);
5730 /* create message MimeInfo */
5731 mimemsg = procmime_mimeinfo_new();
5732 mimemsg->type = MIMETYPE_MESSAGE;
5733 mimemsg->subtype = g_strdup("rfc822");
5734 mimemsg->content = MIMECONTENT_MEM;
5735 mimemsg->tmp = TRUE; /* must free content later */
5736 mimemsg->data.mem = compose_get_header(compose);
5738 /* Create text part MimeInfo */
5739 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5740 gtk_text_buffer_get_end_iter(buffer, &end);
5741 tmp = end;
5743 /* We make sure that there is a newline at the end. */
5744 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5745 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5746 if (*chars != '\n') {
5747 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5749 g_free(chars);
5752 /* get all composed text */
5753 gtk_text_buffer_get_start_iter(buffer, &start);
5754 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5756 out_codeset = conv_get_charset_str(compose->out_encoding);
5758 if (!out_codeset && is_ascii_str(chars)) {
5759 out_codeset = CS_US_ASCII;
5760 } else if (prefs_common.outgoing_fallback_to_ascii &&
5761 is_ascii_str(chars)) {
5762 out_codeset = CS_US_ASCII;
5763 encoding = ENC_7BIT;
5766 if (!out_codeset) {
5767 gchar *test_conv_global_out = NULL;
5768 gchar *test_conv_reply = NULL;
5770 /* automatic mode. be automatic. */
5771 codeconv_set_strict(TRUE);
5773 out_codeset = conv_get_outgoing_charset_str();
5774 if (out_codeset) {
5775 debug_print("trying to convert to %s\n", out_codeset);
5776 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5779 if (!test_conv_global_out && compose->orig_charset
5780 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5781 out_codeset = compose->orig_charset;
5782 debug_print("failure; trying to convert to %s\n", out_codeset);
5783 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5786 if (!test_conv_global_out && !test_conv_reply) {
5787 /* we're lost */
5788 out_codeset = CS_INTERNAL;
5789 debug_print("failure; finally using %s\n", out_codeset);
5791 g_free(test_conv_global_out);
5792 g_free(test_conv_reply);
5793 codeconv_set_strict(FALSE);
5796 if (encoding == ENC_UNKNOWN) {
5797 if (prefs_common.encoding_method == CTE_BASE64)
5798 encoding = ENC_BASE64;
5799 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5800 encoding = ENC_QUOTED_PRINTABLE;
5801 else if (prefs_common.encoding_method == CTE_8BIT)
5802 encoding = ENC_8BIT;
5803 else
5804 encoding = procmime_get_encoding_for_charset(out_codeset);
5807 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5808 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5810 if (action == COMPOSE_WRITE_FOR_SEND) {
5811 codeconv_set_strict(TRUE);
5812 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5813 codeconv_set_strict(FALSE);
5815 if (!buf) {
5816 AlertValue aval;
5818 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5819 "to the specified %s charset.\n"
5820 "Send it as %s?"), out_codeset, src_codeset);
5821 aval = alertpanel_full(_("Error"), msg, NULL, _("_Cancel"),
5822 NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND, FALSE,
5823 NULL, ALERT_ERROR);
5824 g_free(msg);
5826 if (aval != G_ALERTALTERNATE) {
5827 g_free(chars);
5828 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5829 } else {
5830 buf = chars;
5831 out_codeset = src_codeset;
5832 chars = NULL;
5835 } else {
5836 buf = chars;
5837 out_codeset = src_codeset;
5838 chars = NULL;
5840 g_free(chars);
5842 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5843 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5844 strstr(buf, "\nFrom ") != NULL) {
5845 encoding = ENC_QUOTED_PRINTABLE;
5849 mimetext = procmime_mimeinfo_new();
5850 mimetext->content = MIMECONTENT_MEM;
5851 mimetext->tmp = TRUE; /* must free content later */
5852 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5853 * and free the data, which we need later. */
5854 mimetext->data.mem = g_strdup(buf);
5855 mimetext->type = MIMETYPE_TEXT;
5856 mimetext->subtype = g_strdup("plain");
5857 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5858 g_strdup(out_codeset));
5860 /* protect trailing spaces when signing message */
5861 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5862 privacy_system_can_sign(compose->privacy_system)) {
5863 encoding = ENC_QUOTED_PRINTABLE;
5866 debug_print("main text: %" G_GSIZE_FORMAT " bytes encoded as %s in %d\n",
5867 strlen(buf), out_codeset, encoding);
5869 /* check for line length limit */
5870 if (action == COMPOSE_WRITE_FOR_SEND &&
5871 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5872 check_line_length(buf, 1000, &line) < 0) {
5873 AlertValue aval;
5875 msg = g_strdup_printf
5876 (_("Line %d exceeds the line length limit (998 bytes).\n"
5877 "The contents of the message might be broken on the way to the delivery.\n"
5878 "\n"
5879 "Send it anyway?"), line + 1);
5880 aval = alertpanel(_("Warning"), msg, NULL, _("_Cancel"), NULL, _("_OK"),
5881 NULL, NULL, ALERTFOCUS_FIRST);
5882 g_free(msg);
5883 if (aval != G_ALERTALTERNATE) {
5884 g_free(buf);
5885 return COMPOSE_QUEUE_ERROR_NO_MSG;
5889 if (encoding != ENC_UNKNOWN)
5890 procmime_encode_content(mimetext, encoding);
5892 /* append attachment parts */
5893 if (compose_use_attach(compose) && attach_parts) {
5894 MimeInfo *mimempart;
5895 gchar *boundary = NULL;
5896 mimempart = procmime_mimeinfo_new();
5897 mimempart->content = MIMECONTENT_EMPTY;
5898 mimempart->type = MIMETYPE_MULTIPART;
5899 mimempart->subtype = g_strdup("mixed");
5901 do {
5902 g_free(boundary);
5903 boundary = generate_mime_boundary(NULL);
5904 } while (strstr(buf, boundary) != NULL);
5906 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5907 boundary);
5909 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5911 g_node_append(mimempart->node, mimetext->node);
5912 g_node_append(mimemsg->node, mimempart->node);
5914 if (compose_add_attachments(compose, mimempart) < 0)
5915 return COMPOSE_QUEUE_ERROR_NO_MSG;
5916 } else
5917 g_node_append(mimemsg->node, mimetext->node);
5919 g_free(buf);
5921 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5922 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5923 /* extract name and address */
5924 if (strstr(spec, " <") && strstr(spec, ">")) {
5925 from_addr = g_strdup(strrchr(spec, '<')+1);
5926 *(strrchr(from_addr, '>')) = '\0';
5927 from_name = g_strdup(spec);
5928 *(strrchr(from_name, '<')) = '\0';
5929 } else {
5930 from_name = NULL;
5931 from_addr = NULL;
5933 g_free(spec);
5935 /* sign message if sending */
5936 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5937 privacy_system_can_sign(compose->privacy_system))
5938 if (!privacy_sign(compose->privacy_system, mimemsg,
5939 compose->account, from_addr)) {
5940 g_free(from_name);
5941 g_free(from_addr);
5942 if (!privacy_peek_error())
5943 return COMPOSE_QUEUE_SIGNING_CANCELLED;
5944 else
5945 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5947 g_free(from_name);
5948 g_free(from_addr);
5950 if (compose->use_encryption) {
5951 if (compose->encdata != NULL &&
5952 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5954 /* First, write an unencrypted copy and save it to outbox, if
5955 * user wants that. */
5956 if (compose->account->save_encrypted_as_clear_text) {
5957 debug_print("saving sent message unencrypted...\n");
5958 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5959 if (tmpfp) {
5960 claws_fclose(tmpfp);
5962 /* fp now points to a file with headers written,
5963 * let's make a copy. */
5964 rewind(fp);
5965 content = file_read_stream_to_str(fp);
5967 str_write_to_file(content, tmp_enc_file, TRUE);
5968 g_free(content);
5970 /* Now write the unencrypted body. */
5971 if ((tmpfp = claws_fopen(tmp_enc_file, "a")) != NULL) {
5972 procmime_write_mimeinfo(mimemsg, tmpfp);
5973 claws_fclose(tmpfp);
5975 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5976 if (!outbox)
5977 outbox = folder_get_default_outbox();
5979 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5980 claws_unlink(tmp_enc_file);
5981 } else {
5982 g_warning("can't open file '%s'", tmp_enc_file);
5984 } else {
5985 g_warning("couldn't get tempfile");
5988 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5989 debug_print("Couldn't encrypt mime structure: %s.\n",
5990 privacy_get_error());
5991 if (tmp_enc_file)
5992 g_free(tmp_enc_file);
5993 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5997 if (tmp_enc_file)
5998 g_free(tmp_enc_file);
6000 procmime_write_mimeinfo(mimemsg, fp);
6002 procmime_mimeinfo_free_all(&mimemsg);
6004 return 0;
6007 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
6009 GtkTextBuffer *buffer;
6010 GtkTextIter start, end;
6011 FILE *fp;
6012 size_t len;
6013 gchar *chars, *tmp;
6015 if ((fp = claws_fopen(file, "wb")) == NULL) {
6016 FILE_OP_ERROR(file, "claws_fopen");
6017 return -1;
6020 /* chmod for security */
6021 if (change_file_mode_rw(fp, file) < 0) {
6022 FILE_OP_ERROR(file, "chmod");
6023 g_warning("can't change file mode");
6026 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
6027 gtk_text_buffer_get_start_iter(buffer, &start);
6028 gtk_text_buffer_get_end_iter(buffer, &end);
6029 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
6031 chars = conv_codeset_strdup
6032 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6034 g_free(tmp);
6035 if (!chars) {
6036 claws_fclose(fp);
6037 claws_unlink(file);
6038 return -1;
6040 /* write body */
6041 len = strlen(chars);
6042 if (claws_fwrite(chars, sizeof(gchar), len, fp) != len) {
6043 FILE_OP_ERROR(file, "claws_fwrite");
6044 g_free(chars);
6045 claws_fclose(fp);
6046 claws_unlink(file);
6047 return -1;
6050 g_free(chars);
6052 if (claws_safe_fclose(fp) == EOF) {
6053 FILE_OP_ERROR(file, "claws_fclose");
6054 claws_unlink(file);
6055 return -1;
6057 return 0;
6060 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6062 FolderItem *item;
6063 MsgInfo *msginfo = compose->targetinfo;
6065 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6066 if (!msginfo) return -1;
6068 if (!force && MSG_IS_LOCKED(msginfo->flags))
6069 return 0;
6071 item = msginfo->folder;
6072 cm_return_val_if_fail(item != NULL, -1);
6074 if (procmsg_msg_exist(msginfo) &&
6075 (folder_has_parent_of_type(item, F_QUEUE) ||
6076 folder_has_parent_of_type(item, F_DRAFT)
6077 || msginfo == compose->autosaved_draft)) {
6078 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6079 g_warning("can't remove the old message");
6080 return -1;
6081 } else {
6082 debug_print("removed reedit target %d\n", msginfo->msgnum);
6086 return 0;
6089 static void compose_remove_draft(Compose *compose)
6091 FolderItem *drafts;
6092 MsgInfo *msginfo = compose->targetinfo;
6093 drafts = account_get_special_folder(compose->account, F_DRAFT);
6095 if (procmsg_msg_exist(msginfo)) {
6096 folder_item_remove_msg(drafts, msginfo->msgnum);
6101 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6102 gboolean remove_reedit_target)
6104 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6107 static gboolean compose_warn_encryption(Compose *compose)
6109 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6110 AlertValue val = G_ALERTALTERNATE;
6112 if (warning == NULL)
6113 return TRUE;
6115 val = alertpanel_full(_("Encryption warning"), warning,
6116 NULL, _("_Cancel"), NULL, _("C_ontinue"), NULL, NULL,
6117 ALERTFOCUS_SECOND, TRUE, NULL, ALERT_WARNING);
6118 if (val & G_ALERTDISABLE) {
6119 val &= ~G_ALERTDISABLE;
6120 if (val == G_ALERTALTERNATE)
6121 privacy_inhibit_encrypt_warning(compose->privacy_system,
6122 TRUE);
6125 if (val == G_ALERTALTERNATE) {
6126 return TRUE;
6127 } else {
6128 return FALSE;
6132 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6133 gchar **msgpath, gboolean perform_checks,
6134 gboolean remove_reedit_target)
6136 FolderItem *queue;
6137 gchar *tmp;
6138 FILE *fp;
6139 GSList *cur;
6140 gint num;
6141 PrefsAccount *mailac = NULL, *newsac = NULL;
6142 gboolean err = FALSE;
6144 debug_print("queueing message...\n");
6145 cm_return_val_if_fail(compose->account != NULL, -1);
6147 if (compose_check_entries(compose, perform_checks) == FALSE) {
6148 if (compose->batch) {
6149 gtk_widget_show_all(compose->window);
6151 return COMPOSE_QUEUE_ERROR_NO_MSG;
6154 if (!compose->to_list && !compose->newsgroup_list) {
6155 g_warning("can't get recipient list");
6156 return COMPOSE_QUEUE_ERROR_NO_MSG;
6159 if (compose->to_list) {
6160 if (compose->account->protocol != A_NNTP)
6161 mailac = compose->account;
6162 else if (cur_account && cur_account->protocol != A_NNTP)
6163 mailac = cur_account;
6164 else if (!(mailac = compose_current_mail_account())) {
6165 alertpanel_error(_("No account for sending mails available!"));
6166 return COMPOSE_QUEUE_ERROR_NO_MSG;
6170 if (compose->newsgroup_list) {
6171 if (compose->account->protocol == A_NNTP)
6172 newsac = compose->account;
6173 else {
6174 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6175 return COMPOSE_QUEUE_ERROR_NO_MSG;
6179 /* write queue header */
6180 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6181 G_DIR_SEPARATOR, compose, (guint) rand());
6182 debug_print("queuing to %s\n", tmp);
6183 if ((fp = claws_fopen(tmp, "w+b")) == NULL) {
6184 FILE_OP_ERROR(tmp, "claws_fopen");
6185 g_free(tmp);
6186 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6189 if (change_file_mode_rw(fp, tmp) < 0) {
6190 FILE_OP_ERROR(tmp, "chmod");
6191 g_warning("can't change file mode");
6194 /* queueing variables */
6195 err |= (fprintf(fp, "AF:\n") < 0);
6196 err |= (fprintf(fp, "NF:0\n") < 0);
6197 err |= (fprintf(fp, "PS:10\n") < 0);
6198 err |= (fprintf(fp, "SRH:1\n") < 0);
6199 err |= (fprintf(fp, "SFN:\n") < 0);
6200 err |= (fprintf(fp, "DSR:\n") < 0);
6201 if (compose->msgid)
6202 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6203 else
6204 err |= (fprintf(fp, "MID:\n") < 0);
6205 err |= (fprintf(fp, "CFG:\n") < 0);
6206 err |= (fprintf(fp, "PT:0\n") < 0);
6207 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6208 err |= (fprintf(fp, "RQ:\n") < 0);
6209 if (mailac)
6210 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6211 else
6212 err |= (fprintf(fp, "SSV:\n") < 0);
6213 if (newsac)
6214 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6215 else
6216 err |= (fprintf(fp, "NSV:\n") < 0);
6217 err |= (fprintf(fp, "SSH:\n") < 0);
6218 /* write recipient list */
6219 if (compose->to_list) {
6220 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6221 for (cur = compose->to_list->next; cur != NULL;
6222 cur = cur->next)
6223 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6224 err |= (fprintf(fp, "\n") < 0);
6226 /* write newsgroup list */
6227 if (compose->newsgroup_list) {
6228 err |= (fprintf(fp, "NG:") < 0);
6229 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6230 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6231 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6232 err |= (fprintf(fp, "\n") < 0);
6234 /* account IDs */
6235 if (mailac)
6236 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6237 if (newsac)
6238 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6241 if (compose->privacy_system != NULL) {
6242 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6243 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6244 if (compose->use_encryption) {
6245 if (!compose_warn_encryption(compose)) {
6246 claws_fclose(fp);
6247 claws_unlink(tmp);
6248 g_free(tmp);
6249 return COMPOSE_QUEUE_ERROR_NO_MSG;
6251 if (mailac && mailac->encrypt_to_self) {
6252 GSList *tmp_list = g_slist_copy(compose->to_list);
6253 tmp_list = g_slist_append(tmp_list, compose->account->address);
6254 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6255 g_slist_free(tmp_list);
6256 } else {
6257 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6259 if (compose->encdata != NULL) {
6260 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6261 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6262 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6263 compose->encdata) < 0);
6264 } /* else we finally dont want to encrypt */
6265 } else {
6266 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6267 /* and if encdata was null, it means there's been a problem in
6268 * key selection */
6269 if (err == TRUE)
6270 g_warning("failed to write queue message");
6271 claws_fclose(fp);
6272 claws_unlink(tmp);
6273 g_free(tmp);
6274 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6279 /* Save copy folder */
6280 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6281 gchar *savefolderid;
6283 savefolderid = compose_get_save_to(compose);
6284 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6285 g_free(savefolderid);
6287 /* Save copy folder */
6288 if (compose->return_receipt) {
6289 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6291 /* Message-ID of message replying to */
6292 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6293 gchar *folderid = NULL;
6295 if (compose->replyinfo->folder)
6296 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6297 if (folderid == NULL)
6298 folderid = g_strdup("NULL");
6300 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6301 g_free(folderid);
6303 /* Message-ID of message forwarding to */
6304 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6305 gchar *folderid = NULL;
6307 if (compose->fwdinfo->folder)
6308 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6309 if (folderid == NULL)
6310 folderid = g_strdup("NULL");
6312 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6313 g_free(folderid);
6316 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6317 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6319 /* end of headers */
6320 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6322 if (compose->redirect_filename != NULL) {
6323 if (compose_redirect_write_to_file(compose, fp) < 0) {
6324 claws_fclose(fp);
6325 claws_unlink(tmp);
6326 g_free(tmp);
6327 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6329 } else {
6330 gint result = 0;
6331 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6332 claws_fclose(fp);
6333 claws_unlink(tmp);
6334 g_free(tmp);
6335 return result;
6338 if (err == TRUE) {
6339 g_warning("failed to write queue message");
6340 claws_fclose(fp);
6341 claws_unlink(tmp);
6342 g_free(tmp);
6343 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6345 if (claws_safe_fclose(fp) == EOF) {
6346 FILE_OP_ERROR(tmp, "claws_fclose");
6347 claws_unlink(tmp);
6348 g_free(tmp);
6349 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6352 if (item && *item) {
6353 queue = *item;
6354 } else {
6355 queue = account_get_special_folder(compose->account, F_QUEUE);
6357 if (!queue) {
6358 g_warning("can't find queue folder");
6359 claws_unlink(tmp);
6360 g_free(tmp);
6361 return COMPOSE_QUEUE_ERROR_NO_MSG;
6363 folder_item_scan(queue);
6364 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6365 g_warning("can't queue the message");
6366 claws_unlink(tmp);
6367 g_free(tmp);
6368 return COMPOSE_QUEUE_ERROR_NO_MSG;
6371 if (msgpath == NULL) {
6372 claws_unlink(tmp);
6373 g_free(tmp);
6374 } else
6375 *msgpath = tmp;
6377 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6378 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6379 if (mi) {
6380 procmsg_msginfo_change_flags(mi,
6381 compose->targetinfo->flags.perm_flags,
6382 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6383 0, 0);
6385 g_slist_free(mi->tags);
6386 mi->tags = g_slist_copy(compose->targetinfo->tags);
6387 procmsg_msginfo_free(&mi);
6391 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6392 compose_remove_reedit_target(compose, FALSE);
6395 if ((msgnum != NULL) && (item != NULL)) {
6396 *msgnum = num;
6397 *item = queue;
6400 return COMPOSE_QUEUE_SUCCESS;
6403 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6405 AttachInfo *ainfo;
6406 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6407 MimeInfo *mimepart;
6408 #ifdef G_OS_WIN32
6409 GFile *f;
6410 GFileInfo *fi;
6411 GError *error = NULL;
6412 #else
6413 GStatBuf statbuf;
6414 #endif
6415 goffset size;
6416 gchar *type, *subtype;
6417 GtkTreeModel *model;
6418 GtkTreeIter iter;
6420 model = gtk_tree_view_get_model(tree_view);
6422 if (!gtk_tree_model_get_iter_first(model, &iter))
6423 return 0;
6424 do {
6425 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6427 if (!is_file_exist(ainfo->file)) {
6428 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6429 AlertValue val = alertpanel_full(_("Warning"), msg,
6430 NULL, _("Cancel sending"),
6431 NULL, _("Ignore attachment"), NULL, NULL,
6432 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6433 g_free(msg);
6434 if (val == G_ALERTDEFAULT) {
6435 return -1;
6437 continue;
6439 #ifdef G_OS_WIN32
6440 f = g_file_new_for_path(ainfo->file);
6441 fi = g_file_query_info(f, "standard::size",
6442 G_FILE_QUERY_INFO_NONE, NULL, &error);
6443 if (error != NULL) {
6444 g_warning(error->message);
6445 g_error_free(error);
6446 g_object_unref(f);
6447 return -1;
6449 size = g_file_info_get_size(fi);
6450 g_object_unref(fi);
6451 g_object_unref(f);
6452 #else
6453 if (g_stat(ainfo->file, &statbuf) < 0)
6454 return -1;
6455 size = statbuf.st_size;
6456 #endif
6458 mimepart = procmime_mimeinfo_new();
6459 mimepart->content = MIMECONTENT_FILE;
6460 mimepart->data.filename = g_strdup(ainfo->file);
6461 mimepart->tmp = FALSE; /* or we destroy our attachment */
6462 mimepart->offset = 0;
6463 mimepart->length = size;
6465 type = g_strdup(ainfo->content_type);
6467 if (!strchr(type, '/')) {
6468 g_free(type);
6469 type = g_strdup("application/octet-stream");
6472 subtype = strchr(type, '/') + 1;
6473 *(subtype - 1) = '\0';
6474 mimepart->type = procmime_get_media_type(type);
6475 mimepart->subtype = g_strdup(subtype);
6476 g_free(type);
6478 if (mimepart->type == MIMETYPE_MESSAGE &&
6479 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6480 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6481 } else if (mimepart->type == MIMETYPE_TEXT) {
6482 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6483 /* Text parts with no name come from multipart/alternative
6484 * forwards. Make sure the recipient won't look at the
6485 * original HTML part by mistake. */
6486 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6487 ainfo->name = g_strdup_printf(_("Original %s part"),
6488 mimepart->subtype);
6490 if (ainfo->charset)
6491 g_hash_table_insert(mimepart->typeparameters,
6492 g_strdup("charset"), g_strdup(ainfo->charset));
6494 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6495 if (mimepart->type == MIMETYPE_APPLICATION &&
6496 !g_strcmp0(mimepart->subtype, "octet-stream"))
6497 g_hash_table_insert(mimepart->typeparameters,
6498 g_strdup("name"), g_strdup(ainfo->name));
6499 g_hash_table_insert(mimepart->dispositionparameters,
6500 g_strdup("filename"), g_strdup(ainfo->name));
6501 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6504 if (mimepart->type == MIMETYPE_MESSAGE
6505 || mimepart->type == MIMETYPE_MULTIPART)
6506 ainfo->encoding = ENC_BINARY;
6507 else if (compose->use_signing || compose->fwdinfo != NULL) {
6508 if (ainfo->encoding == ENC_7BIT)
6509 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6510 else if (ainfo->encoding == ENC_8BIT)
6511 ainfo->encoding = ENC_BASE64;
6514 procmime_encode_content(mimepart, ainfo->encoding);
6516 g_node_append(parent->node, mimepart->node);
6517 } while (gtk_tree_model_iter_next(model, &iter));
6519 return 0;
6522 static gchar *compose_quote_list_of_addresses(gchar *str)
6524 GSList *list = NULL, *item = NULL;
6525 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6527 list = address_list_append_with_comments(list, str);
6528 for (item = list; item != NULL; item = item->next) {
6529 gchar *spec = item->data;
6530 gchar *endofname = strstr(spec, " <");
6531 if (endofname != NULL) {
6532 gchar * qqname;
6533 *endofname = '\0';
6534 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6535 qqname = escape_internal_quotes(qname, '"');
6536 *endofname = ' ';
6537 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6538 gchar *addr = g_strdup(endofname);
6539 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6540 faddr = g_strconcat(name, addr, NULL);
6541 g_free(name);
6542 g_free(addr);
6543 debug_print("new auto-quoted address: '%s'\n", faddr);
6546 if (result == NULL)
6547 result = g_strdup((faddr != NULL)? faddr: spec);
6548 else {
6549 gchar *tmp = g_strconcat(result,
6550 ", ",
6551 (faddr != NULL)? faddr: spec,
6552 NULL);
6553 g_free(result);
6554 result = tmp;
6556 if (faddr != NULL) {
6557 g_free(faddr);
6558 faddr = NULL;
6561 slist_free_strings_full(list);
6563 return result;
6566 #define IS_IN_CUSTOM_HEADER(header) \
6567 (compose->account->add_customhdr && \
6568 custom_header_find(compose->account->customhdr_list, header) != NULL)
6570 static const gchar *compose_untranslated_header_name(gchar *header_name)
6572 /* return the untranslated header name, if header_name is a known
6573 header name, in either its translated or untranslated form, with
6574 or without trailing colon. otherwise, returns header_name. */
6575 gchar *translated_header_name;
6576 gchar *translated_header_name_wcolon;
6577 const gchar *untranslated_header_name;
6578 const gchar *untranslated_header_name_wcolon;
6579 gint i;
6581 cm_return_val_if_fail(header_name != NULL, NULL);
6583 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6584 untranslated_header_name = HEADERS[i].header_name;
6585 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6587 translated_header_name = gettext(untranslated_header_name);
6588 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6590 if (!strcmp(header_name, untranslated_header_name) ||
6591 !strcmp(header_name, translated_header_name)) {
6592 return untranslated_header_name;
6593 } else {
6594 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6595 !strcmp(header_name, translated_header_name_wcolon)) {
6596 return untranslated_header_name_wcolon;
6600 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6601 return header_name;
6604 static void compose_add_headerfield_from_headerlist(Compose *compose,
6605 GString *header,
6606 const gchar *fieldname,
6607 const gchar *seperator)
6609 gchar *str, *fieldname_w_colon;
6610 gboolean add_field = FALSE;
6611 GSList *list;
6612 ComposeHeaderEntry *headerentry;
6613 const gchar *headerentryname;
6614 const gchar *trans_fieldname;
6615 GString *fieldstr;
6617 if (IS_IN_CUSTOM_HEADER(fieldname))
6618 return;
6620 debug_print("Adding %s-fields\n", fieldname);
6622 fieldstr = g_string_sized_new(64);
6624 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6625 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6627 for (list = compose->header_list; list; list = list->next) {
6628 headerentry = ((ComposeHeaderEntry *)list->data);
6629 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6631 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6632 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6633 g_strstrip(ustr);
6634 str = compose_quote_list_of_addresses(ustr);
6635 g_free(ustr);
6636 if (str != NULL && str[0] != '\0') {
6637 if (add_field)
6638 g_string_append(fieldstr, seperator);
6639 g_string_append(fieldstr, str);
6640 add_field = TRUE;
6642 g_free(str);
6645 if (add_field) {
6646 gchar *buf;
6648 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6649 compose_convert_header
6650 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6651 strlen(fieldname) + 2, TRUE);
6652 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6653 g_free(buf);
6656 g_free(fieldname_w_colon);
6657 g_string_free(fieldstr, TRUE);
6659 return;
6662 static gchar *compose_get_manual_headers_info(Compose *compose)
6664 GString *sh_header = g_string_new(" ");
6665 GSList *list;
6666 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6668 for (list = compose->header_list; list; list = list->next) {
6669 ComposeHeaderEntry *headerentry;
6670 gchar *tmp;
6671 gchar *headername;
6672 gchar *headername_wcolon;
6673 const gchar *headername_trans;
6674 gchar **string;
6675 gboolean standard_header = FALSE;
6677 headerentry = ((ComposeHeaderEntry *)list->data);
6679 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6680 g_strstrip(tmp);
6681 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6682 g_free(tmp);
6683 continue;
6686 if (!strstr(tmp, ":")) {
6687 headername_wcolon = g_strconcat(tmp, ":", NULL);
6688 headername = g_strdup(tmp);
6689 } else {
6690 headername_wcolon = g_strdup(tmp);
6691 headername = g_strdup(strtok(tmp, ":"));
6693 g_free(tmp);
6695 string = std_headers;
6696 while (*string != NULL) {
6697 headername_trans = prefs_common_translated_header_name(*string);
6698 if (!strcmp(headername_trans, headername_wcolon))
6699 standard_header = TRUE;
6700 string++;
6702 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6703 g_string_append_printf(sh_header, "%s ", headername);
6704 g_free(headername);
6705 g_free(headername_wcolon);
6707 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6708 return g_string_free(sh_header, FALSE);
6711 static gchar *compose_get_header(Compose *compose)
6713 gchar date[RFC822_DATE_BUFFSIZE];
6714 gchar buf[BUFFSIZE];
6715 const gchar *entry_str;
6716 gchar *str;
6717 gchar *name;
6718 GSList *list;
6719 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6720 GString *header;
6721 gchar *from_name = NULL, *from_address = NULL;
6722 gchar *tmp;
6724 cm_return_val_if_fail(compose->account != NULL, NULL);
6725 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6727 header = g_string_sized_new(64);
6729 /* Date */
6730 if (prefs_common.hide_timezone)
6731 get_rfc822_date_hide_tz(date, sizeof(date));
6732 else
6733 get_rfc822_date(date, sizeof(date));
6734 g_string_append_printf(header, "Date: %s\n", date);
6736 /* From */
6738 if (compose->account->name && *compose->account->name) {
6739 gchar *buf;
6740 QUOTE_IF_REQUIRED(buf, compose->account->name);
6741 tmp = g_strdup_printf("%s <%s>",
6742 buf, compose->account->address);
6743 } else {
6744 tmp = g_strdup_printf("%s",
6745 compose->account->address);
6747 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6748 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6749 /* use default */
6750 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6751 from_address = g_strdup(compose->account->address);
6752 } else {
6753 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6754 /* extract name and address */
6755 if (strstr(spec, " <") && strstr(spec, ">")) {
6756 from_address = g_strdup(strrchr(spec, '<')+1);
6757 *(strrchr(from_address, '>')) = '\0';
6758 from_name = g_strdup(spec);
6759 *(strrchr(from_name, '<')) = '\0';
6760 } else {
6761 from_name = NULL;
6762 from_address = g_strdup(spec);
6764 g_free(spec);
6766 g_free(tmp);
6769 if (from_name && *from_name) {
6770 gchar *qname;
6771 compose_convert_header
6772 (compose, buf, sizeof(buf), from_name,
6773 strlen("From: "), TRUE);
6774 QUOTE_IF_REQUIRED(name, buf);
6775 qname = escape_internal_quotes(name, '"');
6777 g_string_append_printf(header, "From: %s <%s>\n",
6778 qname, from_address);
6779 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6780 compose->return_receipt) {
6781 compose_convert_header(compose, buf, sizeof(buf), from_name,
6782 strlen("Disposition-Notification-To: "),
6783 TRUE);
6784 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6786 if (qname != name)
6787 g_free(qname);
6788 } else {
6789 g_string_append_printf(header, "From: %s\n", from_address);
6790 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6791 compose->return_receipt)
6792 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6795 g_free(from_name);
6796 g_free(from_address);
6798 /* To */
6799 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6801 /* Newsgroups */
6802 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6804 /* Cc */
6805 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6807 /* Bcc */
6809 * If this account is a NNTP account remove Bcc header from
6810 * message body since it otherwise will be publicly shown
6812 if (compose->account->protocol != A_NNTP)
6813 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6815 /* Subject */
6816 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6818 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6819 g_strstrip(str);
6820 if (*str != '\0') {
6821 compose_convert_header(compose, buf, sizeof(buf), str,
6822 strlen("Subject: "), FALSE);
6823 g_string_append_printf(header, "Subject: %s\n", buf);
6826 g_free(str);
6828 /* Message-ID */
6829 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6830 g_string_append_printf(header, "Message-ID: <%s>\n",
6831 compose->msgid);
6834 if (compose->remove_references == FALSE) {
6835 /* In-Reply-To */
6836 if (compose->inreplyto && compose->to_list)
6837 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6839 /* References */
6840 if (compose->references)
6841 g_string_append_printf(header, "References: %s\n", compose->references);
6844 /* Followup-To */
6845 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6847 /* Reply-To */
6848 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6850 /* Organization */
6851 if (compose->account->organization &&
6852 strlen(compose->account->organization) &&
6853 !IS_IN_CUSTOM_HEADER("Organization")) {
6854 compose_convert_header(compose, buf, sizeof(buf),
6855 compose->account->organization,
6856 strlen("Organization: "), FALSE);
6857 g_string_append_printf(header, "Organization: %s\n", buf);
6860 /* Program version and system info */
6861 if (compose->account->gen_xmailer &&
6862 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6863 !compose->newsgroup_list) {
6864 g_string_append_printf(header, "X-Mailer: %s (GTK %d.%d.%d; %s)\n",
6865 prog_version,
6866 gtk_major_version, gtk_minor_version, gtk_micro_version,
6867 TARGET_ALIAS);
6869 if (compose->account->gen_xmailer &&
6870 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6871 g_string_append_printf(header, "X-Newsreader: %s (GTK %d.%d.%d; %s)\n",
6872 prog_version,
6873 gtk_major_version, gtk_minor_version, gtk_micro_version,
6874 TARGET_ALIAS);
6877 /* custom headers */
6878 if (compose->account->add_customhdr) {
6879 GSList *cur;
6881 for (cur = compose->account->customhdr_list; cur != NULL;
6882 cur = cur->next) {
6883 CustomHeader *chdr = (CustomHeader *)cur->data;
6885 if (custom_header_is_allowed(chdr->name)
6886 && chdr->value != NULL
6887 && *(chdr->value) != '\0') {
6888 compose_convert_header
6889 (compose, buf, sizeof(buf),
6890 chdr->value,
6891 strlen(chdr->name) + 2, FALSE);
6892 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6897 /* Automatic Faces and X-Faces */
6898 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6899 g_string_append_printf(header, "X-Face: %s\n", buf);
6901 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6902 g_string_append_printf(header, "X-Face: %s\n", buf);
6904 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6905 g_string_append_printf(header, "Face: %s\n", buf);
6907 else if (get_default_face (buf, sizeof(buf)) == 0) {
6908 g_string_append_printf(header, "Face: %s\n", buf);
6911 /* PRIORITY */
6912 switch (compose->priority) {
6913 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6914 "X-Priority: 1 (Highest)\n");
6915 break;
6916 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6917 "X-Priority: 2 (High)\n");
6918 break;
6919 case PRIORITY_NORMAL: break;
6920 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6921 "X-Priority: 4 (Low)\n");
6922 break;
6923 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6924 "X-Priority: 5 (Lowest)\n");
6925 break;
6926 default: debug_print("compose: priority unknown : %d\n",
6927 compose->priority);
6930 /* get special headers */
6931 for (list = compose->header_list; list; list = list->next) {
6932 ComposeHeaderEntry *headerentry;
6933 gchar *tmp;
6934 gchar *headername;
6935 gchar *headername_wcolon;
6936 const gchar *headername_trans;
6937 gchar *headervalue;
6938 gchar **string;
6939 gboolean standard_header = FALSE;
6941 headerentry = ((ComposeHeaderEntry *)list->data);
6943 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6944 g_strstrip(tmp);
6945 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6946 g_free(tmp);
6947 continue;
6950 if (!strstr(tmp, ":")) {
6951 headername_wcolon = g_strconcat(tmp, ":", NULL);
6952 headername = g_strdup(tmp);
6953 } else {
6954 headername_wcolon = g_strdup(tmp);
6955 headername = g_strdup(strtok(tmp, ":"));
6957 g_free(tmp);
6959 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6960 Xstrdup_a(headervalue, entry_str, {
6961 g_free(headername);
6962 g_free(headername_wcolon);
6963 g_string_free(header, TRUE);
6964 return NULL;
6966 subst_char(headervalue, '\r', ' ');
6967 subst_char(headervalue, '\n', ' ');
6968 g_strstrip(headervalue);
6969 if (*headervalue != '\0') {
6970 string = std_headers;
6971 while (*string != NULL && !standard_header) {
6972 headername_trans = prefs_common_translated_header_name(*string);
6973 /* support mixed translated and untranslated headers */
6974 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6975 standard_header = TRUE;
6976 string++;
6978 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6979 /* store untranslated header name */
6980 g_string_append_printf(header, "%s %s\n",
6981 compose_untranslated_header_name(headername_wcolon), headervalue);
6984 g_free(headername);
6985 g_free(headername_wcolon);
6988 str = header->str;
6989 g_string_free(header, FALSE);
6991 return str;
6994 #undef IS_IN_CUSTOM_HEADER
6996 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6997 gint header_len, gboolean addr_field)
6999 gchar *tmpstr = NULL;
7000 const gchar *out_codeset = NULL;
7002 cm_return_if_fail(src != NULL);
7003 cm_return_if_fail(dest != NULL);
7005 if (len < 1) return;
7007 tmpstr = g_strdup(src);
7009 subst_char(tmpstr, '\n', ' ');
7010 subst_char(tmpstr, '\r', ' ');
7011 g_strchomp(tmpstr);
7013 if (!g_utf8_validate(tmpstr, -1, NULL)) {
7014 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
7015 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
7016 g_free(tmpstr);
7017 tmpstr = mybuf;
7020 codeconv_set_strict(TRUE);
7021 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7022 conv_get_charset_str(compose->out_encoding));
7023 codeconv_set_strict(FALSE);
7025 if (!dest || *dest == '\0') {
7026 gchar *test_conv_global_out = NULL;
7027 gchar *test_conv_reply = NULL;
7029 /* automatic mode. be automatic. */
7030 codeconv_set_strict(TRUE);
7032 out_codeset = conv_get_outgoing_charset_str();
7033 if (out_codeset) {
7034 debug_print("trying to convert to %s\n", out_codeset);
7035 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7038 if (!test_conv_global_out && compose->orig_charset
7039 && strcmp(compose->orig_charset, CS_US_ASCII)) {
7040 out_codeset = compose->orig_charset;
7041 debug_print("failure; trying to convert to %s\n", out_codeset);
7042 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7045 if (!test_conv_global_out && !test_conv_reply) {
7046 /* we're lost */
7047 out_codeset = CS_INTERNAL;
7048 debug_print("finally using %s\n", out_codeset);
7050 g_free(test_conv_global_out);
7051 g_free(test_conv_reply);
7052 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7053 out_codeset);
7054 codeconv_set_strict(FALSE);
7056 g_free(tmpstr);
7059 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7061 gchar *address;
7063 cm_return_if_fail(user_data != NULL);
7065 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7066 g_strstrip(address);
7067 if (*address != '\0') {
7068 gchar *name = procheader_get_fromname(address);
7069 extract_address(address);
7070 #ifndef USE_ALT_ADDRBOOK
7071 addressbook_add_contact(name, address, NULL, NULL);
7072 #else
7073 debug_print("%s: %s\n", name, address);
7074 if (addressadd_selection(name, address, NULL, NULL)) {
7075 debug_print( "addressbook_add_contact - added\n" );
7077 #endif
7079 g_free(address);
7082 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7084 GtkWidget *menuitem;
7085 gchar *address;
7087 cm_return_if_fail(menu != NULL);
7088 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7090 menuitem = gtk_separator_menu_item_new();
7091 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7092 gtk_widget_show(menuitem);
7094 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7095 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7097 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7098 g_strstrip(address);
7099 if (*address == '\0') {
7100 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7103 g_signal_connect(G_OBJECT(menuitem), "activate",
7104 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7105 gtk_widget_show(menuitem);
7108 void compose_add_extra_header(gchar *header, GtkListStore *model)
7110 GtkTreeIter iter;
7111 if (strcmp(header, "")) {
7112 COMBOBOX_ADD(model, header, COMPOSE_TO);
7116 void compose_add_extra_header_entries(GtkListStore *model)
7118 FILE *exh;
7119 gchar *exhrc;
7120 gchar buf[BUFFSIZE];
7121 gint lastc;
7123 if (extra_headers == NULL) {
7124 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7125 if ((exh = claws_fopen(exhrc, "rb")) == NULL) {
7126 debug_print("extra headers file not found\n");
7127 goto extra_headers_done;
7129 while (claws_fgets(buf, BUFFSIZE, exh) != NULL) {
7130 lastc = strlen(buf) - 1; /* remove trailing control chars */
7131 while (lastc >= 0 && buf[lastc] != ':')
7132 buf[lastc--] = '\0';
7133 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7134 buf[lastc] = '\0'; /* remove trailing : for comparison */
7135 if (custom_header_is_allowed(buf)) {
7136 buf[lastc] = ':';
7137 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7139 else
7140 g_message("disallowed extra header line: %s\n", buf);
7142 else {
7143 if (buf[0] != '#')
7144 g_message("invalid extra header line: %s\n", buf);
7147 claws_fclose(exh);
7148 extra_headers_done:
7149 g_free(exhrc);
7150 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7151 extra_headers = g_slist_reverse(extra_headers);
7153 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7156 #ifdef USE_LDAP
7157 static void _ldap_srv_func(gpointer data, gpointer user_data)
7159 LdapServer *server = (LdapServer *)data;
7160 gboolean *enable = (gboolean *)user_data;
7162 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7163 server->searchFlag = *enable;
7165 #endif
7167 static void compose_create_header_entry(Compose *compose)
7169 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7171 GtkWidget *combo;
7172 GtkWidget *entry;
7173 GtkWidget *button;
7174 GtkWidget *hbox;
7175 gchar **string;
7176 const gchar *header = NULL;
7177 ComposeHeaderEntry *headerentry;
7178 gboolean standard_header = FALSE;
7179 GtkListStore *model;
7180 GtkTreeIter iter;
7182 headerentry = g_new0(ComposeHeaderEntry, 1);
7184 /* Combo box model */
7185 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7186 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7187 COMPOSE_TO);
7188 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7189 COMPOSE_CC);
7190 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7191 COMPOSE_BCC);
7192 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7193 COMPOSE_NEWSGROUPS);
7194 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7195 COMPOSE_REPLYTO);
7196 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7197 COMPOSE_FOLLOWUPTO);
7198 compose_add_extra_header_entries(model);
7200 /* Combo box */
7201 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7202 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7203 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7204 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7205 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7206 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7207 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7208 G_CALLBACK(compose_grab_focus_cb), compose);
7209 gtk_widget_show(combo);
7211 gtk_grid_attach(GTK_GRID(compose->header_table), combo, 0, compose->header_nextrow,
7212 1, 1);
7213 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7214 const gchar *last_header_entry = gtk_entry_get_text(
7215 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7216 string = headers;
7217 while (*string != NULL) {
7218 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7219 standard_header = TRUE;
7220 string++;
7222 if (standard_header)
7223 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7225 if (!compose->header_last || !standard_header) {
7226 switch(compose->account->protocol) {
7227 case A_NNTP:
7228 header = prefs_common_translated_header_name("Newsgroups:");
7229 break;
7230 default:
7231 header = prefs_common_translated_header_name("To:");
7232 break;
7235 if (header)
7236 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7238 gtk_editable_set_editable(
7239 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7240 prefs_common.type_any_header);
7242 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7243 G_CALLBACK(compose_grab_focus_cb), compose);
7245 /* Entry field with cleanup button */
7246 button = gtk_button_new_from_icon_name("edit-clear", GTK_ICON_SIZE_MENU);
7247 gtk_widget_show(button);
7248 CLAWS_SET_TIP(button,
7249 _("Delete entry contents"));
7250 entry = gtk_entry_new();
7251 gtk_widget_show(entry);
7252 CLAWS_SET_TIP(entry,
7253 _("Use <tab> to autocomplete from addressbook"));
7254 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
7255 gtk_widget_show(hbox);
7256 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7257 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7258 gtk_grid_attach(GTK_GRID(compose->header_table), hbox, 1, compose->header_nextrow,
7259 1, 1);
7260 gtk_widget_set_hexpand(hbox, TRUE);
7261 gtk_widget_set_halign(hbox, GTK_ALIGN_FILL);
7263 g_signal_connect(G_OBJECT(entry), "key-press-event",
7264 G_CALLBACK(compose_headerentry_key_press_event_cb),
7265 headerentry);
7266 g_signal_connect(G_OBJECT(entry), "changed",
7267 G_CALLBACK(compose_headerentry_changed_cb),
7268 headerentry);
7269 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7270 G_CALLBACK(compose_grab_focus_cb), compose);
7272 g_signal_connect(G_OBJECT(button), "clicked",
7273 G_CALLBACK(compose_headerentry_button_clicked_cb),
7274 headerentry);
7276 /* email dnd */
7277 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7278 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7279 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7280 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7281 G_CALLBACK(compose_header_drag_received_cb),
7282 entry);
7283 g_signal_connect(G_OBJECT(entry), "drag-drop",
7284 G_CALLBACK(compose_drag_drop),
7285 compose);
7286 g_signal_connect(G_OBJECT(entry), "populate-popup",
7287 G_CALLBACK(compose_entry_popup_extend),
7288 NULL);
7290 #ifdef USE_LDAP
7291 #ifndef PASSWORD_CRYPTO_OLD
7292 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7293 if (pwd_servers != NULL && primary_passphrase() == NULL) {
7294 gboolean enable = FALSE;
7295 debug_print("Primary passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7296 /* Temporarily disable password-protected LDAP servers,
7297 * because user did not provide a primary passphrase.
7298 * We can safely enable searchFlag on all servers in this list
7299 * later, since addrindex_get_password_protected_ldap_servers()
7300 * includes servers which have it enabled initially. */
7301 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7302 compose->passworded_ldap_servers = pwd_servers;
7304 #endif /* PASSWORD_CRYPTO_OLD */
7305 #endif /* USE_LDAP */
7307 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7309 headerentry->compose = compose;
7310 headerentry->combo = combo;
7311 headerentry->entry = entry;
7312 headerentry->button = button;
7313 headerentry->hbox = hbox;
7314 headerentry->headernum = compose->header_nextrow;
7315 headerentry->type = PREF_NONE;
7317 compose->header_nextrow++;
7318 compose->header_last = headerentry;
7319 compose->header_list =
7320 g_slist_append(compose->header_list,
7321 headerentry);
7324 static void compose_add_header_entry(Compose *compose, const gchar *header,
7325 gchar *text, ComposePrefType pref_type)
7327 ComposeHeaderEntry *last_header = compose->header_last;
7328 gchar *tmp = g_strdup(text), *email;
7329 gboolean replyto_hdr;
7331 replyto_hdr = (!strcasecmp(header,
7332 prefs_common_translated_header_name("Reply-To:")) ||
7333 !strcasecmp(header,
7334 prefs_common_translated_header_name("Followup-To:")) ||
7335 !strcasecmp(header,
7336 prefs_common_translated_header_name("In-Reply-To:")));
7338 extract_address(tmp);
7339 email = g_utf8_strdown(tmp, -1);
7341 if (replyto_hdr == FALSE &&
7342 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7344 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7345 header, text, (gint) pref_type);
7346 g_free(email);
7347 g_free(tmp);
7348 return;
7351 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7352 gtk_entry_set_text(GTK_ENTRY(
7353 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7354 else
7355 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7356 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7357 last_header->type = pref_type;
7359 if (replyto_hdr == FALSE)
7360 g_hash_table_insert(compose->email_hashtable, email,
7361 GUINT_TO_POINTER(1));
7362 else
7363 g_free(email);
7365 g_free(tmp);
7368 static void compose_destroy_headerentry(Compose *compose,
7369 ComposeHeaderEntry *headerentry)
7371 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7372 gchar *email;
7374 extract_address(text);
7375 email = g_utf8_strdown(text, -1);
7376 g_hash_table_remove(compose->email_hashtable, email);
7377 g_free(text);
7378 g_free(email);
7380 gtk_widget_destroy(headerentry->combo);
7381 gtk_widget_destroy(headerentry->entry);
7382 gtk_widget_destroy(headerentry->button);
7383 gtk_widget_destroy(headerentry->hbox);
7384 g_free(headerentry);
7387 static void compose_remove_header_entries(Compose *compose)
7389 GSList *list;
7390 for (list = compose->header_list; list; list = list->next)
7391 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7393 compose->header_last = NULL;
7394 g_slist_free(compose->header_list);
7395 compose->header_list = NULL;
7396 compose->header_nextrow = 1;
7397 compose_create_header_entry(compose);
7400 static GtkWidget *compose_create_header(Compose *compose)
7402 GtkWidget *from_optmenu_hbox;
7403 GtkWidget *header_table_main;
7404 GtkWidget *header_scrolledwin;
7405 GtkWidget *header_table;
7407 /* parent with account selection and from header */
7408 header_table_main = gtk_grid_new();
7409 gtk_widget_show(header_table_main);
7410 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7412 from_optmenu_hbox = compose_account_option_menu_create(compose);
7413 gtk_grid_attach(GTK_GRID(header_table_main),from_optmenu_hbox, 0, 0, 1, 1);
7414 gtk_widget_set_hexpand(from_optmenu_hbox, TRUE);
7415 gtk_widget_set_halign(from_optmenu_hbox, GTK_ALIGN_FILL);
7417 /* child with header labels and entries */
7418 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7419 gtk_widget_show(header_scrolledwin);
7420 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7422 header_table = gtk_grid_new();
7423 gtk_widget_show(header_table);
7424 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7425 gtk_container_add(GTK_CONTAINER(header_scrolledwin), header_table);
7426 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7427 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7428 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7430 gtk_grid_attach(GTK_GRID(header_table_main), header_scrolledwin, 0, 1, 1, 1);
7431 gtk_widget_set_vexpand(header_scrolledwin, TRUE);
7432 gtk_widget_set_valign(header_scrolledwin, GTK_ALIGN_FILL);
7434 compose->header_table = header_table;
7435 compose->header_list = NULL;
7436 compose->header_nextrow = 0;
7438 compose_create_header_entry(compose);
7440 compose->table = NULL;
7442 return header_table_main;
7445 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7447 Compose *compose = (Compose *)data;
7448 GdkEventButton event;
7450 event.button = 3;
7451 event.time = gtk_get_current_event_time();
7453 return attach_button_pressed(compose->attach_clist, &event, compose);
7456 static GtkWidget *compose_create_attach(Compose *compose)
7458 GtkWidget *attach_scrwin;
7459 GtkWidget *attach_clist;
7461 GtkListStore *store;
7462 GtkCellRenderer *renderer;
7463 GtkTreeViewColumn *column;
7464 GtkTreeSelection *selection;
7466 /* attachment list */
7467 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7468 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7469 GTK_POLICY_AUTOMATIC,
7470 GTK_POLICY_AUTOMATIC);
7471 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7473 store = gtk_list_store_new(N_ATTACH_COLS,
7474 G_TYPE_STRING,
7475 G_TYPE_STRING,
7476 G_TYPE_STRING,
7477 G_TYPE_STRING,
7478 G_TYPE_POINTER,
7479 G_TYPE_AUTO_POINTER,
7480 -1);
7481 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7482 (GTK_TREE_MODEL(store)));
7483 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7484 g_object_unref(store);
7486 renderer = gtk_cell_renderer_text_new();
7487 column = gtk_tree_view_column_new_with_attributes
7488 (_("Mime type"), renderer, "text",
7489 COL_MIMETYPE, NULL);
7490 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7492 renderer = gtk_cell_renderer_text_new();
7493 column = gtk_tree_view_column_new_with_attributes
7494 (_("Size"), renderer, "text",
7495 COL_SIZE, NULL);
7496 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7498 renderer = gtk_cell_renderer_text_new();
7499 column = gtk_tree_view_column_new_with_attributes
7500 (_("Name"), renderer, "text",
7501 COL_NAME, NULL);
7502 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7504 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7505 prefs_common.use_stripes_everywhere);
7506 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7507 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7509 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7510 G_CALLBACK(attach_selected), compose);
7511 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7512 G_CALLBACK(attach_button_pressed), compose);
7513 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7514 G_CALLBACK(popup_attach_button_pressed), compose);
7515 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7516 G_CALLBACK(attach_key_pressed), compose);
7518 /* drag and drop */
7519 gtk_drag_dest_set(attach_clist,
7520 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7521 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7522 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7523 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7524 G_CALLBACK(compose_attach_drag_received_cb),
7525 compose);
7526 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7527 G_CALLBACK(compose_drag_drop),
7528 compose);
7530 compose->attach_scrwin = attach_scrwin;
7531 compose->attach_clist = attach_clist;
7533 return attach_scrwin;
7536 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7538 static GtkWidget *compose_create_others(Compose *compose)
7540 GtkWidget *table;
7541 GtkWidget *savemsg_checkbtn;
7542 GtkWidget *savemsg_combo;
7543 GtkWidget *savemsg_select;
7545 guint rowcount = 0;
7546 gchar *folderidentifier;
7548 /* Table for settings */
7549 table = gtk_grid_new();
7550 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7551 gtk_widget_show(table);
7552 gtk_grid_set_row_spacing(GTK_GRID(table), VSPACING_NARROW);
7553 rowcount = 0;
7555 /* Save Message to folder */
7556 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7557 gtk_widget_show(savemsg_checkbtn);
7558 gtk_grid_attach(GTK_GRID(table), savemsg_checkbtn, 0, rowcount, 1, 1);
7559 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7560 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7563 savemsg_combo = gtk_combo_box_text_new_with_entry();
7564 compose->savemsg_checkbtn = savemsg_checkbtn;
7565 compose->savemsg_combo = savemsg_combo;
7566 gtk_widget_show(savemsg_combo);
7568 if (prefs_common.compose_save_to_history)
7569 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7570 prefs_common.compose_save_to_history);
7571 gtk_grid_attach(GTK_GRID(table), savemsg_combo, 1, rowcount, 1, 1);
7572 gtk_widget_set_hexpand(savemsg_combo, TRUE);
7573 gtk_widget_set_halign(savemsg_combo, GTK_ALIGN_FILL);
7574 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7575 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7576 G_CALLBACK(compose_grab_focus_cb), compose);
7577 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7578 if (compose->account->set_sent_folder || prefs_common.savemsg)
7579 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), TRUE);
7580 else
7581 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), FALSE);
7582 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), TRUE);
7583 folderidentifier = folder_item_get_identifier(account_get_special_folder
7584 (compose->account, F_OUTBOX));
7585 compose_set_save_to(compose, folderidentifier);
7586 g_free(folderidentifier);
7589 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7590 gtk_widget_show(savemsg_select);
7591 gtk_grid_attach(GTK_GRID(table), savemsg_select, 2, rowcount, 1, 1);
7592 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7593 G_CALLBACK(compose_savemsg_select_cb),
7594 compose);
7596 return table;
7599 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7601 FolderItem *dest;
7602 gchar * path;
7604 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7605 _("Select folder to save message to"));
7606 if (!dest) return;
7608 path = folder_item_get_identifier(dest);
7610 compose_set_save_to(compose, path);
7611 g_free(path);
7614 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7615 GdkAtom clip, GtkTextIter *insert_place);
7618 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7619 Compose *compose)
7621 gint prev_autowrap;
7622 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7623 #if USE_ENCHANT
7624 if (event->button == 3) {
7625 GtkTextIter iter;
7626 GtkTextIter sel_start, sel_end;
7627 gboolean stuff_selected;
7628 gint x, y;
7629 /* move the cursor to allow GtkAspell to check the word
7630 * under the mouse */
7631 if (event->x && event->y) {
7632 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7633 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7634 &x, &y);
7635 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7636 &iter, x, y);
7637 } else {
7638 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7639 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7641 /* get selection */
7642 stuff_selected = gtk_text_buffer_get_selection_bounds(
7643 buffer,
7644 &sel_start, &sel_end);
7646 gtk_text_buffer_place_cursor (buffer, &iter);
7647 /* reselect stuff */
7648 if (stuff_selected
7649 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7650 gtk_text_buffer_select_range(buffer,
7651 &sel_start, &sel_end);
7653 return FALSE; /* pass the event so that the right-click goes through */
7655 #endif
7656 if (event->button == 2) {
7657 GtkTextIter iter;
7658 gint x, y;
7659 BLOCK_WRAP();
7661 /* get the middle-click position to paste at the correct place */
7662 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7663 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7664 &x, &y);
7665 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7666 &iter, x, y);
7668 entry_paste_clipboard(compose, text,
7669 prefs_common.linewrap_pastes,
7670 GDK_SELECTION_PRIMARY, &iter);
7671 UNBLOCK_WRAP();
7672 return TRUE;
7674 return FALSE;
7677 #if USE_ENCHANT
7678 static void compose_spell_menu_changed(void *data)
7680 Compose *compose = (Compose *)data;
7681 GSList *items;
7682 GtkWidget *menuitem;
7683 GtkWidget *parent_item;
7684 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7685 GSList *spell_menu;
7687 if (compose->gtkaspell == NULL)
7688 return;
7690 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7691 "/Menu/Spelling/Options");
7693 /* setting the submenu removes /Spelling/Options from the factory
7694 * so we need to save it */
7696 if (parent_item == NULL) {
7697 parent_item = compose->aspell_options_menu;
7698 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7699 } else
7700 compose->aspell_options_menu = parent_item;
7702 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7704 spell_menu = g_slist_reverse(spell_menu);
7705 for (items = spell_menu;
7706 items; items = items->next) {
7707 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7708 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7709 gtk_widget_show(GTK_WIDGET(menuitem));
7711 g_slist_free(spell_menu);
7713 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7714 gtk_widget_show(parent_item);
7717 static void compose_dict_changed(void *data)
7719 Compose *compose = (Compose *) data;
7721 if(!compose->gtkaspell)
7722 return;
7723 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7724 return;
7726 gtkaspell_highlight_all(compose->gtkaspell);
7727 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7729 #endif
7731 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7733 Compose *compose = (Compose *)data;
7734 GdkEventButton event;
7736 event.button = 3;
7737 event.time = gtk_get_current_event_time();
7738 event.x = 0;
7739 event.y = 0;
7741 return text_clicked(compose->text, &event, compose);
7744 static gboolean compose_force_window_origin = TRUE;
7745 static Compose *compose_create(PrefsAccount *account,
7746 FolderItem *folder,
7747 ComposeMode mode,
7748 gboolean batch)
7750 Compose *compose;
7751 GtkWidget *window;
7752 GtkWidget *vbox;
7753 GtkWidget *menubar;
7754 GtkWidget *handlebox;
7756 GtkWidget *notebook;
7758 GtkWidget *attach_hbox;
7759 GtkWidget *attach_lab1;
7760 GtkWidget *attach_lab2;
7762 GtkWidget *vbox2;
7764 GtkWidget *label;
7765 GtkWidget *subject_hbox;
7766 GtkWidget *subject_frame;
7767 GtkWidget *subject_entry;
7768 GtkWidget *subject;
7769 GtkWidget *paned;
7771 GtkWidget *edit_vbox;
7772 GtkWidget *ruler_hbox;
7773 GtkWidget *ruler;
7774 GtkWidget *scrolledwin;
7775 GtkWidget *text;
7776 GtkTextBuffer *buffer;
7777 GtkClipboard *clipboard;
7779 UndoMain *undostruct;
7781 GtkWidget *popupmenu;
7782 GtkWidget *tmpl_menu;
7783 GtkActionGroup *action_group = NULL;
7785 #if USE_ENCHANT
7786 GtkAspell * gtkaspell = NULL;
7787 #endif
7789 static GdkGeometry geometry;
7790 GdkRectangle workarea = {0};
7792 cm_return_val_if_fail(account != NULL, NULL);
7794 default_header_bgcolor = prefs_common.color[COL_DEFAULT_HEADER_BG],
7795 default_header_color = prefs_common.color[COL_DEFAULT_HEADER],
7797 debug_print("Creating compose window...\n");
7798 compose = g_new0(Compose, 1);
7800 compose->batch = batch;
7801 compose->account = account;
7802 compose->folder = folder;
7804 g_mutex_init(&compose->mutex);
7805 compose->set_cursor_pos = -1;
7807 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7809 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7810 gtk_window_set_default_size(GTK_WINDOW(window), prefs_common.compose_width,
7811 prefs_common.compose_height);
7813 gdk_monitor_get_workarea(gdk_display_get_primary_monitor(gdk_display_get_default()),
7814 &workarea);
7816 if (!geometry.max_width) {
7817 geometry.max_width = workarea.width;
7818 geometry.max_height = workarea.height;
7821 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7822 &geometry, GDK_HINT_MAX_SIZE);
7823 if (!geometry.min_width) {
7824 geometry.min_width = 600;
7825 geometry.min_height = 440;
7827 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7828 &geometry, GDK_HINT_MIN_SIZE);
7830 #ifndef GENERIC_UMPC
7831 if (compose_force_window_origin)
7832 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7833 prefs_common.compose_y);
7834 #endif
7835 g_signal_connect(G_OBJECT(window), "delete_event",
7836 G_CALLBACK(compose_delete_cb), compose);
7837 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7838 gtk_widget_realize(window);
7840 gtkut_widget_set_composer_icon(window);
7842 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
7843 gtk_container_add(GTK_CONTAINER(window), vbox);
7845 compose->ui_manager = gtk_ui_manager_new();
7846 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7847 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7848 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7849 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7850 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7851 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7852 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7853 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7854 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7855 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7861 #ifdef USE_ENCHANT
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7863 #endif
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7868 /* Compose menu */
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7883 /* Edit menu */
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7885 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7908 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7915 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7926 #if USE_ENCHANT
7927 /* Spelling menu */
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7934 #endif
7936 /* Options menu */
7937 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7938 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7940 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7941 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7943 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7944 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7946 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7947 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7950 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7951 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7952 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7953 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7954 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7955 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7956 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7958 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7959 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7960 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7961 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7962 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7964 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7966 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7967 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7968 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7969 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7970 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7972 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7973 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)
7974 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)
7975 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7977 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7979 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7980 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)
7981 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)
7983 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7985 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7986 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)
7987 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7989 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7990 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)
7991 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7993 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7995 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7996 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)
7997 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7998 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7999 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
8000 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
8002 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
8003 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)
8004 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)
8005 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
8006 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
8008 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
8009 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
8010 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
8011 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
8012 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
8013 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
8015 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
8016 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
8017 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)
8019 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
8020 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
8021 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
8022 /* phew. */
8024 /* Tools menu */
8025 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
8026 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
8027 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
8028 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8029 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
8030 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8032 /* Help menu */
8033 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
8035 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
8036 gtk_widget_show_all(menubar);
8038 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
8039 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
8041 handlebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8042 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8044 gtk_widget_realize(handlebox);
8045 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8046 (gpointer)compose);
8048 vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
8049 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8050 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8052 /* Notebook */
8053 notebook = gtk_notebook_new();
8054 gtk_widget_show(notebook);
8056 /* header labels and entries */
8057 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8058 compose_create_header(compose),
8059 gtk_label_new_with_mnemonic(_("Hea_der")));
8060 /* attachment list */
8061 attach_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8062 gtk_widget_show(attach_hbox);
8064 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8065 gtk_widget_show(attach_lab1);
8066 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8068 attach_lab2 = gtk_label_new("");
8069 gtk_widget_show(attach_lab2);
8070 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8072 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8073 compose_create_attach(compose),
8074 attach_hbox);
8075 /* Others Tab */
8076 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8077 compose_create_others(compose),
8078 gtk_label_new_with_mnemonic(_("Othe_rs")));
8080 /* Subject */
8081 subject_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8082 gtk_widget_show(subject_hbox);
8084 subject_frame = gtk_frame_new(NULL);
8085 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8086 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8087 gtk_widget_show(subject_frame);
8089 subject = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, HSPACING_NARROW);
8090 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8091 gtk_widget_show(subject);
8093 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8094 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8095 gtk_widget_show(label);
8097 #ifdef USE_ENCHANT
8098 subject_entry = claws_spell_entry_new();
8099 #else
8100 subject_entry = gtk_entry_new();
8101 #endif
8102 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8103 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8104 G_CALLBACK(compose_grab_focus_cb), compose);
8105 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8106 gtk_widget_show(subject_entry);
8107 compose->subject_entry = subject_entry;
8108 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8110 edit_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
8112 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8114 /* ruler */
8115 ruler_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8116 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8118 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8119 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8120 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8121 BORDER_WIDTH);
8123 /* text widget */
8124 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8125 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8126 GTK_POLICY_AUTOMATIC,
8127 GTK_POLICY_AUTOMATIC);
8128 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8129 GTK_SHADOW_IN);
8130 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8132 text = gtk_text_view_new();
8133 if (prefs_common.show_compose_margin) {
8134 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8135 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8137 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8138 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8139 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8140 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8141 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8143 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8144 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8145 G_CALLBACK(compose_edit_size_alloc),
8146 ruler);
8147 g_signal_connect(G_OBJECT(buffer), "changed",
8148 G_CALLBACK(compose_changed_cb), compose);
8149 g_signal_connect(G_OBJECT(text), "grab_focus",
8150 G_CALLBACK(compose_grab_focus_cb), compose);
8151 g_signal_connect(G_OBJECT(buffer), "insert_text",
8152 G_CALLBACK(text_inserted), compose);
8153 g_signal_connect(G_OBJECT(text), "button_press_event",
8154 G_CALLBACK(text_clicked), compose);
8155 g_signal_connect(G_OBJECT(text), "popup-menu",
8156 G_CALLBACK(compose_popup_menu), compose);
8157 g_signal_connect(G_OBJECT(subject_entry), "changed",
8158 G_CALLBACK(compose_changed_cb), compose);
8159 g_signal_connect(G_OBJECT(subject_entry), "activate",
8160 G_CALLBACK(compose_subject_entry_activated), compose);
8162 /* drag and drop */
8163 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8164 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8165 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8166 g_signal_connect(G_OBJECT(text), "drag_data_received",
8167 G_CALLBACK(compose_insert_drag_received_cb),
8168 compose);
8169 g_signal_connect(G_OBJECT(text), "drag-drop",
8170 G_CALLBACK(compose_drag_drop),
8171 compose);
8172 g_signal_connect(G_OBJECT(text), "key-press-event",
8173 G_CALLBACK(completion_set_focus_to_subject),
8174 compose);
8175 gtk_widget_show_all(vbox);
8177 /* pane between attach clist and text */
8178 paned = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
8179 gtk_box_pack_start(GTK_BOX(vbox2), paned, TRUE, TRUE, 0);
8180 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8181 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8182 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8183 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8184 G_CALLBACK(compose_notebook_size_alloc), paned);
8186 gtk_widget_show_all(paned);
8189 if (prefs_common.textfont) {
8190 PangoFontDescription *font_desc;
8192 font_desc = pango_font_description_from_string
8193 (prefs_common.textfont);
8194 if (font_desc) {
8195 gtk_widget_override_font(text, font_desc);
8196 pango_font_description_free(font_desc);
8200 gtk_action_group_add_actions(action_group, compose_popup_entries,
8201 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8202 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8203 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8204 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8205 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8206 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8207 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8209 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8211 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8212 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8213 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8215 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8217 undostruct = undo_init(text);
8218 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8219 compose);
8221 address_completion_start(window);
8223 compose->window = window;
8224 compose->vbox = vbox;
8225 compose->menubar = menubar;
8226 compose->handlebox = handlebox;
8228 compose->vbox2 = vbox2;
8230 compose->paned = paned;
8232 compose->attach_label = attach_lab2;
8234 compose->notebook = notebook;
8235 compose->edit_vbox = edit_vbox;
8236 compose->ruler_hbox = ruler_hbox;
8237 compose->ruler = ruler;
8238 compose->scrolledwin = scrolledwin;
8239 compose->text = text;
8241 compose->focused_editable = NULL;
8243 compose->popupmenu = popupmenu;
8245 compose->tmpl_menu = tmpl_menu;
8247 compose->mode = mode;
8248 compose->rmode = mode;
8250 compose->targetinfo = NULL;
8251 compose->replyinfo = NULL;
8252 compose->fwdinfo = NULL;
8254 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8255 g_str_equal, (GDestroyNotify) g_free, NULL);
8257 compose->replyto = NULL;
8258 compose->cc = NULL;
8259 compose->bcc = NULL;
8260 compose->followup_to = NULL;
8262 compose->ml_post = NULL;
8264 compose->inreplyto = NULL;
8265 compose->references = NULL;
8266 compose->msgid = NULL;
8267 compose->boundary = NULL;
8269 compose->autowrap = prefs_common.autowrap;
8270 compose->autoindent = prefs_common.auto_indent;
8271 compose->use_signing = FALSE;
8272 compose->use_encryption = FALSE;
8273 compose->privacy_system = NULL;
8274 compose->encdata = NULL;
8276 compose->modified = FALSE;
8278 compose->return_receipt = FALSE;
8280 compose->to_list = NULL;
8281 compose->newsgroup_list = NULL;
8283 compose->undostruct = undostruct;
8285 compose->sig_str = NULL;
8287 compose->exteditor_file = NULL;
8288 compose->exteditor_pid = INVALID_PID;
8289 compose->exteditor_tag = -1;
8290 compose->exteditor_socket = NULL;
8291 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8293 compose->folder_update_callback_id =
8294 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8295 compose_update_folder_hook,
8296 (gpointer) compose);
8298 #if USE_ENCHANT
8299 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8300 if (mode != COMPOSE_REDIRECT) {
8301 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8302 strcmp(prefs_common.dictionary, "")) {
8303 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8304 prefs_common.alt_dictionary,
8305 conv_get_locale_charset_str(),
8306 prefs_common.color[COL_MISSPELLED],
8307 prefs_common.check_while_typing,
8308 prefs_common.recheck_when_changing_dict,
8309 prefs_common.use_alternate,
8310 prefs_common.use_both_dicts,
8311 GTK_TEXT_VIEW(text),
8312 GTK_WINDOW(compose->window),
8313 compose_dict_changed,
8314 compose_spell_menu_changed,
8315 compose);
8316 if (!gtkaspell) {
8317 alertpanel_error(_("Spell checker could not "
8318 "be started.\n%s"),
8319 gtkaspell_checkers_strerror());
8320 gtkaspell_checkers_reset_error();
8321 } else {
8322 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8326 compose->gtkaspell = gtkaspell;
8327 compose_spell_menu_changed(compose);
8328 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8329 #endif
8331 compose_select_account(compose, account, TRUE);
8333 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8334 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8336 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8337 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8339 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8340 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8342 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8343 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8345 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8346 if (account->protocol != A_NNTP)
8347 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8348 prefs_common_translated_header_name("To:"));
8349 else
8350 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8351 prefs_common_translated_header_name("Newsgroups:"));
8353 #ifndef USE_ALT_ADDRBOOK
8354 addressbook_set_target_compose(compose);
8355 #endif
8356 if (mode != COMPOSE_REDIRECT)
8357 compose_set_template_menu(compose);
8358 else {
8359 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8362 compose_list = g_list_append(compose_list, compose);
8364 if (!prefs_common.show_ruler)
8365 gtk_widget_hide(ruler_hbox);
8367 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8369 /* Priority */
8370 compose->priority = PRIORITY_NORMAL;
8371 compose_update_priority_menu_item(compose);
8373 compose_set_out_encoding(compose);
8375 /* Actions menu */
8376 compose_update_actions_menu(compose);
8378 /* Privacy Systems menu */
8379 compose_update_privacy_systems_menu(compose);
8380 compose_activate_privacy_system(compose, account, TRUE);
8382 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8383 if (batch) {
8384 gtk_widget_realize(window);
8385 } else {
8386 gtk_widget_show(window);
8389 return compose;
8392 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8394 GList *accounts;
8395 GtkWidget *hbox;
8396 GtkWidget *optmenu;
8397 GtkWidget *optmenubox;
8398 GtkWidget *fromlabel;
8399 GtkListStore *menu;
8400 GtkTreeIter iter;
8401 GtkWidget *from_name = NULL;
8403 gint num = 0, def_menu = 0;
8405 accounts = account_get_list();
8406 cm_return_val_if_fail(accounts != NULL, NULL);
8408 optmenubox = gtk_event_box_new();
8409 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8410 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8412 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
8413 from_name = gtk_entry_new();
8415 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8416 G_CALLBACK(compose_grab_focus_cb), compose);
8417 g_signal_connect_after(G_OBJECT(from_name), "activate",
8418 G_CALLBACK(from_name_activate_cb), optmenu);
8420 for (; accounts != NULL; accounts = accounts->next, num++) {
8421 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8422 gchar *name, *from = NULL;
8424 if (ac == compose->account) def_menu = num;
8426 name = g_markup_printf_escaped("<i>%s</i>",
8427 ac->account_name);
8429 if (ac == compose->account) {
8430 if (ac->name && *ac->name) {
8431 gchar *buf;
8432 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8433 from = g_strdup_printf("%s <%s>",
8434 buf, ac->address);
8435 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8436 } else {
8437 from = g_strdup_printf("%s",
8438 ac->address);
8439 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8441 if (cur_account != compose->account) {
8442 GdkColor color;
8444 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_bgcolor, color);
8445 gtk_widget_modify_base(
8446 GTK_WIDGET(from_name),
8447 GTK_STATE_NORMAL, &color);
8448 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_color, color);
8449 gtk_widget_modify_text(
8450 GTK_WIDGET(from_name),
8451 GTK_STATE_NORMAL, &color);
8454 COMBOBOX_ADD(menu, name, ac->account_id);
8455 g_free(name);
8456 g_free(from);
8459 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8461 g_signal_connect(G_OBJECT(optmenu), "changed",
8462 G_CALLBACK(account_activated),
8463 compose);
8464 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8465 G_CALLBACK(compose_entry_popup_extend),
8466 NULL);
8468 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8469 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8471 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8472 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8473 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8475 CLAWS_SET_TIP(optmenubox,
8476 _("Account to use for this email"));
8477 CLAWS_SET_TIP(from_name,
8478 _("Sender address to be used"));
8480 compose->account_combo = optmenu;
8481 compose->from_name = from_name;
8483 return hbox;
8486 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8488 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8489 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8490 Compose *compose = (Compose *) data;
8491 if (active) {
8492 compose->priority = value;
8496 static void compose_reply_change_mode(Compose *compose,
8497 ComposeMode action)
8499 gboolean was_modified = compose->modified;
8501 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8503 cm_return_if_fail(compose->replyinfo != NULL);
8505 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8506 ml = TRUE;
8507 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8508 followup = TRUE;
8509 if (action == COMPOSE_REPLY_TO_ALL)
8510 all = TRUE;
8511 if (action == COMPOSE_REPLY_TO_SENDER)
8512 sender = TRUE;
8513 if (action == COMPOSE_REPLY_TO_LIST)
8514 ml = TRUE;
8516 compose_remove_header_entries(compose);
8517 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8518 if (compose->account->set_autocc && compose->account->auto_cc)
8519 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8521 if (compose->account->set_autobcc && compose->account->auto_bcc)
8522 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8524 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8525 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8526 compose_show_first_last_header(compose, TRUE);
8527 compose->modified = was_modified;
8528 compose_set_title(compose);
8531 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8533 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8534 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8535 Compose *compose = (Compose *) data;
8537 if (active)
8538 compose_reply_change_mode(compose, value);
8541 static void compose_update_priority_menu_item(Compose * compose)
8543 GtkWidget *menuitem = NULL;
8544 switch (compose->priority) {
8545 case PRIORITY_HIGHEST:
8546 menuitem = gtk_ui_manager_get_widget
8547 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8548 break;
8549 case PRIORITY_HIGH:
8550 menuitem = gtk_ui_manager_get_widget
8551 (compose->ui_manager, "/Menu/Options/Priority/High");
8552 break;
8553 case PRIORITY_NORMAL:
8554 menuitem = gtk_ui_manager_get_widget
8555 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8556 break;
8557 case PRIORITY_LOW:
8558 menuitem = gtk_ui_manager_get_widget
8559 (compose->ui_manager, "/Menu/Options/Priority/Low");
8560 break;
8561 case PRIORITY_LOWEST:
8562 menuitem = gtk_ui_manager_get_widget
8563 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8564 break;
8566 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8569 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8571 Compose *compose = (Compose *) data;
8572 gchar *systemid;
8573 gboolean can_sign = FALSE, can_encrypt = FALSE;
8575 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8577 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8578 return;
8580 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8581 g_free(compose->privacy_system);
8582 compose->privacy_system = NULL;
8583 g_free(compose->encdata);
8584 compose->encdata = NULL;
8585 if (systemid != NULL) {
8586 compose->privacy_system = g_strdup(systemid);
8588 can_sign = privacy_system_can_sign(systemid);
8589 can_encrypt = privacy_system_can_encrypt(systemid);
8592 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8594 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8595 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8596 if (compose->toolbar->privacy_sign_btn != NULL) {
8597 gtk_widget_set_sensitive(
8598 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8599 can_sign);
8600 gtk_toggle_tool_button_set_active(
8601 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8602 can_sign ? compose->use_signing : FALSE);
8604 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8605 gtk_widget_set_sensitive(
8606 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8607 can_encrypt);
8608 gtk_toggle_tool_button_set_active(
8609 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8610 can_encrypt ? compose->use_encryption : FALSE);
8614 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8616 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8617 GtkWidget *menuitem = NULL;
8618 GList *children, *amenu;
8619 gboolean can_sign = FALSE, can_encrypt = FALSE;
8620 gboolean found = FALSE;
8622 if (compose->privacy_system != NULL) {
8623 gchar *systemid;
8624 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8625 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8626 cm_return_if_fail(menuitem != NULL);
8628 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8629 amenu = children;
8630 menuitem = NULL;
8631 while (amenu != NULL) {
8632 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8633 if (systemid != NULL) {
8634 if (strcmp(systemid, compose->privacy_system) == 0 &&
8635 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8636 menuitem = GTK_WIDGET(amenu->data);
8638 can_sign = privacy_system_can_sign(systemid);
8639 can_encrypt = privacy_system_can_encrypt(systemid);
8640 found = TRUE;
8641 break;
8643 } else if (strlen(compose->privacy_system) == 0 &&
8644 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8645 menuitem = GTK_WIDGET(amenu->data);
8647 can_sign = FALSE;
8648 can_encrypt = FALSE;
8649 found = TRUE;
8650 break;
8653 amenu = amenu->next;
8655 g_list_free(children);
8656 if (menuitem != NULL)
8657 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8659 if (warn && !found && strlen(compose->privacy_system)) {
8660 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8661 "will not be able to sign or encrypt this message."),
8662 compose->privacy_system);
8666 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8667 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8668 if (compose->toolbar->privacy_sign_btn != NULL) {
8669 gtk_widget_set_sensitive(
8670 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8671 can_sign);
8673 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8674 gtk_widget_set_sensitive(
8675 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8676 can_encrypt);
8680 static void compose_set_out_encoding(Compose *compose)
8682 CharSet out_encoding;
8683 const gchar *branch = NULL;
8684 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8686 switch(out_encoding) {
8687 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8688 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8689 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8690 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8691 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8692 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8693 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8694 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8695 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8696 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8697 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8698 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8699 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8700 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8701 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8702 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8703 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8704 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8705 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8706 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8707 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8708 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8709 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8710 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8711 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8712 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8713 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8714 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8715 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8716 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8717 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8718 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8719 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8720 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8722 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8725 static void compose_set_template_menu(Compose *compose)
8727 GSList *tmpl_list, *cur;
8728 GtkWidget *menu;
8729 GtkWidget *item;
8731 tmpl_list = template_get_config();
8733 menu = gtk_menu_new();
8735 gtk_menu_set_accel_group (GTK_MENU (menu),
8736 gtk_ui_manager_get_accel_group(compose->ui_manager));
8737 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8738 Template *tmpl = (Template *)cur->data;
8739 gchar *accel_path = NULL;
8740 item = gtk_menu_item_new_with_label(tmpl->name);
8741 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8742 g_signal_connect(G_OBJECT(item), "activate",
8743 G_CALLBACK(compose_template_activate_cb),
8744 compose);
8745 g_object_set_data(G_OBJECT(item), "template", tmpl);
8746 gtk_widget_show(item);
8747 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8748 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8749 g_free(accel_path);
8752 gtk_widget_show(menu);
8753 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8756 void compose_update_actions_menu(Compose *compose)
8758 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8761 static void compose_update_privacy_systems_menu(Compose *compose)
8763 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8764 GSList *systems, *cur;
8765 GtkWidget *widget;
8766 GtkWidget *system_none;
8767 GSList *group;
8768 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8769 GtkWidget *privacy_menu = gtk_menu_new();
8771 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8772 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8774 g_signal_connect(G_OBJECT(system_none), "activate",
8775 G_CALLBACK(compose_set_privacy_system_cb), compose);
8777 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8778 gtk_widget_show(system_none);
8780 systems = privacy_get_system_ids();
8781 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8782 gchar *systemid = cur->data;
8784 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8785 widget = gtk_radio_menu_item_new_with_label(group,
8786 privacy_system_get_name(systemid));
8787 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8788 g_strdup(systemid), g_free);
8789 g_signal_connect(G_OBJECT(widget), "activate",
8790 G_CALLBACK(compose_set_privacy_system_cb), compose);
8792 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8793 gtk_widget_show(widget);
8794 g_free(systemid);
8796 g_slist_free(systems);
8797 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8798 gtk_widget_show_all(privacy_menu);
8799 gtk_widget_show_all(privacy_menuitem);
8802 void compose_reflect_prefs_all(void)
8804 GList *cur;
8805 Compose *compose;
8807 for (cur = compose_list; cur != NULL; cur = cur->next) {
8808 compose = (Compose *)cur->data;
8809 compose_set_template_menu(compose);
8813 void compose_reflect_prefs_pixmap_theme(void)
8815 GList *cur;
8816 Compose *compose;
8818 for (cur = compose_list; cur != NULL; cur = cur->next) {
8819 compose = (Compose *)cur->data;
8820 toolbar_update(TOOLBAR_COMPOSE, compose);
8824 static const gchar *compose_quote_char_from_context(Compose *compose)
8826 const gchar *qmark = NULL;
8828 cm_return_val_if_fail(compose != NULL, NULL);
8830 switch (compose->mode) {
8831 /* use forward-specific quote char */
8832 case COMPOSE_FORWARD:
8833 case COMPOSE_FORWARD_AS_ATTACH:
8834 case COMPOSE_FORWARD_INLINE:
8835 if (compose->folder && compose->folder->prefs &&
8836 compose->folder->prefs->forward_with_format)
8837 qmark = compose->folder->prefs->forward_quotemark;
8838 else if (compose->account->forward_with_format)
8839 qmark = compose->account->forward_quotemark;
8840 else
8841 qmark = prefs_common.fw_quotemark;
8842 break;
8844 /* use reply-specific quote char in all other modes */
8845 default:
8846 if (compose->folder && compose->folder->prefs &&
8847 compose->folder->prefs->reply_with_format)
8848 qmark = compose->folder->prefs->reply_quotemark;
8849 else if (compose->account->reply_with_format)
8850 qmark = compose->account->reply_quotemark;
8851 else
8852 qmark = prefs_common.quotemark;
8853 break;
8856 if (qmark == NULL || *qmark == '\0')
8857 qmark = "> ";
8859 return qmark;
8862 static void compose_template_apply(Compose *compose, Template *tmpl,
8863 gboolean replace)
8865 GtkTextView *text;
8866 GtkTextBuffer *buffer;
8867 GtkTextMark *mark;
8868 GtkTextIter iter;
8869 const gchar *qmark;
8870 gchar *parsed_str = NULL;
8871 gint cursor_pos = 0;
8872 const gchar *err_msg = _("The body of the template has an error at line %d.");
8873 if (!tmpl) return;
8875 /* process the body */
8877 text = GTK_TEXT_VIEW(compose->text);
8878 buffer = gtk_text_view_get_buffer(text);
8880 if (tmpl->value) {
8881 qmark = compose_quote_char_from_context(compose);
8883 if (compose->replyinfo != NULL) {
8885 if (replace)
8886 gtk_text_buffer_set_text(buffer, "", -1);
8887 mark = gtk_text_buffer_get_insert(buffer);
8888 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8890 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8891 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8893 } else if (compose->fwdinfo != NULL) {
8895 if (replace)
8896 gtk_text_buffer_set_text(buffer, "", -1);
8897 mark = gtk_text_buffer_get_insert(buffer);
8898 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8900 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8901 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8903 } else {
8904 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8906 GtkTextIter start, end;
8907 gchar *tmp = NULL;
8909 gtk_text_buffer_get_start_iter(buffer, &start);
8910 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8911 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8913 /* clear the buffer now */
8914 if (replace)
8915 gtk_text_buffer_set_text(buffer, "", -1);
8917 parsed_str = compose_quote_fmt(compose, dummyinfo,
8918 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8919 procmsg_msginfo_free( &dummyinfo );
8921 g_free( tmp );
8923 } else {
8924 if (replace)
8925 gtk_text_buffer_set_text(buffer, "", -1);
8926 mark = gtk_text_buffer_get_insert(buffer);
8927 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8930 if (replace && parsed_str && compose->account->auto_sig)
8931 compose_insert_sig(compose, FALSE);
8933 if (replace && parsed_str) {
8934 gtk_text_buffer_get_start_iter(buffer, &iter);
8935 gtk_text_buffer_place_cursor(buffer, &iter);
8938 if (parsed_str) {
8939 cursor_pos = quote_fmt_get_cursor_pos();
8940 compose->set_cursor_pos = cursor_pos;
8941 if (cursor_pos == -1)
8942 cursor_pos = 0;
8943 gtk_text_buffer_get_start_iter(buffer, &iter);
8944 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8945 gtk_text_buffer_place_cursor(buffer, &iter);
8948 /* process the other fields */
8950 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8951 compose_template_apply_fields(compose, tmpl);
8952 quote_fmt_reset_vartable();
8953 quote_fmtlex_destroy();
8955 compose_changed_cb(NULL, compose);
8957 #ifdef USE_ENCHANT
8958 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8959 gtkaspell_highlight_all(compose->gtkaspell);
8960 #endif
8963 static void compose_template_apply_fields_error(const gchar *header)
8965 gchar *tr;
8966 gchar *text;
8968 tr = g_strdup(C_("'%s' stands for a header name",
8969 "Template '%s' format error."));
8970 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8971 alertpanel_error("%s", text);
8973 g_free(text);
8974 g_free(tr);
8977 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8979 MsgInfo* dummyinfo = NULL;
8980 MsgInfo *msginfo = NULL;
8981 gchar *buf = NULL;
8983 if (compose->replyinfo != NULL)
8984 msginfo = compose->replyinfo;
8985 else if (compose->fwdinfo != NULL)
8986 msginfo = compose->fwdinfo;
8987 else {
8988 dummyinfo = compose_msginfo_new_from_compose(compose);
8989 msginfo = dummyinfo;
8992 if (tmpl->from && *tmpl->from != '\0') {
8993 #ifdef USE_ENCHANT
8994 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8995 compose->gtkaspell);
8996 #else
8997 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8998 #endif
8999 quote_fmt_scan_string(tmpl->from);
9000 quote_fmt_parse();
9002 buf = quote_fmt_get_buffer();
9003 if (buf == NULL) {
9004 compose_template_apply_fields_error("From");
9005 } else {
9006 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
9009 quote_fmt_reset_vartable();
9010 quote_fmtlex_destroy();
9013 if (tmpl->to && *tmpl->to != '\0') {
9014 #ifdef USE_ENCHANT
9015 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9016 compose->gtkaspell);
9017 #else
9018 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9019 #endif
9020 quote_fmt_scan_string(tmpl->to);
9021 quote_fmt_parse();
9023 buf = quote_fmt_get_buffer();
9024 if (buf == NULL) {
9025 compose_template_apply_fields_error("To");
9026 } else {
9027 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
9030 quote_fmt_reset_vartable();
9031 quote_fmtlex_destroy();
9034 if (tmpl->cc && *tmpl->cc != '\0') {
9035 #ifdef USE_ENCHANT
9036 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9037 compose->gtkaspell);
9038 #else
9039 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9040 #endif
9041 quote_fmt_scan_string(tmpl->cc);
9042 quote_fmt_parse();
9044 buf = quote_fmt_get_buffer();
9045 if (buf == NULL) {
9046 compose_template_apply_fields_error("Cc");
9047 } else {
9048 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9051 quote_fmt_reset_vartable();
9052 quote_fmtlex_destroy();
9055 if (tmpl->bcc && *tmpl->bcc != '\0') {
9056 #ifdef USE_ENCHANT
9057 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9058 compose->gtkaspell);
9059 #else
9060 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9061 #endif
9062 quote_fmt_scan_string(tmpl->bcc);
9063 quote_fmt_parse();
9065 buf = quote_fmt_get_buffer();
9066 if (buf == NULL) {
9067 compose_template_apply_fields_error("Bcc");
9068 } else {
9069 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9072 quote_fmt_reset_vartable();
9073 quote_fmtlex_destroy();
9076 if (tmpl->replyto && *tmpl->replyto != '\0') {
9077 #ifdef USE_ENCHANT
9078 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9079 compose->gtkaspell);
9080 #else
9081 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9082 #endif
9083 quote_fmt_scan_string(tmpl->replyto);
9084 quote_fmt_parse();
9086 buf = quote_fmt_get_buffer();
9087 if (buf == NULL) {
9088 compose_template_apply_fields_error("Reply-To");
9089 } else {
9090 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9093 quote_fmt_reset_vartable();
9094 quote_fmtlex_destroy();
9097 /* process the subject */
9098 if (tmpl->subject && *tmpl->subject != '\0') {
9099 #ifdef USE_ENCHANT
9100 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9101 compose->gtkaspell);
9102 #else
9103 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9104 #endif
9105 quote_fmt_scan_string(tmpl->subject);
9106 quote_fmt_parse();
9108 buf = quote_fmt_get_buffer();
9109 if (buf == NULL) {
9110 compose_template_apply_fields_error("Subject");
9111 } else {
9112 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9115 quote_fmt_reset_vartable();
9116 quote_fmtlex_destroy();
9119 procmsg_msginfo_free( &dummyinfo );
9122 static void compose_destroy(Compose *compose)
9124 GtkAllocation allocation;
9125 GtkTextBuffer *buffer;
9126 GtkClipboard *clipboard;
9128 compose_list = g_list_remove(compose_list, compose);
9130 #ifdef USE_LDAP
9131 gboolean enable = TRUE;
9132 g_slist_foreach(compose->passworded_ldap_servers,
9133 _ldap_srv_func, &enable);
9134 g_slist_free(compose->passworded_ldap_servers);
9135 #endif
9137 if (compose->updating) {
9138 debug_print("danger, not destroying anything now\n");
9139 compose->deferred_destroy = TRUE;
9140 return;
9143 /* NOTE: address_completion_end() does nothing with the window
9144 * however this may change. */
9145 address_completion_end(compose->window);
9147 slist_free_strings_full(compose->to_list);
9148 slist_free_strings_full(compose->newsgroup_list);
9149 slist_free_strings_full(compose->header_list);
9151 slist_free_strings_full(extra_headers);
9152 extra_headers = NULL;
9154 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9156 g_hash_table_destroy(compose->email_hashtable);
9158 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9159 compose->folder_update_callback_id);
9161 procmsg_msginfo_free(&(compose->targetinfo));
9162 procmsg_msginfo_free(&(compose->replyinfo));
9163 procmsg_msginfo_free(&(compose->fwdinfo));
9165 g_free(compose->replyto);
9166 g_free(compose->cc);
9167 g_free(compose->bcc);
9168 g_free(compose->newsgroups);
9169 g_free(compose->followup_to);
9171 g_free(compose->ml_post);
9173 g_free(compose->inreplyto);
9174 g_free(compose->references);
9175 g_free(compose->msgid);
9176 g_free(compose->boundary);
9178 g_free(compose->redirect_filename);
9179 if (compose->undostruct)
9180 undo_destroy(compose->undostruct);
9182 g_free(compose->sig_str);
9184 g_free(compose->exteditor_file);
9186 g_free(compose->orig_charset);
9188 g_free(compose->privacy_system);
9189 g_free(compose->encdata);
9191 #ifndef USE_ALT_ADDRBOOK
9192 if (addressbook_get_target_compose() == compose)
9193 addressbook_set_target_compose(NULL);
9194 #endif
9195 #if USE_ENCHANT
9196 if (compose->gtkaspell) {
9197 gtkaspell_delete(compose->gtkaspell);
9198 compose->gtkaspell = NULL;
9200 #endif
9202 if (!compose->batch) {
9203 gtk_window_get_size(GTK_WINDOW(compose->window),
9204 &allocation.width, &allocation.height);
9205 prefs_common.compose_width = allocation.width;
9206 prefs_common.compose_height = allocation.height;
9209 if (!gtk_widget_get_parent(compose->paned))
9210 gtk_widget_destroy(compose->paned);
9211 gtk_widget_destroy(compose->popupmenu);
9213 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9214 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9215 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9217 message_search_close(compose);
9218 gtk_widget_destroy(compose->window);
9219 toolbar_destroy(compose->toolbar);
9220 g_free(compose->toolbar);
9221 g_mutex_clear(&compose->mutex);
9222 g_free(compose);
9225 static void compose_attach_info_free(AttachInfo *ainfo)
9227 g_free(ainfo->file);
9228 g_free(ainfo->content_type);
9229 g_free(ainfo->name);
9230 g_free(ainfo->charset);
9231 g_free(ainfo);
9234 static void compose_attach_update_label(Compose *compose)
9236 GtkTreeIter iter;
9237 gint i = 1;
9238 gchar *text;
9239 GtkTreeModel *model;
9240 goffset total_size;
9241 AttachInfo *ainfo;
9243 if (compose == NULL)
9244 return;
9246 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9247 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9248 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9249 return;
9252 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9253 total_size = ainfo->size;
9254 while(gtk_tree_model_iter_next(model, &iter)) {
9255 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9256 total_size += ainfo->size;
9257 i++;
9259 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9260 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9261 g_free(text);
9264 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9266 Compose *compose = (Compose *)data;
9267 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9268 GtkTreeSelection *selection;
9269 GList *sel, *cur;
9270 GtkTreeModel *model;
9272 selection = gtk_tree_view_get_selection(tree_view);
9273 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9274 cm_return_if_fail(sel);
9276 for (cur = sel; cur != NULL; cur = cur->next) {
9277 GtkTreePath *path = cur->data;
9278 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9279 (model, cur->data);
9280 cur->data = ref;
9281 gtk_tree_path_free(path);
9284 for (cur = sel; cur != NULL; cur = cur->next) {
9285 GtkTreeRowReference *ref = cur->data;
9286 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9287 GtkTreeIter iter;
9289 if (gtk_tree_model_get_iter(model, &iter, path))
9290 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9292 gtk_tree_path_free(path);
9293 gtk_tree_row_reference_free(ref);
9296 g_list_free(sel);
9297 compose_attach_update_label(compose);
9300 static struct _AttachProperty
9302 GtkWidget *window;
9303 GtkWidget *mimetype_entry;
9304 GtkWidget *encoding_optmenu;
9305 GtkWidget *path_entry;
9306 GtkWidget *filename_entry;
9307 GtkWidget *ok_btn;
9308 GtkWidget *cancel_btn;
9309 } attach_prop;
9311 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9313 gtk_tree_path_free((GtkTreePath *)ptr);
9316 static void compose_attach_property(GtkAction *action, gpointer data)
9318 Compose *compose = (Compose *)data;
9319 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9320 AttachInfo *ainfo;
9321 GtkComboBox *optmenu;
9322 GtkTreeSelection *selection;
9323 GList *sel;
9324 GtkTreeModel *model;
9325 GtkTreeIter iter;
9326 GtkTreePath *path;
9327 static gboolean cancelled;
9329 /* only if one selected */
9330 selection = gtk_tree_view_get_selection(tree_view);
9331 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9332 return;
9334 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9335 cm_return_if_fail(sel);
9337 path = (GtkTreePath *) sel->data;
9338 gtk_tree_model_get_iter(model, &iter, path);
9339 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9341 if (!ainfo) {
9342 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9343 g_list_free(sel);
9344 return;
9346 g_list_free(sel);
9348 if (!attach_prop.window)
9349 compose_attach_property_create(&cancelled);
9350 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9351 gtk_widget_grab_focus(attach_prop.ok_btn);
9352 gtk_widget_show(attach_prop.window);
9353 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9354 GTK_WINDOW(compose->window));
9356 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9357 if (ainfo->encoding == ENC_UNKNOWN)
9358 combobox_select_by_data(optmenu, ENC_BASE64);
9359 else
9360 combobox_select_by_data(optmenu, ainfo->encoding);
9362 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9363 ainfo->content_type ? ainfo->content_type : "");
9364 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9365 ainfo->file ? ainfo->file : "");
9366 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9367 ainfo->name ? ainfo->name : "");
9369 for (;;) {
9370 const gchar *entry_text;
9371 gchar *text;
9372 gchar *cnttype = NULL;
9373 gchar *file = NULL;
9374 off_t size = 0;
9376 cancelled = FALSE;
9377 gtk_main();
9379 gtk_widget_hide(attach_prop.window);
9380 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9382 if (cancelled)
9383 break;
9385 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9386 if (*entry_text != '\0') {
9387 gchar *p;
9389 text = g_strstrip(g_strdup(entry_text));
9390 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9391 cnttype = g_strdup(text);
9392 g_free(text);
9393 } else {
9394 alertpanel_error(_("Invalid MIME type."));
9395 g_free(text);
9396 continue;
9400 ainfo->encoding = combobox_get_active_data(optmenu);
9402 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9403 if (*entry_text != '\0') {
9404 if (is_file_exist(entry_text) &&
9405 (size = get_file_size(entry_text)) > 0)
9406 file = g_strdup(entry_text);
9407 else {
9408 alertpanel_error
9409 (_("File doesn't exist or is empty."));
9410 g_free(cnttype);
9411 continue;
9415 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9416 if (*entry_text != '\0') {
9417 g_free(ainfo->name);
9418 ainfo->name = g_strdup(entry_text);
9421 if (cnttype) {
9422 g_free(ainfo->content_type);
9423 ainfo->content_type = cnttype;
9425 if (file) {
9426 g_free(ainfo->file);
9427 ainfo->file = file;
9429 if (size)
9430 ainfo->size = (goffset)size;
9432 /* update tree store */
9433 text = to_human_readable(ainfo->size);
9434 gtk_tree_model_get_iter(model, &iter, path);
9435 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9436 COL_MIMETYPE, ainfo->content_type,
9437 COL_SIZE, text,
9438 COL_NAME, ainfo->name,
9439 COL_CHARSET, ainfo->charset,
9440 -1);
9442 break;
9445 gtk_tree_path_free(path);
9448 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9450 label = gtk_label_new(str); \
9451 gtk_grid_attach(GTK_GRID(table), label, 0, top, 1, 1); \
9452 gtk_label_set_xalign(GTK_LABEL(label), 0.0); \
9453 entry = gtk_entry_new(); \
9454 gtk_grid_attach(GTK_GRID(table), entry, 1, top, 1, 1); \
9457 static void compose_attach_property_create(gboolean *cancelled)
9459 GtkWidget *window;
9460 GtkWidget *vbox;
9461 GtkWidget *table;
9462 GtkWidget *label;
9463 GtkWidget *mimetype_entry;
9464 GtkWidget *hbox;
9465 GtkWidget *optmenu;
9466 GtkListStore *optmenu_menu;
9467 GtkWidget *path_entry;
9468 GtkWidget *filename_entry;
9469 GtkWidget *hbbox;
9470 GtkWidget *ok_btn;
9471 GtkWidget *cancel_btn;
9472 GList *mime_type_list, *strlist;
9473 GtkTreeIter iter;
9475 debug_print("Creating attach_property window...\n");
9477 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9478 gtk_widget_set_size_request(window, 480, -1);
9479 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9480 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9481 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9482 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
9483 g_signal_connect(G_OBJECT(window), "delete_event",
9484 G_CALLBACK(attach_property_delete_event),
9485 cancelled);
9486 g_signal_connect(G_OBJECT(window), "key_press_event",
9487 G_CALLBACK(attach_property_key_pressed),
9488 cancelled);
9490 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
9491 gtk_container_add(GTK_CONTAINER(window), vbox);
9493 table = gtk_grid_new();
9494 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9495 gtk_grid_set_row_spacing(GTK_GRID(table), 8);
9496 gtk_grid_set_column_spacing(GTK_GRID(table), 8);
9498 label = gtk_label_new(_("MIME type"));
9499 gtk_grid_attach(GTK_GRID(table), label, 0, 0, 1, 1);
9500 gtk_label_set_xalign(GTK_LABEL(label), 0.0);
9501 mimetype_entry = gtk_combo_box_text_new_with_entry();
9502 gtk_grid_attach(GTK_GRID(table), mimetype_entry, 1, 0, 1, 1);
9503 gtk_widget_set_hexpand(mimetype_entry, TRUE);
9504 gtk_widget_set_halign(mimetype_entry, GTK_ALIGN_FILL);
9506 /* stuff with list */
9507 mime_type_list = procmime_get_mime_type_list();
9508 strlist = NULL;
9509 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9510 MimeType *type = (MimeType *) mime_type_list->data;
9511 gchar *tmp;
9513 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9515 if (g_list_find_custom(strlist, tmp, (GCompareFunc)g_strcmp0))
9516 g_free(tmp);
9517 else
9518 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9519 (GCompareFunc)g_strcmp0);
9522 for (mime_type_list = strlist; mime_type_list != NULL;
9523 mime_type_list = mime_type_list->next) {
9524 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9525 g_free(mime_type_list->data);
9527 g_list_free(strlist);
9528 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9529 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9531 label = gtk_label_new(_("Encoding"));
9532 gtk_grid_attach(GTK_GRID(table), label, 0, 1, 1, 1);
9533 gtk_label_set_xalign(GTK_LABEL(label), 0.0);
9535 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
9536 gtk_grid_attach(GTK_GRID(table), hbox, 1, 1, 1, 1);
9537 gtk_widget_set_hexpand(hbox, TRUE);
9538 gtk_widget_set_halign(hbox, GTK_ALIGN_FILL);
9540 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9541 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9543 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9544 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9545 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9546 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9547 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9549 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9551 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9552 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9554 gtkut_stock_button_set_create(&hbbox, &cancel_btn, NULL, _("_Cancel"),
9555 &ok_btn, NULL, _("_OK"),
9556 NULL, NULL, NULL);
9557 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9558 gtk_widget_grab_default(ok_btn);
9560 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9561 G_CALLBACK(attach_property_ok),
9562 cancelled);
9563 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9564 G_CALLBACK(attach_property_cancel),
9565 cancelled);
9567 gtk_widget_show_all(vbox);
9569 attach_prop.window = window;
9570 attach_prop.mimetype_entry = mimetype_entry;
9571 attach_prop.encoding_optmenu = optmenu;
9572 attach_prop.path_entry = path_entry;
9573 attach_prop.filename_entry = filename_entry;
9574 attach_prop.ok_btn = ok_btn;
9575 attach_prop.cancel_btn = cancel_btn;
9578 #undef SET_LABEL_AND_ENTRY
9580 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9582 *cancelled = FALSE;
9583 gtk_main_quit();
9586 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9588 *cancelled = TRUE;
9589 gtk_main_quit();
9592 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9593 gboolean *cancelled)
9595 *cancelled = TRUE;
9596 gtk_main_quit();
9598 return TRUE;
9601 static gboolean attach_property_key_pressed(GtkWidget *widget,
9602 GdkEventKey *event,
9603 gboolean *cancelled)
9605 if (event && event->keyval == GDK_KEY_Escape) {
9606 *cancelled = TRUE;
9607 gtk_main_quit();
9609 if (event && (event->keyval == GDK_KEY_KP_Enter ||
9610 event->keyval == GDK_KEY_Return)) {
9611 *cancelled = FALSE;
9612 gtk_main_quit();
9613 return TRUE;
9615 return FALSE;
9618 static gboolean compose_can_autosave(Compose *compose)
9620 if (compose->privacy_system && compose->use_encryption)
9621 return prefs_common.autosave && prefs_common.autosave_encrypted;
9622 else
9623 return prefs_common.autosave;
9627 * compose_exec_ext_editor:
9629 * Open (and optionally embed) external editor
9631 static void compose_exec_ext_editor(Compose *compose)
9633 gchar *tmp;
9634 #ifndef G_OS_WIN32
9635 GtkWidget *socket;
9636 Window socket_wid = 0;
9637 gchar *p, *s;
9638 #endif /* G_OS_WIN32 */
9639 GPid pid;
9640 GError *error = NULL;
9641 gchar *cmd = NULL;
9642 gchar **argv;
9644 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9645 G_DIR_SEPARATOR, compose);
9647 if (compose_write_body_to_file(compose, tmp) < 0) {
9648 alertpanel_error(_("Could not write the body to file:\n%s"),
9649 tmp);
9650 g_free(tmp);
9651 return;
9654 if (compose_get_ext_editor_uses_socket()) {
9655 #ifndef G_OS_WIN32
9656 /* Only allow one socket */
9657 if (compose->exteditor_socket != NULL) {
9658 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9659 /* Move the focus off of the socket */
9660 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9662 g_free(tmp);
9663 return;
9665 /* Create the receiving GtkSocket */
9666 socket = gtk_socket_new ();
9667 g_signal_connect (G_OBJECT(socket), "plug-removed",
9668 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9669 compose);
9670 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9671 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9672 /* Realize the socket so that we can use its ID */
9673 gtk_widget_realize(socket);
9674 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9675 compose->exteditor_socket = socket;
9676 #else
9677 alertpanel_error(_("Socket communication with an external editor is not available on Windows."));
9678 g_free(tmp);
9679 return;
9680 #endif /* G_OS_WIN32 */
9683 if (compose_get_ext_editor_cmd_valid()) {
9684 if (compose_get_ext_editor_uses_socket()) {
9685 #ifndef G_OS_WIN32
9686 p = g_strdup(prefs_common_get_ext_editor_cmd());
9687 s = strstr(p, "%w");
9688 s[1] = 'u';
9689 if (strstr(p, "%s") < s)
9690 cmd = g_strdup_printf(p, tmp, socket_wid);
9691 else
9692 cmd = g_strdup_printf(p, socket_wid, tmp);
9693 g_free(p);
9694 #endif /* G_OS_WIN32 */
9695 } else {
9696 cmd = g_strdup_printf(prefs_common_get_ext_editor_cmd(), tmp);
9698 } else {
9699 if (prefs_common_get_ext_editor_cmd())
9700 g_warning("external editor command-line is invalid: '%s'",
9701 prefs_common_get_ext_editor_cmd());
9702 cmd = g_strdup_printf(DEFAULT_EDITOR_CMD, tmp);
9705 argv = strsplit_with_quote(cmd, " ", 0);
9707 if (!g_spawn_async(NULL, argv, NULL,
9708 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
9709 NULL, NULL, &pid, &error)) {
9710 alertpanel_error(_("Could not spawn the following "
9711 "external editor command:\n%s\n%s"),
9712 cmd, error ? error->message : _("Unknown error"));
9713 if (error)
9714 g_error_free(error);
9715 g_free(tmp);
9716 g_free(cmd);
9717 g_strfreev(argv);
9718 return;
9720 g_free(cmd);
9721 g_strfreev(argv);
9723 compose->exteditor_file = g_strdup(tmp);
9724 compose->exteditor_pid = pid;
9725 compose->exteditor_tag = g_child_watch_add(pid,
9726 compose_ext_editor_closed_cb,
9727 compose);
9729 compose_set_ext_editor_sensitive(compose, FALSE);
9731 g_free(tmp);
9735 * compose_ext_editor_cb:
9737 * External editor has closed (called by g_child_watch)
9739 static void compose_ext_editor_closed_cb(GPid pid, gint exit_status, gpointer data)
9741 Compose *compose = (Compose *)data;
9742 GError *error = NULL;
9743 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9744 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9745 GtkTextIter start, end;
9746 gchar *chars;
9748 #if GLIB_CHECK_VERSION(2,70,0)
9749 if (!g_spawn_check_wait_status(exit_status, &error)) {
9750 #else
9751 if (!g_spawn_check_exit_status(exit_status, &error)) {
9752 #endif
9753 alertpanel_error(
9754 _("External editor stopped with an error: %s"),
9755 error ? error->message : _("Unknown error"));
9756 if (error)
9757 g_error_free(error);
9759 g_spawn_close_pid(compose->exteditor_pid);
9761 gtk_text_buffer_set_text(buffer, "", -1);
9762 compose_insert_file(compose, compose->exteditor_file);
9763 compose_changed_cb(NULL, compose);
9765 /* Check if we should save the draft or not */
9766 if (compose_can_autosave(compose))
9767 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9769 if (claws_unlink(compose->exteditor_file) < 0)
9770 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9772 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9773 gtk_text_buffer_get_start_iter(buffer, &start);
9774 gtk_text_buffer_get_end_iter(buffer, &end);
9775 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9776 if (chars && strlen(chars) > 0)
9777 compose->modified = TRUE;
9778 g_free(chars);
9780 compose_set_ext_editor_sensitive(compose, TRUE);
9782 g_free(compose->exteditor_file);
9783 compose->exteditor_file = NULL;
9784 compose->exteditor_pid = INVALID_PID;
9785 compose->exteditor_tag = -1;
9786 if (compose->exteditor_socket) {
9787 gtk_widget_destroy(compose->exteditor_socket);
9788 compose->exteditor_socket = NULL;
9793 static gboolean compose_get_ext_editor_cmd_valid()
9795 gboolean has_s = FALSE;
9796 gboolean has_w = FALSE;
9797 const gchar *p = prefs_common_get_ext_editor_cmd();
9798 if (!p)
9799 return FALSE;
9800 while ((p = strchr(p, '%'))) {
9801 p++;
9802 if (*p == 's') {
9803 if (has_s)
9804 return FALSE;
9805 has_s = TRUE;
9806 } else if (*p == 'w') {
9807 if (has_w)
9808 return FALSE;
9809 has_w = TRUE;
9810 } else {
9811 return FALSE;
9814 return TRUE;
9817 static gboolean compose_ext_editor_kill(Compose *compose)
9819 GPid pid = compose->exteditor_pid;
9820 gchar *pidmsg = NULL;
9822 if (pid > 0) {
9823 AlertValue val;
9824 gchar *msg;
9826 pidmsg = g_strdup_printf
9827 #if GLIB_CHECK_VERSION(2, 50, 0)
9828 (_("process id: %" G_PID_FORMAT),
9829 #else
9830 (_("process id: %d"),
9831 #endif
9832 pid);
9834 msg = g_strdup_printf
9835 (_("The external editor is still working.\n"
9836 "Force terminating the process?\n"
9837 "%s"), pidmsg);
9838 val = alertpanel_full(_("Notice"), msg, NULL, _("_No"), NULL, _("_Yes"),
9839 NULL, NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9840 ALERT_WARNING);
9841 g_free(msg);
9843 if (val == G_ALERTALTERNATE) {
9844 g_source_remove(compose->exteditor_tag);
9846 #ifdef G_OS_WIN32
9847 if (!TerminateProcess(compose->exteditor_pid, 0))
9848 perror("TerminateProcess");
9849 #else
9850 if (kill(pid, SIGTERM) < 0) perror("kill");
9851 waitpid(compose->exteditor_pid, NULL, 0);
9852 #endif /* G_OS_WIN32 */
9854 g_warning("terminated %s, temporary file: %s",
9855 pidmsg, compose->exteditor_file);
9856 g_spawn_close_pid(compose->exteditor_pid);
9858 compose_set_ext_editor_sensitive(compose, TRUE);
9860 g_free(compose->exteditor_file);
9861 compose->exteditor_file = NULL;
9862 compose->exteditor_pid = INVALID_PID;
9863 compose->exteditor_tag = -1;
9864 } else {
9865 g_free(pidmsg);
9866 return FALSE;
9870 if (pidmsg)
9871 g_free(pidmsg);
9872 return TRUE;
9875 static char *ext_editor_menu_entries[] = {
9876 "Menu/Message/Send",
9877 "Menu/Message/SendLater",
9878 "Menu/Message/InsertFile",
9879 "Menu/Message/InsertSig",
9880 "Menu/Message/ReplaceSig",
9881 "Menu/Message/Save",
9882 "Menu/Message/Print",
9883 "Menu/Edit",
9884 #if USE_ENCHANT
9885 "Menu/Spelling",
9886 #endif
9887 "Menu/Tools/ShowRuler",
9888 "Menu/Tools/Actions",
9889 "Menu/Help",
9890 NULL
9893 static void compose_set_ext_editor_sensitive(Compose *compose,
9894 gboolean sensitive)
9896 int i;
9898 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9899 cm_menu_set_sensitive_full(compose->ui_manager,
9900 ext_editor_menu_entries[i], sensitive);
9903 if (compose_get_ext_editor_uses_socket()) {
9904 if (sensitive) {
9905 if (compose->exteditor_socket)
9906 gtk_widget_hide(compose->exteditor_socket);
9907 gtk_widget_show(compose->scrolledwin);
9908 if (prefs_common.show_ruler)
9909 gtk_widget_show(compose->ruler_hbox);
9910 /* Fix the focus, as it doesn't go anywhere when the
9911 * socket is hidden or destroyed */
9912 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9913 } else {
9914 g_assert (compose->exteditor_socket != NULL);
9915 /* Fix the focus, as it doesn't go anywhere when the
9916 * edit box is hidden */
9917 if (gtk_widget_is_focus(compose->text))
9918 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9919 gtk_widget_hide(compose->scrolledwin);
9920 gtk_widget_hide(compose->ruler_hbox);
9921 gtk_widget_show(compose->exteditor_socket);
9923 } else {
9924 gtk_widget_set_sensitive(compose->text, sensitive);
9926 if (compose->toolbar->send_btn)
9927 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9928 if (compose->toolbar->sendl_btn)
9929 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9930 if (compose->toolbar->draft_btn)
9931 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9932 if (compose->toolbar->insert_btn)
9933 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9934 if (compose->toolbar->sig_btn)
9935 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9936 if (compose->toolbar->exteditor_btn)
9937 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9938 if (compose->toolbar->linewrap_current_btn)
9939 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9940 if (compose->toolbar->linewrap_all_btn)
9941 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9944 static gboolean compose_get_ext_editor_uses_socket()
9946 return (prefs_common_get_ext_editor_cmd() &&
9947 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9950 #ifndef G_OS_WIN32
9951 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9953 compose->exteditor_socket = NULL;
9954 /* returning FALSE allows destruction of the socket */
9955 return FALSE;
9957 #endif /* G_OS_WIN32 */
9960 * compose_undo_state_changed:
9962 * Change the sensivity of the menuentries undo and redo
9964 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9965 gint redo_state, gpointer data)
9967 Compose *compose = (Compose *)data;
9969 switch (undo_state) {
9970 case UNDO_STATE_TRUE:
9971 if (!undostruct->undo_state) {
9972 undostruct->undo_state = TRUE;
9973 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9975 break;
9976 case UNDO_STATE_FALSE:
9977 if (undostruct->undo_state) {
9978 undostruct->undo_state = FALSE;
9979 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9981 break;
9982 case UNDO_STATE_UNCHANGED:
9983 break;
9984 case UNDO_STATE_REFRESH:
9985 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9986 break;
9987 default:
9988 g_warning("undo state not recognized");
9989 break;
9992 switch (redo_state) {
9993 case UNDO_STATE_TRUE:
9994 if (!undostruct->redo_state) {
9995 undostruct->redo_state = TRUE;
9996 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9998 break;
9999 case UNDO_STATE_FALSE:
10000 if (undostruct->redo_state) {
10001 undostruct->redo_state = FALSE;
10002 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
10004 break;
10005 case UNDO_STATE_UNCHANGED:
10006 break;
10007 case UNDO_STATE_REFRESH:
10008 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
10009 break;
10010 default:
10011 g_warning("redo state not recognized");
10012 break;
10016 /* callback functions */
10018 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10019 GtkAllocation *allocation,
10020 GtkPaned *paned)
10022 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10025 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10026 * includes "non-client" (windows-izm) in calculation, so this calculation
10027 * may not be accurate.
10029 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10030 GtkAllocation *allocation,
10031 GtkSHRuler *shruler)
10033 if (prefs_common.show_ruler) {
10034 gint char_width = 0, char_height = 0;
10035 gint line_width_in_chars;
10037 gtkut_get_font_size(GTK_WIDGET(widget),
10038 &char_width, &char_height);
10039 line_width_in_chars =
10040 (allocation->width - allocation->x) / char_width;
10042 /* got the maximum */
10043 gtk_shruler_set_range(GTK_SHRULER(shruler),
10044 0.0, line_width_in_chars, 0);
10047 return TRUE;
10050 typedef struct {
10051 gchar *header;
10052 gchar *entry;
10053 ComposePrefType type;
10054 gboolean entry_marked;
10055 } HeaderEntryState;
10057 static void account_activated(GtkComboBox *optmenu, gpointer data)
10059 Compose *compose = (Compose *)data;
10061 PrefsAccount *ac;
10062 gchar *folderidentifier;
10063 gint account_id = 0;
10064 GtkTreeModel *menu;
10065 GtkTreeIter iter;
10066 GSList *list, *saved_list = NULL;
10067 HeaderEntryState *state;
10069 /* Get ID of active account in the combo box */
10070 menu = gtk_combo_box_get_model(optmenu);
10071 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10072 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10074 ac = account_find_from_id(account_id);
10075 cm_return_if_fail(ac != NULL);
10077 if (ac != compose->account) {
10078 compose_select_account(compose, ac, FALSE);
10080 for (list = compose->header_list; list; list = list->next) {
10081 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10083 if (hentry->type == PREF_ACCOUNT || !list->next) {
10084 compose_destroy_headerentry(compose, hentry);
10085 continue;
10087 state = g_malloc0(sizeof(HeaderEntryState));
10088 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10089 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10090 state->entry = gtk_editable_get_chars(
10091 GTK_EDITABLE(hentry->entry), 0, -1);
10092 state->type = hentry->type;
10094 saved_list = g_slist_append(saved_list, state);
10095 compose_destroy_headerentry(compose, hentry);
10098 compose->header_last = NULL;
10099 g_slist_free(compose->header_list);
10100 compose->header_list = NULL;
10101 compose->header_nextrow = 1;
10102 compose_create_header_entry(compose);
10104 if (ac->set_autocc && ac->auto_cc)
10105 compose_entry_append(compose, ac->auto_cc,
10106 COMPOSE_CC, PREF_ACCOUNT);
10107 if (ac->set_autobcc && ac->auto_bcc)
10108 compose_entry_append(compose, ac->auto_bcc,
10109 COMPOSE_BCC, PREF_ACCOUNT);
10110 if (ac->set_autoreplyto && ac->auto_replyto)
10111 compose_entry_append(compose, ac->auto_replyto,
10112 COMPOSE_REPLYTO, PREF_ACCOUNT);
10114 for (list = saved_list; list; list = list->next) {
10115 state = (HeaderEntryState *) list->data;
10117 compose_add_header_entry(compose, state->header,
10118 state->entry, state->type);
10120 g_free(state->header);
10121 g_free(state->entry);
10122 g_free(state);
10124 g_slist_free(saved_list);
10126 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10127 (ac->protocol == A_NNTP) ?
10128 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10131 /* Set message save folder */
10132 compose_set_save_to(compose, NULL);
10133 if (compose->folder && compose->folder->prefs && compose->folder->prefs->save_copy_to_folder) {
10134 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10135 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10136 folderidentifier = folder_item_get_identifier(compose->folder);
10137 compose_set_save_to(compose, folderidentifier);
10138 g_free(folderidentifier);
10139 } else if (account_get_special_folder(compose->account, F_OUTBOX)) {
10140 if (compose->account->set_sent_folder || prefs_common.savemsg)
10141 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10142 else
10143 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE);
10144 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10145 folderidentifier = folder_item_get_identifier(account_get_special_folder
10146 (compose->account, F_OUTBOX));
10147 compose_set_save_to(compose, folderidentifier);
10148 g_free(folderidentifier);
10152 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10153 GtkTreeViewColumn *column, Compose *compose)
10155 compose_attach_property(NULL, compose);
10158 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10159 gpointer data)
10161 Compose *compose = (Compose *)data;
10162 GtkTreeSelection *attach_selection;
10163 gint attach_nr_selected;
10164 GtkTreePath *path;
10166 if (!event) return FALSE;
10168 if (event->button == 3) {
10169 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10170 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10172 /* If no rows, or just one row is selected, right-click should
10173 * open menu relevant to the row being right-clicked on. We
10174 * achieve that by selecting the clicked row first. If more
10175 * than one row is selected, we shouldn't modify the selection,
10176 * as user may want to remove selected rows (attachments). */
10177 if (attach_nr_selected < 2) {
10178 gtk_tree_selection_unselect_all(attach_selection);
10179 attach_nr_selected = 0;
10180 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10181 event->x, event->y, &path, NULL, NULL, NULL);
10182 if (path != NULL) {
10183 gtk_tree_selection_select_path(attach_selection, path);
10184 gtk_tree_path_free(path);
10185 attach_nr_selected++;
10189 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10190 /* Properties menu item makes no sense with more than one row
10191 * selected, the properties dialog can only edit one attachment. */
10192 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10194 gtk_menu_popup_at_pointer(GTK_MENU(compose->popupmenu), NULL);
10196 return TRUE;
10199 return FALSE;
10202 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10203 gpointer data)
10205 Compose *compose = (Compose *)data;
10207 if (!event) return FALSE;
10209 switch (event->keyval) {
10210 case GDK_KEY_Delete:
10211 compose_attach_remove_selected(NULL, compose);
10212 break;
10214 return FALSE;
10217 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10219 toolbar_comp_set_sensitive(compose, allow);
10220 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10221 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10222 #if USE_ENCHANT
10223 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10224 #endif
10225 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10226 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10227 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10229 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10233 static void compose_send_cb(GtkAction *action, gpointer data)
10235 Compose *compose = (Compose *)data;
10237 #ifdef G_OS_UNIX
10238 if (compose->exteditor_tag != -1) {
10239 debug_print("ignoring send: external editor still open\n");
10240 return;
10242 #endif
10243 if (prefs_common.work_offline &&
10244 !inc_offline_should_override(TRUE,
10245 _("Claws Mail needs network access in order "
10246 "to send this email.")))
10247 return;
10249 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10250 g_source_remove(compose->draft_timeout_tag);
10251 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10254 compose_send(compose);
10257 static void compose_send_later_cb(GtkAction *action, gpointer data)
10259 Compose *compose = (Compose *)data;
10260 ComposeQueueResult val;
10262 inc_lock();
10263 compose_allow_user_actions(compose, FALSE);
10264 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10265 compose_allow_user_actions(compose, TRUE);
10266 inc_unlock();
10268 if (val == COMPOSE_QUEUE_SUCCESS) {
10269 compose_close(compose);
10270 } else {
10271 _display_queue_error(val);
10274 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10277 #define DRAFTED_AT_EXIT "drafted_at_exit"
10278 static void compose_register_draft(MsgInfo *info)
10280 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10281 DRAFTED_AT_EXIT, NULL);
10282 FILE *fp = claws_fopen(filepath, "ab");
10284 if (fp) {
10285 gchar *name = folder_item_get_identifier(info->folder);
10286 fprintf(fp, "%s\t%d\n", name, info->msgnum);
10287 g_free(name);
10288 claws_fclose(fp);
10291 g_free(filepath);
10294 gboolean compose_draft (gpointer data, guint action)
10296 Compose *compose = (Compose *)data;
10297 FolderItem *draft;
10298 FolderItemPrefs *prefs;
10299 gchar *tmp;
10300 gchar *sheaders;
10301 gint msgnum;
10302 MsgFlags flag = {0, 0};
10303 static gboolean lock = FALSE;
10304 MsgInfo *newmsginfo;
10305 FILE *fp;
10306 gboolean target_locked = FALSE;
10307 gboolean err = FALSE;
10308 gint filemode = 0;
10310 if (lock) return FALSE;
10312 if (compose->sending)
10313 return TRUE;
10315 draft = account_get_special_folder(compose->account, F_DRAFT);
10316 cm_return_val_if_fail(draft != NULL, FALSE);
10318 if (!g_mutex_trylock(&compose->mutex)) {
10319 /* we don't want to lock the mutex once it's available,
10320 * because as the only other part of compose.c locking
10321 * it is compose_close - which means once unlocked,
10322 * the compose struct will be freed */
10323 debug_print("couldn't lock mutex, probably sending\n");
10324 return FALSE;
10327 lock = TRUE;
10329 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10330 G_DIR_SEPARATOR, compose);
10331 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10332 FILE_OP_ERROR(tmp, "claws_fopen");
10333 goto warn_err;
10336 /* chmod for security unless folder chmod is set */
10337 prefs = draft->prefs;
10338 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) {
10339 filemode = prefs->folder_chmod;
10340 if (filemode & S_IRGRP) filemode |= S_IWGRP;
10341 if (filemode & S_IROTH) filemode |= S_IWOTH;
10342 if (chmod(tmp, filemode) < 0)
10343 FILE_OP_ERROR(tmp, "chmod");
10344 } else if (change_file_mode_rw(fp, tmp) < 0) {
10345 FILE_OP_ERROR(tmp, "chmod");
10346 g_warning("can't change file mode");
10349 /* Save draft infos */
10350 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10351 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10353 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10354 gchar *savefolderid;
10356 savefolderid = compose_get_save_to(compose);
10357 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10358 g_free(savefolderid);
10360 if (compose->return_receipt) {
10361 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10363 if (compose->privacy_system) {
10364 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10365 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10366 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10369 /* Message-ID of message replying to */
10370 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10371 gchar *folderid = NULL;
10373 if (compose->replyinfo->folder)
10374 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10375 if (folderid == NULL)
10376 folderid = g_strdup("NULL");
10378 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10379 g_free(folderid);
10381 /* Message-ID of message forwarding to */
10382 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10383 gchar *folderid = NULL;
10385 if (compose->fwdinfo->folder)
10386 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10387 if (folderid == NULL)
10388 folderid = g_strdup("NULL");
10390 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10391 g_free(folderid);
10394 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10395 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10397 sheaders = compose_get_manual_headers_info(compose);
10398 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10399 g_free(sheaders);
10401 /* end of headers */
10402 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10404 if (err) {
10405 claws_fclose(fp);
10406 goto warn_err;
10409 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10410 claws_fclose(fp);
10411 goto warn_err;
10413 if (claws_safe_fclose(fp) == EOF) {
10414 goto warn_err;
10417 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10418 if (compose->targetinfo) {
10419 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10420 if (target_locked)
10421 flag.perm_flags |= MSG_LOCKED;
10423 flag.tmp_flags = MSG_DRAFT;
10425 folder_item_scan(draft);
10426 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10427 MsgInfo *tmpinfo = NULL;
10428 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10429 if (compose->msgid) {
10430 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10432 if (tmpinfo) {
10433 msgnum = tmpinfo->msgnum;
10434 procmsg_msginfo_free(&tmpinfo);
10435 debug_print("got draft msgnum %d from scanning\n", msgnum);
10436 } else {
10437 debug_print("didn't get draft msgnum after scanning\n");
10439 } else {
10440 debug_print("got draft msgnum %d from adding\n", msgnum);
10442 if (msgnum < 0) {
10443 warn_err:
10444 claws_unlink(tmp);
10445 g_free(tmp);
10446 if (action != COMPOSE_AUTO_SAVE) {
10447 if (action != COMPOSE_DRAFT_FOR_EXIT)
10448 alertpanel_error(_("Could not save draft."));
10449 else {
10450 AlertValue val;
10451 gtkut_window_popup(compose->window);
10452 val = alertpanel_full(_("Could not save draft"),
10453 _("Could not save draft.\n"
10454 "Do you want to cancel exit or discard this email?"),
10455 NULL, _("_Cancel exit"), NULL, _("_Discard email"),
10456 NULL, NULL, ALERTFOCUS_FIRST, FALSE, NULL, ALERT_QUESTION);
10457 if (val == G_ALERTALTERNATE) {
10458 lock = FALSE;
10459 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10460 compose_close(compose);
10461 return TRUE;
10462 } else {
10463 lock = FALSE;
10464 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10465 return FALSE;
10469 goto unlock;
10471 g_free(tmp);
10473 if (compose->mode == COMPOSE_REEDIT) {
10474 compose_remove_reedit_target(compose, TRUE);
10477 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10479 if (newmsginfo) {
10480 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10481 if (target_locked)
10482 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10483 else
10484 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10485 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10486 procmsg_msginfo_set_flags(newmsginfo, 0,
10487 MSG_HAS_ATTACHMENT);
10489 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10490 compose_register_draft(newmsginfo);
10492 procmsg_msginfo_free(&newmsginfo);
10495 folder_item_scan(draft);
10497 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10498 lock = FALSE;
10499 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10500 compose_close(compose);
10501 return TRUE;
10502 } else {
10503 #ifdef G_OS_WIN32
10504 GFile *f;
10505 GFileInfo *fi;
10506 GTimeVal tv;
10507 GError *error = NULL;
10508 #else
10509 GStatBuf s;
10510 #endif
10511 gchar *path;
10512 goffset size, mtime;
10514 path = folder_item_fetch_msg(draft, msgnum);
10515 if (path == NULL) {
10516 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10517 goto unlock;
10519 #ifdef G_OS_WIN32
10520 f = g_file_new_for_path(path);
10521 fi = g_file_query_info(f, "standard::size,time::modified",
10522 G_FILE_QUERY_INFO_NONE, NULL, &error);
10523 if (error != NULL) {
10524 debug_print("couldn't query file info for '%s': %s\n",
10525 path, error->message);
10526 g_error_free(error);
10527 g_free(path);
10528 g_object_unref(f);
10529 goto unlock;
10531 size = g_file_info_get_size(fi);
10532 g_file_info_get_modification_time(fi, &tv);
10533 mtime = tv.tv_sec;
10534 g_object_unref(fi);
10535 g_object_unref(f);
10536 #else
10537 if (g_stat(path, &s) < 0) {
10538 FILE_OP_ERROR(path, "stat");
10539 g_free(path);
10540 goto unlock;
10542 size = s.st_size;
10543 mtime = s.st_mtime;
10544 #endif
10545 g_free(path);
10547 procmsg_msginfo_free(&(compose->targetinfo));
10548 compose->targetinfo = procmsg_msginfo_new();
10549 compose->targetinfo->msgnum = msgnum;
10550 compose->targetinfo->size = size;
10551 compose->targetinfo->mtime = mtime;
10552 compose->targetinfo->folder = draft;
10553 if (target_locked)
10554 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10555 compose->mode = COMPOSE_REEDIT;
10557 if (action == COMPOSE_AUTO_SAVE) {
10558 compose->modified = FALSE;
10559 compose->autosaved_draft = compose->targetinfo;
10561 compose_set_title(compose);
10563 unlock:
10564 lock = FALSE;
10565 g_mutex_unlock(&compose->mutex);
10566 return TRUE;
10569 void compose_clear_exit_drafts(void)
10571 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10572 DRAFTED_AT_EXIT, NULL);
10573 if (is_file_exist(filepath))
10574 claws_unlink(filepath);
10576 g_free(filepath);
10579 void compose_reopen_exit_drafts(void)
10581 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10582 DRAFTED_AT_EXIT, NULL);
10583 FILE *fp = claws_fopen(filepath, "rb");
10584 gchar buf[1024];
10586 if (fp) {
10587 while (claws_fgets(buf, sizeof(buf), fp)) {
10588 gchar **parts = g_strsplit(buf, "\t", 2);
10589 const gchar *folder = parts[0];
10590 int msgnum = parts[1] ? atoi(parts[1]):-1;
10592 if (folder && *folder && msgnum > -1) {
10593 FolderItem *item = folder_find_item_from_identifier(folder);
10594 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10595 if (info)
10596 compose_reedit(info, FALSE);
10598 g_strfreev(parts);
10600 claws_fclose(fp);
10602 g_free(filepath);
10603 compose_clear_exit_drafts();
10606 static void compose_save_cb(GtkAction *action, gpointer data)
10608 Compose *compose = (Compose *)data;
10609 compose_draft(compose, COMPOSE_KEEP_EDITING);
10610 compose->rmode = COMPOSE_REEDIT;
10611 compose->modified = FALSE;
10612 compose_set_title(compose);
10615 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10617 if (compose && file_list) {
10618 GList *tmp;
10620 for ( tmp = file_list; tmp; tmp = tmp->next) {
10621 gchar *file = (gchar *) tmp->data;
10622 gchar *utf8_filename = conv_filename_to_utf8(file);
10623 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10624 compose_changed_cb(NULL, compose);
10625 if (free_data) {
10626 g_free(file);
10627 tmp->data = NULL;
10629 g_free(utf8_filename);
10634 static void compose_attach_cb(GtkAction *action, gpointer data)
10636 Compose *compose = (Compose *)data;
10637 GList *file_list;
10639 if (compose->redirect_filename != NULL)
10640 return;
10642 /* Set focus_window properly, in case we were called via popup menu,
10643 * which unsets it (via focus_out_event callback on compose window). */
10644 manage_window_focus_in(compose->window, NULL, NULL);
10646 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10648 if (file_list) {
10649 compose_attach_from_list(compose, file_list, TRUE);
10650 g_list_free(file_list);
10654 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10656 Compose *compose = (Compose *)data;
10657 GList *file_list;
10658 gint files_inserted = 0;
10660 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10662 if (file_list) {
10663 GList *tmp;
10665 for ( tmp = file_list; tmp; tmp = tmp->next) {
10666 gchar *file = (gchar *) tmp->data;
10667 gchar *filedup = g_strdup(file);
10668 gchar *shortfile = g_path_get_basename(filedup);
10669 ComposeInsertResult res;
10670 /* insert the file if the file is short or if the user confirmed that
10671 he/she wants to insert the large file */
10672 res = compose_insert_file(compose, file);
10673 if (res == COMPOSE_INSERT_READ_ERROR) {
10674 alertpanel_error(_("File '%s' could not be read."), shortfile);
10675 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10676 alertpanel_error(_("File '%s' contained invalid characters\n"
10677 "for the current encoding, insertion may be incorrect."),
10678 shortfile);
10679 } else if (res == COMPOSE_INSERT_SUCCESS)
10680 files_inserted++;
10682 g_free(shortfile);
10683 g_free(filedup);
10684 g_free(file);
10686 g_list_free(file_list);
10689 #ifdef USE_ENCHANT
10690 if (files_inserted > 0 && compose->gtkaspell &&
10691 compose->gtkaspell->check_while_typing)
10692 gtkaspell_highlight_all(compose->gtkaspell);
10693 #endif
10696 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10698 Compose *compose = (Compose *)data;
10700 compose_insert_sig(compose, FALSE);
10703 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10705 Compose *compose = (Compose *)data;
10707 compose_insert_sig(compose, TRUE);
10710 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10711 gpointer data)
10713 gint x, y;
10714 Compose *compose = (Compose *)data;
10716 gtk_window_get_position(GTK_WINDOW(widget), &x, &y);
10717 if (!compose->batch) {
10718 prefs_common.compose_x = x;
10719 prefs_common.compose_y = y;
10721 if (compose->sending || compose->updating)
10722 return TRUE;
10723 compose_close_cb(NULL, compose);
10724 return TRUE;
10727 void compose_close_toolbar(Compose *compose)
10729 compose_close_cb(NULL, compose);
10732 static void compose_close_cb(GtkAction *action, gpointer data)
10734 Compose *compose = (Compose *)data;
10735 AlertValue val;
10737 if (compose->exteditor_tag != -1) {
10738 if (!compose_ext_editor_kill(compose))
10739 return;
10742 if (compose->modified) {
10743 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10744 if (!g_mutex_trylock(&compose->mutex)) {
10745 /* we don't want to lock the mutex once it's available,
10746 * because as the only other part of compose.c locking
10747 * it is compose_close - which means once unlocked,
10748 * the compose struct will be freed */
10749 debug_print("couldn't lock mutex, probably sending\n");
10750 return;
10752 if (!reedit || (compose->folder != NULL && compose->folder->stype == F_DRAFT)) {
10753 val = alertpanel(_("Discard message"),
10754 _("This message has been modified. Discard it?"),
10755 NULL, _("_Discard"), NULL, _("_Save to Drafts"), NULL, _("_Cancel"),
10756 ALERTFOCUS_FIRST);
10757 } else {
10758 val = alertpanel(_("Save changes"),
10759 _("This message has been modified. Save the latest changes?"),
10760 NULL, _("_Don't save"), NULL, _("_Save to Drafts"), NULL, _("_Cancel"),
10761 ALERTFOCUS_SECOND);
10763 g_mutex_unlock(&compose->mutex);
10764 switch (val) {
10765 case G_ALERTDEFAULT:
10766 if (compose_can_autosave(compose) && !reedit)
10767 compose_remove_draft(compose);
10768 break;
10769 case G_ALERTALTERNATE:
10770 compose_draft(data, COMPOSE_QUIT_EDITING);
10771 return;
10772 default:
10773 return;
10777 compose_close(compose);
10780 static void compose_print_cb(GtkAction *action, gpointer data)
10782 Compose *compose = (Compose *) data;
10784 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10785 if (compose->targetinfo)
10786 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10789 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10791 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10792 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10793 Compose *compose = (Compose *) data;
10795 if (active)
10796 compose->out_encoding = (CharSet)value;
10799 static void compose_address_cb(GtkAction *action, gpointer data)
10801 Compose *compose = (Compose *)data;
10803 #ifndef USE_ALT_ADDRBOOK
10804 addressbook_open(compose);
10805 #else
10806 GError* error = NULL;
10807 addressbook_connect_signals(compose);
10808 addressbook_dbus_open(TRUE, &error);
10809 if (error) {
10810 g_warning("%s", error->message);
10811 g_error_free(error);
10813 #endif
10816 static void about_show_cb(GtkAction *action, gpointer data)
10818 about_show();
10821 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10823 Compose *compose = (Compose *)data;
10824 Template *tmpl;
10825 gchar *msg;
10826 AlertValue val;
10828 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10829 cm_return_if_fail(tmpl != NULL);
10831 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10832 tmpl->name);
10833 val = alertpanel(_("Apply template"), msg,
10834 NULL, _("_Replace"), NULL, _("_Insert"), NULL, _("_Cancel"),
10835 ALERTFOCUS_FIRST);
10836 g_free(msg);
10838 if (val == G_ALERTDEFAULT)
10839 compose_template_apply(compose, tmpl, TRUE);
10840 else if (val == G_ALERTALTERNATE)
10841 compose_template_apply(compose, tmpl, FALSE);
10844 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10846 Compose *compose = (Compose *)data;
10848 if (compose->exteditor_tag != -1) {
10849 debug_print("ignoring open external editor: external editor still open\n");
10850 return;
10852 compose_exec_ext_editor(compose);
10855 static void compose_undo_cb(GtkAction *action, gpointer data)
10857 Compose *compose = (Compose *)data;
10858 gboolean prev_autowrap = compose->autowrap;
10860 compose->autowrap = FALSE;
10861 undo_undo(compose->undostruct);
10862 compose->autowrap = prev_autowrap;
10865 static void compose_redo_cb(GtkAction *action, gpointer data)
10867 Compose *compose = (Compose *)data;
10868 gboolean prev_autowrap = compose->autowrap;
10870 compose->autowrap = FALSE;
10871 undo_redo(compose->undostruct);
10872 compose->autowrap = prev_autowrap;
10875 static void entry_cut_clipboard(GtkWidget *entry)
10877 if (GTK_IS_EDITABLE(entry))
10878 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10879 else if (GTK_IS_TEXT_VIEW(entry))
10880 gtk_text_buffer_cut_clipboard(
10881 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10882 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10883 TRUE);
10886 static void entry_copy_clipboard(GtkWidget *entry)
10888 if (GTK_IS_EDITABLE(entry))
10889 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10890 else if (GTK_IS_TEXT_VIEW(entry))
10891 gtk_text_buffer_copy_clipboard(
10892 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10893 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10896 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10897 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10899 if (GTK_IS_TEXT_VIEW(entry)) {
10900 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10901 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10902 GtkTextIter start_iter, end_iter;
10903 gint start, end;
10904 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10906 if (contents == NULL)
10907 return;
10909 glong len = g_utf8_strlen(contents, -1);
10910 if (len > MAX_ALLOCA_MEM_SIZE) {
10911 alertpanel_error(_("Size of pasted text exceeds limit (%dKiB) for paste.\n"
10912 "Attach as file instead."),
10913 (MAX_ALLOCA_MEM_SIZE / 1024));
10914 return;
10916 /* we shouldn't delete the selection when middle-click-pasting, or we
10917 * can't mid-click-paste our own selection */
10918 if (clip != GDK_SELECTION_PRIMARY) {
10919 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10920 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10923 if (insert_place == NULL) {
10924 /* if insert_place isn't specified, insert at the cursor.
10925 * used for Ctrl-V pasting */
10926 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10927 start = gtk_text_iter_get_offset(&start_iter);
10928 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10929 } else {
10930 /* if insert_place is specified, paste here.
10931 * used for mid-click-pasting */
10932 start = gtk_text_iter_get_offset(insert_place);
10933 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10934 if (prefs_common.primary_paste_unselects)
10935 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10938 if (!wrap) {
10939 /* paste unwrapped: mark the paste so it's not wrapped later */
10940 end = start + strlen(contents);
10941 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10942 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10943 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10944 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10945 /* rewrap paragraph now (after a mid-click-paste) */
10946 mark_start = gtk_text_buffer_get_insert(buffer);
10947 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10948 gtk_text_iter_backward_char(&start_iter);
10949 compose_beautify_paragraph(compose, &start_iter, TRUE);
10951 } else if (GTK_IS_EDITABLE(entry))
10952 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10954 compose->modified = TRUE;
10957 static void entry_allsel(GtkWidget *entry)
10959 if (GTK_IS_EDITABLE(entry))
10960 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10961 else if (GTK_IS_TEXT_VIEW(entry)) {
10962 GtkTextIter startiter, enditer;
10963 GtkTextBuffer *textbuf;
10965 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10966 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10967 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10969 gtk_text_buffer_move_mark_by_name(textbuf,
10970 "selection_bound", &startiter);
10971 gtk_text_buffer_move_mark_by_name(textbuf,
10972 "insert", &enditer);
10976 static void compose_cut_cb(GtkAction *action, gpointer data)
10978 Compose *compose = (Compose *)data;
10979 if (compose->focused_editable
10980 #ifndef GENERIC_UMPC
10981 && gtk_widget_has_focus(compose->focused_editable)
10982 #endif
10984 entry_cut_clipboard(compose->focused_editable);
10987 static void compose_copy_cb(GtkAction *action, gpointer data)
10989 Compose *compose = (Compose *)data;
10990 if (compose->focused_editable
10991 #ifndef GENERIC_UMPC
10992 && gtk_widget_has_focus(compose->focused_editable)
10993 #endif
10995 entry_copy_clipboard(compose->focused_editable);
10998 static void compose_paste_cb(GtkAction *action, gpointer data)
11000 Compose *compose = (Compose *)data;
11001 gint prev_autowrap;
11002 GtkTextBuffer *buffer;
11003 BLOCK_WRAP();
11004 if (compose->focused_editable
11005 #ifndef GENERIC_UMPC
11006 && gtk_widget_has_focus(compose->focused_editable)
11007 #endif
11009 entry_paste_clipboard(compose, compose->focused_editable,
11010 prefs_common.linewrap_pastes,
11011 GDK_SELECTION_CLIPBOARD, NULL);
11012 UNBLOCK_WRAP();
11014 #ifdef USE_ENCHANT
11015 if (
11016 #ifndef GENERIC_UMPC
11017 gtk_widget_has_focus(compose->text) &&
11018 #endif
11019 compose->gtkaspell &&
11020 compose->gtkaspell->check_while_typing)
11021 gtkaspell_highlight_all(compose->gtkaspell);
11022 #endif
11025 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11027 Compose *compose = (Compose *)data;
11028 gint wrap_quote = prefs_common.linewrap_quote;
11029 if (compose->focused_editable
11030 #ifndef GENERIC_UMPC
11031 && gtk_widget_has_focus(compose->focused_editable)
11032 #endif
11034 /* let text_insert() (called directly or at a later time
11035 * after the gtk_editable_paste_clipboard) know that
11036 * text is to be inserted as a quotation. implemented
11037 * by using a simple refcount... */
11038 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11039 G_OBJECT(compose->focused_editable),
11040 "paste_as_quotation"));
11041 g_object_set_data(G_OBJECT(compose->focused_editable),
11042 "paste_as_quotation",
11043 GINT_TO_POINTER(paste_as_quotation + 1));
11044 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11045 entry_paste_clipboard(compose, compose->focused_editable,
11046 prefs_common.linewrap_pastes,
11047 GDK_SELECTION_CLIPBOARD, NULL);
11048 prefs_common.linewrap_quote = wrap_quote;
11052 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11054 Compose *compose = (Compose *)data;
11055 gint prev_autowrap;
11056 GtkTextBuffer *buffer;
11057 BLOCK_WRAP();
11058 if (compose->focused_editable
11059 #ifndef GENERIC_UMPC
11060 && gtk_widget_has_focus(compose->focused_editable)
11061 #endif
11063 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11064 GDK_SELECTION_CLIPBOARD, NULL);
11065 UNBLOCK_WRAP();
11067 #ifdef USE_ENCHANT
11068 if (
11069 #ifndef GENERIC_UMPC
11070 gtk_widget_has_focus(compose->text) &&
11071 #endif
11072 compose->gtkaspell &&
11073 compose->gtkaspell->check_while_typing)
11074 gtkaspell_highlight_all(compose->gtkaspell);
11075 #endif
11078 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11080 Compose *compose = (Compose *)data;
11081 gint prev_autowrap;
11082 GtkTextBuffer *buffer;
11083 BLOCK_WRAP();
11084 if (compose->focused_editable
11085 #ifndef GENERIC_UMPC
11086 && gtk_widget_has_focus(compose->focused_editable)
11087 #endif
11089 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11090 GDK_SELECTION_CLIPBOARD, NULL);
11091 UNBLOCK_WRAP();
11093 #ifdef USE_ENCHANT
11094 if (
11095 #ifndef GENERIC_UMPC
11096 gtk_widget_has_focus(compose->text) &&
11097 #endif
11098 compose->gtkaspell &&
11099 compose->gtkaspell->check_while_typing)
11100 gtkaspell_highlight_all(compose->gtkaspell);
11101 #endif
11104 static void compose_allsel_cb(GtkAction *action, gpointer data)
11106 Compose *compose = (Compose *)data;
11107 if (compose->focused_editable
11108 #ifndef GENERIC_UMPC
11109 && gtk_widget_has_focus(compose->focused_editable)
11110 #endif
11112 entry_allsel(compose->focused_editable);
11115 static void textview_move_beginning_of_line (GtkTextView *text)
11117 GtkTextBuffer *buffer;
11118 GtkTextMark *mark;
11119 GtkTextIter ins;
11121 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11123 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11124 mark = gtk_text_buffer_get_insert(buffer);
11125 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11126 gtk_text_iter_set_line_offset(&ins, 0);
11127 gtk_text_buffer_place_cursor(buffer, &ins);
11130 static void textview_move_forward_character (GtkTextView *text)
11132 GtkTextBuffer *buffer;
11133 GtkTextMark *mark;
11134 GtkTextIter ins;
11136 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11138 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11139 mark = gtk_text_buffer_get_insert(buffer);
11140 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11141 if (gtk_text_iter_forward_cursor_position(&ins))
11142 gtk_text_buffer_place_cursor(buffer, &ins);
11145 static void textview_move_backward_character (GtkTextView *text)
11147 GtkTextBuffer *buffer;
11148 GtkTextMark *mark;
11149 GtkTextIter ins;
11151 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11153 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11154 mark = gtk_text_buffer_get_insert(buffer);
11155 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11156 if (gtk_text_iter_backward_cursor_position(&ins))
11157 gtk_text_buffer_place_cursor(buffer, &ins);
11160 static void textview_move_forward_word (GtkTextView *text)
11162 GtkTextBuffer *buffer;
11163 GtkTextMark *mark;
11164 GtkTextIter ins;
11165 gint count;
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 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11173 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11174 gtk_text_iter_backward_word_start(&ins);
11175 gtk_text_buffer_place_cursor(buffer, &ins);
11179 static void textview_move_backward_word (GtkTextView *text)
11181 GtkTextBuffer *buffer;
11182 GtkTextMark *mark;
11183 GtkTextIter ins;
11185 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11187 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11188 mark = gtk_text_buffer_get_insert(buffer);
11189 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11190 if (gtk_text_iter_backward_word_starts(&ins, 1))
11191 gtk_text_buffer_place_cursor(buffer, &ins);
11194 static void textview_move_end_of_line (GtkTextView *text)
11196 GtkTextBuffer *buffer;
11197 GtkTextMark *mark;
11198 GtkTextIter ins;
11200 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11202 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11203 mark = gtk_text_buffer_get_insert(buffer);
11204 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11205 if (gtk_text_iter_forward_to_line_end(&ins))
11206 gtk_text_buffer_place_cursor(buffer, &ins);
11209 static void textview_move_next_line (GtkTextView *text)
11211 GtkTextBuffer *buffer;
11212 GtkTextMark *mark;
11213 GtkTextIter ins;
11214 gint offset;
11216 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11218 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11219 mark = gtk_text_buffer_get_insert(buffer);
11220 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11221 offset = gtk_text_iter_get_line_offset(&ins);
11222 if (gtk_text_iter_forward_line(&ins)) {
11223 gtk_text_iter_set_line_offset(&ins, offset);
11224 gtk_text_buffer_place_cursor(buffer, &ins);
11228 static void textview_move_previous_line (GtkTextView *text)
11230 GtkTextBuffer *buffer;
11231 GtkTextMark *mark;
11232 GtkTextIter ins;
11233 gint offset;
11235 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11237 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11238 mark = gtk_text_buffer_get_insert(buffer);
11239 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11240 offset = gtk_text_iter_get_line_offset(&ins);
11241 if (gtk_text_iter_backward_line(&ins)) {
11242 gtk_text_iter_set_line_offset(&ins, offset);
11243 gtk_text_buffer_place_cursor(buffer, &ins);
11247 static void textview_delete_forward_character (GtkTextView *text)
11249 GtkTextBuffer *buffer;
11250 GtkTextMark *mark;
11251 GtkTextIter ins, end_iter;
11253 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11255 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11256 mark = gtk_text_buffer_get_insert(buffer);
11257 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11258 end_iter = ins;
11259 if (gtk_text_iter_forward_char(&end_iter)) {
11260 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11264 static void textview_delete_backward_character (GtkTextView *text)
11266 GtkTextBuffer *buffer;
11267 GtkTextMark *mark;
11268 GtkTextIter ins, end_iter;
11270 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11272 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11273 mark = gtk_text_buffer_get_insert(buffer);
11274 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11275 end_iter = ins;
11276 if (gtk_text_iter_backward_char(&end_iter)) {
11277 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11281 static void textview_delete_forward_word (GtkTextView *text)
11283 GtkTextBuffer *buffer;
11284 GtkTextMark *mark;
11285 GtkTextIter ins, end_iter;
11287 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11289 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11290 mark = gtk_text_buffer_get_insert(buffer);
11291 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11292 end_iter = ins;
11293 if (gtk_text_iter_forward_word_end(&end_iter)) {
11294 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11298 static void textview_delete_backward_word (GtkTextView *text)
11300 GtkTextBuffer *buffer;
11301 GtkTextMark *mark;
11302 GtkTextIter ins, end_iter;
11304 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11306 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11307 mark = gtk_text_buffer_get_insert(buffer);
11308 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11309 end_iter = ins;
11310 if (gtk_text_iter_backward_word_start(&end_iter)) {
11311 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11315 static void textview_delete_line (GtkTextView *text)
11317 GtkTextBuffer *buffer;
11318 GtkTextMark *mark;
11319 GtkTextIter ins, start_iter, end_iter;
11321 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11323 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11324 mark = gtk_text_buffer_get_insert(buffer);
11325 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11327 start_iter = ins;
11328 gtk_text_iter_set_line_offset(&start_iter, 0);
11330 end_iter = ins;
11331 if (gtk_text_iter_ends_line(&end_iter)){
11332 if (!gtk_text_iter_forward_char(&end_iter))
11333 gtk_text_iter_backward_char(&start_iter);
11335 else
11336 gtk_text_iter_forward_to_line_end(&end_iter);
11337 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11340 static void textview_delete_to_line_end (GtkTextView *text)
11342 GtkTextBuffer *buffer;
11343 GtkTextMark *mark;
11344 GtkTextIter ins, end_iter;
11346 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11348 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11349 mark = gtk_text_buffer_get_insert(buffer);
11350 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11351 end_iter = ins;
11352 if (gtk_text_iter_ends_line(&end_iter))
11353 gtk_text_iter_forward_char(&end_iter);
11354 else
11355 gtk_text_iter_forward_to_line_end(&end_iter);
11356 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11359 #define DO_ACTION(name, act) { \
11360 if(!strcmp(name, a_name)) { \
11361 return act; \
11364 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11366 const gchar *a_name = gtk_action_get_name(action);
11367 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11368 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11369 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11370 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11371 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11372 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11373 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11374 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11375 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11376 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11377 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11378 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11379 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11380 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11381 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11384 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11386 Compose *compose = (Compose *)data;
11387 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11388 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11390 action = compose_call_advanced_action_from_path(gaction);
11392 static struct {
11393 void (*do_action) (GtkTextView *text);
11394 } action_table[] = {
11395 {textview_move_beginning_of_line},
11396 {textview_move_forward_character},
11397 {textview_move_backward_character},
11398 {textview_move_forward_word},
11399 {textview_move_backward_word},
11400 {textview_move_end_of_line},
11401 {textview_move_next_line},
11402 {textview_move_previous_line},
11403 {textview_delete_forward_character},
11404 {textview_delete_backward_character},
11405 {textview_delete_forward_word},
11406 {textview_delete_backward_word},
11407 {textview_delete_line},
11408 {textview_delete_to_line_end}
11411 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11413 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11414 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11415 if (action_table[action].do_action)
11416 action_table[action].do_action(text);
11417 else
11418 g_warning("not implemented yet");
11422 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11424 GtkAllocation allocation;
11425 GtkWidget *parent;
11426 gchar *str = NULL;
11428 if (GTK_IS_EDITABLE(widget)) {
11429 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11430 gtk_editable_set_position(GTK_EDITABLE(widget),
11431 strlen(str));
11432 g_free(str);
11433 if ((parent = gtk_widget_get_parent(widget))
11434 && (parent = gtk_widget_get_parent(parent))
11435 && (parent = gtk_widget_get_parent(parent))) {
11436 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11437 gtk_widget_get_allocation(widget, &allocation);
11438 gint y = allocation.y;
11439 gint height = allocation.height;
11440 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11441 (GTK_SCROLLED_WINDOW(parent));
11443 gfloat value = gtk_adjustment_get_value(shown);
11444 gfloat upper = gtk_adjustment_get_upper(shown);
11445 gfloat page_size = gtk_adjustment_get_page_size(shown);
11446 if (y < (int)value) {
11447 gtk_adjustment_set_value(shown, y - 1);
11449 if ((y + height) > ((int)value + (int)page_size)) {
11450 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11451 gtk_adjustment_set_value(shown,
11452 y + height - (int)page_size - 1);
11453 } else {
11454 gtk_adjustment_set_value(shown,
11455 (int)upper - (int)page_size - 1);
11462 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11463 compose->focused_editable = widget;
11465 #ifdef GENERIC_UMPC
11466 if (GTK_IS_TEXT_VIEW(widget)
11467 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11468 g_object_ref(compose->notebook);
11469 g_object_ref(compose->edit_vbox);
11470 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11471 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11472 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11473 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11474 g_object_unref(compose->notebook);
11475 g_object_unref(compose->edit_vbox);
11476 g_signal_handlers_block_by_func(G_OBJECT(widget),
11477 G_CALLBACK(compose_grab_focus_cb),
11478 compose);
11479 gtk_widget_grab_focus(widget);
11480 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11481 G_CALLBACK(compose_grab_focus_cb),
11482 compose);
11483 } else if (!GTK_IS_TEXT_VIEW(widget)
11484 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11485 g_object_ref(compose->notebook);
11486 g_object_ref(compose->edit_vbox);
11487 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11488 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11489 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11490 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11491 g_object_unref(compose->notebook);
11492 g_object_unref(compose->edit_vbox);
11493 g_signal_handlers_block_by_func(G_OBJECT(widget),
11494 G_CALLBACK(compose_grab_focus_cb),
11495 compose);
11496 gtk_widget_grab_focus(widget);
11497 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11498 G_CALLBACK(compose_grab_focus_cb),
11499 compose);
11501 #endif
11504 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11506 compose->modified = TRUE;
11507 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11508 #ifndef GENERIC_UMPC
11509 compose_set_title(compose);
11510 #endif
11513 static void compose_wrap_cb(GtkAction *action, gpointer data)
11515 Compose *compose = (Compose *)data;
11516 compose_beautify_paragraph(compose, NULL, TRUE);
11519 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11521 Compose *compose = (Compose *)data;
11522 compose_wrap_all_full(compose, TRUE);
11525 static void compose_find_cb(GtkAction *action, gpointer data)
11527 Compose *compose = (Compose *)data;
11529 message_search_compose(compose);
11532 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11533 gpointer data)
11535 Compose *compose = (Compose *)data;
11536 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11537 if (compose->autowrap)
11538 compose_wrap_all_full(compose, TRUE);
11539 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11542 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11543 gpointer data)
11545 Compose *compose = (Compose *)data;
11546 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11549 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11551 Compose *compose = (Compose *)data;
11553 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11554 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11557 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11559 Compose *compose = (Compose *)data;
11561 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11562 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11565 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11567 g_free(compose->privacy_system);
11568 g_free(compose->encdata);
11570 compose->privacy_system = g_strdup(account->default_privacy_system);
11571 compose_update_privacy_system_menu_item(compose, warn);
11574 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11576 if (folder_item != NULL) {
11577 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT &&
11578 privacy_system_can_sign(compose->privacy_system)) {
11579 compose_use_signing(compose,
11580 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11582 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT &&
11583 privacy_system_can_encrypt(compose->privacy_system)) {
11584 compose_use_encryption(compose,
11585 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11590 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11592 Compose *compose = (Compose *)data;
11594 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11595 gtk_widget_show(compose->ruler_hbox);
11596 prefs_common.show_ruler = TRUE;
11597 } else {
11598 gtk_widget_hide(compose->ruler_hbox);
11599 gtk_widget_queue_resize(compose->edit_vbox);
11600 prefs_common.show_ruler = FALSE;
11604 static void compose_attach_drag_received_cb (GtkWidget *widget,
11605 GdkDragContext *context,
11606 gint x,
11607 gint y,
11608 GtkSelectionData *data,
11609 guint info,
11610 guint time,
11611 gpointer user_data)
11613 Compose *compose = (Compose *)user_data;
11614 GList *list, *tmp;
11615 GdkAtom type;
11617 type = gtk_selection_data_get_data_type(data);
11618 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11619 && gtk_drag_get_source_widget(context) !=
11620 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11621 list = uri_list_extract_filenames(
11622 (const gchar *)gtk_selection_data_get_data(data));
11623 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11624 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11625 compose_attach_append
11626 (compose, (const gchar *)tmp->data,
11627 utf8_filename, NULL, NULL);
11628 g_free(utf8_filename);
11630 if (list)
11631 compose_changed_cb(NULL, compose);
11632 list_free_strings_full(list);
11633 } else if (gtk_drag_get_source_widget(context)
11634 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11635 /* comes from our summaryview */
11636 SummaryView * summaryview = NULL;
11637 GSList * list = NULL, *cur = NULL;
11639 if (mainwindow_get_mainwindow())
11640 summaryview = mainwindow_get_mainwindow()->summaryview;
11642 if (summaryview)
11643 list = summary_get_selected_msg_list(summaryview);
11645 for (cur = list; cur; cur = cur->next) {
11646 MsgInfo *msginfo = (MsgInfo *)cur->data;
11647 gchar *file = NULL;
11648 if (msginfo)
11649 file = procmsg_get_message_file_full(msginfo,
11650 TRUE, TRUE);
11651 if (file) {
11652 compose_attach_append(compose, (const gchar *)file,
11653 (const gchar *)file, "message/rfc822", NULL);
11654 g_free(file);
11657 g_slist_free(list);
11661 static gboolean compose_drag_drop(GtkWidget *widget,
11662 GdkDragContext *drag_context,
11663 gint x, gint y,
11664 guint time, gpointer user_data)
11666 /* not handling this signal makes compose_insert_drag_received_cb
11667 * called twice */
11668 return TRUE;
11671 static gboolean completion_set_focus_to_subject
11672 (GtkWidget *widget,
11673 GdkEventKey *event,
11674 Compose *compose)
11676 cm_return_val_if_fail(compose != NULL, FALSE);
11678 /* make backtab move to subject field */
11679 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11680 gtk_widget_grab_focus(compose->subject_entry);
11681 return TRUE;
11683 return FALSE;
11686 static void compose_insert_drag_received_cb (GtkWidget *widget,
11687 GdkDragContext *drag_context,
11688 gint x,
11689 gint y,
11690 GtkSelectionData *data,
11691 guint info,
11692 guint time,
11693 gpointer user_data)
11695 Compose *compose = (Compose *)user_data;
11696 GList *list, *tmp;
11697 GdkAtom type;
11698 guint num_files;
11699 gchar *msg;
11701 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11702 * does not work */
11703 type = gtk_selection_data_get_data_type(data);
11704 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11705 AlertValue val = G_ALERTDEFAULT;
11706 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11708 list = uri_list_extract_filenames(ddata);
11709 num_files = g_list_length(list);
11710 if (list == NULL && strstr(ddata, "://")) {
11711 /* Assume a list of no files, and data has ://, is a remote link */
11712 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11713 gchar *tmpfile = get_tmp_file();
11714 str_write_to_file(tmpdata, tmpfile, TRUE);
11715 g_free(tmpdata);
11716 compose_insert_file(compose, tmpfile);
11717 claws_unlink(tmpfile);
11718 g_free(tmpfile);
11719 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11720 compose_beautify_paragraph(compose, NULL, TRUE);
11721 return;
11723 switch (prefs_common.compose_dnd_mode) {
11724 case COMPOSE_DND_ASK:
11725 msg = g_strdup_printf(
11726 ngettext(
11727 "Do you want to insert the contents of the file "
11728 "into the message body, or attach it to the email?",
11729 "Do you want to insert the contents of the %d files "
11730 "into the message body, or attach them to the email?",
11731 num_files),
11732 num_files);
11733 val = alertpanel_full(_("Insert or attach?"), msg,
11734 NULL, _("_Cancel"), NULL, _("_Insert"), NULL, _("_Attach"),
11735 ALERTFOCUS_SECOND, TRUE, NULL, ALERT_QUESTION);
11736 g_free(msg);
11737 break;
11738 case COMPOSE_DND_INSERT:
11739 val = G_ALERTALTERNATE;
11740 break;
11741 case COMPOSE_DND_ATTACH:
11742 val = G_ALERTOTHER;
11743 break;
11744 default:
11745 /* unexpected case */
11746 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11749 if (val & G_ALERTDISABLE) {
11750 val &= ~G_ALERTDISABLE;
11751 /* remember what action to perform by default, only if we don't click Cancel */
11752 if (val == G_ALERTALTERNATE)
11753 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11754 else if (val == G_ALERTOTHER)
11755 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11758 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11759 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11760 list_free_strings_full(list);
11761 return;
11762 } else if (val == G_ALERTOTHER) {
11763 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11764 list_free_strings_full(list);
11765 return;
11768 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11769 compose_insert_file(compose, (const gchar *)tmp->data);
11771 list_free_strings_full(list);
11772 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11773 return;
11777 static void compose_header_drag_received_cb (GtkWidget *widget,
11778 GdkDragContext *drag_context,
11779 gint x,
11780 gint y,
11781 GtkSelectionData *data,
11782 guint info,
11783 guint time,
11784 gpointer user_data)
11786 GtkEditable *entry = (GtkEditable *)user_data;
11787 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11789 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11790 * does not work */
11792 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11793 gchar *decoded=g_new(gchar, strlen(email));
11794 int start = 0;
11796 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11797 gtk_editable_delete_text(entry, 0, -1);
11798 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11799 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11800 g_free(decoded);
11801 return;
11803 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11806 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11808 Compose *compose = (Compose *)data;
11810 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11811 compose->return_receipt = TRUE;
11812 else
11813 compose->return_receipt = FALSE;
11816 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11818 Compose *compose = (Compose *)data;
11820 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11821 compose->remove_references = TRUE;
11822 else
11823 compose->remove_references = FALSE;
11826 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11827 ComposeHeaderEntry *headerentry)
11829 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11830 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11831 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11832 return FALSE;
11835 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11836 GdkEventKey *event,
11837 ComposeHeaderEntry *headerentry)
11839 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11840 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11841 !(event->state & GDK_MODIFIER_MASK) &&
11842 (event->keyval == GDK_KEY_BackSpace) &&
11843 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11844 gtk_container_remove
11845 (GTK_CONTAINER(headerentry->compose->header_table),
11846 headerentry->combo);
11847 gtk_container_remove
11848 (GTK_CONTAINER(headerentry->compose->header_table),
11849 headerentry->entry);
11850 headerentry->compose->header_list =
11851 g_slist_remove(headerentry->compose->header_list,
11852 headerentry);
11853 g_free(headerentry);
11854 } else if (event->keyval == GDK_KEY_Tab) {
11855 if (headerentry->compose->header_last == headerentry) {
11856 /* Override default next focus, and give it to subject_entry
11857 * instead of notebook tabs
11859 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11860 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11861 return TRUE;
11864 return FALSE;
11867 static gboolean scroll_postpone(gpointer data)
11869 Compose *compose = (Compose *)data;
11871 if (compose->batch)
11872 return FALSE;
11874 GTK_EVENTS_FLUSH();
11875 compose_show_first_last_header(compose, FALSE);
11876 return FALSE;
11879 static void compose_headerentry_changed_cb(GtkWidget *entry,
11880 ComposeHeaderEntry *headerentry)
11882 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11883 compose_create_header_entry(headerentry->compose);
11884 g_signal_handlers_disconnect_matched
11885 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11886 0, 0, NULL, NULL, headerentry);
11888 if (!headerentry->compose->batch)
11889 g_timeout_add(0, scroll_postpone, headerentry->compose);
11893 static gboolean compose_defer_auto_save_draft(Compose *compose)
11895 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11896 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11897 return FALSE;
11900 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11902 GtkAdjustment *vadj;
11904 cm_return_if_fail(compose);
11906 if(compose->batch)
11907 return;
11909 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11910 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11911 vadj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(gtk_widget_get_parent(compose->header_table)));
11912 gtk_adjustment_set_value(vadj, (show_first ?
11913 gtk_adjustment_get_lower(vadj) :
11914 (gtk_adjustment_get_upper(vadj) -
11915 gtk_adjustment_get_page_size(vadj))));
11918 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11919 const gchar *text, gint len, Compose *compose)
11921 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11922 (G_OBJECT(compose->text), "paste_as_quotation"));
11923 GtkTextMark *mark;
11925 cm_return_if_fail(text != NULL);
11927 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11928 G_CALLBACK(text_inserted),
11929 compose);
11930 if (paste_as_quotation) {
11931 gchar *new_text;
11932 const gchar *qmark;
11933 guint pos = 0;
11934 GtkTextIter start_iter;
11936 if (len < 0)
11937 len = strlen(text);
11939 new_text = g_strndup(text, len);
11941 qmark = compose_quote_char_from_context(compose);
11943 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11944 gtk_text_buffer_place_cursor(buffer, iter);
11946 pos = gtk_text_iter_get_offset(iter);
11948 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11949 _("Quote format error at line %d."));
11950 quote_fmt_reset_vartable();
11951 g_free(new_text);
11952 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11953 GINT_TO_POINTER(paste_as_quotation - 1));
11955 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11956 gtk_text_buffer_place_cursor(buffer, iter);
11957 gtk_text_buffer_delete_mark(buffer, mark);
11959 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11960 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11961 compose_beautify_paragraph(compose, &start_iter, FALSE);
11962 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11963 gtk_text_buffer_delete_mark(buffer, mark);
11964 } else {
11965 if (strcmp(text, "\n") || compose->automatic_break
11966 || gtk_text_iter_starts_line(iter)) {
11967 GtkTextIter before_ins;
11968 gtk_text_buffer_insert(buffer, iter, text, len);
11969 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11970 before_ins = *iter;
11971 gtk_text_iter_backward_chars(&before_ins, len);
11972 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11974 } else {
11975 /* check if the preceding is just whitespace or quote */
11976 GtkTextIter start_line;
11977 gchar *tmp = NULL, *quote = NULL;
11978 gint quote_len = 0, is_normal = 0;
11979 start_line = *iter;
11980 gtk_text_iter_set_line_offset(&start_line, 0);
11981 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11982 g_strstrip(tmp);
11984 if (*tmp == '\0') {
11985 is_normal = 1;
11986 } else {
11987 quote = compose_get_quote_str(buffer, &start_line, &quote_len);
11988 if (quote)
11989 is_normal = 1;
11990 g_free(quote);
11992 g_free(tmp);
11994 if (is_normal) {
11995 gtk_text_buffer_insert(buffer, iter, text, len);
11996 } else {
11997 gtk_text_buffer_insert_with_tags_by_name(buffer,
11998 iter, text, len, "no_join", NULL);
12003 if (!paste_as_quotation) {
12004 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
12005 compose_beautify_paragraph(compose, iter, FALSE);
12006 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12007 gtk_text_buffer_delete_mark(buffer, mark);
12010 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
12011 G_CALLBACK(text_inserted),
12012 compose);
12013 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
12015 if (compose_can_autosave(compose) &&
12016 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
12017 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
12018 compose->draft_timeout_tag = g_timeout_add
12019 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
12022 #if USE_ENCHANT
12023 static void compose_check_all(GtkAction *action, gpointer data)
12025 Compose *compose = (Compose *)data;
12026 if (!compose->gtkaspell)
12027 return;
12029 if (gtk_widget_has_focus(compose->subject_entry))
12030 claws_spell_entry_check_all(
12031 CLAWS_SPELL_ENTRY(compose->subject_entry));
12032 else
12033 gtkaspell_check_all(compose->gtkaspell);
12036 static void compose_highlight_all(GtkAction *action, gpointer data)
12038 Compose *compose = (Compose *)data;
12039 if (compose->gtkaspell) {
12040 claws_spell_entry_recheck_all(
12041 CLAWS_SPELL_ENTRY(compose->subject_entry));
12042 gtkaspell_highlight_all(compose->gtkaspell);
12046 static void compose_check_backwards(GtkAction *action, gpointer data)
12048 Compose *compose = (Compose *)data;
12049 if (!compose->gtkaspell) {
12050 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12051 return;
12054 if (gtk_widget_has_focus(compose->subject_entry))
12055 claws_spell_entry_check_backwards(
12056 CLAWS_SPELL_ENTRY(compose->subject_entry));
12057 else
12058 gtkaspell_check_backwards(compose->gtkaspell);
12061 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12063 Compose *compose = (Compose *)data;
12064 if (!compose->gtkaspell) {
12065 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12066 return;
12069 if (gtk_widget_has_focus(compose->subject_entry))
12070 claws_spell_entry_check_forwards_go(
12071 CLAWS_SPELL_ENTRY(compose->subject_entry));
12072 else
12073 gtkaspell_check_forwards_go(compose->gtkaspell);
12075 #endif
12078 *\brief Guess originating forward account from MsgInfo and several
12079 * "common preference" settings. Return NULL if no guess.
12081 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12083 PrefsAccount *account = NULL;
12085 cm_return_val_if_fail(msginfo, NULL);
12086 cm_return_val_if_fail(msginfo->folder, NULL);
12087 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12089 if (msginfo->folder->prefs->enable_default_account)
12090 account = account_find_from_id(msginfo->folder->prefs->default_account);
12092 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12093 gchar *to;
12094 Xstrdup_a(to, msginfo->to, return NULL);
12095 extract_address(to);
12096 account = account_find_from_address(to, FALSE);
12099 if (!account && prefs_common.forward_account_autosel) {
12100 gchar *cc = NULL;
12101 if (!procheader_get_header_from_msginfo
12102 (msginfo, &cc, "Cc:")) {
12103 gchar *buf = cc + strlen("Cc:");
12104 extract_address(buf);
12105 account = account_find_from_address(buf, FALSE);
12106 g_free(cc);
12110 if (!account && prefs_common.forward_account_autosel) {
12111 gchar *deliveredto = NULL;
12112 if (!procheader_get_header_from_msginfo
12113 (msginfo, &deliveredto, "Delivered-To:")) {
12114 gchar *buf = deliveredto + strlen("Delivered-To:");
12115 extract_address(buf);
12116 account = account_find_from_address(buf, FALSE);
12117 g_free(deliveredto);
12121 if (!account)
12122 account = msginfo->folder->folder->account;
12124 return account;
12127 gboolean compose_close(Compose *compose)
12129 gint x, y;
12131 cm_return_val_if_fail(compose, FALSE);
12133 if (!g_mutex_trylock(&compose->mutex)) {
12134 /* we have to wait for the (possibly deferred by auto-save)
12135 * drafting to be done, before destroying the compose under
12136 * it. */
12137 debug_print("waiting for drafting to finish...\n");
12138 compose_allow_user_actions(compose, FALSE);
12139 if (compose->close_timeout_tag == 0) {
12140 compose->close_timeout_tag =
12141 g_timeout_add (500, (GSourceFunc) compose_close,
12142 compose);
12144 return TRUE;
12147 if (compose->draft_timeout_tag >= 0) {
12148 g_source_remove(compose->draft_timeout_tag);
12149 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12152 gtk_window_get_position(GTK_WINDOW(compose->window), &x, &y);
12153 if (!compose->batch) {
12154 prefs_common.compose_x = x;
12155 prefs_common.compose_y = y;
12157 g_mutex_unlock(&compose->mutex);
12158 compose_destroy(compose);
12159 return FALSE;
12163 * Add entry field for each address in list.
12164 * \param compose E-Mail composition object.
12165 * \param listAddress List of (formatted) E-Mail addresses.
12167 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12168 GList *node;
12169 gchar *addr;
12170 node = listAddress;
12171 while( node ) {
12172 addr = ( gchar * ) node->data;
12173 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12174 node = g_list_next( node );
12178 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12179 guint action, gboolean opening_multiple)
12181 gchar *body = NULL;
12182 GSList *new_msglist = NULL;
12183 MsgInfo *tmp_msginfo = NULL;
12184 gboolean originally_enc = FALSE;
12185 gboolean originally_sig = FALSE;
12186 Compose *compose = NULL;
12187 gchar *s_system = NULL;
12189 cm_return_if_fail(msginfo_list != NULL);
12191 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12192 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12193 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12195 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12196 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12197 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12198 orig_msginfo, mimeinfo);
12199 if (tmp_msginfo != NULL) {
12200 new_msglist = g_slist_append(NULL, tmp_msginfo);
12202 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12203 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12204 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12206 tmp_msginfo->folder = orig_msginfo->folder;
12207 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12208 if (orig_msginfo->tags) {
12209 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12210 tmp_msginfo->folder->tags_dirty = TRUE;
12216 if (!opening_multiple && msgview != NULL)
12217 body = messageview_get_selection(msgview);
12219 if (new_msglist) {
12220 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12221 procmsg_msginfo_free(&tmp_msginfo);
12222 g_slist_free(new_msglist);
12223 } else
12224 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12226 if (compose && originally_enc) {
12227 compose_force_encryption(compose, compose->account, FALSE, s_system);
12230 if (compose && originally_sig && compose->account->default_sign_reply) {
12231 compose_force_signing(compose, compose->account, s_system);
12233 g_free(s_system);
12234 g_free(body);
12235 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12238 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12239 guint action)
12241 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12242 && msginfo_list != NULL
12243 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12244 GSList *cur = msginfo_list;
12245 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12246 "messages. Opening the windows "
12247 "could take some time. Do you "
12248 "want to continue?"),
12249 g_slist_length(msginfo_list));
12250 if (g_slist_length(msginfo_list) > 9
12251 && alertpanel(_("Warning"), msg, NULL, _("_Cancel"), NULL, _("_Yes"),
12252 NULL, NULL, ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12253 g_free(msg);
12254 return;
12256 g_free(msg);
12257 /* We'll open multiple compose windows */
12258 /* let the WM place the next windows */
12259 compose_force_window_origin = FALSE;
12260 for (; cur; cur = cur->next) {
12261 GSList tmplist;
12262 tmplist.data = cur->data;
12263 tmplist.next = NULL;
12264 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12266 compose_force_window_origin = TRUE;
12267 } else {
12268 /* forwarding multiple mails as attachments is done via a
12269 * single compose window */
12270 if (msginfo_list != NULL) {
12271 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12272 } else if (msgview != NULL) {
12273 GSList tmplist;
12274 tmplist.data = msgview->msginfo;
12275 tmplist.next = NULL;
12276 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12277 } else {
12278 debug_print("Nothing to reply to\n");
12283 void compose_check_for_email_account(Compose *compose)
12285 PrefsAccount *ac = NULL, *curr = NULL;
12286 GList *list;
12288 if (!compose)
12289 return;
12291 if (compose->account && compose->account->protocol == A_NNTP) {
12292 ac = account_get_cur_account();
12293 if (ac->protocol == A_NNTP) {
12294 list = account_get_list();
12296 for( ; list != NULL ; list = g_list_next(list)) {
12297 curr = (PrefsAccount *) list->data;
12298 if (curr->protocol != A_NNTP) {
12299 ac = curr;
12300 break;
12304 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12305 ac->account_id);
12309 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12310 const gchar *address)
12312 GSList *msginfo_list = NULL;
12313 gchar *body = messageview_get_selection(msgview);
12314 Compose *compose;
12316 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12318 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12319 compose_check_for_email_account(compose);
12320 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12321 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12322 compose_reply_set_subject(compose, msginfo);
12324 g_free(body);
12325 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12328 void compose_set_position(Compose *compose, gint pos)
12330 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12332 gtkut_text_view_set_position(text, pos);
12335 gboolean compose_search_string(Compose *compose,
12336 const gchar *str, gboolean case_sens)
12338 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12340 return gtkut_text_view_search_string(text, str, case_sens);
12343 gboolean compose_search_string_backward(Compose *compose,
12344 const gchar *str, gboolean case_sens)
12346 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12348 return gtkut_text_view_search_string_backward(text, str, case_sens);
12351 /* allocate a msginfo structure and populate its data from a compose data structure */
12352 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12354 MsgInfo *newmsginfo;
12355 GSList *list;
12356 gchar date[RFC822_DATE_BUFFSIZE];
12358 cm_return_val_if_fail( compose != NULL, NULL );
12360 newmsginfo = procmsg_msginfo_new();
12362 /* date is now */
12363 get_rfc822_date(date, sizeof(date));
12364 newmsginfo->date = g_strdup(date);
12366 /* from */
12367 if (compose->from_name) {
12368 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12369 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12372 /* subject */
12373 if (compose->subject_entry)
12374 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12376 /* to, cc, reply-to, newsgroups */
12377 for (list = compose->header_list; list; list = list->next) {
12378 gchar *header = gtk_editable_get_chars(
12379 GTK_EDITABLE(
12380 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12381 gchar *entry = gtk_editable_get_chars(
12382 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12384 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12385 if ( newmsginfo->to == NULL ) {
12386 newmsginfo->to = g_strdup(entry);
12387 } else if (entry && *entry) {
12388 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12389 g_free(newmsginfo->to);
12390 newmsginfo->to = tmp;
12392 } else
12393 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12394 if ( newmsginfo->cc == NULL ) {
12395 newmsginfo->cc = g_strdup(entry);
12396 } else if (entry && *entry) {
12397 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12398 g_free(newmsginfo->cc);
12399 newmsginfo->cc = tmp;
12401 } else
12402 if ( strcasecmp(header,
12403 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12404 if ( newmsginfo->newsgroups == NULL ) {
12405 newmsginfo->newsgroups = g_strdup(entry);
12406 } else if (entry && *entry) {
12407 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12408 g_free(newmsginfo->newsgroups);
12409 newmsginfo->newsgroups = tmp;
12413 g_free(header);
12414 g_free(entry);
12417 /* other data is unset */
12419 return newmsginfo;
12422 #ifdef USE_ENCHANT
12423 /* update compose's dictionaries from folder dict settings */
12424 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12425 FolderItem *folder_item)
12427 cm_return_if_fail(compose != NULL);
12429 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12430 FolderItemPrefs *prefs = folder_item->prefs;
12432 if (prefs->enable_default_dictionary)
12433 gtkaspell_change_dict(compose->gtkaspell,
12434 prefs->default_dictionary, FALSE);
12435 if (folder_item->prefs->enable_default_alt_dictionary)
12436 gtkaspell_change_alt_dict(compose->gtkaspell,
12437 prefs->default_alt_dictionary);
12438 if (prefs->enable_default_dictionary
12439 || prefs->enable_default_alt_dictionary)
12440 compose_spell_menu_changed(compose);
12443 #endif
12445 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12447 Compose *compose = (Compose *)data;
12449 cm_return_if_fail(compose != NULL);
12451 gtk_widget_grab_focus(compose->text);
12454 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12456 gtk_combo_box_popup(GTK_COMBO_BOX(data));
12461 * End of Source.