when redirecting make encrypt and sign buttons inactive, and don't care about folder...
[claws.git] / src / compose.c
blob748800c0745be6e3a643fc623941413ebba71563
1 /*
2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2020 the Claws Mail team and Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #include "claws-features.h"
22 #endif
24 #include "defs.h"
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
28 #endif
30 #include <glib.h>
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtk.h>
35 #include <pango/pango-break.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <ctype.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <unistd.h>
43 #include <time.h>
44 #include <stdlib.h>
45 #if HAVE_SYS_WAIT_H
46 # include <sys/wait.h>
47 #endif
48 #include <signal.h>
49 #include <errno.h>
50 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
51 #include <libgen.h>
52 #endif
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
55 # include <wchar.h>
56 # include <wctype.h>
57 #endif
59 #include "claws.h"
60 #include "main.h"
61 #include "mainwindow.h"
62 #include "compose.h"
63 #ifndef USE_ALT_ADDRBOOK
64 #include "addressbook.h"
65 #else
66 #include "addressbook-dbus.h"
67 #include "addressadd.h"
68 #endif
69 #include "folderview.h"
70 #include "procmsg.h"
71 #include "menu.h"
72 #include "stock_pixmap.h"
73 #include "send_message.h"
74 #include "imap.h"
75 #include "news.h"
76 #include "customheader.h"
77 #include "prefs_common.h"
78 #include "prefs_account.h"
79 #include "action.h"
80 #include "account.h"
81 #include "filesel.h"
82 #include "procheader.h"
83 #include "procmime.h"
84 #include "statusbar.h"
85 #include "about.h"
86 #include "quoted-printable.h"
87 #include "codeconv.h"
88 #include "utils.h"
89 #include "gtkutils.h"
90 #include "gtkshruler.h"
91 #include "socket.h"
92 #include "alertpanel.h"
93 #include "manage_window.h"
94 #include "folder.h"
95 #include "folder_item_prefs.h"
96 #include "addr_compl.h"
97 #include "quote_fmt.h"
98 #include "undo.h"
99 #include "foldersel.h"
100 #include "toolbar.h"
101 #include "inc.h"
102 #include "message_search.h"
103 #include "combobox.h"
104 #include "hooks.h"
105 #include "privacy.h"
106 #include "timing.h"
107 #include "autofaces.h"
108 #include "spell_entry.h"
109 #include "headers.h"
110 #include "file-utils.h"
112 #ifdef USE_LDAP
113 #include "password.h"
114 #include "ldapserver.h"
115 #endif
117 enum
119 COL_MIMETYPE = 0,
120 COL_SIZE = 1,
121 COL_NAME = 2,
122 COL_CHARSET = 3,
123 COL_DATA = 4,
124 COL_AUTODATA = 5,
125 N_COL_COLUMNS
128 #define N_ATTACH_COLS (N_COL_COLUMNS)
130 typedef enum
132 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
135 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
137 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
139 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
141 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
142 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
143 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
144 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
145 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
146 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
147 } ComposeCallAdvancedAction;
149 typedef enum
151 PRIORITY_HIGHEST = 1,
152 PRIORITY_HIGH,
153 PRIORITY_NORMAL,
154 PRIORITY_LOW,
155 PRIORITY_LOWEST
156 } PriorityLevel;
158 typedef enum
160 COMPOSE_INSERT_SUCCESS,
161 COMPOSE_INSERT_READ_ERROR,
162 COMPOSE_INSERT_INVALID_CHARACTER,
163 COMPOSE_INSERT_NO_FILE
164 } ComposeInsertResult;
166 typedef enum
168 COMPOSE_WRITE_FOR_SEND,
169 COMPOSE_WRITE_FOR_STORE
170 } ComposeWriteType;
172 typedef enum
174 COMPOSE_QUOTE_FORCED,
175 COMPOSE_QUOTE_CHECK,
176 COMPOSE_QUOTE_SKIP
177 } ComposeQuoteMode;
179 typedef enum {
180 TO_FIELD_PRESENT,
181 SUBJECT_FIELD_PRESENT,
182 BODY_FIELD_PRESENT,
183 NO_FIELD_PRESENT
184 } MailField;
186 #define B64_LINE_SIZE 57
187 #define B64_BUFFSIZE 77
189 #define MAX_REFERENCES_LEN 999
191 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
192 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
194 static GdkColor default_header_bgcolor = {
195 (gulong)0,
196 (gushort)0,
197 (gushort)0,
198 (gushort)0
201 static GdkColor default_header_color = {
202 (gulong)0,
203 (gushort)0,
204 (gushort)0,
205 (gushort)0
208 static GList *compose_list = NULL;
209 static GSList *extra_headers = NULL;
211 static Compose *compose_generic_new (PrefsAccount *account,
212 const gchar *to,
213 FolderItem *item,
214 GList *attach_files,
215 GList *listAddress );
217 static Compose *compose_create (PrefsAccount *account,
218 FolderItem *item,
219 ComposeMode mode,
220 gboolean batch);
222 static void compose_entry_indicate (Compose *compose,
223 const gchar *address);
224 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
225 ComposeQuoteMode quote_mode,
226 gboolean to_all,
227 gboolean to_sender,
228 const gchar *body);
229 static Compose *compose_forward_multiple (PrefsAccount *account,
230 GSList *msginfo_list);
231 static Compose *compose_reply (MsgInfo *msginfo,
232 ComposeQuoteMode quote_mode,
233 gboolean to_all,
234 gboolean to_ml,
235 gboolean to_sender,
236 const gchar *body);
237 static Compose *compose_reply_mode (ComposeMode mode,
238 GSList *msginfo_list,
239 gchar *body);
240 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
241 static void compose_update_privacy_systems_menu(Compose *compose);
243 static GtkWidget *compose_account_option_menu_create
244 (Compose *compose);
245 static void compose_set_out_encoding (Compose *compose);
246 static void compose_set_template_menu (Compose *compose);
247 static void compose_destroy (Compose *compose);
249 static MailField compose_entries_set (Compose *compose,
250 const gchar *mailto,
251 ComposeEntryType to_type);
252 static gint compose_parse_header (Compose *compose,
253 MsgInfo *msginfo);
254 static gint compose_parse_manual_headers (Compose *compose,
255 MsgInfo *msginfo,
256 HeaderEntry *entries);
257 static gchar *compose_parse_references (const gchar *ref,
258 const gchar *msgid);
260 static gchar *compose_quote_fmt (Compose *compose,
261 MsgInfo *msginfo,
262 const gchar *fmt,
263 const gchar *qmark,
264 const gchar *body,
265 gboolean rewrap,
266 gboolean need_unescape,
267 const gchar *err_msg);
269 static void compose_reply_set_entry (Compose *compose,
270 MsgInfo *msginfo,
271 gboolean to_all,
272 gboolean to_ml,
273 gboolean to_sender,
274 gboolean
275 followup_and_reply_to);
276 static void compose_reedit_set_entry (Compose *compose,
277 MsgInfo *msginfo);
279 static void compose_insert_sig (Compose *compose,
280 gboolean replace);
281 static ComposeInsertResult compose_insert_file (Compose *compose,
282 const gchar *file);
284 static gboolean compose_attach_append (Compose *compose,
285 const gchar *file,
286 const gchar *type,
287 const gchar *content_type,
288 const gchar *charset);
289 static void compose_attach_parts (Compose *compose,
290 MsgInfo *msginfo);
292 static gboolean compose_beautify_paragraph (Compose *compose,
293 GtkTextIter *par_iter,
294 gboolean force);
295 static void compose_wrap_all (Compose *compose);
296 static void compose_wrap_all_full (Compose *compose,
297 gboolean autowrap);
299 static void compose_set_title (Compose *compose);
300 static void compose_select_account (Compose *compose,
301 PrefsAccount *account,
302 gboolean init);
304 static PrefsAccount *compose_current_mail_account(void);
305 /* static gint compose_send (Compose *compose); */
306 static gboolean compose_check_for_valid_recipient
307 (Compose *compose);
308 static gboolean compose_check_entries (Compose *compose,
309 gboolean check_everything);
310 static gint compose_write_to_file (Compose *compose,
311 FILE *fp,
312 gint action,
313 gboolean attach_parts);
314 static gint compose_write_body_to_file (Compose *compose,
315 const gchar *file);
316 static gint compose_remove_reedit_target (Compose *compose,
317 gboolean force);
318 static void compose_remove_draft (Compose *compose);
319 static ComposeQueueResult compose_queue_sub (Compose *compose,
320 gint *msgnum,
321 FolderItem **item,
322 gchar **msgpath,
323 gboolean perform_checks,
324 gboolean remove_reedit_target);
325 static int compose_add_attachments (Compose *compose,
326 MimeInfo *parent);
327 static gchar *compose_get_header (Compose *compose);
328 static gchar *compose_get_manual_headers_info (Compose *compose);
330 static void compose_convert_header (Compose *compose,
331 gchar *dest,
332 gint len,
333 gchar *src,
334 gint header_len,
335 gboolean addr_field);
337 static void compose_attach_info_free (AttachInfo *ainfo);
338 static void compose_attach_remove_selected (GtkAction *action,
339 gpointer data);
341 static void compose_template_apply (Compose *compose,
342 Template *tmpl,
343 gboolean replace);
344 static void compose_attach_property (GtkAction *action,
345 gpointer data);
346 static void compose_attach_property_create (gboolean *cancelled);
347 static void attach_property_ok (GtkWidget *widget,
348 gboolean *cancelled);
349 static void attach_property_cancel (GtkWidget *widget,
350 gboolean *cancelled);
351 static gint attach_property_delete_event (GtkWidget *widget,
352 GdkEventAny *event,
353 gboolean *cancelled);
354 static gboolean attach_property_key_pressed (GtkWidget *widget,
355 GdkEventKey *event,
356 gboolean *cancelled);
358 static void compose_exec_ext_editor (Compose *compose);
359 #ifdef G_OS_UNIX
360 static gint compose_exec_ext_editor_real (const gchar *file,
361 GdkNativeWindow socket_wid);
362 static gboolean compose_ext_editor_kill (Compose *compose);
363 static gboolean compose_input_cb (GIOChannel *source,
364 GIOCondition condition,
365 gpointer data);
366 static void compose_set_ext_editor_sensitive (Compose *compose,
367 gboolean sensitive);
368 static gboolean compose_get_ext_editor_cmd_valid();
369 static gboolean compose_get_ext_editor_uses_socket();
370 static gboolean compose_ext_editor_plug_removed_cb
371 (GtkSocket *socket,
372 Compose *compose);
373 #endif /* G_OS_UNIX */
375 static void compose_undo_state_changed (UndoMain *undostruct,
376 gint undo_state,
377 gint redo_state,
378 gpointer data);
380 static void compose_create_header_entry (Compose *compose);
381 static void compose_add_header_entry (Compose *compose, const gchar *header,
382 gchar *text, ComposePrefType pref_type);
383 static void compose_remove_header_entries(Compose *compose);
385 static void compose_update_priority_menu_item(Compose * compose);
386 #if USE_ENCHANT
387 static void compose_spell_menu_changed (void *data);
388 static void compose_dict_changed (void *data);
389 #endif
390 static void compose_add_field_list ( Compose *compose,
391 GList *listAddress );
393 /* callback functions */
395 static void compose_notebook_size_alloc (GtkNotebook *notebook,
396 GtkAllocation *allocation,
397 GtkPaned *paned);
398 static gboolean compose_edit_size_alloc (GtkEditable *widget,
399 GtkAllocation *allocation,
400 GtkSHRuler *shruler);
401 static void account_activated (GtkComboBox *optmenu,
402 gpointer data);
403 static void attach_selected (GtkTreeView *tree_view,
404 GtkTreePath *tree_path,
405 GtkTreeViewColumn *column,
406 Compose *compose);
407 static gboolean attach_button_pressed (GtkWidget *widget,
408 GdkEventButton *event,
409 gpointer data);
410 static gboolean attach_key_pressed (GtkWidget *widget,
411 GdkEventKey *event,
412 gpointer data);
413 static void compose_send_cb (GtkAction *action, gpointer data);
414 static void compose_send_later_cb (GtkAction *action, gpointer data);
416 static void compose_save_cb (GtkAction *action,
417 gpointer data);
419 static void compose_attach_cb (GtkAction *action,
420 gpointer data);
421 static void compose_insert_file_cb (GtkAction *action,
422 gpointer data);
423 static void compose_insert_sig_cb (GtkAction *action,
424 gpointer data);
425 static void compose_replace_sig_cb (GtkAction *action,
426 gpointer data);
428 static void compose_close_cb (GtkAction *action,
429 gpointer data);
430 static void compose_print_cb (GtkAction *action,
431 gpointer data);
433 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
435 static void compose_address_cb (GtkAction *action,
436 gpointer data);
437 static void about_show_cb (GtkAction *action,
438 gpointer data);
439 static void compose_template_activate_cb(GtkWidget *widget,
440 gpointer data);
442 static void compose_ext_editor_cb (GtkAction *action,
443 gpointer data);
445 static gint compose_delete_cb (GtkWidget *widget,
446 GdkEventAny *event,
447 gpointer data);
449 static void compose_undo_cb (GtkAction *action,
450 gpointer data);
451 static void compose_redo_cb (GtkAction *action,
452 gpointer data);
453 static void compose_cut_cb (GtkAction *action,
454 gpointer data);
455 static void compose_copy_cb (GtkAction *action,
456 gpointer data);
457 static void compose_paste_cb (GtkAction *action,
458 gpointer data);
459 static void compose_paste_as_quote_cb (GtkAction *action,
460 gpointer data);
461 static void compose_paste_no_wrap_cb (GtkAction *action,
462 gpointer data);
463 static void compose_paste_wrap_cb (GtkAction *action,
464 gpointer data);
465 static void compose_allsel_cb (GtkAction *action,
466 gpointer data);
468 static void compose_advanced_action_cb (GtkAction *action,
469 gpointer data);
471 static void compose_grab_focus_cb (GtkWidget *widget,
472 Compose *compose);
474 static void compose_changed_cb (GtkTextBuffer *textbuf,
475 Compose *compose);
477 static void compose_wrap_cb (GtkAction *action,
478 gpointer data);
479 static void compose_wrap_all_cb (GtkAction *action,
480 gpointer data);
481 static void compose_find_cb (GtkAction *action,
482 gpointer data);
483 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
484 gpointer data);
485 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
486 gpointer data);
488 static void compose_toggle_ruler_cb (GtkToggleAction *action,
489 gpointer data);
490 static void compose_toggle_sign_cb (GtkToggleAction *action,
491 gpointer data);
492 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
493 gpointer data);
494 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
495 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
496 static void compose_activate_privacy_system (Compose *compose,
497 PrefsAccount *account,
498 gboolean warn);
499 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item);
500 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
501 gpointer data);
502 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
503 gpointer data);
504 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
505 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
506 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
508 static void compose_attach_drag_received_cb (GtkWidget *widget,
509 GdkDragContext *drag_context,
510 gint x,
511 gint y,
512 GtkSelectionData *data,
513 guint info,
514 guint time,
515 gpointer user_data);
516 static void compose_insert_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_header_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);
533 static gboolean compose_drag_drop (GtkWidget *widget,
534 GdkDragContext *drag_context,
535 gint x, gint y,
536 guint time, gpointer user_data);
537 static gboolean completion_set_focus_to_subject
538 (GtkWidget *widget,
539 GdkEventKey *event,
540 Compose *user_data);
542 static void text_inserted (GtkTextBuffer *buffer,
543 GtkTextIter *iter,
544 const gchar *text,
545 gint len,
546 Compose *compose);
547 static Compose *compose_generic_reply(MsgInfo *msginfo,
548 ComposeQuoteMode quote_mode,
549 gboolean to_all,
550 gboolean to_ml,
551 gboolean to_sender,
552 gboolean followup_and_reply_to,
553 const gchar *body);
555 static void compose_headerentry_changed_cb (GtkWidget *entry,
556 ComposeHeaderEntry *headerentry);
557 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
558 GdkEventKey *event,
559 ComposeHeaderEntry *headerentry);
560 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
561 ComposeHeaderEntry *headerentry);
563 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
565 static void compose_allow_user_actions (Compose *compose, gboolean allow);
567 static void compose_nothing_cb (GtkAction *action, gpointer data)
572 #if USE_ENCHANT
573 static void compose_check_all (GtkAction *action, gpointer data);
574 static void compose_highlight_all (GtkAction *action, gpointer data);
575 static void compose_check_backwards (GtkAction *action, gpointer data);
576 static void compose_check_forwards_go (GtkAction *action, gpointer data);
577 #endif
579 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
581 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
583 #ifdef USE_ENCHANT
584 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
585 FolderItem *folder_item);
586 #endif
587 static void compose_attach_update_label(Compose *compose);
588 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
589 gboolean respect_default_to);
590 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
591 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
593 static GtkActionEntry compose_popup_entries[] =
595 {"Compose", NULL, "Compose", NULL, NULL, NULL },
596 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
597 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
598 {"Compose/---", NULL, "---", NULL, NULL, NULL },
599 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
602 static GtkActionEntry compose_entries[] =
604 {"Menu", NULL, "Menu", NULL, NULL, NULL },
605 /* menus */
606 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
607 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
608 #if USE_ENCHANT
609 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
610 #endif
611 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
612 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
613 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
614 /* Message menu */
615 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
616 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
617 {"Message/---", NULL, "---", NULL, NULL, NULL },
619 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
620 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
621 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
622 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
623 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
624 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
625 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
626 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
627 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
628 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
630 /* Edit menu */
631 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
632 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
633 {"Edit/---", NULL, "---", NULL, NULL, NULL },
635 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
636 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
637 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
639 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
640 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
641 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
642 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
644 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
646 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
647 {"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*/
648 {"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*/
649 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
650 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
651 {"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*/
652 {"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*/
653 {"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*/
654 {"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*/
655 {"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*/
656 {"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*/
657 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
658 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
659 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
660 {"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*/
662 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
663 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
665 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
666 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
667 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
668 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
669 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
670 #if USE_ENCHANT
671 /* Spelling menu */
672 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
673 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
674 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
675 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
677 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
678 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
679 #endif
681 /* Options menu */
682 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
683 {"Options/---", NULL, "---", NULL, NULL, NULL },
684 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
685 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
687 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
688 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
690 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
691 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
692 #define ENC_ACTION(cs_char,c_char,string) \
693 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
695 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
696 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
697 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
698 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
699 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
700 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
701 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
702 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
703 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
705 /* Tools menu */
706 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
708 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
709 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
710 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
711 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
713 /* Help menu */
714 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
717 static GtkToggleActionEntry compose_toggle_entries[] =
719 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
720 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
721 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
722 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
723 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
724 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
725 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
728 static GtkRadioActionEntry compose_radio_rm_entries[] =
730 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
731 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
732 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
733 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
736 static GtkRadioActionEntry compose_radio_prio_entries[] =
738 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
739 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
740 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
741 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
742 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
745 static GtkRadioActionEntry compose_radio_enc_entries[] =
747 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
777 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
778 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
779 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
782 static GtkTargetEntry compose_mime_types[] =
784 {"text/uri-list", 0, 0},
785 {"UTF8_STRING", 0, 0},
786 {"text/plain", 0, 0}
789 static gboolean compose_put_existing_to_front(MsgInfo *info)
791 const GList *compose_list = compose_get_compose_list();
792 const GList *elem = NULL;
794 if (compose_list) {
795 for (elem = compose_list; elem != NULL && elem->data != NULL;
796 elem = elem->next) {
797 Compose *c = (Compose*)elem->data;
799 if (!c->targetinfo || !c->targetinfo->msgid ||
800 !info->msgid)
801 continue;
803 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
804 gtkut_window_popup(c->window);
805 return TRUE;
809 return FALSE;
812 static GdkColor quote_color1 =
813 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
814 static GdkColor quote_color2 =
815 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
816 static GdkColor quote_color3 =
817 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
819 static GdkColor quote_bgcolor1 =
820 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
821 static GdkColor quote_bgcolor2 =
822 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
823 static GdkColor quote_bgcolor3 =
824 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
826 static GdkColor signature_color = {
827 (gulong)0,
828 (gushort)0x7fff,
829 (gushort)0x7fff,
830 (gushort)0x7fff
833 static GdkColor uri_color = {
834 (gulong)0,
835 (gushort)0,
836 (gushort)0,
837 (gushort)0
840 static void compose_create_tags(GtkTextView *text, Compose *compose)
842 GtkTextBuffer *buffer;
843 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
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 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1],
850 &quote_color1);
851 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2],
852 &quote_color2);
853 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3],
854 &quote_color3);
855 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1_BG],
856 &quote_bgcolor1);
857 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2_BG],
858 &quote_bgcolor2);
859 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3_BG],
860 &quote_bgcolor3);
861 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_SIGNATURE],
862 &signature_color);
863 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_URI],
864 &uri_color);
865 } else {
866 signature_color = quote_color1 = quote_color2 = quote_color3 =
867 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
870 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
871 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
872 "foreground-gdk", &quote_color1,
873 "paragraph-background-gdk", &quote_bgcolor1,
874 NULL);
875 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
876 "foreground-gdk", &quote_color2,
877 "paragraph-background-gdk", &quote_bgcolor2,
878 NULL);
879 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
880 "foreground-gdk", &quote_color3,
881 "paragraph-background-gdk", &quote_bgcolor3,
882 NULL);
883 } else {
884 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
885 "foreground-gdk", &quote_color1,
886 NULL);
887 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
888 "foreground-gdk", &quote_color2,
889 NULL);
890 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
891 "foreground-gdk", &quote_color3,
892 NULL);
895 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
896 "foreground-gdk", &signature_color,
897 NULL);
899 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
900 "foreground-gdk", &uri_color,
901 NULL);
902 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
903 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
906 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
907 GList *attach_files)
909 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
912 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
914 return compose_generic_new(account, mailto, item, NULL, NULL);
917 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
919 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
922 #define SCROLL_TO_CURSOR(compose) { \
923 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
924 gtk_text_view_get_buffer( \
925 GTK_TEXT_VIEW(compose->text))); \
926 gtk_text_view_scroll_mark_onscreen( \
927 GTK_TEXT_VIEW(compose->text), \
928 cmark); \
931 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
933 GtkEditable *entry;
934 if (folderidentifier) {
935 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
936 prefs_common.compose_save_to_history = add_history(
937 prefs_common.compose_save_to_history, folderidentifier);
938 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
939 prefs_common.compose_save_to_history);
942 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
943 if (folderidentifier)
944 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
945 else
946 gtk_entry_set_text(GTK_ENTRY(entry), "");
949 static gchar *compose_get_save_to(Compose *compose)
951 GtkEditable *entry;
952 gchar *result = NULL;
953 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
954 result = gtk_editable_get_chars(entry, 0, -1);
956 if (result) {
957 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
958 prefs_common.compose_save_to_history = add_history(
959 prefs_common.compose_save_to_history, result);
960 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
961 prefs_common.compose_save_to_history);
963 return result;
966 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
967 GList *attach_files, GList *listAddress )
969 Compose *compose;
970 GtkTextView *textview;
971 GtkTextBuffer *textbuf;
972 GtkTextIter iter;
973 const gchar *subject_format = NULL;
974 const gchar *body_format = NULL;
975 gchar *mailto_from = NULL;
976 PrefsAccount *mailto_account = NULL;
977 MsgInfo* dummyinfo = NULL;
978 gint cursor_pos = -1;
979 MailField mfield = NO_FIELD_PRESENT;
980 gchar* buf;
981 GtkTextMark *mark;
983 /* check if mailto defines a from */
984 if (mailto && *mailto != '\0') {
985 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
986 /* mailto defines a from, check if we can get account prefs from it,
987 if not, the account prefs will be guessed using other ways, but we'll keep
988 the from anyway */
989 if (mailto_from) {
990 mailto_account = account_find_from_address(mailto_from, TRUE);
991 if (mailto_account == NULL) {
992 gchar *tmp_from;
993 Xstrdup_a(tmp_from, mailto_from, return NULL);
994 extract_address(tmp_from);
995 mailto_account = account_find_from_address(tmp_from, TRUE);
998 if (mailto_account)
999 account = mailto_account;
1002 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1003 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1004 account = account_find_from_id(item->prefs->default_account);
1006 /* if no account prefs set, fallback to the current one */
1007 if (!account) account = cur_account;
1008 cm_return_val_if_fail(account != NULL, NULL);
1010 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1011 compose_apply_folder_privacy_settings(compose, item);
1013 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1014 (account->default_encrypt || account->default_sign))
1015 alertpanel_error(_("You have opted to sign and/or encrypt this "
1016 "message but have not selected a privacy system.\n\n"
1017 "Signing and encrypting have been disabled for this "
1018 "message."));
1020 /* override from name if mailto asked for it */
1021 if (mailto_from) {
1022 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1023 g_free(mailto_from);
1024 } else
1025 /* override from name according to folder properties */
1026 if (item && item->prefs &&
1027 item->prefs->compose_with_format &&
1028 item->prefs->compose_override_from_format &&
1029 *item->prefs->compose_override_from_format != '\0') {
1031 gchar *tmp = NULL;
1032 gchar *buf = NULL;
1034 dummyinfo = compose_msginfo_new_from_compose(compose);
1036 /* decode \-escape sequences in the internal representation of the quote format */
1037 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1038 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1040 #ifdef USE_ENCHANT
1041 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1042 compose->gtkaspell);
1043 #else
1044 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1045 #endif
1046 quote_fmt_scan_string(tmp);
1047 quote_fmt_parse();
1049 buf = quote_fmt_get_buffer();
1050 if (buf == NULL)
1051 alertpanel_error(_("New message From format error."));
1052 else
1053 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1054 quote_fmt_reset_vartable();
1055 quote_fmtlex_destroy();
1057 g_free(tmp);
1060 compose->replyinfo = NULL;
1061 compose->fwdinfo = NULL;
1063 textview = GTK_TEXT_VIEW(compose->text);
1064 textbuf = gtk_text_view_get_buffer(textview);
1065 compose_create_tags(textview, compose);
1067 undo_block(compose->undostruct);
1068 #ifdef USE_ENCHANT
1069 compose_set_dictionaries_from_folder_prefs(compose, item);
1070 #endif
1072 if (account->auto_sig)
1073 compose_insert_sig(compose, FALSE);
1074 gtk_text_buffer_get_start_iter(textbuf, &iter);
1075 gtk_text_buffer_place_cursor(textbuf, &iter);
1077 if (account->protocol != A_NNTP) {
1078 if (mailto && *mailto != '\0') {
1079 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1081 } else {
1082 compose_set_folder_prefs(compose, item, TRUE);
1084 if (item && item->ret_rcpt) {
1085 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1087 } else {
1088 if (mailto && *mailto != '\0') {
1089 if (!strchr(mailto, '@'))
1090 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1091 else
1092 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1093 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1094 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1095 mfield = TO_FIELD_PRESENT;
1098 * CLAWS: just don't allow return receipt request, even if the user
1099 * may want to send an email. simple but foolproof.
1101 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1103 compose_add_field_list( compose, listAddress );
1105 if (item && item->prefs && item->prefs->compose_with_format) {
1106 subject_format = item->prefs->compose_subject_format;
1107 body_format = item->prefs->compose_body_format;
1108 } else if (account->compose_with_format) {
1109 subject_format = account->compose_subject_format;
1110 body_format = account->compose_body_format;
1111 } else if (prefs_common.compose_with_format) {
1112 subject_format = prefs_common.compose_subject_format;
1113 body_format = prefs_common.compose_body_format;
1116 if (subject_format || body_format) {
1118 if ( subject_format
1119 && *subject_format != '\0' )
1121 gchar *subject = NULL;
1122 gchar *tmp = NULL;
1123 gchar *buf = NULL;
1125 if (!dummyinfo)
1126 dummyinfo = compose_msginfo_new_from_compose(compose);
1128 /* decode \-escape sequences in the internal representation of the quote format */
1129 tmp = g_malloc(strlen(subject_format)+1);
1130 pref_get_unescaped_pref(tmp, subject_format);
1132 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1133 #ifdef USE_ENCHANT
1134 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1135 compose->gtkaspell);
1136 #else
1137 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1138 #endif
1139 quote_fmt_scan_string(tmp);
1140 quote_fmt_parse();
1142 buf = quote_fmt_get_buffer();
1143 if (buf == NULL)
1144 alertpanel_error(_("New message subject format error."));
1145 else
1146 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1147 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1148 quote_fmt_reset_vartable();
1149 quote_fmtlex_destroy();
1151 g_free(subject);
1152 g_free(tmp);
1153 mfield = SUBJECT_FIELD_PRESENT;
1156 if ( body_format
1157 && *body_format != '\0' )
1159 GtkTextView *text;
1160 GtkTextBuffer *buffer;
1161 GtkTextIter start, end;
1162 gchar *tmp = NULL;
1164 if (!dummyinfo)
1165 dummyinfo = compose_msginfo_new_from_compose(compose);
1167 text = GTK_TEXT_VIEW(compose->text);
1168 buffer = gtk_text_view_get_buffer(text);
1169 gtk_text_buffer_get_start_iter(buffer, &start);
1170 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1171 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1173 compose_quote_fmt(compose, dummyinfo,
1174 body_format,
1175 NULL, tmp, FALSE, TRUE,
1176 _("The body of the \"New message\" template has an error at line %d."));
1177 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1178 quote_fmt_reset_vartable();
1180 g_free(tmp);
1181 #ifdef USE_ENCHANT
1182 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1183 gtkaspell_highlight_all(compose->gtkaspell);
1184 #endif
1185 mfield = BODY_FIELD_PRESENT;
1189 procmsg_msginfo_free( &dummyinfo );
1191 if (attach_files) {
1192 GList *curr;
1193 AttachInfo *ainfo;
1195 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1196 ainfo = (AttachInfo *) curr->data;
1197 if (ainfo->insert)
1198 compose_insert_file(compose, ainfo->file);
1199 else
1200 compose_attach_append(compose, ainfo->file, ainfo->file,
1201 ainfo->content_type, ainfo->charset);
1205 compose_show_first_last_header(compose, TRUE);
1207 /* Set save folder */
1208 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1209 gchar *folderidentifier;
1211 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1212 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1213 folderidentifier = folder_item_get_identifier(item);
1214 compose_set_save_to(compose, folderidentifier);
1215 g_free(folderidentifier);
1218 /* Place cursor according to provided input (mfield) */
1219 switch (mfield) {
1220 case NO_FIELD_PRESENT:
1221 if (compose->header_last)
1222 gtk_widget_grab_focus(compose->header_last->entry);
1223 break;
1224 case TO_FIELD_PRESENT:
1225 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1226 if (buf) {
1227 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1228 g_free(buf);
1230 gtk_widget_grab_focus(compose->subject_entry);
1231 break;
1232 case SUBJECT_FIELD_PRESENT:
1233 textview = GTK_TEXT_VIEW(compose->text);
1234 if (!textview)
1235 break;
1236 textbuf = gtk_text_view_get_buffer(textview);
1237 if (!textbuf)
1238 break;
1239 mark = gtk_text_buffer_get_insert(textbuf);
1240 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1241 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1243 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1244 * only defers where it comes to the variable body
1245 * is not null. If no body is present compose->text
1246 * will be null in which case you cannot place the
1247 * cursor inside the component so. An empty component
1248 * is therefore created before placing the cursor
1250 case BODY_FIELD_PRESENT:
1251 cursor_pos = quote_fmt_get_cursor_pos();
1252 if (cursor_pos == -1)
1253 gtk_widget_grab_focus(compose->header_last->entry);
1254 else
1255 gtk_widget_grab_focus(compose->text);
1256 break;
1259 undo_unblock(compose->undostruct);
1261 if (prefs_common.auto_exteditor)
1262 compose_exec_ext_editor(compose);
1264 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1266 SCROLL_TO_CURSOR(compose);
1268 compose->modified = FALSE;
1269 compose_set_title(compose);
1271 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1273 return compose;
1276 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1277 gboolean override_pref, const gchar *system)
1279 const gchar *privacy = NULL;
1281 cm_return_if_fail(compose != NULL);
1282 cm_return_if_fail(account != NULL);
1284 if (privacy_system_can_encrypt(compose->privacy_system) == FALSE ||
1285 (override_pref == FALSE && account->default_encrypt_reply == FALSE))
1286 return;
1288 if (account->default_privacy_system && strlen(account->default_privacy_system))
1289 privacy = account->default_privacy_system;
1290 else if (system)
1291 privacy = system;
1292 else {
1293 GSList *privacy_avail = privacy_get_system_ids();
1294 if (privacy_avail && g_slist_length(privacy_avail)) {
1295 privacy = (gchar *)(privacy_avail->data);
1297 g_slist_free_full(privacy_avail, g_free);
1299 if (privacy != NULL) {
1300 if (system) {
1301 g_free(compose->privacy_system);
1302 compose->privacy_system = NULL;
1303 g_free(compose->encdata);
1304 compose->encdata = NULL;
1306 if (compose->privacy_system == NULL)
1307 compose->privacy_system = g_strdup(privacy);
1308 else if (*(compose->privacy_system) == '\0') {
1309 g_free(compose->privacy_system);
1310 g_free(compose->encdata);
1311 compose->encdata = NULL;
1312 compose->privacy_system = g_strdup(privacy);
1314 compose_update_privacy_system_menu_item(compose, FALSE);
1315 compose_use_encryption(compose, TRUE);
1319 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1321 const gchar *privacy = NULL;
1322 if (privacy_system_can_sign(compose->privacy_system) == FALSE)
1323 return;
1325 if (account->default_privacy_system && strlen(account->default_privacy_system))
1326 privacy = account->default_privacy_system;
1327 else if (system)
1328 privacy = system;
1329 else {
1330 GSList *privacy_avail = privacy_get_system_ids();
1331 if (privacy_avail && g_slist_length(privacy_avail)) {
1332 privacy = (gchar *)(privacy_avail->data);
1336 if (privacy != NULL) {
1337 if (system) {
1338 g_free(compose->privacy_system);
1339 compose->privacy_system = NULL;
1340 g_free(compose->encdata);
1341 compose->encdata = NULL;
1343 if (compose->privacy_system == NULL)
1344 compose->privacy_system = g_strdup(privacy);
1345 compose_update_privacy_system_menu_item(compose, FALSE);
1346 compose_use_signing(compose, TRUE);
1350 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1352 MsgInfo *msginfo;
1353 guint list_len;
1354 Compose *compose = NULL;
1356 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1358 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1359 cm_return_val_if_fail(msginfo != NULL, NULL);
1361 list_len = g_slist_length(msginfo_list);
1363 switch (mode) {
1364 case COMPOSE_REPLY:
1365 case COMPOSE_REPLY_TO_ADDRESS:
1366 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1367 FALSE, prefs_common.default_reply_list, FALSE, body);
1368 break;
1369 case COMPOSE_REPLY_WITH_QUOTE:
1370 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1371 FALSE, prefs_common.default_reply_list, FALSE, body);
1372 break;
1373 case COMPOSE_REPLY_WITHOUT_QUOTE:
1374 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1375 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1376 break;
1377 case COMPOSE_REPLY_TO_SENDER:
1378 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1379 FALSE, FALSE, TRUE, body);
1380 break;
1381 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1382 compose = compose_followup_and_reply_to(msginfo,
1383 COMPOSE_QUOTE_CHECK,
1384 FALSE, FALSE, body);
1385 break;
1386 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1387 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1388 FALSE, FALSE, TRUE, body);
1389 break;
1390 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1391 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1392 FALSE, FALSE, TRUE, NULL);
1393 break;
1394 case COMPOSE_REPLY_TO_ALL:
1395 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1396 TRUE, FALSE, FALSE, body);
1397 break;
1398 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1399 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1400 TRUE, FALSE, FALSE, body);
1401 break;
1402 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1403 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1404 TRUE, FALSE, FALSE, NULL);
1405 break;
1406 case COMPOSE_REPLY_TO_LIST:
1407 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1408 FALSE, TRUE, FALSE, body);
1409 break;
1410 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1411 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1412 FALSE, TRUE, FALSE, body);
1413 break;
1414 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1415 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1416 FALSE, TRUE, FALSE, NULL);
1417 break;
1418 case COMPOSE_FORWARD:
1419 if (prefs_common.forward_as_attachment) {
1420 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1421 return compose;
1422 } else {
1423 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1424 return compose;
1426 break;
1427 case COMPOSE_FORWARD_INLINE:
1428 /* check if we reply to more than one Message */
1429 if (list_len == 1) {
1430 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1431 break;
1433 /* more messages FALL THROUGH */
1434 case COMPOSE_FORWARD_AS_ATTACH:
1435 compose = compose_forward_multiple(NULL, msginfo_list);
1436 break;
1437 case COMPOSE_REDIRECT:
1438 compose = compose_redirect(NULL, msginfo, FALSE);
1439 break;
1440 default:
1441 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1444 if (compose == NULL) {
1445 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1446 return NULL;
1449 compose->rmode = mode;
1450 switch (compose->rmode) {
1451 case COMPOSE_REPLY:
1452 case COMPOSE_REPLY_WITH_QUOTE:
1453 case COMPOSE_REPLY_WITHOUT_QUOTE:
1454 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1455 debug_print("reply mode Normal\n");
1456 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1457 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1458 break;
1459 case COMPOSE_REPLY_TO_SENDER:
1460 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1461 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1462 debug_print("reply mode Sender\n");
1463 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1464 break;
1465 case COMPOSE_REPLY_TO_ALL:
1466 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1467 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1468 debug_print("reply mode All\n");
1469 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1470 break;
1471 case COMPOSE_REPLY_TO_LIST:
1472 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1473 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1474 debug_print("reply mode List\n");
1475 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1476 break;
1477 case COMPOSE_REPLY_TO_ADDRESS:
1478 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1479 break;
1480 default:
1481 break;
1483 return compose;
1486 static Compose *compose_reply(MsgInfo *msginfo,
1487 ComposeQuoteMode quote_mode,
1488 gboolean to_all,
1489 gboolean to_ml,
1490 gboolean to_sender,
1491 const gchar *body)
1493 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1494 to_sender, FALSE, body);
1497 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1498 ComposeQuoteMode quote_mode,
1499 gboolean to_all,
1500 gboolean to_sender,
1501 const gchar *body)
1503 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1504 to_sender, TRUE, body);
1507 static void compose_extract_original_charset(Compose *compose)
1509 MsgInfo *info = NULL;
1510 if (compose->replyinfo) {
1511 info = compose->replyinfo;
1512 } else if (compose->fwdinfo) {
1513 info = compose->fwdinfo;
1514 } else if (compose->targetinfo) {
1515 info = compose->targetinfo;
1517 if (info) {
1518 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1519 MimeInfo *partinfo = mimeinfo;
1520 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1521 partinfo = procmime_mimeinfo_next(partinfo);
1522 if (partinfo) {
1523 compose->orig_charset =
1524 g_strdup(procmime_mimeinfo_get_parameter(
1525 partinfo, "charset"));
1527 procmime_mimeinfo_free_all(&mimeinfo);
1531 #define SIGNAL_BLOCK(buffer) { \
1532 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1533 G_CALLBACK(compose_changed_cb), \
1534 compose); \
1535 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1536 G_CALLBACK(text_inserted), \
1537 compose); \
1540 #define SIGNAL_UNBLOCK(buffer) { \
1541 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1542 G_CALLBACK(compose_changed_cb), \
1543 compose); \
1544 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1545 G_CALLBACK(text_inserted), \
1546 compose); \
1549 static Compose *compose_generic_reply(MsgInfo *msginfo,
1550 ComposeQuoteMode quote_mode,
1551 gboolean to_all, gboolean to_ml,
1552 gboolean to_sender,
1553 gboolean followup_and_reply_to,
1554 const gchar *body)
1556 Compose *compose;
1557 PrefsAccount *account = NULL;
1558 GtkTextView *textview;
1559 GtkTextBuffer *textbuf;
1560 gboolean quote = FALSE;
1561 const gchar *qmark = NULL;
1562 const gchar *body_fmt = NULL;
1563 gchar *s_system = NULL;
1564 START_TIMING("");
1565 cm_return_val_if_fail(msginfo != NULL, NULL);
1566 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1568 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1570 cm_return_val_if_fail(account != NULL, NULL);
1572 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1573 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1575 compose->updating = TRUE;
1577 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1578 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1580 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1581 if (!compose->replyinfo)
1582 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1584 compose_extract_original_charset(compose);
1586 if (msginfo->folder && msginfo->folder->ret_rcpt)
1587 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1589 /* Set save folder */
1590 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1591 gchar *folderidentifier;
1593 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1594 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1595 folderidentifier = folder_item_get_identifier(msginfo->folder);
1596 compose_set_save_to(compose, folderidentifier);
1597 g_free(folderidentifier);
1600 if (compose_parse_header(compose, msginfo) < 0) {
1601 compose->updating = FALSE;
1602 compose_destroy(compose);
1603 return NULL;
1606 /* override from name according to folder properties */
1607 if (msginfo->folder && msginfo->folder->prefs &&
1608 msginfo->folder->prefs->reply_with_format &&
1609 msginfo->folder->prefs->reply_override_from_format &&
1610 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1612 gchar *tmp = NULL;
1613 gchar *buf = NULL;
1615 /* decode \-escape sequences in the internal representation of the quote format */
1616 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1617 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1619 #ifdef USE_ENCHANT
1620 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1621 compose->gtkaspell);
1622 #else
1623 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1624 #endif
1625 quote_fmt_scan_string(tmp);
1626 quote_fmt_parse();
1628 buf = quote_fmt_get_buffer();
1629 if (buf == NULL)
1630 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1631 else
1632 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1633 quote_fmt_reset_vartable();
1634 quote_fmtlex_destroy();
1636 g_free(tmp);
1639 textview = (GTK_TEXT_VIEW(compose->text));
1640 textbuf = gtk_text_view_get_buffer(textview);
1641 compose_create_tags(textview, compose);
1643 undo_block(compose->undostruct);
1644 #ifdef USE_ENCHANT
1645 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1646 gtkaspell_block_check(compose->gtkaspell);
1647 #endif
1649 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1650 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1651 /* use the reply format of folder (if enabled), or the account's one
1652 (if enabled) or fallback to the global reply format, which is always
1653 enabled (even if empty), and use the relevant quotemark */
1654 quote = TRUE;
1655 if (msginfo->folder && msginfo->folder->prefs &&
1656 msginfo->folder->prefs->reply_with_format) {
1657 qmark = msginfo->folder->prefs->reply_quotemark;
1658 body_fmt = msginfo->folder->prefs->reply_body_format;
1660 } else if (account->reply_with_format) {
1661 qmark = account->reply_quotemark;
1662 body_fmt = account->reply_body_format;
1664 } else {
1665 qmark = prefs_common.quotemark;
1666 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1667 body_fmt = gettext(prefs_common.quotefmt);
1668 else
1669 body_fmt = "";
1673 if (quote) {
1674 /* empty quotemark is not allowed */
1675 if (qmark == NULL || *qmark == '\0')
1676 qmark = "> ";
1677 compose_quote_fmt(compose, compose->replyinfo,
1678 body_fmt, qmark, body, FALSE, TRUE,
1679 _("The body of the \"Reply\" template has an error at line %d."));
1680 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1681 quote_fmt_reset_vartable();
1684 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1685 compose_force_encryption(compose, account, FALSE, s_system);
1688 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1689 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1690 compose_force_signing(compose, account, s_system);
1692 g_free(s_system);
1694 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1695 ((account->default_encrypt_reply && MSG_IS_ENCRYPTED(compose->replyinfo->flags)) ||
1696 (account->default_sign_reply && MSG_IS_SIGNED(compose->replyinfo->flags))))
1697 alertpanel_error(_("You have opted to sign and/or encrypt this "
1698 "message but have not selected a privacy system.\n\n"
1699 "Signing and encrypting have been disabled for this "
1700 "message."));
1702 SIGNAL_BLOCK(textbuf);
1704 if (account->auto_sig)
1705 compose_insert_sig(compose, FALSE);
1707 compose_wrap_all(compose);
1709 #ifdef USE_ENCHANT
1710 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1711 gtkaspell_highlight_all(compose->gtkaspell);
1712 gtkaspell_unblock_check(compose->gtkaspell);
1713 #endif
1714 SIGNAL_UNBLOCK(textbuf);
1716 gtk_widget_grab_focus(compose->text);
1718 undo_unblock(compose->undostruct);
1720 if (prefs_common.auto_exteditor)
1721 compose_exec_ext_editor(compose);
1723 compose->modified = FALSE;
1724 compose_set_title(compose);
1726 compose->updating = FALSE;
1727 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1728 SCROLL_TO_CURSOR(compose);
1730 if (compose->deferred_destroy) {
1731 compose_destroy(compose);
1732 return NULL;
1734 END_TIMING();
1736 return compose;
1739 #define INSERT_FW_HEADER(var, hdr) \
1740 if (msginfo->var && *msginfo->var) { \
1741 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1742 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1743 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1746 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1747 gboolean as_attach, const gchar *body,
1748 gboolean no_extedit,
1749 gboolean batch)
1751 Compose *compose;
1752 GtkTextView *textview;
1753 GtkTextBuffer *textbuf;
1754 gint cursor_pos = -1;
1755 ComposeMode mode;
1757 cm_return_val_if_fail(msginfo != NULL, NULL);
1758 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1760 if (!account && !(account = compose_find_account(msginfo)))
1761 account = cur_account;
1763 if (!prefs_common.forward_as_attachment)
1764 mode = COMPOSE_FORWARD_INLINE;
1765 else
1766 mode = COMPOSE_FORWARD;
1767 compose = compose_create(account, msginfo->folder, mode, batch);
1768 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1770 compose->updating = TRUE;
1771 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1772 if (!compose->fwdinfo)
1773 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1775 compose_extract_original_charset(compose);
1777 if (msginfo->subject && *msginfo->subject) {
1778 gchar *buf, *buf2, *p;
1780 buf = p = g_strdup(msginfo->subject);
1781 p += subject_get_prefix_length(p);
1782 memmove(buf, p, strlen(p) + 1);
1784 buf2 = g_strdup_printf("Fw: %s", buf);
1785 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1787 g_free(buf);
1788 g_free(buf2);
1791 /* override from name according to folder properties */
1792 if (msginfo->folder && msginfo->folder->prefs &&
1793 msginfo->folder->prefs->forward_with_format &&
1794 msginfo->folder->prefs->forward_override_from_format &&
1795 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1797 gchar *tmp = NULL;
1798 gchar *buf = NULL;
1799 MsgInfo *full_msginfo = NULL;
1801 if (!as_attach)
1802 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1803 if (!full_msginfo)
1804 full_msginfo = procmsg_msginfo_copy(msginfo);
1806 /* decode \-escape sequences in the internal representation of the quote format */
1807 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1808 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1810 #ifdef USE_ENCHANT
1811 gtkaspell_block_check(compose->gtkaspell);
1812 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1813 compose->gtkaspell);
1814 #else
1815 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1816 #endif
1817 quote_fmt_scan_string(tmp);
1818 quote_fmt_parse();
1820 buf = quote_fmt_get_buffer();
1821 if (buf == NULL)
1822 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1823 else
1824 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1825 quote_fmt_reset_vartable();
1826 quote_fmtlex_destroy();
1828 g_free(tmp);
1829 procmsg_msginfo_free(&full_msginfo);
1832 textview = GTK_TEXT_VIEW(compose->text);
1833 textbuf = gtk_text_view_get_buffer(textview);
1834 compose_create_tags(textview, compose);
1836 undo_block(compose->undostruct);
1837 if (as_attach) {
1838 gchar *msgfile;
1840 msgfile = procmsg_get_message_file(msginfo);
1841 if (!is_file_exist(msgfile))
1842 g_warning("%s: file does not exist", msgfile);
1843 else
1844 compose_attach_append(compose, msgfile, msgfile,
1845 "message/rfc822", NULL);
1847 g_free(msgfile);
1848 } else {
1849 const gchar *qmark = NULL;
1850 const gchar *body_fmt = NULL;
1851 MsgInfo *full_msginfo;
1853 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1854 if (!full_msginfo)
1855 full_msginfo = procmsg_msginfo_copy(msginfo);
1857 /* use the forward format of folder (if enabled), or the account's one
1858 (if enabled) or fallback to the global forward format, which is always
1859 enabled (even if empty), and use the relevant quotemark */
1860 if (msginfo->folder && msginfo->folder->prefs &&
1861 msginfo->folder->prefs->forward_with_format) {
1862 qmark = msginfo->folder->prefs->forward_quotemark;
1863 body_fmt = msginfo->folder->prefs->forward_body_format;
1865 } else if (account->forward_with_format) {
1866 qmark = account->forward_quotemark;
1867 body_fmt = account->forward_body_format;
1869 } else {
1870 qmark = prefs_common.fw_quotemark;
1871 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1872 body_fmt = gettext(prefs_common.fw_quotefmt);
1873 else
1874 body_fmt = "";
1877 /* empty quotemark is not allowed */
1878 if (qmark == NULL || *qmark == '\0')
1879 qmark = "> ";
1881 compose_quote_fmt(compose, full_msginfo,
1882 body_fmt, qmark, body, FALSE, TRUE,
1883 _("The body of the \"Forward\" template has an error at line %d."));
1884 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1885 quote_fmt_reset_vartable();
1886 compose_attach_parts(compose, msginfo);
1888 procmsg_msginfo_free(&full_msginfo);
1891 SIGNAL_BLOCK(textbuf);
1893 if (account->auto_sig)
1894 compose_insert_sig(compose, FALSE);
1896 compose_wrap_all(compose);
1898 #ifdef USE_ENCHANT
1899 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1900 gtkaspell_highlight_all(compose->gtkaspell);
1901 gtkaspell_unblock_check(compose->gtkaspell);
1902 #endif
1903 SIGNAL_UNBLOCK(textbuf);
1905 cursor_pos = quote_fmt_get_cursor_pos();
1906 if (cursor_pos == -1)
1907 gtk_widget_grab_focus(compose->header_last->entry);
1908 else
1909 gtk_widget_grab_focus(compose->text);
1911 if (!no_extedit && prefs_common.auto_exteditor)
1912 compose_exec_ext_editor(compose);
1914 /*save folder*/
1915 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1916 gchar *folderidentifier;
1918 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1919 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1920 folderidentifier = folder_item_get_identifier(msginfo->folder);
1921 compose_set_save_to(compose, folderidentifier);
1922 g_free(folderidentifier);
1925 undo_unblock(compose->undostruct);
1927 compose->modified = FALSE;
1928 compose_set_title(compose);
1930 compose->updating = FALSE;
1931 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1932 SCROLL_TO_CURSOR(compose);
1934 if (compose->deferred_destroy) {
1935 compose_destroy(compose);
1936 return NULL;
1939 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1941 return compose;
1944 #undef INSERT_FW_HEADER
1946 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1948 Compose *compose;
1949 GtkTextView *textview;
1950 GtkTextBuffer *textbuf;
1951 GtkTextIter iter;
1952 GSList *msginfo;
1953 gchar *msgfile;
1954 gboolean single_mail = TRUE;
1956 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1958 if (g_slist_length(msginfo_list) > 1)
1959 single_mail = FALSE;
1961 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1962 if (((MsgInfo *)msginfo->data)->folder == NULL)
1963 return NULL;
1965 /* guess account from first selected message */
1966 if (!account &&
1967 !(account = compose_find_account(msginfo_list->data)))
1968 account = cur_account;
1970 cm_return_val_if_fail(account != NULL, NULL);
1972 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1973 if (msginfo->data) {
1974 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1975 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1979 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1980 g_warning("no msginfo_list");
1981 return NULL;
1984 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1985 compose_apply_folder_privacy_settings(compose, ((MsgInfo *)msginfo_list->data)->folder);
1987 compose->updating = TRUE;
1989 /* override from name according to folder properties */
1990 if (msginfo_list->data) {
1991 MsgInfo *msginfo = msginfo_list->data;
1993 if (msginfo->folder && msginfo->folder->prefs &&
1994 msginfo->folder->prefs->forward_with_format &&
1995 msginfo->folder->prefs->forward_override_from_format &&
1996 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1998 gchar *tmp = NULL;
1999 gchar *buf = NULL;
2001 /* decode \-escape sequences in the internal representation of the quote format */
2002 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2003 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2005 #ifdef USE_ENCHANT
2006 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2007 compose->gtkaspell);
2008 #else
2009 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2010 #endif
2011 quote_fmt_scan_string(tmp);
2012 quote_fmt_parse();
2014 buf = quote_fmt_get_buffer();
2015 if (buf == NULL)
2016 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2017 else
2018 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2019 quote_fmt_reset_vartable();
2020 quote_fmtlex_destroy();
2022 g_free(tmp);
2026 textview = GTK_TEXT_VIEW(compose->text);
2027 textbuf = gtk_text_view_get_buffer(textview);
2028 compose_create_tags(textview, compose);
2030 undo_block(compose->undostruct);
2031 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2032 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2034 if (!is_file_exist(msgfile))
2035 g_warning("%s: file does not exist", msgfile);
2036 else
2037 compose_attach_append(compose, msgfile, msgfile,
2038 "message/rfc822", NULL);
2039 g_free(msgfile);
2042 if (single_mail) {
2043 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2044 if (info->subject && *info->subject) {
2045 gchar *buf, *buf2, *p;
2047 buf = p = g_strdup(info->subject);
2048 p += subject_get_prefix_length(p);
2049 memmove(buf, p, strlen(p) + 1);
2051 buf2 = g_strdup_printf("Fw: %s", buf);
2052 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2054 g_free(buf);
2055 g_free(buf2);
2057 } else {
2058 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2059 _("Fw: multiple emails"));
2062 SIGNAL_BLOCK(textbuf);
2064 if (account->auto_sig)
2065 compose_insert_sig(compose, FALSE);
2067 compose_wrap_all(compose);
2069 SIGNAL_UNBLOCK(textbuf);
2071 gtk_text_buffer_get_start_iter(textbuf, &iter);
2072 gtk_text_buffer_place_cursor(textbuf, &iter);
2074 if (prefs_common.auto_exteditor)
2075 compose_exec_ext_editor(compose);
2077 gtk_widget_grab_focus(compose->header_last->entry);
2078 undo_unblock(compose->undostruct);
2079 compose->modified = FALSE;
2080 compose_set_title(compose);
2082 compose->updating = FALSE;
2083 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2084 SCROLL_TO_CURSOR(compose);
2086 if (compose->deferred_destroy) {
2087 compose_destroy(compose);
2088 return NULL;
2091 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2093 return compose;
2096 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2098 GtkTextIter start = *iter;
2099 GtkTextIter end_iter;
2100 int start_pos = gtk_text_iter_get_offset(&start);
2101 gchar *str = NULL;
2102 if (!compose->account->sig_sep)
2103 return FALSE;
2105 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2106 start_pos+strlen(compose->account->sig_sep));
2108 /* check sig separator */
2109 str = gtk_text_iter_get_text(&start, &end_iter);
2110 if (!strcmp(str, compose->account->sig_sep)) {
2111 gchar *tmp = NULL;
2112 /* check end of line (\n) */
2113 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2114 start_pos+strlen(compose->account->sig_sep));
2115 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2116 start_pos+strlen(compose->account->sig_sep)+1);
2117 tmp = gtk_text_iter_get_text(&start, &end_iter);
2118 if (!strcmp(tmp,"\n")) {
2119 g_free(str);
2120 g_free(tmp);
2121 return TRUE;
2123 g_free(tmp);
2125 g_free(str);
2127 return FALSE;
2130 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2132 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2133 Compose *compose = (Compose *)data;
2134 FolderItem *old_item = NULL;
2135 FolderItem *new_item = NULL;
2136 gchar *old_id, *new_id;
2138 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2139 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2140 return FALSE;
2142 old_item = hookdata->item;
2143 new_item = hookdata->item2;
2145 old_id = folder_item_get_identifier(old_item);
2146 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2148 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2149 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2150 compose->targetinfo->folder = new_item;
2153 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2154 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2155 compose->replyinfo->folder = new_item;
2158 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2159 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2160 compose->fwdinfo->folder = new_item;
2163 g_free(old_id);
2164 g_free(new_id);
2165 return FALSE;
2168 static void compose_colorize_signature(Compose *compose)
2170 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2171 GtkTextIter iter;
2172 GtkTextIter end_iter;
2173 gtk_text_buffer_get_start_iter(buffer, &iter);
2174 while (gtk_text_iter_forward_line(&iter))
2175 if (compose_is_sig_separator(compose, buffer, &iter)) {
2176 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2177 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2181 #define BLOCK_WRAP() { \
2182 prev_autowrap = compose->autowrap; \
2183 buffer = gtk_text_view_get_buffer( \
2184 GTK_TEXT_VIEW(compose->text)); \
2185 compose->autowrap = FALSE; \
2187 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2188 G_CALLBACK(compose_changed_cb), \
2189 compose); \
2190 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2191 G_CALLBACK(text_inserted), \
2192 compose); \
2194 #define UNBLOCK_WRAP() { \
2195 compose->autowrap = prev_autowrap; \
2196 if (compose->autowrap) { \
2197 gint old = compose->draft_timeout_tag; \
2198 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2199 compose_wrap_all(compose); \
2200 compose->draft_timeout_tag = old; \
2203 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2204 G_CALLBACK(compose_changed_cb), \
2205 compose); \
2206 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2207 G_CALLBACK(text_inserted), \
2208 compose); \
2211 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2213 Compose *compose = NULL;
2214 PrefsAccount *account = NULL;
2215 GtkTextView *textview;
2216 GtkTextBuffer *textbuf;
2217 GtkTextMark *mark;
2218 GtkTextIter iter;
2219 FILE *fp;
2220 gboolean use_signing = FALSE;
2221 gboolean use_encryption = FALSE;
2222 gchar *privacy_system = NULL;
2223 int priority = PRIORITY_NORMAL;
2224 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2225 gboolean autowrap = prefs_common.autowrap;
2226 gboolean autoindent = prefs_common.auto_indent;
2227 HeaderEntry *manual_headers = NULL;
2229 cm_return_val_if_fail(msginfo != NULL, NULL);
2230 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2232 if (compose_put_existing_to_front(msginfo)) {
2233 return NULL;
2236 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2237 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2238 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2239 gchar *queueheader_buf = NULL;
2240 gint id, param;
2242 /* Select Account from queue headers */
2243 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2244 "X-Claws-Account-Id:")) {
2245 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2246 account = account_find_from_id(id);
2247 g_free(queueheader_buf);
2249 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2250 "X-Sylpheed-Account-Id:")) {
2251 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2252 account = account_find_from_id(id);
2253 g_free(queueheader_buf);
2255 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2256 "NAID:")) {
2257 id = atoi(&queueheader_buf[strlen("NAID:")]);
2258 account = account_find_from_id(id);
2259 g_free(queueheader_buf);
2261 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2262 "MAID:")) {
2263 id = atoi(&queueheader_buf[strlen("MAID:")]);
2264 account = account_find_from_id(id);
2265 g_free(queueheader_buf);
2267 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2268 "S:")) {
2269 account = account_find_from_address(queueheader_buf, FALSE);
2270 g_free(queueheader_buf);
2272 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2273 "X-Claws-Sign:")) {
2274 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2275 use_signing = param;
2276 g_free(queueheader_buf);
2278 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2279 "X-Sylpheed-Sign:")) {
2280 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2281 use_signing = param;
2282 g_free(queueheader_buf);
2284 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2285 "X-Claws-Encrypt:")) {
2286 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2287 use_encryption = param;
2288 g_free(queueheader_buf);
2290 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2291 "X-Sylpheed-Encrypt:")) {
2292 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2293 use_encryption = param;
2294 g_free(queueheader_buf);
2296 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2297 "X-Claws-Auto-Wrapping:")) {
2298 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2299 autowrap = param;
2300 g_free(queueheader_buf);
2302 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2303 "X-Claws-Auto-Indent:")) {
2304 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2305 autoindent = param;
2306 g_free(queueheader_buf);
2308 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2309 "X-Claws-Privacy-System:")) {
2310 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2311 g_free(queueheader_buf);
2313 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2314 "X-Sylpheed-Privacy-System:")) {
2315 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2316 g_free(queueheader_buf);
2318 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2319 "X-Priority: ")) {
2320 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2321 priority = param;
2322 g_free(queueheader_buf);
2324 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2325 "RMID:")) {
2326 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2327 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2328 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2329 if (orig_item != NULL) {
2330 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2332 g_strfreev(tokens);
2334 g_free(queueheader_buf);
2336 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2337 "FMID:")) {
2338 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2339 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2340 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2341 if (orig_item != NULL) {
2342 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2344 g_strfreev(tokens);
2346 g_free(queueheader_buf);
2348 /* Get manual headers */
2349 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2350 "X-Claws-Manual-Headers:")) {
2351 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2352 if (listmh && *listmh != '\0') {
2353 debug_print("Got manual headers: %s\n", listmh);
2354 manual_headers = procheader_entries_from_str(listmh);
2355 g_free(listmh);
2357 g_free(queueheader_buf);
2359 } else {
2360 account = msginfo->folder->folder->account;
2363 if (!account && prefs_common.reedit_account_autosel) {
2364 gchar *from = NULL;
2365 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2366 extract_address(from);
2367 account = account_find_from_address(from, FALSE);
2368 g_free(from);
2371 if (!account) {
2372 account = cur_account;
2374 cm_return_val_if_fail(account != NULL, NULL);
2376 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2378 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2379 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2380 compose->autowrap = autowrap;
2381 compose->replyinfo = replyinfo;
2382 compose->fwdinfo = fwdinfo;
2384 compose->updating = TRUE;
2385 compose->priority = priority;
2387 if (privacy_system != NULL) {
2388 compose->privacy_system = privacy_system;
2389 compose_use_signing(compose, use_signing);
2390 compose_use_encryption(compose, use_encryption);
2391 compose_update_privacy_system_menu_item(compose, FALSE);
2392 } else {
2393 compose_activate_privacy_system(compose, account, FALSE);
2395 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2397 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2398 compose->targetinfo->tags = g_slist_copy(msginfo->tags);
2400 compose_extract_original_charset(compose);
2402 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2403 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2404 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2405 gchar *queueheader_buf = NULL;
2407 /* Set message save folder */
2408 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2409 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2410 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2411 compose_set_save_to(compose, &queueheader_buf[4]);
2412 g_free(queueheader_buf);
2414 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2415 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2416 if (active) {
2417 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2419 g_free(queueheader_buf);
2423 if (compose_parse_header(compose, msginfo) < 0) {
2424 compose->updating = FALSE;
2425 compose_destroy(compose);
2426 return NULL;
2428 compose_reedit_set_entry(compose, msginfo);
2430 textview = GTK_TEXT_VIEW(compose->text);
2431 textbuf = gtk_text_view_get_buffer(textview);
2432 compose_create_tags(textview, compose);
2434 mark = gtk_text_buffer_get_insert(textbuf);
2435 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2437 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2438 G_CALLBACK(compose_changed_cb),
2439 compose);
2441 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2442 fp = procmime_get_first_encrypted_text_content(msginfo);
2443 if (fp) {
2444 compose_force_encryption(compose, account, TRUE, NULL);
2446 } else {
2447 fp = procmime_get_first_text_content(msginfo);
2449 if (fp == NULL) {
2450 g_warning("Can't get text part");
2453 if (fp != NULL) {
2454 gchar buf[BUFFSIZE];
2455 gboolean prev_autowrap;
2456 GtkTextBuffer *buffer;
2457 BLOCK_WRAP();
2458 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2459 strcrchomp(buf);
2460 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2462 UNBLOCK_WRAP();
2463 claws_fclose(fp);
2466 compose_attach_parts(compose, msginfo);
2468 compose_colorize_signature(compose);
2470 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2471 G_CALLBACK(compose_changed_cb),
2472 compose);
2474 if (manual_headers != NULL) {
2475 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2476 procheader_entries_free(manual_headers);
2477 compose->updating = FALSE;
2478 compose_destroy(compose);
2479 return NULL;
2481 procheader_entries_free(manual_headers);
2484 gtk_widget_grab_focus(compose->text);
2486 if (prefs_common.auto_exteditor) {
2487 compose_exec_ext_editor(compose);
2489 compose->modified = FALSE;
2490 compose_set_title(compose);
2492 compose->updating = FALSE;
2493 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2494 SCROLL_TO_CURSOR(compose);
2496 if (compose->deferred_destroy) {
2497 compose_destroy(compose);
2498 return NULL;
2501 compose->sig_str = account_get_signature_str(compose->account);
2503 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2505 return compose;
2508 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2509 gboolean batch)
2511 Compose *compose;
2512 gchar *filename;
2513 FolderItem *item;
2515 cm_return_val_if_fail(msginfo != NULL, NULL);
2517 if (!account)
2518 account = account_get_reply_account(msginfo,
2519 prefs_common.reply_account_autosel);
2520 cm_return_val_if_fail(account != NULL, NULL);
2522 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2524 compose->updating = TRUE;
2526 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2527 compose->replyinfo = NULL;
2528 compose->fwdinfo = NULL;
2530 compose_show_first_last_header(compose, TRUE);
2532 gtk_widget_grab_focus(compose->header_last->entry);
2534 filename = procmsg_get_message_file(msginfo);
2536 if (filename == NULL) {
2537 compose->updating = FALSE;
2538 compose_destroy(compose);
2540 return NULL;
2543 compose->redirect_filename = filename;
2545 /* Set save folder */
2546 item = msginfo->folder;
2547 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2548 gchar *folderidentifier;
2550 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2551 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2552 folderidentifier = folder_item_get_identifier(item);
2553 compose_set_save_to(compose, folderidentifier);
2554 g_free(folderidentifier);
2557 compose_attach_parts(compose, msginfo);
2559 if (msginfo->subject)
2560 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2561 msginfo->subject);
2562 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2564 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2565 _("The body of the \"Redirect\" template has an error at line %d."));
2566 quote_fmt_reset_vartable();
2567 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2569 compose_colorize_signature(compose);
2572 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2573 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2574 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2576 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2577 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2578 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2579 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2580 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2581 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2582 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2583 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2584 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2586 if (compose->toolbar->draft_btn)
2587 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2588 if (compose->toolbar->insert_btn)
2589 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2590 if (compose->toolbar->attach_btn)
2591 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2592 if (compose->toolbar->sig_btn)
2593 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2594 if (compose->toolbar->exteditor_btn)
2595 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2596 if (compose->toolbar->linewrap_current_btn)
2597 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2598 if (compose->toolbar->linewrap_all_btn)
2599 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2600 if (compose->toolbar->privacy_sign_btn)
2601 gtk_widget_set_sensitive(compose->toolbar->privacy_sign_btn, FALSE);
2602 if (compose->toolbar->privacy_encrypt_btn)
2603 gtk_widget_set_sensitive(compose->toolbar->privacy_encrypt_btn, FALSE);
2605 compose->modified = FALSE;
2606 compose_set_title(compose);
2607 compose->updating = FALSE;
2608 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2609 SCROLL_TO_CURSOR(compose);
2611 if (compose->deferred_destroy) {
2612 compose_destroy(compose);
2613 return NULL;
2616 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2618 return compose;
2621 const GList *compose_get_compose_list(void)
2623 return compose_list;
2626 void compose_entry_append(Compose *compose, const gchar *address,
2627 ComposeEntryType type, ComposePrefType pref_type)
2629 const gchar *header;
2630 gchar *cur, *begin;
2631 gboolean in_quote = FALSE;
2632 if (!address || *address == '\0') return;
2634 switch (type) {
2635 case COMPOSE_CC:
2636 header = N_("Cc:");
2637 break;
2638 case COMPOSE_BCC:
2639 header = N_("Bcc:");
2640 break;
2641 case COMPOSE_REPLYTO:
2642 header = N_("Reply-To:");
2643 break;
2644 case COMPOSE_NEWSGROUPS:
2645 header = N_("Newsgroups:");
2646 break;
2647 case COMPOSE_FOLLOWUPTO:
2648 header = N_( "Followup-To:");
2649 break;
2650 case COMPOSE_INREPLYTO:
2651 header = N_( "In-Reply-To:");
2652 break;
2653 case COMPOSE_TO:
2654 default:
2655 header = N_("To:");
2656 break;
2658 header = prefs_common_translated_header_name(header);
2660 cur = begin = (gchar *)address;
2662 /* we separate the line by commas, but not if we're inside a quoted
2663 * string */
2664 while (*cur != '\0') {
2665 if (*cur == '"')
2666 in_quote = !in_quote;
2667 if (*cur == ',' && !in_quote) {
2668 gchar *tmp = g_strdup(begin);
2669 gchar *o_tmp = tmp;
2670 tmp[cur-begin]='\0';
2671 cur++;
2672 begin = cur;
2673 while (*tmp == ' ' || *tmp == '\t')
2674 tmp++;
2675 compose_add_header_entry(compose, header, tmp, pref_type);
2676 compose_entry_indicate(compose, tmp);
2677 g_free(o_tmp);
2678 continue;
2680 cur++;
2682 if (begin < cur) {
2683 gchar *tmp = g_strdup(begin);
2684 gchar *o_tmp = tmp;
2685 tmp[cur-begin]='\0';
2686 while (*tmp == ' ' || *tmp == '\t')
2687 tmp++;
2688 compose_add_header_entry(compose, header, tmp, pref_type);
2689 compose_entry_indicate(compose, tmp);
2690 g_free(o_tmp);
2694 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2696 GSList *h_list;
2697 GtkEntry *entry;
2699 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2700 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2701 if (gtk_entry_get_text(entry) &&
2702 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2703 gtk_widget_modify_base(
2704 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2705 GTK_STATE_NORMAL, &default_header_bgcolor);
2706 gtk_widget_modify_text(
2707 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2708 GTK_STATE_NORMAL, &default_header_color);
2713 void compose_toolbar_cb(gint action, gpointer data)
2715 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2716 Compose *compose = (Compose*)toolbar_item->parent;
2718 cm_return_if_fail(compose != NULL);
2720 switch(action) {
2721 case A_SEND:
2722 compose_send_cb(NULL, compose);
2723 break;
2724 case A_SEND_LATER:
2725 compose_send_later_cb(NULL, compose);
2726 break;
2727 case A_DRAFT:
2728 compose_draft(compose, COMPOSE_QUIT_EDITING);
2729 break;
2730 case A_INSERT:
2731 compose_insert_file_cb(NULL, compose);
2732 break;
2733 case A_ATTACH:
2734 compose_attach_cb(NULL, compose);
2735 break;
2736 case A_SIG:
2737 compose_insert_sig(compose, FALSE);
2738 break;
2739 case A_REP_SIG:
2740 compose_insert_sig(compose, TRUE);
2741 break;
2742 case A_EXTEDITOR:
2743 compose_ext_editor_cb(NULL, compose);
2744 break;
2745 case A_LINEWRAP_CURRENT:
2746 compose_beautify_paragraph(compose, NULL, TRUE);
2747 break;
2748 case A_LINEWRAP_ALL:
2749 compose_wrap_all_full(compose, TRUE);
2750 break;
2751 case A_ADDRBOOK:
2752 compose_address_cb(NULL, compose);
2753 break;
2754 #ifdef USE_ENCHANT
2755 case A_CHECK_SPELLING:
2756 compose_check_all(NULL, compose);
2757 break;
2758 #endif
2759 case A_PRIVACY_SIGN:
2760 break;
2761 case A_PRIVACY_ENCRYPT:
2762 break;
2763 default:
2764 break;
2768 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2770 gchar *to = NULL;
2771 gchar *cc = NULL;
2772 gchar *bcc = NULL;
2773 gchar *subject = NULL;
2774 gchar *body = NULL;
2775 gchar *temp = NULL;
2776 gsize len = 0;
2777 gchar **attach = NULL;
2778 gchar *inreplyto = NULL;
2779 MailField mfield = NO_FIELD_PRESENT;
2781 /* get mailto parts but skip from */
2782 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2784 if (to) {
2785 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2786 mfield = TO_FIELD_PRESENT;
2788 if (cc)
2789 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2790 if (bcc)
2791 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2792 if (subject) {
2793 if (!g_utf8_validate (subject, -1, NULL)) {
2794 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2795 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2796 g_free(temp);
2797 } else {
2798 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2800 mfield = SUBJECT_FIELD_PRESENT;
2802 if (body) {
2803 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2804 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2805 GtkTextMark *mark;
2806 GtkTextIter iter;
2807 gboolean prev_autowrap = compose->autowrap;
2809 compose->autowrap = FALSE;
2811 mark = gtk_text_buffer_get_insert(buffer);
2812 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2814 if (!g_utf8_validate (body, -1, NULL)) {
2815 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2816 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2817 g_free(temp);
2818 } else {
2819 gtk_text_buffer_insert(buffer, &iter, body, -1);
2821 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2823 compose->autowrap = prev_autowrap;
2824 if (compose->autowrap)
2825 compose_wrap_all(compose);
2826 mfield = BODY_FIELD_PRESENT;
2829 if (attach) {
2830 gint i = 0, att = 0;
2831 gchar *warn_files = NULL;
2832 while (attach[i] != NULL) {
2833 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2834 if (utf8_filename) {
2835 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2836 gchar *tmp = g_strdup_printf("%s%s\n",
2837 warn_files?warn_files:"",
2838 utf8_filename);
2839 g_free(warn_files);
2840 warn_files = tmp;
2841 att++;
2843 g_free(utf8_filename);
2844 } else {
2845 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2847 i++;
2849 if (warn_files) {
2850 alertpanel_notice(ngettext(
2851 "The following file has been attached: \n%s",
2852 "The following files have been attached: \n%s", att), warn_files);
2853 g_free(warn_files);
2856 if (inreplyto)
2857 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2859 g_free(to);
2860 g_free(cc);
2861 g_free(bcc);
2862 g_free(subject);
2863 g_free(body);
2864 g_strfreev(attach);
2865 g_free(inreplyto);
2867 return mfield;
2870 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2872 static HeaderEntry hentry[] = {
2873 {"Reply-To:", NULL, TRUE },
2874 {"Cc:", NULL, TRUE },
2875 {"References:", NULL, FALSE },
2876 {"Bcc:", NULL, TRUE },
2877 {"Newsgroups:", NULL, TRUE },
2878 {"Followup-To:", NULL, TRUE },
2879 {"List-Post:", NULL, FALSE },
2880 {"X-Priority:", NULL, FALSE },
2881 {NULL, NULL, FALSE }
2884 enum
2886 H_REPLY_TO = 0,
2887 H_CC = 1,
2888 H_REFERENCES = 2,
2889 H_BCC = 3,
2890 H_NEWSGROUPS = 4,
2891 H_FOLLOWUP_TO = 5,
2892 H_LIST_POST = 6,
2893 H_X_PRIORITY = 7
2896 FILE *fp;
2898 cm_return_val_if_fail(msginfo != NULL, -1);
2900 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
2901 procheader_get_header_fields(fp, hentry);
2902 claws_fclose(fp);
2904 if (hentry[H_REPLY_TO].body != NULL) {
2905 if (hentry[H_REPLY_TO].body[0] != '\0') {
2906 compose->replyto =
2907 conv_unmime_header(hentry[H_REPLY_TO].body,
2908 NULL, TRUE);
2910 g_free(hentry[H_REPLY_TO].body);
2911 hentry[H_REPLY_TO].body = NULL;
2913 if (hentry[H_CC].body != NULL) {
2914 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2915 g_free(hentry[H_CC].body);
2916 hentry[H_CC].body = NULL;
2918 if (hentry[H_REFERENCES].body != NULL) {
2919 if (compose->mode == COMPOSE_REEDIT)
2920 compose->references = hentry[H_REFERENCES].body;
2921 else {
2922 compose->references = compose_parse_references
2923 (hentry[H_REFERENCES].body, msginfo->msgid);
2924 g_free(hentry[H_REFERENCES].body);
2926 hentry[H_REFERENCES].body = NULL;
2928 if (hentry[H_BCC].body != NULL) {
2929 if (compose->mode == COMPOSE_REEDIT)
2930 compose->bcc =
2931 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2932 g_free(hentry[H_BCC].body);
2933 hentry[H_BCC].body = NULL;
2935 if (hentry[H_NEWSGROUPS].body != NULL) {
2936 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2937 hentry[H_NEWSGROUPS].body = NULL;
2939 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2940 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2941 compose->followup_to =
2942 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2943 NULL, TRUE);
2945 g_free(hentry[H_FOLLOWUP_TO].body);
2946 hentry[H_FOLLOWUP_TO].body = NULL;
2948 if (hentry[H_LIST_POST].body != NULL) {
2949 gchar *to = NULL, *start = NULL;
2951 extract_address(hentry[H_LIST_POST].body);
2952 if (hentry[H_LIST_POST].body[0] != '\0') {
2953 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2955 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2956 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2958 if (to) {
2959 g_free(compose->ml_post);
2960 compose->ml_post = to;
2963 g_free(hentry[H_LIST_POST].body);
2964 hentry[H_LIST_POST].body = NULL;
2967 /* CLAWS - X-Priority */
2968 if (compose->mode == COMPOSE_REEDIT)
2969 if (hentry[H_X_PRIORITY].body != NULL) {
2970 gint priority;
2972 priority = atoi(hentry[H_X_PRIORITY].body);
2973 g_free(hentry[H_X_PRIORITY].body);
2975 hentry[H_X_PRIORITY].body = NULL;
2977 if (priority < PRIORITY_HIGHEST ||
2978 priority > PRIORITY_LOWEST)
2979 priority = PRIORITY_NORMAL;
2981 compose->priority = priority;
2984 if (compose->mode == COMPOSE_REEDIT) {
2985 if (msginfo->inreplyto && *msginfo->inreplyto)
2986 compose->inreplyto = g_strdup(msginfo->inreplyto);
2988 if (msginfo->msgid && *msginfo->msgid &&
2989 compose->folder != NULL &&
2990 compose->folder->stype == F_DRAFT)
2991 compose->msgid = g_strdup(msginfo->msgid);
2992 } else {
2993 if (msginfo->msgid && *msginfo->msgid)
2994 compose->inreplyto = g_strdup(msginfo->msgid);
2996 if (!compose->references) {
2997 if (msginfo->msgid && *msginfo->msgid) {
2998 if (msginfo->inreplyto && *msginfo->inreplyto)
2999 compose->references =
3000 g_strdup_printf("<%s>\n\t<%s>",
3001 msginfo->inreplyto,
3002 msginfo->msgid);
3003 else
3004 compose->references =
3005 g_strconcat("<", msginfo->msgid, ">",
3006 NULL);
3007 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3008 compose->references =
3009 g_strconcat("<", msginfo->inreplyto, ">",
3010 NULL);
3015 return 0;
3018 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3020 FILE *fp;
3021 HeaderEntry *he;
3023 cm_return_val_if_fail(msginfo != NULL, -1);
3025 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
3026 procheader_get_header_fields(fp, entries);
3027 claws_fclose(fp);
3029 he = entries;
3030 while (he != NULL && he->name != NULL) {
3031 GtkTreeIter iter;
3032 GtkListStore *model = NULL;
3034 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3035 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3036 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3037 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3038 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3039 ++he;
3042 return 0;
3045 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3047 GSList *ref_id_list, *cur;
3048 GString *new_ref;
3049 gchar *new_ref_str;
3051 ref_id_list = references_list_append(NULL, ref);
3052 if (!ref_id_list) return NULL;
3053 if (msgid && *msgid)
3054 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3056 for (;;) {
3057 gint len = 0;
3059 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3060 /* "<" + Message-ID + ">" + CR+LF+TAB */
3061 len += strlen((gchar *)cur->data) + 5;
3063 if (len > MAX_REFERENCES_LEN) {
3064 /* remove second message-ID */
3065 if (ref_id_list && ref_id_list->next &&
3066 ref_id_list->next->next) {
3067 g_free(ref_id_list->next->data);
3068 ref_id_list = g_slist_remove
3069 (ref_id_list, ref_id_list->next->data);
3070 } else {
3071 slist_free_strings_full(ref_id_list);
3072 return NULL;
3074 } else
3075 break;
3078 new_ref = g_string_new("");
3079 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3080 if (new_ref->len > 0)
3081 g_string_append(new_ref, "\n\t");
3082 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3085 slist_free_strings_full(ref_id_list);
3087 new_ref_str = new_ref->str;
3088 g_string_free(new_ref, FALSE);
3090 return new_ref_str;
3093 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3094 const gchar *fmt, const gchar *qmark,
3095 const gchar *body, gboolean rewrap,
3096 gboolean need_unescape,
3097 const gchar *err_msg)
3099 MsgInfo* dummyinfo = NULL;
3100 gchar *quote_str = NULL;
3101 gchar *buf;
3102 gboolean prev_autowrap;
3103 const gchar *trimmed_body = body;
3104 gint cursor_pos = -1;
3105 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3106 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3107 GtkTextIter iter;
3108 GtkTextMark *mark;
3111 SIGNAL_BLOCK(buffer);
3113 if (!msginfo) {
3114 dummyinfo = compose_msginfo_new_from_compose(compose);
3115 msginfo = dummyinfo;
3118 if (qmark != NULL) {
3119 #ifdef USE_ENCHANT
3120 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3121 compose->gtkaspell);
3122 #else
3123 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3124 #endif
3125 quote_fmt_scan_string(qmark);
3126 quote_fmt_parse();
3128 buf = quote_fmt_get_buffer();
3130 if (buf == NULL)
3131 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3132 else
3133 Xstrdup_a(quote_str, buf, goto error)
3136 if (fmt && *fmt != '\0') {
3138 if (trimmed_body)
3139 while (*trimmed_body == '\n')
3140 trimmed_body++;
3142 #ifdef USE_ENCHANT
3143 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3144 compose->gtkaspell);
3145 #else
3146 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3147 #endif
3148 if (need_unescape) {
3149 gchar *tmp = NULL;
3151 /* decode \-escape sequences in the internal representation of the quote format */
3152 tmp = g_malloc(strlen(fmt)+1);
3153 pref_get_unescaped_pref(tmp, fmt);
3154 quote_fmt_scan_string(tmp);
3155 quote_fmt_parse();
3156 g_free(tmp);
3157 } else {
3158 quote_fmt_scan_string(fmt);
3159 quote_fmt_parse();
3162 buf = quote_fmt_get_buffer();
3164 if (buf == NULL) {
3165 gint line = quote_fmt_get_line();
3166 alertpanel_error(err_msg, line);
3168 goto error;
3171 } else
3172 buf = "";
3174 prev_autowrap = compose->autowrap;
3175 compose->autowrap = FALSE;
3177 mark = gtk_text_buffer_get_insert(buffer);
3178 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3179 if (g_utf8_validate(buf, -1, NULL)) {
3180 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3181 } else {
3182 gchar *tmpout = NULL;
3183 tmpout = conv_codeset_strdup
3184 (buf, conv_get_locale_charset_str_no_utf8(),
3185 CS_INTERNAL);
3186 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3187 g_free(tmpout);
3188 tmpout = g_malloc(strlen(buf)*2+1);
3189 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3191 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3192 g_free(tmpout);
3195 cursor_pos = quote_fmt_get_cursor_pos();
3196 if (cursor_pos == -1)
3197 cursor_pos = gtk_text_iter_get_offset(&iter);
3198 compose->set_cursor_pos = cursor_pos;
3200 gtk_text_buffer_get_start_iter(buffer, &iter);
3201 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3202 gtk_text_buffer_place_cursor(buffer, &iter);
3204 compose->autowrap = prev_autowrap;
3205 if (compose->autowrap && rewrap)
3206 compose_wrap_all(compose);
3208 goto ok;
3210 error:
3211 buf = NULL;
3213 SIGNAL_UNBLOCK(buffer);
3215 procmsg_msginfo_free( &dummyinfo );
3217 return buf;
3220 /* if ml_post is of type addr@host and from is of type
3221 * addr-anything@host, return TRUE
3223 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3225 gchar *left_ml = NULL;
3226 gchar *right_ml = NULL;
3227 gchar *left_from = NULL;
3228 gchar *right_from = NULL;
3229 gboolean result = FALSE;
3231 if (!ml_post || !from)
3232 return FALSE;
3234 left_ml = g_strdup(ml_post);
3235 if (strstr(left_ml, "@")) {
3236 right_ml = strstr(left_ml, "@")+1;
3237 *(strstr(left_ml, "@")) = '\0';
3240 left_from = g_strdup(from);
3241 if (strstr(left_from, "@")) {
3242 right_from = strstr(left_from, "@")+1;
3243 *(strstr(left_from, "@")) = '\0';
3246 if (right_ml && right_from
3247 && !strncmp(left_from, left_ml, strlen(left_ml))
3248 && !strcmp(right_from, right_ml)) {
3249 result = TRUE;
3251 g_free(left_ml);
3252 g_free(left_from);
3254 return result;
3257 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3258 gboolean respect_default_to)
3260 if (!compose)
3261 return;
3262 if (!folder || !folder->prefs)
3263 return;
3265 if (respect_default_to && folder->prefs->enable_default_to) {
3266 compose_entry_append(compose, folder->prefs->default_to,
3267 COMPOSE_TO, PREF_FOLDER);
3268 compose_entry_indicate(compose, folder->prefs->default_to);
3270 if (folder->prefs->enable_default_cc) {
3271 compose_entry_append(compose, folder->prefs->default_cc,
3272 COMPOSE_CC, PREF_FOLDER);
3273 compose_entry_indicate(compose, folder->prefs->default_cc);
3275 if (folder->prefs->enable_default_bcc) {
3276 compose_entry_append(compose, folder->prefs->default_bcc,
3277 COMPOSE_BCC, PREF_FOLDER);
3278 compose_entry_indicate(compose, folder->prefs->default_bcc);
3280 if (folder->prefs->enable_default_replyto) {
3281 compose_entry_append(compose, folder->prefs->default_replyto,
3282 COMPOSE_REPLYTO, PREF_FOLDER);
3283 compose_entry_indicate(compose, folder->prefs->default_replyto);
3287 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3289 gchar *buf, *buf2;
3290 gchar *p;
3292 if (!compose || !msginfo)
3293 return;
3295 if (msginfo->subject && *msginfo->subject) {
3296 buf = p = g_strdup(msginfo->subject);
3297 p += subject_get_prefix_length(p);
3298 memmove(buf, p, strlen(p) + 1);
3300 buf2 = g_strdup_printf("Re: %s", buf);
3301 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3303 g_free(buf2);
3304 g_free(buf);
3305 } else
3306 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3309 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3310 gboolean to_all, gboolean to_ml,
3311 gboolean to_sender,
3312 gboolean followup_and_reply_to)
3314 GSList *cc_list = NULL;
3315 GSList *cur;
3316 gchar *from = NULL;
3317 gchar *replyto = NULL;
3318 gchar *ac_email = NULL;
3320 gboolean reply_to_ml = FALSE;
3321 gboolean default_reply_to = FALSE;
3323 cm_return_if_fail(compose->account != NULL);
3324 cm_return_if_fail(msginfo != NULL);
3326 reply_to_ml = to_ml && compose->ml_post;
3328 default_reply_to = msginfo->folder &&
3329 msginfo->folder->prefs->enable_default_reply_to;
3331 if (compose->account->protocol != A_NNTP) {
3332 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3334 if (reply_to_ml && !default_reply_to) {
3336 gboolean is_subscr = is_subscription(compose->ml_post,
3337 msginfo->from);
3338 if (!is_subscr) {
3339 /* normal answer to ml post with a reply-to */
3340 compose_entry_append(compose,
3341 compose->ml_post,
3342 COMPOSE_TO, PREF_ML);
3343 if (compose->replyto)
3344 compose_entry_append(compose,
3345 compose->replyto,
3346 COMPOSE_CC, PREF_ML);
3347 } else {
3348 /* answer to subscription confirmation */
3349 if (compose->replyto)
3350 compose_entry_append(compose,
3351 compose->replyto,
3352 COMPOSE_TO, PREF_ML);
3353 else if (msginfo->from)
3354 compose_entry_append(compose,
3355 msginfo->from,
3356 COMPOSE_TO, PREF_ML);
3359 else if (!(to_all || to_sender) && default_reply_to) {
3360 compose_entry_append(compose,
3361 msginfo->folder->prefs->default_reply_to,
3362 COMPOSE_TO, PREF_FOLDER);
3363 compose_entry_indicate(compose,
3364 msginfo->folder->prefs->default_reply_to);
3365 } else {
3366 gchar *tmp1 = NULL;
3367 if (!msginfo->from)
3368 return;
3369 if (to_sender)
3370 compose_entry_append(compose, msginfo->from,
3371 COMPOSE_TO, PREF_NONE);
3372 else if (to_all) {
3373 Xstrdup_a(tmp1, msginfo->from, return);
3374 extract_address(tmp1);
3375 compose_entry_append(compose,
3376 (!account_find_from_address(tmp1, FALSE))
3377 ? msginfo->from :
3378 msginfo->to,
3379 COMPOSE_TO, PREF_NONE);
3380 if (compose->replyto)
3381 compose_entry_append(compose,
3382 compose->replyto,
3383 COMPOSE_CC, PREF_NONE);
3384 } else {
3385 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3386 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3387 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3388 if (compose->replyto) {
3389 compose_entry_append(compose,
3390 compose->replyto,
3391 COMPOSE_TO, PREF_NONE);
3392 } else {
3393 compose_entry_append(compose,
3394 msginfo->from ? msginfo->from : "",
3395 COMPOSE_TO, PREF_NONE);
3397 } else {
3398 /* replying to own mail, use original recp */
3399 compose_entry_append(compose,
3400 msginfo->to ? msginfo->to : "",
3401 COMPOSE_TO, PREF_NONE);
3402 compose_entry_append(compose,
3403 msginfo->cc ? msginfo->cc : "",
3404 COMPOSE_CC, PREF_NONE);
3408 } else {
3409 if (to_sender || (compose->followup_to &&
3410 !strncmp(compose->followup_to, "poster", 6)))
3411 compose_entry_append
3412 (compose,
3413 (compose->replyto ? compose->replyto :
3414 msginfo->from ? msginfo->from : ""),
3415 COMPOSE_TO, PREF_NONE);
3417 else if (followup_and_reply_to || to_all) {
3418 compose_entry_append
3419 (compose,
3420 (compose->replyto ? compose->replyto :
3421 msginfo->from ? msginfo->from : ""),
3422 COMPOSE_TO, PREF_NONE);
3424 compose_entry_append
3425 (compose,
3426 compose->followup_to ? compose->followup_to :
3427 compose->newsgroups ? compose->newsgroups : "",
3428 COMPOSE_NEWSGROUPS, PREF_NONE);
3430 compose_entry_append
3431 (compose,
3432 msginfo->cc ? msginfo->cc : "",
3433 COMPOSE_CC, PREF_NONE);
3435 else
3436 compose_entry_append
3437 (compose,
3438 compose->followup_to ? compose->followup_to :
3439 compose->newsgroups ? compose->newsgroups : "",
3440 COMPOSE_NEWSGROUPS, PREF_NONE);
3442 compose_reply_set_subject(compose, msginfo);
3444 if (to_ml && compose->ml_post) return;
3445 if (!to_all || compose->account->protocol == A_NNTP) return;
3447 if (compose->replyto) {
3448 Xstrdup_a(replyto, compose->replyto, return);
3449 extract_address(replyto);
3451 if (msginfo->from) {
3452 Xstrdup_a(from, msginfo->from, return);
3453 extract_address(from);
3456 if (replyto && from)
3457 cc_list = address_list_append_with_comments(cc_list, from);
3458 if (to_all && msginfo->folder &&
3459 msginfo->folder->prefs->enable_default_reply_to)
3460 cc_list = address_list_append_with_comments(cc_list,
3461 msginfo->folder->prefs->default_reply_to);
3462 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3463 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3465 ac_email = g_utf8_strdown(compose->account->address, -1);
3467 if (cc_list) {
3468 for (cur = cc_list; cur != NULL; cur = cur->next) {
3469 gchar *addr = g_utf8_strdown(cur->data, -1);
3470 extract_address(addr);
3472 if (strcmp(ac_email, addr))
3473 compose_entry_append(compose, (gchar *)cur->data,
3474 COMPOSE_CC, PREF_NONE);
3475 else
3476 debug_print("Cc address same as compose account's, ignoring\n");
3478 g_free(addr);
3481 slist_free_strings_full(cc_list);
3484 g_free(ac_email);
3487 #define SET_ENTRY(entry, str) \
3489 if (str && *str) \
3490 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3493 #define SET_ADDRESS(type, str) \
3495 if (str && *str) \
3496 compose_entry_append(compose, str, type, PREF_NONE); \
3499 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3501 cm_return_if_fail(msginfo != NULL);
3503 SET_ENTRY(subject_entry, msginfo->subject);
3504 SET_ENTRY(from_name, msginfo->from);
3505 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3506 SET_ADDRESS(COMPOSE_CC, compose->cc);
3507 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3508 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3509 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3510 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3512 compose_update_priority_menu_item(compose);
3513 compose_update_privacy_system_menu_item(compose, FALSE);
3514 compose_show_first_last_header(compose, TRUE);
3517 #undef SET_ENTRY
3518 #undef SET_ADDRESS
3520 static void compose_insert_sig(Compose *compose, gboolean replace)
3522 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3523 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3524 GtkTextMark *mark;
3525 GtkTextIter iter, iter_end;
3526 gint cur_pos, ins_pos;
3527 gboolean prev_autowrap;
3528 gboolean found = FALSE;
3529 gboolean exists = FALSE;
3531 cm_return_if_fail(compose->account != NULL);
3533 BLOCK_WRAP();
3535 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3536 G_CALLBACK(compose_changed_cb),
3537 compose);
3539 mark = gtk_text_buffer_get_insert(buffer);
3540 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3541 cur_pos = gtk_text_iter_get_offset (&iter);
3542 ins_pos = cur_pos;
3544 gtk_text_buffer_get_end_iter(buffer, &iter);
3546 exists = (compose->sig_str != NULL);
3548 if (replace) {
3549 GtkTextIter first_iter, start_iter, end_iter;
3551 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3553 if (!exists || compose->sig_str[0] == '\0')
3554 found = FALSE;
3555 else
3556 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3557 compose->signature_tag);
3559 if (found) {
3560 /* include previous \n\n */
3561 gtk_text_iter_backward_chars(&first_iter, 1);
3562 start_iter = first_iter;
3563 end_iter = first_iter;
3564 /* skip re-start */
3565 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3566 compose->signature_tag);
3567 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3568 compose->signature_tag);
3569 if (found) {
3570 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3571 iter = start_iter;
3576 g_free(compose->sig_str);
3577 compose->sig_str = account_get_signature_str(compose->account);
3579 cur_pos = gtk_text_iter_get_offset(&iter);
3581 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3582 g_free(compose->sig_str);
3583 compose->sig_str = NULL;
3584 } else {
3585 if (compose->sig_inserted == FALSE)
3586 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3587 compose->sig_inserted = TRUE;
3589 cur_pos = gtk_text_iter_get_offset(&iter);
3590 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3591 /* remove \n\n */
3592 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3593 gtk_text_iter_forward_chars(&iter, 1);
3594 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3595 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3597 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3598 cur_pos = gtk_text_buffer_get_char_count (buffer);
3601 /* put the cursor where it should be
3602 * either where the quote_fmt says, either where it was */
3603 if (compose->set_cursor_pos < 0)
3604 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3605 else
3606 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3607 compose->set_cursor_pos);
3609 compose->set_cursor_pos = -1;
3610 gtk_text_buffer_place_cursor(buffer, &iter);
3611 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3612 G_CALLBACK(compose_changed_cb),
3613 compose);
3615 UNBLOCK_WRAP();
3618 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3620 GtkTextView *text;
3621 GtkTextBuffer *buffer;
3622 GtkTextMark *mark;
3623 GtkTextIter iter;
3624 const gchar *cur_encoding;
3625 gchar buf[BUFFSIZE];
3626 gint len;
3627 FILE *fp;
3628 gboolean prev_autowrap;
3629 #ifdef G_OS_WIN32
3630 GFile *f;
3631 GFileInfo *fi;
3632 GError *error = NULL;
3633 #else
3634 GStatBuf file_stat;
3635 #endif
3636 int ret;
3637 goffset size;
3638 GString *file_contents = NULL;
3639 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3641 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3643 /* get the size of the file we are about to insert */
3644 #ifdef G_OS_WIN32
3645 f = g_file_new_for_path(file);
3646 fi = g_file_query_info(f, "standard::size",
3647 G_FILE_QUERY_INFO_NONE, NULL, &error);
3648 ret = 0;
3649 if (error != NULL) {
3650 g_warning(error->message);
3651 ret = 1;
3652 g_error_free(error);
3653 g_object_unref(f);
3655 #else
3656 ret = g_stat(file, &file_stat);
3657 #endif
3658 if (ret != 0) {
3659 gchar *shortfile = g_path_get_basename(file);
3660 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3661 g_free(shortfile);
3662 return COMPOSE_INSERT_NO_FILE;
3663 } else if (prefs_common.warn_large_insert == TRUE) {
3664 #ifdef G_OS_WIN32
3665 size = g_file_info_get_size(fi);
3666 g_object_unref(fi);
3667 g_object_unref(f);
3668 #else
3669 size = file_stat.st_size;
3670 #endif
3672 /* ask user for confirmation if the file is large */
3673 if (prefs_common.warn_large_insert_size < 0 ||
3674 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3675 AlertValue aval;
3676 gchar *msg;
3678 msg = g_strdup_printf(_("You are about to insert a file of %s "
3679 "in the message body. Are you sure you want to do that?"),
3680 to_human_readable(size));
3681 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3682 _("_Insert"), NULL, ALERTFOCUS_SECOND, TRUE,
3683 NULL, ALERT_QUESTION);
3684 g_free(msg);
3686 /* do we ask for confirmation next time? */
3687 if (aval & G_ALERTDISABLE) {
3688 /* no confirmation next time, disable feature in preferences */
3689 aval &= ~G_ALERTDISABLE;
3690 prefs_common.warn_large_insert = FALSE;
3693 /* abort file insertion if user canceled action */
3694 if (aval != G_ALERTALTERNATE) {
3695 return COMPOSE_INSERT_NO_FILE;
3701 if ((fp = claws_fopen(file, "rb")) == NULL) {
3702 FILE_OP_ERROR(file, "claws_fopen");
3703 return COMPOSE_INSERT_READ_ERROR;
3706 prev_autowrap = compose->autowrap;
3707 compose->autowrap = FALSE;
3709 text = GTK_TEXT_VIEW(compose->text);
3710 buffer = gtk_text_view_get_buffer(text);
3711 mark = gtk_text_buffer_get_insert(buffer);
3712 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3714 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3715 G_CALLBACK(text_inserted),
3716 compose);
3718 cur_encoding = conv_get_locale_charset_str_no_utf8();
3720 file_contents = g_string_new("");
3721 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3722 gchar *str;
3724 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3725 str = g_strdup(buf);
3726 else {
3727 codeconv_set_strict(TRUE);
3728 str = conv_codeset_strdup
3729 (buf, cur_encoding, CS_INTERNAL);
3730 codeconv_set_strict(FALSE);
3732 if (!str) {
3733 result = COMPOSE_INSERT_INVALID_CHARACTER;
3734 break;
3737 if (!str) continue;
3739 /* strip <CR> if DOS/Windows file,
3740 replace <CR> with <LF> if Macintosh file. */
3741 strcrchomp(str);
3742 len = strlen(str);
3743 if (len > 0 && str[len - 1] != '\n') {
3744 while (--len >= 0)
3745 if (str[len] == '\r') str[len] = '\n';
3748 file_contents = g_string_append(file_contents, str);
3749 g_free(str);
3752 if (result == COMPOSE_INSERT_SUCCESS) {
3753 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3755 compose_changed_cb(NULL, compose);
3756 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3757 G_CALLBACK(text_inserted),
3758 compose);
3759 compose->autowrap = prev_autowrap;
3760 if (compose->autowrap)
3761 compose_wrap_all(compose);
3764 g_string_free(file_contents, TRUE);
3765 claws_fclose(fp);
3767 return result;
3770 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3771 const gchar *filename,
3772 const gchar *content_type,
3773 const gchar *charset)
3775 AttachInfo *ainfo;
3776 GtkTreeIter iter;
3777 FILE *fp;
3778 off_t size;
3779 GAuto *auto_ainfo;
3780 gchar *size_text;
3781 GtkListStore *store;
3782 gchar *name;
3783 gboolean has_binary = FALSE;
3785 if (!is_file_exist(file)) {
3786 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3787 gboolean result = FALSE;
3788 if (file_from_uri && is_file_exist(file_from_uri)) {
3789 result = compose_attach_append(
3790 compose, file_from_uri,
3791 filename, content_type,
3792 charset);
3794 g_free(file_from_uri);
3795 if (result)
3796 return TRUE;
3797 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3798 return FALSE;
3800 if ((size = get_file_size(file)) < 0) {
3801 alertpanel_error("Can't get file size of %s\n", filename);
3802 return FALSE;
3805 /* In batch mode, we allow 0-length files to be attached no questions asked */
3806 if (size == 0 && !compose->batch) {
3807 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3808 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3809 GTK_STOCK_CANCEL, _("_Attach anyway"), NULL,
3810 ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3811 g_free(msg);
3813 if (aval != G_ALERTALTERNATE) {
3814 return FALSE;
3817 if ((fp = claws_fopen(file, "rb")) == NULL) {
3818 alertpanel_error(_("Can't read %s."), filename);
3819 return FALSE;
3821 claws_fclose(fp);
3823 ainfo = g_new0(AttachInfo, 1);
3824 auto_ainfo = g_auto_pointer_new_with_free
3825 (ainfo, (GFreeFunc) compose_attach_info_free);
3826 ainfo->file = g_strdup(file);
3828 if (content_type) {
3829 ainfo->content_type = g_strdup(content_type);
3830 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3831 MsgInfo *msginfo;
3832 MsgFlags flags = {0, 0};
3834 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3835 ainfo->encoding = ENC_7BIT;
3836 else
3837 ainfo->encoding = ENC_8BIT;
3839 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3840 if (msginfo && msginfo->subject)
3841 name = g_strdup(msginfo->subject);
3842 else
3843 name = g_path_get_basename(filename ? filename : file);
3845 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3847 procmsg_msginfo_free(&msginfo);
3848 } else {
3849 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3850 ainfo->charset = g_strdup(charset);
3851 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3852 } else {
3853 ainfo->encoding = ENC_BASE64;
3855 name = g_path_get_basename(filename ? filename : file);
3856 ainfo->name = g_strdup(name);
3858 g_free(name);
3859 } else {
3860 ainfo->content_type = procmime_get_mime_type(file);
3861 if (!ainfo->content_type) {
3862 ainfo->content_type =
3863 g_strdup("application/octet-stream");
3864 ainfo->encoding = ENC_BASE64;
3865 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3866 ainfo->encoding =
3867 procmime_get_encoding_for_text_file(file, &has_binary);
3868 else
3869 ainfo->encoding = ENC_BASE64;
3870 name = g_path_get_basename(filename ? filename : file);
3871 ainfo->name = g_strdup(name);
3872 g_free(name);
3875 if (ainfo->name != NULL
3876 && !strcmp(ainfo->name, ".")) {
3877 g_free(ainfo->name);
3878 ainfo->name = NULL;
3881 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3882 g_free(ainfo->content_type);
3883 ainfo->content_type = g_strdup("application/octet-stream");
3884 g_free(ainfo->charset);
3885 ainfo->charset = NULL;
3888 ainfo->size = (goffset)size;
3889 size_text = to_human_readable((goffset)size);
3891 store = GTK_LIST_STORE(gtk_tree_view_get_model
3892 (GTK_TREE_VIEW(compose->attach_clist)));
3894 gtk_list_store_append(store, &iter);
3895 gtk_list_store_set(store, &iter,
3896 COL_MIMETYPE, ainfo->content_type,
3897 COL_SIZE, size_text,
3898 COL_NAME, ainfo->name,
3899 COL_CHARSET, ainfo->charset,
3900 COL_DATA, ainfo,
3901 COL_AUTODATA, auto_ainfo,
3902 -1);
3904 g_auto_pointer_free(auto_ainfo);
3905 compose_attach_update_label(compose);
3906 return TRUE;
3909 void compose_use_signing(Compose *compose, gboolean use_signing)
3911 compose->use_signing = use_signing;
3912 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3915 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3917 compose->use_encryption = use_encryption;
3918 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3921 #define NEXT_PART_NOT_CHILD(info) \
3923 node = info->node; \
3924 while (node->children) \
3925 node = g_node_last_child(node); \
3926 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3929 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3931 MimeInfo *mimeinfo;
3932 MimeInfo *child;
3933 MimeInfo *firsttext = NULL;
3934 MimeInfo *encrypted = NULL;
3935 GNode *node;
3936 gchar *outfile;
3937 const gchar *partname = NULL;
3939 mimeinfo = procmime_scan_message(msginfo);
3940 if (!mimeinfo) return;
3942 if (mimeinfo->node->children == NULL) {
3943 procmime_mimeinfo_free_all(&mimeinfo);
3944 return;
3947 /* find first content part */
3948 child = (MimeInfo *) mimeinfo->node->children->data;
3949 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3950 child = (MimeInfo *)child->node->children->data;
3952 if (child) {
3953 if (child->type == MIMETYPE_TEXT) {
3954 firsttext = child;
3955 debug_print("First text part found\n");
3956 } else if (compose->mode == COMPOSE_REEDIT &&
3957 child->type == MIMETYPE_APPLICATION &&
3958 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3959 encrypted = (MimeInfo *)child->node->parent->data;
3962 child = (MimeInfo *) mimeinfo->node->children->data;
3963 while (child != NULL) {
3964 gint err;
3966 if (child == encrypted) {
3967 /* skip this part of tree */
3968 NEXT_PART_NOT_CHILD(child);
3969 continue;
3972 if (child->type == MIMETYPE_MULTIPART) {
3973 /* get the actual content */
3974 child = procmime_mimeinfo_next(child);
3975 continue;
3978 if (child == firsttext) {
3979 child = procmime_mimeinfo_next(child);
3980 continue;
3983 outfile = procmime_get_tmp_file_name(child);
3984 if ((err = procmime_get_part(outfile, child)) < 0)
3985 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3986 else {
3987 gchar *content_type;
3989 content_type = procmime_get_content_type_str(child->type, child->subtype);
3991 /* if we meet a pgp signature, we don't attach it, but
3992 * we force signing. */
3993 if ((strcmp(content_type, "application/pgp-signature") &&
3994 strcmp(content_type, "application/pkcs7-signature") &&
3995 strcmp(content_type, "application/x-pkcs7-signature"))
3996 || compose->mode == COMPOSE_REDIRECT) {
3997 partname = procmime_mimeinfo_get_parameter(child, "filename");
3998 if (partname == NULL)
3999 partname = procmime_mimeinfo_get_parameter(child, "name");
4000 if (partname == NULL)
4001 partname = "";
4002 compose_attach_append(compose, outfile,
4003 partname, content_type,
4004 procmime_mimeinfo_get_parameter(child, "charset"));
4005 } else {
4006 compose_force_signing(compose, compose->account, NULL);
4008 g_free(content_type);
4010 g_free(outfile);
4011 NEXT_PART_NOT_CHILD(child);
4013 procmime_mimeinfo_free_all(&mimeinfo);
4016 #undef NEXT_PART_NOT_CHILD
4020 typedef enum {
4021 WAIT_FOR_INDENT_CHAR,
4022 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4023 } IndentState;
4025 /* return indent length, we allow:
4026 indent characters followed by indent characters or spaces/tabs,
4027 alphabets and numbers immediately followed by indent characters,
4028 and the repeating sequences of the above
4029 If quote ends with multiple spaces, only the first one is included. */
4030 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4031 const GtkTextIter *start, gint *len)
4033 GtkTextIter iter = *start;
4034 gunichar wc;
4035 gchar ch[6];
4036 gint clen;
4037 IndentState state = WAIT_FOR_INDENT_CHAR;
4038 gboolean is_space;
4039 gboolean is_indent;
4040 gint alnum_count = 0;
4041 gint space_count = 0;
4042 gint quote_len = 0;
4044 if (prefs_common.quote_chars == NULL) {
4045 return 0 ;
4048 while (!gtk_text_iter_ends_line(&iter)) {
4049 wc = gtk_text_iter_get_char(&iter);
4050 if (g_unichar_iswide(wc))
4051 break;
4052 clen = g_unichar_to_utf8(wc, ch);
4053 if (clen != 1)
4054 break;
4056 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4057 is_space = g_unichar_isspace(wc);
4059 if (state == WAIT_FOR_INDENT_CHAR) {
4060 if (!is_indent && !g_unichar_isalnum(wc))
4061 break;
4062 if (is_indent) {
4063 quote_len += alnum_count + space_count + 1;
4064 alnum_count = space_count = 0;
4065 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4066 } else
4067 alnum_count++;
4068 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4069 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4070 break;
4071 if (is_space)
4072 space_count++;
4073 else if (is_indent) {
4074 quote_len += alnum_count + space_count + 1;
4075 alnum_count = space_count = 0;
4076 } else {
4077 alnum_count++;
4078 state = WAIT_FOR_INDENT_CHAR;
4082 gtk_text_iter_forward_char(&iter);
4085 if (quote_len > 0 && space_count > 0)
4086 quote_len++;
4088 if (len)
4089 *len = quote_len;
4091 if (quote_len > 0) {
4092 iter = *start;
4093 gtk_text_iter_forward_chars(&iter, quote_len);
4094 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4097 return NULL;
4100 /* return >0 if the line is itemized */
4101 static int compose_itemized_length(GtkTextBuffer *buffer,
4102 const GtkTextIter *start)
4104 GtkTextIter iter = *start;
4105 gunichar wc;
4106 gchar ch[6];
4107 gint clen;
4108 gint len = 0;
4109 if (gtk_text_iter_ends_line(&iter))
4110 return 0;
4112 while (1) {
4113 len++;
4114 wc = gtk_text_iter_get_char(&iter);
4115 if (!g_unichar_isspace(wc))
4116 break;
4117 gtk_text_iter_forward_char(&iter);
4118 if (gtk_text_iter_ends_line(&iter))
4119 return 0;
4122 clen = g_unichar_to_utf8(wc, ch);
4123 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4124 (clen == 3 && (
4125 wc == 0x2022 || /* BULLET */
4126 wc == 0x2023 || /* TRIANGULAR BULLET */
4127 wc == 0x2043 || /* HYPHEN BULLET */
4128 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4129 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4130 wc == 0x2219 || /* BULLET OPERATOR */
4131 wc == 0x25d8 || /* INVERSE BULLET */
4132 wc == 0x25e6 || /* WHITE BULLET */
4133 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4134 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4135 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4136 wc == 0x29be || /* CIRCLED WHITE BULLET */
4137 wc == 0x29bf /* CIRCLED BULLET */
4138 ))))
4139 return 0;
4141 gtk_text_iter_forward_char(&iter);
4142 if (gtk_text_iter_ends_line(&iter))
4143 return 0;
4144 wc = gtk_text_iter_get_char(&iter);
4145 if (g_unichar_isspace(wc)) {
4146 return len+1;
4148 return 0;
4151 /* return the string at the start of the itemization */
4152 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4153 const GtkTextIter *start)
4155 GtkTextIter iter = *start;
4156 gunichar wc;
4157 gint len = 0;
4158 GString *item_chars = g_string_new("");
4159 gchar *str = NULL;
4161 if (gtk_text_iter_ends_line(&iter))
4162 return NULL;
4164 while (1) {
4165 len++;
4166 wc = gtk_text_iter_get_char(&iter);
4167 if (!g_unichar_isspace(wc))
4168 break;
4169 gtk_text_iter_forward_char(&iter);
4170 if (gtk_text_iter_ends_line(&iter))
4171 break;
4172 g_string_append_unichar(item_chars, wc);
4175 str = item_chars->str;
4176 g_string_free(item_chars, FALSE);
4177 return str;
4180 /* return the number of spaces at a line's start */
4181 static int compose_left_offset_length(GtkTextBuffer *buffer,
4182 const GtkTextIter *start)
4184 GtkTextIter iter = *start;
4185 gunichar wc;
4186 gint len = 0;
4187 if (gtk_text_iter_ends_line(&iter))
4188 return 0;
4190 while (1) {
4191 wc = gtk_text_iter_get_char(&iter);
4192 if (!g_unichar_isspace(wc))
4193 break;
4194 len++;
4195 gtk_text_iter_forward_char(&iter);
4196 if (gtk_text_iter_ends_line(&iter))
4197 return 0;
4200 gtk_text_iter_forward_char(&iter);
4201 if (gtk_text_iter_ends_line(&iter))
4202 return 0;
4203 return len;
4206 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4207 const GtkTextIter *start,
4208 GtkTextIter *break_pos,
4209 gint max_col,
4210 gint quote_len)
4212 GtkTextIter iter = *start, line_end = *start;
4213 PangoLogAttr *attrs;
4214 gchar *str;
4215 gchar *p;
4216 gint len;
4217 gint i;
4218 gint col = 0;
4219 gint pos = 0;
4220 gboolean can_break = FALSE;
4221 gboolean do_break = FALSE;
4222 gboolean was_white = FALSE;
4223 gboolean prev_dont_break = FALSE;
4225 gtk_text_iter_forward_to_line_end(&line_end);
4226 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4227 len = g_utf8_strlen(str, -1);
4229 if (len == 0) {
4230 g_free(str);
4231 g_warning("compose_get_line_break_pos: len = 0!");
4232 return FALSE;
4235 /* g_print("breaking line: %d: %s (len = %d)\n",
4236 gtk_text_iter_get_line(&iter), str, len); */
4238 attrs = g_new(PangoLogAttr, len + 1);
4240 pango_default_break(str, -1, NULL, attrs, len + 1);
4242 p = str;
4244 /* skip quote and leading spaces */
4245 for (i = 0; *p != '\0' && i < len; i++) {
4246 gunichar wc;
4248 wc = g_utf8_get_char(p);
4249 if (i >= quote_len && !g_unichar_isspace(wc))
4250 break;
4251 if (g_unichar_iswide(wc))
4252 col += 2;
4253 else if (*p == '\t')
4254 col += 8;
4255 else
4256 col++;
4257 p = g_utf8_next_char(p);
4260 for (; *p != '\0' && i < len; i++) {
4261 PangoLogAttr *attr = attrs + i;
4262 gunichar wc = g_utf8_get_char(p);
4263 gint uri_len;
4265 /* attr->is_line_break will be false for some characters that
4266 * we want to break a line before, like '/' or ':', so we
4267 * also allow breaking on any non-wide character. The
4268 * mentioned pango attribute is still useful to decide on
4269 * line breaks when wide characters are involved. */
4270 if ((!g_unichar_iswide(wc) || attr->is_line_break)
4271 && can_break && was_white && !prev_dont_break)
4272 pos = i;
4274 was_white = attr->is_white;
4276 /* don't wrap URI */
4277 if ((uri_len = get_uri_len(p)) > 0) {
4278 col += uri_len;
4279 if (pos > 0 && col > max_col) {
4280 do_break = TRUE;
4281 break;
4283 i += uri_len - 1;
4284 p += uri_len;
4285 can_break = TRUE;
4286 continue;
4289 if (g_unichar_iswide(wc)) {
4290 col += 2;
4291 if (prev_dont_break && can_break && attr->is_line_break)
4292 pos = i;
4293 } else if (*p == '\t')
4294 col += 8;
4295 else
4296 col++;
4297 if (pos > 0 && col > max_col) {
4298 do_break = TRUE;
4299 break;
4302 if (*p == '-' || *p == '/')
4303 prev_dont_break = TRUE;
4304 else
4305 prev_dont_break = FALSE;
4307 p = g_utf8_next_char(p);
4308 can_break = TRUE;
4311 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4313 g_free(attrs);
4314 g_free(str);
4316 *break_pos = *start;
4317 gtk_text_iter_set_line_offset(break_pos, pos);
4319 return do_break;
4322 static gboolean compose_join_next_line(Compose *compose,
4323 GtkTextBuffer *buffer,
4324 GtkTextIter *iter,
4325 const gchar *quote_str)
4327 GtkTextIter iter_ = *iter, cur, prev, next, end;
4328 PangoLogAttr attrs[3];
4329 gchar *str;
4330 gchar *next_quote_str;
4331 gunichar wc1, wc2;
4332 gint quote_len;
4333 gboolean keep_cursor = FALSE;
4335 if (!gtk_text_iter_forward_line(&iter_) ||
4336 gtk_text_iter_ends_line(&iter_)) {
4337 return FALSE;
4339 next_quote_str = compose_get_quote_str(buffer, &iter_, &quote_len);
4341 if ((quote_str || next_quote_str) &&
4342 g_strcmp0(quote_str, next_quote_str) != 0) {
4343 g_free(next_quote_str);
4344 return FALSE;
4346 g_free(next_quote_str);
4348 end = iter_;
4349 if (quote_len > 0) {
4350 gtk_text_iter_forward_chars(&end, quote_len);
4351 if (gtk_text_iter_ends_line(&end)) {
4352 return FALSE;
4356 /* don't join itemized lines */
4357 if (compose_itemized_length(buffer, &end) > 0) {
4358 return FALSE;
4361 /* don't join signature separator */
4362 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4363 return FALSE;
4365 /* delete quote str */
4366 if (quote_len > 0)
4367 gtk_text_buffer_delete(buffer, &iter_, &end);
4369 /* don't join line breaks put by the user */
4370 prev = cur = iter_;
4371 gtk_text_iter_backward_char(&cur);
4372 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4373 gtk_text_iter_forward_char(&cur);
4374 *iter = cur;
4375 return FALSE;
4377 gtk_text_iter_forward_char(&cur);
4378 /* delete linebreak and extra spaces */
4379 while (gtk_text_iter_backward_char(&cur)) {
4380 wc1 = gtk_text_iter_get_char(&cur);
4381 if (!g_unichar_isspace(wc1))
4382 break;
4383 prev = cur;
4385 next = cur = iter_;
4386 while (!gtk_text_iter_ends_line(&cur)) {
4387 wc1 = gtk_text_iter_get_char(&cur);
4388 if (!g_unichar_isspace(wc1))
4389 break;
4390 gtk_text_iter_forward_char(&cur);
4391 next = cur;
4393 if (!gtk_text_iter_equal(&prev, &next)) {
4394 GtkTextMark *mark;
4396 mark = gtk_text_buffer_get_insert(buffer);
4397 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4398 if (gtk_text_iter_equal(&prev, &cur))
4399 keep_cursor = TRUE;
4400 gtk_text_buffer_delete(buffer, &prev, &next);
4402 iter_ = prev;
4404 /* insert space if required */
4405 gtk_text_iter_backward_char(&prev);
4406 wc1 = gtk_text_iter_get_char(&prev);
4407 wc2 = gtk_text_iter_get_char(&next);
4408 gtk_text_iter_forward_char(&next);
4409 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4410 pango_default_break(str, -1, NULL, attrs, 3);
4411 if (!attrs[1].is_line_break ||
4412 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4413 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4414 if (keep_cursor) {
4415 gtk_text_iter_backward_char(&iter_);
4416 gtk_text_buffer_place_cursor(buffer, &iter_);
4419 g_free(str);
4421 *iter = iter_;
4422 return TRUE;
4425 #define ADD_TXT_POS(bp_, ep_, pti_) \
4426 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4427 last = last->next; \
4428 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4429 last->next = NULL; \
4430 } else { \
4431 g_warning("alloc error scanning URIs"); \
4434 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4436 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4437 GtkTextBuffer *buffer;
4438 GtkTextIter iter, break_pos, end_of_line;
4439 gchar *quote_str = NULL;
4440 gint quote_len;
4441 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4442 gboolean prev_autowrap = compose->autowrap;
4443 gint startq_offset = -1, noq_offset = -1;
4444 gint uri_start = -1, uri_stop = -1;
4445 gint nouri_start = -1, nouri_stop = -1;
4446 gint num_blocks = 0;
4447 gint quotelevel = -1;
4448 gboolean modified = force;
4449 gboolean removed = FALSE;
4450 gboolean modified_before_remove = FALSE;
4451 gint lines = 0;
4452 gboolean start = TRUE;
4453 gint itemized_len = 0, rem_item_len = 0;
4454 gchar *itemized_chars = NULL;
4455 gboolean item_continuation = FALSE;
4457 if (force) {
4458 modified = TRUE;
4460 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4461 modified = TRUE;
4464 compose->autowrap = FALSE;
4466 buffer = gtk_text_view_get_buffer(text);
4467 undo_wrapping(compose->undostruct, TRUE);
4468 if (par_iter) {
4469 iter = *par_iter;
4470 } else {
4471 GtkTextMark *mark;
4472 mark = gtk_text_buffer_get_insert(buffer);
4473 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4477 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4478 if (gtk_text_iter_ends_line(&iter)) {
4479 while (gtk_text_iter_ends_line(&iter) &&
4480 gtk_text_iter_forward_line(&iter))
4482 } else {
4483 while (gtk_text_iter_backward_line(&iter)) {
4484 if (gtk_text_iter_ends_line(&iter)) {
4485 gtk_text_iter_forward_line(&iter);
4486 break;
4490 } else {
4491 /* move to line start */
4492 gtk_text_iter_set_line_offset(&iter, 0);
4495 itemized_len = compose_itemized_length(buffer, &iter);
4497 if (!itemized_len) {
4498 itemized_len = compose_left_offset_length(buffer, &iter);
4499 item_continuation = TRUE;
4502 if (itemized_len)
4503 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4505 /* go until paragraph end (empty line) */
4506 while (start || !gtk_text_iter_ends_line(&iter)) {
4507 gchar *scanpos = NULL;
4508 /* parse table - in order of priority */
4509 struct table {
4510 const gchar *needle; /* token */
4512 /* token search function */
4513 gchar *(*search) (const gchar *haystack,
4514 const gchar *needle);
4515 /* part parsing function */
4516 gboolean (*parse) (const gchar *start,
4517 const gchar *scanpos,
4518 const gchar **bp_,
4519 const gchar **ep_,
4520 gboolean hdr);
4521 /* part to URI function */
4522 gchar *(*build_uri) (const gchar *bp,
4523 const gchar *ep);
4526 static struct table parser[] = {
4527 {"http://", strcasestr, get_uri_part, make_uri_string},
4528 {"https://", strcasestr, get_uri_part, make_uri_string},
4529 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4530 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4531 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4532 {"www.", strcasestr, get_uri_part, make_http_string},
4533 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4534 {"@", strcasestr, get_email_part, make_email_string}
4536 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4537 gint last_index = PARSE_ELEMS;
4538 gint n;
4539 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4540 gint walk_pos;
4542 start = FALSE;
4543 if (!prev_autowrap && num_blocks == 0) {
4544 num_blocks++;
4545 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4546 G_CALLBACK(text_inserted),
4547 compose);
4549 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4550 goto colorize;
4552 uri_start = uri_stop = -1;
4553 quote_len = 0;
4554 quote_str = compose_get_quote_str(buffer, &iter, &quote_len);
4556 if (quote_str) {
4557 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4558 if (startq_offset == -1)
4559 startq_offset = gtk_text_iter_get_offset(&iter);
4560 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4561 if (quotelevel > 2) {
4562 /* recycle colors */
4563 if (prefs_common.recycle_quote_colors)
4564 quotelevel %= 3;
4565 else
4566 quotelevel = 2;
4568 if (!wrap_quote) {
4569 goto colorize;
4571 } else {
4572 if (startq_offset == -1)
4573 noq_offset = gtk_text_iter_get_offset(&iter);
4574 quotelevel = -1;
4577 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4578 goto colorize;
4580 if (gtk_text_iter_ends_line(&iter)) {
4581 goto colorize;
4582 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4583 prefs_common.linewrap_len,
4584 quote_len)) {
4585 GtkTextIter prev, next, cur;
4586 if (prev_autowrap != FALSE || force) {
4587 compose->automatic_break = TRUE;
4588 modified = TRUE;
4589 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4590 compose->automatic_break = FALSE;
4591 if (itemized_len && compose->autoindent) {
4592 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4593 if (!item_continuation)
4594 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4596 } else if (quote_str && wrap_quote) {
4597 compose->automatic_break = TRUE;
4598 modified = TRUE;
4599 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4600 compose->automatic_break = FALSE;
4601 if (itemized_len && compose->autoindent) {
4602 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4603 if (!item_continuation)
4604 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4606 } else
4607 goto colorize;
4608 /* remove trailing spaces */
4609 cur = break_pos;
4610 rem_item_len = itemized_len;
4611 while (compose->autoindent && rem_item_len-- > 0)
4612 gtk_text_iter_backward_char(&cur);
4613 gtk_text_iter_backward_char(&cur);
4615 prev = next = cur;
4616 while (!gtk_text_iter_starts_line(&cur)) {
4617 gunichar wc;
4619 gtk_text_iter_backward_char(&cur);
4620 wc = gtk_text_iter_get_char(&cur);
4621 if (!g_unichar_isspace(wc))
4622 break;
4623 prev = cur;
4625 if (!gtk_text_iter_equal(&prev, &next)) {
4626 gtk_text_buffer_delete(buffer, &prev, &next);
4627 break_pos = next;
4628 gtk_text_iter_forward_char(&break_pos);
4631 if (quote_str)
4632 gtk_text_buffer_insert(buffer, &break_pos,
4633 quote_str, -1);
4635 iter = break_pos;
4636 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4638 /* move iter to current line start */
4639 gtk_text_iter_set_line_offset(&iter, 0);
4640 if (quote_str) {
4641 g_free(quote_str);
4642 quote_str = NULL;
4644 continue;
4645 } else {
4646 /* move iter to next line start */
4647 iter = break_pos;
4648 lines++;
4651 colorize:
4652 if (!prev_autowrap && num_blocks > 0) {
4653 num_blocks--;
4654 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4655 G_CALLBACK(text_inserted),
4656 compose);
4658 end_of_line = iter;
4659 while (!gtk_text_iter_ends_line(&end_of_line)) {
4660 gtk_text_iter_forward_char(&end_of_line);
4662 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4664 nouri_start = gtk_text_iter_get_offset(&iter);
4665 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4667 walk_pos = gtk_text_iter_get_offset(&iter);
4668 /* FIXME: this looks phony. scanning for anything in the parse table */
4669 for (n = 0; n < PARSE_ELEMS; n++) {
4670 gchar *tmp;
4672 tmp = parser[n].search(walk, parser[n].needle);
4673 if (tmp) {
4674 if (scanpos == NULL || tmp < scanpos) {
4675 scanpos = tmp;
4676 last_index = n;
4681 bp = ep = 0;
4682 if (scanpos) {
4683 /* check if URI can be parsed */
4684 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4685 (const gchar **)&ep, FALSE)
4686 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4687 walk = ep;
4688 } else
4689 walk = scanpos +
4690 strlen(parser[last_index].needle);
4692 if (bp && ep) {
4693 uri_start = walk_pos + (bp - o_walk);
4694 uri_stop = walk_pos + (ep - o_walk);
4696 g_free(o_walk);
4697 o_walk = NULL;
4698 gtk_text_iter_forward_line(&iter);
4699 g_free(quote_str);
4700 quote_str = NULL;
4701 if (startq_offset != -1) {
4702 GtkTextIter startquote, endquote;
4703 gtk_text_buffer_get_iter_at_offset(
4704 buffer, &startquote, startq_offset);
4705 endquote = iter;
4707 switch (quotelevel) {
4708 case 0:
4709 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4710 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4711 gtk_text_buffer_apply_tag_by_name(
4712 buffer, "quote0", &startquote, &endquote);
4713 gtk_text_buffer_remove_tag_by_name(
4714 buffer, "quote1", &startquote, &endquote);
4715 gtk_text_buffer_remove_tag_by_name(
4716 buffer, "quote2", &startquote, &endquote);
4717 modified = TRUE;
4719 break;
4720 case 1:
4721 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4722 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4723 gtk_text_buffer_apply_tag_by_name(
4724 buffer, "quote1", &startquote, &endquote);
4725 gtk_text_buffer_remove_tag_by_name(
4726 buffer, "quote0", &startquote, &endquote);
4727 gtk_text_buffer_remove_tag_by_name(
4728 buffer, "quote2", &startquote, &endquote);
4729 modified = TRUE;
4731 break;
4732 case 2:
4733 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4734 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4735 gtk_text_buffer_apply_tag_by_name(
4736 buffer, "quote2", &startquote, &endquote);
4737 gtk_text_buffer_remove_tag_by_name(
4738 buffer, "quote0", &startquote, &endquote);
4739 gtk_text_buffer_remove_tag_by_name(
4740 buffer, "quote1", &startquote, &endquote);
4741 modified = TRUE;
4743 break;
4745 startq_offset = -1;
4746 } else if (noq_offset != -1) {
4747 GtkTextIter startnoquote, endnoquote;
4748 gtk_text_buffer_get_iter_at_offset(
4749 buffer, &startnoquote, noq_offset);
4750 endnoquote = iter;
4752 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4753 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4754 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4755 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4756 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4757 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4758 gtk_text_buffer_remove_tag_by_name(
4759 buffer, "quote0", &startnoquote, &endnoquote);
4760 gtk_text_buffer_remove_tag_by_name(
4761 buffer, "quote1", &startnoquote, &endnoquote);
4762 gtk_text_buffer_remove_tag_by_name(
4763 buffer, "quote2", &startnoquote, &endnoquote);
4764 modified = TRUE;
4766 noq_offset = -1;
4769 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4770 GtkTextIter nouri_start_iter, nouri_end_iter;
4771 gtk_text_buffer_get_iter_at_offset(
4772 buffer, &nouri_start_iter, nouri_start);
4773 gtk_text_buffer_get_iter_at_offset(
4774 buffer, &nouri_end_iter, nouri_stop);
4775 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4776 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4777 gtk_text_buffer_remove_tag_by_name(
4778 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4779 modified_before_remove = modified;
4780 modified = TRUE;
4781 removed = TRUE;
4784 if (uri_start >= 0 && uri_stop > 0) {
4785 GtkTextIter uri_start_iter, uri_end_iter, back;
4786 gtk_text_buffer_get_iter_at_offset(
4787 buffer, &uri_start_iter, uri_start);
4788 gtk_text_buffer_get_iter_at_offset(
4789 buffer, &uri_end_iter, uri_stop);
4790 back = uri_end_iter;
4791 gtk_text_iter_backward_char(&back);
4792 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4793 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4794 gtk_text_buffer_apply_tag_by_name(
4795 buffer, "link", &uri_start_iter, &uri_end_iter);
4796 modified = TRUE;
4797 if (removed && !modified_before_remove) {
4798 modified = FALSE;
4802 if (!modified) {
4803 /* debug_print("not modified, out after %d lines\n", lines); */
4804 goto end;
4807 /* debug_print("modified, out after %d lines\n", lines); */
4808 end:
4809 g_free(itemized_chars);
4810 if (par_iter)
4811 *par_iter = iter;
4812 undo_wrapping(compose->undostruct, FALSE);
4813 compose->autowrap = prev_autowrap;
4815 return modified;
4818 void compose_action_cb(void *data)
4820 Compose *compose = (Compose *)data;
4821 compose_wrap_all(compose);
4824 static void compose_wrap_all(Compose *compose)
4826 compose_wrap_all_full(compose, FALSE);
4829 static void compose_wrap_all_full(Compose *compose, gboolean force)
4831 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4832 GtkTextBuffer *buffer;
4833 GtkTextIter iter;
4834 gboolean modified = TRUE;
4836 buffer = gtk_text_view_get_buffer(text);
4838 gtk_text_buffer_get_start_iter(buffer, &iter);
4840 undo_wrapping(compose->undostruct, TRUE);
4842 while (!gtk_text_iter_is_end(&iter) && modified)
4843 modified = compose_beautify_paragraph(compose, &iter, force);
4845 undo_wrapping(compose->undostruct, FALSE);
4849 static void compose_set_title(Compose *compose)
4851 gchar *str;
4852 gchar *edited;
4853 gchar *subject;
4855 edited = compose->modified ? _(" [Edited]") : "";
4857 subject = gtk_editable_get_chars(
4858 GTK_EDITABLE(compose->subject_entry), 0, -1);
4860 #ifndef GENERIC_UMPC
4861 if (subject && strlen(subject))
4862 str = g_strdup_printf(_("%s - Compose message%s"),
4863 subject, edited);
4864 else
4865 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4866 #else
4867 str = g_strdup(_("Compose message"));
4868 #endif
4870 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4871 g_free(str);
4872 g_free(subject);
4876 * compose_current_mail_account:
4878 * Find a current mail account (the currently selected account, or the
4879 * default account, if a news account is currently selected). If a
4880 * mail account cannot be found, display an error message.
4882 * Return value: Mail account, or NULL if not found.
4884 static PrefsAccount *
4885 compose_current_mail_account(void)
4887 PrefsAccount *ac;
4889 if (cur_account && cur_account->protocol != A_NNTP)
4890 ac = cur_account;
4891 else {
4892 ac = account_get_default();
4893 if (!ac || ac->protocol == A_NNTP) {
4894 alertpanel_error(_("Account for sending mail is not specified.\n"
4895 "Please select a mail account before sending."));
4896 return NULL;
4899 return ac;
4902 #define QUOTE_IF_REQUIRED(out, str) \
4904 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4905 gchar *__tmp; \
4906 gint len; \
4908 len = strlen(str) + 3; \
4909 if ((__tmp = alloca(len)) == NULL) { \
4910 g_warning("can't allocate memory"); \
4911 g_string_free(header, TRUE); \
4912 return NULL; \
4914 g_snprintf(__tmp, len, "\"%s\"", str); \
4915 out = __tmp; \
4916 } else { \
4917 gchar *__tmp; \
4919 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4920 g_warning("can't allocate memory"); \
4921 g_string_free(header, TRUE); \
4922 return NULL; \
4923 } else \
4924 strcpy(__tmp, str); \
4926 out = __tmp; \
4930 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4932 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4933 gchar *__tmp; \
4934 gint len; \
4936 len = strlen(str) + 3; \
4937 if ((__tmp = alloca(len)) == NULL) { \
4938 g_warning("can't allocate memory"); \
4939 errret; \
4941 g_snprintf(__tmp, len, "\"%s\"", str); \
4942 out = __tmp; \
4943 } else { \
4944 gchar *__tmp; \
4946 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4947 g_warning("can't allocate memory"); \
4948 errret; \
4949 } else \
4950 strcpy(__tmp, str); \
4952 out = __tmp; \
4956 static void compose_select_account(Compose *compose, PrefsAccount *account,
4957 gboolean init)
4959 gchar *from = NULL, *header = NULL;
4960 ComposeHeaderEntry *header_entry;
4961 GtkTreeIter iter;
4963 cm_return_if_fail(account != NULL);
4965 compose->account = account;
4966 if (account->name && *account->name) {
4967 gchar *buf, *qbuf;
4968 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4969 qbuf = escape_internal_quotes(buf, '"');
4970 from = g_strdup_printf("%s <%s>",
4971 qbuf, account->address);
4972 if (qbuf != buf)
4973 g_free(qbuf);
4974 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4975 } else {
4976 from = g_strdup_printf("<%s>",
4977 account->address);
4978 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4981 g_free(from);
4983 compose_set_title(compose);
4985 compose_activate_privacy_system(compose, account, FALSE);
4987 if (account->default_sign && privacy_system_can_sign(compose->privacy_system) &&
4988 compose->mode != COMPOSE_REDIRECT)
4989 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4990 else
4991 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4992 if (account->default_encrypt && privacy_system_can_encrypt(compose->privacy_system) &&
4993 compose->mode != COMPOSE_REDIRECT)
4994 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4995 else
4996 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4998 if (!init && compose->mode != COMPOSE_REDIRECT) {
4999 undo_block(compose->undostruct);
5000 compose_insert_sig(compose, TRUE);
5001 undo_unblock(compose->undostruct);
5004 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
5005 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
5006 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
5007 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5009 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5010 if (account->protocol == A_NNTP) {
5011 if (!strcmp(header, _("To:")))
5012 combobox_select_by_text(
5013 GTK_COMBO_BOX(header_entry->combo),
5014 _("Newsgroups:"));
5015 } else {
5016 if (!strcmp(header, _("Newsgroups:")))
5017 combobox_select_by_text(
5018 GTK_COMBO_BOX(header_entry->combo),
5019 _("To:"));
5023 g_free(header);
5025 #ifdef USE_ENCHANT
5026 /* use account's dict info if set */
5027 if (compose->gtkaspell) {
5028 if (account->enable_default_dictionary)
5029 gtkaspell_change_dict(compose->gtkaspell,
5030 account->default_dictionary, FALSE);
5031 if (account->enable_default_alt_dictionary)
5032 gtkaspell_change_alt_dict(compose->gtkaspell,
5033 account->default_alt_dictionary);
5034 if (account->enable_default_dictionary
5035 || account->enable_default_alt_dictionary)
5036 compose_spell_menu_changed(compose);
5038 #endif
5041 gboolean compose_check_for_valid_recipient(Compose *compose) {
5042 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5043 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5044 gboolean recipient_found = FALSE;
5045 GSList *list;
5046 gchar **strptr;
5048 /* free to and newsgroup list */
5049 slist_free_strings_full(compose->to_list);
5050 compose->to_list = NULL;
5052 slist_free_strings_full(compose->newsgroup_list);
5053 compose->newsgroup_list = NULL;
5055 /* search header entries for to and newsgroup entries */
5056 for (list = compose->header_list; list; list = list->next) {
5057 gchar *header;
5058 gchar *entry;
5059 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5060 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5061 g_strstrip(entry);
5062 g_strstrip(header);
5063 if (entry[0] != '\0') {
5064 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5065 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5066 compose->to_list = address_list_append(compose->to_list, entry);
5067 recipient_found = TRUE;
5070 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5071 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5072 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5073 recipient_found = TRUE;
5077 g_free(header);
5078 g_free(entry);
5080 return recipient_found;
5083 static gboolean compose_check_for_set_recipients(Compose *compose)
5085 if (compose->account->set_autocc && compose->account->auto_cc) {
5086 gboolean found_other = FALSE;
5087 GSList *list;
5088 /* search header entries for to and newsgroup entries */
5089 for (list = compose->header_list; list; list = list->next) {
5090 gchar *entry;
5091 gchar *header;
5092 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5093 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5094 g_strstrip(entry);
5095 g_strstrip(header);
5096 if (strcmp(entry, compose->account->auto_cc)
5097 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5098 found_other = TRUE;
5099 g_free(entry);
5100 break;
5102 g_free(entry);
5103 g_free(header);
5105 if (!found_other) {
5106 AlertValue aval;
5107 gchar *text;
5108 if (compose->batch) {
5109 gtk_widget_show_all(compose->window);
5111 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5112 prefs_common_translated_header_name("Cc"));
5113 aval = alertpanel(_("Send"),
5114 text,
5115 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5116 g_free(text);
5117 if (aval != G_ALERTALTERNATE)
5118 return FALSE;
5121 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5122 gboolean found_other = FALSE;
5123 GSList *list;
5124 /* search header entries for to and newsgroup entries */
5125 for (list = compose->header_list; list; list = list->next) {
5126 gchar *entry;
5127 gchar *header;
5128 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5129 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5130 g_strstrip(entry);
5131 g_strstrip(header);
5132 if (strcmp(entry, compose->account->auto_bcc)
5133 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5134 found_other = TRUE;
5135 g_free(entry);
5136 g_free(header);
5137 break;
5139 g_free(entry);
5140 g_free(header);
5142 if (!found_other) {
5143 AlertValue aval;
5144 gchar *text;
5145 if (compose->batch) {
5146 gtk_widget_show_all(compose->window);
5148 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5149 prefs_common_translated_header_name("Bcc"));
5150 aval = alertpanel(_("Send"),
5151 text,
5152 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5153 g_free(text);
5154 if (aval != G_ALERTALTERNATE)
5155 return FALSE;
5158 return TRUE;
5161 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5163 const gchar *str;
5165 if (compose_check_for_valid_recipient(compose) == FALSE) {
5166 if (compose->batch) {
5167 gtk_widget_show_all(compose->window);
5169 alertpanel_error(_("Recipient is not specified."));
5170 return FALSE;
5173 if (compose_check_for_set_recipients(compose) == FALSE) {
5174 return FALSE;
5177 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5178 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5179 if (*str == '\0' && check_everything == TRUE &&
5180 compose->mode != COMPOSE_REDIRECT) {
5181 AlertValue aval;
5182 gchar *message;
5184 message = g_strdup_printf(_("Subject is empty. %s"),
5185 compose->sending?_("Send it anyway?"):
5186 _("Queue it anyway?"));
5188 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5189 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5190 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5191 g_free(message);
5192 if (aval & G_ALERTDISABLE) {
5193 aval &= ~G_ALERTDISABLE;
5194 prefs_common.warn_empty_subj = FALSE;
5196 if (aval != G_ALERTALTERNATE)
5197 return FALSE;
5201 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5202 && check_everything == TRUE) {
5203 GSList *list;
5204 gint cnt = 0;
5206 /* count To and Cc recipients */
5207 for (list = compose->header_list; list; list = list->next) {
5208 gchar *header;
5209 gchar *entry;
5211 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5212 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5213 g_strstrip(header);
5214 g_strstrip(entry);
5215 if ((entry[0] != '\0') &&
5216 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5217 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5218 cnt++;
5220 g_free(header);
5221 g_free(entry);
5223 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5224 AlertValue aval;
5225 gchar *message;
5227 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5228 compose->sending?_("Send it anyway?"):
5229 _("Queue it anyway?"));
5231 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5232 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5233 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5234 g_free(message);
5235 if (aval & G_ALERTDISABLE) {
5236 aval &= ~G_ALERTDISABLE;
5237 prefs_common.warn_sending_many_recipients_num = 0;
5239 if (aval != G_ALERTALTERNATE)
5240 return FALSE;
5244 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5245 return FALSE;
5247 return TRUE;
5250 static void _display_queue_error(ComposeQueueResult val)
5252 switch (val) {
5253 case COMPOSE_QUEUE_SUCCESS:
5254 break;
5255 case COMPOSE_QUEUE_ERROR_NO_MSG:
5256 alertpanel_error(_("Could not queue message."));
5257 break;
5258 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5259 alertpanel_error(_("Could not queue message:\n\n%s."),
5260 g_strerror(errno));
5261 break;
5262 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5263 alertpanel_error(_("Could not queue message for sending:\n\n"
5264 "Signature failed: %s"),
5265 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5266 break;
5267 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5268 alertpanel_error(_("Could not queue message for sending:\n\n"
5269 "Encryption failed: %s"),
5270 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5271 break;
5272 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5273 alertpanel_error(_("Could not queue message for sending:\n\n"
5274 "Charset conversion failed."));
5275 break;
5276 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5277 alertpanel_error(_("Could not queue message for sending:\n\n"
5278 "Couldn't get recipient encryption key."));
5279 break;
5280 case COMPOSE_QUEUE_SIGNING_CANCELLED:
5281 debug_print("signing cancelled\n");
5282 break;
5283 default:
5284 /* unhandled error */
5285 debug_print("oops, unhandled compose_queue() return value %d\n",
5286 val);
5287 break;
5291 gint compose_send(Compose *compose)
5293 gint msgnum;
5294 FolderItem *folder = NULL;
5295 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5296 gchar *msgpath = NULL;
5297 gboolean discard_window = FALSE;
5298 gchar *errstr = NULL;
5299 gchar *tmsgid = NULL;
5300 MainWindow *mainwin = mainwindow_get_mainwindow();
5301 gboolean queued_removed = FALSE;
5303 if (prefs_common.send_dialog_invisible
5304 || compose->batch == TRUE)
5305 discard_window = TRUE;
5307 compose_allow_user_actions (compose, FALSE);
5308 compose->sending = TRUE;
5310 if (compose_check_entries(compose, TRUE) == FALSE) {
5311 if (compose->batch) {
5312 gtk_widget_show_all(compose->window);
5314 goto bail;
5317 inc_lock();
5318 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5320 if (val != COMPOSE_QUEUE_SUCCESS) {
5321 if (compose->batch) {
5322 gtk_widget_show_all(compose->window);
5325 _display_queue_error(val);
5327 goto bail;
5330 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5331 if (discard_window) {
5332 compose->sending = FALSE;
5333 compose_close(compose);
5334 /* No more compose access in the normal codepath
5335 * after this point! */
5336 compose = NULL;
5339 if (msgnum == 0) {
5340 alertpanel_error(_("The message was queued but could not be "
5341 "sent.\nUse \"Send queued messages\" from "
5342 "the main window to retry."));
5343 if (!discard_window) {
5344 goto bail;
5346 inc_unlock();
5347 g_free(tmsgid);
5348 return -1;
5350 if (msgpath == NULL) {
5351 msgpath = folder_item_fetch_msg(folder, msgnum);
5352 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5353 g_free(msgpath);
5354 } else {
5355 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5356 claws_unlink(msgpath);
5357 g_free(msgpath);
5359 if (!discard_window) {
5360 if (val != 0) {
5361 if (!queued_removed)
5362 folder_item_remove_msg(folder, msgnum);
5363 folder_item_scan(folder);
5364 if (tmsgid) {
5365 /* make sure we delete that */
5366 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5367 if (tmp) {
5368 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5369 folder_item_remove_msg(folder, tmp->msgnum);
5370 procmsg_msginfo_free(&tmp);
5376 if (val == 0) {
5377 if (!queued_removed)
5378 folder_item_remove_msg(folder, msgnum);
5379 folder_item_scan(folder);
5380 if (tmsgid) {
5381 /* make sure we delete that */
5382 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5383 if (tmp) {
5384 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5385 folder_item_remove_msg(folder, tmp->msgnum);
5386 procmsg_msginfo_free(&tmp);
5389 if (!discard_window) {
5390 compose->sending = FALSE;
5391 compose_allow_user_actions (compose, TRUE);
5392 compose_close(compose);
5394 } else {
5395 if (errstr) {
5396 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5397 "the main window to retry."), errstr);
5398 g_free(errstr);
5399 } else {
5400 alertpanel_error_log(_("The message was queued but could not be "
5401 "sent.\nUse \"Send queued messages\" from "
5402 "the main window to retry."));
5404 if (!discard_window) {
5405 goto bail;
5407 inc_unlock();
5408 g_free(tmsgid);
5409 return -1;
5411 g_free(tmsgid);
5412 inc_unlock();
5413 toolbar_main_set_sensitive(mainwin);
5414 main_window_set_menu_sensitive(mainwin);
5415 return 0;
5417 bail:
5418 inc_unlock();
5419 g_free(tmsgid);
5420 compose_allow_user_actions (compose, TRUE);
5421 compose->sending = FALSE;
5422 compose->modified = TRUE;
5423 toolbar_main_set_sensitive(mainwin);
5424 main_window_set_menu_sensitive(mainwin);
5426 return -1;
5429 static gboolean compose_use_attach(Compose *compose)
5431 GtkTreeModel *model = gtk_tree_view_get_model
5432 (GTK_TREE_VIEW(compose->attach_clist));
5433 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5436 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5437 FILE *fp)
5439 gchar buf[BUFFSIZE];
5440 gchar *str;
5441 gboolean first_to_address;
5442 gboolean first_cc_address;
5443 GSList *list;
5444 ComposeHeaderEntry *headerentry;
5445 const gchar *headerentryname;
5446 const gchar *cc_hdr;
5447 const gchar *to_hdr;
5448 gboolean err = FALSE;
5450 debug_print("Writing redirect header\n");
5452 cc_hdr = prefs_common_translated_header_name("Cc:");
5453 to_hdr = prefs_common_translated_header_name("To:");
5455 first_to_address = TRUE;
5456 for (list = compose->header_list; list; list = list->next) {
5457 headerentry = ((ComposeHeaderEntry *)list->data);
5458 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5460 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5461 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5462 Xstrdup_a(str, entstr, return -1);
5463 g_strstrip(str);
5464 if (str[0] != '\0') {
5465 compose_convert_header
5466 (compose, buf, sizeof(buf), str,
5467 strlen("Resent-To") + 2, TRUE);
5469 if (first_to_address) {
5470 err |= (fprintf(fp, "Resent-To: ") < 0);
5471 first_to_address = FALSE;
5472 } else {
5473 err |= (fprintf(fp, ",") < 0);
5475 err |= (fprintf(fp, "%s", buf) < 0);
5479 if (!first_to_address) {
5480 err |= (fprintf(fp, "\n") < 0);
5483 first_cc_address = TRUE;
5484 for (list = compose->header_list; list; list = list->next) {
5485 headerentry = ((ComposeHeaderEntry *)list->data);
5486 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5488 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5489 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5490 Xstrdup_a(str, strg, return -1);
5491 g_strstrip(str);
5492 if (str[0] != '\0') {
5493 compose_convert_header
5494 (compose, buf, sizeof(buf), str,
5495 strlen("Resent-Cc") + 2, TRUE);
5497 if (first_cc_address) {
5498 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5499 first_cc_address = FALSE;
5500 } else {
5501 err |= (fprintf(fp, ",") < 0);
5503 err |= (fprintf(fp, "%s", buf) < 0);
5507 if (!first_cc_address) {
5508 err |= (fprintf(fp, "\n") < 0);
5511 return (err ? -1:0);
5514 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5516 gchar date[RFC822_DATE_BUFFSIZE];
5517 gchar buf[BUFFSIZE];
5518 gchar *str;
5519 const gchar *entstr;
5520 /* struct utsname utsbuf; */
5521 gboolean err = FALSE;
5523 cm_return_val_if_fail(fp != NULL, -1);
5524 cm_return_val_if_fail(compose->account != NULL, -1);
5525 cm_return_val_if_fail(compose->account->address != NULL, -1);
5527 /* Resent-Date */
5528 if (prefs_common.hide_timezone)
5529 get_rfc822_date_hide_tz(date, sizeof(date));
5530 else
5531 get_rfc822_date(date, sizeof(date));
5532 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5534 /* Resent-From */
5535 if (compose->account->name && *compose->account->name) {
5536 compose_convert_header
5537 (compose, buf, sizeof(buf), compose->account->name,
5538 strlen("From: "), TRUE);
5539 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5540 buf, compose->account->address) < 0);
5541 } else
5542 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5544 /* Subject */
5545 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5546 if (*entstr != '\0') {
5547 Xstrdup_a(str, entstr, return -1);
5548 g_strstrip(str);
5549 if (*str != '\0') {
5550 compose_convert_header(compose, buf, sizeof(buf), str,
5551 strlen("Subject: "), FALSE);
5552 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5556 /* Resent-Message-ID */
5557 if (compose->account->gen_msgid) {
5558 gchar *addr = prefs_account_generate_msgid(compose->account);
5559 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5560 if (compose->msgid)
5561 g_free(compose->msgid);
5562 compose->msgid = addr;
5563 } else {
5564 compose->msgid = NULL;
5567 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5568 return -1;
5570 /* separator between header and body */
5571 err |= (claws_fputs("\n", fp) == EOF);
5573 return (err ? -1:0);
5576 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5578 FILE *fp;
5579 size_t len;
5580 gchar *buf = NULL;
5581 gchar rewrite_buf[BUFFSIZE];
5582 int i = 0;
5583 gboolean skip = FALSE;
5584 gboolean err = FALSE;
5585 gchar *not_included[]={
5586 "Return-Path:", "Delivered-To:", "Received:",
5587 "Subject:", "X-UIDL:", "AF:",
5588 "NF:", "PS:", "SRH:",
5589 "SFN:", "DSR:", "MID:",
5590 "CFG:", "PT:", "S:",
5591 "RQ:", "SSV:", "NSV:",
5592 "SSH:", "R:", "MAID:",
5593 "NAID:", "RMID:", "FMID:",
5594 "SCF:", "RRCPT:", "NG:",
5595 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5596 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5597 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5598 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5599 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5600 NULL
5602 gint ret = 0;
5604 if ((fp = claws_fopen(compose->redirect_filename, "rb")) == NULL) {
5605 FILE_OP_ERROR(compose->redirect_filename, "claws_fopen");
5606 return -1;
5609 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5610 skip = FALSE;
5611 for (i = 0; not_included[i] != NULL; i++) {
5612 if (g_ascii_strncasecmp(buf, not_included[i],
5613 strlen(not_included[i])) == 0) {
5614 skip = TRUE;
5615 break;
5618 if (skip) {
5619 g_free(buf);
5620 buf = NULL;
5621 continue;
5623 if (claws_fputs(buf, fdest) == -1) {
5624 g_free(buf);
5625 buf = NULL;
5626 goto error;
5629 if (!prefs_common.redirect_keep_from) {
5630 if (g_ascii_strncasecmp(buf, "From:",
5631 strlen("From:")) == 0) {
5632 err |= (claws_fputs(" (by way of ", fdest) == EOF);
5633 if (compose->account->name
5634 && *compose->account->name) {
5635 gchar buffer[BUFFSIZE];
5637 compose_convert_header
5638 (compose, buffer, sizeof(buffer),
5639 compose->account->name,
5640 strlen("From: "),
5641 FALSE);
5642 err |= (fprintf(fdest, "%s <%s>",
5643 buffer,
5644 compose->account->address) < 0);
5645 } else
5646 err |= (fprintf(fdest, "%s",
5647 compose->account->address) < 0);
5648 err |= (claws_fputs(")", fdest) == EOF);
5652 g_free(buf);
5653 buf = NULL;
5654 if (claws_fputs("\n", fdest) == -1)
5655 goto error;
5658 if (err)
5659 goto error;
5661 if (compose_redirect_write_headers(compose, fdest))
5662 goto error;
5664 while ((len = claws_fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5665 if (claws_fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5666 goto error;
5669 claws_fclose(fp);
5671 return 0;
5673 error:
5674 claws_fclose(fp);
5676 return -1;
5679 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5681 GtkTextBuffer *buffer;
5682 GtkTextIter start, end, tmp;
5683 gchar *chars, *tmp_enc_file, *content;
5684 gchar *buf, *msg;
5685 const gchar *out_codeset;
5686 EncodingType encoding = ENC_UNKNOWN;
5687 MimeInfo *mimemsg, *mimetext;
5688 gint line;
5689 const gchar *src_codeset = CS_INTERNAL;
5690 gchar *from_addr = NULL;
5691 gchar *from_name = NULL;
5692 FolderItem *outbox;
5694 if (action == COMPOSE_WRITE_FOR_SEND) {
5695 attach_parts = TRUE;
5697 /* We're sending the message, generate a Message-ID
5698 * if necessary. */
5699 if (compose->msgid == NULL &&
5700 compose->account->gen_msgid) {
5701 compose->msgid = prefs_account_generate_msgid(compose->account);
5705 /* create message MimeInfo */
5706 mimemsg = procmime_mimeinfo_new();
5707 mimemsg->type = MIMETYPE_MESSAGE;
5708 mimemsg->subtype = g_strdup("rfc822");
5709 mimemsg->content = MIMECONTENT_MEM;
5710 mimemsg->tmp = TRUE; /* must free content later */
5711 mimemsg->data.mem = compose_get_header(compose);
5713 /* Create text part MimeInfo */
5714 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5715 gtk_text_buffer_get_end_iter(buffer, &end);
5716 tmp = end;
5718 /* We make sure that there is a newline at the end. */
5719 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5720 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5721 if (*chars != '\n') {
5722 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5724 g_free(chars);
5727 /* get all composed text */
5728 gtk_text_buffer_get_start_iter(buffer, &start);
5729 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5731 out_codeset = conv_get_charset_str(compose->out_encoding);
5733 if (!out_codeset && is_ascii_str(chars)) {
5734 out_codeset = CS_US_ASCII;
5735 } else if (prefs_common.outgoing_fallback_to_ascii &&
5736 is_ascii_str(chars)) {
5737 out_codeset = CS_US_ASCII;
5738 encoding = ENC_7BIT;
5741 if (!out_codeset) {
5742 gchar *test_conv_global_out = NULL;
5743 gchar *test_conv_reply = NULL;
5745 /* automatic mode. be automatic. */
5746 codeconv_set_strict(TRUE);
5748 out_codeset = conv_get_outgoing_charset_str();
5749 if (out_codeset) {
5750 debug_print("trying to convert to %s\n", out_codeset);
5751 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5754 if (!test_conv_global_out && compose->orig_charset
5755 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5756 out_codeset = compose->orig_charset;
5757 debug_print("failure; trying to convert to %s\n", out_codeset);
5758 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5761 if (!test_conv_global_out && !test_conv_reply) {
5762 /* we're lost */
5763 out_codeset = CS_INTERNAL;
5764 debug_print("failure; finally using %s\n", out_codeset);
5766 g_free(test_conv_global_out);
5767 g_free(test_conv_reply);
5768 codeconv_set_strict(FALSE);
5771 if (encoding == ENC_UNKNOWN) {
5772 if (prefs_common.encoding_method == CTE_BASE64)
5773 encoding = ENC_BASE64;
5774 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5775 encoding = ENC_QUOTED_PRINTABLE;
5776 else if (prefs_common.encoding_method == CTE_8BIT)
5777 encoding = ENC_8BIT;
5778 else
5779 encoding = procmime_get_encoding_for_charset(out_codeset);
5782 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5783 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5785 if (action == COMPOSE_WRITE_FOR_SEND) {
5786 codeconv_set_strict(TRUE);
5787 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5788 codeconv_set_strict(FALSE);
5790 if (!buf) {
5791 AlertValue aval;
5793 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5794 "to the specified %s charset.\n"
5795 "Send it as %s?"), out_codeset, src_codeset);
5796 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5797 _("_Send"), NULL, ALERTFOCUS_SECOND, FALSE,
5798 NULL, ALERT_ERROR);
5799 g_free(msg);
5801 if (aval != G_ALERTALTERNATE) {
5802 g_free(chars);
5803 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5804 } else {
5805 buf = chars;
5806 out_codeset = src_codeset;
5807 chars = NULL;
5810 } else {
5811 buf = chars;
5812 out_codeset = src_codeset;
5813 chars = NULL;
5815 g_free(chars);
5817 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5818 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5819 strstr(buf, "\nFrom ") != NULL) {
5820 encoding = ENC_QUOTED_PRINTABLE;
5824 mimetext = procmime_mimeinfo_new();
5825 mimetext->content = MIMECONTENT_MEM;
5826 mimetext->tmp = TRUE; /* must free content later */
5827 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5828 * and free the data, which we need later. */
5829 mimetext->data.mem = g_strdup(buf);
5830 mimetext->type = MIMETYPE_TEXT;
5831 mimetext->subtype = g_strdup("plain");
5832 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5833 g_strdup(out_codeset));
5835 /* protect trailing spaces when signing message */
5836 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5837 privacy_system_can_sign(compose->privacy_system)) {
5838 encoding = ENC_QUOTED_PRINTABLE;
5841 #ifdef G_OS_WIN32
5842 debug_print("main text: %Id bytes encoded as %s in %d\n",
5843 #else
5844 debug_print("main text: %zd bytes encoded as %s in %d\n",
5845 #endif
5846 strlen(buf), out_codeset, encoding);
5848 /* check for line length limit */
5849 if (action == COMPOSE_WRITE_FOR_SEND &&
5850 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5851 check_line_length(buf, 1000, &line) < 0) {
5852 AlertValue aval;
5854 msg = g_strdup_printf
5855 (_("Line %d exceeds the line length limit (998 bytes).\n"
5856 "The contents of the message might be broken on the way to the delivery.\n"
5857 "\n"
5858 "Send it anyway?"), line + 1);
5859 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL,
5860 ALERTFOCUS_FIRST);
5861 g_free(msg);
5862 if (aval != G_ALERTALTERNATE) {
5863 g_free(buf);
5864 return COMPOSE_QUEUE_ERROR_NO_MSG;
5868 if (encoding != ENC_UNKNOWN)
5869 procmime_encode_content(mimetext, encoding);
5871 /* append attachment parts */
5872 if (compose_use_attach(compose) && attach_parts) {
5873 MimeInfo *mimempart;
5874 gchar *boundary = NULL;
5875 mimempart = procmime_mimeinfo_new();
5876 mimempart->content = MIMECONTENT_EMPTY;
5877 mimempart->type = MIMETYPE_MULTIPART;
5878 mimempart->subtype = g_strdup("mixed");
5880 do {
5881 g_free(boundary);
5882 boundary = generate_mime_boundary(NULL);
5883 } while (strstr(buf, boundary) != NULL);
5885 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5886 boundary);
5888 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5890 g_node_append(mimempart->node, mimetext->node);
5891 g_node_append(mimemsg->node, mimempart->node);
5893 if (compose_add_attachments(compose, mimempart) < 0)
5894 return COMPOSE_QUEUE_ERROR_NO_MSG;
5895 } else
5896 g_node_append(mimemsg->node, mimetext->node);
5898 g_free(buf);
5900 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5901 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5902 /* extract name and address */
5903 if (strstr(spec, " <") && strstr(spec, ">")) {
5904 from_addr = g_strdup(strrchr(spec, '<')+1);
5905 *(strrchr(from_addr, '>')) = '\0';
5906 from_name = g_strdup(spec);
5907 *(strrchr(from_name, '<')) = '\0';
5908 } else {
5909 from_name = NULL;
5910 from_addr = NULL;
5912 g_free(spec);
5914 /* sign message if sending */
5915 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5916 privacy_system_can_sign(compose->privacy_system))
5917 if (!privacy_sign(compose->privacy_system, mimemsg,
5918 compose->account, from_addr)) {
5919 g_free(from_name);
5920 g_free(from_addr);
5921 if (!privacy_peek_error())
5922 return COMPOSE_QUEUE_SIGNING_CANCELLED;
5923 else
5924 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5926 g_free(from_name);
5927 g_free(from_addr);
5929 if (compose->use_encryption) {
5930 if (compose->encdata != NULL &&
5931 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5933 /* First, write an unencrypted copy and save it to outbox, if
5934 * user wants that. */
5935 if (compose->account->save_encrypted_as_clear_text) {
5936 debug_print("saving sent message unencrypted...\n");
5937 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5938 if (tmpfp) {
5939 claws_fclose(tmpfp);
5941 /* fp now points to a file with headers written,
5942 * let's make a copy. */
5943 rewind(fp);
5944 content = file_read_stream_to_str(fp);
5946 str_write_to_file(content, tmp_enc_file, TRUE);
5947 g_free(content);
5949 /* Now write the unencrypted body. */
5950 if ((tmpfp = claws_fopen(tmp_enc_file, "a")) != NULL) {
5951 procmime_write_mimeinfo(mimemsg, tmpfp);
5952 claws_fclose(tmpfp);
5954 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5955 if (!outbox)
5956 outbox = folder_get_default_outbox();
5958 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5959 claws_unlink(tmp_enc_file);
5960 } else {
5961 g_warning("Can't open file '%s'", tmp_enc_file);
5963 } else {
5964 g_warning("couldn't get tempfile");
5967 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5968 debug_print("Couldn't encrypt mime structure: %s.\n",
5969 privacy_get_error());
5970 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5975 procmime_write_mimeinfo(mimemsg, fp);
5977 procmime_mimeinfo_free_all(&mimemsg);
5979 return 0;
5982 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5984 GtkTextBuffer *buffer;
5985 GtkTextIter start, end;
5986 FILE *fp;
5987 size_t len;
5988 gchar *chars, *tmp;
5990 if ((fp = claws_fopen(file, "wb")) == NULL) {
5991 FILE_OP_ERROR(file, "claws_fopen");
5992 return -1;
5995 /* chmod for security */
5996 if (change_file_mode_rw(fp, file) < 0) {
5997 FILE_OP_ERROR(file, "chmod");
5998 g_warning("can't change file mode");
6001 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
6002 gtk_text_buffer_get_start_iter(buffer, &start);
6003 gtk_text_buffer_get_end_iter(buffer, &end);
6004 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
6006 chars = conv_codeset_strdup
6007 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6009 g_free(tmp);
6010 if (!chars) {
6011 claws_fclose(fp);
6012 claws_unlink(file);
6013 return -1;
6015 /* write body */
6016 len = strlen(chars);
6017 if (claws_fwrite(chars, sizeof(gchar), len, fp) != len) {
6018 FILE_OP_ERROR(file, "claws_fwrite");
6019 g_free(chars);
6020 claws_fclose(fp);
6021 claws_unlink(file);
6022 return -1;
6025 g_free(chars);
6027 if (claws_safe_fclose(fp) == EOF) {
6028 FILE_OP_ERROR(file, "claws_fclose");
6029 claws_unlink(file);
6030 return -1;
6032 return 0;
6035 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6037 FolderItem *item;
6038 MsgInfo *msginfo = compose->targetinfo;
6040 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6041 if (!msginfo) return -1;
6043 if (!force && MSG_IS_LOCKED(msginfo->flags))
6044 return 0;
6046 item = msginfo->folder;
6047 cm_return_val_if_fail(item != NULL, -1);
6049 if (procmsg_msg_exist(msginfo) &&
6050 (folder_has_parent_of_type(item, F_QUEUE) ||
6051 folder_has_parent_of_type(item, F_DRAFT)
6052 || msginfo == compose->autosaved_draft)) {
6053 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6054 g_warning("can't remove the old message");
6055 return -1;
6056 } else {
6057 debug_print("removed reedit target %d\n", msginfo->msgnum);
6061 return 0;
6064 static void compose_remove_draft(Compose *compose)
6066 FolderItem *drafts;
6067 MsgInfo *msginfo = compose->targetinfo;
6068 drafts = account_get_special_folder(compose->account, F_DRAFT);
6070 if (procmsg_msg_exist(msginfo)) {
6071 folder_item_remove_msg(drafts, msginfo->msgnum);
6076 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6077 gboolean remove_reedit_target)
6079 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6082 static gboolean compose_warn_encryption(Compose *compose)
6084 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6085 AlertValue val = G_ALERTALTERNATE;
6087 if (warning == NULL)
6088 return TRUE;
6090 val = alertpanel_full(_("Encryption warning"), warning,
6091 GTK_STOCK_CANCEL, _("C_ontinue"), NULL, ALERTFOCUS_SECOND,
6092 TRUE, NULL, ALERT_WARNING);
6093 if (val & G_ALERTDISABLE) {
6094 val &= ~G_ALERTDISABLE;
6095 if (val == G_ALERTALTERNATE)
6096 privacy_inhibit_encrypt_warning(compose->privacy_system,
6097 TRUE);
6100 if (val == G_ALERTALTERNATE) {
6101 return TRUE;
6102 } else {
6103 return FALSE;
6107 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6108 gchar **msgpath, gboolean perform_checks,
6109 gboolean remove_reedit_target)
6111 FolderItem *queue;
6112 gchar *tmp;
6113 FILE *fp;
6114 GSList *cur;
6115 gint num;
6116 PrefsAccount *mailac = NULL, *newsac = NULL;
6117 gboolean err = FALSE;
6119 debug_print("queueing message...\n");
6120 cm_return_val_if_fail(compose->account != NULL, -1);
6122 if (compose_check_entries(compose, perform_checks) == FALSE) {
6123 if (compose->batch) {
6124 gtk_widget_show_all(compose->window);
6126 return COMPOSE_QUEUE_ERROR_NO_MSG;
6129 if (!compose->to_list && !compose->newsgroup_list) {
6130 g_warning("can't get recipient list.");
6131 return COMPOSE_QUEUE_ERROR_NO_MSG;
6134 if (compose->to_list) {
6135 if (compose->account->protocol != A_NNTP)
6136 mailac = compose->account;
6137 else if (cur_account && cur_account->protocol != A_NNTP)
6138 mailac = cur_account;
6139 else if (!(mailac = compose_current_mail_account())) {
6140 alertpanel_error(_("No account for sending mails available!"));
6141 return COMPOSE_QUEUE_ERROR_NO_MSG;
6145 if (compose->newsgroup_list) {
6146 if (compose->account->protocol == A_NNTP)
6147 newsac = compose->account;
6148 else {
6149 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6150 return COMPOSE_QUEUE_ERROR_NO_MSG;
6154 /* write queue header */
6155 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6156 G_DIR_SEPARATOR, compose, (guint) rand());
6157 debug_print("queuing to %s\n", tmp);
6158 if ((fp = claws_fopen(tmp, "w+b")) == NULL) {
6159 FILE_OP_ERROR(tmp, "claws_fopen");
6160 g_free(tmp);
6161 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6164 if (change_file_mode_rw(fp, tmp) < 0) {
6165 FILE_OP_ERROR(tmp, "chmod");
6166 g_warning("can't change file mode");
6169 /* queueing variables */
6170 err |= (fprintf(fp, "AF:\n") < 0);
6171 err |= (fprintf(fp, "NF:0\n") < 0);
6172 err |= (fprintf(fp, "PS:10\n") < 0);
6173 err |= (fprintf(fp, "SRH:1\n") < 0);
6174 err |= (fprintf(fp, "SFN:\n") < 0);
6175 err |= (fprintf(fp, "DSR:\n") < 0);
6176 if (compose->msgid)
6177 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6178 else
6179 err |= (fprintf(fp, "MID:\n") < 0);
6180 err |= (fprintf(fp, "CFG:\n") < 0);
6181 err |= (fprintf(fp, "PT:0\n") < 0);
6182 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6183 err |= (fprintf(fp, "RQ:\n") < 0);
6184 if (mailac)
6185 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6186 else
6187 err |= (fprintf(fp, "SSV:\n") < 0);
6188 if (newsac)
6189 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6190 else
6191 err |= (fprintf(fp, "NSV:\n") < 0);
6192 err |= (fprintf(fp, "SSH:\n") < 0);
6193 /* write recipient list */
6194 if (compose->to_list) {
6195 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6196 for (cur = compose->to_list->next; cur != NULL;
6197 cur = cur->next)
6198 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6199 err |= (fprintf(fp, "\n") < 0);
6201 /* write newsgroup list */
6202 if (compose->newsgroup_list) {
6203 err |= (fprintf(fp, "NG:") < 0);
6204 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6205 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6206 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6207 err |= (fprintf(fp, "\n") < 0);
6209 /* account IDs */
6210 if (mailac)
6211 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6212 if (newsac)
6213 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6216 if (compose->privacy_system != NULL) {
6217 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6218 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6219 if (compose->use_encryption) {
6220 if (!compose_warn_encryption(compose)) {
6221 claws_fclose(fp);
6222 claws_unlink(tmp);
6223 g_free(tmp);
6224 return COMPOSE_QUEUE_ERROR_NO_MSG;
6226 if (mailac && mailac->encrypt_to_self) {
6227 GSList *tmp_list = g_slist_copy(compose->to_list);
6228 tmp_list = g_slist_append(tmp_list, compose->account->address);
6229 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6230 g_slist_free(tmp_list);
6231 } else {
6232 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6234 if (compose->encdata != NULL) {
6235 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6236 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6237 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6238 compose->encdata) < 0);
6239 } /* else we finally dont want to encrypt */
6240 } else {
6241 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6242 /* and if encdata was null, it means there's been a problem in
6243 * key selection */
6244 if (err == TRUE)
6245 g_warning("failed to write queue message");
6246 claws_fclose(fp);
6247 claws_unlink(tmp);
6248 g_free(tmp);
6249 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6254 /* Save copy folder */
6255 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6256 gchar *savefolderid;
6258 savefolderid = compose_get_save_to(compose);
6259 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6260 g_free(savefolderid);
6262 /* Save copy folder */
6263 if (compose->return_receipt) {
6264 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6266 /* Message-ID of message replying to */
6267 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6268 gchar *folderid = NULL;
6270 if (compose->replyinfo->folder)
6271 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6272 if (folderid == NULL)
6273 folderid = g_strdup("NULL");
6275 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6276 g_free(folderid);
6278 /* Message-ID of message forwarding to */
6279 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6280 gchar *folderid = NULL;
6282 if (compose->fwdinfo->folder)
6283 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6284 if (folderid == NULL)
6285 folderid = g_strdup("NULL");
6287 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6288 g_free(folderid);
6291 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6292 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6294 /* end of headers */
6295 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6297 if (compose->redirect_filename != NULL) {
6298 if (compose_redirect_write_to_file(compose, fp) < 0) {
6299 claws_fclose(fp);
6300 claws_unlink(tmp);
6301 g_free(tmp);
6302 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6304 } else {
6305 gint result = 0;
6306 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6307 claws_fclose(fp);
6308 claws_unlink(tmp);
6309 g_free(tmp);
6310 return result;
6313 if (err == TRUE) {
6314 g_warning("failed to write queue message");
6315 claws_fclose(fp);
6316 claws_unlink(tmp);
6317 g_free(tmp);
6318 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6320 if (claws_safe_fclose(fp) == EOF) {
6321 FILE_OP_ERROR(tmp, "claws_fclose");
6322 claws_unlink(tmp);
6323 g_free(tmp);
6324 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6327 if (item && *item) {
6328 queue = *item;
6329 } else {
6330 queue = account_get_special_folder(compose->account, F_QUEUE);
6332 if (!queue) {
6333 g_warning("can't find queue folder");
6334 claws_unlink(tmp);
6335 g_free(tmp);
6336 return COMPOSE_QUEUE_ERROR_NO_MSG;
6338 folder_item_scan(queue);
6339 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6340 g_warning("can't queue the message");
6341 claws_unlink(tmp);
6342 g_free(tmp);
6343 return COMPOSE_QUEUE_ERROR_NO_MSG;
6346 if (msgpath == NULL) {
6347 claws_unlink(tmp);
6348 g_free(tmp);
6349 } else
6350 *msgpath = tmp;
6352 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6353 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6354 if (mi) {
6355 procmsg_msginfo_change_flags(mi,
6356 compose->targetinfo->flags.perm_flags,
6357 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6358 0, 0);
6360 g_slist_free(mi->tags);
6361 mi->tags = g_slist_copy(compose->targetinfo->tags);
6362 procmsg_msginfo_free(&mi);
6366 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6367 compose_remove_reedit_target(compose, FALSE);
6370 if ((msgnum != NULL) && (item != NULL)) {
6371 *msgnum = num;
6372 *item = queue;
6375 return COMPOSE_QUEUE_SUCCESS;
6378 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6380 AttachInfo *ainfo;
6381 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6382 MimeInfo *mimepart;
6383 #ifdef G_OS_WIN32
6384 GFile *f;
6385 GFileInfo *fi;
6386 GError *error = NULL;
6387 #else
6388 GStatBuf statbuf;
6389 #endif
6390 goffset size;
6391 gchar *type, *subtype;
6392 GtkTreeModel *model;
6393 GtkTreeIter iter;
6395 model = gtk_tree_view_get_model(tree_view);
6397 if (!gtk_tree_model_get_iter_first(model, &iter))
6398 return 0;
6399 do {
6400 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6402 if (!is_file_exist(ainfo->file)) {
6403 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6404 AlertValue val = alertpanel_full(_("Warning"), msg,
6405 _("Cancel sending"), _("Ignore attachment"), NULL,
6406 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6407 g_free(msg);
6408 if (val == G_ALERTDEFAULT) {
6409 return -1;
6411 continue;
6413 #ifdef G_OS_WIN32
6414 f = g_file_new_for_path(ainfo->file);
6415 fi = g_file_query_info(f, "standard::size",
6416 G_FILE_QUERY_INFO_NONE, NULL, &error);
6417 if (error != NULL) {
6418 g_warning(error->message);
6419 g_error_free(error);
6420 g_object_unref(f);
6421 return -1;
6423 size = g_file_info_get_size(fi);
6424 g_object_unref(fi);
6425 g_object_unref(f);
6426 #else
6427 if (g_stat(ainfo->file, &statbuf) < 0)
6428 return -1;
6429 size = statbuf.st_size;
6430 #endif
6432 mimepart = procmime_mimeinfo_new();
6433 mimepart->content = MIMECONTENT_FILE;
6434 mimepart->data.filename = g_strdup(ainfo->file);
6435 mimepart->tmp = FALSE; /* or we destroy our attachment */
6436 mimepart->offset = 0;
6437 mimepart->length = size;
6439 type = g_strdup(ainfo->content_type);
6441 if (!strchr(type, '/')) {
6442 g_free(type);
6443 type = g_strdup("application/octet-stream");
6446 subtype = strchr(type, '/') + 1;
6447 *(subtype - 1) = '\0';
6448 mimepart->type = procmime_get_media_type(type);
6449 mimepart->subtype = g_strdup(subtype);
6450 g_free(type);
6452 if (mimepart->type == MIMETYPE_MESSAGE &&
6453 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6454 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6455 } else if (mimepart->type == MIMETYPE_TEXT) {
6456 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6457 /* Text parts with no name come from multipart/alternative
6458 * forwards. Make sure the recipient won't look at the
6459 * original HTML part by mistake. */
6460 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6461 ainfo->name = g_strdup_printf(_("Original %s part"),
6462 mimepart->subtype);
6464 if (ainfo->charset)
6465 g_hash_table_insert(mimepart->typeparameters,
6466 g_strdup("charset"), g_strdup(ainfo->charset));
6468 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6469 if (mimepart->type == MIMETYPE_APPLICATION &&
6470 !g_strcmp0(mimepart->subtype, "octet-stream"))
6471 g_hash_table_insert(mimepart->typeparameters,
6472 g_strdup("name"), g_strdup(ainfo->name));
6473 g_hash_table_insert(mimepart->dispositionparameters,
6474 g_strdup("filename"), g_strdup(ainfo->name));
6475 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6478 if (mimepart->type == MIMETYPE_MESSAGE
6479 || mimepart->type == MIMETYPE_MULTIPART)
6480 ainfo->encoding = ENC_BINARY;
6481 else if (compose->use_signing || compose->fwdinfo != NULL) {
6482 if (ainfo->encoding == ENC_7BIT)
6483 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6484 else if (ainfo->encoding == ENC_8BIT)
6485 ainfo->encoding = ENC_BASE64;
6488 procmime_encode_content(mimepart, ainfo->encoding);
6490 g_node_append(parent->node, mimepart->node);
6491 } while (gtk_tree_model_iter_next(model, &iter));
6493 return 0;
6496 static gchar *compose_quote_list_of_addresses(gchar *str)
6498 GSList *list = NULL, *item = NULL;
6499 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6501 list = address_list_append_with_comments(list, str);
6502 for (item = list; item != NULL; item = item->next) {
6503 gchar *spec = item->data;
6504 gchar *endofname = strstr(spec, " <");
6505 if (endofname != NULL) {
6506 gchar * qqname;
6507 *endofname = '\0';
6508 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6509 qqname = escape_internal_quotes(qname, '"');
6510 *endofname = ' ';
6511 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6512 gchar *addr = g_strdup(endofname);
6513 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6514 faddr = g_strconcat(name, addr, NULL);
6515 g_free(name);
6516 g_free(addr);
6517 debug_print("new auto-quoted address: '%s'\n", faddr);
6520 if (result == NULL)
6521 result = g_strdup((faddr != NULL)? faddr: spec);
6522 else {
6523 result = g_strconcat(result,
6524 ", ",
6525 (faddr != NULL)? faddr: spec,
6526 NULL);
6528 if (faddr != NULL) {
6529 g_free(faddr);
6530 faddr = NULL;
6533 slist_free_strings_full(list);
6535 return result;
6538 #define IS_IN_CUSTOM_HEADER(header) \
6539 (compose->account->add_customhdr && \
6540 custom_header_find(compose->account->customhdr_list, header) != NULL)
6542 static const gchar *compose_untranslated_header_name(gchar *header_name)
6544 /* return the untranslated header name, if header_name is a known
6545 header name, in either its translated or untranslated form, with
6546 or without trailing colon. otherwise, returns header_name. */
6547 gchar *translated_header_name;
6548 gchar *translated_header_name_wcolon;
6549 const gchar *untranslated_header_name;
6550 const gchar *untranslated_header_name_wcolon;
6551 gint i;
6553 cm_return_val_if_fail(header_name != NULL, NULL);
6555 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6556 untranslated_header_name = HEADERS[i].header_name;
6557 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6559 translated_header_name = gettext(untranslated_header_name);
6560 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6562 if (!strcmp(header_name, untranslated_header_name) ||
6563 !strcmp(header_name, translated_header_name)) {
6564 return untranslated_header_name;
6565 } else {
6566 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6567 !strcmp(header_name, translated_header_name_wcolon)) {
6568 return untranslated_header_name_wcolon;
6572 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6573 return header_name;
6576 static void compose_add_headerfield_from_headerlist(Compose *compose,
6577 GString *header,
6578 const gchar *fieldname,
6579 const gchar *seperator)
6581 gchar *str, *fieldname_w_colon;
6582 gboolean add_field = FALSE;
6583 GSList *list;
6584 ComposeHeaderEntry *headerentry;
6585 const gchar *headerentryname;
6586 const gchar *trans_fieldname;
6587 GString *fieldstr;
6589 if (IS_IN_CUSTOM_HEADER(fieldname))
6590 return;
6592 debug_print("Adding %s-fields\n", fieldname);
6594 fieldstr = g_string_sized_new(64);
6596 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6597 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6599 for (list = compose->header_list; list; list = list->next) {
6600 headerentry = ((ComposeHeaderEntry *)list->data);
6601 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6603 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6604 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6605 g_strstrip(ustr);
6606 str = compose_quote_list_of_addresses(ustr);
6607 g_free(ustr);
6608 if (str != NULL && str[0] != '\0') {
6609 if (add_field)
6610 g_string_append(fieldstr, seperator);
6611 g_string_append(fieldstr, str);
6612 add_field = TRUE;
6614 g_free(str);
6617 if (add_field) {
6618 gchar *buf;
6620 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6621 compose_convert_header
6622 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6623 strlen(fieldname) + 2, TRUE);
6624 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6625 g_free(buf);
6628 g_free(fieldname_w_colon);
6629 g_string_free(fieldstr, TRUE);
6631 return;
6634 static gchar *compose_get_manual_headers_info(Compose *compose)
6636 GString *sh_header = g_string_new(" ");
6637 GSList *list;
6638 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6640 for (list = compose->header_list; list; list = list->next) {
6641 ComposeHeaderEntry *headerentry;
6642 gchar *tmp;
6643 gchar *headername;
6644 gchar *headername_wcolon;
6645 const gchar *headername_trans;
6646 gchar **string;
6647 gboolean standard_header = FALSE;
6649 headerentry = ((ComposeHeaderEntry *)list->data);
6651 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6652 g_strstrip(tmp);
6653 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6654 g_free(tmp);
6655 continue;
6658 if (!strstr(tmp, ":")) {
6659 headername_wcolon = g_strconcat(tmp, ":", NULL);
6660 headername = g_strdup(tmp);
6661 } else {
6662 headername_wcolon = g_strdup(tmp);
6663 headername = g_strdup(strtok(tmp, ":"));
6665 g_free(tmp);
6667 string = std_headers;
6668 while (*string != NULL) {
6669 headername_trans = prefs_common_translated_header_name(*string);
6670 if (!strcmp(headername_trans, headername_wcolon))
6671 standard_header = TRUE;
6672 string++;
6674 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6675 g_string_append_printf(sh_header, "%s ", headername);
6676 g_free(headername);
6677 g_free(headername_wcolon);
6679 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6680 return g_string_free(sh_header, FALSE);
6683 static gchar *compose_get_header(Compose *compose)
6685 gchar date[RFC822_DATE_BUFFSIZE];
6686 gchar buf[BUFFSIZE];
6687 const gchar *entry_str;
6688 gchar *str;
6689 gchar *name;
6690 GSList *list;
6691 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6692 GString *header;
6693 gchar *from_name = NULL, *from_address = NULL;
6694 gchar *tmp;
6696 cm_return_val_if_fail(compose->account != NULL, NULL);
6697 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6699 header = g_string_sized_new(64);
6701 /* Date */
6702 if (prefs_common.hide_timezone)
6703 get_rfc822_date_hide_tz(date, sizeof(date));
6704 else
6705 get_rfc822_date(date, sizeof(date));
6706 g_string_append_printf(header, "Date: %s\n", date);
6708 /* From */
6710 if (compose->account->name && *compose->account->name) {
6711 gchar *buf;
6712 QUOTE_IF_REQUIRED(buf, compose->account->name);
6713 tmp = g_strdup_printf("%s <%s>",
6714 buf, compose->account->address);
6715 } else {
6716 tmp = g_strdup_printf("%s",
6717 compose->account->address);
6719 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6720 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6721 /* use default */
6722 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6723 from_address = g_strdup(compose->account->address);
6724 } else {
6725 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6726 /* extract name and address */
6727 if (strstr(spec, " <") && strstr(spec, ">")) {
6728 from_address = g_strdup(strrchr(spec, '<')+1);
6729 *(strrchr(from_address, '>')) = '\0';
6730 from_name = g_strdup(spec);
6731 *(strrchr(from_name, '<')) = '\0';
6732 } else {
6733 from_name = NULL;
6734 from_address = g_strdup(spec);
6736 g_free(spec);
6738 g_free(tmp);
6741 if (from_name && *from_name) {
6742 gchar *qname;
6743 compose_convert_header
6744 (compose, buf, sizeof(buf), from_name,
6745 strlen("From: "), TRUE);
6746 QUOTE_IF_REQUIRED(name, buf);
6747 qname = escape_internal_quotes(name, '"');
6749 g_string_append_printf(header, "From: %s <%s>\n",
6750 qname, from_address);
6751 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6752 compose->return_receipt) {
6753 compose_convert_header(compose, buf, sizeof(buf), from_name,
6754 strlen("Disposition-Notification-To: "),
6755 TRUE);
6756 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6758 if (qname != name)
6759 g_free(qname);
6760 } else {
6761 g_string_append_printf(header, "From: %s\n", from_address);
6762 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6763 compose->return_receipt)
6764 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6767 g_free(from_name);
6768 g_free(from_address);
6770 /* To */
6771 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6773 /* Newsgroups */
6774 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6776 /* Cc */
6777 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6779 /* Bcc */
6781 * If this account is a NNTP account remove Bcc header from
6782 * message body since it otherwise will be publicly shown
6784 if (compose->account->protocol != A_NNTP)
6785 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6787 /* Subject */
6788 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6790 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6791 g_strstrip(str);
6792 if (*str != '\0') {
6793 compose_convert_header(compose, buf, sizeof(buf), str,
6794 strlen("Subject: "), FALSE);
6795 g_string_append_printf(header, "Subject: %s\n", buf);
6798 g_free(str);
6800 /* Message-ID */
6801 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6802 g_string_append_printf(header, "Message-ID: <%s>\n",
6803 compose->msgid);
6806 if (compose->remove_references == FALSE) {
6807 /* In-Reply-To */
6808 if (compose->inreplyto && compose->to_list)
6809 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6811 /* References */
6812 if (compose->references)
6813 g_string_append_printf(header, "References: %s\n", compose->references);
6816 /* Followup-To */
6817 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6819 /* Reply-To */
6820 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6822 /* Organization */
6823 if (compose->account->organization &&
6824 strlen(compose->account->organization) &&
6825 !IS_IN_CUSTOM_HEADER("Organization")) {
6826 compose_convert_header(compose, buf, sizeof(buf),
6827 compose->account->organization,
6828 strlen("Organization: "), FALSE);
6829 g_string_append_printf(header, "Organization: %s\n", buf);
6832 /* Program version and system info */
6833 if (compose->account->gen_xmailer &&
6834 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6835 !compose->newsgroup_list) {
6836 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6837 prog_version,
6838 gtk_major_version, gtk_minor_version, gtk_micro_version,
6839 TARGET_ALIAS);
6841 if (compose->account->gen_xmailer &&
6842 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6843 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6844 prog_version,
6845 gtk_major_version, gtk_minor_version, gtk_micro_version,
6846 TARGET_ALIAS);
6849 /* custom headers */
6850 if (compose->account->add_customhdr) {
6851 GSList *cur;
6853 for (cur = compose->account->customhdr_list; cur != NULL;
6854 cur = cur->next) {
6855 CustomHeader *chdr = (CustomHeader *)cur->data;
6857 if (custom_header_is_allowed(chdr->name)
6858 && chdr->value != NULL
6859 && *(chdr->value) != '\0') {
6860 compose_convert_header
6861 (compose, buf, sizeof(buf),
6862 chdr->value,
6863 strlen(chdr->name) + 2, FALSE);
6864 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6869 /* Automatic Faces and X-Faces */
6870 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6871 g_string_append_printf(header, "X-Face: %s\n", buf);
6873 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6874 g_string_append_printf(header, "X-Face: %s\n", buf);
6876 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6877 g_string_append_printf(header, "Face: %s\n", buf);
6879 else if (get_default_face (buf, sizeof(buf)) == 0) {
6880 g_string_append_printf(header, "Face: %s\n", buf);
6883 /* PRIORITY */
6884 switch (compose->priority) {
6885 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6886 "X-Priority: 1 (Highest)\n");
6887 break;
6888 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6889 "X-Priority: 2 (High)\n");
6890 break;
6891 case PRIORITY_NORMAL: break;
6892 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6893 "X-Priority: 4 (Low)\n");
6894 break;
6895 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6896 "X-Priority: 5 (Lowest)\n");
6897 break;
6898 default: debug_print("compose: priority unknown : %d\n",
6899 compose->priority);
6902 /* get special headers */
6903 for (list = compose->header_list; list; list = list->next) {
6904 ComposeHeaderEntry *headerentry;
6905 gchar *tmp;
6906 gchar *headername;
6907 gchar *headername_wcolon;
6908 const gchar *headername_trans;
6909 gchar *headervalue;
6910 gchar **string;
6911 gboolean standard_header = FALSE;
6913 headerentry = ((ComposeHeaderEntry *)list->data);
6915 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6916 g_strstrip(tmp);
6917 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6918 g_free(tmp);
6919 continue;
6922 if (!strstr(tmp, ":")) {
6923 headername_wcolon = g_strconcat(tmp, ":", NULL);
6924 headername = g_strdup(tmp);
6925 } else {
6926 headername_wcolon = g_strdup(tmp);
6927 headername = g_strdup(strtok(tmp, ":"));
6929 g_free(tmp);
6931 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6932 Xstrdup_a(headervalue, entry_str, return NULL);
6933 subst_char(headervalue, '\r', ' ');
6934 subst_char(headervalue, '\n', ' ');
6935 g_strstrip(headervalue);
6936 if (*headervalue != '\0') {
6937 string = std_headers;
6938 while (*string != NULL && !standard_header) {
6939 headername_trans = prefs_common_translated_header_name(*string);
6940 /* support mixed translated and untranslated headers */
6941 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6942 standard_header = TRUE;
6943 string++;
6945 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6946 /* store untranslated header name */
6947 g_string_append_printf(header, "%s %s\n",
6948 compose_untranslated_header_name(headername_wcolon), headervalue);
6951 g_free(headername);
6952 g_free(headername_wcolon);
6955 str = header->str;
6956 g_string_free(header, FALSE);
6958 return str;
6961 #undef IS_IN_CUSTOM_HEADER
6963 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6964 gint header_len, gboolean addr_field)
6966 gchar *tmpstr = NULL;
6967 const gchar *out_codeset = NULL;
6969 cm_return_if_fail(src != NULL);
6970 cm_return_if_fail(dest != NULL);
6972 if (len < 1) return;
6974 tmpstr = g_strdup(src);
6976 subst_char(tmpstr, '\n', ' ');
6977 subst_char(tmpstr, '\r', ' ');
6978 g_strchomp(tmpstr);
6980 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6981 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6982 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6983 g_free(tmpstr);
6984 tmpstr = mybuf;
6987 codeconv_set_strict(TRUE);
6988 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6989 conv_get_charset_str(compose->out_encoding));
6990 codeconv_set_strict(FALSE);
6992 if (!dest || *dest == '\0') {
6993 gchar *test_conv_global_out = NULL;
6994 gchar *test_conv_reply = NULL;
6996 /* automatic mode. be automatic. */
6997 codeconv_set_strict(TRUE);
6999 out_codeset = conv_get_outgoing_charset_str();
7000 if (out_codeset) {
7001 debug_print("trying to convert to %s\n", out_codeset);
7002 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7005 if (!test_conv_global_out && compose->orig_charset
7006 && strcmp(compose->orig_charset, CS_US_ASCII)) {
7007 out_codeset = compose->orig_charset;
7008 debug_print("failure; trying to convert to %s\n", out_codeset);
7009 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7012 if (!test_conv_global_out && !test_conv_reply) {
7013 /* we're lost */
7014 out_codeset = CS_INTERNAL;
7015 debug_print("finally using %s\n", out_codeset);
7017 g_free(test_conv_global_out);
7018 g_free(test_conv_reply);
7019 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7020 out_codeset);
7021 codeconv_set_strict(FALSE);
7023 g_free(tmpstr);
7026 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7028 gchar *address;
7030 cm_return_if_fail(user_data != NULL);
7032 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7033 g_strstrip(address);
7034 if (*address != '\0') {
7035 gchar *name = procheader_get_fromname(address);
7036 extract_address(address);
7037 #ifndef USE_ALT_ADDRBOOK
7038 addressbook_add_contact(name, address, NULL, NULL);
7039 #else
7040 debug_print("%s: %s\n", name, address);
7041 if (addressadd_selection(name, address, NULL, NULL)) {
7042 debug_print( "addressbook_add_contact - added\n" );
7044 #endif
7046 g_free(address);
7049 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7051 GtkWidget *menuitem;
7052 gchar *address;
7054 cm_return_if_fail(menu != NULL);
7055 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7057 menuitem = gtk_separator_menu_item_new();
7058 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7059 gtk_widget_show(menuitem);
7061 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7062 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7064 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7065 g_strstrip(address);
7066 if (*address == '\0') {
7067 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7070 g_signal_connect(G_OBJECT(menuitem), "activate",
7071 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7072 gtk_widget_show(menuitem);
7075 void compose_add_extra_header(gchar *header, GtkListStore *model)
7077 GtkTreeIter iter;
7078 if (strcmp(header, "")) {
7079 COMBOBOX_ADD(model, header, COMPOSE_TO);
7083 void compose_add_extra_header_entries(GtkListStore *model)
7085 FILE *exh;
7086 gchar *exhrc;
7087 gchar buf[BUFFSIZE];
7088 gint lastc;
7090 if (extra_headers == NULL) {
7091 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7092 if ((exh = claws_fopen(exhrc, "rb")) == NULL) {
7093 debug_print("extra headers file not found\n");
7094 goto extra_headers_done;
7096 while (claws_fgets(buf, BUFFSIZE, exh) != NULL) {
7097 lastc = strlen(buf) - 1; /* remove trailing control chars */
7098 while (lastc >= 0 && buf[lastc] != ':')
7099 buf[lastc--] = '\0';
7100 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7101 buf[lastc] = '\0'; /* remove trailing : for comparison */
7102 if (custom_header_is_allowed(buf)) {
7103 buf[lastc] = ':';
7104 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7106 else
7107 g_message("disallowed extra header line: %s\n", buf);
7109 else {
7110 if (buf[0] != '#')
7111 g_message("invalid extra header line: %s\n", buf);
7114 claws_fclose(exh);
7115 extra_headers_done:
7116 g_free(exhrc);
7117 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7118 extra_headers = g_slist_reverse(extra_headers);
7120 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7123 #ifdef USE_LDAP
7124 static void _ldap_srv_func(gpointer data, gpointer user_data)
7126 LdapServer *server = (LdapServer *)data;
7127 gboolean *enable = (gboolean *)user_data;
7129 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7130 server->searchFlag = *enable;
7132 #endif
7134 static void compose_create_header_entry(Compose *compose)
7136 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7138 GtkWidget *combo;
7139 GtkWidget *entry;
7140 GtkWidget *button;
7141 GtkWidget *hbox;
7142 gchar **string;
7143 const gchar *header = NULL;
7144 ComposeHeaderEntry *headerentry;
7145 gboolean standard_header = FALSE;
7146 GtkListStore *model;
7147 GtkTreeIter iter;
7149 headerentry = g_new0(ComposeHeaderEntry, 1);
7151 /* Combo box model */
7152 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7153 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7154 COMPOSE_TO);
7155 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7156 COMPOSE_CC);
7157 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7158 COMPOSE_BCC);
7159 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7160 COMPOSE_NEWSGROUPS);
7161 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7162 COMPOSE_REPLYTO);
7163 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7164 COMPOSE_FOLLOWUPTO);
7165 compose_add_extra_header_entries(model);
7167 /* Combo box */
7168 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7169 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7170 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7171 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7172 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7173 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7174 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7175 G_CALLBACK(compose_grab_focus_cb), compose);
7176 gtk_widget_show(combo);
7178 /* Putting only the combobox child into focus chain of its parent causes
7179 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7180 * This eliminates need to pres Tab twice in order to really get from the
7181 * combobox to next widget. */
7182 GList *l = NULL;
7183 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7184 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7185 g_list_free(l);
7187 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7188 compose->header_nextrow, compose->header_nextrow+1,
7189 GTK_SHRINK, GTK_FILL, 0, 0);
7190 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7191 const gchar *last_header_entry = gtk_entry_get_text(
7192 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7193 string = headers;
7194 while (*string != NULL) {
7195 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7196 standard_header = TRUE;
7197 string++;
7199 if (standard_header)
7200 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7202 if (!compose->header_last || !standard_header) {
7203 switch(compose->account->protocol) {
7204 case A_NNTP:
7205 header = prefs_common_translated_header_name("Newsgroups:");
7206 break;
7207 default:
7208 header = prefs_common_translated_header_name("To:");
7209 break;
7212 if (header)
7213 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7215 gtk_editable_set_editable(
7216 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7217 prefs_common.type_any_header);
7219 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7220 G_CALLBACK(compose_grab_focus_cb), compose);
7222 /* Entry field with cleanup button */
7223 button = gtk_button_new();
7224 gtk_button_set_image(GTK_BUTTON(button),
7225 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7226 gtk_widget_show(button);
7227 CLAWS_SET_TIP(button,
7228 _("Delete entry contents"));
7229 entry = gtk_entry_new();
7230 gtk_widget_show(entry);
7231 CLAWS_SET_TIP(entry,
7232 _("Use <tab> to autocomplete from addressbook"));
7233 hbox = gtk_hbox_new (FALSE, 0);
7234 gtk_widget_show(hbox);
7235 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7236 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7237 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7238 compose->header_nextrow, compose->header_nextrow+1,
7239 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7241 g_signal_connect(G_OBJECT(entry), "key-press-event",
7242 G_CALLBACK(compose_headerentry_key_press_event_cb),
7243 headerentry);
7244 g_signal_connect(G_OBJECT(entry), "changed",
7245 G_CALLBACK(compose_headerentry_changed_cb),
7246 headerentry);
7247 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7248 G_CALLBACK(compose_grab_focus_cb), compose);
7250 g_signal_connect(G_OBJECT(button), "clicked",
7251 G_CALLBACK(compose_headerentry_button_clicked_cb),
7252 headerentry);
7254 /* email dnd */
7255 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7256 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7257 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7258 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7259 G_CALLBACK(compose_header_drag_received_cb),
7260 entry);
7261 g_signal_connect(G_OBJECT(entry), "drag-drop",
7262 G_CALLBACK(compose_drag_drop),
7263 compose);
7264 g_signal_connect(G_OBJECT(entry), "populate-popup",
7265 G_CALLBACK(compose_entry_popup_extend),
7266 NULL);
7268 #ifdef USE_LDAP
7269 #ifndef PASSWORD_CRYPTO_OLD
7270 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7271 if (pwd_servers != NULL && master_passphrase() == NULL) {
7272 gboolean enable = FALSE;
7273 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7274 /* Temporarily disable password-protected LDAP servers,
7275 * because user did not provide a master passphrase.
7276 * We can safely enable searchFlag on all servers in this list
7277 * later, since addrindex_get_password_protected_ldap_servers()
7278 * includes servers which have it enabled initially. */
7279 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7280 compose->passworded_ldap_servers = pwd_servers;
7282 #endif /* PASSWORD_CRYPTO_OLD */
7283 #endif /* USE_LDAP */
7285 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7287 headerentry->compose = compose;
7288 headerentry->combo = combo;
7289 headerentry->entry = entry;
7290 headerentry->button = button;
7291 headerentry->hbox = hbox;
7292 headerentry->headernum = compose->header_nextrow;
7293 headerentry->type = PREF_NONE;
7295 compose->header_nextrow++;
7296 compose->header_last = headerentry;
7297 compose->header_list =
7298 g_slist_append(compose->header_list,
7299 headerentry);
7302 static void compose_add_header_entry(Compose *compose, const gchar *header,
7303 gchar *text, ComposePrefType pref_type)
7305 ComposeHeaderEntry *last_header = compose->header_last;
7306 gchar *tmp = g_strdup(text), *email;
7307 gboolean replyto_hdr;
7309 replyto_hdr = (!strcasecmp(header,
7310 prefs_common_translated_header_name("Reply-To:")) ||
7311 !strcasecmp(header,
7312 prefs_common_translated_header_name("Followup-To:")) ||
7313 !strcasecmp(header,
7314 prefs_common_translated_header_name("In-Reply-To:")));
7316 extract_address(tmp);
7317 email = g_utf8_strdown(tmp, -1);
7319 if (replyto_hdr == FALSE &&
7320 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7322 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7323 header, text, (gint) pref_type);
7324 g_free(email);
7325 g_free(tmp);
7326 return;
7329 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7330 gtk_entry_set_text(GTK_ENTRY(
7331 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7332 else
7333 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7334 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7335 last_header->type = pref_type;
7337 if (replyto_hdr == FALSE)
7338 g_hash_table_insert(compose->email_hashtable, email,
7339 GUINT_TO_POINTER(1));
7340 else
7341 g_free(email);
7343 g_free(tmp);
7346 static void compose_destroy_headerentry(Compose *compose,
7347 ComposeHeaderEntry *headerentry)
7349 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7350 gchar *email;
7352 extract_address(text);
7353 email = g_utf8_strdown(text, -1);
7354 g_hash_table_remove(compose->email_hashtable, email);
7355 g_free(text);
7356 g_free(email);
7358 gtk_widget_destroy(headerentry->combo);
7359 gtk_widget_destroy(headerentry->entry);
7360 gtk_widget_destroy(headerentry->button);
7361 gtk_widget_destroy(headerentry->hbox);
7362 g_free(headerentry);
7365 static void compose_remove_header_entries(Compose *compose)
7367 GSList *list;
7368 for (list = compose->header_list; list; list = list->next)
7369 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7371 compose->header_last = NULL;
7372 g_slist_free(compose->header_list);
7373 compose->header_list = NULL;
7374 compose->header_nextrow = 1;
7375 compose_create_header_entry(compose);
7378 static GtkWidget *compose_create_header(Compose *compose)
7380 GtkWidget *from_optmenu_hbox;
7381 GtkWidget *header_table_main;
7382 GtkWidget *header_scrolledwin;
7383 GtkWidget *header_table;
7385 /* parent with account selection and from header */
7386 header_table_main = gtk_table_new(2, 2, FALSE);
7387 gtk_widget_show(header_table_main);
7388 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7390 from_optmenu_hbox = compose_account_option_menu_create(compose);
7391 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7392 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7394 /* child with header labels and entries */
7395 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7396 gtk_widget_show(header_scrolledwin);
7397 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7399 header_table = gtk_table_new(2, 2, FALSE);
7400 gtk_widget_show(header_table);
7401 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7402 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7403 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7404 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7405 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7407 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7408 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7410 compose->header_table = header_table;
7411 compose->header_list = NULL;
7412 compose->header_nextrow = 0;
7414 compose_create_header_entry(compose);
7416 compose->table = NULL;
7418 return header_table_main;
7421 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7423 Compose *compose = (Compose *)data;
7424 GdkEventButton event;
7426 event.button = 3;
7427 event.time = gtk_get_current_event_time();
7429 return attach_button_pressed(compose->attach_clist, &event, compose);
7432 static GtkWidget *compose_create_attach(Compose *compose)
7434 GtkWidget *attach_scrwin;
7435 GtkWidget *attach_clist;
7437 GtkListStore *store;
7438 GtkCellRenderer *renderer;
7439 GtkTreeViewColumn *column;
7440 GtkTreeSelection *selection;
7442 /* attachment list */
7443 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7444 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7445 GTK_POLICY_AUTOMATIC,
7446 GTK_POLICY_AUTOMATIC);
7447 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7449 store = gtk_list_store_new(N_ATTACH_COLS,
7450 G_TYPE_STRING,
7451 G_TYPE_STRING,
7452 G_TYPE_STRING,
7453 G_TYPE_STRING,
7454 G_TYPE_POINTER,
7455 G_TYPE_AUTO_POINTER,
7456 -1);
7457 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7458 (GTK_TREE_MODEL(store)));
7459 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7460 g_object_unref(store);
7462 renderer = gtk_cell_renderer_text_new();
7463 column = gtk_tree_view_column_new_with_attributes
7464 (_("Mime type"), renderer, "text",
7465 COL_MIMETYPE, NULL);
7466 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7468 renderer = gtk_cell_renderer_text_new();
7469 column = gtk_tree_view_column_new_with_attributes
7470 (_("Size"), renderer, "text",
7471 COL_SIZE, NULL);
7472 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7474 renderer = gtk_cell_renderer_text_new();
7475 column = gtk_tree_view_column_new_with_attributes
7476 (_("Name"), renderer, "text",
7477 COL_NAME, NULL);
7478 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7480 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7481 prefs_common.use_stripes_everywhere);
7482 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7483 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7485 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7486 G_CALLBACK(attach_selected), compose);
7487 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7488 G_CALLBACK(attach_button_pressed), compose);
7489 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7490 G_CALLBACK(popup_attach_button_pressed), compose);
7491 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7492 G_CALLBACK(attach_key_pressed), compose);
7494 /* drag and drop */
7495 gtk_drag_dest_set(attach_clist,
7496 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7497 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7498 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7499 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7500 G_CALLBACK(compose_attach_drag_received_cb),
7501 compose);
7502 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7503 G_CALLBACK(compose_drag_drop),
7504 compose);
7506 compose->attach_scrwin = attach_scrwin;
7507 compose->attach_clist = attach_clist;
7509 return attach_scrwin;
7512 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7514 static GtkWidget *compose_create_others(Compose *compose)
7516 GtkWidget *table;
7517 GtkWidget *savemsg_checkbtn;
7518 GtkWidget *savemsg_combo;
7519 GtkWidget *savemsg_select;
7521 guint rowcount = 0;
7522 gchar *folderidentifier;
7524 /* Table for settings */
7525 table = gtk_table_new(3, 1, FALSE);
7526 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7527 gtk_widget_show(table);
7528 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7529 rowcount = 0;
7531 /* Save Message to folder */
7532 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7533 gtk_widget_show(savemsg_checkbtn);
7534 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7535 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7536 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7539 savemsg_combo = gtk_combo_box_text_new_with_entry();
7540 compose->savemsg_checkbtn = savemsg_checkbtn;
7541 compose->savemsg_combo = savemsg_combo;
7542 gtk_widget_show(savemsg_combo);
7544 if (prefs_common.compose_save_to_history)
7545 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7546 prefs_common.compose_save_to_history);
7547 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7548 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7549 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7550 G_CALLBACK(compose_grab_focus_cb), compose);
7551 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7552 if (compose->account->set_sent_folder || prefs_common.savemsg)
7553 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), TRUE);
7554 else
7555 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), FALSE);
7556 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), TRUE);
7557 folderidentifier = folder_item_get_identifier(account_get_special_folder
7558 (compose->account, F_OUTBOX));
7559 compose_set_save_to(compose, folderidentifier);
7560 g_free(folderidentifier);
7563 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7564 gtk_widget_show(savemsg_select);
7565 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7566 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7567 G_CALLBACK(compose_savemsg_select_cb),
7568 compose);
7570 return table;
7573 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7575 FolderItem *dest;
7576 gchar * path;
7578 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7579 _("Select folder to save message to"));
7580 if (!dest) return;
7582 path = folder_item_get_identifier(dest);
7584 compose_set_save_to(compose, path);
7585 g_free(path);
7588 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7589 GdkAtom clip, GtkTextIter *insert_place);
7592 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7593 Compose *compose)
7595 gint prev_autowrap;
7596 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7597 #if USE_ENCHANT
7598 if (event->button == 3) {
7599 GtkTextIter iter;
7600 GtkTextIter sel_start, sel_end;
7601 gboolean stuff_selected;
7602 gint x, y;
7603 /* move the cursor to allow GtkAspell to check the word
7604 * under the mouse */
7605 if (event->x && event->y) {
7606 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7607 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7608 &x, &y);
7609 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7610 &iter, x, y);
7611 } else {
7612 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7613 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7615 /* get selection */
7616 stuff_selected = gtk_text_buffer_get_selection_bounds(
7617 buffer,
7618 &sel_start, &sel_end);
7620 gtk_text_buffer_place_cursor (buffer, &iter);
7621 /* reselect stuff */
7622 if (stuff_selected
7623 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7624 gtk_text_buffer_select_range(buffer,
7625 &sel_start, &sel_end);
7627 return FALSE; /* pass the event so that the right-click goes through */
7629 #endif
7630 if (event->button == 2) {
7631 GtkTextIter iter;
7632 gint x, y;
7633 BLOCK_WRAP();
7635 /* get the middle-click position to paste at the correct place */
7636 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7637 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7638 &x, &y);
7639 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7640 &iter, x, y);
7642 entry_paste_clipboard(compose, text,
7643 prefs_common.linewrap_pastes,
7644 GDK_SELECTION_PRIMARY, &iter);
7645 UNBLOCK_WRAP();
7646 return TRUE;
7648 return FALSE;
7651 #if USE_ENCHANT
7652 static void compose_spell_menu_changed(void *data)
7654 Compose *compose = (Compose *)data;
7655 GSList *items;
7656 GtkWidget *menuitem;
7657 GtkWidget *parent_item;
7658 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7659 GSList *spell_menu;
7661 if (compose->gtkaspell == NULL)
7662 return;
7664 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7665 "/Menu/Spelling/Options");
7667 /* setting the submenu removes /Spelling/Options from the factory
7668 * so we need to save it */
7670 if (parent_item == NULL) {
7671 parent_item = compose->aspell_options_menu;
7672 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7673 } else
7674 compose->aspell_options_menu = parent_item;
7676 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7678 spell_menu = g_slist_reverse(spell_menu);
7679 for (items = spell_menu;
7680 items; items = items->next) {
7681 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7682 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7683 gtk_widget_show(GTK_WIDGET(menuitem));
7685 g_slist_free(spell_menu);
7687 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7688 gtk_widget_show(parent_item);
7691 static void compose_dict_changed(void *data)
7693 Compose *compose = (Compose *) data;
7695 if(!compose->gtkaspell)
7696 return;
7697 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7698 return;
7700 gtkaspell_highlight_all(compose->gtkaspell);
7701 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7703 #endif
7705 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7707 Compose *compose = (Compose *)data;
7708 GdkEventButton event;
7710 event.button = 3;
7711 event.time = gtk_get_current_event_time();
7712 event.x = 0;
7713 event.y = 0;
7715 return text_clicked(compose->text, &event, compose);
7718 static gboolean compose_force_window_origin = TRUE;
7719 static Compose *compose_create(PrefsAccount *account,
7720 FolderItem *folder,
7721 ComposeMode mode,
7722 gboolean batch)
7724 Compose *compose;
7725 GtkWidget *window;
7726 GtkWidget *vbox;
7727 GtkWidget *menubar;
7728 GtkWidget *handlebox;
7730 GtkWidget *notebook;
7732 GtkWidget *attach_hbox;
7733 GtkWidget *attach_lab1;
7734 GtkWidget *attach_lab2;
7736 GtkWidget *vbox2;
7738 GtkWidget *label;
7739 GtkWidget *subject_hbox;
7740 GtkWidget *subject_frame;
7741 GtkWidget *subject_entry;
7742 GtkWidget *subject;
7743 GtkWidget *paned;
7745 GtkWidget *edit_vbox;
7746 GtkWidget *ruler_hbox;
7747 GtkWidget *ruler;
7748 GtkWidget *scrolledwin;
7749 GtkWidget *text;
7750 GtkTextBuffer *buffer;
7751 GtkClipboard *clipboard;
7753 UndoMain *undostruct;
7755 GtkWidget *popupmenu;
7756 GtkWidget *tmpl_menu;
7757 GtkActionGroup *action_group = NULL;
7759 #if USE_ENCHANT
7760 GtkAspell * gtkaspell = NULL;
7761 #endif
7763 static GdkGeometry geometry;
7765 cm_return_val_if_fail(account != NULL, NULL);
7767 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7768 &default_header_bgcolor);
7769 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7770 &default_header_color);
7772 debug_print("Creating compose window...\n");
7773 compose = g_new0(Compose, 1);
7775 compose->batch = batch;
7776 compose->account = account;
7777 compose->folder = folder;
7779 compose->mutex = cm_mutex_new();
7780 compose->set_cursor_pos = -1;
7782 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7784 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7785 gtk_widget_set_size_request(window, prefs_common.compose_width,
7786 prefs_common.compose_height);
7788 if (!geometry.max_width) {
7789 geometry.max_width = gdk_screen_width();
7790 geometry.max_height = gdk_screen_height();
7793 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7794 &geometry, GDK_HINT_MAX_SIZE);
7795 if (!geometry.min_width) {
7796 geometry.min_width = 600;
7797 geometry.min_height = 440;
7799 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7800 &geometry, GDK_HINT_MIN_SIZE);
7802 #ifndef GENERIC_UMPC
7803 if (compose_force_window_origin)
7804 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7805 prefs_common.compose_y);
7806 #endif
7807 g_signal_connect(G_OBJECT(window), "delete_event",
7808 G_CALLBACK(compose_delete_cb), compose);
7809 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7810 gtk_widget_realize(window);
7812 gtkut_widget_set_composer_icon(window);
7814 vbox = gtk_vbox_new(FALSE, 0);
7815 gtk_container_add(GTK_CONTAINER(window), vbox);
7817 compose->ui_manager = gtk_ui_manager_new();
7818 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7819 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7820 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7821 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7822 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7823 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7824 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7825 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7826 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7827 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7829 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7831 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7833 #ifdef USE_ENCHANT
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7835 #endif
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7837 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7840 /* Compose menu */
7841 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7847 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7848 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7855 /* Edit menu */
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7885 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7899 #if USE_ENCHANT
7900 /* Spelling menu */
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7907 #endif
7909 /* Options menu */
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7935 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7937 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7940 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7941 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7942 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7943 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7946 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)
7947 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)
7948 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7950 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7952 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7953 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)
7954 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)
7956 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7958 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7959 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)
7960 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7962 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7963 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)
7964 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7966 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7968 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7969 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)
7970 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7971 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7972 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7973 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7975 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7976 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)
7977 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)
7978 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7979 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7981 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7982 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7983 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7984 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7985 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7986 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7988 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7989 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7990 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)
7992 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7993 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7994 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7995 /* phew. */
7997 /* Tools menu */
7998 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7999 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
8000 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
8001 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8002 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
8003 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8005 /* Help menu */
8006 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
8008 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
8009 gtk_widget_show_all(menubar);
8011 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
8012 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
8014 if (prefs_common.toolbar_detachable) {
8015 handlebox = gtk_handle_box_new();
8016 } else {
8017 handlebox = gtk_hbox_new(FALSE, 0);
8019 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8021 gtk_widget_realize(handlebox);
8022 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8023 (gpointer)compose);
8025 vbox2 = gtk_vbox_new(FALSE, 2);
8026 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8027 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8029 /* Notebook */
8030 notebook = gtk_notebook_new();
8031 gtk_widget_show(notebook);
8033 /* header labels and entries */
8034 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8035 compose_create_header(compose),
8036 gtk_label_new_with_mnemonic(_("Hea_der")));
8037 /* attachment list */
8038 attach_hbox = gtk_hbox_new(FALSE, 0);
8039 gtk_widget_show(attach_hbox);
8041 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8042 gtk_widget_show(attach_lab1);
8043 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8045 attach_lab2 = gtk_label_new("");
8046 gtk_widget_show(attach_lab2);
8047 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8049 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8050 compose_create_attach(compose),
8051 attach_hbox);
8052 /* Others Tab */
8053 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8054 compose_create_others(compose),
8055 gtk_label_new_with_mnemonic(_("Othe_rs")));
8057 /* Subject */
8058 subject_hbox = gtk_hbox_new(FALSE, 0);
8059 gtk_widget_show(subject_hbox);
8061 subject_frame = gtk_frame_new(NULL);
8062 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8063 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8064 gtk_widget_show(subject_frame);
8066 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8067 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8068 gtk_widget_show(subject);
8070 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8071 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8072 gtk_widget_show(label);
8074 #ifdef USE_ENCHANT
8075 subject_entry = claws_spell_entry_new();
8076 #else
8077 subject_entry = gtk_entry_new();
8078 #endif
8079 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8080 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8081 G_CALLBACK(compose_grab_focus_cb), compose);
8082 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8083 gtk_widget_show(subject_entry);
8084 compose->subject_entry = subject_entry;
8085 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8087 edit_vbox = gtk_vbox_new(FALSE, 0);
8089 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8091 /* ruler */
8092 ruler_hbox = gtk_hbox_new(FALSE, 0);
8093 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8095 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8096 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8097 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8098 BORDER_WIDTH);
8100 /* text widget */
8101 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8102 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8103 GTK_POLICY_AUTOMATIC,
8104 GTK_POLICY_AUTOMATIC);
8105 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8106 GTK_SHADOW_IN);
8107 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8109 text = gtk_text_view_new();
8110 if (prefs_common.show_compose_margin) {
8111 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8112 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8114 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8115 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8116 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8117 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8118 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8120 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8121 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8122 G_CALLBACK(compose_edit_size_alloc),
8123 ruler);
8124 g_signal_connect(G_OBJECT(buffer), "changed",
8125 G_CALLBACK(compose_changed_cb), compose);
8126 g_signal_connect(G_OBJECT(text), "grab_focus",
8127 G_CALLBACK(compose_grab_focus_cb), compose);
8128 g_signal_connect(G_OBJECT(buffer), "insert_text",
8129 G_CALLBACK(text_inserted), compose);
8130 g_signal_connect(G_OBJECT(text), "button_press_event",
8131 G_CALLBACK(text_clicked), compose);
8132 g_signal_connect(G_OBJECT(text), "popup-menu",
8133 G_CALLBACK(compose_popup_menu), compose);
8134 g_signal_connect(G_OBJECT(subject_entry), "changed",
8135 G_CALLBACK(compose_changed_cb), compose);
8136 g_signal_connect(G_OBJECT(subject_entry), "activate",
8137 G_CALLBACK(compose_subject_entry_activated), compose);
8139 /* drag and drop */
8140 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8141 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8142 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8143 g_signal_connect(G_OBJECT(text), "drag_data_received",
8144 G_CALLBACK(compose_insert_drag_received_cb),
8145 compose);
8146 g_signal_connect(G_OBJECT(text), "drag-drop",
8147 G_CALLBACK(compose_drag_drop),
8148 compose);
8149 g_signal_connect(G_OBJECT(text), "key-press-event",
8150 G_CALLBACK(completion_set_focus_to_subject),
8151 compose);
8152 gtk_widget_show_all(vbox);
8154 /* pane between attach clist and text */
8155 paned = gtk_vpaned_new();
8156 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8157 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8158 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8159 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8160 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8161 G_CALLBACK(compose_notebook_size_alloc), paned);
8163 gtk_widget_show_all(paned);
8166 if (prefs_common.textfont) {
8167 PangoFontDescription *font_desc;
8169 font_desc = pango_font_description_from_string
8170 (prefs_common.textfont);
8171 if (font_desc) {
8172 gtk_widget_modify_font(text, font_desc);
8173 pango_font_description_free(font_desc);
8177 gtk_action_group_add_actions(action_group, compose_popup_entries,
8178 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8179 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8180 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8181 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8182 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8183 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8184 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8186 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8188 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8189 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8190 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8192 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8194 undostruct = undo_init(text);
8195 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8196 compose);
8198 address_completion_start(window);
8200 compose->window = window;
8201 compose->vbox = vbox;
8202 compose->menubar = menubar;
8203 compose->handlebox = handlebox;
8205 compose->vbox2 = vbox2;
8207 compose->paned = paned;
8209 compose->attach_label = attach_lab2;
8211 compose->notebook = notebook;
8212 compose->edit_vbox = edit_vbox;
8213 compose->ruler_hbox = ruler_hbox;
8214 compose->ruler = ruler;
8215 compose->scrolledwin = scrolledwin;
8216 compose->text = text;
8218 compose->focused_editable = NULL;
8220 compose->popupmenu = popupmenu;
8222 compose->tmpl_menu = tmpl_menu;
8224 compose->mode = mode;
8225 compose->rmode = mode;
8227 compose->targetinfo = NULL;
8228 compose->replyinfo = NULL;
8229 compose->fwdinfo = NULL;
8231 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8232 g_str_equal, (GDestroyNotify) g_free, NULL);
8234 compose->replyto = NULL;
8235 compose->cc = NULL;
8236 compose->bcc = NULL;
8237 compose->followup_to = NULL;
8239 compose->ml_post = NULL;
8241 compose->inreplyto = NULL;
8242 compose->references = NULL;
8243 compose->msgid = NULL;
8244 compose->boundary = NULL;
8246 compose->autowrap = prefs_common.autowrap;
8247 compose->autoindent = prefs_common.auto_indent;
8248 compose->use_signing = FALSE;
8249 compose->use_encryption = FALSE;
8250 compose->privacy_system = NULL;
8251 compose->encdata = NULL;
8253 compose->modified = FALSE;
8255 compose->return_receipt = FALSE;
8257 compose->to_list = NULL;
8258 compose->newsgroup_list = NULL;
8260 compose->undostruct = undostruct;
8262 compose->sig_str = NULL;
8264 compose->exteditor_file = NULL;
8265 compose->exteditor_pid = -1;
8266 compose->exteditor_tag = -1;
8267 compose->exteditor_socket = NULL;
8268 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8270 compose->folder_update_callback_id =
8271 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8272 compose_update_folder_hook,
8273 (gpointer) compose);
8275 #if USE_ENCHANT
8276 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8277 if (mode != COMPOSE_REDIRECT) {
8278 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8279 strcmp(prefs_common.dictionary, "")) {
8280 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8281 prefs_common.alt_dictionary,
8282 conv_get_locale_charset_str(),
8283 prefs_common.color[COL_MISSPELLED],
8284 prefs_common.check_while_typing,
8285 prefs_common.recheck_when_changing_dict,
8286 prefs_common.use_alternate,
8287 prefs_common.use_both_dicts,
8288 GTK_TEXT_VIEW(text),
8289 GTK_WINDOW(compose->window),
8290 compose_dict_changed,
8291 compose_spell_menu_changed,
8292 compose);
8293 if (!gtkaspell) {
8294 alertpanel_error(_("Spell checker could not "
8295 "be started.\n%s"),
8296 gtkaspell_checkers_strerror());
8297 gtkaspell_checkers_reset_error();
8298 } else {
8299 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8303 compose->gtkaspell = gtkaspell;
8304 compose_spell_menu_changed(compose);
8305 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8306 #endif
8308 compose_select_account(compose, account, TRUE);
8310 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8311 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8313 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8314 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8316 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8317 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8319 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8320 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8322 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8323 if (account->protocol != A_NNTP)
8324 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8325 prefs_common_translated_header_name("To:"));
8326 else
8327 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8328 prefs_common_translated_header_name("Newsgroups:"));
8330 #ifndef USE_ALT_ADDRBOOK
8331 addressbook_set_target_compose(compose);
8332 #endif
8333 if (mode != COMPOSE_REDIRECT)
8334 compose_set_template_menu(compose);
8335 else {
8336 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8339 compose_list = g_list_append(compose_list, compose);
8341 if (!prefs_common.show_ruler)
8342 gtk_widget_hide(ruler_hbox);
8344 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8346 /* Priority */
8347 compose->priority = PRIORITY_NORMAL;
8348 compose_update_priority_menu_item(compose);
8350 compose_set_out_encoding(compose);
8352 /* Actions menu */
8353 compose_update_actions_menu(compose);
8355 /* Privacy Systems menu */
8356 compose_update_privacy_systems_menu(compose);
8357 compose_activate_privacy_system(compose, account, TRUE);
8359 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8360 if (batch) {
8361 gtk_widget_realize(window);
8362 } else {
8363 gtk_widget_show(window);
8366 return compose;
8369 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8371 GList *accounts;
8372 GtkWidget *hbox;
8373 GtkWidget *optmenu;
8374 GtkWidget *optmenubox;
8375 GtkWidget *fromlabel;
8376 GtkListStore *menu;
8377 GtkTreeIter iter;
8378 GtkWidget *from_name = NULL;
8380 gint num = 0, def_menu = 0;
8382 accounts = account_get_list();
8383 cm_return_val_if_fail(accounts != NULL, NULL);
8385 optmenubox = gtk_event_box_new();
8386 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8387 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8389 hbox = gtk_hbox_new(FALSE, 4);
8390 from_name = gtk_entry_new();
8392 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8393 G_CALLBACK(compose_grab_focus_cb), compose);
8394 g_signal_connect_after(G_OBJECT(from_name), "activate",
8395 G_CALLBACK(from_name_activate_cb), optmenu);
8397 for (; accounts != NULL; accounts = accounts->next, num++) {
8398 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8399 gchar *name, *from = NULL;
8401 if (ac == compose->account) def_menu = num;
8403 name = g_markup_printf_escaped("<i>%s</i>",
8404 ac->account_name);
8406 if (ac == compose->account) {
8407 if (ac->name && *ac->name) {
8408 gchar *buf;
8409 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8410 from = g_strdup_printf("%s <%s>",
8411 buf, ac->address);
8412 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8413 } else {
8414 from = g_strdup_printf("%s",
8415 ac->address);
8416 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8418 if (cur_account != compose->account) {
8419 gtk_widget_modify_base(
8420 GTK_WIDGET(from_name),
8421 GTK_STATE_NORMAL, &default_header_bgcolor);
8422 gtk_widget_modify_text(
8423 GTK_WIDGET(from_name),
8424 GTK_STATE_NORMAL, &default_header_color);
8427 COMBOBOX_ADD(menu, name, ac->account_id);
8428 g_free(name);
8429 g_free(from);
8432 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8434 g_signal_connect(G_OBJECT(optmenu), "changed",
8435 G_CALLBACK(account_activated),
8436 compose);
8437 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8438 G_CALLBACK(compose_entry_popup_extend),
8439 NULL);
8441 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8442 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8444 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8445 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8446 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8448 /* Putting only the GtkEntry into focus chain of parent hbox causes
8449 * the account selector combobox next to it to be unreachable when
8450 * navigating widgets in GtkTable with up/down arrow keys.
8451 * Note: gtk_widget_set_can_focus() was not enough. */
8452 GList *l = NULL;
8453 l = g_list_prepend(l, from_name);
8454 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8455 g_list_free(l);
8457 CLAWS_SET_TIP(optmenubox,
8458 _("Account to use for this email"));
8459 CLAWS_SET_TIP(from_name,
8460 _("Sender address to be used"));
8462 compose->account_combo = optmenu;
8463 compose->from_name = from_name;
8465 return hbox;
8468 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8470 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8471 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8472 Compose *compose = (Compose *) data;
8473 if (active) {
8474 compose->priority = value;
8478 static void compose_reply_change_mode(Compose *compose,
8479 ComposeMode action)
8481 gboolean was_modified = compose->modified;
8483 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8485 cm_return_if_fail(compose->replyinfo != NULL);
8487 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8488 ml = TRUE;
8489 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8490 followup = TRUE;
8491 if (action == COMPOSE_REPLY_TO_ALL)
8492 all = TRUE;
8493 if (action == COMPOSE_REPLY_TO_SENDER)
8494 sender = TRUE;
8495 if (action == COMPOSE_REPLY_TO_LIST)
8496 ml = TRUE;
8498 compose_remove_header_entries(compose);
8499 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8500 if (compose->account->set_autocc && compose->account->auto_cc)
8501 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8503 if (compose->account->set_autobcc && compose->account->auto_bcc)
8504 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8506 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8507 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8508 compose_show_first_last_header(compose, TRUE);
8509 compose->modified = was_modified;
8510 compose_set_title(compose);
8513 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8515 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8516 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8517 Compose *compose = (Compose *) data;
8519 if (active)
8520 compose_reply_change_mode(compose, value);
8523 static void compose_update_priority_menu_item(Compose * compose)
8525 GtkWidget *menuitem = NULL;
8526 switch (compose->priority) {
8527 case PRIORITY_HIGHEST:
8528 menuitem = gtk_ui_manager_get_widget
8529 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8530 break;
8531 case PRIORITY_HIGH:
8532 menuitem = gtk_ui_manager_get_widget
8533 (compose->ui_manager, "/Menu/Options/Priority/High");
8534 break;
8535 case PRIORITY_NORMAL:
8536 menuitem = gtk_ui_manager_get_widget
8537 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8538 break;
8539 case PRIORITY_LOW:
8540 menuitem = gtk_ui_manager_get_widget
8541 (compose->ui_manager, "/Menu/Options/Priority/Low");
8542 break;
8543 case PRIORITY_LOWEST:
8544 menuitem = gtk_ui_manager_get_widget
8545 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8546 break;
8548 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8551 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8553 Compose *compose = (Compose *) data;
8554 gchar *systemid;
8555 gboolean can_sign = FALSE, can_encrypt = FALSE;
8557 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8559 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8560 return;
8562 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8563 g_free(compose->privacy_system);
8564 compose->privacy_system = NULL;
8565 g_free(compose->encdata);
8566 compose->encdata = NULL;
8567 if (systemid != NULL) {
8568 compose->privacy_system = g_strdup(systemid);
8570 can_sign = privacy_system_can_sign(systemid);
8571 can_encrypt = privacy_system_can_encrypt(systemid);
8574 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8576 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8577 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8578 if (compose->toolbar->privacy_sign_btn != NULL) {
8579 gtk_widget_set_sensitive(
8580 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8581 can_sign);
8582 gtk_toggle_tool_button_set_active(
8583 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8584 can_sign ? compose->use_signing : FALSE);
8586 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8587 gtk_widget_set_sensitive(
8588 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8589 can_encrypt);
8590 gtk_toggle_tool_button_set_active(
8591 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8592 can_encrypt ? compose->use_encryption : FALSE);
8596 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8598 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8599 GtkWidget *menuitem = NULL;
8600 GList *children, *amenu;
8601 gboolean can_sign = FALSE, can_encrypt = FALSE;
8602 gboolean found = FALSE;
8604 if (compose->privacy_system != NULL) {
8605 gchar *systemid;
8606 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8607 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8608 cm_return_if_fail(menuitem != NULL);
8610 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8611 amenu = children;
8612 menuitem = NULL;
8613 while (amenu != NULL) {
8614 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8615 if (systemid != NULL) {
8616 if (strcmp(systemid, compose->privacy_system) == 0 &&
8617 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8618 menuitem = GTK_WIDGET(amenu->data);
8620 can_sign = privacy_system_can_sign(systemid);
8621 can_encrypt = privacy_system_can_encrypt(systemid);
8622 found = TRUE;
8623 break;
8625 } else if (strlen(compose->privacy_system) == 0 &&
8626 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8627 menuitem = GTK_WIDGET(amenu->data);
8629 can_sign = FALSE;
8630 can_encrypt = FALSE;
8631 found = TRUE;
8632 break;
8635 amenu = amenu->next;
8637 g_list_free(children);
8638 if (menuitem != NULL)
8639 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8641 if (warn && !found && strlen(compose->privacy_system)) {
8642 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8643 "will not be able to sign or encrypt this message."),
8644 compose->privacy_system);
8648 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8649 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8650 if (compose->toolbar->privacy_sign_btn != NULL) {
8651 gtk_widget_set_sensitive(
8652 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8653 can_sign);
8655 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8656 gtk_widget_set_sensitive(
8657 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8658 can_encrypt);
8662 static void compose_set_out_encoding(Compose *compose)
8664 CharSet out_encoding;
8665 const gchar *branch = NULL;
8666 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8668 switch(out_encoding) {
8669 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8670 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8671 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8672 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8673 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8674 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8675 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8676 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8677 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8678 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8679 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8680 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8681 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8682 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8683 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8684 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8685 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8686 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8687 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8688 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8689 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8690 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8691 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8692 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8693 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8694 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8695 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8696 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8697 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8698 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8699 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8700 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8701 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8702 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8704 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8707 static void compose_set_template_menu(Compose *compose)
8709 GSList *tmpl_list, *cur;
8710 GtkWidget *menu;
8711 GtkWidget *item;
8713 tmpl_list = template_get_config();
8715 menu = gtk_menu_new();
8717 gtk_menu_set_accel_group (GTK_MENU (menu),
8718 gtk_ui_manager_get_accel_group(compose->ui_manager));
8719 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8720 Template *tmpl = (Template *)cur->data;
8721 gchar *accel_path = NULL;
8722 item = gtk_menu_item_new_with_label(tmpl->name);
8723 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8724 g_signal_connect(G_OBJECT(item), "activate",
8725 G_CALLBACK(compose_template_activate_cb),
8726 compose);
8727 g_object_set_data(G_OBJECT(item), "template", tmpl);
8728 gtk_widget_show(item);
8729 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8730 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8731 g_free(accel_path);
8734 gtk_widget_show(menu);
8735 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8738 void compose_update_actions_menu(Compose *compose)
8740 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8743 static void compose_update_privacy_systems_menu(Compose *compose)
8745 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8746 GSList *systems, *cur;
8747 GtkWidget *widget;
8748 GtkWidget *system_none;
8749 GSList *group;
8750 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8751 GtkWidget *privacy_menu = gtk_menu_new();
8753 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8754 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8756 g_signal_connect(G_OBJECT(system_none), "activate",
8757 G_CALLBACK(compose_set_privacy_system_cb), compose);
8759 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8760 gtk_widget_show(system_none);
8762 systems = privacy_get_system_ids();
8763 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8764 gchar *systemid = cur->data;
8766 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8767 widget = gtk_radio_menu_item_new_with_label(group,
8768 privacy_system_get_name(systemid));
8769 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8770 g_strdup(systemid), g_free);
8771 g_signal_connect(G_OBJECT(widget), "activate",
8772 G_CALLBACK(compose_set_privacy_system_cb), compose);
8774 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8775 gtk_widget_show(widget);
8776 g_free(systemid);
8778 g_slist_free(systems);
8779 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8780 gtk_widget_show_all(privacy_menu);
8781 gtk_widget_show_all(privacy_menuitem);
8784 void compose_reflect_prefs_all(void)
8786 GList *cur;
8787 Compose *compose;
8789 for (cur = compose_list; cur != NULL; cur = cur->next) {
8790 compose = (Compose *)cur->data;
8791 compose_set_template_menu(compose);
8795 void compose_reflect_prefs_pixmap_theme(void)
8797 GList *cur;
8798 Compose *compose;
8800 for (cur = compose_list; cur != NULL; cur = cur->next) {
8801 compose = (Compose *)cur->data;
8802 toolbar_update(TOOLBAR_COMPOSE, compose);
8806 static const gchar *compose_quote_char_from_context(Compose *compose)
8808 const gchar *qmark = NULL;
8810 cm_return_val_if_fail(compose != NULL, NULL);
8812 switch (compose->mode) {
8813 /* use forward-specific quote char */
8814 case COMPOSE_FORWARD:
8815 case COMPOSE_FORWARD_AS_ATTACH:
8816 case COMPOSE_FORWARD_INLINE:
8817 if (compose->folder && compose->folder->prefs &&
8818 compose->folder->prefs->forward_with_format)
8819 qmark = compose->folder->prefs->forward_quotemark;
8820 else if (compose->account->forward_with_format)
8821 qmark = compose->account->forward_quotemark;
8822 else
8823 qmark = prefs_common.fw_quotemark;
8824 break;
8826 /* use reply-specific quote char in all other modes */
8827 default:
8828 if (compose->folder && compose->folder->prefs &&
8829 compose->folder->prefs->reply_with_format)
8830 qmark = compose->folder->prefs->reply_quotemark;
8831 else if (compose->account->reply_with_format)
8832 qmark = compose->account->reply_quotemark;
8833 else
8834 qmark = prefs_common.quotemark;
8835 break;
8838 if (qmark == NULL || *qmark == '\0')
8839 qmark = "> ";
8841 return qmark;
8844 static void compose_template_apply(Compose *compose, Template *tmpl,
8845 gboolean replace)
8847 GtkTextView *text;
8848 GtkTextBuffer *buffer;
8849 GtkTextMark *mark;
8850 GtkTextIter iter;
8851 const gchar *qmark;
8852 gchar *parsed_str = NULL;
8853 gint cursor_pos = 0;
8854 const gchar *err_msg = _("The body of the template has an error at line %d.");
8855 if (!tmpl) return;
8857 /* process the body */
8859 text = GTK_TEXT_VIEW(compose->text);
8860 buffer = gtk_text_view_get_buffer(text);
8862 if (tmpl->value) {
8863 qmark = compose_quote_char_from_context(compose);
8865 if (compose->replyinfo != NULL) {
8867 if (replace)
8868 gtk_text_buffer_set_text(buffer, "", -1);
8869 mark = gtk_text_buffer_get_insert(buffer);
8870 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8872 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8873 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8875 } else if (compose->fwdinfo != NULL) {
8877 if (replace)
8878 gtk_text_buffer_set_text(buffer, "", -1);
8879 mark = gtk_text_buffer_get_insert(buffer);
8880 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8882 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8883 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8885 } else {
8886 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8888 GtkTextIter start, end;
8889 gchar *tmp = NULL;
8891 gtk_text_buffer_get_start_iter(buffer, &start);
8892 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8893 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8895 /* clear the buffer now */
8896 if (replace)
8897 gtk_text_buffer_set_text(buffer, "", -1);
8899 parsed_str = compose_quote_fmt(compose, dummyinfo,
8900 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8901 procmsg_msginfo_free( &dummyinfo );
8903 g_free( tmp );
8905 } else {
8906 if (replace)
8907 gtk_text_buffer_set_text(buffer, "", -1);
8908 mark = gtk_text_buffer_get_insert(buffer);
8909 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8912 if (replace && parsed_str && compose->account->auto_sig)
8913 compose_insert_sig(compose, FALSE);
8915 if (replace && parsed_str) {
8916 gtk_text_buffer_get_start_iter(buffer, &iter);
8917 gtk_text_buffer_place_cursor(buffer, &iter);
8920 if (parsed_str) {
8921 cursor_pos = quote_fmt_get_cursor_pos();
8922 compose->set_cursor_pos = cursor_pos;
8923 if (cursor_pos == -1)
8924 cursor_pos = 0;
8925 gtk_text_buffer_get_start_iter(buffer, &iter);
8926 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8927 gtk_text_buffer_place_cursor(buffer, &iter);
8930 /* process the other fields */
8932 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8933 compose_template_apply_fields(compose, tmpl);
8934 quote_fmt_reset_vartable();
8935 quote_fmtlex_destroy();
8937 compose_changed_cb(NULL, compose);
8939 #ifdef USE_ENCHANT
8940 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8941 gtkaspell_highlight_all(compose->gtkaspell);
8942 #endif
8945 static void compose_template_apply_fields_error(const gchar *header)
8947 gchar *tr;
8948 gchar *text;
8950 tr = g_strdup(C_("'%s' stands for a header name",
8951 "Template '%s' format error."));
8952 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8953 alertpanel_error("%s", text);
8955 g_free(text);
8956 g_free(tr);
8959 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8961 MsgInfo* dummyinfo = NULL;
8962 MsgInfo *msginfo = NULL;
8963 gchar *buf = NULL;
8965 if (compose->replyinfo != NULL)
8966 msginfo = compose->replyinfo;
8967 else if (compose->fwdinfo != NULL)
8968 msginfo = compose->fwdinfo;
8969 else {
8970 dummyinfo = compose_msginfo_new_from_compose(compose);
8971 msginfo = dummyinfo;
8974 if (tmpl->from && *tmpl->from != '\0') {
8975 #ifdef USE_ENCHANT
8976 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8977 compose->gtkaspell);
8978 #else
8979 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8980 #endif
8981 quote_fmt_scan_string(tmpl->from);
8982 quote_fmt_parse();
8984 buf = quote_fmt_get_buffer();
8985 if (buf == NULL) {
8986 compose_template_apply_fields_error("From");
8987 } else {
8988 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8991 quote_fmt_reset_vartable();
8992 quote_fmtlex_destroy();
8995 if (tmpl->to && *tmpl->to != '\0') {
8996 #ifdef USE_ENCHANT
8997 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8998 compose->gtkaspell);
8999 #else
9000 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9001 #endif
9002 quote_fmt_scan_string(tmpl->to);
9003 quote_fmt_parse();
9005 buf = quote_fmt_get_buffer();
9006 if (buf == NULL) {
9007 compose_template_apply_fields_error("To");
9008 } else {
9009 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
9012 quote_fmt_reset_vartable();
9013 quote_fmtlex_destroy();
9016 if (tmpl->cc && *tmpl->cc != '\0') {
9017 #ifdef USE_ENCHANT
9018 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9019 compose->gtkaspell);
9020 #else
9021 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9022 #endif
9023 quote_fmt_scan_string(tmpl->cc);
9024 quote_fmt_parse();
9026 buf = quote_fmt_get_buffer();
9027 if (buf == NULL) {
9028 compose_template_apply_fields_error("Cc");
9029 } else {
9030 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9033 quote_fmt_reset_vartable();
9034 quote_fmtlex_destroy();
9037 if (tmpl->bcc && *tmpl->bcc != '\0') {
9038 #ifdef USE_ENCHANT
9039 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9040 compose->gtkaspell);
9041 #else
9042 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9043 #endif
9044 quote_fmt_scan_string(tmpl->bcc);
9045 quote_fmt_parse();
9047 buf = quote_fmt_get_buffer();
9048 if (buf == NULL) {
9049 compose_template_apply_fields_error("Bcc");
9050 } else {
9051 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9054 quote_fmt_reset_vartable();
9055 quote_fmtlex_destroy();
9058 if (tmpl->replyto && *tmpl->replyto != '\0') {
9059 #ifdef USE_ENCHANT
9060 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9061 compose->gtkaspell);
9062 #else
9063 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9064 #endif
9065 quote_fmt_scan_string(tmpl->replyto);
9066 quote_fmt_parse();
9068 buf = quote_fmt_get_buffer();
9069 if (buf == NULL) {
9070 compose_template_apply_fields_error("Reply-To");
9071 } else {
9072 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9075 quote_fmt_reset_vartable();
9076 quote_fmtlex_destroy();
9079 /* process the subject */
9080 if (tmpl->subject && *tmpl->subject != '\0') {
9081 #ifdef USE_ENCHANT
9082 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9083 compose->gtkaspell);
9084 #else
9085 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9086 #endif
9087 quote_fmt_scan_string(tmpl->subject);
9088 quote_fmt_parse();
9090 buf = quote_fmt_get_buffer();
9091 if (buf == NULL) {
9092 compose_template_apply_fields_error("Subject");
9093 } else {
9094 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9097 quote_fmt_reset_vartable();
9098 quote_fmtlex_destroy();
9101 procmsg_msginfo_free( &dummyinfo );
9104 static void compose_destroy(Compose *compose)
9106 GtkAllocation allocation;
9107 GtkTextBuffer *buffer;
9108 GtkClipboard *clipboard;
9110 compose_list = g_list_remove(compose_list, compose);
9112 #ifdef USE_LDAP
9113 gboolean enable = TRUE;
9114 g_slist_foreach(compose->passworded_ldap_servers,
9115 _ldap_srv_func, &enable);
9116 g_slist_free(compose->passworded_ldap_servers);
9117 #endif
9119 if (compose->updating) {
9120 debug_print("danger, not destroying anything now\n");
9121 compose->deferred_destroy = TRUE;
9122 return;
9125 /* NOTE: address_completion_end() does nothing with the window
9126 * however this may change. */
9127 address_completion_end(compose->window);
9129 slist_free_strings_full(compose->to_list);
9130 slist_free_strings_full(compose->newsgroup_list);
9131 slist_free_strings_full(compose->header_list);
9133 slist_free_strings_full(extra_headers);
9134 extra_headers = NULL;
9136 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9138 g_hash_table_destroy(compose->email_hashtable);
9140 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9141 compose->folder_update_callback_id);
9143 procmsg_msginfo_free(&(compose->targetinfo));
9144 procmsg_msginfo_free(&(compose->replyinfo));
9145 procmsg_msginfo_free(&(compose->fwdinfo));
9147 g_free(compose->replyto);
9148 g_free(compose->cc);
9149 g_free(compose->bcc);
9150 g_free(compose->newsgroups);
9151 g_free(compose->followup_to);
9153 g_free(compose->ml_post);
9155 g_free(compose->inreplyto);
9156 g_free(compose->references);
9157 g_free(compose->msgid);
9158 g_free(compose->boundary);
9160 g_free(compose->redirect_filename);
9161 if (compose->undostruct)
9162 undo_destroy(compose->undostruct);
9164 g_free(compose->sig_str);
9166 g_free(compose->exteditor_file);
9168 g_free(compose->orig_charset);
9170 g_free(compose->privacy_system);
9171 g_free(compose->encdata);
9173 #ifndef USE_ALT_ADDRBOOK
9174 if (addressbook_get_target_compose() == compose)
9175 addressbook_set_target_compose(NULL);
9176 #endif
9177 #if USE_ENCHANT
9178 if (compose->gtkaspell) {
9179 gtkaspell_delete(compose->gtkaspell);
9180 compose->gtkaspell = NULL;
9182 #endif
9184 if (!compose->batch) {
9185 gtk_widget_get_allocation(compose->window, &allocation);
9186 prefs_common.compose_width = allocation.width;
9187 prefs_common.compose_height = allocation.height;
9190 if (!gtk_widget_get_parent(compose->paned))
9191 gtk_widget_destroy(compose->paned);
9192 gtk_widget_destroy(compose->popupmenu);
9194 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9195 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9196 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9198 message_search_close(compose);
9199 gtk_widget_destroy(compose->window);
9200 toolbar_destroy(compose->toolbar);
9201 g_free(compose->toolbar);
9202 cm_mutex_free(compose->mutex);
9203 g_free(compose);
9206 static void compose_attach_info_free(AttachInfo *ainfo)
9208 g_free(ainfo->file);
9209 g_free(ainfo->content_type);
9210 g_free(ainfo->name);
9211 g_free(ainfo->charset);
9212 g_free(ainfo);
9215 static void compose_attach_update_label(Compose *compose)
9217 GtkTreeIter iter;
9218 gint i = 1;
9219 gchar *text;
9220 GtkTreeModel *model;
9221 goffset total_size;
9222 AttachInfo *ainfo;
9224 if (compose == NULL)
9225 return;
9227 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9228 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9229 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9230 return;
9233 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9234 total_size = ainfo->size;
9235 while(gtk_tree_model_iter_next(model, &iter)) {
9236 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9237 total_size += ainfo->size;
9238 i++;
9240 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9241 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9242 g_free(text);
9245 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9247 Compose *compose = (Compose *)data;
9248 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9249 GtkTreeSelection *selection;
9250 GList *sel, *cur;
9251 GtkTreeModel *model;
9253 selection = gtk_tree_view_get_selection(tree_view);
9254 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9255 cm_return_if_fail(sel);
9257 for (cur = sel; cur != NULL; cur = cur->next) {
9258 GtkTreePath *path = cur->data;
9259 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9260 (model, cur->data);
9261 cur->data = ref;
9262 gtk_tree_path_free(path);
9265 for (cur = sel; cur != NULL; cur = cur->next) {
9266 GtkTreeRowReference *ref = cur->data;
9267 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9268 GtkTreeIter iter;
9270 if (gtk_tree_model_get_iter(model, &iter, path))
9271 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9273 gtk_tree_path_free(path);
9274 gtk_tree_row_reference_free(ref);
9277 g_list_free(sel);
9278 compose_attach_update_label(compose);
9281 static struct _AttachProperty
9283 GtkWidget *window;
9284 GtkWidget *mimetype_entry;
9285 GtkWidget *encoding_optmenu;
9286 GtkWidget *path_entry;
9287 GtkWidget *filename_entry;
9288 GtkWidget *ok_btn;
9289 GtkWidget *cancel_btn;
9290 } attach_prop;
9292 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9294 gtk_tree_path_free((GtkTreePath *)ptr);
9297 static void compose_attach_property(GtkAction *action, gpointer data)
9299 Compose *compose = (Compose *)data;
9300 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9301 AttachInfo *ainfo;
9302 GtkComboBox *optmenu;
9303 GtkTreeSelection *selection;
9304 GList *sel;
9305 GtkTreeModel *model;
9306 GtkTreeIter iter;
9307 GtkTreePath *path;
9308 static gboolean cancelled;
9310 /* only if one selected */
9311 selection = gtk_tree_view_get_selection(tree_view);
9312 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9313 return;
9315 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9316 cm_return_if_fail(sel);
9318 path = (GtkTreePath *) sel->data;
9319 gtk_tree_model_get_iter(model, &iter, path);
9320 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9322 if (!ainfo) {
9323 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9324 g_list_free(sel);
9325 return;
9327 g_list_free(sel);
9329 if (!attach_prop.window)
9330 compose_attach_property_create(&cancelled);
9331 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9332 gtk_widget_grab_focus(attach_prop.ok_btn);
9333 gtk_widget_show(attach_prop.window);
9334 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9335 GTK_WINDOW(compose->window));
9337 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9338 if (ainfo->encoding == ENC_UNKNOWN)
9339 combobox_select_by_data(optmenu, ENC_BASE64);
9340 else
9341 combobox_select_by_data(optmenu, ainfo->encoding);
9343 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9344 ainfo->content_type ? ainfo->content_type : "");
9345 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9346 ainfo->file ? ainfo->file : "");
9347 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9348 ainfo->name ? ainfo->name : "");
9350 for (;;) {
9351 const gchar *entry_text;
9352 gchar *text;
9353 gchar *cnttype = NULL;
9354 gchar *file = NULL;
9355 off_t size = 0;
9357 cancelled = FALSE;
9358 gtk_main();
9360 gtk_widget_hide(attach_prop.window);
9361 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9363 if (cancelled)
9364 break;
9366 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9367 if (*entry_text != '\0') {
9368 gchar *p;
9370 text = g_strstrip(g_strdup(entry_text));
9371 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9372 cnttype = g_strdup(text);
9373 g_free(text);
9374 } else {
9375 alertpanel_error(_("Invalid MIME type."));
9376 g_free(text);
9377 continue;
9381 ainfo->encoding = combobox_get_active_data(optmenu);
9383 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9384 if (*entry_text != '\0') {
9385 if (is_file_exist(entry_text) &&
9386 (size = get_file_size(entry_text)) > 0)
9387 file = g_strdup(entry_text);
9388 else {
9389 alertpanel_error
9390 (_("File doesn't exist or is empty."));
9391 g_free(cnttype);
9392 continue;
9396 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9397 if (*entry_text != '\0') {
9398 g_free(ainfo->name);
9399 ainfo->name = g_strdup(entry_text);
9402 if (cnttype) {
9403 g_free(ainfo->content_type);
9404 ainfo->content_type = cnttype;
9406 if (file) {
9407 g_free(ainfo->file);
9408 ainfo->file = file;
9410 if (size)
9411 ainfo->size = (goffset)size;
9413 /* update tree store */
9414 text = to_human_readable(ainfo->size);
9415 gtk_tree_model_get_iter(model, &iter, path);
9416 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9417 COL_MIMETYPE, ainfo->content_type,
9418 COL_SIZE, text,
9419 COL_NAME, ainfo->name,
9420 COL_CHARSET, ainfo->charset,
9421 -1);
9423 break;
9426 gtk_tree_path_free(path);
9429 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9431 label = gtk_label_new(str); \
9432 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9433 GTK_FILL, 0, 0, 0); \
9434 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9436 entry = gtk_entry_new(); \
9437 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9438 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9441 static void compose_attach_property_create(gboolean *cancelled)
9443 GtkWidget *window;
9444 GtkWidget *vbox;
9445 GtkWidget *table;
9446 GtkWidget *label;
9447 GtkWidget *mimetype_entry;
9448 GtkWidget *hbox;
9449 GtkWidget *optmenu;
9450 GtkListStore *optmenu_menu;
9451 GtkWidget *path_entry;
9452 GtkWidget *filename_entry;
9453 GtkWidget *hbbox;
9454 GtkWidget *ok_btn;
9455 GtkWidget *cancel_btn;
9456 GList *mime_type_list, *strlist;
9457 GtkTreeIter iter;
9459 debug_print("Creating attach_property window...\n");
9461 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9462 gtk_widget_set_size_request(window, 480, -1);
9463 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9464 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9465 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9466 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
9467 g_signal_connect(G_OBJECT(window), "delete_event",
9468 G_CALLBACK(attach_property_delete_event),
9469 cancelled);
9470 g_signal_connect(G_OBJECT(window), "key_press_event",
9471 G_CALLBACK(attach_property_key_pressed),
9472 cancelled);
9474 vbox = gtk_vbox_new(FALSE, 8);
9475 gtk_container_add(GTK_CONTAINER(window), vbox);
9477 table = gtk_table_new(4, 2, FALSE);
9478 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9479 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9480 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9482 label = gtk_label_new(_("MIME type"));
9483 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9484 GTK_FILL, 0, 0, 0);
9485 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9486 mimetype_entry = gtk_combo_box_text_new_with_entry();
9487 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9488 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9490 /* stuff with list */
9491 mime_type_list = procmime_get_mime_type_list();
9492 strlist = NULL;
9493 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9494 MimeType *type = (MimeType *) mime_type_list->data;
9495 gchar *tmp;
9497 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9499 if (g_list_find_custom(strlist, tmp, (GCompareFunc)g_strcmp0))
9500 g_free(tmp);
9501 else
9502 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9503 (GCompareFunc)g_strcmp0);
9506 for (mime_type_list = strlist; mime_type_list != NULL;
9507 mime_type_list = mime_type_list->next) {
9508 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9509 g_free(mime_type_list->data);
9511 g_list_free(strlist);
9512 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9513 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9515 label = gtk_label_new(_("Encoding"));
9516 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9517 GTK_FILL, 0, 0, 0);
9518 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9520 hbox = gtk_hbox_new(FALSE, 0);
9521 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9522 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9524 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9525 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9527 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9528 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9529 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9530 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9531 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9533 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9535 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9536 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9538 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9539 &ok_btn, GTK_STOCK_OK,
9540 NULL, NULL);
9541 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9542 gtk_widget_grab_default(ok_btn);
9544 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9545 G_CALLBACK(attach_property_ok),
9546 cancelled);
9547 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9548 G_CALLBACK(attach_property_cancel),
9549 cancelled);
9551 gtk_widget_show_all(vbox);
9553 attach_prop.window = window;
9554 attach_prop.mimetype_entry = mimetype_entry;
9555 attach_prop.encoding_optmenu = optmenu;
9556 attach_prop.path_entry = path_entry;
9557 attach_prop.filename_entry = filename_entry;
9558 attach_prop.ok_btn = ok_btn;
9559 attach_prop.cancel_btn = cancel_btn;
9562 #undef SET_LABEL_AND_ENTRY
9564 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9566 *cancelled = FALSE;
9567 gtk_main_quit();
9570 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9572 *cancelled = TRUE;
9573 gtk_main_quit();
9576 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9577 gboolean *cancelled)
9579 *cancelled = TRUE;
9580 gtk_main_quit();
9582 return TRUE;
9585 static gboolean attach_property_key_pressed(GtkWidget *widget,
9586 GdkEventKey *event,
9587 gboolean *cancelled)
9589 if (event && event->keyval == GDK_KEY_Escape) {
9590 *cancelled = TRUE;
9591 gtk_main_quit();
9593 if (event && event->keyval == GDK_KEY_Return) {
9594 *cancelled = FALSE;
9595 gtk_main_quit();
9596 return TRUE;
9598 return FALSE;
9601 static void compose_exec_ext_editor(Compose *compose)
9603 #ifdef G_OS_UNIX
9604 gchar *tmp;
9605 GtkWidget *socket;
9606 GdkNativeWindow socket_wid = 0;
9607 pid_t pid;
9608 gint pipe_fds[2];
9610 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9611 G_DIR_SEPARATOR, compose);
9613 if (compose_get_ext_editor_uses_socket()) {
9614 /* Only allow one socket */
9615 if (compose->exteditor_socket != NULL) {
9616 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9617 /* Move the focus off of the socket */
9618 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9620 g_free(tmp);
9621 return;
9623 /* Create the receiving GtkSocket */
9624 socket = gtk_socket_new ();
9625 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9626 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9627 compose);
9628 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9629 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9630 /* Realize the socket so that we can use its ID */
9631 gtk_widget_realize(socket);
9632 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9633 compose->exteditor_socket = socket;
9636 if (pipe(pipe_fds) < 0) {
9637 perror("pipe");
9638 g_free(tmp);
9639 return;
9642 if ((pid = fork()) < 0) {
9643 perror("fork");
9644 g_free(tmp);
9645 return;
9648 if (pid != 0) {
9649 /* close the write side of the pipe */
9650 close(pipe_fds[1]);
9652 compose->exteditor_file = g_strdup(tmp);
9653 compose->exteditor_pid = pid;
9655 compose_set_ext_editor_sensitive(compose, FALSE);
9657 #ifndef G_OS_WIN32
9658 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9659 #else
9660 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9661 #endif
9662 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9663 G_IO_IN,
9664 compose_input_cb,
9665 compose);
9666 } else { /* process-monitoring process */
9667 pid_t pid_ed;
9669 if (setpgid(0, 0))
9670 perror("setpgid");
9672 /* close the read side of the pipe */
9673 close(pipe_fds[0]);
9675 if (compose_write_body_to_file(compose, tmp) < 0) {
9676 fd_write_all(pipe_fds[1], "2\n", 2);
9677 _exit(1);
9680 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9681 if (pid_ed < 0) {
9682 fd_write_all(pipe_fds[1], "1\n", 2);
9683 _exit(1);
9686 /* wait until editor is terminated */
9687 waitpid(pid_ed, NULL, 0);
9689 fd_write_all(pipe_fds[1], "0\n", 2);
9691 close(pipe_fds[1]);
9692 _exit(0);
9695 g_free(tmp);
9696 #endif /* G_OS_UNIX */
9699 static gboolean compose_can_autosave(Compose *compose)
9701 if (compose->privacy_system && compose->use_encryption)
9702 return prefs_common.autosave && prefs_common.autosave_encrypted;
9703 else
9704 return prefs_common.autosave;
9707 #ifdef G_OS_UNIX
9708 static gboolean compose_get_ext_editor_cmd_valid()
9710 gboolean has_s = FALSE;
9711 gboolean has_w = FALSE;
9712 const gchar *p = prefs_common_get_ext_editor_cmd();
9713 if (!p)
9714 return FALSE;
9715 while ((p = strchr(p, '%'))) {
9716 p++;
9717 if (*p == 's') {
9718 if (has_s)
9719 return FALSE;
9720 has_s = TRUE;
9721 } else if (*p == 'w') {
9722 if (has_w)
9723 return FALSE;
9724 has_w = TRUE;
9725 } else {
9726 return FALSE;
9729 return TRUE;
9732 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9734 gchar *buf;
9735 gchar *p, *s;
9736 gchar **cmdline;
9737 pid_t pid;
9739 cm_return_val_if_fail(file != NULL, -1);
9741 if ((pid = fork()) < 0) {
9742 perror("fork");
9743 return -1;
9746 if (pid != 0) return pid;
9748 /* grandchild process */
9750 if (setpgid(0, getppid()))
9751 perror("setpgid");
9753 if (compose_get_ext_editor_cmd_valid()) {
9754 if (compose_get_ext_editor_uses_socket()) {
9755 p = g_strdup(prefs_common_get_ext_editor_cmd());
9756 s = strstr(p, "%w");
9757 s[1] = 'u';
9758 if (strstr(p, "%s") < s)
9759 buf = g_strdup_printf(p, file, socket_wid);
9760 else
9761 buf = g_strdup_printf(p, socket_wid, file);
9762 g_free(p);
9763 } else {
9764 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9766 } else {
9767 if (prefs_common_get_ext_editor_cmd())
9768 g_warning("External editor command-line is invalid: '%s'",
9769 prefs_common_get_ext_editor_cmd());
9770 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9773 cmdline = strsplit_with_quote(buf, " ", 0);
9774 g_free(buf);
9775 execvp(cmdline[0], cmdline);
9777 perror("execvp");
9778 g_strfreev(cmdline);
9780 _exit(1);
9783 static gboolean compose_ext_editor_kill(Compose *compose)
9785 pid_t pgid = compose->exteditor_pid * -1;
9786 gint ret;
9788 ret = kill(pgid, 0);
9790 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9791 AlertValue val;
9792 gchar *msg;
9794 msg = g_strdup_printf
9795 (_("The external editor is still working.\n"
9796 "Force terminating the process?\n"
9797 "process group id: %d"), -pgid);
9798 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9799 NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9800 ALERT_WARNING);
9802 g_free(msg);
9804 if (val == G_ALERTALTERNATE) {
9805 g_source_remove(compose->exteditor_tag);
9806 g_io_channel_shutdown(compose->exteditor_ch,
9807 FALSE, NULL);
9808 g_io_channel_unref(compose->exteditor_ch);
9810 if (kill(pgid, SIGTERM) < 0) perror("kill");
9811 waitpid(compose->exteditor_pid, NULL, 0);
9813 g_warning("Terminated process group id: %d. "
9814 "Temporary file: %s", -pgid, compose->exteditor_file);
9816 compose_set_ext_editor_sensitive(compose, TRUE);
9818 g_free(compose->exteditor_file);
9819 compose->exteditor_file = NULL;
9820 compose->exteditor_pid = -1;
9821 compose->exteditor_ch = NULL;
9822 compose->exteditor_tag = -1;
9823 } else
9824 return FALSE;
9827 return TRUE;
9830 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9831 gpointer data)
9833 gchar buf[3] = "3";
9834 Compose *compose = (Compose *)data;
9835 gsize bytes_read;
9837 debug_print("Compose: input from monitoring process\n");
9839 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9840 bytes_read = 0;
9841 buf[0] = '\0';
9844 g_io_channel_shutdown(source, FALSE, NULL);
9845 g_io_channel_unref(source);
9847 waitpid(compose->exteditor_pid, NULL, 0);
9849 if (buf[0] == '0') { /* success */
9850 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9851 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9852 GtkTextIter start, end;
9853 gchar *chars;
9855 gtk_text_buffer_set_text(buffer, "", -1);
9856 compose_insert_file(compose, compose->exteditor_file);
9857 compose_changed_cb(NULL, compose);
9859 /* Check if we should save the draft or not */
9860 if (compose_can_autosave(compose))
9861 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9863 if (claws_unlink(compose->exteditor_file) < 0)
9864 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9866 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9867 gtk_text_buffer_get_start_iter(buffer, &start);
9868 gtk_text_buffer_get_end_iter(buffer, &end);
9869 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9870 if (chars && strlen(chars) > 0)
9871 compose->modified = TRUE;
9872 g_free(chars);
9873 } else if (buf[0] == '1') { /* failed */
9874 g_warning("Couldn't exec external editor");
9875 if (claws_unlink(compose->exteditor_file) < 0)
9876 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9877 } else if (buf[0] == '2') {
9878 g_warning("Couldn't write to file");
9879 } else if (buf[0] == '3') {
9880 g_warning("Pipe read failed");
9883 compose_set_ext_editor_sensitive(compose, TRUE);
9885 g_free(compose->exteditor_file);
9886 compose->exteditor_file = NULL;
9887 compose->exteditor_pid = -1;
9888 compose->exteditor_ch = NULL;
9889 compose->exteditor_tag = -1;
9890 if (compose->exteditor_socket) {
9891 gtk_widget_destroy(compose->exteditor_socket);
9892 compose->exteditor_socket = NULL;
9896 return FALSE;
9899 static char *ext_editor_menu_entries[] = {
9900 "Menu/Message/Send",
9901 "Menu/Message/SendLater",
9902 "Menu/Message/InsertFile",
9903 "Menu/Message/InsertSig",
9904 "Menu/Message/ReplaceSig",
9905 "Menu/Message/Save",
9906 "Menu/Message/Print",
9907 "Menu/Edit",
9908 #if USE_ENCHANT
9909 "Menu/Spelling",
9910 #endif
9911 "Menu/Tools/ShowRuler",
9912 "Menu/Tools/Actions",
9913 "Menu/Help",
9914 NULL
9917 static void compose_set_ext_editor_sensitive(Compose *compose,
9918 gboolean sensitive)
9920 int i;
9922 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9923 cm_menu_set_sensitive_full(compose->ui_manager,
9924 ext_editor_menu_entries[i], sensitive);
9927 if (compose_get_ext_editor_uses_socket()) {
9928 if (sensitive) {
9929 if (compose->exteditor_socket)
9930 gtk_widget_hide(compose->exteditor_socket);
9931 gtk_widget_show(compose->scrolledwin);
9932 if (prefs_common.show_ruler)
9933 gtk_widget_show(compose->ruler_hbox);
9934 /* Fix the focus, as it doesn't go anywhere when the
9935 * socket is hidden or destroyed */
9936 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9937 } else {
9938 g_assert (compose->exteditor_socket != NULL);
9939 /* Fix the focus, as it doesn't go anywhere when the
9940 * edit box is hidden */
9941 if (gtk_widget_is_focus(compose->text))
9942 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9943 gtk_widget_hide(compose->scrolledwin);
9944 gtk_widget_hide(compose->ruler_hbox);
9945 gtk_widget_show(compose->exteditor_socket);
9947 } else {
9948 gtk_widget_set_sensitive(compose->text, sensitive);
9950 if (compose->toolbar->send_btn)
9951 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9952 if (compose->toolbar->sendl_btn)
9953 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9954 if (compose->toolbar->draft_btn)
9955 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9956 if (compose->toolbar->insert_btn)
9957 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9958 if (compose->toolbar->sig_btn)
9959 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9960 if (compose->toolbar->exteditor_btn)
9961 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9962 if (compose->toolbar->linewrap_current_btn)
9963 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9964 if (compose->toolbar->linewrap_all_btn)
9965 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9968 static gboolean compose_get_ext_editor_uses_socket()
9970 return (prefs_common_get_ext_editor_cmd() &&
9971 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9974 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9976 compose->exteditor_socket = NULL;
9977 /* returning FALSE allows destruction of the socket */
9978 return FALSE;
9980 #endif /* G_OS_UNIX */
9983 * compose_undo_state_changed:
9985 * Change the sensivity of the menuentries undo and redo
9987 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9988 gint redo_state, gpointer data)
9990 Compose *compose = (Compose *)data;
9992 switch (undo_state) {
9993 case UNDO_STATE_TRUE:
9994 if (!undostruct->undo_state) {
9995 undostruct->undo_state = TRUE;
9996 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9998 break;
9999 case UNDO_STATE_FALSE:
10000 if (undostruct->undo_state) {
10001 undostruct->undo_state = FALSE;
10002 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", 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/Undo", undostruct->undo_state);
10009 break;
10010 default:
10011 g_warning("Undo state not recognized");
10012 break;
10015 switch (redo_state) {
10016 case UNDO_STATE_TRUE:
10017 if (!undostruct->redo_state) {
10018 undostruct->redo_state = TRUE;
10019 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
10021 break;
10022 case UNDO_STATE_FALSE:
10023 if (undostruct->redo_state) {
10024 undostruct->redo_state = FALSE;
10025 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
10027 break;
10028 case UNDO_STATE_UNCHANGED:
10029 break;
10030 case UNDO_STATE_REFRESH:
10031 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
10032 break;
10033 default:
10034 g_warning("Redo state not recognized");
10035 break;
10039 /* callback functions */
10041 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10042 GtkAllocation *allocation,
10043 GtkPaned *paned)
10045 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10048 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10049 * includes "non-client" (windows-izm) in calculation, so this calculation
10050 * may not be accurate.
10052 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10053 GtkAllocation *allocation,
10054 GtkSHRuler *shruler)
10056 if (prefs_common.show_ruler) {
10057 gint char_width = 0, char_height = 0;
10058 gint line_width_in_chars;
10060 gtkut_get_font_size(GTK_WIDGET(widget),
10061 &char_width, &char_height);
10062 line_width_in_chars =
10063 (allocation->width - allocation->x) / char_width;
10065 /* got the maximum */
10066 gtk_shruler_set_range(GTK_SHRULER(shruler),
10067 0.0, line_width_in_chars, 0);
10070 return TRUE;
10073 typedef struct {
10074 gchar *header;
10075 gchar *entry;
10076 ComposePrefType type;
10077 gboolean entry_marked;
10078 } HeaderEntryState;
10080 static void account_activated(GtkComboBox *optmenu, gpointer data)
10082 Compose *compose = (Compose *)data;
10084 PrefsAccount *ac;
10085 gchar *folderidentifier;
10086 gint account_id = 0;
10087 GtkTreeModel *menu;
10088 GtkTreeIter iter;
10089 GSList *list, *saved_list = NULL;
10090 HeaderEntryState *state;
10092 /* Get ID of active account in the combo box */
10093 menu = gtk_combo_box_get_model(optmenu);
10094 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10095 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10097 ac = account_find_from_id(account_id);
10098 cm_return_if_fail(ac != NULL);
10100 if (ac != compose->account) {
10101 compose_select_account(compose, ac, FALSE);
10103 for (list = compose->header_list; list; list = list->next) {
10104 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10106 if (hentry->type == PREF_ACCOUNT || !list->next) {
10107 compose_destroy_headerentry(compose, hentry);
10108 continue;
10110 state = g_malloc0(sizeof(HeaderEntryState));
10111 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10112 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10113 state->entry = gtk_editable_get_chars(
10114 GTK_EDITABLE(hentry->entry), 0, -1);
10115 state->type = hentry->type;
10117 saved_list = g_slist_append(saved_list, state);
10118 compose_destroy_headerentry(compose, hentry);
10121 compose->header_last = NULL;
10122 g_slist_free(compose->header_list);
10123 compose->header_list = NULL;
10124 compose->header_nextrow = 1;
10125 compose_create_header_entry(compose);
10127 if (ac->set_autocc && ac->auto_cc)
10128 compose_entry_append(compose, ac->auto_cc,
10129 COMPOSE_CC, PREF_ACCOUNT);
10130 if (ac->set_autobcc && ac->auto_bcc)
10131 compose_entry_append(compose, ac->auto_bcc,
10132 COMPOSE_BCC, PREF_ACCOUNT);
10133 if (ac->set_autoreplyto && ac->auto_replyto)
10134 compose_entry_append(compose, ac->auto_replyto,
10135 COMPOSE_REPLYTO, PREF_ACCOUNT);
10137 for (list = saved_list; list; list = list->next) {
10138 state = (HeaderEntryState *) list->data;
10140 compose_add_header_entry(compose, state->header,
10141 state->entry, state->type);
10143 g_free(state->header);
10144 g_free(state->entry);
10145 g_free(state);
10147 g_slist_free(saved_list);
10149 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10150 (ac->protocol == A_NNTP) ?
10151 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10154 /* Set message save folder */
10155 compose_set_save_to(compose, NULL);
10156 if (compose->folder && compose->folder->prefs && compose->folder->prefs->save_copy_to_folder) {
10157 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10158 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10159 folderidentifier = folder_item_get_identifier(compose->folder);
10160 compose_set_save_to(compose, folderidentifier);
10161 g_free(folderidentifier);
10162 } else if (account_get_special_folder(compose->account, F_OUTBOX)) {
10163 if (compose->account->set_sent_folder || prefs_common.savemsg)
10164 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10165 else
10166 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE);
10167 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10168 folderidentifier = folder_item_get_identifier(account_get_special_folder
10169 (compose->account, F_OUTBOX));
10170 compose_set_save_to(compose, folderidentifier);
10171 g_free(folderidentifier);
10175 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10176 GtkTreeViewColumn *column, Compose *compose)
10178 compose_attach_property(NULL, compose);
10181 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10182 gpointer data)
10184 Compose *compose = (Compose *)data;
10185 GtkTreeSelection *attach_selection;
10186 gint attach_nr_selected;
10187 GtkTreePath *path;
10189 if (!event) return FALSE;
10191 if (event->button == 3) {
10192 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10193 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10195 /* If no rows, or just one row is selected, right-click should
10196 * open menu relevant to the row being right-clicked on. We
10197 * achieve that by selecting the clicked row first. If more
10198 * than one row is selected, we shouldn't modify the selection,
10199 * as user may want to remove selected rows (attachments). */
10200 if (attach_nr_selected < 2) {
10201 gtk_tree_selection_unselect_all(attach_selection);
10202 attach_nr_selected = 0;
10203 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10204 event->x, event->y, &path, NULL, NULL, NULL);
10205 if (path != NULL) {
10206 gtk_tree_selection_select_path(attach_selection, path);
10207 gtk_tree_path_free(path);
10208 attach_nr_selected++;
10212 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10213 /* Properties menu item makes no sense with more than one row
10214 * selected, the properties dialog can only edit one attachment. */
10215 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10217 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10218 NULL, NULL, event->button, event->time);
10219 return TRUE;
10222 return FALSE;
10225 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10226 gpointer data)
10228 Compose *compose = (Compose *)data;
10230 if (!event) return FALSE;
10232 switch (event->keyval) {
10233 case GDK_KEY_Delete:
10234 compose_attach_remove_selected(NULL, compose);
10235 break;
10237 return FALSE;
10240 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10242 toolbar_comp_set_sensitive(compose, allow);
10243 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10244 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10245 #if USE_ENCHANT
10246 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10247 #endif
10248 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10249 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10250 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10252 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10256 static void compose_send_cb(GtkAction *action, gpointer data)
10258 Compose *compose = (Compose *)data;
10260 #ifdef G_OS_UNIX
10261 if (compose->exteditor_tag != -1) {
10262 debug_print("ignoring send: external editor still open\n");
10263 return;
10265 #endif
10266 if (prefs_common.work_offline &&
10267 !inc_offline_should_override(TRUE,
10268 _("Claws Mail needs network access in order "
10269 "to send this email.")))
10270 return;
10272 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10273 g_source_remove(compose->draft_timeout_tag);
10274 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10277 compose_send(compose);
10280 static void compose_send_later_cb(GtkAction *action, gpointer data)
10282 Compose *compose = (Compose *)data;
10283 ComposeQueueResult val;
10285 inc_lock();
10286 compose_allow_user_actions(compose, FALSE);
10287 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10288 compose_allow_user_actions(compose, TRUE);
10289 inc_unlock();
10291 if (val == COMPOSE_QUEUE_SUCCESS) {
10292 compose_close(compose);
10293 } else {
10294 _display_queue_error(val);
10297 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10300 #define DRAFTED_AT_EXIT "drafted_at_exit"
10301 static void compose_register_draft(MsgInfo *info)
10303 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10304 DRAFTED_AT_EXIT, NULL);
10305 FILE *fp = claws_fopen(filepath, "ab");
10307 if (fp) {
10308 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10309 info->msgnum);
10310 claws_fclose(fp);
10313 g_free(filepath);
10316 gboolean compose_draft (gpointer data, guint action)
10318 Compose *compose = (Compose *)data;
10319 FolderItem *draft;
10320 gchar *tmp;
10321 gchar *sheaders;
10322 gint msgnum;
10323 MsgFlags flag = {0, 0};
10324 static gboolean lock = FALSE;
10325 MsgInfo *newmsginfo;
10326 FILE *fp;
10327 gboolean target_locked = FALSE;
10328 gboolean err = FALSE;
10330 if (lock) return FALSE;
10332 if (compose->sending)
10333 return TRUE;
10335 draft = account_get_special_folder(compose->account, F_DRAFT);
10336 cm_return_val_if_fail(draft != NULL, FALSE);
10338 if (!g_mutex_trylock(compose->mutex)) {
10339 /* we don't want to lock the mutex once it's available,
10340 * because as the only other part of compose.c locking
10341 * it is compose_close - which means once unlocked,
10342 * the compose struct will be freed */
10343 debug_print("couldn't lock mutex, probably sending\n");
10344 return FALSE;
10347 lock = TRUE;
10349 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10350 G_DIR_SEPARATOR, compose);
10351 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10352 FILE_OP_ERROR(tmp, "claws_fopen");
10353 goto warn_err;
10356 /* chmod for security */
10357 if (change_file_mode_rw(fp, tmp) < 0) {
10358 FILE_OP_ERROR(tmp, "chmod");
10359 g_warning("can't change file mode");
10362 /* Save draft infos */
10363 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10364 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10366 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10367 gchar *savefolderid;
10369 savefolderid = compose_get_save_to(compose);
10370 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10371 g_free(savefolderid);
10373 if (compose->return_receipt) {
10374 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10376 if (compose->privacy_system) {
10377 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10378 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10379 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10382 /* Message-ID of message replying to */
10383 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10384 gchar *folderid = NULL;
10386 if (compose->replyinfo->folder)
10387 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10388 if (folderid == NULL)
10389 folderid = g_strdup("NULL");
10391 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10392 g_free(folderid);
10394 /* Message-ID of message forwarding to */
10395 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10396 gchar *folderid = NULL;
10398 if (compose->fwdinfo->folder)
10399 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10400 if (folderid == NULL)
10401 folderid = g_strdup("NULL");
10403 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10404 g_free(folderid);
10407 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10408 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10410 sheaders = compose_get_manual_headers_info(compose);
10411 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10412 g_free(sheaders);
10414 /* end of headers */
10415 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10417 if (err) {
10418 claws_fclose(fp);
10419 goto warn_err;
10422 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10423 claws_fclose(fp);
10424 goto warn_err;
10426 if (claws_safe_fclose(fp) == EOF) {
10427 goto warn_err;
10430 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10431 if (compose->targetinfo) {
10432 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10433 if (target_locked)
10434 flag.perm_flags |= MSG_LOCKED;
10436 flag.tmp_flags = MSG_DRAFT;
10438 folder_item_scan(draft);
10439 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10440 MsgInfo *tmpinfo = NULL;
10441 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10442 if (compose->msgid) {
10443 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10445 if (tmpinfo) {
10446 msgnum = tmpinfo->msgnum;
10447 procmsg_msginfo_free(&tmpinfo);
10448 debug_print("got draft msgnum %d from scanning\n", msgnum);
10449 } else {
10450 debug_print("didn't get draft msgnum after scanning\n");
10452 } else {
10453 debug_print("got draft msgnum %d from adding\n", msgnum);
10455 if (msgnum < 0) {
10456 warn_err:
10457 claws_unlink(tmp);
10458 g_free(tmp);
10459 if (action != COMPOSE_AUTO_SAVE) {
10460 if (action != COMPOSE_DRAFT_FOR_EXIT)
10461 alertpanel_error(_("Could not save draft."));
10462 else {
10463 AlertValue val;
10464 gtkut_window_popup(compose->window);
10465 val = alertpanel_full(_("Could not save draft"),
10466 _("Could not save draft.\n"
10467 "Do you want to cancel exit or discard this email?"),
10468 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10469 FALSE, NULL, ALERT_QUESTION);
10470 if (val == G_ALERTALTERNATE) {
10471 lock = FALSE;
10472 g_mutex_unlock(compose->mutex); /* must be done before closing */
10473 compose_close(compose);
10474 return TRUE;
10475 } else {
10476 lock = FALSE;
10477 g_mutex_unlock(compose->mutex); /* must be done before closing */
10478 return FALSE;
10482 goto unlock;
10484 g_free(tmp);
10486 if (compose->mode == COMPOSE_REEDIT) {
10487 compose_remove_reedit_target(compose, TRUE);
10490 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10492 if (newmsginfo) {
10493 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10494 if (target_locked)
10495 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10496 else
10497 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10498 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10499 procmsg_msginfo_set_flags(newmsginfo, 0,
10500 MSG_HAS_ATTACHMENT);
10502 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10503 compose_register_draft(newmsginfo);
10505 procmsg_msginfo_free(&newmsginfo);
10508 folder_item_scan(draft);
10510 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10511 lock = FALSE;
10512 g_mutex_unlock(compose->mutex); /* must be done before closing */
10513 compose_close(compose);
10514 return TRUE;
10515 } else {
10516 #ifdef G_OS_WIN32
10517 GFile *f;
10518 GFileInfo *fi;
10519 GTimeVal tv;
10520 GError *error = NULL;
10521 #else
10522 GStatBuf s;
10523 #endif
10524 gchar *path;
10525 goffset size, mtime;
10527 path = folder_item_fetch_msg(draft, msgnum);
10528 if (path == NULL) {
10529 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10530 goto unlock;
10532 #ifdef G_OS_WIN32
10533 f = g_file_new_for_path(path);
10534 fi = g_file_query_info(f, "standard::size,time::modified",
10535 G_FILE_QUERY_INFO_NONE, NULL, &error);
10536 if (error != NULL) {
10537 debug_print("couldn't query file info for '%s': %s\n",
10538 path, error->message);
10539 g_error_free(error);
10540 g_free(path);
10541 g_object_unref(f);
10542 goto unlock;
10544 size = g_file_info_get_size(fi);
10545 g_file_info_get_modification_time(fi, &tv);
10546 mtime = tv.tv_sec;
10547 g_object_unref(fi);
10548 g_object_unref(f);
10549 #else
10550 if (g_stat(path, &s) < 0) {
10551 FILE_OP_ERROR(path, "stat");
10552 g_free(path);
10553 goto unlock;
10555 size = s.st_size;
10556 mtime = s.st_mtime;
10557 #endif
10558 g_free(path);
10560 procmsg_msginfo_free(&(compose->targetinfo));
10561 compose->targetinfo = procmsg_msginfo_new();
10562 compose->targetinfo->msgnum = msgnum;
10563 compose->targetinfo->size = size;
10564 compose->targetinfo->mtime = mtime;
10565 compose->targetinfo->folder = draft;
10566 if (target_locked)
10567 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10568 compose->mode = COMPOSE_REEDIT;
10570 if (action == COMPOSE_AUTO_SAVE) {
10571 compose->autosaved_draft = compose->targetinfo;
10573 compose->modified = FALSE;
10574 compose_set_title(compose);
10576 unlock:
10577 lock = FALSE;
10578 g_mutex_unlock(compose->mutex);
10579 return TRUE;
10582 void compose_clear_exit_drafts(void)
10584 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10585 DRAFTED_AT_EXIT, NULL);
10586 if (is_file_exist(filepath))
10587 claws_unlink(filepath);
10589 g_free(filepath);
10592 void compose_reopen_exit_drafts(void)
10594 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10595 DRAFTED_AT_EXIT, NULL);
10596 FILE *fp = claws_fopen(filepath, "rb");
10597 gchar buf[1024];
10599 if (fp) {
10600 while (claws_fgets(buf, sizeof(buf), fp)) {
10601 gchar **parts = g_strsplit(buf, "\t", 2);
10602 const gchar *folder = parts[0];
10603 int msgnum = parts[1] ? atoi(parts[1]):-1;
10605 if (folder && *folder && msgnum > -1) {
10606 FolderItem *item = folder_find_item_from_identifier(folder);
10607 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10608 if (info)
10609 compose_reedit(info, FALSE);
10611 g_strfreev(parts);
10613 claws_fclose(fp);
10615 g_free(filepath);
10616 compose_clear_exit_drafts();
10619 static void compose_save_cb(GtkAction *action, gpointer data)
10621 Compose *compose = (Compose *)data;
10622 compose_draft(compose, COMPOSE_KEEP_EDITING);
10623 compose->rmode = COMPOSE_REEDIT;
10626 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10628 if (compose && file_list) {
10629 GList *tmp;
10631 for ( tmp = file_list; tmp; tmp = tmp->next) {
10632 gchar *file = (gchar *) tmp->data;
10633 gchar *utf8_filename = conv_filename_to_utf8(file);
10634 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10635 compose_changed_cb(NULL, compose);
10636 if (free_data) {
10637 g_free(file);
10638 tmp->data = NULL;
10640 g_free(utf8_filename);
10645 static void compose_attach_cb(GtkAction *action, gpointer data)
10647 Compose *compose = (Compose *)data;
10648 GList *file_list;
10650 if (compose->redirect_filename != NULL)
10651 return;
10653 /* Set focus_window properly, in case we were called via popup menu,
10654 * which unsets it (via focus_out_event callback on compose window). */
10655 manage_window_focus_in(compose->window, NULL, NULL);
10657 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10659 if (file_list) {
10660 compose_attach_from_list(compose, file_list, TRUE);
10661 g_list_free(file_list);
10665 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10667 Compose *compose = (Compose *)data;
10668 GList *file_list;
10669 gint files_inserted = 0;
10671 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10673 if (file_list) {
10674 GList *tmp;
10676 for ( tmp = file_list; tmp; tmp = tmp->next) {
10677 gchar *file = (gchar *) tmp->data;
10678 gchar *filedup = g_strdup(file);
10679 gchar *shortfile = g_path_get_basename(filedup);
10680 ComposeInsertResult res;
10681 /* insert the file if the file is short or if the user confirmed that
10682 he/she wants to insert the large file */
10683 res = compose_insert_file(compose, file);
10684 if (res == COMPOSE_INSERT_READ_ERROR) {
10685 alertpanel_error(_("File '%s' could not be read."), shortfile);
10686 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10687 alertpanel_error(_("File '%s' contained invalid characters\n"
10688 "for the current encoding, insertion may be incorrect."),
10689 shortfile);
10690 } else if (res == COMPOSE_INSERT_SUCCESS)
10691 files_inserted++;
10693 g_free(shortfile);
10694 g_free(filedup);
10695 g_free(file);
10697 g_list_free(file_list);
10700 #ifdef USE_ENCHANT
10701 if (files_inserted > 0 && compose->gtkaspell &&
10702 compose->gtkaspell->check_while_typing)
10703 gtkaspell_highlight_all(compose->gtkaspell);
10704 #endif
10707 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10709 Compose *compose = (Compose *)data;
10711 compose_insert_sig(compose, FALSE);
10714 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10716 Compose *compose = (Compose *)data;
10718 compose_insert_sig(compose, TRUE);
10721 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10722 gpointer data)
10724 gint x, y;
10725 Compose *compose = (Compose *)data;
10727 gtkut_widget_get_uposition(widget, &x, &y);
10728 if (!compose->batch) {
10729 prefs_common.compose_x = x;
10730 prefs_common.compose_y = y;
10732 if (compose->sending || compose->updating)
10733 return TRUE;
10734 compose_close_cb(NULL, compose);
10735 return TRUE;
10738 void compose_close_toolbar(Compose *compose)
10740 compose_close_cb(NULL, compose);
10743 static void compose_close_cb(GtkAction *action, gpointer data)
10745 Compose *compose = (Compose *)data;
10746 AlertValue val;
10748 #ifdef G_OS_UNIX
10749 if (compose->exteditor_tag != -1) {
10750 if (!compose_ext_editor_kill(compose))
10751 return;
10753 #endif
10755 if (compose->modified) {
10756 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10757 if (!g_mutex_trylock(compose->mutex)) {
10758 /* we don't want to lock the mutex once it's available,
10759 * because as the only other part of compose.c locking
10760 * it is compose_close - which means once unlocked,
10761 * the compose struct will be freed */
10762 debug_print("couldn't lock mutex, probably sending\n");
10763 return;
10765 if (!reedit || compose->folder->stype == F_DRAFT) {
10766 val = alertpanel(_("Discard message"),
10767 _("This message has been modified. Discard it?"),
10768 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10769 ALERTFOCUS_FIRST);
10770 } else {
10771 val = alertpanel(_("Save changes"),
10772 _("This message has been modified. Save the latest changes?"),
10773 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10774 ALERTFOCUS_SECOND);
10776 g_mutex_unlock(compose->mutex);
10777 switch (val) {
10778 case G_ALERTDEFAULT:
10779 if (compose_can_autosave(compose) && !reedit)
10780 compose_remove_draft(compose);
10781 break;
10782 case G_ALERTALTERNATE:
10783 compose_draft(data, COMPOSE_QUIT_EDITING);
10784 return;
10785 default:
10786 return;
10790 compose_close(compose);
10793 static void compose_print_cb(GtkAction *action, gpointer data)
10795 Compose *compose = (Compose *) data;
10797 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10798 if (compose->targetinfo)
10799 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10802 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10804 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10805 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10806 Compose *compose = (Compose *) data;
10808 if (active)
10809 compose->out_encoding = (CharSet)value;
10812 static void compose_address_cb(GtkAction *action, gpointer data)
10814 Compose *compose = (Compose *)data;
10816 #ifndef USE_ALT_ADDRBOOK
10817 addressbook_open(compose);
10818 #else
10819 GError* error = NULL;
10820 addressbook_connect_signals(compose);
10821 addressbook_dbus_open(TRUE, &error);
10822 if (error) {
10823 g_warning("%s", error->message);
10824 g_error_free(error);
10826 #endif
10829 static void about_show_cb(GtkAction *action, gpointer data)
10831 about_show();
10834 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10836 Compose *compose = (Compose *)data;
10837 Template *tmpl;
10838 gchar *msg;
10839 AlertValue val;
10841 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10842 cm_return_if_fail(tmpl != NULL);
10844 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10845 tmpl->name);
10846 val = alertpanel(_("Apply template"), msg,
10847 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10848 g_free(msg);
10850 if (val == G_ALERTDEFAULT)
10851 compose_template_apply(compose, tmpl, TRUE);
10852 else if (val == G_ALERTALTERNATE)
10853 compose_template_apply(compose, tmpl, FALSE);
10856 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10858 Compose *compose = (Compose *)data;
10860 #ifdef G_OS_UNIX
10861 if (compose->exteditor_tag != -1) {
10862 debug_print("ignoring open external editor: external editor still open\n");
10863 return;
10865 #endif
10866 compose_exec_ext_editor(compose);
10869 static void compose_undo_cb(GtkAction *action, gpointer data)
10871 Compose *compose = (Compose *)data;
10872 gboolean prev_autowrap = compose->autowrap;
10874 compose->autowrap = FALSE;
10875 undo_undo(compose->undostruct);
10876 compose->autowrap = prev_autowrap;
10879 static void compose_redo_cb(GtkAction *action, gpointer data)
10881 Compose *compose = (Compose *)data;
10882 gboolean prev_autowrap = compose->autowrap;
10884 compose->autowrap = FALSE;
10885 undo_redo(compose->undostruct);
10886 compose->autowrap = prev_autowrap;
10889 static void entry_cut_clipboard(GtkWidget *entry)
10891 if (GTK_IS_EDITABLE(entry))
10892 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10893 else if (GTK_IS_TEXT_VIEW(entry))
10894 gtk_text_buffer_cut_clipboard(
10895 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10896 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10897 TRUE);
10900 static void entry_copy_clipboard(GtkWidget *entry)
10902 if (GTK_IS_EDITABLE(entry))
10903 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10904 else if (GTK_IS_TEXT_VIEW(entry))
10905 gtk_text_buffer_copy_clipboard(
10906 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10907 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10910 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10911 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10913 if (GTK_IS_TEXT_VIEW(entry)) {
10914 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10915 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10916 GtkTextIter start_iter, end_iter;
10917 gint start, end;
10918 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10920 if (contents == NULL)
10921 return;
10923 /* we shouldn't delete the selection when middle-click-pasting, or we
10924 * can't mid-click-paste our own selection */
10925 if (clip != GDK_SELECTION_PRIMARY) {
10926 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10927 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10930 if (insert_place == NULL) {
10931 /* if insert_place isn't specified, insert at the cursor.
10932 * used for Ctrl-V pasting */
10933 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10934 start = gtk_text_iter_get_offset(&start_iter);
10935 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10936 } else {
10937 /* if insert_place is specified, paste here.
10938 * used for mid-click-pasting */
10939 start = gtk_text_iter_get_offset(insert_place);
10940 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10941 if (prefs_common.primary_paste_unselects)
10942 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10945 if (!wrap) {
10946 /* paste unwrapped: mark the paste so it's not wrapped later */
10947 end = start + strlen(contents);
10948 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10949 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10950 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10951 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10952 /* rewrap paragraph now (after a mid-click-paste) */
10953 mark_start = gtk_text_buffer_get_insert(buffer);
10954 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10955 gtk_text_iter_backward_char(&start_iter);
10956 compose_beautify_paragraph(compose, &start_iter, TRUE);
10958 } else if (GTK_IS_EDITABLE(entry))
10959 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10961 compose->modified = TRUE;
10964 static void entry_allsel(GtkWidget *entry)
10966 if (GTK_IS_EDITABLE(entry))
10967 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10968 else if (GTK_IS_TEXT_VIEW(entry)) {
10969 GtkTextIter startiter, enditer;
10970 GtkTextBuffer *textbuf;
10972 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10973 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10974 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10976 gtk_text_buffer_move_mark_by_name(textbuf,
10977 "selection_bound", &startiter);
10978 gtk_text_buffer_move_mark_by_name(textbuf,
10979 "insert", &enditer);
10983 static void compose_cut_cb(GtkAction *action, gpointer data)
10985 Compose *compose = (Compose *)data;
10986 if (compose->focused_editable
10987 #ifndef GENERIC_UMPC
10988 && gtk_widget_has_focus(compose->focused_editable)
10989 #endif
10991 entry_cut_clipboard(compose->focused_editable);
10994 static void compose_copy_cb(GtkAction *action, gpointer data)
10996 Compose *compose = (Compose *)data;
10997 if (compose->focused_editable
10998 #ifndef GENERIC_UMPC
10999 && gtk_widget_has_focus(compose->focused_editable)
11000 #endif
11002 entry_copy_clipboard(compose->focused_editable);
11005 static void compose_paste_cb(GtkAction *action, gpointer data)
11007 Compose *compose = (Compose *)data;
11008 gint prev_autowrap;
11009 GtkTextBuffer *buffer;
11010 BLOCK_WRAP();
11011 if (compose->focused_editable
11012 #ifndef GENERIC_UMPC
11013 && gtk_widget_has_focus(compose->focused_editable)
11014 #endif
11016 entry_paste_clipboard(compose, compose->focused_editable,
11017 prefs_common.linewrap_pastes,
11018 GDK_SELECTION_CLIPBOARD, NULL);
11019 UNBLOCK_WRAP();
11021 #ifdef USE_ENCHANT
11022 if (
11023 #ifndef GENERIC_UMPC
11024 gtk_widget_has_focus(compose->text) &&
11025 #endif
11026 compose->gtkaspell &&
11027 compose->gtkaspell->check_while_typing)
11028 gtkaspell_highlight_all(compose->gtkaspell);
11029 #endif
11032 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11034 Compose *compose = (Compose *)data;
11035 gint wrap_quote = prefs_common.linewrap_quote;
11036 if (compose->focused_editable
11037 #ifndef GENERIC_UMPC
11038 && gtk_widget_has_focus(compose->focused_editable)
11039 #endif
11041 /* let text_insert() (called directly or at a later time
11042 * after the gtk_editable_paste_clipboard) know that
11043 * text is to be inserted as a quotation. implemented
11044 * by using a simple refcount... */
11045 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11046 G_OBJECT(compose->focused_editable),
11047 "paste_as_quotation"));
11048 g_object_set_data(G_OBJECT(compose->focused_editable),
11049 "paste_as_quotation",
11050 GINT_TO_POINTER(paste_as_quotation + 1));
11051 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11052 entry_paste_clipboard(compose, compose->focused_editable,
11053 prefs_common.linewrap_pastes,
11054 GDK_SELECTION_CLIPBOARD, NULL);
11055 prefs_common.linewrap_quote = wrap_quote;
11059 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11061 Compose *compose = (Compose *)data;
11062 gint prev_autowrap;
11063 GtkTextBuffer *buffer;
11064 BLOCK_WRAP();
11065 if (compose->focused_editable
11066 #ifndef GENERIC_UMPC
11067 && gtk_widget_has_focus(compose->focused_editable)
11068 #endif
11070 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11071 GDK_SELECTION_CLIPBOARD, NULL);
11072 UNBLOCK_WRAP();
11074 #ifdef USE_ENCHANT
11075 if (
11076 #ifndef GENERIC_UMPC
11077 gtk_widget_has_focus(compose->text) &&
11078 #endif
11079 compose->gtkaspell &&
11080 compose->gtkaspell->check_while_typing)
11081 gtkaspell_highlight_all(compose->gtkaspell);
11082 #endif
11085 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11087 Compose *compose = (Compose *)data;
11088 gint prev_autowrap;
11089 GtkTextBuffer *buffer;
11090 BLOCK_WRAP();
11091 if (compose->focused_editable
11092 #ifndef GENERIC_UMPC
11093 && gtk_widget_has_focus(compose->focused_editable)
11094 #endif
11096 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11097 GDK_SELECTION_CLIPBOARD, NULL);
11098 UNBLOCK_WRAP();
11100 #ifdef USE_ENCHANT
11101 if (
11102 #ifndef GENERIC_UMPC
11103 gtk_widget_has_focus(compose->text) &&
11104 #endif
11105 compose->gtkaspell &&
11106 compose->gtkaspell->check_while_typing)
11107 gtkaspell_highlight_all(compose->gtkaspell);
11108 #endif
11111 static void compose_allsel_cb(GtkAction *action, gpointer data)
11113 Compose *compose = (Compose *)data;
11114 if (compose->focused_editable
11115 #ifndef GENERIC_UMPC
11116 && gtk_widget_has_focus(compose->focused_editable)
11117 #endif
11119 entry_allsel(compose->focused_editable);
11122 static void textview_move_beginning_of_line (GtkTextView *text)
11124 GtkTextBuffer *buffer;
11125 GtkTextMark *mark;
11126 GtkTextIter ins;
11128 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11130 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11131 mark = gtk_text_buffer_get_insert(buffer);
11132 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11133 gtk_text_iter_set_line_offset(&ins, 0);
11134 gtk_text_buffer_place_cursor(buffer, &ins);
11137 static void textview_move_forward_character (GtkTextView *text)
11139 GtkTextBuffer *buffer;
11140 GtkTextMark *mark;
11141 GtkTextIter ins;
11143 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11145 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11146 mark = gtk_text_buffer_get_insert(buffer);
11147 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11148 if (gtk_text_iter_forward_cursor_position(&ins))
11149 gtk_text_buffer_place_cursor(buffer, &ins);
11152 static void textview_move_backward_character (GtkTextView *text)
11154 GtkTextBuffer *buffer;
11155 GtkTextMark *mark;
11156 GtkTextIter ins;
11158 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11160 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11161 mark = gtk_text_buffer_get_insert(buffer);
11162 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11163 if (gtk_text_iter_backward_cursor_position(&ins))
11164 gtk_text_buffer_place_cursor(buffer, &ins);
11167 static void textview_move_forward_word (GtkTextView *text)
11169 GtkTextBuffer *buffer;
11170 GtkTextMark *mark;
11171 GtkTextIter ins;
11172 gint count;
11174 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11176 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11177 mark = gtk_text_buffer_get_insert(buffer);
11178 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11179 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11180 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11181 gtk_text_iter_backward_word_start(&ins);
11182 gtk_text_buffer_place_cursor(buffer, &ins);
11186 static void textview_move_backward_word (GtkTextView *text)
11188 GtkTextBuffer *buffer;
11189 GtkTextMark *mark;
11190 GtkTextIter ins;
11192 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11194 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11195 mark = gtk_text_buffer_get_insert(buffer);
11196 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11197 if (gtk_text_iter_backward_word_starts(&ins, 1))
11198 gtk_text_buffer_place_cursor(buffer, &ins);
11201 static void textview_move_end_of_line (GtkTextView *text)
11203 GtkTextBuffer *buffer;
11204 GtkTextMark *mark;
11205 GtkTextIter ins;
11207 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11209 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11210 mark = gtk_text_buffer_get_insert(buffer);
11211 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11212 if (gtk_text_iter_forward_to_line_end(&ins))
11213 gtk_text_buffer_place_cursor(buffer, &ins);
11216 static void textview_move_next_line (GtkTextView *text)
11218 GtkTextBuffer *buffer;
11219 GtkTextMark *mark;
11220 GtkTextIter ins;
11221 gint offset;
11223 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11225 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11226 mark = gtk_text_buffer_get_insert(buffer);
11227 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11228 offset = gtk_text_iter_get_line_offset(&ins);
11229 if (gtk_text_iter_forward_line(&ins)) {
11230 gtk_text_iter_set_line_offset(&ins, offset);
11231 gtk_text_buffer_place_cursor(buffer, &ins);
11235 static void textview_move_previous_line (GtkTextView *text)
11237 GtkTextBuffer *buffer;
11238 GtkTextMark *mark;
11239 GtkTextIter ins;
11240 gint offset;
11242 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11244 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11245 mark = gtk_text_buffer_get_insert(buffer);
11246 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11247 offset = gtk_text_iter_get_line_offset(&ins);
11248 if (gtk_text_iter_backward_line(&ins)) {
11249 gtk_text_iter_set_line_offset(&ins, offset);
11250 gtk_text_buffer_place_cursor(buffer, &ins);
11254 static void textview_delete_forward_character (GtkTextView *text)
11256 GtkTextBuffer *buffer;
11257 GtkTextMark *mark;
11258 GtkTextIter ins, end_iter;
11260 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11262 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11263 mark = gtk_text_buffer_get_insert(buffer);
11264 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11265 end_iter = ins;
11266 if (gtk_text_iter_forward_char(&end_iter)) {
11267 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11271 static void textview_delete_backward_character (GtkTextView *text)
11273 GtkTextBuffer *buffer;
11274 GtkTextMark *mark;
11275 GtkTextIter ins, end_iter;
11277 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11279 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11280 mark = gtk_text_buffer_get_insert(buffer);
11281 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11282 end_iter = ins;
11283 if (gtk_text_iter_backward_char(&end_iter)) {
11284 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11288 static void textview_delete_forward_word (GtkTextView *text)
11290 GtkTextBuffer *buffer;
11291 GtkTextMark *mark;
11292 GtkTextIter ins, end_iter;
11294 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11296 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11297 mark = gtk_text_buffer_get_insert(buffer);
11298 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11299 end_iter = ins;
11300 if (gtk_text_iter_forward_word_end(&end_iter)) {
11301 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11305 static void textview_delete_backward_word (GtkTextView *text)
11307 GtkTextBuffer *buffer;
11308 GtkTextMark *mark;
11309 GtkTextIter ins, end_iter;
11311 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11313 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11314 mark = gtk_text_buffer_get_insert(buffer);
11315 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11316 end_iter = ins;
11317 if (gtk_text_iter_backward_word_start(&end_iter)) {
11318 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11322 static void textview_delete_line (GtkTextView *text)
11324 GtkTextBuffer *buffer;
11325 GtkTextMark *mark;
11326 GtkTextIter ins, start_iter, end_iter;
11328 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11330 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11331 mark = gtk_text_buffer_get_insert(buffer);
11332 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11334 start_iter = ins;
11335 gtk_text_iter_set_line_offset(&start_iter, 0);
11337 end_iter = ins;
11338 if (gtk_text_iter_ends_line(&end_iter)){
11339 if (!gtk_text_iter_forward_char(&end_iter))
11340 gtk_text_iter_backward_char(&start_iter);
11342 else
11343 gtk_text_iter_forward_to_line_end(&end_iter);
11344 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11347 static void textview_delete_to_line_end (GtkTextView *text)
11349 GtkTextBuffer *buffer;
11350 GtkTextMark *mark;
11351 GtkTextIter ins, end_iter;
11353 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11355 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11356 mark = gtk_text_buffer_get_insert(buffer);
11357 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11358 end_iter = ins;
11359 if (gtk_text_iter_ends_line(&end_iter))
11360 gtk_text_iter_forward_char(&end_iter);
11361 else
11362 gtk_text_iter_forward_to_line_end(&end_iter);
11363 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11366 #define DO_ACTION(name, act) { \
11367 if(!strcmp(name, a_name)) { \
11368 return act; \
11371 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11373 const gchar *a_name = gtk_action_get_name(action);
11374 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11375 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11376 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11377 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11378 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11379 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11380 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11381 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11382 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11383 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11384 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11385 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11386 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11387 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11388 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11391 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11393 Compose *compose = (Compose *)data;
11394 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11395 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11397 action = compose_call_advanced_action_from_path(gaction);
11399 static struct {
11400 void (*do_action) (GtkTextView *text);
11401 } action_table[] = {
11402 {textview_move_beginning_of_line},
11403 {textview_move_forward_character},
11404 {textview_move_backward_character},
11405 {textview_move_forward_word},
11406 {textview_move_backward_word},
11407 {textview_move_end_of_line},
11408 {textview_move_next_line},
11409 {textview_move_previous_line},
11410 {textview_delete_forward_character},
11411 {textview_delete_backward_character},
11412 {textview_delete_forward_word},
11413 {textview_delete_backward_word},
11414 {textview_delete_line},
11415 {textview_delete_to_line_end}
11418 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11420 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11421 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11422 if (action_table[action].do_action)
11423 action_table[action].do_action(text);
11424 else
11425 g_warning("Not implemented yet.");
11429 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11431 GtkAllocation allocation;
11432 GtkWidget *parent;
11433 gchar *str = NULL;
11435 if (GTK_IS_EDITABLE(widget)) {
11436 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11437 gtk_editable_set_position(GTK_EDITABLE(widget),
11438 strlen(str));
11439 g_free(str);
11440 if ((parent = gtk_widget_get_parent(widget))
11441 && (parent = gtk_widget_get_parent(parent))
11442 && (parent = gtk_widget_get_parent(parent))) {
11443 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11444 gtk_widget_get_allocation(widget, &allocation);
11445 gint y = allocation.y;
11446 gint height = allocation.height;
11447 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11448 (GTK_SCROLLED_WINDOW(parent));
11450 gfloat value = gtk_adjustment_get_value(shown);
11451 gfloat upper = gtk_adjustment_get_upper(shown);
11452 gfloat page_size = gtk_adjustment_get_page_size(shown);
11453 if (y < (int)value) {
11454 gtk_adjustment_set_value(shown, y - 1);
11456 if ((y + height) > ((int)value + (int)page_size)) {
11457 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11458 gtk_adjustment_set_value(shown,
11459 y + height - (int)page_size - 1);
11460 } else {
11461 gtk_adjustment_set_value(shown,
11462 (int)upper - (int)page_size - 1);
11469 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11470 compose->focused_editable = widget;
11472 #ifdef GENERIC_UMPC
11473 if (GTK_IS_TEXT_VIEW(widget)
11474 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11475 g_object_ref(compose->notebook);
11476 g_object_ref(compose->edit_vbox);
11477 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11478 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11479 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11480 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11481 g_object_unref(compose->notebook);
11482 g_object_unref(compose->edit_vbox);
11483 g_signal_handlers_block_by_func(G_OBJECT(widget),
11484 G_CALLBACK(compose_grab_focus_cb),
11485 compose);
11486 gtk_widget_grab_focus(widget);
11487 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11488 G_CALLBACK(compose_grab_focus_cb),
11489 compose);
11490 } else if (!GTK_IS_TEXT_VIEW(widget)
11491 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11492 g_object_ref(compose->notebook);
11493 g_object_ref(compose->edit_vbox);
11494 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11495 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11496 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11497 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11498 g_object_unref(compose->notebook);
11499 g_object_unref(compose->edit_vbox);
11500 g_signal_handlers_block_by_func(G_OBJECT(widget),
11501 G_CALLBACK(compose_grab_focus_cb),
11502 compose);
11503 gtk_widget_grab_focus(widget);
11504 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11505 G_CALLBACK(compose_grab_focus_cb),
11506 compose);
11508 #endif
11511 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11513 compose->modified = TRUE;
11514 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11515 #ifndef GENERIC_UMPC
11516 compose_set_title(compose);
11517 #endif
11520 static void compose_wrap_cb(GtkAction *action, gpointer data)
11522 Compose *compose = (Compose *)data;
11523 compose_beautify_paragraph(compose, NULL, TRUE);
11526 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11528 Compose *compose = (Compose *)data;
11529 compose_wrap_all_full(compose, TRUE);
11532 static void compose_find_cb(GtkAction *action, gpointer data)
11534 Compose *compose = (Compose *)data;
11536 message_search_compose(compose);
11539 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11540 gpointer data)
11542 Compose *compose = (Compose *)data;
11543 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11544 if (compose->autowrap)
11545 compose_wrap_all_full(compose, TRUE);
11546 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11549 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11550 gpointer data)
11552 Compose *compose = (Compose *)data;
11553 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11556 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11558 Compose *compose = (Compose *)data;
11560 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11561 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11564 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11566 Compose *compose = (Compose *)data;
11568 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11569 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11572 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11574 g_free(compose->privacy_system);
11575 g_free(compose->encdata);
11577 compose->privacy_system = g_strdup(account->default_privacy_system);
11578 compose_update_privacy_system_menu_item(compose, warn);
11581 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11583 if (folder_item != NULL) {
11584 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT &&
11585 privacy_system_can_sign(compose->privacy_system)) {
11586 compose_use_signing(compose,
11587 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11589 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT &&
11590 privacy_system_can_encrypt(compose->privacy_system)) {
11591 compose_use_encryption(compose,
11592 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11597 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11599 Compose *compose = (Compose *)data;
11601 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11602 gtk_widget_show(compose->ruler_hbox);
11603 prefs_common.show_ruler = TRUE;
11604 } else {
11605 gtk_widget_hide(compose->ruler_hbox);
11606 gtk_widget_queue_resize(compose->edit_vbox);
11607 prefs_common.show_ruler = FALSE;
11611 static void compose_attach_drag_received_cb (GtkWidget *widget,
11612 GdkDragContext *context,
11613 gint x,
11614 gint y,
11615 GtkSelectionData *data,
11616 guint info,
11617 guint time,
11618 gpointer user_data)
11620 Compose *compose = (Compose *)user_data;
11621 GList *list, *tmp;
11622 GdkAtom type;
11624 type = gtk_selection_data_get_data_type(data);
11625 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11626 && gtk_drag_get_source_widget(context) !=
11627 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11628 list = uri_list_extract_filenames(
11629 (const gchar *)gtk_selection_data_get_data(data));
11630 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11631 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11632 compose_attach_append
11633 (compose, (const gchar *)tmp->data,
11634 utf8_filename, NULL, NULL);
11635 g_free(utf8_filename);
11637 if (list)
11638 compose_changed_cb(NULL, compose);
11639 list_free_strings_full(list);
11640 } else if (gtk_drag_get_source_widget(context)
11641 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11642 /* comes from our summaryview */
11643 SummaryView * summaryview = NULL;
11644 GSList * list = NULL, *cur = NULL;
11646 if (mainwindow_get_mainwindow())
11647 summaryview = mainwindow_get_mainwindow()->summaryview;
11649 if (summaryview)
11650 list = summary_get_selected_msg_list(summaryview);
11652 for (cur = list; cur; cur = cur->next) {
11653 MsgInfo *msginfo = (MsgInfo *)cur->data;
11654 gchar *file = NULL;
11655 if (msginfo)
11656 file = procmsg_get_message_file_full(msginfo,
11657 TRUE, TRUE);
11658 if (file) {
11659 compose_attach_append(compose, (const gchar *)file,
11660 (const gchar *)file, "message/rfc822", NULL);
11661 g_free(file);
11664 g_slist_free(list);
11668 static gboolean compose_drag_drop(GtkWidget *widget,
11669 GdkDragContext *drag_context,
11670 gint x, gint y,
11671 guint time, gpointer user_data)
11673 /* not handling this signal makes compose_insert_drag_received_cb
11674 * called twice */
11675 return TRUE;
11678 static gboolean completion_set_focus_to_subject
11679 (GtkWidget *widget,
11680 GdkEventKey *event,
11681 Compose *compose)
11683 cm_return_val_if_fail(compose != NULL, FALSE);
11685 /* make backtab move to subject field */
11686 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11687 gtk_widget_grab_focus(compose->subject_entry);
11688 return TRUE;
11690 return FALSE;
11693 static void compose_insert_drag_received_cb (GtkWidget *widget,
11694 GdkDragContext *drag_context,
11695 gint x,
11696 gint y,
11697 GtkSelectionData *data,
11698 guint info,
11699 guint time,
11700 gpointer user_data)
11702 Compose *compose = (Compose *)user_data;
11703 GList *list, *tmp;
11704 GdkAtom type;
11705 guint num_files;
11706 gchar *msg;
11708 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11709 * does not work */
11710 type = gtk_selection_data_get_data_type(data);
11711 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11712 AlertValue val = G_ALERTDEFAULT;
11713 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11715 list = uri_list_extract_filenames(ddata);
11716 num_files = g_list_length(list);
11717 if (list == NULL && strstr(ddata, "://")) {
11718 /* Assume a list of no files, and data has ://, is a remote link */
11719 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11720 gchar *tmpfile = get_tmp_file();
11721 str_write_to_file(tmpdata, tmpfile, TRUE);
11722 g_free(tmpdata);
11723 compose_insert_file(compose, tmpfile);
11724 claws_unlink(tmpfile);
11725 g_free(tmpfile);
11726 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11727 compose_beautify_paragraph(compose, NULL, TRUE);
11728 return;
11730 switch (prefs_common.compose_dnd_mode) {
11731 case COMPOSE_DND_ASK:
11732 msg = g_strdup_printf(
11733 ngettext(
11734 "Do you want to insert the contents of the file "
11735 "into the message body, or attach it to the email?",
11736 "Do you want to insert the contents of the %d files "
11737 "into the message body, or attach them to the email?",
11738 num_files),
11739 num_files);
11740 val = alertpanel_full(_("Insert or attach?"), msg,
11741 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11742 ALERTFOCUS_SECOND,
11743 TRUE, NULL, ALERT_QUESTION);
11744 g_free(msg);
11745 break;
11746 case COMPOSE_DND_INSERT:
11747 val = G_ALERTALTERNATE;
11748 break;
11749 case COMPOSE_DND_ATTACH:
11750 val = G_ALERTOTHER;
11751 break;
11752 default:
11753 /* unexpected case */
11754 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11757 if (val & G_ALERTDISABLE) {
11758 val &= ~G_ALERTDISABLE;
11759 /* remember what action to perform by default, only if we don't click Cancel */
11760 if (val == G_ALERTALTERNATE)
11761 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11762 else if (val == G_ALERTOTHER)
11763 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11766 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11767 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11768 list_free_strings_full(list);
11769 return;
11770 } else if (val == G_ALERTOTHER) {
11771 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11772 list_free_strings_full(list);
11773 return;
11776 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11777 compose_insert_file(compose, (const gchar *)tmp->data);
11779 list_free_strings_full(list);
11780 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11781 return;
11785 static void compose_header_drag_received_cb (GtkWidget *widget,
11786 GdkDragContext *drag_context,
11787 gint x,
11788 gint y,
11789 GtkSelectionData *data,
11790 guint info,
11791 guint time,
11792 gpointer user_data)
11794 GtkEditable *entry = (GtkEditable *)user_data;
11795 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11797 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11798 * does not work */
11800 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11801 gchar *decoded=g_new(gchar, strlen(email));
11802 int start = 0;
11804 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11805 gtk_editable_delete_text(entry, 0, -1);
11806 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11807 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11808 g_free(decoded);
11809 return;
11811 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11814 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11816 Compose *compose = (Compose *)data;
11818 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11819 compose->return_receipt = TRUE;
11820 else
11821 compose->return_receipt = FALSE;
11824 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11826 Compose *compose = (Compose *)data;
11828 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11829 compose->remove_references = TRUE;
11830 else
11831 compose->remove_references = FALSE;
11834 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11835 ComposeHeaderEntry *headerentry)
11837 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11838 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11839 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11840 return FALSE;
11843 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11844 GdkEventKey *event,
11845 ComposeHeaderEntry *headerentry)
11847 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11848 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11849 !(event->state & GDK_MODIFIER_MASK) &&
11850 (event->keyval == GDK_KEY_BackSpace) &&
11851 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11852 gtk_container_remove
11853 (GTK_CONTAINER(headerentry->compose->header_table),
11854 headerentry->combo);
11855 gtk_container_remove
11856 (GTK_CONTAINER(headerentry->compose->header_table),
11857 headerentry->entry);
11858 headerentry->compose->header_list =
11859 g_slist_remove(headerentry->compose->header_list,
11860 headerentry);
11861 g_free(headerentry);
11862 } else if (event->keyval == GDK_KEY_Tab) {
11863 if (headerentry->compose->header_last == headerentry) {
11864 /* Override default next focus, and give it to subject_entry
11865 * instead of notebook tabs
11867 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11868 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11869 return TRUE;
11872 return FALSE;
11875 static gboolean scroll_postpone(gpointer data)
11877 Compose *compose = (Compose *)data;
11879 if (compose->batch)
11880 return FALSE;
11882 GTK_EVENTS_FLUSH();
11883 compose_show_first_last_header(compose, FALSE);
11884 return FALSE;
11887 static void compose_headerentry_changed_cb(GtkWidget *entry,
11888 ComposeHeaderEntry *headerentry)
11890 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11891 compose_create_header_entry(headerentry->compose);
11892 g_signal_handlers_disconnect_matched
11893 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11894 0, 0, NULL, NULL, headerentry);
11896 if (!headerentry->compose->batch)
11897 g_timeout_add(0, scroll_postpone, headerentry->compose);
11901 static gboolean compose_defer_auto_save_draft(Compose *compose)
11903 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11904 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11905 return FALSE;
11908 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11910 GtkAdjustment *vadj;
11912 cm_return_if_fail(compose);
11914 if(compose->batch)
11915 return;
11917 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11918 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11919 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11920 gtk_widget_get_parent(compose->header_table)));
11921 gtk_adjustment_set_value(vadj, (show_first ?
11922 gtk_adjustment_get_lower(vadj) :
11923 (gtk_adjustment_get_upper(vadj) -
11924 gtk_adjustment_get_page_size(vadj))));
11925 gtk_adjustment_changed(vadj);
11928 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11929 const gchar *text, gint len, Compose *compose)
11931 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11932 (G_OBJECT(compose->text), "paste_as_quotation"));
11933 GtkTextMark *mark;
11935 cm_return_if_fail(text != NULL);
11937 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11938 G_CALLBACK(text_inserted),
11939 compose);
11940 if (paste_as_quotation) {
11941 gchar *new_text;
11942 const gchar *qmark;
11943 guint pos = 0;
11944 GtkTextIter start_iter;
11946 if (len < 0)
11947 len = strlen(text);
11949 new_text = g_strndup(text, len);
11951 qmark = compose_quote_char_from_context(compose);
11953 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11954 gtk_text_buffer_place_cursor(buffer, iter);
11956 pos = gtk_text_iter_get_offset(iter);
11958 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11959 _("Quote format error at line %d."));
11960 quote_fmt_reset_vartable();
11961 g_free(new_text);
11962 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11963 GINT_TO_POINTER(paste_as_quotation - 1));
11965 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11966 gtk_text_buffer_place_cursor(buffer, iter);
11967 gtk_text_buffer_delete_mark(buffer, mark);
11969 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11970 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11971 compose_beautify_paragraph(compose, &start_iter, FALSE);
11972 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11973 gtk_text_buffer_delete_mark(buffer, mark);
11974 } else {
11975 if (strcmp(text, "\n") || compose->automatic_break
11976 || gtk_text_iter_starts_line(iter)) {
11977 GtkTextIter before_ins;
11978 gtk_text_buffer_insert(buffer, iter, text, len);
11979 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11980 before_ins = *iter;
11981 gtk_text_iter_backward_chars(&before_ins, len);
11982 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11984 } else {
11985 /* check if the preceding is just whitespace or quote */
11986 GtkTextIter start_line;
11987 gchar *tmp = NULL, *quote = NULL;
11988 gint quote_len = 0, is_normal = 0;
11989 start_line = *iter;
11990 gtk_text_iter_set_line_offset(&start_line, 0);
11991 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11992 g_strstrip(tmp);
11994 if (*tmp == '\0') {
11995 is_normal = 1;
11996 } else {
11997 quote = compose_get_quote_str(buffer, &start_line, &quote_len);
11998 if (quote)
11999 is_normal = 1;
12000 g_free(quote);
12002 g_free(tmp);
12004 if (is_normal) {
12005 gtk_text_buffer_insert(buffer, iter, text, len);
12006 } else {
12007 gtk_text_buffer_insert_with_tags_by_name(buffer,
12008 iter, text, len, "no_join", NULL);
12013 if (!paste_as_quotation) {
12014 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
12015 compose_beautify_paragraph(compose, iter, FALSE);
12016 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12017 gtk_text_buffer_delete_mark(buffer, mark);
12020 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
12021 G_CALLBACK(text_inserted),
12022 compose);
12023 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
12025 if (compose_can_autosave(compose) &&
12026 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
12027 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
12028 compose->draft_timeout_tag = g_timeout_add
12029 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
12032 #if USE_ENCHANT
12033 static void compose_check_all(GtkAction *action, gpointer data)
12035 Compose *compose = (Compose *)data;
12036 if (!compose->gtkaspell)
12037 return;
12039 if (gtk_widget_has_focus(compose->subject_entry))
12040 claws_spell_entry_check_all(
12041 CLAWS_SPELL_ENTRY(compose->subject_entry));
12042 else
12043 gtkaspell_check_all(compose->gtkaspell);
12046 static void compose_highlight_all(GtkAction *action, gpointer data)
12048 Compose *compose = (Compose *)data;
12049 if (compose->gtkaspell) {
12050 claws_spell_entry_recheck_all(
12051 CLAWS_SPELL_ENTRY(compose->subject_entry));
12052 gtkaspell_highlight_all(compose->gtkaspell);
12056 static void compose_check_backwards(GtkAction *action, gpointer data)
12058 Compose *compose = (Compose *)data;
12059 if (!compose->gtkaspell) {
12060 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12061 return;
12064 if (gtk_widget_has_focus(compose->subject_entry))
12065 claws_spell_entry_check_backwards(
12066 CLAWS_SPELL_ENTRY(compose->subject_entry));
12067 else
12068 gtkaspell_check_backwards(compose->gtkaspell);
12071 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12073 Compose *compose = (Compose *)data;
12074 if (!compose->gtkaspell) {
12075 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12076 return;
12079 if (gtk_widget_has_focus(compose->subject_entry))
12080 claws_spell_entry_check_forwards_go(
12081 CLAWS_SPELL_ENTRY(compose->subject_entry));
12082 else
12083 gtkaspell_check_forwards_go(compose->gtkaspell);
12085 #endif
12088 *\brief Guess originating forward account from MsgInfo and several
12089 * "common preference" settings. Return NULL if no guess.
12091 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12093 PrefsAccount *account = NULL;
12095 cm_return_val_if_fail(msginfo, NULL);
12096 cm_return_val_if_fail(msginfo->folder, NULL);
12097 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12099 if (msginfo->folder->prefs->enable_default_account)
12100 account = account_find_from_id(msginfo->folder->prefs->default_account);
12102 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12103 gchar *to;
12104 Xstrdup_a(to, msginfo->to, return NULL);
12105 extract_address(to);
12106 account = account_find_from_address(to, FALSE);
12109 if (!account && prefs_common.forward_account_autosel) {
12110 gchar *cc = NULL;
12111 if (!procheader_get_header_from_msginfo
12112 (msginfo, &cc, "Cc:")) {
12113 gchar *buf = cc + strlen("Cc:");
12114 extract_address(buf);
12115 account = account_find_from_address(buf, FALSE);
12116 g_free(cc);
12120 if (!account && prefs_common.forward_account_autosel) {
12121 gchar *deliveredto = NULL;
12122 if (!procheader_get_header_from_msginfo
12123 (msginfo, &deliveredto, "Delivered-To:")) {
12124 gchar *buf = deliveredto + strlen("Delivered-To:");
12125 extract_address(buf);
12126 account = account_find_from_address(buf, FALSE);
12127 g_free(deliveredto);
12131 if (!account)
12132 account = msginfo->folder->folder->account;
12134 return account;
12137 gboolean compose_close(Compose *compose)
12139 gint x, y;
12141 cm_return_val_if_fail(compose, FALSE);
12143 if (!g_mutex_trylock(compose->mutex)) {
12144 /* we have to wait for the (possibly deferred by auto-save)
12145 * drafting to be done, before destroying the compose under
12146 * it. */
12147 debug_print("waiting for drafting to finish...\n");
12148 compose_allow_user_actions(compose, FALSE);
12149 if (compose->close_timeout_tag == 0) {
12150 compose->close_timeout_tag =
12151 g_timeout_add (500, (GSourceFunc) compose_close,
12152 compose);
12154 return TRUE;
12157 if (compose->draft_timeout_tag >= 0) {
12158 g_source_remove(compose->draft_timeout_tag);
12159 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12162 gtkut_widget_get_uposition(compose->window, &x, &y);
12163 if (!compose->batch) {
12164 prefs_common.compose_x = x;
12165 prefs_common.compose_y = y;
12167 g_mutex_unlock(compose->mutex);
12168 compose_destroy(compose);
12169 return FALSE;
12173 * Add entry field for each address in list.
12174 * \param compose E-Mail composition object.
12175 * \param listAddress List of (formatted) E-Mail addresses.
12177 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12178 GList *node;
12179 gchar *addr;
12180 node = listAddress;
12181 while( node ) {
12182 addr = ( gchar * ) node->data;
12183 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12184 node = g_list_next( node );
12188 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12189 guint action, gboolean opening_multiple)
12191 gchar *body = NULL;
12192 GSList *new_msglist = NULL;
12193 MsgInfo *tmp_msginfo = NULL;
12194 gboolean originally_enc = FALSE;
12195 gboolean originally_sig = FALSE;
12196 Compose *compose = NULL;
12197 gchar *s_system = NULL;
12199 cm_return_if_fail(msginfo_list != NULL);
12201 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12202 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12203 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12205 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12206 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12207 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12208 orig_msginfo, mimeinfo);
12209 if (tmp_msginfo != NULL) {
12210 new_msglist = g_slist_append(NULL, tmp_msginfo);
12212 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12213 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12214 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12216 tmp_msginfo->folder = orig_msginfo->folder;
12217 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12218 if (orig_msginfo->tags) {
12219 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12220 tmp_msginfo->folder->tags_dirty = TRUE;
12226 if (!opening_multiple && msgview != NULL)
12227 body = messageview_get_selection(msgview);
12229 if (new_msglist) {
12230 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12231 procmsg_msginfo_free(&tmp_msginfo);
12232 g_slist_free(new_msglist);
12233 } else
12234 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12236 if (compose && originally_enc) {
12237 compose_force_encryption(compose, compose->account, FALSE, s_system);
12240 if (compose && originally_sig && compose->account->default_sign_reply) {
12241 compose_force_signing(compose, compose->account, s_system);
12243 g_free(s_system);
12244 g_free(body);
12245 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12248 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12249 guint action)
12251 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12252 && msginfo_list != NULL
12253 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12254 GSList *cur = msginfo_list;
12255 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12256 "messages. Opening the windows "
12257 "could take some time. Do you "
12258 "want to continue?"),
12259 g_slist_length(msginfo_list));
12260 if (g_slist_length(msginfo_list) > 9
12261 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12262 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12263 g_free(msg);
12264 return;
12266 g_free(msg);
12267 /* We'll open multiple compose windows */
12268 /* let the WM place the next windows */
12269 compose_force_window_origin = FALSE;
12270 for (; cur; cur = cur->next) {
12271 GSList tmplist;
12272 tmplist.data = cur->data;
12273 tmplist.next = NULL;
12274 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12276 compose_force_window_origin = TRUE;
12277 } else {
12278 /* forwarding multiple mails as attachments is done via a
12279 * single compose window */
12280 if (msginfo_list != NULL) {
12281 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12282 } else if (msgview != NULL) {
12283 GSList tmplist;
12284 tmplist.data = msgview->msginfo;
12285 tmplist.next = NULL;
12286 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12287 } else {
12288 debug_print("Nothing to reply to\n");
12293 void compose_check_for_email_account(Compose *compose)
12295 PrefsAccount *ac = NULL, *curr = NULL;
12296 GList *list;
12298 if (!compose)
12299 return;
12301 if (compose->account && compose->account->protocol == A_NNTP) {
12302 ac = account_get_cur_account();
12303 if (ac->protocol == A_NNTP) {
12304 list = account_get_list();
12306 for( ; list != NULL ; list = g_list_next(list)) {
12307 curr = (PrefsAccount *) list->data;
12308 if (curr->protocol != A_NNTP) {
12309 ac = curr;
12310 break;
12314 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12315 ac->account_id);
12319 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12320 const gchar *address)
12322 GSList *msginfo_list = NULL;
12323 gchar *body = messageview_get_selection(msgview);
12324 Compose *compose;
12326 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12328 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12329 compose_check_for_email_account(compose);
12330 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12331 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12332 compose_reply_set_subject(compose, msginfo);
12334 g_free(body);
12335 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12338 void compose_set_position(Compose *compose, gint pos)
12340 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12342 gtkut_text_view_set_position(text, pos);
12345 gboolean compose_search_string(Compose *compose,
12346 const gchar *str, gboolean case_sens)
12348 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12350 return gtkut_text_view_search_string(text, str, case_sens);
12353 gboolean compose_search_string_backward(Compose *compose,
12354 const gchar *str, gboolean case_sens)
12356 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12358 return gtkut_text_view_search_string_backward(text, str, case_sens);
12361 /* allocate a msginfo structure and populate its data from a compose data structure */
12362 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12364 MsgInfo *newmsginfo;
12365 GSList *list;
12366 gchar date[RFC822_DATE_BUFFSIZE];
12368 cm_return_val_if_fail( compose != NULL, NULL );
12370 newmsginfo = procmsg_msginfo_new();
12372 /* date is now */
12373 get_rfc822_date(date, sizeof(date));
12374 newmsginfo->date = g_strdup(date);
12376 /* from */
12377 if (compose->from_name) {
12378 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12379 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12382 /* subject */
12383 if (compose->subject_entry)
12384 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12386 /* to, cc, reply-to, newsgroups */
12387 for (list = compose->header_list; list; list = list->next) {
12388 gchar *header = gtk_editable_get_chars(
12389 GTK_EDITABLE(
12390 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12391 gchar *entry = gtk_editable_get_chars(
12392 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12394 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12395 if ( newmsginfo->to == NULL ) {
12396 newmsginfo->to = g_strdup(entry);
12397 } else if (entry && *entry) {
12398 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12399 g_free(newmsginfo->to);
12400 newmsginfo->to = tmp;
12402 } else
12403 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12404 if ( newmsginfo->cc == NULL ) {
12405 newmsginfo->cc = g_strdup(entry);
12406 } else if (entry && *entry) {
12407 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12408 g_free(newmsginfo->cc);
12409 newmsginfo->cc = tmp;
12411 } else
12412 if ( strcasecmp(header,
12413 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12414 if ( newmsginfo->newsgroups == NULL ) {
12415 newmsginfo->newsgroups = g_strdup(entry);
12416 } else if (entry && *entry) {
12417 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12418 g_free(newmsginfo->newsgroups);
12419 newmsginfo->newsgroups = tmp;
12423 g_free(header);
12424 g_free(entry);
12427 /* other data is unset */
12429 return newmsginfo;
12432 #ifdef USE_ENCHANT
12433 /* update compose's dictionaries from folder dict settings */
12434 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12435 FolderItem *folder_item)
12437 cm_return_if_fail(compose != NULL);
12439 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12440 FolderItemPrefs *prefs = folder_item->prefs;
12442 if (prefs->enable_default_dictionary)
12443 gtkaspell_change_dict(compose->gtkaspell,
12444 prefs->default_dictionary, FALSE);
12445 if (folder_item->prefs->enable_default_alt_dictionary)
12446 gtkaspell_change_alt_dict(compose->gtkaspell,
12447 prefs->default_alt_dictionary);
12448 if (prefs->enable_default_dictionary
12449 || prefs->enable_default_alt_dictionary)
12450 compose_spell_menu_changed(compose);
12453 #endif
12455 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12457 Compose *compose = (Compose *)data;
12459 cm_return_if_fail(compose != NULL);
12461 gtk_widget_grab_focus(compose->text);
12464 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12466 gtk_combo_box_popup(GTK_COMBO_BOX(data));
12471 * End of Source.