fix message which is shown when mail can't be sent
[claws.git] / src / compose.c
bloba69e38e26ca52b7b1130674a5b7c7bb65296cd15
1 /*
2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2022 the Claws Mail team and Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #include "claws-features.h"
22 #endif
24 #include "defs.h"
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
28 #endif
30 #include <glib.h>
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtk.h>
34 #ifdef GDK_WINDOWING_X11
35 #include <gtk/gtkx.h>
36 #endif
38 #include <pango/pango-break.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <ctype.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <unistd.h>
46 #include <time.h>
47 #if HAVE_SYS_WAIT_H
48 # include <sys/wait.h>
49 #endif
50 #include <signal.h>
51 #include <errno.h>
52 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
53 #include <libgen.h>
54 #endif
55 #ifdef G_OS_WIN32
56 #include <windows.h>
57 #endif
59 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
60 # include <wchar.h>
61 # include <wctype.h>
62 #endif
64 #include "claws.h"
65 #include "main.h"
66 #include "mainwindow.h"
67 #include "compose.h"
68 #ifndef USE_ALT_ADDRBOOK
69 #include "addressbook.h"
70 #else
71 #include "addressbook-dbus.h"
72 #include "addressadd.h"
73 #endif
74 #include "folderview.h"
75 #include "procmsg.h"
76 #include "menu.h"
77 #include "stock_pixmap.h"
78 #include "send_message.h"
79 #include "imap.h"
80 #include "news.h"
81 #include "customheader.h"
82 #include "prefs_common.h"
83 #include "prefs_account.h"
84 #include "action.h"
85 #include "account.h"
86 #include "filesel.h"
87 #include "procheader.h"
88 #include "procmime.h"
89 #include "statusbar.h"
90 #include "about.h"
91 #include "quoted-printable.h"
92 #include "codeconv.h"
93 #include "utils.h"
94 #include "gtkutils.h"
95 #include "gtkshruler.h"
96 #include "socket.h"
97 #include "alertpanel.h"
98 #include "manage_window.h"
99 #include "folder.h"
100 #include "folder_item_prefs.h"
101 #include "addr_compl.h"
102 #include "quote_fmt.h"
103 #include "undo.h"
104 #include "foldersel.h"
105 #include "toolbar.h"
106 #include "inc.h"
107 #include "message_search.h"
108 #include "combobox.h"
109 #include "hooks.h"
110 #include "privacy.h"
111 #include "timing.h"
112 #include "autofaces.h"
113 #include "spell_entry.h"
114 #include "headers.h"
115 #include "file-utils.h"
117 #ifdef USE_LDAP
118 #include "password.h"
119 #include "ldapserver.h"
120 #endif
122 enum
124 COL_MIMETYPE = 0,
125 COL_SIZE = 1,
126 COL_NAME = 2,
127 COL_CHARSET = 3,
128 COL_DATA = 4,
129 COL_AUTODATA = 5,
130 N_COL_COLUMNS
133 #define N_ATTACH_COLS (N_COL_COLUMNS)
135 typedef enum
137 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
138 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
139 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
140 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
141 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
142 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
143 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
144 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
145 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
146 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
147 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
148 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
149 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
150 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
151 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
152 } ComposeCallAdvancedAction;
154 typedef enum
156 PRIORITY_HIGHEST = 1,
157 PRIORITY_HIGH,
158 PRIORITY_NORMAL,
159 PRIORITY_LOW,
160 PRIORITY_LOWEST
161 } PriorityLevel;
163 typedef enum
165 COMPOSE_INSERT_SUCCESS,
166 COMPOSE_INSERT_READ_ERROR,
167 COMPOSE_INSERT_INVALID_CHARACTER,
168 COMPOSE_INSERT_NO_FILE
169 } ComposeInsertResult;
171 typedef enum
173 COMPOSE_WRITE_FOR_SEND,
174 COMPOSE_WRITE_FOR_STORE
175 } ComposeWriteType;
177 typedef enum
179 COMPOSE_QUOTE_FORCED,
180 COMPOSE_QUOTE_CHECK,
181 COMPOSE_QUOTE_SKIP
182 } ComposeQuoteMode;
184 typedef enum {
185 TO_FIELD_PRESENT,
186 SUBJECT_FIELD_PRESENT,
187 BODY_FIELD_PRESENT,
188 NO_FIELD_PRESENT
189 } MailField;
191 #define B64_LINE_SIZE 57
192 #define B64_BUFFSIZE 77
194 #define MAX_REFERENCES_LEN 999
196 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
197 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
199 #define COMPOSE_PRIVACY_WARNING() { \
200 alertpanel_error(_("You have opted to sign and/or encrypt this " \
201 "message but have not selected a privacy system.\n\n" \
202 "Signing and encrypting have been disabled for this " \
203 "message.")); \
206 #ifdef G_OS_WIN32
207 #define INVALID_PID INVALID_HANDLE_VALUE
208 #else
209 #define INVALID_PID -1
210 #endif
212 static GdkRGBA default_header_bgcolor =
213 {0, 0, 0, 1};
215 static GdkRGBA default_header_color =
216 {0, 0, 0, 1};
218 static GList *compose_list = NULL;
219 static GSList *extra_headers = NULL;
221 static Compose *compose_generic_new (PrefsAccount *account,
222 const gchar *to,
223 FolderItem *item,
224 GList *attach_files,
225 GList *listAddress );
227 static Compose *compose_create (PrefsAccount *account,
228 FolderItem *item,
229 ComposeMode mode,
230 gboolean batch);
232 static void compose_entry_indicate (Compose *compose,
233 const gchar *address);
234 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
235 ComposeQuoteMode quote_mode,
236 gboolean to_all,
237 gboolean to_sender,
238 const gchar *body);
239 static Compose *compose_forward_multiple (PrefsAccount *account,
240 GSList *msginfo_list);
241 static Compose *compose_reply (MsgInfo *msginfo,
242 ComposeQuoteMode quote_mode,
243 gboolean to_all,
244 gboolean to_ml,
245 gboolean to_sender,
246 const gchar *body);
247 static Compose *compose_reply_mode (ComposeMode mode,
248 GSList *msginfo_list,
249 gchar *body);
250 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
251 static void compose_update_privacy_systems_menu(Compose *compose);
253 static GtkWidget *compose_account_option_menu_create
254 (Compose *compose);
255 static void compose_set_out_encoding (Compose *compose);
256 static void compose_set_template_menu (Compose *compose);
257 static void compose_destroy (Compose *compose);
259 static MailField compose_entries_set (Compose *compose,
260 const gchar *mailto,
261 ComposeEntryType to_type);
262 static gint compose_parse_header (Compose *compose,
263 MsgInfo *msginfo);
264 static gint compose_parse_manual_headers (Compose *compose,
265 MsgInfo *msginfo,
266 HeaderEntry *entries);
267 static gchar *compose_parse_references (const gchar *ref,
268 const gchar *msgid);
270 static gchar *compose_quote_fmt (Compose *compose,
271 MsgInfo *msginfo,
272 const gchar *fmt,
273 const gchar *qmark,
274 const gchar *body,
275 gboolean rewrap,
276 gboolean need_unescape,
277 const gchar *err_msg);
279 static void compose_reply_set_entry (Compose *compose,
280 MsgInfo *msginfo,
281 gboolean to_all,
282 gboolean to_ml,
283 gboolean to_sender,
284 gboolean
285 followup_and_reply_to);
286 static void compose_reedit_set_entry (Compose *compose,
287 MsgInfo *msginfo);
289 static void compose_insert_sig (Compose *compose,
290 gboolean replace);
291 static ComposeInsertResult compose_insert_file (Compose *compose,
292 const gchar *file);
294 static gboolean compose_attach_append (Compose *compose,
295 const gchar *file,
296 const gchar *type,
297 const gchar *content_type,
298 const gchar *charset);
299 static void compose_attach_parts (Compose *compose,
300 MsgInfo *msginfo);
302 static gboolean compose_beautify_paragraph (Compose *compose,
303 GtkTextIter *par_iter,
304 gboolean force);
305 static void compose_wrap_all (Compose *compose);
306 static void compose_wrap_all_full (Compose *compose,
307 gboolean autowrap);
309 static void compose_set_title (Compose *compose);
310 static void compose_select_account (Compose *compose,
311 PrefsAccount *account,
312 gboolean init);
314 static PrefsAccount *compose_current_mail_account(void);
315 /* static gint compose_send (Compose *compose); */
316 static gboolean compose_check_for_valid_recipient
317 (Compose *compose);
318 static gboolean compose_check_entries (Compose *compose,
319 gboolean check_everything);
320 static gint compose_write_to_file (Compose *compose,
321 FILE *fp,
322 gint action,
323 gboolean attach_parts);
324 static gint compose_write_body_to_file (Compose *compose,
325 const gchar *file);
326 static gint compose_remove_reedit_target (Compose *compose,
327 gboolean force);
328 static void compose_remove_draft (Compose *compose);
329 static ComposeQueueResult compose_queue_sub (Compose *compose,
330 gint *msgnum,
331 FolderItem **item,
332 gchar **msgpath,
333 gboolean perform_checks,
334 gboolean remove_reedit_target);
335 static int compose_add_attachments (Compose *compose,
336 MimeInfo *parent);
337 static gchar *compose_get_header (Compose *compose);
338 static gchar *compose_get_manual_headers_info (Compose *compose);
340 static void compose_convert_header (Compose *compose,
341 gchar *dest,
342 gint len,
343 gchar *src,
344 gint header_len,
345 gboolean addr_field);
347 static void compose_attach_info_free (AttachInfo *ainfo);
348 static void compose_attach_remove_selected (GtkAction *action,
349 gpointer data);
351 static void compose_template_apply (Compose *compose,
352 Template *tmpl,
353 gboolean replace);
354 static void compose_attach_property (GtkAction *action,
355 gpointer data);
356 static void compose_attach_property_create (gboolean *cancelled);
357 static void attach_property_ok (GtkWidget *widget,
358 gboolean *cancelled);
359 static void attach_property_cancel (GtkWidget *widget,
360 gboolean *cancelled);
361 static gint attach_property_delete_event (GtkWidget *widget,
362 GdkEventAny *event,
363 gboolean *cancelled);
364 static gboolean attach_property_key_pressed (GtkWidget *widget,
365 GdkEventKey *event,
366 gboolean *cancelled);
368 static void compose_exec_ext_editor (Compose *compose);
369 static gboolean compose_ext_editor_kill (Compose *compose);
370 static void compose_ext_editor_closed_cb (GPid pid,
371 gint exit_status,
372 gpointer data);
373 static void compose_set_ext_editor_sensitive (Compose *compose,
374 gboolean sensitive);
375 static gboolean compose_get_ext_editor_cmd_valid();
376 static gboolean compose_get_ext_editor_uses_socket();
377 #ifndef G_OS_WIN32
378 static gboolean compose_ext_editor_plug_removed_cb
379 (GtkSocket *socket,
380 Compose *compose);
381 #endif /* G_OS_WIN32 */
383 static void compose_undo_state_changed (UndoMain *undostruct,
384 gint undo_state,
385 gint redo_state,
386 gpointer data);
388 static void compose_create_header_entry (Compose *compose);
389 static void compose_add_header_entry (Compose *compose, const gchar *header,
390 gchar *text, ComposePrefType pref_type);
391 static void compose_remove_header_entries(Compose *compose);
393 static void compose_update_priority_menu_item(Compose * compose);
394 #if USE_ENCHANT
395 static void compose_spell_menu_changed (void *data);
396 static void compose_dict_changed (void *data);
397 #endif
398 static void compose_add_field_list ( Compose *compose,
399 GList *listAddress );
401 /* callback functions */
403 static void compose_notebook_size_alloc (GtkNotebook *notebook,
404 GtkAllocation *allocation,
405 GtkPaned *paned);
406 static gboolean compose_edit_size_alloc (GtkEditable *widget,
407 GtkAllocation *allocation,
408 GtkSHRuler *shruler);
409 static void account_activated (GtkComboBox *optmenu,
410 gpointer data);
411 static void attach_selected (GtkTreeView *tree_view,
412 GtkTreePath *tree_path,
413 GtkTreeViewColumn *column,
414 Compose *compose);
415 static gboolean attach_button_pressed (GtkWidget *widget,
416 GdkEventButton *event,
417 gpointer data);
418 static gboolean attach_key_pressed (GtkWidget *widget,
419 GdkEventKey *event,
420 gpointer data);
421 static void compose_send_cb (GtkAction *action, gpointer data);
422 static void compose_send_later_cb (GtkAction *action, gpointer data);
424 static void compose_save_cb (GtkAction *action,
425 gpointer data);
427 static void compose_attach_cb (GtkAction *action,
428 gpointer data);
429 static void compose_insert_file_cb (GtkAction *action,
430 gpointer data);
431 static void compose_insert_sig_cb (GtkAction *action,
432 gpointer data);
433 static void compose_replace_sig_cb (GtkAction *action,
434 gpointer data);
436 static void compose_close_cb (GtkAction *action,
437 gpointer data);
438 static void compose_print_cb (GtkAction *action,
439 gpointer data);
441 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
443 static void compose_address_cb (GtkAction *action,
444 gpointer data);
445 static void about_show_cb (GtkAction *action,
446 gpointer data);
447 static void compose_template_activate_cb(GtkWidget *widget,
448 gpointer data);
450 static void compose_ext_editor_cb (GtkAction *action,
451 gpointer data);
453 static gint compose_delete_cb (GtkWidget *widget,
454 GdkEventAny *event,
455 gpointer data);
457 static void compose_undo_cb (GtkAction *action,
458 gpointer data);
459 static void compose_redo_cb (GtkAction *action,
460 gpointer data);
461 static void compose_cut_cb (GtkAction *action,
462 gpointer data);
463 static void compose_copy_cb (GtkAction *action,
464 gpointer data);
465 static void compose_paste_cb (GtkAction *action,
466 gpointer data);
467 static void compose_paste_as_quote_cb (GtkAction *action,
468 gpointer data);
469 static void compose_paste_no_wrap_cb (GtkAction *action,
470 gpointer data);
471 static void compose_paste_wrap_cb (GtkAction *action,
472 gpointer data);
473 static void compose_allsel_cb (GtkAction *action,
474 gpointer data);
476 static void compose_advanced_action_cb (GtkAction *action,
477 gpointer data);
479 static void compose_grab_focus_cb (GtkWidget *widget,
480 Compose *compose);
482 static void compose_changed_cb (GtkTextBuffer *textbuf,
483 Compose *compose);
485 static void compose_wrap_cb (GtkAction *action,
486 gpointer data);
487 static void compose_wrap_all_cb (GtkAction *action,
488 gpointer data);
489 static void compose_find_cb (GtkAction *action,
490 gpointer data);
491 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
492 gpointer data);
493 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
494 gpointer data);
496 static void compose_toggle_ruler_cb (GtkToggleAction *action,
497 gpointer data);
498 static void compose_toggle_sign_cb (GtkToggleAction *action,
499 gpointer data);
500 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
501 gpointer data);
502 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
503 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
504 static void compose_activate_privacy_system (Compose *compose,
505 PrefsAccount *account,
506 gboolean warn);
507 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item);
508 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
509 gpointer data);
510 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
511 gpointer data);
512 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
513 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
514 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
516 static void compose_attach_drag_received_cb (GtkWidget *widget,
517 GdkDragContext *drag_context,
518 gint x,
519 gint y,
520 GtkSelectionData *data,
521 guint info,
522 guint time,
523 gpointer user_data);
524 static void compose_insert_drag_received_cb (GtkWidget *widget,
525 GdkDragContext *drag_context,
526 gint x,
527 gint y,
528 GtkSelectionData *data,
529 guint info,
530 guint time,
531 gpointer user_data);
532 static void compose_header_drag_received_cb (GtkWidget *widget,
533 GdkDragContext *drag_context,
534 gint x,
535 gint y,
536 GtkSelectionData *data,
537 guint info,
538 guint time,
539 gpointer user_data);
541 static gboolean compose_drag_drop (GtkWidget *widget,
542 GdkDragContext *drag_context,
543 gint x, gint y,
544 guint time, gpointer user_data);
545 static gboolean completion_set_focus_to_subject
546 (GtkWidget *widget,
547 GdkEventKey *event,
548 Compose *user_data);
550 static void text_inserted (GtkTextBuffer *buffer,
551 GtkTextIter *iter,
552 const gchar *text,
553 gint len,
554 Compose *compose);
555 static Compose *compose_generic_reply(MsgInfo *msginfo,
556 ComposeQuoteMode quote_mode,
557 gboolean to_all,
558 gboolean to_ml,
559 gboolean to_sender,
560 gboolean followup_and_reply_to,
561 const gchar *body);
563 static void compose_headerentry_changed_cb (GtkWidget *entry,
564 ComposeHeaderEntry *headerentry);
565 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
566 GdkEventKey *event,
567 ComposeHeaderEntry *headerentry);
568 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
569 ComposeHeaderEntry *headerentry);
571 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
573 static void compose_allow_user_actions (Compose *compose, gboolean allow);
575 static void compose_nothing_cb (GtkAction *action, gpointer data)
580 #if USE_ENCHANT
581 static void compose_check_all (GtkAction *action, gpointer data);
582 static void compose_highlight_all (GtkAction *action, gpointer data);
583 static void compose_check_backwards (GtkAction *action, gpointer data);
584 static void compose_check_forwards_go (GtkAction *action, gpointer data);
585 #endif
587 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
589 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
591 #ifdef USE_ENCHANT
592 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
593 FolderItem *folder_item);
594 #endif
595 static void compose_attach_update_label(Compose *compose);
596 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
597 gboolean respect_default_to);
598 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
599 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
601 static GtkActionEntry compose_popup_entries[] =
603 {"Compose", NULL, "Compose", NULL, NULL, NULL },
604 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
605 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
606 {"Compose/---", NULL, "---", NULL, NULL, NULL },
607 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
610 static GtkActionEntry compose_entries[] =
612 {"Menu", NULL, "Menu", NULL, NULL, NULL },
613 /* menus */
614 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
615 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
616 #if USE_ENCHANT
617 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
618 #endif
619 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
620 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
621 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
622 /* Message menu */
623 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
624 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
625 {"Message/---", NULL, "---", NULL, NULL, NULL },
627 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
628 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
629 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
630 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
631 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
632 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
633 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
634 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
635 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
636 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
638 /* Edit menu */
639 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
640 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
641 {"Edit/---", NULL, "---", NULL, NULL, NULL },
643 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
644 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
645 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
647 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
648 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
649 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
650 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
652 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
654 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
655 {"Edit/Advanced/BackChar", NULL, N_("Move a character backward"), "<shift><control>B", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
656 {"Edit/Advanced/ForwChar", NULL, N_("Move a character forward"), "<shift><control>F", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
657 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
658 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
659 {"Edit/Advanced/BegLine", NULL, N_("Move to beginning of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
660 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
661 {"Edit/Advanced/PrevLine", NULL, N_("Move to previous line"), "<control>P", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
662 {"Edit/Advanced/NextLine", NULL, N_("Move to next line"), "<control>N", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
663 {"Edit/Advanced/DelBackChar", NULL, N_("Delete a character backward"), "<control>H", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
664 {"Edit/Advanced/DelForwChar", NULL, N_("Delete a character forward"), "<control>D", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
665 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
666 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
667 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
668 {"Edit/Advanced/DelEndLine", NULL, N_("Delete to end of line"), "<control>K", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
670 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
671 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
673 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
674 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
675 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
676 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
677 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
678 #if USE_ENCHANT
679 /* Spelling menu */
680 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
681 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
682 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
683 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
685 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
686 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
687 #endif
689 /* Options menu */
690 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
691 {"Options/---", NULL, "---", NULL, NULL, NULL },
692 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
693 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
695 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
696 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
698 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
699 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
700 #define ENC_ACTION(cs_char,c_char,string) \
701 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
703 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
704 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
705 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
706 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
707 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
708 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
709 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
710 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
711 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
713 /* Tools menu */
714 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
716 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
717 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
718 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
719 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
721 /* Help menu */
722 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
725 static GtkToggleActionEntry compose_toggle_entries[] =
727 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
728 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
729 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
730 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
731 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
732 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
733 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
736 static GtkRadioActionEntry compose_radio_rm_entries[] =
738 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
739 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
740 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
741 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
744 static GtkRadioActionEntry compose_radio_prio_entries[] =
746 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
747 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
748 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
749 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
750 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
753 static GtkRadioActionEntry compose_radio_enc_entries[] =
755 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
777 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
778 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
779 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
780 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
781 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
782 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
783 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
784 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
785 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
786 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
787 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
790 static GtkTargetEntry compose_mime_types[] =
792 {"text/uri-list", 0, 0},
793 {"UTF8_STRING", 0, 0},
794 {"text/plain", 0, 0}
797 static gboolean compose_put_existing_to_front(MsgInfo *info)
799 const GList *compose_list = compose_get_compose_list();
800 const GList *elem = NULL;
802 if (compose_list) {
803 for (elem = compose_list; elem != NULL && elem->data != NULL;
804 elem = elem->next) {
805 Compose *c = (Compose*)elem->data;
807 if (!c->targetinfo || !c->targetinfo->msgid ||
808 !info->msgid)
809 continue;
811 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
812 gtkut_window_popup(c->window);
813 return TRUE;
817 return FALSE;
820 static GdkRGBA quote_color1 =
821 {0, 0, 0, 1};
822 static GdkRGBA quote_color2 =
823 {0, 0, 0, 1};
824 static GdkRGBA quote_color3 =
825 {0, 0, 0, 1};
827 static GdkRGBA quote_bgcolor1 =
828 {0, 0, 0, 1};
829 static GdkRGBA quote_bgcolor2 =
830 {0, 0, 0, 1};
831 static GdkRGBA quote_bgcolor3 =
832 {0, 0, 0, 1};
834 static GdkRGBA signature_color =
835 {0.5, 0.5, 0.5, 1};
837 static GdkRGBA uri_color =
838 {0, 0, 0, 1};
840 static void compose_create_tags(GtkTextView *text, Compose *compose)
842 GtkTextBuffer *buffer;
843 GdkRGBA black = { 0, 0, 0, 1 };
845 buffer = gtk_text_view_get_buffer(text);
847 if (prefs_common.enable_color) {
848 /* grab the quote colors, converting from an int to a GdkColor */
849 quote_color1 = prefs_common.color[COL_QUOTE_LEVEL1];
850 quote_color2 = prefs_common.color[COL_QUOTE_LEVEL2];
851 quote_color3 = prefs_common.color[COL_QUOTE_LEVEL3];
852 quote_bgcolor1 = prefs_common.color[COL_QUOTE_LEVEL1_BG];
853 quote_bgcolor2 = prefs_common.color[COL_QUOTE_LEVEL2_BG];
854 quote_bgcolor3 = prefs_common.color[COL_QUOTE_LEVEL3_BG];
855 signature_color = prefs_common.color[COL_SIGNATURE];
856 uri_color = prefs_common.color[COL_URI];
857 } else {
858 signature_color = quote_color1 = quote_color2 = quote_color3 =
859 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
862 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
863 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
864 "foreground-rgba", &quote_color1,
865 "paragraph-background-rgba", &quote_bgcolor1,
866 NULL);
867 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
868 "foreground-rgba", &quote_color2,
869 "paragraph-background-rgba", &quote_bgcolor2,
870 NULL);
871 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
872 "foreground-rgba", &quote_color3,
873 "paragraph-background-rgba", &quote_bgcolor3,
874 NULL);
875 } else {
876 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
877 "foreground-rgba", &quote_color1,
878 NULL);
879 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
880 "foreground-rgba", &quote_color2,
881 NULL);
882 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
883 "foreground-rgba", &quote_color3,
884 NULL);
887 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
888 "foreground-rgba", &signature_color,
889 NULL);
891 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
892 "foreground-rgba", &uri_color,
893 NULL);
894 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
895 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
898 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
899 GList *attach_files)
901 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
904 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
906 return compose_generic_new(account, mailto, item, NULL, NULL);
909 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
911 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
914 #define SCROLL_TO_CURSOR(compose) { \
915 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
916 gtk_text_view_get_buffer( \
917 GTK_TEXT_VIEW(compose->text))); \
918 gtk_text_view_scroll_mark_onscreen( \
919 GTK_TEXT_VIEW(compose->text), \
920 cmark); \
923 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
925 GtkEditable *entry;
926 if (folderidentifier) {
927 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
928 prefs_common.compose_save_to_history = add_history(
929 prefs_common.compose_save_to_history, folderidentifier);
930 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
931 prefs_common.compose_save_to_history);
934 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
935 if (folderidentifier)
936 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
937 else
938 gtk_entry_set_text(GTK_ENTRY(entry), "");
941 static gchar *compose_get_save_to(Compose *compose)
943 GtkEditable *entry;
944 gchar *result = NULL;
945 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
946 result = gtk_editable_get_chars(entry, 0, -1);
948 if (result) {
949 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
950 prefs_common.compose_save_to_history = add_history(
951 prefs_common.compose_save_to_history, result);
952 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
953 prefs_common.compose_save_to_history);
955 return result;
958 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
959 GList *attach_files, GList *listAddress )
961 Compose *compose;
962 GtkTextView *textview;
963 GtkTextBuffer *textbuf;
964 GtkTextIter iter;
965 const gchar *subject_format = NULL;
966 const gchar *body_format = NULL;
967 gchar *mailto_from = NULL;
968 PrefsAccount *mailto_account = NULL;
969 MsgInfo* dummyinfo = NULL;
970 gint cursor_pos = -1;
971 MailField mfield = NO_FIELD_PRESENT;
972 gchar* buf;
973 GtkTextMark *mark;
975 /* check if mailto defines a from */
976 if (mailto && *mailto != '\0') {
977 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
978 /* mailto defines a from, check if we can get account prefs from it,
979 if not, the account prefs will be guessed using other ways, but we'll keep
980 the from anyway */
981 if (mailto_from) {
982 mailto_account = account_find_from_address(mailto_from, TRUE);
983 if (mailto_account == NULL) {
984 gchar *tmp_from;
985 Xstrdup_a(tmp_from, mailto_from, return NULL);
986 extract_address(tmp_from);
987 mailto_account = account_find_from_address(tmp_from, TRUE);
990 if (mailto_account)
991 account = mailto_account;
994 /* if no account prefs set from mailto, set if from folder prefs (if any) */
995 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
996 account = account_find_from_id(item->prefs->default_account);
998 /* if no account prefs set, fallback to the current one */
999 if (!account) account = cur_account;
1000 cm_return_val_if_fail(account != NULL, NULL);
1002 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1003 compose_apply_folder_privacy_settings(compose, item);
1005 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1006 (account->default_encrypt || account->default_sign))
1007 COMPOSE_PRIVACY_WARNING();
1009 /* override from name if mailto asked for it */
1010 if (mailto_from) {
1011 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1012 g_free(mailto_from);
1013 } else
1014 /* override from name according to folder properties */
1015 if (item && item->prefs &&
1016 item->prefs->compose_with_format &&
1017 item->prefs->compose_override_from_format &&
1018 *item->prefs->compose_override_from_format != '\0') {
1020 gchar *tmp = NULL;
1021 gchar *buf = NULL;
1023 dummyinfo = compose_msginfo_new_from_compose(compose);
1025 /* decode \-escape sequences in the internal representation of the quote format */
1026 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1027 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1029 #ifdef USE_ENCHANT
1030 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1031 compose->gtkaspell);
1032 #else
1033 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1034 #endif
1035 quote_fmt_scan_string(tmp);
1036 quote_fmt_parse();
1038 buf = quote_fmt_get_buffer();
1039 if (buf == NULL)
1040 alertpanel_error(_("New message From format error."));
1041 else
1042 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1043 quote_fmt_reset_vartable();
1044 quote_fmtlex_destroy();
1046 g_free(tmp);
1049 compose->replyinfo = NULL;
1050 compose->fwdinfo = NULL;
1052 textview = GTK_TEXT_VIEW(compose->text);
1053 textbuf = gtk_text_view_get_buffer(textview);
1054 compose_create_tags(textview, compose);
1056 undo_block(compose->undostruct);
1057 #ifdef USE_ENCHANT
1058 compose_set_dictionaries_from_folder_prefs(compose, item);
1059 #endif
1061 if (account->auto_sig)
1062 compose_insert_sig(compose, FALSE);
1063 gtk_text_buffer_get_start_iter(textbuf, &iter);
1064 gtk_text_buffer_place_cursor(textbuf, &iter);
1066 if (account->protocol != A_NNTP) {
1067 if (mailto && *mailto != '\0') {
1068 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1070 } else {
1071 compose_set_folder_prefs(compose, item, TRUE);
1073 if (item && item->ret_rcpt) {
1074 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1076 } else {
1077 if (mailto && *mailto != '\0') {
1078 if (!strchr(mailto, '@'))
1079 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1080 else
1081 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1082 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1083 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1084 mfield = TO_FIELD_PRESENT;
1087 * CLAWS: just don't allow return receipt request, even if the user
1088 * may want to send an email. simple but foolproof.
1090 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1092 compose_add_field_list( compose, listAddress );
1094 if (item && item->prefs && item->prefs->compose_with_format) {
1095 subject_format = item->prefs->compose_subject_format;
1096 body_format = item->prefs->compose_body_format;
1097 } else if (account->compose_with_format) {
1098 subject_format = account->compose_subject_format;
1099 body_format = account->compose_body_format;
1100 } else if (prefs_common.compose_with_format) {
1101 subject_format = prefs_common.compose_subject_format;
1102 body_format = prefs_common.compose_body_format;
1105 if (subject_format || body_format) {
1107 if ( subject_format
1108 && *subject_format != '\0' )
1110 gchar *subject = NULL;
1111 gchar *tmp = NULL;
1112 gchar *buf = NULL;
1114 if (!dummyinfo)
1115 dummyinfo = compose_msginfo_new_from_compose(compose);
1117 /* decode \-escape sequences in the internal representation of the quote format */
1118 tmp = g_malloc(strlen(subject_format)+1);
1119 pref_get_unescaped_pref(tmp, subject_format);
1121 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1122 #ifdef USE_ENCHANT
1123 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1124 compose->gtkaspell);
1125 #else
1126 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1127 #endif
1128 quote_fmt_scan_string(tmp);
1129 quote_fmt_parse();
1131 buf = quote_fmt_get_buffer();
1132 if (buf == NULL)
1133 alertpanel_error(_("New message subject format error."));
1134 else
1135 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1136 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1137 quote_fmt_reset_vartable();
1138 quote_fmtlex_destroy();
1140 g_free(subject);
1141 g_free(tmp);
1142 mfield = SUBJECT_FIELD_PRESENT;
1145 if ( body_format
1146 && *body_format != '\0' )
1148 GtkTextView *text;
1149 GtkTextBuffer *buffer;
1150 GtkTextIter start, end;
1151 gchar *tmp = NULL;
1153 if (!dummyinfo)
1154 dummyinfo = compose_msginfo_new_from_compose(compose);
1156 text = GTK_TEXT_VIEW(compose->text);
1157 buffer = gtk_text_view_get_buffer(text);
1158 gtk_text_buffer_get_start_iter(buffer, &start);
1159 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1160 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1162 compose_quote_fmt(compose, dummyinfo,
1163 body_format,
1164 NULL, tmp, FALSE, TRUE,
1165 _("The body of the \"New message\" template has an error at line %d."));
1166 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1167 quote_fmt_reset_vartable();
1169 g_free(tmp);
1170 #ifdef USE_ENCHANT
1171 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1172 gtkaspell_highlight_all(compose->gtkaspell);
1173 #endif
1174 mfield = BODY_FIELD_PRESENT;
1178 procmsg_msginfo_free( &dummyinfo );
1180 if (attach_files) {
1181 GList *curr;
1182 AttachInfo *ainfo;
1184 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1185 ainfo = (AttachInfo *) curr->data;
1186 if (ainfo->insert)
1187 compose_insert_file(compose, ainfo->file);
1188 else
1189 compose_attach_append(compose, ainfo->file, ainfo->file,
1190 ainfo->content_type, ainfo->charset);
1194 compose_show_first_last_header(compose, TRUE);
1196 /* Set save folder */
1197 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1198 gchar *folderidentifier;
1200 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1201 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1202 folderidentifier = folder_item_get_identifier(item);
1203 compose_set_save_to(compose, folderidentifier);
1204 g_free(folderidentifier);
1207 /* Place cursor according to provided input (mfield) */
1208 switch (mfield) {
1209 case NO_FIELD_PRESENT:
1210 if (compose->header_last)
1211 gtk_widget_grab_focus(compose->header_last->entry);
1212 break;
1213 case TO_FIELD_PRESENT:
1214 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1215 if (buf) {
1216 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1217 g_free(buf);
1219 gtk_widget_grab_focus(compose->subject_entry);
1220 break;
1221 case SUBJECT_FIELD_PRESENT:
1222 textview = GTK_TEXT_VIEW(compose->text);
1223 if (!textview)
1224 break;
1225 textbuf = gtk_text_view_get_buffer(textview);
1226 if (!textbuf)
1227 break;
1228 mark = gtk_text_buffer_get_insert(textbuf);
1229 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1230 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1232 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1233 * only defers where it comes to the variable body
1234 * is not null. If no body is present compose->text
1235 * will be null in which case you cannot place the
1236 * cursor inside the component so. An empty component
1237 * is therefore created before placing the cursor
1239 case BODY_FIELD_PRESENT:
1240 cursor_pos = quote_fmt_get_cursor_pos();
1241 if (cursor_pos == -1)
1242 gtk_widget_grab_focus(compose->header_last->entry);
1243 else
1244 gtk_widget_grab_focus(compose->text);
1245 break;
1248 undo_unblock(compose->undostruct);
1250 if (prefs_common.auto_exteditor)
1251 compose_exec_ext_editor(compose);
1253 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1255 SCROLL_TO_CURSOR(compose);
1257 compose->modified = FALSE;
1258 compose_set_title(compose);
1260 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1262 return compose;
1265 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1266 gboolean override_pref, const gchar *system)
1268 const gchar *privacy = NULL;
1270 cm_return_if_fail(compose != NULL);
1271 cm_return_if_fail(account != NULL);
1273 if (privacy_system_can_encrypt(compose->privacy_system) == FALSE ||
1274 (override_pref == FALSE && account->default_encrypt_reply == FALSE))
1275 return;
1277 if (account->default_privacy_system && strlen(account->default_privacy_system))
1278 privacy = account->default_privacy_system;
1279 else if (system)
1280 privacy = system;
1281 else {
1282 GSList *privacy_avail = privacy_get_system_ids();
1283 if (privacy_avail && g_slist_length(privacy_avail)) {
1284 privacy = (gchar *)(privacy_avail->data);
1286 g_slist_free_full(privacy_avail, g_free);
1288 if (privacy != NULL) {
1289 if (system) {
1290 g_free(compose->privacy_system);
1291 compose->privacy_system = NULL;
1292 g_free(compose->encdata);
1293 compose->encdata = NULL;
1295 if (compose->privacy_system == NULL)
1296 compose->privacy_system = g_strdup(privacy);
1297 else if (*(compose->privacy_system) == '\0') {
1298 g_free(compose->privacy_system);
1299 g_free(compose->encdata);
1300 compose->encdata = NULL;
1301 compose->privacy_system = g_strdup(privacy);
1303 compose_update_privacy_system_menu_item(compose, FALSE);
1304 compose_use_encryption(compose, TRUE);
1308 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1310 const gchar *privacy = NULL;
1311 if (privacy_system_can_sign(compose->privacy_system) == FALSE)
1312 return;
1314 if (account->default_privacy_system && strlen(account->default_privacy_system))
1315 privacy = account->default_privacy_system;
1316 else if (system)
1317 privacy = system;
1318 else {
1319 GSList *privacy_avail = privacy_get_system_ids();
1320 if (privacy_avail && g_slist_length(privacy_avail)) {
1321 privacy = (gchar *)(privacy_avail->data);
1325 if (privacy != NULL) {
1326 if (system) {
1327 g_free(compose->privacy_system);
1328 compose->privacy_system = NULL;
1329 g_free(compose->encdata);
1330 compose->encdata = NULL;
1332 if (compose->privacy_system == NULL)
1333 compose->privacy_system = g_strdup(privacy);
1334 compose_update_privacy_system_menu_item(compose, FALSE);
1335 compose_use_signing(compose, TRUE);
1339 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1341 MsgInfo *msginfo;
1342 guint list_len;
1343 Compose *compose = NULL;
1345 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1347 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1348 cm_return_val_if_fail(msginfo != NULL, NULL);
1350 list_len = g_slist_length(msginfo_list);
1352 switch (mode) {
1353 case COMPOSE_REPLY:
1354 case COMPOSE_REPLY_TO_ADDRESS:
1355 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1356 FALSE, prefs_common.default_reply_list, FALSE, body);
1357 break;
1358 case COMPOSE_REPLY_WITH_QUOTE:
1359 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1360 FALSE, prefs_common.default_reply_list, FALSE, body);
1361 break;
1362 case COMPOSE_REPLY_WITHOUT_QUOTE:
1363 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1364 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1365 break;
1366 case COMPOSE_REPLY_TO_SENDER:
1367 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1368 FALSE, FALSE, TRUE, body);
1369 break;
1370 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1371 compose = compose_followup_and_reply_to(msginfo,
1372 COMPOSE_QUOTE_CHECK,
1373 FALSE, FALSE, body);
1374 break;
1375 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1376 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1377 FALSE, FALSE, TRUE, body);
1378 break;
1379 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1380 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1381 FALSE, FALSE, TRUE, NULL);
1382 break;
1383 case COMPOSE_REPLY_TO_ALL:
1384 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1385 TRUE, FALSE, FALSE, body);
1386 break;
1387 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1388 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1389 TRUE, FALSE, FALSE, body);
1390 break;
1391 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1392 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1393 TRUE, FALSE, FALSE, NULL);
1394 break;
1395 case COMPOSE_REPLY_TO_LIST:
1396 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1397 FALSE, TRUE, FALSE, body);
1398 break;
1399 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1400 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1401 FALSE, TRUE, FALSE, body);
1402 break;
1403 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1404 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1405 FALSE, TRUE, FALSE, NULL);
1406 break;
1407 case COMPOSE_FORWARD:
1408 if (prefs_common.forward_as_attachment) {
1409 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1410 return compose;
1411 } else {
1412 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1413 return compose;
1415 break;
1416 case COMPOSE_FORWARD_INLINE:
1417 /* check if we reply to more than one Message */
1418 if (list_len == 1) {
1419 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1420 break;
1422 /* more messages FALL THROUGH */
1423 case COMPOSE_FORWARD_AS_ATTACH:
1424 compose = compose_forward_multiple(NULL, msginfo_list);
1425 break;
1426 case COMPOSE_REDIRECT:
1427 compose = compose_redirect(NULL, msginfo, FALSE);
1428 break;
1429 default:
1430 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1433 if (compose == NULL) {
1434 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1435 return NULL;
1438 compose->rmode = mode;
1439 switch (compose->rmode) {
1440 case COMPOSE_REPLY:
1441 case COMPOSE_REPLY_WITH_QUOTE:
1442 case COMPOSE_REPLY_WITHOUT_QUOTE:
1443 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1444 debug_print("reply mode Normal\n");
1445 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1446 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1447 break;
1448 case COMPOSE_REPLY_TO_SENDER:
1449 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1450 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1451 debug_print("reply mode Sender\n");
1452 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1453 break;
1454 case COMPOSE_REPLY_TO_ALL:
1455 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1456 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1457 debug_print("reply mode All\n");
1458 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1459 break;
1460 case COMPOSE_REPLY_TO_LIST:
1461 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1462 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1463 debug_print("reply mode List\n");
1464 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1465 break;
1466 case COMPOSE_REPLY_TO_ADDRESS:
1467 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1468 break;
1469 default:
1470 break;
1472 return compose;
1475 static Compose *compose_reply(MsgInfo *msginfo,
1476 ComposeQuoteMode quote_mode,
1477 gboolean to_all,
1478 gboolean to_ml,
1479 gboolean to_sender,
1480 const gchar *body)
1482 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1483 to_sender, FALSE, body);
1486 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1487 ComposeQuoteMode quote_mode,
1488 gboolean to_all,
1489 gboolean to_sender,
1490 const gchar *body)
1492 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1493 to_sender, TRUE, body);
1496 static void compose_extract_original_charset(Compose *compose)
1498 MsgInfo *info = NULL;
1499 if (compose->replyinfo) {
1500 info = compose->replyinfo;
1501 } else if (compose->fwdinfo) {
1502 info = compose->fwdinfo;
1503 } else if (compose->targetinfo) {
1504 info = compose->targetinfo;
1506 if (info) {
1507 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1508 MimeInfo *partinfo = mimeinfo;
1509 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1510 partinfo = procmime_mimeinfo_next(partinfo);
1511 if (partinfo) {
1512 compose->orig_charset =
1513 g_strdup(procmime_mimeinfo_get_parameter(
1514 partinfo, "charset"));
1516 procmime_mimeinfo_free_all(&mimeinfo);
1520 #define SIGNAL_BLOCK(buffer) { \
1521 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1522 G_CALLBACK(compose_changed_cb), \
1523 compose); \
1524 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1525 G_CALLBACK(text_inserted), \
1526 compose); \
1529 #define SIGNAL_UNBLOCK(buffer) { \
1530 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1531 G_CALLBACK(compose_changed_cb), \
1532 compose); \
1533 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1534 G_CALLBACK(text_inserted), \
1535 compose); \
1538 static Compose *compose_generic_reply(MsgInfo *msginfo,
1539 ComposeQuoteMode quote_mode,
1540 gboolean to_all, gboolean to_ml,
1541 gboolean to_sender,
1542 gboolean followup_and_reply_to,
1543 const gchar *body)
1545 Compose *compose;
1546 PrefsAccount *account = NULL;
1547 GtkTextView *textview;
1548 GtkTextBuffer *textbuf;
1549 gboolean quote = FALSE;
1550 const gchar *qmark = NULL;
1551 const gchar *body_fmt = NULL;
1552 gchar *s_system = NULL;
1553 START_TIMING("");
1554 cm_return_val_if_fail(msginfo != NULL, NULL);
1555 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1557 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1559 cm_return_val_if_fail(account != NULL, NULL);
1561 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1562 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1564 compose->updating = TRUE;
1566 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1567 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1569 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1570 if (!compose->replyinfo)
1571 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1573 compose_extract_original_charset(compose);
1575 if (msginfo->folder && msginfo->folder->ret_rcpt)
1576 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1578 /* Set save folder */
1579 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1580 gchar *folderidentifier;
1582 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1583 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1584 folderidentifier = folder_item_get_identifier(msginfo->folder);
1585 compose_set_save_to(compose, folderidentifier);
1586 g_free(folderidentifier);
1589 if (compose_parse_header(compose, msginfo) < 0) {
1590 compose->updating = FALSE;
1591 compose_destroy(compose);
1592 return NULL;
1595 /* override from name according to folder properties */
1596 if (msginfo->folder && msginfo->folder->prefs &&
1597 msginfo->folder->prefs->reply_with_format &&
1598 msginfo->folder->prefs->reply_override_from_format &&
1599 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1601 gchar *tmp = NULL;
1602 gchar *buf = NULL;
1604 /* decode \-escape sequences in the internal representation of the quote format */
1605 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1606 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1608 #ifdef USE_ENCHANT
1609 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1610 compose->gtkaspell);
1611 #else
1612 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1613 #endif
1614 quote_fmt_scan_string(tmp);
1615 quote_fmt_parse();
1617 buf = quote_fmt_get_buffer();
1618 if (buf == NULL)
1619 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1620 else
1621 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1622 quote_fmt_reset_vartable();
1623 quote_fmtlex_destroy();
1625 g_free(tmp);
1628 textview = (GTK_TEXT_VIEW(compose->text));
1629 textbuf = gtk_text_view_get_buffer(textview);
1630 compose_create_tags(textview, compose);
1632 undo_block(compose->undostruct);
1633 #ifdef USE_ENCHANT
1634 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1635 gtkaspell_block_check(compose->gtkaspell);
1636 #endif
1638 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1639 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1640 /* use the reply format of folder (if enabled), or the account's one
1641 (if enabled) or fallback to the global reply format, which is always
1642 enabled (even if empty), and use the relevant quotemark */
1643 quote = TRUE;
1644 if (msginfo->folder && msginfo->folder->prefs &&
1645 msginfo->folder->prefs->reply_with_format) {
1646 qmark = msginfo->folder->prefs->reply_quotemark;
1647 body_fmt = msginfo->folder->prefs->reply_body_format;
1649 } else if (account->reply_with_format) {
1650 qmark = account->reply_quotemark;
1651 body_fmt = account->reply_body_format;
1653 } else {
1654 qmark = prefs_common.quotemark;
1655 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1656 body_fmt = gettext(prefs_common.quotefmt);
1657 else
1658 body_fmt = "";
1662 if (quote) {
1663 /* empty quotemark is not allowed */
1664 if (qmark == NULL || *qmark == '\0')
1665 qmark = "> ";
1666 compose_quote_fmt(compose, compose->replyinfo,
1667 body_fmt, qmark, body, FALSE, TRUE,
1668 _("The body of the \"Reply\" template has an error at line %d."));
1669 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1670 quote_fmt_reset_vartable();
1673 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1674 compose_force_encryption(compose, account, FALSE, s_system);
1677 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1678 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1679 compose_force_signing(compose, account, s_system);
1681 g_free(s_system);
1683 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1684 ((account->default_encrypt || account->default_sign) ||
1685 (account->default_encrypt_reply && MSG_IS_ENCRYPTED(compose->replyinfo->flags)) ||
1686 (account->default_sign_reply && MSG_IS_SIGNED(compose->replyinfo->flags))))
1687 COMPOSE_PRIVACY_WARNING();
1689 SIGNAL_BLOCK(textbuf);
1691 if (account->auto_sig)
1692 compose_insert_sig(compose, FALSE);
1694 compose_wrap_all(compose);
1696 #ifdef USE_ENCHANT
1697 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1698 gtkaspell_highlight_all(compose->gtkaspell);
1699 gtkaspell_unblock_check(compose->gtkaspell);
1700 #endif
1701 SIGNAL_UNBLOCK(textbuf);
1703 gtk_widget_grab_focus(compose->text);
1705 undo_unblock(compose->undostruct);
1707 if (prefs_common.auto_exteditor)
1708 compose_exec_ext_editor(compose);
1710 compose->modified = FALSE;
1711 compose_set_title(compose);
1713 compose->updating = FALSE;
1714 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1715 SCROLL_TO_CURSOR(compose);
1717 if (compose->deferred_destroy) {
1718 compose_destroy(compose);
1719 return NULL;
1721 END_TIMING();
1723 return compose;
1726 #define INSERT_FW_HEADER(var, hdr) \
1727 if (msginfo->var && *msginfo->var) { \
1728 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1729 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1730 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1733 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1734 gboolean as_attach, const gchar *body,
1735 gboolean no_extedit,
1736 gboolean batch)
1738 Compose *compose;
1739 GtkTextView *textview;
1740 GtkTextBuffer *textbuf;
1741 gint cursor_pos = -1;
1742 ComposeMode mode;
1744 cm_return_val_if_fail(msginfo != NULL, NULL);
1745 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1747 if (!account && !(account = compose_find_account(msginfo)))
1748 account = cur_account;
1750 if (!prefs_common.forward_as_attachment)
1751 mode = COMPOSE_FORWARD_INLINE;
1752 else
1753 mode = COMPOSE_FORWARD;
1754 compose = compose_create(account, msginfo->folder, mode, batch);
1755 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1757 compose->updating = TRUE;
1758 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1759 if (!compose->fwdinfo)
1760 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1762 compose_extract_original_charset(compose);
1764 if (msginfo->subject && *msginfo->subject) {
1765 gchar *buf, *buf2, *p;
1767 buf = p = g_strdup(msginfo->subject);
1768 p += subject_get_prefix_length(p);
1769 memmove(buf, p, strlen(p) + 1);
1771 buf2 = g_strdup_printf("Fw: %s", buf);
1772 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1774 g_free(buf);
1775 g_free(buf2);
1778 /* override from name according to folder properties */
1779 if (msginfo->folder && msginfo->folder->prefs &&
1780 msginfo->folder->prefs->forward_with_format &&
1781 msginfo->folder->prefs->forward_override_from_format &&
1782 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1784 gchar *tmp = NULL;
1785 gchar *buf = NULL;
1786 MsgInfo *full_msginfo = NULL;
1788 if (!as_attach)
1789 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1790 if (!full_msginfo)
1791 full_msginfo = procmsg_msginfo_copy(msginfo);
1793 /* decode \-escape sequences in the internal representation of the quote format */
1794 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1795 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1797 #ifdef USE_ENCHANT
1798 gtkaspell_block_check(compose->gtkaspell);
1799 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1800 compose->gtkaspell);
1801 #else
1802 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1803 #endif
1804 quote_fmt_scan_string(tmp);
1805 quote_fmt_parse();
1807 buf = quote_fmt_get_buffer();
1808 if (buf == NULL)
1809 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1810 else
1811 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1812 quote_fmt_reset_vartable();
1813 quote_fmtlex_destroy();
1815 g_free(tmp);
1816 procmsg_msginfo_free(&full_msginfo);
1819 textview = GTK_TEXT_VIEW(compose->text);
1820 textbuf = gtk_text_view_get_buffer(textview);
1821 compose_create_tags(textview, compose);
1823 undo_block(compose->undostruct);
1824 if (as_attach) {
1825 gchar *msgfile;
1827 msgfile = procmsg_get_message_file(msginfo);
1828 if (!is_file_exist(msgfile))
1829 g_warning("%s: file does not exist", msgfile);
1830 else
1831 compose_attach_append(compose, msgfile, msgfile,
1832 "message/rfc822", NULL);
1834 g_free(msgfile);
1835 } else {
1836 const gchar *qmark = NULL;
1837 const gchar *body_fmt = NULL;
1838 MsgInfo *full_msginfo;
1840 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1841 if (!full_msginfo)
1842 full_msginfo = procmsg_msginfo_copy(msginfo);
1844 /* use the forward format of folder (if enabled), or the account's one
1845 (if enabled) or fallback to the global forward format, which is always
1846 enabled (even if empty), and use the relevant quotemark */
1847 if (msginfo->folder && msginfo->folder->prefs &&
1848 msginfo->folder->prefs->forward_with_format) {
1849 qmark = msginfo->folder->prefs->forward_quotemark;
1850 body_fmt = msginfo->folder->prefs->forward_body_format;
1852 } else if (account->forward_with_format) {
1853 qmark = account->forward_quotemark;
1854 body_fmt = account->forward_body_format;
1856 } else {
1857 qmark = prefs_common.fw_quotemark;
1858 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1859 body_fmt = gettext(prefs_common.fw_quotefmt);
1860 else
1861 body_fmt = "";
1864 /* empty quotemark is not allowed */
1865 if (qmark == NULL || *qmark == '\0')
1866 qmark = "> ";
1868 compose_quote_fmt(compose, full_msginfo,
1869 body_fmt, qmark, body, FALSE, TRUE,
1870 _("The body of the \"Forward\" template has an error at line %d."));
1871 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1872 quote_fmt_reset_vartable();
1873 compose_attach_parts(compose, msginfo);
1875 procmsg_msginfo_free(&full_msginfo);
1878 SIGNAL_BLOCK(textbuf);
1880 if (account->auto_sig)
1881 compose_insert_sig(compose, FALSE);
1883 compose_wrap_all(compose);
1885 #ifdef USE_ENCHANT
1886 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1887 gtkaspell_highlight_all(compose->gtkaspell);
1888 gtkaspell_unblock_check(compose->gtkaspell);
1889 #endif
1890 SIGNAL_UNBLOCK(textbuf);
1892 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1893 (account->default_encrypt || account->default_sign))
1894 COMPOSE_PRIVACY_WARNING();
1896 cursor_pos = quote_fmt_get_cursor_pos();
1897 if (cursor_pos == -1)
1898 gtk_widget_grab_focus(compose->header_last->entry);
1899 else
1900 gtk_widget_grab_focus(compose->text);
1902 if (!no_extedit && prefs_common.auto_exteditor)
1903 compose_exec_ext_editor(compose);
1905 /*save folder*/
1906 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1907 gchar *folderidentifier;
1909 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1910 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1911 folderidentifier = folder_item_get_identifier(msginfo->folder);
1912 compose_set_save_to(compose, folderidentifier);
1913 g_free(folderidentifier);
1916 undo_unblock(compose->undostruct);
1918 compose->modified = FALSE;
1919 compose_set_title(compose);
1921 compose->updating = FALSE;
1922 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1923 SCROLL_TO_CURSOR(compose);
1925 if (compose->deferred_destroy) {
1926 compose_destroy(compose);
1927 return NULL;
1930 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1932 return compose;
1935 #undef INSERT_FW_HEADER
1937 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1939 Compose *compose;
1940 GtkTextView *textview;
1941 GtkTextBuffer *textbuf;
1942 GtkTextIter iter;
1943 GSList *msginfo;
1944 gchar *msgfile;
1945 gboolean single_mail = TRUE;
1947 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1949 if (g_slist_length(msginfo_list) > 1)
1950 single_mail = FALSE;
1952 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1953 if (((MsgInfo *)msginfo->data)->folder == NULL)
1954 return NULL;
1956 /* guess account from first selected message */
1957 if (!account &&
1958 !(account = compose_find_account(msginfo_list->data)))
1959 account = cur_account;
1961 cm_return_val_if_fail(account != NULL, NULL);
1963 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1964 if (msginfo->data) {
1965 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1966 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1970 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1971 g_warning("no msginfo_list");
1972 return NULL;
1975 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1976 compose_apply_folder_privacy_settings(compose, ((MsgInfo *)msginfo_list->data)->folder);
1977 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1978 (account->default_encrypt || account->default_sign))
1979 COMPOSE_PRIVACY_WARNING();
1981 compose->updating = TRUE;
1983 /* override from name according to folder properties */
1984 if (msginfo_list->data) {
1985 MsgInfo *msginfo = msginfo_list->data;
1987 if (msginfo->folder && msginfo->folder->prefs &&
1988 msginfo->folder->prefs->forward_with_format &&
1989 msginfo->folder->prefs->forward_override_from_format &&
1990 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1992 gchar *tmp = NULL;
1993 gchar *buf = NULL;
1995 /* decode \-escape sequences in the internal representation of the quote format */
1996 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1997 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1999 #ifdef USE_ENCHANT
2000 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2001 compose->gtkaspell);
2002 #else
2003 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2004 #endif
2005 quote_fmt_scan_string(tmp);
2006 quote_fmt_parse();
2008 buf = quote_fmt_get_buffer();
2009 if (buf == NULL)
2010 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2011 else
2012 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2013 quote_fmt_reset_vartable();
2014 quote_fmtlex_destroy();
2016 g_free(tmp);
2020 textview = GTK_TEXT_VIEW(compose->text);
2021 textbuf = gtk_text_view_get_buffer(textview);
2022 compose_create_tags(textview, compose);
2024 undo_block(compose->undostruct);
2025 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2026 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2028 if (!is_file_exist(msgfile))
2029 g_warning("%s: file does not exist", msgfile);
2030 else
2031 compose_attach_append(compose, msgfile, msgfile,
2032 "message/rfc822", NULL);
2033 g_free(msgfile);
2036 if (single_mail) {
2037 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2038 if (info->subject && *info->subject) {
2039 gchar *buf, *buf2, *p;
2041 buf = p = g_strdup(info->subject);
2042 p += subject_get_prefix_length(p);
2043 memmove(buf, p, strlen(p) + 1);
2045 buf2 = g_strdup_printf("Fw: %s", buf);
2046 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2048 g_free(buf);
2049 g_free(buf2);
2051 } else {
2052 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2053 _("Fw: multiple emails"));
2056 SIGNAL_BLOCK(textbuf);
2058 if (account->auto_sig)
2059 compose_insert_sig(compose, FALSE);
2061 compose_wrap_all(compose);
2063 SIGNAL_UNBLOCK(textbuf);
2065 gtk_text_buffer_get_start_iter(textbuf, &iter);
2066 gtk_text_buffer_place_cursor(textbuf, &iter);
2068 if (prefs_common.auto_exteditor)
2069 compose_exec_ext_editor(compose);
2071 gtk_widget_grab_focus(compose->header_last->entry);
2072 undo_unblock(compose->undostruct);
2073 compose->modified = FALSE;
2074 compose_set_title(compose);
2076 compose->updating = FALSE;
2077 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2078 SCROLL_TO_CURSOR(compose);
2080 if (compose->deferred_destroy) {
2081 compose_destroy(compose);
2082 return NULL;
2085 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2087 return compose;
2090 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2092 GtkTextIter start = *iter;
2093 GtkTextIter end_iter;
2094 int start_pos = gtk_text_iter_get_offset(&start);
2095 gchar *str = NULL;
2096 if (!compose->account->sig_sep)
2097 return FALSE;
2099 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2100 start_pos+strlen(compose->account->sig_sep));
2102 /* check sig separator */
2103 str = gtk_text_iter_get_text(&start, &end_iter);
2104 if (!strcmp(str, compose->account->sig_sep)) {
2105 gchar *tmp = NULL;
2106 /* check end of line (\n) */
2107 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2108 start_pos+strlen(compose->account->sig_sep));
2109 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2110 start_pos+strlen(compose->account->sig_sep)+1);
2111 tmp = gtk_text_iter_get_text(&start, &end_iter);
2112 if (!strcmp(tmp,"\n")) {
2113 g_free(str);
2114 g_free(tmp);
2115 return TRUE;
2117 g_free(tmp);
2119 g_free(str);
2121 return FALSE;
2124 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2126 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2127 Compose *compose = (Compose *)data;
2128 FolderItem *old_item = NULL;
2129 FolderItem *new_item = NULL;
2130 gchar *old_id, *new_id;
2132 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2133 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2134 return FALSE;
2136 old_item = hookdata->item;
2137 new_item = hookdata->item2;
2139 old_id = folder_item_get_identifier(old_item);
2140 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2142 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2143 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2144 compose->targetinfo->folder = new_item;
2147 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2148 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2149 compose->replyinfo->folder = new_item;
2152 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2153 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2154 compose->fwdinfo->folder = new_item;
2157 g_free(old_id);
2158 g_free(new_id);
2159 return FALSE;
2162 static void compose_colorize_signature(Compose *compose)
2164 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2165 GtkTextIter iter;
2166 GtkTextIter end_iter;
2167 gtk_text_buffer_get_start_iter(buffer, &iter);
2168 while (gtk_text_iter_forward_line(&iter))
2169 if (compose_is_sig_separator(compose, buffer, &iter)) {
2170 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2171 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2175 #define BLOCK_WRAP() { \
2176 prev_autowrap = compose->autowrap; \
2177 buffer = gtk_text_view_get_buffer( \
2178 GTK_TEXT_VIEW(compose->text)); \
2179 compose->autowrap = FALSE; \
2181 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2182 G_CALLBACK(compose_changed_cb), \
2183 compose); \
2184 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2185 G_CALLBACK(text_inserted), \
2186 compose); \
2188 #define UNBLOCK_WRAP() { \
2189 compose->autowrap = prev_autowrap; \
2190 if (compose->autowrap) { \
2191 gint old = compose->draft_timeout_tag; \
2192 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2193 compose_wrap_all(compose); \
2194 compose->draft_timeout_tag = old; \
2197 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2198 G_CALLBACK(compose_changed_cb), \
2199 compose); \
2200 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2201 G_CALLBACK(text_inserted), \
2202 compose); \
2205 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2207 Compose *compose = NULL;
2208 PrefsAccount *account = NULL;
2209 GtkTextView *textview;
2210 GtkTextBuffer *textbuf;
2211 GtkTextMark *mark;
2212 GtkTextIter iter;
2213 FILE *fp;
2214 gboolean use_signing = FALSE;
2215 gboolean use_encryption = FALSE;
2216 gchar *privacy_system = NULL;
2217 int priority = PRIORITY_NORMAL;
2218 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2219 gboolean autowrap = prefs_common.autowrap;
2220 gboolean autoindent = prefs_common.auto_indent;
2221 HeaderEntry *manual_headers = NULL;
2223 cm_return_val_if_fail(msginfo != NULL, NULL);
2224 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2226 if (compose_put_existing_to_front(msginfo)) {
2227 return NULL;
2230 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2231 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2232 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2233 gchar *queueheader_buf = NULL;
2234 gint id, param;
2236 /* Select Account from queue headers */
2237 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2238 "X-Claws-Account-Id:")) {
2239 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2240 account = account_find_from_id(id);
2241 g_free(queueheader_buf);
2243 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2244 "X-Sylpheed-Account-Id:")) {
2245 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2246 account = account_find_from_id(id);
2247 g_free(queueheader_buf);
2249 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2250 "NAID:")) {
2251 id = atoi(&queueheader_buf[strlen("NAID:")]);
2252 account = account_find_from_id(id);
2253 g_free(queueheader_buf);
2255 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2256 "MAID:")) {
2257 id = atoi(&queueheader_buf[strlen("MAID:")]);
2258 account = account_find_from_id(id);
2259 g_free(queueheader_buf);
2261 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2262 "S:")) {
2263 account = account_find_from_address(queueheader_buf, FALSE);
2264 g_free(queueheader_buf);
2266 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2267 "X-Claws-Sign:")) {
2268 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2269 use_signing = param;
2270 g_free(queueheader_buf);
2272 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2273 "X-Sylpheed-Sign:")) {
2274 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2275 use_signing = param;
2276 g_free(queueheader_buf);
2278 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2279 "X-Claws-Encrypt:")) {
2280 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2281 use_encryption = param;
2282 g_free(queueheader_buf);
2284 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2285 "X-Sylpheed-Encrypt:")) {
2286 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2287 use_encryption = param;
2288 g_free(queueheader_buf);
2290 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2291 "X-Claws-Auto-Wrapping:")) {
2292 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2293 autowrap = param;
2294 g_free(queueheader_buf);
2296 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2297 "X-Claws-Auto-Indent:")) {
2298 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2299 autoindent = param;
2300 g_free(queueheader_buf);
2302 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2303 "X-Claws-Privacy-System:")) {
2304 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2305 g_free(queueheader_buf);
2307 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2308 "X-Sylpheed-Privacy-System:")) {
2309 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2310 g_free(queueheader_buf);
2312 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2313 "X-Priority: ")) {
2314 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2315 priority = param;
2316 g_free(queueheader_buf);
2318 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2319 "RMID:")) {
2320 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2321 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2322 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2323 if (orig_item != NULL) {
2324 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2327 if (tokens)
2328 g_strfreev(tokens);
2329 g_free(queueheader_buf);
2331 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2332 "FMID:")) {
2333 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2334 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2335 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2336 if (orig_item != NULL) {
2337 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2340 if (tokens)
2341 g_strfreev(tokens);
2342 g_free(queueheader_buf);
2344 /* Get manual headers */
2345 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2346 "X-Claws-Manual-Headers:")) {
2347 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2348 if (listmh && *listmh != '\0') {
2349 debug_print("Got manual headers: %s\n", listmh);
2350 manual_headers = procheader_entries_from_str(listmh);
2352 if (listmh)
2353 g_free(listmh);
2354 g_free(queueheader_buf);
2356 } else {
2357 account = msginfo->folder->folder->account;
2360 if (!account && prefs_common.reedit_account_autosel) {
2361 gchar *from = NULL;
2362 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2363 extract_address(from);
2364 account = account_find_from_address(from, FALSE);
2366 if (from)
2367 g_free(from);
2369 if (!account) {
2370 account = cur_account;
2372 if (!account) {
2373 g_warning("can't select account");
2374 if (manual_headers)
2375 procheader_entries_free(manual_headers);
2376 return NULL;
2379 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2381 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
2382 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
2383 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2384 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2385 compose->autowrap = autowrap;
2386 compose->replyinfo = replyinfo;
2387 compose->fwdinfo = fwdinfo;
2389 compose->updating = TRUE;
2390 compose->priority = priority;
2392 if (privacy_system != NULL) {
2393 compose->privacy_system = privacy_system;
2394 compose_use_signing(compose, use_signing);
2395 compose_use_encryption(compose, use_encryption);
2396 compose_update_privacy_system_menu_item(compose, FALSE);
2397 } else {
2398 compose_activate_privacy_system(compose, account, FALSE);
2400 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2401 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
2402 (account->default_encrypt || account->default_sign))
2403 COMPOSE_PRIVACY_WARNING();
2405 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2406 compose->targetinfo->tags = g_slist_copy(msginfo->tags);
2408 compose_extract_original_charset(compose);
2410 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2411 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2412 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2413 gchar *queueheader_buf = NULL;
2415 /* Set message save folder */
2416 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2417 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2418 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2419 compose_set_save_to(compose, &queueheader_buf[4]);
2420 g_free(queueheader_buf);
2422 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2423 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2424 if (active) {
2425 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2427 g_free(queueheader_buf);
2431 if (compose_parse_header(compose, msginfo) < 0) {
2432 compose->updating = FALSE;
2433 compose_destroy(compose);
2434 if (manual_headers)
2435 procheader_entries_free(manual_headers);
2436 return NULL;
2438 compose_reedit_set_entry(compose, msginfo);
2440 textview = GTK_TEXT_VIEW(compose->text);
2441 textbuf = gtk_text_view_get_buffer(textview);
2442 compose_create_tags(textview, compose);
2444 mark = gtk_text_buffer_get_insert(textbuf);
2445 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2447 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2448 G_CALLBACK(compose_changed_cb),
2449 compose);
2451 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2452 fp = procmime_get_first_encrypted_text_content(msginfo);
2453 if (fp) {
2454 compose_force_encryption(compose, account, TRUE, NULL);
2456 } else {
2457 fp = procmime_get_first_text_content(msginfo);
2459 if (fp == NULL) {
2460 g_warning("can't get text part");
2463 if (fp != NULL) {
2464 gchar buf[BUFFSIZE];
2465 gboolean prev_autowrap;
2466 GtkTextBuffer *buffer;
2467 BLOCK_WRAP();
2468 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2469 strcrchomp(buf);
2470 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2472 UNBLOCK_WRAP();
2473 claws_fclose(fp);
2476 compose_attach_parts(compose, msginfo);
2478 compose_colorize_signature(compose);
2480 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2481 G_CALLBACK(compose_changed_cb),
2482 compose);
2484 if (manual_headers != NULL) {
2485 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2486 procheader_entries_free(manual_headers);
2487 compose->updating = FALSE;
2488 compose_destroy(compose);
2489 return NULL;
2491 procheader_entries_free(manual_headers);
2494 gtk_widget_grab_focus(compose->text);
2496 if (prefs_common.auto_exteditor) {
2497 compose_exec_ext_editor(compose);
2499 compose->modified = FALSE;
2500 compose_set_title(compose);
2502 compose->updating = FALSE;
2503 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2504 SCROLL_TO_CURSOR(compose);
2506 if (compose->deferred_destroy) {
2507 compose_destroy(compose);
2508 return NULL;
2511 compose->sig_str = account_get_signature_str(compose->account);
2513 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2515 return compose;
2518 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2519 gboolean batch)
2521 Compose *compose;
2522 gchar *filename;
2523 FolderItem *item;
2525 cm_return_val_if_fail(msginfo != NULL, NULL);
2527 if (!account)
2528 account = account_get_reply_account(msginfo,
2529 prefs_common.reply_account_autosel);
2530 cm_return_val_if_fail(account != NULL, NULL);
2532 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2534 compose->updating = TRUE;
2536 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2537 compose->replyinfo = NULL;
2538 compose->fwdinfo = NULL;
2540 compose_show_first_last_header(compose, TRUE);
2542 gtk_widget_grab_focus(compose->header_last->entry);
2544 filename = procmsg_get_message_file(msginfo);
2546 if (filename == NULL) {
2547 compose->updating = FALSE;
2548 compose_destroy(compose);
2550 return NULL;
2553 compose->redirect_filename = filename;
2555 /* Set save folder */
2556 item = msginfo->folder;
2557 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2558 gchar *folderidentifier;
2560 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2561 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2562 folderidentifier = folder_item_get_identifier(item);
2563 compose_set_save_to(compose, folderidentifier);
2564 g_free(folderidentifier);
2567 compose_attach_parts(compose, msginfo);
2569 if (msginfo->subject)
2570 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2571 msginfo->subject);
2572 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2574 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2575 _("The body of the \"Redirect\" template has an error at line %d."));
2576 quote_fmt_reset_vartable();
2577 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2579 compose_colorize_signature(compose);
2582 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2583 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2584 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2586 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2587 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2588 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2589 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2590 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2591 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2592 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2593 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2594 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2596 if (compose->toolbar->draft_btn)
2597 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2598 if (compose->toolbar->insert_btn)
2599 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2600 if (compose->toolbar->attach_btn)
2601 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2602 if (compose->toolbar->sig_btn)
2603 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2604 if (compose->toolbar->exteditor_btn)
2605 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2606 if (compose->toolbar->linewrap_current_btn)
2607 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2608 if (compose->toolbar->linewrap_all_btn)
2609 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2610 if (compose->toolbar->privacy_sign_btn)
2611 gtk_widget_set_sensitive(compose->toolbar->privacy_sign_btn, FALSE);
2612 if (compose->toolbar->privacy_encrypt_btn)
2613 gtk_widget_set_sensitive(compose->toolbar->privacy_encrypt_btn, FALSE);
2615 compose->modified = FALSE;
2616 compose_set_title(compose);
2617 compose->updating = FALSE;
2618 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2619 SCROLL_TO_CURSOR(compose);
2621 if (compose->deferred_destroy) {
2622 compose_destroy(compose);
2623 return NULL;
2626 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2628 return compose;
2631 const GList *compose_get_compose_list(void)
2633 return compose_list;
2636 void compose_entry_append(Compose *compose, const gchar *address,
2637 ComposeEntryType type, ComposePrefType pref_type)
2639 const gchar *header;
2640 gchar *cur, *begin;
2641 gboolean in_quote = FALSE;
2642 if (!address || *address == '\0') return;
2644 switch (type) {
2645 case COMPOSE_CC:
2646 header = N_("Cc:");
2647 break;
2648 case COMPOSE_BCC:
2649 header = N_("Bcc:");
2650 break;
2651 case COMPOSE_REPLYTO:
2652 header = N_("Reply-To:");
2653 break;
2654 case COMPOSE_NEWSGROUPS:
2655 header = N_("Newsgroups:");
2656 break;
2657 case COMPOSE_FOLLOWUPTO:
2658 header = N_( "Followup-To:");
2659 break;
2660 case COMPOSE_INREPLYTO:
2661 header = N_( "In-Reply-To:");
2662 break;
2663 case COMPOSE_TO:
2664 default:
2665 header = N_("To:");
2666 break;
2668 header = prefs_common_translated_header_name(header);
2670 cur = begin = (gchar *)address;
2672 /* we separate the line by commas, but not if we're inside a quoted
2673 * string */
2674 while (*cur != '\0') {
2675 if (*cur == '"')
2676 in_quote = !in_quote;
2677 if (*cur == ',' && !in_quote) {
2678 gchar *tmp = g_strdup(begin);
2679 gchar *o_tmp = tmp;
2680 tmp[cur-begin]='\0';
2681 cur++;
2682 begin = cur;
2683 while (*tmp == ' ' || *tmp == '\t')
2684 tmp++;
2685 compose_add_header_entry(compose, header, tmp, pref_type);
2686 compose_entry_indicate(compose, tmp);
2687 g_free(o_tmp);
2688 continue;
2690 cur++;
2692 if (begin < cur) {
2693 gchar *tmp = g_strdup(begin);
2694 gchar *o_tmp = tmp;
2695 tmp[cur-begin]='\0';
2696 while (*tmp == ' ' || *tmp == '\t')
2697 tmp++;
2698 compose_add_header_entry(compose, header, tmp, pref_type);
2699 compose_entry_indicate(compose, tmp);
2700 g_free(o_tmp);
2704 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2706 GSList *h_list;
2707 GtkEntry *entry;
2708 GdkColor color;
2710 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2711 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2712 if (gtk_entry_get_text(entry) &&
2713 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2714 /* Modify background color */
2715 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_bgcolor, color);
2716 gtk_widget_modify_base(
2717 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2718 GTK_STATE_NORMAL, &color);
2720 /* Modify foreground color */
2721 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_color, color);
2722 gtk_widget_modify_text(
2723 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2724 GTK_STATE_NORMAL, &color);
2729 void compose_toolbar_cb(gint action, gpointer data)
2731 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2732 Compose *compose = (Compose*)toolbar_item->parent;
2734 cm_return_if_fail(compose != NULL);
2736 switch(action) {
2737 case A_SEND:
2738 compose_send_cb(NULL, compose);
2739 break;
2740 case A_SEND_LATER:
2741 compose_send_later_cb(NULL, compose);
2742 break;
2743 case A_DRAFT:
2744 compose_draft(compose, COMPOSE_QUIT_EDITING);
2745 break;
2746 case A_INSERT:
2747 compose_insert_file_cb(NULL, compose);
2748 break;
2749 case A_ATTACH:
2750 compose_attach_cb(NULL, compose);
2751 break;
2752 case A_SIG:
2753 compose_insert_sig(compose, FALSE);
2754 break;
2755 case A_REP_SIG:
2756 compose_insert_sig(compose, TRUE);
2757 break;
2758 case A_EXTEDITOR:
2759 compose_ext_editor_cb(NULL, compose);
2760 break;
2761 case A_LINEWRAP_CURRENT:
2762 compose_beautify_paragraph(compose, NULL, TRUE);
2763 break;
2764 case A_LINEWRAP_ALL:
2765 compose_wrap_all_full(compose, TRUE);
2766 break;
2767 case A_ADDRBOOK:
2768 compose_address_cb(NULL, compose);
2769 break;
2770 #ifdef USE_ENCHANT
2771 case A_CHECK_SPELLING:
2772 compose_check_all(NULL, compose);
2773 break;
2774 #endif
2775 case A_PRIVACY_SIGN:
2776 break;
2777 case A_PRIVACY_ENCRYPT:
2778 break;
2779 default:
2780 break;
2784 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2786 gchar *to = NULL;
2787 gchar *cc = NULL;
2788 gchar *bcc = NULL;
2789 gchar *subject = NULL;
2790 gchar *body = NULL;
2791 gchar *temp = NULL;
2792 gsize len = 0;
2793 gchar **attach = NULL;
2794 gchar *inreplyto = NULL;
2795 MailField mfield = NO_FIELD_PRESENT;
2797 /* get mailto parts but skip from */
2798 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2800 if (to) {
2801 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2802 mfield = TO_FIELD_PRESENT;
2804 if (cc)
2805 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2806 if (bcc)
2807 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2808 if (subject) {
2809 if (!g_utf8_validate (subject, -1, NULL)) {
2810 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2811 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2812 g_free(temp);
2813 } else {
2814 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2816 mfield = SUBJECT_FIELD_PRESENT;
2818 if (body) {
2819 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2820 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2821 GtkTextMark *mark;
2822 GtkTextIter iter;
2823 gboolean prev_autowrap = compose->autowrap;
2825 compose->autowrap = FALSE;
2827 mark = gtk_text_buffer_get_insert(buffer);
2828 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2830 if (!g_utf8_validate (body, -1, NULL)) {
2831 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2832 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2833 g_free(temp);
2834 } else {
2835 gtk_text_buffer_insert(buffer, &iter, body, -1);
2837 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2839 compose->autowrap = prev_autowrap;
2840 if (compose->autowrap)
2841 compose_wrap_all(compose);
2842 mfield = BODY_FIELD_PRESENT;
2845 if (attach) {
2846 gint i = 0, att = 0;
2847 gchar *warn_files = NULL;
2848 while (attach[i] != NULL) {
2849 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2850 if (utf8_filename) {
2851 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2852 gchar *tmp = g_strdup_printf("%s%s\n",
2853 warn_files?warn_files:"",
2854 utf8_filename);
2855 g_free(warn_files);
2856 warn_files = tmp;
2857 att++;
2859 g_free(utf8_filename);
2860 } else {
2861 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2863 i++;
2865 if (warn_files) {
2866 alertpanel_notice(ngettext(
2867 "The following file has been attached: \n%s",
2868 "The following files have been attached: \n%s", att), warn_files);
2869 g_free(warn_files);
2872 if (inreplyto)
2873 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2875 g_free(to);
2876 g_free(cc);
2877 g_free(bcc);
2878 g_free(subject);
2879 g_free(body);
2880 g_strfreev(attach);
2881 g_free(inreplyto);
2883 return mfield;
2886 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2888 static HeaderEntry hentry[] = {
2889 {"Reply-To:", NULL, TRUE },
2890 {"Cc:", NULL, TRUE },
2891 {"References:", NULL, FALSE },
2892 {"Bcc:", NULL, TRUE },
2893 {"Newsgroups:", NULL, TRUE },
2894 {"Followup-To:", NULL, TRUE },
2895 {"List-Post:", NULL, FALSE },
2896 {"X-Priority:", NULL, FALSE },
2897 {NULL, NULL, FALSE }
2900 enum
2902 H_REPLY_TO = 0,
2903 H_CC = 1,
2904 H_REFERENCES = 2,
2905 H_BCC = 3,
2906 H_NEWSGROUPS = 4,
2907 H_FOLLOWUP_TO = 5,
2908 H_LIST_POST = 6,
2909 H_X_PRIORITY = 7
2912 FILE *fp;
2914 cm_return_val_if_fail(msginfo != NULL, -1);
2916 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
2917 procheader_get_header_fields(fp, hentry);
2918 claws_fclose(fp);
2920 if (hentry[H_REPLY_TO].body != NULL) {
2921 if (hentry[H_REPLY_TO].body[0] != '\0') {
2922 compose->replyto =
2923 conv_unmime_header(hentry[H_REPLY_TO].body,
2924 NULL, TRUE);
2926 g_free(hentry[H_REPLY_TO].body);
2927 hentry[H_REPLY_TO].body = NULL;
2929 if (hentry[H_CC].body != NULL) {
2930 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2931 g_free(hentry[H_CC].body);
2932 hentry[H_CC].body = NULL;
2934 if (hentry[H_REFERENCES].body != NULL) {
2935 if (compose->mode == COMPOSE_REEDIT)
2936 compose->references = hentry[H_REFERENCES].body;
2937 else {
2938 compose->references = compose_parse_references
2939 (hentry[H_REFERENCES].body, msginfo->msgid);
2940 g_free(hentry[H_REFERENCES].body);
2942 hentry[H_REFERENCES].body = NULL;
2944 if (hentry[H_BCC].body != NULL) {
2945 if (compose->mode == COMPOSE_REEDIT)
2946 compose->bcc =
2947 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2948 g_free(hentry[H_BCC].body);
2949 hentry[H_BCC].body = NULL;
2951 if (hentry[H_NEWSGROUPS].body != NULL) {
2952 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2953 hentry[H_NEWSGROUPS].body = NULL;
2955 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2956 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2957 compose->followup_to =
2958 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2959 NULL, TRUE);
2961 g_free(hentry[H_FOLLOWUP_TO].body);
2962 hentry[H_FOLLOWUP_TO].body = NULL;
2964 if (hentry[H_LIST_POST].body != NULL) {
2965 gchar *to = NULL, *start = NULL;
2967 extract_address(hentry[H_LIST_POST].body);
2968 if (hentry[H_LIST_POST].body[0] != '\0') {
2969 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2971 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2972 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2974 if (to) {
2975 g_free(compose->ml_post);
2976 compose->ml_post = to;
2979 g_free(hentry[H_LIST_POST].body);
2980 hentry[H_LIST_POST].body = NULL;
2983 /* CLAWS - X-Priority */
2984 if (compose->mode == COMPOSE_REEDIT)
2985 if (hentry[H_X_PRIORITY].body != NULL) {
2986 gint priority;
2988 priority = atoi(hentry[H_X_PRIORITY].body);
2989 g_free(hentry[H_X_PRIORITY].body);
2991 hentry[H_X_PRIORITY].body = NULL;
2993 if (priority < PRIORITY_HIGHEST ||
2994 priority > PRIORITY_LOWEST)
2995 priority = PRIORITY_NORMAL;
2997 compose->priority = priority;
3000 if (compose->mode == COMPOSE_REEDIT) {
3001 if (msginfo->inreplyto && *msginfo->inreplyto)
3002 compose->inreplyto = g_strdup(msginfo->inreplyto);
3004 if (msginfo->msgid && *msginfo->msgid &&
3005 compose->folder != NULL &&
3006 compose->folder->stype == F_DRAFT)
3007 compose->msgid = g_strdup(msginfo->msgid);
3008 } else {
3009 if (msginfo->msgid && *msginfo->msgid)
3010 compose->inreplyto = g_strdup(msginfo->msgid);
3012 if (!compose->references) {
3013 if (msginfo->msgid && *msginfo->msgid) {
3014 if (msginfo->inreplyto && *msginfo->inreplyto)
3015 compose->references =
3016 g_strdup_printf("<%s>\n\t<%s>",
3017 msginfo->inreplyto,
3018 msginfo->msgid);
3019 else
3020 compose->references =
3021 g_strconcat("<", msginfo->msgid, ">",
3022 NULL);
3023 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3024 compose->references =
3025 g_strconcat("<", msginfo->inreplyto, ">",
3026 NULL);
3031 return 0;
3034 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3036 FILE *fp;
3037 HeaderEntry *he;
3039 cm_return_val_if_fail(msginfo != NULL, -1);
3041 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
3042 procheader_get_header_fields(fp, entries);
3043 claws_fclose(fp);
3045 he = entries;
3046 while (he != NULL && he->name != NULL) {
3047 GtkTreeIter iter;
3048 GtkListStore *model = NULL;
3050 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3051 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3052 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3053 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3054 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3055 ++he;
3058 return 0;
3061 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3063 GSList *ref_id_list, *cur;
3064 GString *new_ref;
3065 gchar *new_ref_str;
3067 ref_id_list = references_list_append(NULL, ref);
3068 if (!ref_id_list) return NULL;
3069 if (msgid && *msgid)
3070 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3072 for (;;) {
3073 gint len = 0;
3075 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3076 /* "<" + Message-ID + ">" + CR+LF+TAB */
3077 len += strlen((gchar *)cur->data) + 5;
3079 if (len > MAX_REFERENCES_LEN) {
3080 /* remove second message-ID */
3081 if (ref_id_list && ref_id_list->next &&
3082 ref_id_list->next->next) {
3083 g_free(ref_id_list->next->data);
3084 ref_id_list = g_slist_remove
3085 (ref_id_list, ref_id_list->next->data);
3086 } else {
3087 slist_free_strings_full(ref_id_list);
3088 return NULL;
3090 } else
3091 break;
3094 new_ref = g_string_new("");
3095 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3096 if (new_ref->len > 0)
3097 g_string_append(new_ref, "\n\t");
3098 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3101 slist_free_strings_full(ref_id_list);
3103 new_ref_str = new_ref->str;
3104 g_string_free(new_ref, FALSE);
3106 return new_ref_str;
3109 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3110 const gchar *fmt, const gchar *qmark,
3111 const gchar *body, gboolean rewrap,
3112 gboolean need_unescape,
3113 const gchar *err_msg)
3115 MsgInfo* dummyinfo = NULL;
3116 gchar *quote_str = NULL;
3117 gchar *buf;
3118 gboolean prev_autowrap;
3119 const gchar *trimmed_body = body;
3120 gint cursor_pos = -1;
3121 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3122 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3123 GtkTextIter iter;
3124 GtkTextMark *mark;
3127 SIGNAL_BLOCK(buffer);
3129 if (!msginfo) {
3130 dummyinfo = compose_msginfo_new_from_compose(compose);
3131 msginfo = dummyinfo;
3134 if (qmark != NULL) {
3135 #ifdef USE_ENCHANT
3136 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3137 compose->gtkaspell);
3138 #else
3139 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3140 #endif
3141 quote_fmt_scan_string(qmark);
3142 quote_fmt_parse();
3144 buf = quote_fmt_get_buffer();
3146 if (buf == NULL)
3147 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3148 else
3149 Xstrdup_a(quote_str, buf, goto error)
3152 if (fmt && *fmt != '\0') {
3154 if (trimmed_body)
3155 while (*trimmed_body == '\n')
3156 trimmed_body++;
3158 #ifdef USE_ENCHANT
3159 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3160 compose->gtkaspell);
3161 #else
3162 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3163 #endif
3164 if (need_unescape) {
3165 gchar *tmp = NULL;
3167 /* decode \-escape sequences in the internal representation of the quote format */
3168 tmp = g_malloc(strlen(fmt)+1);
3169 pref_get_unescaped_pref(tmp, fmt);
3170 quote_fmt_scan_string(tmp);
3171 quote_fmt_parse();
3172 g_free(tmp);
3173 } else {
3174 quote_fmt_scan_string(fmt);
3175 quote_fmt_parse();
3178 buf = quote_fmt_get_buffer();
3180 if (buf == NULL) {
3181 gint line = quote_fmt_get_line();
3182 alertpanel_error(err_msg, line);
3184 goto error;
3187 } else
3188 buf = "";
3190 prev_autowrap = compose->autowrap;
3191 compose->autowrap = FALSE;
3193 mark = gtk_text_buffer_get_insert(buffer);
3194 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3195 if (g_utf8_validate(buf, -1, NULL)) {
3196 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3197 } else {
3198 gchar *tmpout = NULL;
3199 tmpout = conv_codeset_strdup
3200 (buf, conv_get_locale_charset_str_no_utf8(),
3201 CS_INTERNAL);
3202 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3203 g_free(tmpout);
3204 tmpout = g_malloc(strlen(buf)*2+1);
3205 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3207 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3208 g_free(tmpout);
3211 cursor_pos = quote_fmt_get_cursor_pos();
3212 if (cursor_pos == -1)
3213 cursor_pos = gtk_text_iter_get_offset(&iter);
3214 compose->set_cursor_pos = cursor_pos;
3216 gtk_text_buffer_get_start_iter(buffer, &iter);
3217 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3218 gtk_text_buffer_place_cursor(buffer, &iter);
3220 compose->autowrap = prev_autowrap;
3221 if (compose->autowrap && rewrap)
3222 compose_wrap_all(compose);
3224 goto ok;
3226 error:
3227 buf = NULL;
3229 SIGNAL_UNBLOCK(buffer);
3231 procmsg_msginfo_free( &dummyinfo );
3233 return buf;
3236 /* if ml_post is of type addr@host and from is of type
3237 * addr-anything@host, return TRUE
3239 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3241 gchar *left_ml = NULL;
3242 gchar *right_ml = NULL;
3243 gchar *left_from = NULL;
3244 gchar *right_from = NULL;
3245 gboolean result = FALSE;
3247 if (!ml_post || !from)
3248 return FALSE;
3250 left_ml = g_strdup(ml_post);
3251 if (strstr(left_ml, "@")) {
3252 right_ml = strstr(left_ml, "@")+1;
3253 *(strstr(left_ml, "@")) = '\0';
3256 left_from = g_strdup(from);
3257 if (strstr(left_from, "@")) {
3258 right_from = strstr(left_from, "@")+1;
3259 *(strstr(left_from, "@")) = '\0';
3262 if (right_ml && right_from
3263 && !strncmp(left_from, left_ml, strlen(left_ml))
3264 && !strcmp(right_from, right_ml)) {
3265 result = TRUE;
3267 g_free(left_ml);
3268 g_free(left_from);
3270 return result;
3273 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3274 gboolean respect_default_to)
3276 if (!compose)
3277 return;
3278 if (!folder || !folder->prefs)
3279 return;
3281 if (folder->prefs->enable_default_from) {
3282 gtk_entry_set_text(GTK_ENTRY(compose->from_name), folder->prefs->default_from);
3283 compose_entry_indicate(compose, folder->prefs->default_from);
3285 if (respect_default_to && folder->prefs->enable_default_to) {
3286 compose_entry_append(compose, folder->prefs->default_to,
3287 COMPOSE_TO, PREF_FOLDER);
3288 compose_entry_indicate(compose, folder->prefs->default_to);
3290 if (folder->prefs->enable_default_cc) {
3291 compose_entry_append(compose, folder->prefs->default_cc,
3292 COMPOSE_CC, PREF_FOLDER);
3293 compose_entry_indicate(compose, folder->prefs->default_cc);
3295 if (folder->prefs->enable_default_bcc) {
3296 compose_entry_append(compose, folder->prefs->default_bcc,
3297 COMPOSE_BCC, PREF_FOLDER);
3298 compose_entry_indicate(compose, folder->prefs->default_bcc);
3300 if (folder->prefs->enable_default_replyto) {
3301 compose_entry_append(compose, folder->prefs->default_replyto,
3302 COMPOSE_REPLYTO, PREF_FOLDER);
3303 compose_entry_indicate(compose, folder->prefs->default_replyto);
3307 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3309 gchar *buf, *buf2;
3310 gchar *p;
3312 if (!compose || !msginfo)
3313 return;
3315 if (msginfo->subject && *msginfo->subject) {
3316 buf = p = g_strdup(msginfo->subject);
3317 p += subject_get_prefix_length(p);
3318 memmove(buf, p, strlen(p) + 1);
3320 buf2 = g_strdup_printf("Re: %s", buf);
3321 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3323 g_free(buf2);
3324 g_free(buf);
3325 } else
3326 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3329 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3330 gboolean to_all, gboolean to_ml,
3331 gboolean to_sender,
3332 gboolean followup_and_reply_to)
3334 GSList *cc_list = NULL;
3335 GSList *cur;
3336 gchar *from = NULL;
3337 gchar *replyto = NULL;
3338 gchar *ac_email = NULL;
3340 gboolean reply_to_ml = FALSE;
3341 gboolean default_reply_to = FALSE;
3343 cm_return_if_fail(compose->account != NULL);
3344 cm_return_if_fail(msginfo != NULL);
3346 reply_to_ml = to_ml && compose->ml_post;
3348 default_reply_to = msginfo->folder &&
3349 msginfo->folder->prefs->enable_default_reply_to;
3351 if (compose->account->protocol != A_NNTP) {
3352 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3354 if (reply_to_ml && !default_reply_to) {
3356 gboolean is_subscr = is_subscription(compose->ml_post,
3357 msginfo->from);
3358 if (!is_subscr) {
3359 /* normal answer to ml post with a reply-to */
3360 compose_entry_append(compose,
3361 compose->ml_post,
3362 COMPOSE_TO, PREF_ML);
3363 if (compose->replyto)
3364 compose_entry_append(compose,
3365 compose->replyto,
3366 COMPOSE_CC, PREF_ML);
3367 } else {
3368 /* answer to subscription confirmation */
3369 if (compose->replyto)
3370 compose_entry_append(compose,
3371 compose->replyto,
3372 COMPOSE_TO, PREF_ML);
3373 else if (msginfo->from)
3374 compose_entry_append(compose,
3375 msginfo->from,
3376 COMPOSE_TO, PREF_ML);
3379 else if (!(to_all || to_sender) && default_reply_to) {
3380 compose_entry_append(compose,
3381 msginfo->folder->prefs->default_reply_to,
3382 COMPOSE_TO, PREF_FOLDER);
3383 compose_entry_indicate(compose,
3384 msginfo->folder->prefs->default_reply_to);
3385 } else {
3386 gchar *tmp1 = NULL;
3387 if (!msginfo->from)
3388 return;
3389 if (to_sender)
3390 compose_entry_append(compose, msginfo->from,
3391 COMPOSE_TO, PREF_NONE);
3392 else if (to_all) {
3393 Xstrdup_a(tmp1, msginfo->from, return);
3394 extract_address(tmp1);
3395 compose_entry_append(compose,
3396 (!account_find_from_address(tmp1, FALSE))
3397 ? msginfo->from :
3398 msginfo->to,
3399 COMPOSE_TO, PREF_NONE);
3400 if (compose->replyto)
3401 compose_entry_append(compose,
3402 compose->replyto,
3403 COMPOSE_CC, PREF_NONE);
3404 } else {
3405 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3406 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3407 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3408 if (compose->replyto) {
3409 compose_entry_append(compose,
3410 compose->replyto,
3411 COMPOSE_TO, PREF_NONE);
3412 } else {
3413 compose_entry_append(compose,
3414 msginfo->from ? msginfo->from : "",
3415 COMPOSE_TO, PREF_NONE);
3417 } else {
3418 /* replying to own mail, use original recp */
3419 compose_entry_append(compose,
3420 msginfo->to ? msginfo->to : "",
3421 COMPOSE_TO, PREF_NONE);
3422 compose_entry_append(compose,
3423 msginfo->cc ? msginfo->cc : "",
3424 COMPOSE_CC, PREF_NONE);
3428 } else {
3429 if (to_sender || (compose->followup_to &&
3430 !strncmp(compose->followup_to, "poster", 6)))
3431 compose_entry_append
3432 (compose,
3433 (compose->replyto ? compose->replyto :
3434 msginfo->from ? msginfo->from : ""),
3435 COMPOSE_TO, PREF_NONE);
3437 else if (followup_and_reply_to || to_all) {
3438 compose_entry_append
3439 (compose,
3440 (compose->replyto ? compose->replyto :
3441 msginfo->from ? msginfo->from : ""),
3442 COMPOSE_TO, PREF_NONE);
3444 compose_entry_append
3445 (compose,
3446 compose->followup_to ? compose->followup_to :
3447 compose->newsgroups ? compose->newsgroups : "",
3448 COMPOSE_NEWSGROUPS, PREF_NONE);
3450 compose_entry_append
3451 (compose,
3452 msginfo->cc ? msginfo->cc : "",
3453 COMPOSE_CC, PREF_NONE);
3455 else
3456 compose_entry_append
3457 (compose,
3458 compose->followup_to ? compose->followup_to :
3459 compose->newsgroups ? compose->newsgroups : "",
3460 COMPOSE_NEWSGROUPS, PREF_NONE);
3462 compose_reply_set_subject(compose, msginfo);
3464 if (to_ml && compose->ml_post) return;
3465 if (!to_all || compose->account->protocol == A_NNTP) return;
3467 if (compose->replyto) {
3468 Xstrdup_a(replyto, compose->replyto, return);
3469 extract_address(replyto);
3471 if (msginfo->from) {
3472 Xstrdup_a(from, msginfo->from, return);
3473 extract_address(from);
3476 if (replyto && from)
3477 cc_list = address_list_append_with_comments(cc_list, from);
3478 if (to_all && msginfo->folder &&
3479 msginfo->folder->prefs->enable_default_reply_to)
3480 cc_list = address_list_append_with_comments(cc_list,
3481 msginfo->folder->prefs->default_reply_to);
3482 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3483 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3485 ac_email = g_utf8_strdown(compose->account->address, -1);
3487 if (cc_list) {
3488 for (cur = cc_list; cur != NULL; cur = cur->next) {
3489 gchar *addr = g_utf8_strdown(cur->data, -1);
3490 extract_address(addr);
3492 if (strcmp(ac_email, addr))
3493 compose_entry_append(compose, (gchar *)cur->data,
3494 COMPOSE_CC, PREF_NONE);
3495 else
3496 debug_print("Cc address same as compose account's, ignoring\n");
3498 g_free(addr);
3501 slist_free_strings_full(cc_list);
3504 g_free(ac_email);
3507 #define SET_ENTRY(entry, str) \
3509 if (str && *str) \
3510 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3513 #define SET_ADDRESS(type, str) \
3515 if (str && *str) \
3516 compose_entry_append(compose, str, type, PREF_NONE); \
3519 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3521 cm_return_if_fail(msginfo != NULL);
3523 SET_ENTRY(subject_entry, msginfo->subject);
3524 SET_ENTRY(from_name, msginfo->from);
3525 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3526 SET_ADDRESS(COMPOSE_CC, compose->cc);
3527 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3528 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3529 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3530 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3532 compose_update_priority_menu_item(compose);
3533 compose_update_privacy_system_menu_item(compose, FALSE);
3534 compose_show_first_last_header(compose, TRUE);
3537 #undef SET_ENTRY
3538 #undef SET_ADDRESS
3540 static void compose_insert_sig(Compose *compose, gboolean replace)
3542 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3543 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3544 GtkTextMark *mark;
3545 GtkTextIter iter, iter_end;
3546 gint cur_pos, ins_pos;
3547 gboolean prev_autowrap;
3548 gboolean found = FALSE;
3549 gboolean exists = FALSE;
3551 cm_return_if_fail(compose->account != NULL);
3553 BLOCK_WRAP();
3555 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3556 G_CALLBACK(compose_changed_cb),
3557 compose);
3559 mark = gtk_text_buffer_get_insert(buffer);
3560 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3561 cur_pos = gtk_text_iter_get_offset (&iter);
3562 ins_pos = cur_pos;
3564 gtk_text_buffer_get_end_iter(buffer, &iter);
3566 exists = (compose->sig_str != NULL);
3568 if (replace) {
3569 GtkTextIter first_iter, start_iter, end_iter;
3571 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3573 if (!exists || compose->sig_str[0] == '\0')
3574 found = FALSE;
3575 else
3576 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3577 compose->signature_tag);
3579 if (found) {
3580 /* include previous \n\n */
3581 gtk_text_iter_backward_chars(&first_iter, 1);
3582 start_iter = first_iter;
3583 end_iter = first_iter;
3584 /* skip re-start */
3585 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3586 compose->signature_tag);
3587 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3588 compose->signature_tag);
3589 if (found) {
3590 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3591 iter = start_iter;
3596 g_free(compose->sig_str);
3597 compose->sig_str = account_get_signature_str(compose->account);
3599 cur_pos = gtk_text_iter_get_offset(&iter);
3601 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3602 g_free(compose->sig_str);
3603 compose->sig_str = NULL;
3604 } else {
3605 if (compose->sig_inserted == FALSE)
3606 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3607 compose->sig_inserted = TRUE;
3609 cur_pos = gtk_text_iter_get_offset(&iter);
3610 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3611 /* remove \n\n */
3612 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3613 gtk_text_iter_forward_chars(&iter, 1);
3614 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3615 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3617 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3618 cur_pos = gtk_text_buffer_get_char_count (buffer);
3621 /* put the cursor where it should be
3622 * either where the quote_fmt says, either where it was */
3623 if (compose->set_cursor_pos < 0)
3624 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3625 else
3626 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3627 compose->set_cursor_pos);
3629 compose->set_cursor_pos = -1;
3630 gtk_text_buffer_place_cursor(buffer, &iter);
3631 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3632 G_CALLBACK(compose_changed_cb),
3633 compose);
3635 UNBLOCK_WRAP();
3638 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3640 GtkTextView *text;
3641 GtkTextBuffer *buffer;
3642 GtkTextMark *mark;
3643 GtkTextIter iter;
3644 const gchar *cur_encoding;
3645 gchar buf[BUFFSIZE];
3646 gint len;
3647 FILE *fp;
3648 gboolean prev_autowrap;
3649 #ifdef G_OS_WIN32
3650 GFile *f;
3651 GFileInfo *fi;
3652 GError *error = NULL;
3653 #else
3654 GStatBuf file_stat;
3655 #endif
3656 int ret;
3657 goffset size;
3658 GString *file_contents = NULL;
3659 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3661 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3663 /* get the size of the file we are about to insert */
3664 #ifdef G_OS_WIN32
3665 f = g_file_new_for_path(file);
3666 fi = g_file_query_info(f, "standard::size",
3667 G_FILE_QUERY_INFO_NONE, NULL, &error);
3668 ret = 0;
3669 if (error != NULL) {
3670 g_warning(error->message);
3671 ret = 1;
3672 g_error_free(error);
3673 g_object_unref(f);
3675 #else
3676 ret = g_stat(file, &file_stat);
3677 #endif
3678 if (ret != 0) {
3679 gchar *shortfile = g_path_get_basename(file);
3680 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3681 g_free(shortfile);
3682 return COMPOSE_INSERT_NO_FILE;
3683 } else if (prefs_common.warn_large_insert == TRUE) {
3684 #ifdef G_OS_WIN32
3685 size = g_file_info_get_size(fi);
3686 g_object_unref(fi);
3687 g_object_unref(f);
3688 #else
3689 size = file_stat.st_size;
3690 #endif
3692 /* ask user for confirmation if the file is large */
3693 if (prefs_common.warn_large_insert_size < 0 ||
3694 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3695 AlertValue aval;
3696 gchar *msg;
3698 msg = g_strdup_printf(_("You are about to insert a file of %s "
3699 "in the message body. Are you sure you want to do that?"),
3700 to_human_readable(size));
3701 aval = alertpanel_full(_("Are you sure?"), msg, NULL, _("_Cancel"),
3702 NULL, _("_Insert"), NULL, NULL, ALERTFOCUS_SECOND, TRUE,
3703 NULL, ALERT_QUESTION);
3704 g_free(msg);
3706 /* do we ask for confirmation next time? */
3707 if (aval & G_ALERTDISABLE) {
3708 /* no confirmation next time, disable feature in preferences */
3709 aval &= ~G_ALERTDISABLE;
3710 prefs_common.warn_large_insert = FALSE;
3713 /* abort file insertion if user canceled action */
3714 if (aval != G_ALERTALTERNATE) {
3715 return COMPOSE_INSERT_NO_FILE;
3721 if ((fp = claws_fopen(file, "rb")) == NULL) {
3722 FILE_OP_ERROR(file, "claws_fopen");
3723 return COMPOSE_INSERT_READ_ERROR;
3726 prev_autowrap = compose->autowrap;
3727 compose->autowrap = FALSE;
3729 text = GTK_TEXT_VIEW(compose->text);
3730 buffer = gtk_text_view_get_buffer(text);
3731 mark = gtk_text_buffer_get_insert(buffer);
3732 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3734 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3735 G_CALLBACK(text_inserted),
3736 compose);
3738 cur_encoding = conv_get_locale_charset_str_no_utf8();
3740 file_contents = g_string_new("");
3741 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3742 gchar *str;
3744 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3745 str = g_strdup(buf);
3746 else {
3747 codeconv_set_strict(TRUE);
3748 str = conv_codeset_strdup
3749 (buf, cur_encoding, CS_INTERNAL);
3750 codeconv_set_strict(FALSE);
3752 if (!str) {
3753 result = COMPOSE_INSERT_INVALID_CHARACTER;
3754 break;
3757 if (!str) continue;
3759 /* strip <CR> if DOS/Windows file,
3760 replace <CR> with <LF> if Macintosh file. */
3761 strcrchomp(str);
3762 len = strlen(str);
3763 if (len > 0 && str[len - 1] != '\n') {
3764 while (--len >= 0)
3765 if (str[len] == '\r') str[len] = '\n';
3768 file_contents = g_string_append(file_contents, str);
3769 g_free(str);
3772 if (result == COMPOSE_INSERT_SUCCESS) {
3773 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3775 compose_changed_cb(NULL, compose);
3776 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3777 G_CALLBACK(text_inserted),
3778 compose);
3779 compose->autowrap = prev_autowrap;
3780 if (compose->autowrap)
3781 compose_wrap_all(compose);
3784 g_string_free(file_contents, TRUE);
3785 claws_fclose(fp);
3787 return result;
3790 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3791 const gchar *filename,
3792 const gchar *content_type,
3793 const gchar *charset)
3795 AttachInfo *ainfo;
3796 GtkTreeIter iter;
3797 FILE *fp;
3798 off_t size;
3799 GAuto *auto_ainfo;
3800 gchar *size_text;
3801 GtkListStore *store;
3802 gchar *name;
3803 gboolean has_binary = FALSE;
3805 if (!is_file_exist(file)) {
3806 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3807 gboolean result = FALSE;
3808 if (file_from_uri && is_file_exist(file_from_uri)) {
3809 result = compose_attach_append(
3810 compose, file_from_uri,
3811 filename, content_type,
3812 charset);
3814 g_free(file_from_uri);
3815 if (result)
3816 return TRUE;
3817 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3818 return FALSE;
3820 if ((size = get_file_size(file)) < 0) {
3821 alertpanel_error("Can't get file size of %s\n", filename);
3822 return FALSE;
3825 /* In batch mode, we allow 0-length files to be attached no questions asked */
3826 if (size == 0 && !compose->batch) {
3827 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3828 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3829 NULL, _("_Cancel"), NULL, _("_Attach anyway"),
3830 NULL, NULL, ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3831 g_free(msg);
3833 if (aval != G_ALERTALTERNATE) {
3834 return FALSE;
3837 if ((fp = claws_fopen(file, "rb")) == NULL) {
3838 alertpanel_error(_("Can't read %s."), filename);
3839 return FALSE;
3841 claws_fclose(fp);
3843 ainfo = g_new0(AttachInfo, 1);
3844 auto_ainfo = g_auto_pointer_new_with_free
3845 (ainfo, (GFreeFunc) compose_attach_info_free);
3846 ainfo->file = g_strdup(file);
3848 if (content_type) {
3849 ainfo->content_type = g_strdup(content_type);
3850 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3851 MsgInfo *msginfo;
3852 MsgFlags flags = {0, 0};
3854 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3855 ainfo->encoding = ENC_7BIT;
3856 else
3857 ainfo->encoding = ENC_8BIT;
3859 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3860 if (msginfo && msginfo->subject)
3861 name = g_strdup(msginfo->subject);
3862 else
3863 name = g_path_get_basename(filename ? filename : file);
3865 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3867 procmsg_msginfo_free(&msginfo);
3868 } else {
3869 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3870 ainfo->charset = g_strdup(charset);
3871 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3872 } else {
3873 ainfo->encoding = ENC_BASE64;
3875 name = g_path_get_basename(filename ? filename : file);
3876 ainfo->name = g_strdup(name);
3878 g_free(name);
3879 } else {
3880 ainfo->content_type = procmime_get_mime_type(file);
3881 if (!ainfo->content_type) {
3882 ainfo->content_type =
3883 g_strdup("application/octet-stream");
3884 ainfo->encoding = ENC_BASE64;
3885 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3886 ainfo->encoding =
3887 procmime_get_encoding_for_text_file(file, &has_binary);
3888 else
3889 ainfo->encoding = ENC_BASE64;
3890 name = g_path_get_basename(filename ? filename : file);
3891 ainfo->name = g_strdup(name);
3892 g_free(name);
3895 if (ainfo->name != NULL
3896 && !strcmp(ainfo->name, ".")) {
3897 g_free(ainfo->name);
3898 ainfo->name = NULL;
3901 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3902 g_free(ainfo->content_type);
3903 ainfo->content_type = g_strdup("application/octet-stream");
3904 g_free(ainfo->charset);
3905 ainfo->charset = NULL;
3908 ainfo->size = (goffset)size;
3909 size_text = to_human_readable((goffset)size);
3911 store = GTK_LIST_STORE(gtk_tree_view_get_model
3912 (GTK_TREE_VIEW(compose->attach_clist)));
3914 gtk_list_store_append(store, &iter);
3915 gtk_list_store_set(store, &iter,
3916 COL_MIMETYPE, ainfo->content_type,
3917 COL_SIZE, size_text,
3918 COL_NAME, ainfo->name,
3919 COL_CHARSET, ainfo->charset,
3920 COL_DATA, ainfo,
3921 COL_AUTODATA, auto_ainfo,
3922 -1);
3924 g_auto_pointer_free(auto_ainfo);
3925 compose_attach_update_label(compose);
3926 return TRUE;
3929 void compose_use_signing(Compose *compose, gboolean use_signing)
3931 compose->use_signing = use_signing;
3932 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3935 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3937 compose->use_encryption = use_encryption;
3938 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3941 #define NEXT_PART_NOT_CHILD(info) \
3943 node = info->node; \
3944 while (node->children) \
3945 node = g_node_last_child(node); \
3946 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3949 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3951 MimeInfo *mimeinfo;
3952 MimeInfo *child;
3953 MimeInfo *firsttext = NULL;
3954 MimeInfo *encrypted = NULL;
3955 GNode *node;
3956 gchar *outfile;
3957 const gchar *partname = NULL;
3959 mimeinfo = procmime_scan_message(msginfo);
3960 if (!mimeinfo) return;
3962 if (mimeinfo->node->children == NULL) {
3963 procmime_mimeinfo_free_all(&mimeinfo);
3964 return;
3967 /* find first content part */
3968 child = (MimeInfo *) mimeinfo->node->children->data;
3969 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3970 child = (MimeInfo *)child->node->children->data;
3972 if (child) {
3973 if (child->type == MIMETYPE_TEXT) {
3974 firsttext = child;
3975 debug_print("First text part found\n");
3976 } else if (compose->mode == COMPOSE_REEDIT &&
3977 child->type == MIMETYPE_APPLICATION &&
3978 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3979 encrypted = (MimeInfo *)child->node->parent->data;
3982 child = (MimeInfo *) mimeinfo->node->children->data;
3983 while (child != NULL) {
3984 gint err;
3986 if (child == encrypted) {
3987 /* skip this part of tree */
3988 NEXT_PART_NOT_CHILD(child);
3989 continue;
3992 if (child->type == MIMETYPE_MULTIPART) {
3993 /* get the actual content */
3994 child = procmime_mimeinfo_next(child);
3995 continue;
3998 if (child == firsttext) {
3999 child = procmime_mimeinfo_next(child);
4000 continue;
4003 outfile = procmime_get_tmp_file_name(child);
4004 if ((err = procmime_get_part(outfile, child)) < 0)
4005 g_warning("can't get the part of multipart message. (%s)", g_strerror(-err));
4006 else {
4007 gchar *content_type;
4009 content_type = procmime_get_content_type_str(child->type, child->subtype);
4011 /* if we meet a pgp signature, we don't attach it, but
4012 * we force signing. */
4013 if ((strcmp(content_type, "application/pgp-signature") &&
4014 strcmp(content_type, "application/pkcs7-signature") &&
4015 strcmp(content_type, "application/x-pkcs7-signature"))
4016 || compose->mode == COMPOSE_REDIRECT) {
4017 partname = procmime_mimeinfo_get_parameter(child, "filename");
4018 if (partname == NULL)
4019 partname = procmime_mimeinfo_get_parameter(child, "name");
4020 if (partname == NULL)
4021 partname = "";
4022 compose_attach_append(compose, outfile,
4023 partname, content_type,
4024 procmime_mimeinfo_get_parameter(child, "charset"));
4025 } else {
4026 compose_force_signing(compose, compose->account, NULL);
4028 g_free(content_type);
4030 g_free(outfile);
4031 NEXT_PART_NOT_CHILD(child);
4033 procmime_mimeinfo_free_all(&mimeinfo);
4036 #undef NEXT_PART_NOT_CHILD
4040 typedef enum {
4041 WAIT_FOR_INDENT_CHAR,
4042 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4043 } IndentState;
4045 /* return indent length, we allow:
4046 indent characters followed by indent characters or spaces/tabs,
4047 alphabets and numbers immediately followed by indent characters,
4048 and the repeating sequences of the above
4049 If quote ends with multiple spaces, only the first one is included. */
4050 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4051 const GtkTextIter *start, gint *len)
4053 GtkTextIter iter = *start;
4054 gunichar wc;
4055 gchar ch[6];
4056 gint clen;
4057 IndentState state = WAIT_FOR_INDENT_CHAR;
4058 gboolean is_space;
4059 gboolean is_indent;
4060 gint alnum_count = 0;
4061 gint space_count = 0;
4062 gint quote_len = 0;
4064 if (prefs_common.quote_chars == NULL) {
4065 return 0 ;
4068 while (!gtk_text_iter_ends_line(&iter)) {
4069 wc = gtk_text_iter_get_char(&iter);
4070 if (g_unichar_iswide(wc))
4071 break;
4072 clen = g_unichar_to_utf8(wc, ch);
4073 if (clen != 1)
4074 break;
4076 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4077 is_space = g_unichar_isspace(wc);
4079 if (state == WAIT_FOR_INDENT_CHAR) {
4080 if (!is_indent && !g_unichar_isalnum(wc))
4081 break;
4082 if (is_indent) {
4083 quote_len += alnum_count + space_count + 1;
4084 alnum_count = space_count = 0;
4085 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4086 } else
4087 alnum_count++;
4088 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4089 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4090 break;
4091 if (is_space)
4092 space_count++;
4093 else if (is_indent) {
4094 quote_len += alnum_count + space_count + 1;
4095 alnum_count = space_count = 0;
4096 } else {
4097 alnum_count++;
4098 state = WAIT_FOR_INDENT_CHAR;
4102 gtk_text_iter_forward_char(&iter);
4105 if (quote_len > 0 && space_count > 0)
4106 quote_len++;
4108 if (len)
4109 *len = quote_len;
4111 if (quote_len > 0) {
4112 iter = *start;
4113 gtk_text_iter_forward_chars(&iter, quote_len);
4114 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4117 return NULL;
4120 /* return >0 if the line is itemized */
4121 static int compose_itemized_length(GtkTextBuffer *buffer,
4122 const GtkTextIter *start)
4124 GtkTextIter iter = *start;
4125 gunichar wc;
4126 gchar ch[6];
4127 gint clen;
4128 gint len = 0;
4129 if (gtk_text_iter_ends_line(&iter))
4130 return 0;
4132 while (1) {
4133 len++;
4134 wc = gtk_text_iter_get_char(&iter);
4135 if (!g_unichar_isspace(wc))
4136 break;
4137 gtk_text_iter_forward_char(&iter);
4138 if (gtk_text_iter_ends_line(&iter))
4139 return 0;
4142 clen = g_unichar_to_utf8(wc, ch);
4143 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4144 (clen == 3 && (
4145 wc == 0x2022 || /* BULLET */
4146 wc == 0x2023 || /* TRIANGULAR BULLET */
4147 wc == 0x2043 || /* HYPHEN BULLET */
4148 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4149 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4150 wc == 0x2219 || /* BULLET OPERATOR */
4151 wc == 0x25d8 || /* INVERSE BULLET */
4152 wc == 0x25e6 || /* WHITE BULLET */
4153 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4154 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4155 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4156 wc == 0x29be || /* CIRCLED WHITE BULLET */
4157 wc == 0x29bf /* CIRCLED BULLET */
4158 ))))
4159 return 0;
4161 gtk_text_iter_forward_char(&iter);
4162 if (gtk_text_iter_ends_line(&iter))
4163 return 0;
4164 wc = gtk_text_iter_get_char(&iter);
4165 if (g_unichar_isspace(wc)) {
4166 return len+1;
4168 return 0;
4171 /* return the string at the start of the itemization */
4172 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4173 const GtkTextIter *start)
4175 GtkTextIter iter = *start;
4176 gunichar wc;
4177 gint len = 0;
4178 GString *item_chars = g_string_new("");
4179 gchar *str = NULL;
4181 if (gtk_text_iter_ends_line(&iter)) {
4182 g_string_free(item_chars, FALSE);
4183 return NULL;
4186 while (1) {
4187 len++;
4188 wc = gtk_text_iter_get_char(&iter);
4189 if (!g_unichar_isspace(wc))
4190 break;
4191 gtk_text_iter_forward_char(&iter);
4192 if (gtk_text_iter_ends_line(&iter))
4193 break;
4194 g_string_append_unichar(item_chars, wc);
4197 str = item_chars->str;
4198 g_string_free(item_chars, FALSE);
4199 return str;
4202 /* return the number of spaces at a line's start */
4203 static int compose_left_offset_length(GtkTextBuffer *buffer,
4204 const GtkTextIter *start)
4206 GtkTextIter iter = *start;
4207 gunichar wc;
4208 gint len = 0;
4209 if (gtk_text_iter_ends_line(&iter))
4210 return 0;
4212 while (1) {
4213 wc = gtk_text_iter_get_char(&iter);
4214 if (!g_unichar_isspace(wc))
4215 break;
4216 len++;
4217 gtk_text_iter_forward_char(&iter);
4218 if (gtk_text_iter_ends_line(&iter))
4219 return 0;
4222 gtk_text_iter_forward_char(&iter);
4223 if (gtk_text_iter_ends_line(&iter))
4224 return 0;
4225 return len;
4228 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4229 const GtkTextIter *start,
4230 GtkTextIter *break_pos,
4231 gint max_col,
4232 gint quote_len)
4234 GtkTextIter iter = *start, line_end = *start;
4235 PangoLogAttr *attrs;
4236 gchar *str;
4237 gchar *p;
4238 gint len;
4239 gint i;
4240 gint col = 0;
4241 gint pos = 0;
4242 gboolean can_break = FALSE;
4243 gboolean do_break = FALSE;
4244 gboolean was_white = FALSE;
4245 gboolean prev_dont_break = FALSE;
4247 gtk_text_iter_forward_to_line_end(&line_end);
4248 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4249 len = g_utf8_strlen(str, -1);
4251 if (len == 0) {
4252 g_free(str);
4253 g_warning("compose_get_line_break_pos: len = 0!");
4254 return FALSE;
4257 /* g_print("breaking line: %d: %s (len = %d)\n",
4258 gtk_text_iter_get_line(&iter), str, len); */
4260 attrs = g_new(PangoLogAttr, len + 1);
4262 pango_default_break(str, -1, NULL, attrs, len + 1);
4264 p = str;
4266 /* skip quote and leading spaces */
4267 for (i = 0; *p != '\0' && i < len; i++) {
4268 gunichar wc;
4270 wc = g_utf8_get_char(p);
4271 if (i >= quote_len && !g_unichar_isspace(wc))
4272 break;
4273 if (g_unichar_iswide(wc))
4274 col += 2;
4275 else if (*p == '\t')
4276 col += 8;
4277 else
4278 col++;
4279 p = g_utf8_next_char(p);
4282 for (; *p != '\0' && i < len; i++) {
4283 PangoLogAttr *attr = attrs + i;
4284 gunichar wc = g_utf8_get_char(p);
4285 gint uri_len;
4287 /* attr->is_line_break will be false for some characters that
4288 * we want to break a line before, like '/' or ':', so we
4289 * also allow breaking on any non-wide character. The
4290 * mentioned pango attribute is still useful to decide on
4291 * line breaks when wide characters are involved. */
4292 if ((!g_unichar_iswide(wc) || attr->is_line_break)
4293 && can_break && was_white && !prev_dont_break)
4294 pos = i;
4296 was_white = attr->is_white;
4298 /* don't wrap URI */
4299 if ((uri_len = get_uri_len(p)) > 0) {
4300 col += uri_len;
4301 if (pos > 0 && col > max_col) {
4302 do_break = TRUE;
4303 break;
4305 i += uri_len - 1;
4306 p += uri_len;
4307 can_break = TRUE;
4308 continue;
4311 if (g_unichar_iswide(wc)) {
4312 col += 2;
4313 if (prev_dont_break && can_break && attr->is_line_break)
4314 pos = i;
4315 } else if (*p == '\t')
4316 col += 8;
4317 else
4318 col++;
4319 if (pos > 0 && col > max_col) {
4320 do_break = TRUE;
4321 break;
4324 if (*p == '-' || *p == '/')
4325 prev_dont_break = TRUE;
4326 else
4327 prev_dont_break = FALSE;
4329 p = g_utf8_next_char(p);
4330 can_break = TRUE;
4333 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4335 g_free(attrs);
4336 g_free(str);
4338 *break_pos = *start;
4339 gtk_text_iter_set_line_offset(break_pos, pos);
4341 return do_break;
4344 static gboolean compose_join_next_line(Compose *compose,
4345 GtkTextBuffer *buffer,
4346 GtkTextIter *iter,
4347 const gchar *quote_str)
4349 GtkTextIter iter_ = *iter, cur, prev, next, end;
4350 PangoLogAttr attrs[3];
4351 gchar *str;
4352 gchar *next_quote_str;
4353 gunichar wc1, wc2;
4354 gint quote_len;
4355 gboolean keep_cursor = FALSE;
4357 if (!gtk_text_iter_forward_line(&iter_) ||
4358 gtk_text_iter_ends_line(&iter_)) {
4359 return FALSE;
4361 next_quote_str = compose_get_quote_str(buffer, &iter_, &quote_len);
4363 if ((quote_str || next_quote_str) &&
4364 g_strcmp0(quote_str, next_quote_str) != 0) {
4365 g_free(next_quote_str);
4366 return FALSE;
4368 g_free(next_quote_str);
4370 end = iter_;
4371 if (quote_len > 0) {
4372 gtk_text_iter_forward_chars(&end, quote_len);
4373 if (gtk_text_iter_ends_line(&end)) {
4374 return FALSE;
4378 /* don't join itemized lines */
4379 if (compose_itemized_length(buffer, &end) > 0) {
4380 return FALSE;
4383 /* don't join signature separator */
4384 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4385 return FALSE;
4387 /* delete quote str */
4388 if (quote_len > 0)
4389 gtk_text_buffer_delete(buffer, &iter_, &end);
4391 /* don't join line breaks put by the user */
4392 prev = cur = iter_;
4393 gtk_text_iter_backward_char(&cur);
4394 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4395 gtk_text_iter_forward_char(&cur);
4396 *iter = cur;
4397 return FALSE;
4399 gtk_text_iter_forward_char(&cur);
4400 /* delete linebreak and extra spaces */
4401 while (gtk_text_iter_backward_char(&cur)) {
4402 wc1 = gtk_text_iter_get_char(&cur);
4403 if (!g_unichar_isspace(wc1))
4404 break;
4405 prev = cur;
4407 next = cur = iter_;
4408 while (!gtk_text_iter_ends_line(&cur)) {
4409 wc1 = gtk_text_iter_get_char(&cur);
4410 if (!g_unichar_isspace(wc1))
4411 break;
4412 gtk_text_iter_forward_char(&cur);
4413 next = cur;
4415 if (!gtk_text_iter_equal(&prev, &next)) {
4416 GtkTextMark *mark;
4418 mark = gtk_text_buffer_get_insert(buffer);
4419 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4420 if (gtk_text_iter_equal(&prev, &cur))
4421 keep_cursor = TRUE;
4422 gtk_text_buffer_delete(buffer, &prev, &next);
4424 iter_ = prev;
4426 /* insert space if required */
4427 gtk_text_iter_backward_char(&prev);
4428 wc1 = gtk_text_iter_get_char(&prev);
4429 wc2 = gtk_text_iter_get_char(&next);
4430 gtk_text_iter_forward_char(&next);
4431 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4432 pango_default_break(str, -1, NULL, attrs, 3);
4433 if (!attrs[1].is_line_break ||
4434 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4435 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4436 if (keep_cursor) {
4437 gtk_text_iter_backward_char(&iter_);
4438 gtk_text_buffer_place_cursor(buffer, &iter_);
4441 g_free(str);
4443 *iter = iter_;
4444 return TRUE;
4447 #define ADD_TXT_POS(bp_, ep_, pti_) \
4448 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4449 last = last->next; \
4450 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4451 last->next = NULL; \
4452 } else { \
4453 g_warning("alloc error scanning URIs"); \
4456 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4458 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4459 GtkTextBuffer *buffer;
4460 GtkTextIter iter, break_pos, end_of_line;
4461 gchar *quote_str = NULL;
4462 gint quote_len;
4463 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4464 gboolean prev_autowrap = compose->autowrap;
4465 gint startq_offset = -1, noq_offset = -1;
4466 gint uri_start = -1, uri_stop = -1;
4467 gint nouri_start = -1, nouri_stop = -1;
4468 gint num_blocks = 0;
4469 gint quotelevel = -1;
4470 gboolean modified = force;
4471 gboolean removed = FALSE;
4472 gboolean modified_before_remove = FALSE;
4473 gint lines = 0;
4474 gboolean start = TRUE;
4475 gint itemized_len = 0, rem_item_len = 0;
4476 gchar *itemized_chars = NULL;
4477 gboolean item_continuation = FALSE;
4479 if (force) {
4480 modified = TRUE;
4482 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4483 modified = TRUE;
4486 compose->autowrap = FALSE;
4488 buffer = gtk_text_view_get_buffer(text);
4489 undo_wrapping(compose->undostruct, TRUE);
4490 if (par_iter) {
4491 iter = *par_iter;
4492 } else {
4493 GtkTextMark *mark;
4494 mark = gtk_text_buffer_get_insert(buffer);
4495 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4499 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4500 if (gtk_text_iter_ends_line(&iter)) {
4501 while (gtk_text_iter_ends_line(&iter) &&
4502 gtk_text_iter_forward_line(&iter))
4504 } else {
4505 while (gtk_text_iter_backward_line(&iter)) {
4506 if (gtk_text_iter_ends_line(&iter)) {
4507 gtk_text_iter_forward_line(&iter);
4508 break;
4512 } else {
4513 /* move to line start */
4514 gtk_text_iter_set_line_offset(&iter, 0);
4517 itemized_len = compose_itemized_length(buffer, &iter);
4519 if (!itemized_len) {
4520 itemized_len = compose_left_offset_length(buffer, &iter);
4521 item_continuation = TRUE;
4524 if (itemized_len)
4525 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4527 /* go until paragraph end (empty line) */
4528 while (start || !gtk_text_iter_ends_line(&iter)) {
4529 gchar *scanpos = NULL;
4530 /* parse table - in order of priority */
4531 struct table {
4532 const gchar *needle; /* token */
4534 /* token search function */
4535 gchar *(*search) (const gchar *haystack,
4536 const gchar *needle);
4537 /* part parsing function */
4538 gboolean (*parse) (const gchar *start,
4539 const gchar *scanpos,
4540 const gchar **bp_,
4541 const gchar **ep_,
4542 gboolean hdr);
4543 /* part to URI function */
4544 gchar *(*build_uri) (const gchar *bp,
4545 const gchar *ep);
4548 static struct table parser[] = {
4549 {"http://", strcasestr, get_uri_part, make_uri_string},
4550 {"https://", strcasestr, get_uri_part, make_uri_string},
4551 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4552 {"ftps://", strcasestr, get_uri_part, make_uri_string},
4553 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4554 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4555 {"www.", strcasestr, get_uri_part, make_http_string},
4556 {"webcal://",strcasestr, get_uri_part, make_uri_string},
4557 {"webcals://",strcasestr, get_uri_part, make_uri_string},
4558 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4559 {"@", strcasestr, get_email_part, make_email_string}
4561 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4562 gint last_index = PARSE_ELEMS;
4563 gint n;
4564 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4565 gint walk_pos;
4567 start = FALSE;
4568 if (!prev_autowrap && num_blocks == 0) {
4569 num_blocks++;
4570 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4571 G_CALLBACK(text_inserted),
4572 compose);
4574 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4575 goto colorize;
4577 uri_start = uri_stop = -1;
4578 quote_len = 0;
4579 quote_str = compose_get_quote_str(buffer, &iter, &quote_len);
4581 if (quote_str) {
4582 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4583 if (startq_offset == -1)
4584 startq_offset = gtk_text_iter_get_offset(&iter);
4585 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4586 if (quotelevel > 2) {
4587 /* recycle colors */
4588 if (prefs_common.recycle_quote_colors)
4589 quotelevel %= 3;
4590 else
4591 quotelevel = 2;
4593 if (!wrap_quote) {
4594 goto colorize;
4596 } else {
4597 if (startq_offset == -1)
4598 noq_offset = gtk_text_iter_get_offset(&iter);
4599 quotelevel = -1;
4602 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4603 goto colorize;
4605 if (gtk_text_iter_ends_line(&iter)) {
4606 goto colorize;
4607 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4608 prefs_common.linewrap_len,
4609 quote_len)) {
4610 GtkTextIter prev, next, cur;
4611 if (prev_autowrap != FALSE || force) {
4612 compose->automatic_break = TRUE;
4613 modified = TRUE;
4614 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4615 compose->automatic_break = FALSE;
4616 if (itemized_len && compose->autoindent) {
4617 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4618 if (!item_continuation)
4619 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4621 } else if (quote_str && wrap_quote) {
4622 compose->automatic_break = TRUE;
4623 modified = TRUE;
4624 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4625 compose->automatic_break = FALSE;
4626 if (itemized_len && compose->autoindent) {
4627 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4628 if (!item_continuation)
4629 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4631 } else
4632 goto colorize;
4633 /* remove trailing spaces */
4634 cur = break_pos;
4635 rem_item_len = itemized_len;
4636 while (compose->autoindent && rem_item_len-- > 0)
4637 gtk_text_iter_backward_char(&cur);
4638 gtk_text_iter_backward_char(&cur);
4640 prev = next = cur;
4641 while (!gtk_text_iter_starts_line(&cur)) {
4642 gunichar wc;
4644 gtk_text_iter_backward_char(&cur);
4645 wc = gtk_text_iter_get_char(&cur);
4646 if (!g_unichar_isspace(wc))
4647 break;
4648 prev = cur;
4650 if (!gtk_text_iter_equal(&prev, &next)) {
4651 gtk_text_buffer_delete(buffer, &prev, &next);
4652 break_pos = next;
4653 gtk_text_iter_forward_char(&break_pos);
4656 if (quote_str)
4657 gtk_text_buffer_insert(buffer, &break_pos,
4658 quote_str, -1);
4660 iter = break_pos;
4661 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4663 /* move iter to current line start */
4664 gtk_text_iter_set_line_offset(&iter, 0);
4665 if (quote_str) {
4666 g_free(quote_str);
4667 quote_str = NULL;
4669 continue;
4670 } else {
4671 /* move iter to next line start */
4672 iter = break_pos;
4673 lines++;
4676 colorize:
4677 if (!prev_autowrap && num_blocks > 0) {
4678 num_blocks--;
4679 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4680 G_CALLBACK(text_inserted),
4681 compose);
4683 end_of_line = iter;
4684 while (!gtk_text_iter_ends_line(&end_of_line)) {
4685 gtk_text_iter_forward_char(&end_of_line);
4687 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4689 nouri_start = gtk_text_iter_get_offset(&iter);
4690 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4692 walk_pos = gtk_text_iter_get_offset(&iter);
4693 /* FIXME: this looks phony. scanning for anything in the parse table */
4694 for (n = 0; n < PARSE_ELEMS; n++) {
4695 gchar *tmp;
4697 tmp = parser[n].search(walk, parser[n].needle);
4698 if (tmp) {
4699 if (scanpos == NULL || tmp < scanpos) {
4700 scanpos = tmp;
4701 last_index = n;
4706 bp = ep = 0;
4707 if (scanpos) {
4708 /* check if URI can be parsed */
4709 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4710 (const gchar **)&ep, FALSE)
4711 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4712 walk = ep;
4713 } else
4714 walk = scanpos +
4715 strlen(parser[last_index].needle);
4717 if (bp && ep) {
4718 uri_start = walk_pos + (bp - o_walk);
4719 uri_stop = walk_pos + (ep - o_walk);
4721 g_free(o_walk);
4722 o_walk = NULL;
4723 gtk_text_iter_forward_line(&iter);
4724 g_free(quote_str);
4725 quote_str = NULL;
4726 if (startq_offset != -1) {
4727 GtkTextIter startquote, endquote;
4728 gtk_text_buffer_get_iter_at_offset(
4729 buffer, &startquote, startq_offset);
4730 endquote = iter;
4732 switch (quotelevel) {
4733 case 0:
4734 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4735 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4736 gtk_text_buffer_apply_tag_by_name(
4737 buffer, "quote0", &startquote, &endquote);
4738 gtk_text_buffer_remove_tag_by_name(
4739 buffer, "quote1", &startquote, &endquote);
4740 gtk_text_buffer_remove_tag_by_name(
4741 buffer, "quote2", &startquote, &endquote);
4742 modified = TRUE;
4744 break;
4745 case 1:
4746 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4747 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4748 gtk_text_buffer_apply_tag_by_name(
4749 buffer, "quote1", &startquote, &endquote);
4750 gtk_text_buffer_remove_tag_by_name(
4751 buffer, "quote0", &startquote, &endquote);
4752 gtk_text_buffer_remove_tag_by_name(
4753 buffer, "quote2", &startquote, &endquote);
4754 modified = TRUE;
4756 break;
4757 case 2:
4758 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4759 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4760 gtk_text_buffer_apply_tag_by_name(
4761 buffer, "quote2", &startquote, &endquote);
4762 gtk_text_buffer_remove_tag_by_name(
4763 buffer, "quote0", &startquote, &endquote);
4764 gtk_text_buffer_remove_tag_by_name(
4765 buffer, "quote1", &startquote, &endquote);
4766 modified = TRUE;
4768 break;
4770 startq_offset = -1;
4771 } else if (noq_offset != -1) {
4772 GtkTextIter startnoquote, endnoquote;
4773 gtk_text_buffer_get_iter_at_offset(
4774 buffer, &startnoquote, noq_offset);
4775 endnoquote = iter;
4777 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4778 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4779 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4780 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4781 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4782 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4783 gtk_text_buffer_remove_tag_by_name(
4784 buffer, "quote0", &startnoquote, &endnoquote);
4785 gtk_text_buffer_remove_tag_by_name(
4786 buffer, "quote1", &startnoquote, &endnoquote);
4787 gtk_text_buffer_remove_tag_by_name(
4788 buffer, "quote2", &startnoquote, &endnoquote);
4789 modified = TRUE;
4791 noq_offset = -1;
4794 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4795 GtkTextIter nouri_start_iter, nouri_end_iter;
4796 gtk_text_buffer_get_iter_at_offset(
4797 buffer, &nouri_start_iter, nouri_start);
4798 gtk_text_buffer_get_iter_at_offset(
4799 buffer, &nouri_end_iter, nouri_stop);
4800 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4801 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4802 gtk_text_buffer_remove_tag_by_name(
4803 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4804 modified_before_remove = modified;
4805 modified = TRUE;
4806 removed = TRUE;
4809 if (uri_start >= 0 && uri_stop > 0) {
4810 GtkTextIter uri_start_iter, uri_end_iter, back;
4811 gtk_text_buffer_get_iter_at_offset(
4812 buffer, &uri_start_iter, uri_start);
4813 gtk_text_buffer_get_iter_at_offset(
4814 buffer, &uri_end_iter, uri_stop);
4815 back = uri_end_iter;
4816 gtk_text_iter_backward_char(&back);
4817 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4818 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4819 gtk_text_buffer_apply_tag_by_name(
4820 buffer, "link", &uri_start_iter, &uri_end_iter);
4821 modified = TRUE;
4822 if (removed && !modified_before_remove) {
4823 modified = FALSE;
4827 if (!modified) {
4828 /* debug_print("not modified, out after %d lines\n", lines); */
4829 goto end;
4832 /* debug_print("modified, out after %d lines\n", lines); */
4833 end:
4834 g_free(itemized_chars);
4835 if (par_iter)
4836 *par_iter = iter;
4837 undo_wrapping(compose->undostruct, FALSE);
4838 compose->autowrap = prev_autowrap;
4840 return modified;
4843 void compose_action_cb(void *data)
4845 Compose *compose = (Compose *)data;
4846 compose_wrap_all(compose);
4849 static void compose_wrap_all(Compose *compose)
4851 compose_wrap_all_full(compose, FALSE);
4854 static void compose_wrap_all_full(Compose *compose, gboolean force)
4856 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4857 GtkTextBuffer *buffer;
4858 GtkTextIter iter;
4859 gboolean modified = TRUE;
4861 buffer = gtk_text_view_get_buffer(text);
4863 gtk_text_buffer_get_start_iter(buffer, &iter);
4865 undo_wrapping(compose->undostruct, TRUE);
4867 while (!gtk_text_iter_is_end(&iter) && modified)
4868 modified = compose_beautify_paragraph(compose, &iter, force);
4870 undo_wrapping(compose->undostruct, FALSE);
4874 static void compose_set_title(Compose *compose)
4876 gchar *str;
4877 gchar *edited;
4878 gchar *subject;
4880 edited = compose->modified ? _(" [Edited]") : "";
4882 subject = gtk_editable_get_chars(
4883 GTK_EDITABLE(compose->subject_entry), 0, -1);
4885 #ifndef GENERIC_UMPC
4886 if (subject && strlen(subject))
4887 str = g_strdup_printf(_("%s - Compose message%s"),
4888 subject, edited);
4889 else
4890 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4891 #else
4892 str = g_strdup(_("Compose message"));
4893 #endif
4895 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4896 g_free(str);
4897 g_free(subject);
4901 * compose_current_mail_account:
4903 * Find a current mail account (the currently selected account, or the
4904 * default account, if a news account is currently selected). If a
4905 * mail account cannot be found, display an error message.
4907 * Return value: Mail account, or NULL if not found.
4909 static PrefsAccount *
4910 compose_current_mail_account(void)
4912 PrefsAccount *ac;
4914 if (cur_account && cur_account->protocol != A_NNTP)
4915 ac = cur_account;
4916 else {
4917 ac = account_get_default();
4918 if (!ac || ac->protocol == A_NNTP) {
4919 alertpanel_error(_("Account for sending mail is not specified.\n"
4920 "Please select a mail account before sending."));
4921 return NULL;
4924 return ac;
4927 #define QUOTE_IF_REQUIRED(out, str) \
4929 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4930 gchar *__tmp; \
4931 gint len; \
4933 len = strlen(str) + 3; \
4934 if ((__tmp = alloca(len)) == NULL) { \
4935 g_warning("can't allocate memory"); \
4936 g_string_free(header, TRUE); \
4937 return NULL; \
4939 g_snprintf(__tmp, len, "\"%s\"", str); \
4940 out = __tmp; \
4941 } else { \
4942 gchar *__tmp; \
4944 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4945 g_warning("can't allocate memory"); \
4946 g_string_free(header, TRUE); \
4947 return NULL; \
4948 } else \
4949 strcpy(__tmp, str); \
4951 out = __tmp; \
4955 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4957 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4958 gchar *__tmp; \
4959 gint len; \
4961 len = strlen(str) + 3; \
4962 if ((__tmp = alloca(len)) == NULL) { \
4963 g_warning("can't allocate memory"); \
4964 errret; \
4966 g_snprintf(__tmp, len, "\"%s\"", str); \
4967 out = __tmp; \
4968 } else { \
4969 gchar *__tmp; \
4971 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4972 g_warning("can't allocate memory"); \
4973 errret; \
4974 } else \
4975 strcpy(__tmp, str); \
4977 out = __tmp; \
4981 static void compose_select_account(Compose *compose, PrefsAccount *account,
4982 gboolean init)
4984 gchar *from = NULL, *header = NULL;
4985 ComposeHeaderEntry *header_entry;
4986 GtkTreeIter iter;
4988 cm_return_if_fail(account != NULL);
4990 compose->account = account;
4991 if (account->name && *account->name) {
4992 gchar *buf, *qbuf;
4993 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4994 qbuf = escape_internal_quotes(buf, '"');
4995 from = g_strdup_printf("%s <%s>",
4996 qbuf, account->address);
4997 if (qbuf != buf)
4998 g_free(qbuf);
4999 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
5000 } else {
5001 from = g_strdup_printf("<%s>",
5002 account->address);
5003 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
5006 g_free(from);
5008 compose_set_title(compose);
5010 compose_activate_privacy_system(compose, account, FALSE);
5012 if (account->default_sign && privacy_system_can_sign(compose->privacy_system) &&
5013 compose->mode != COMPOSE_REDIRECT)
5014 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
5015 else
5016 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
5017 if (account->default_encrypt && privacy_system_can_encrypt(compose->privacy_system) &&
5018 compose->mode != COMPOSE_REDIRECT)
5019 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
5020 else
5021 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
5023 if (!init && compose->mode != COMPOSE_REDIRECT) {
5024 undo_block(compose->undostruct);
5025 compose_insert_sig(compose, TRUE);
5026 undo_unblock(compose->undostruct);
5029 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
5030 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
5031 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
5032 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5034 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5035 if (account->protocol == A_NNTP) {
5036 if (!strcmp(header, _("To:")))
5037 combobox_select_by_text(
5038 GTK_COMBO_BOX(header_entry->combo),
5039 _("Newsgroups:"));
5040 } else {
5041 if (!strcmp(header, _("Newsgroups:")))
5042 combobox_select_by_text(
5043 GTK_COMBO_BOX(header_entry->combo),
5044 _("To:"));
5048 g_free(header);
5050 #ifdef USE_ENCHANT
5051 /* use account's dict info if set */
5052 if (compose->gtkaspell) {
5053 if (account->enable_default_dictionary)
5054 gtkaspell_change_dict(compose->gtkaspell,
5055 account->default_dictionary, FALSE);
5056 if (account->enable_default_alt_dictionary)
5057 gtkaspell_change_alt_dict(compose->gtkaspell,
5058 account->default_alt_dictionary);
5059 if (account->enable_default_dictionary
5060 || account->enable_default_alt_dictionary)
5061 compose_spell_menu_changed(compose);
5063 #endif
5066 gboolean compose_check_for_valid_recipient(Compose *compose) {
5067 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5068 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5069 gboolean recipient_found = FALSE;
5070 GSList *list;
5071 gchar **strptr;
5073 /* free to and newsgroup list */
5074 slist_free_strings_full(compose->to_list);
5075 compose->to_list = NULL;
5077 slist_free_strings_full(compose->newsgroup_list);
5078 compose->newsgroup_list = NULL;
5080 /* search header entries for to and newsgroup entries */
5081 for (list = compose->header_list; list; list = list->next) {
5082 gchar *header;
5083 gchar *entry;
5084 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5085 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5086 g_strstrip(entry);
5087 g_strstrip(header);
5088 if (entry[0] != '\0') {
5089 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5090 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5091 compose->to_list = address_list_append(compose->to_list, entry);
5092 recipient_found = TRUE;
5095 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5096 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5097 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5098 recipient_found = TRUE;
5102 g_free(header);
5103 g_free(entry);
5105 return recipient_found;
5108 static gboolean compose_check_for_set_recipients(Compose *compose)
5110 if (compose->account->set_autocc && compose->account->auto_cc) {
5111 gboolean found_other = FALSE;
5112 GSList *list;
5113 /* search header entries for to and newsgroup entries */
5114 for (list = compose->header_list; list; list = list->next) {
5115 gchar *entry;
5116 gchar *header;
5117 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5118 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5119 g_strstrip(entry);
5120 g_strstrip(header);
5121 if (strcmp(entry, compose->account->auto_cc)
5122 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5123 found_other = TRUE;
5124 g_free(entry);
5125 break;
5127 g_free(entry);
5128 g_free(header);
5130 if (!found_other) {
5131 AlertValue aval;
5132 gchar *text;
5133 if (compose->batch) {
5134 gtk_widget_show_all(compose->window);
5136 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5137 prefs_common_translated_header_name("Cc"));
5138 aval = alertpanel(_("Send"),
5139 text,
5140 NULL, _("_Cancel"), NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND);
5141 g_free(text);
5142 if (aval != G_ALERTALTERNATE)
5143 return FALSE;
5146 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5147 gboolean found_other = FALSE;
5148 GSList *list;
5149 /* search header entries for to and newsgroup entries */
5150 for (list = compose->header_list; list; list = list->next) {
5151 gchar *entry;
5152 gchar *header;
5153 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5154 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5155 g_strstrip(entry);
5156 g_strstrip(header);
5157 if (strcmp(entry, compose->account->auto_bcc)
5158 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5159 found_other = TRUE;
5160 g_free(entry);
5161 g_free(header);
5162 break;
5164 g_free(entry);
5165 g_free(header);
5167 if (!found_other) {
5168 AlertValue aval;
5169 gchar *text;
5170 if (compose->batch) {
5171 gtk_widget_show_all(compose->window);
5173 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5174 prefs_common_translated_header_name("Bcc"));
5175 aval = alertpanel(_("Send"),
5176 text,
5177 NULL, _("_Cancel"), NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND);
5178 g_free(text);
5179 if (aval != G_ALERTALTERNATE)
5180 return FALSE;
5183 return TRUE;
5186 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5188 const gchar *str;
5190 if (compose_check_for_valid_recipient(compose) == FALSE) {
5191 if (compose->batch) {
5192 gtk_widget_show_all(compose->window);
5194 alertpanel_error(_("Recipient is not specified."));
5195 return FALSE;
5198 if (compose_check_for_set_recipients(compose) == FALSE) {
5199 return FALSE;
5202 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5203 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5204 if (*str == '\0' && check_everything == TRUE &&
5205 compose->mode != COMPOSE_REDIRECT) {
5206 AlertValue aval;
5207 gchar *message;
5209 message = g_strdup_printf(_("Subject is empty. %s"),
5210 compose->sending?_("Send it anyway?"):
5211 _("Queue it anyway?"));
5213 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5214 NULL, _("_Cancel"), NULL, compose->sending?_("_Send"):_("_Queue"),
5215 NULL, NULL, ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5216 g_free(message);
5217 if (aval & G_ALERTDISABLE) {
5218 aval &= ~G_ALERTDISABLE;
5219 prefs_common.warn_empty_subj = FALSE;
5221 if (aval != G_ALERTALTERNATE)
5222 return FALSE;
5226 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5227 && check_everything == TRUE) {
5228 GSList *list;
5229 gint cnt = 0;
5231 /* count To and Cc recipients */
5232 for (list = compose->header_list; list; list = list->next) {
5233 gchar *header;
5234 gchar *entry;
5236 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5237 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5238 g_strstrip(header);
5239 g_strstrip(entry);
5240 if ((entry[0] != '\0') &&
5241 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5242 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5243 cnt++;
5245 g_free(header);
5246 g_free(entry);
5248 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5249 AlertValue aval;
5250 gchar *message;
5252 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5253 compose->sending?_("Send it anyway?"):
5254 _("Queue it anyway?"));
5256 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5257 NULL, _("_Cancel"), NULL, compose->sending?_("_Send"):_("_Queue"),
5258 NULL, NULL, ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5259 g_free(message);
5260 if (aval & G_ALERTDISABLE) {
5261 aval &= ~G_ALERTDISABLE;
5262 prefs_common.warn_sending_many_recipients_num = 0;
5264 if (aval != G_ALERTALTERNATE)
5265 return FALSE;
5269 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5270 return FALSE;
5272 return TRUE;
5275 static void _display_queue_error(ComposeQueueResult val)
5277 switch (val) {
5278 case COMPOSE_QUEUE_SUCCESS:
5279 break;
5280 case COMPOSE_QUEUE_ERROR_NO_MSG:
5281 alertpanel_error(_("Could not queue message."));
5282 break;
5283 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5284 alertpanel_error(_("Could not queue message:\n\n%s."),
5285 g_strerror(errno));
5286 break;
5287 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5288 alertpanel_error(_("Could not queue message for sending:\n\n"
5289 "Signature failed: %s"),
5290 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5291 break;
5292 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5293 alertpanel_error(_("Could not queue message for sending:\n\n"
5294 "Encryption failed: %s"),
5295 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5296 break;
5297 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5298 alertpanel_error(_("Could not queue message for sending:\n\n"
5299 "Charset conversion failed."));
5300 break;
5301 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5302 alertpanel_error(_("Could not queue message for sending:\n\n"
5303 "Couldn't get recipient encryption key."));
5304 break;
5305 case COMPOSE_QUEUE_SIGNING_CANCELLED:
5306 debug_print("signing cancelled\n");
5307 break;
5308 default:
5309 /* unhandled error */
5310 debug_print("oops, unhandled compose_queue() return value %d\n",
5311 val);
5312 break;
5316 gint compose_send(Compose *compose)
5318 gint msgnum;
5319 FolderItem *folder = NULL;
5320 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5321 gchar *msgpath = NULL;
5322 gboolean discard_window = FALSE;
5323 gchar *errstr = NULL;
5324 gchar *tmsgid = NULL;
5325 MainWindow *mainwin = mainwindow_get_mainwindow();
5326 gboolean queued_removed = FALSE;
5328 if (prefs_common.send_dialog_invisible
5329 || compose->batch == TRUE)
5330 discard_window = TRUE;
5332 compose_allow_user_actions (compose, FALSE);
5333 compose->sending = TRUE;
5335 if (compose_check_entries(compose, TRUE) == FALSE) {
5336 if (compose->batch) {
5337 gtk_widget_show_all(compose->window);
5339 goto bail;
5342 inc_lock();
5343 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5345 if (val != COMPOSE_QUEUE_SUCCESS) {
5346 if (compose->batch) {
5347 gtk_widget_show_all(compose->window);
5350 _display_queue_error(val);
5352 goto bail;
5355 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5356 if (discard_window) {
5357 compose->sending = FALSE;
5358 compose_close(compose);
5359 /* No more compose access in the normal codepath
5360 * after this point! */
5361 compose = NULL;
5364 if (msgnum == 0) {
5365 alertpanel_error(_("The message was queued but could not be "
5366 "sent.\nUse \"Send queued messages\" from "
5367 "the main window to retry."));
5368 if (!discard_window) {
5369 goto bail;
5371 inc_unlock();
5372 g_free(tmsgid);
5373 return -1;
5375 if (msgpath == NULL) {
5376 msgpath = folder_item_fetch_msg(folder, msgnum);
5377 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5378 g_free(msgpath);
5379 } else {
5380 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5381 claws_unlink(msgpath);
5382 g_free(msgpath);
5384 if (!discard_window) {
5385 if (val != 0) {
5386 if (!queued_removed)
5387 folder_item_remove_msg(folder, msgnum);
5388 folder_item_scan(folder);
5389 if (tmsgid) {
5390 /* make sure we delete that */
5391 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5392 if (tmp) {
5393 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5394 folder_item_remove_msg(folder, tmp->msgnum);
5395 procmsg_msginfo_free(&tmp);
5401 if (val == 0) {
5402 if (!queued_removed)
5403 folder_item_remove_msg(folder, msgnum);
5404 folder_item_scan(folder);
5405 if (tmsgid) {
5406 /* make sure we delete that */
5407 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5408 if (tmp) {
5409 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5410 folder_item_remove_msg(folder, tmp->msgnum);
5411 procmsg_msginfo_free(&tmp);
5414 if (!discard_window) {
5415 compose->sending = FALSE;
5416 compose_allow_user_actions (compose, TRUE);
5417 compose_close(compose);
5419 } else {
5420 if (errstr) {
5421 alertpanel_error_log(_("%s\nYou can try to \"Send\" again "
5422 "or queue the message with \"Send later\""), errstr);
5423 g_free(errstr);
5424 } else {
5425 alertpanel_error_log(_("The message was queued but could not be "
5426 "sent.\nUse \"Send queued messages\" from "
5427 "the main window to retry."));
5429 if (!discard_window) {
5430 goto bail;
5432 inc_unlock();
5433 g_free(tmsgid);
5434 return -1;
5436 g_free(tmsgid);
5437 inc_unlock();
5438 toolbar_main_set_sensitive(mainwin);
5439 main_window_set_menu_sensitive(mainwin);
5440 return 0;
5442 bail:
5443 inc_unlock();
5444 g_free(tmsgid);
5445 compose_allow_user_actions (compose, TRUE);
5446 compose->sending = FALSE;
5447 compose->modified = TRUE;
5448 toolbar_main_set_sensitive(mainwin);
5449 main_window_set_menu_sensitive(mainwin);
5451 return -1;
5454 static gboolean compose_use_attach(Compose *compose)
5456 GtkTreeModel *model = gtk_tree_view_get_model
5457 (GTK_TREE_VIEW(compose->attach_clist));
5458 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5461 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5462 FILE *fp)
5464 gchar buf[BUFFSIZE];
5465 gchar *str;
5466 gboolean first_to_address;
5467 gboolean first_cc_address;
5468 GSList *list;
5469 ComposeHeaderEntry *headerentry;
5470 const gchar *headerentryname;
5471 const gchar *cc_hdr;
5472 const gchar *to_hdr;
5473 gboolean err = FALSE;
5475 debug_print("Writing redirect header\n");
5477 cc_hdr = prefs_common_translated_header_name("Cc:");
5478 to_hdr = prefs_common_translated_header_name("To:");
5480 first_to_address = TRUE;
5481 for (list = compose->header_list; list; list = list->next) {
5482 headerentry = ((ComposeHeaderEntry *)list->data);
5483 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5485 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5486 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5487 Xstrdup_a(str, entstr, return -1);
5488 g_strstrip(str);
5489 if (str[0] != '\0') {
5490 compose_convert_header
5491 (compose, buf, sizeof(buf), str,
5492 strlen("Resent-To") + 2, TRUE);
5494 if (first_to_address) {
5495 err |= (fprintf(fp, "Resent-To: ") < 0);
5496 first_to_address = FALSE;
5497 } else {
5498 err |= (fprintf(fp, ",") < 0);
5500 err |= (fprintf(fp, "%s", buf) < 0);
5504 if (!first_to_address) {
5505 err |= (fprintf(fp, "\n") < 0);
5508 first_cc_address = TRUE;
5509 for (list = compose->header_list; list; list = list->next) {
5510 headerentry = ((ComposeHeaderEntry *)list->data);
5511 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5513 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5514 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5515 Xstrdup_a(str, strg, return -1);
5516 g_strstrip(str);
5517 if (str[0] != '\0') {
5518 compose_convert_header
5519 (compose, buf, sizeof(buf), str,
5520 strlen("Resent-Cc") + 2, TRUE);
5522 if (first_cc_address) {
5523 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5524 first_cc_address = FALSE;
5525 } else {
5526 err |= (fprintf(fp, ",") < 0);
5528 err |= (fprintf(fp, "%s", buf) < 0);
5532 if (!first_cc_address) {
5533 err |= (fprintf(fp, "\n") < 0);
5536 return (err ? -1:0);
5539 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5541 gchar date[RFC822_DATE_BUFFSIZE];
5542 gchar buf[BUFFSIZE];
5543 gchar *str;
5544 const gchar *entstr;
5545 /* struct utsname utsbuf; */
5546 gboolean err = FALSE;
5548 cm_return_val_if_fail(fp != NULL, -1);
5549 cm_return_val_if_fail(compose->account != NULL, -1);
5550 cm_return_val_if_fail(compose->account->address != NULL, -1);
5552 /* Resent-Date */
5553 if (prefs_common.hide_timezone)
5554 get_rfc822_date_hide_tz(date, sizeof(date));
5555 else
5556 get_rfc822_date(date, sizeof(date));
5557 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5559 /* Resent-From */
5560 if (compose->account->name && *compose->account->name) {
5561 compose_convert_header
5562 (compose, buf, sizeof(buf), compose->account->name,
5563 strlen("From: "), TRUE);
5564 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5565 buf, compose->account->address) < 0);
5566 } else
5567 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5569 /* Subject */
5570 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5571 if (*entstr != '\0') {
5572 Xstrdup_a(str, entstr, return -1);
5573 g_strstrip(str);
5574 if (*str != '\0') {
5575 compose_convert_header(compose, buf, sizeof(buf), str,
5576 strlen("Subject: "), FALSE);
5577 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5581 /* Resent-Message-ID */
5582 if (compose->account->gen_msgid) {
5583 gchar *addr = prefs_account_generate_msgid(compose->account);
5584 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5585 if (compose->msgid)
5586 g_free(compose->msgid);
5587 compose->msgid = addr;
5588 } else {
5589 compose->msgid = NULL;
5592 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5593 return -1;
5595 /* separator between header and body */
5596 err |= (claws_fputs("\n", fp) == EOF);
5598 return (err ? -1:0);
5601 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5603 FILE *fp;
5604 size_t len;
5605 gchar *buf = NULL;
5606 gchar rewrite_buf[BUFFSIZE];
5607 int i = 0;
5608 gboolean skip = FALSE;
5609 gboolean err = FALSE;
5610 gchar *not_included[]={
5611 "Return-Path:", "Delivered-To:", "Received:",
5612 "Subject:", "X-UIDL:", "AF:",
5613 "NF:", "PS:", "SRH:",
5614 "SFN:", "DSR:", "MID:",
5615 "CFG:", "PT:", "S:",
5616 "RQ:", "SSV:", "NSV:",
5617 "SSH:", "R:", "MAID:",
5618 "NAID:", "RMID:", "FMID:",
5619 "SCF:", "RRCPT:", "NG:",
5620 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5621 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5622 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5623 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5624 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5625 NULL
5627 gint ret = 0;
5629 if ((fp = claws_fopen(compose->redirect_filename, "rb")) == NULL) {
5630 FILE_OP_ERROR(compose->redirect_filename, "claws_fopen");
5631 return -1;
5634 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5635 skip = FALSE;
5636 for (i = 0; not_included[i] != NULL; i++) {
5637 if (g_ascii_strncasecmp(buf, not_included[i],
5638 strlen(not_included[i])) == 0) {
5639 skip = TRUE;
5640 break;
5643 if (skip) {
5644 g_free(buf);
5645 buf = NULL;
5646 continue;
5648 if (claws_fputs(buf, fdest) == -1) {
5649 g_free(buf);
5650 buf = NULL;
5651 goto error;
5654 if (!prefs_common.redirect_keep_from) {
5655 if (g_ascii_strncasecmp(buf, "From:",
5656 strlen("From:")) == 0) {
5657 err |= (claws_fputs(" (by way of ", fdest) == EOF);
5658 if (compose->account->name
5659 && *compose->account->name) {
5660 gchar buffer[BUFFSIZE];
5662 compose_convert_header
5663 (compose, buffer, sizeof(buffer),
5664 compose->account->name,
5665 strlen("From: "),
5666 FALSE);
5667 err |= (fprintf(fdest, "%s <%s>",
5668 buffer,
5669 compose->account->address) < 0);
5670 } else
5671 err |= (fprintf(fdest, "%s",
5672 compose->account->address) < 0);
5673 err |= (claws_fputs(")", fdest) == EOF);
5677 g_free(buf);
5678 buf = NULL;
5679 if (claws_fputs("\n", fdest) == -1)
5680 goto error;
5683 if (err)
5684 goto error;
5686 if (compose_redirect_write_headers(compose, fdest))
5687 goto error;
5689 while ((len = claws_fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5690 if (claws_fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5691 goto error;
5694 claws_fclose(fp);
5696 return 0;
5698 error:
5699 claws_fclose(fp);
5701 return -1;
5704 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5706 GtkTextBuffer *buffer;
5707 GtkTextIter start, end, tmp;
5708 gchar *chars, *tmp_enc_file = NULL, *content;
5709 gchar *buf, *msg;
5710 const gchar *out_codeset;
5711 EncodingType encoding = ENC_UNKNOWN;
5712 MimeInfo *mimemsg, *mimetext;
5713 gint line;
5714 const gchar *src_codeset = CS_INTERNAL;
5715 gchar *from_addr = NULL;
5716 gchar *from_name = NULL;
5717 FolderItem *outbox;
5719 if (action == COMPOSE_WRITE_FOR_SEND) {
5720 attach_parts = TRUE;
5722 /* We're sending the message, generate a Message-ID
5723 * if necessary. */
5724 if (compose->msgid == NULL &&
5725 compose->account->gen_msgid) {
5726 compose->msgid = prefs_account_generate_msgid(compose->account);
5730 /* create message MimeInfo */
5731 mimemsg = procmime_mimeinfo_new();
5732 mimemsg->type = MIMETYPE_MESSAGE;
5733 mimemsg->subtype = g_strdup("rfc822");
5734 mimemsg->content = MIMECONTENT_MEM;
5735 mimemsg->tmp = TRUE; /* must free content later */
5736 mimemsg->data.mem = compose_get_header(compose);
5738 /* Create text part MimeInfo */
5739 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5740 gtk_text_buffer_get_end_iter(buffer, &end);
5741 tmp = end;
5743 /* We make sure that there is a newline at the end. */
5744 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5745 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5746 if (*chars != '\n') {
5747 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5749 g_free(chars);
5752 /* get all composed text */
5753 gtk_text_buffer_get_start_iter(buffer, &start);
5754 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5756 out_codeset = conv_get_charset_str(compose->out_encoding);
5758 if (!out_codeset && is_ascii_str(chars)) {
5759 out_codeset = CS_US_ASCII;
5760 } else if (prefs_common.outgoing_fallback_to_ascii &&
5761 is_ascii_str(chars)) {
5762 out_codeset = CS_US_ASCII;
5763 encoding = ENC_7BIT;
5766 if (!out_codeset) {
5767 gchar *test_conv_global_out = NULL;
5768 gchar *test_conv_reply = NULL;
5770 /* automatic mode. be automatic. */
5771 codeconv_set_strict(TRUE);
5773 out_codeset = conv_get_outgoing_charset_str();
5774 if (out_codeset) {
5775 debug_print("trying to convert to %s\n", out_codeset);
5776 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5779 if (!test_conv_global_out && compose->orig_charset
5780 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5781 out_codeset = compose->orig_charset;
5782 debug_print("failure; trying to convert to %s\n", out_codeset);
5783 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5786 if (!test_conv_global_out && !test_conv_reply) {
5787 /* we're lost */
5788 out_codeset = CS_INTERNAL;
5789 debug_print("failure; finally using %s\n", out_codeset);
5791 g_free(test_conv_global_out);
5792 g_free(test_conv_reply);
5793 codeconv_set_strict(FALSE);
5796 if (encoding == ENC_UNKNOWN) {
5797 if (prefs_common.encoding_method == CTE_BASE64)
5798 encoding = ENC_BASE64;
5799 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5800 encoding = ENC_QUOTED_PRINTABLE;
5801 else if (prefs_common.encoding_method == CTE_8BIT)
5802 encoding = ENC_8BIT;
5803 else
5804 encoding = procmime_get_encoding_for_charset(out_codeset);
5807 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5808 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5810 if (action == COMPOSE_WRITE_FOR_SEND) {
5811 codeconv_set_strict(TRUE);
5812 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5813 codeconv_set_strict(FALSE);
5815 if (!buf) {
5816 AlertValue aval;
5818 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5819 "to the specified %s charset.\n"
5820 "Send it as %s?"), out_codeset, src_codeset);
5821 aval = alertpanel_full(_("Error"), msg, NULL, _("_Cancel"),
5822 NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND, FALSE,
5823 NULL, ALERT_ERROR);
5824 g_free(msg);
5826 if (aval != G_ALERTALTERNATE) {
5827 g_free(chars);
5828 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5829 } else {
5830 buf = chars;
5831 out_codeset = src_codeset;
5832 chars = NULL;
5835 } else {
5836 buf = chars;
5837 out_codeset = src_codeset;
5838 chars = NULL;
5840 g_free(chars);
5842 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5843 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5844 strstr(buf, "\nFrom ") != NULL) {
5845 encoding = ENC_QUOTED_PRINTABLE;
5849 mimetext = procmime_mimeinfo_new();
5850 mimetext->content = MIMECONTENT_MEM;
5851 mimetext->tmp = TRUE; /* must free content later */
5852 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5853 * and free the data, which we need later. */
5854 mimetext->data.mem = g_strdup(buf);
5855 mimetext->type = MIMETYPE_TEXT;
5856 mimetext->subtype = g_strdup("plain");
5857 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5858 g_strdup(out_codeset));
5860 /* protect trailing spaces when signing message */
5861 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5862 privacy_system_can_sign(compose->privacy_system)) {
5863 encoding = ENC_QUOTED_PRINTABLE;
5866 debug_print("main text: %" G_GSIZE_FORMAT " bytes encoded as %s in %d\n",
5867 strlen(buf), out_codeset, encoding);
5869 /* check for line length limit */
5870 if (action == COMPOSE_WRITE_FOR_SEND &&
5871 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5872 check_line_length(buf, 1000, &line) < 0) {
5873 AlertValue aval;
5875 msg = g_strdup_printf
5876 (_("Line %d exceeds the line length limit (998 bytes).\n"
5877 "The contents of the message might be broken on the way to the delivery.\n"
5878 "\n"
5879 "Send it anyway?"), line + 1);
5880 aval = alertpanel(_("Warning"), msg, NULL, _("_Cancel"), NULL, _("_OK"),
5881 NULL, NULL, ALERTFOCUS_FIRST);
5882 g_free(msg);
5883 if (aval != G_ALERTALTERNATE) {
5884 g_free(buf);
5885 return COMPOSE_QUEUE_ERROR_NO_MSG;
5889 if (encoding != ENC_UNKNOWN)
5890 procmime_encode_content(mimetext, encoding);
5892 /* append attachment parts */
5893 if (compose_use_attach(compose) && attach_parts) {
5894 MimeInfo *mimempart;
5895 gchar *boundary = NULL;
5896 mimempart = procmime_mimeinfo_new();
5897 mimempart->content = MIMECONTENT_EMPTY;
5898 mimempart->type = MIMETYPE_MULTIPART;
5899 mimempart->subtype = g_strdup("mixed");
5901 do {
5902 g_free(boundary);
5903 boundary = generate_mime_boundary(NULL);
5904 } while (strstr(buf, boundary) != NULL);
5906 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5907 boundary);
5909 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5911 g_node_append(mimempart->node, mimetext->node);
5912 g_node_append(mimemsg->node, mimempart->node);
5914 if (compose_add_attachments(compose, mimempart) < 0)
5915 return COMPOSE_QUEUE_ERROR_NO_MSG;
5916 } else
5917 g_node_append(mimemsg->node, mimetext->node);
5919 g_free(buf);
5921 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5922 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5923 /* extract name and address */
5924 if (strstr(spec, " <") && strstr(spec, ">")) {
5925 from_addr = g_strdup(strrchr(spec, '<')+1);
5926 *(strrchr(from_addr, '>')) = '\0';
5927 from_name = g_strdup(spec);
5928 *(strrchr(from_name, '<')) = '\0';
5929 } else {
5930 from_name = NULL;
5931 from_addr = NULL;
5933 g_free(spec);
5935 /* sign message if sending */
5936 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5937 privacy_system_can_sign(compose->privacy_system))
5938 if (!privacy_sign(compose->privacy_system, mimemsg,
5939 compose->account, from_addr)) {
5940 g_free(from_name);
5941 g_free(from_addr);
5942 if (!privacy_peek_error())
5943 return COMPOSE_QUEUE_SIGNING_CANCELLED;
5944 else
5945 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5947 g_free(from_name);
5948 g_free(from_addr);
5950 if (compose->use_encryption) {
5951 if (compose->encdata != NULL &&
5952 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5954 /* First, write an unencrypted copy and save it to outbox, if
5955 * user wants that. */
5956 if (compose->account->save_encrypted_as_clear_text) {
5957 debug_print("saving sent message unencrypted...\n");
5958 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5959 if (tmpfp) {
5960 claws_fclose(tmpfp);
5962 /* fp now points to a file with headers written,
5963 * let's make a copy. */
5964 rewind(fp);
5965 content = file_read_stream_to_str(fp);
5967 str_write_to_file(content, tmp_enc_file, TRUE);
5968 g_free(content);
5970 /* Now write the unencrypted body. */
5971 if ((tmpfp = claws_fopen(tmp_enc_file, "a")) != NULL) {
5972 procmime_write_mimeinfo(mimemsg, tmpfp);
5973 claws_fclose(tmpfp);
5975 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5976 if (!outbox)
5977 outbox = folder_get_default_outbox();
5979 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5980 claws_unlink(tmp_enc_file);
5981 } else {
5982 g_warning("can't open file '%s'", tmp_enc_file);
5984 } else {
5985 g_warning("couldn't get tempfile");
5988 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5989 debug_print("Couldn't encrypt mime structure: %s.\n",
5990 privacy_get_error());
5991 if (tmp_enc_file)
5992 g_free(tmp_enc_file);
5993 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5997 if (tmp_enc_file)
5998 g_free(tmp_enc_file);
6000 procmime_write_mimeinfo(mimemsg, fp);
6002 procmime_mimeinfo_free_all(&mimemsg);
6004 return 0;
6007 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
6009 GtkTextBuffer *buffer;
6010 GtkTextIter start, end;
6011 FILE *fp;
6012 size_t len;
6013 gchar *chars, *tmp;
6015 if ((fp = claws_fopen(file, "wb")) == NULL) {
6016 FILE_OP_ERROR(file, "claws_fopen");
6017 return -1;
6020 /* chmod for security */
6021 if (change_file_mode_rw(fp, file) < 0) {
6022 FILE_OP_ERROR(file, "chmod");
6023 g_warning("can't change file mode");
6026 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
6027 gtk_text_buffer_get_start_iter(buffer, &start);
6028 gtk_text_buffer_get_end_iter(buffer, &end);
6029 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
6031 chars = conv_codeset_strdup
6032 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6034 g_free(tmp);
6035 if (!chars) {
6036 claws_fclose(fp);
6037 claws_unlink(file);
6038 return -1;
6040 /* write body */
6041 len = strlen(chars);
6042 if (claws_fwrite(chars, sizeof(gchar), len, fp) != len) {
6043 FILE_OP_ERROR(file, "claws_fwrite");
6044 g_free(chars);
6045 claws_fclose(fp);
6046 claws_unlink(file);
6047 return -1;
6050 g_free(chars);
6052 if (claws_safe_fclose(fp) == EOF) {
6053 FILE_OP_ERROR(file, "claws_fclose");
6054 claws_unlink(file);
6055 return -1;
6057 return 0;
6060 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6062 FolderItem *item;
6063 MsgInfo *msginfo = compose->targetinfo;
6065 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6066 if (!msginfo) return -1;
6068 if (!force && MSG_IS_LOCKED(msginfo->flags))
6069 return 0;
6071 item = msginfo->folder;
6072 cm_return_val_if_fail(item != NULL, -1);
6074 if (procmsg_msg_exist(msginfo) &&
6075 (folder_has_parent_of_type(item, F_QUEUE) ||
6076 folder_has_parent_of_type(item, F_DRAFT)
6077 || msginfo == compose->autosaved_draft)) {
6078 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6079 g_warning("can't remove the old message");
6080 return -1;
6081 } else {
6082 debug_print("removed reedit target %d\n", msginfo->msgnum);
6086 return 0;
6089 static void compose_remove_draft(Compose *compose)
6091 FolderItem *drafts;
6092 MsgInfo *msginfo = compose->targetinfo;
6093 drafts = account_get_special_folder(compose->account, F_DRAFT);
6095 if (procmsg_msg_exist(msginfo)) {
6096 folder_item_remove_msg(drafts, msginfo->msgnum);
6101 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6102 gboolean remove_reedit_target)
6104 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6107 static gboolean compose_warn_encryption(Compose *compose)
6109 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6110 AlertValue val = G_ALERTALTERNATE;
6112 if (warning == NULL)
6113 return TRUE;
6115 val = alertpanel_full(_("Encryption warning"), warning,
6116 NULL, _("_Cancel"), NULL, _("C_ontinue"), NULL, NULL,
6117 ALERTFOCUS_SECOND, TRUE, NULL, ALERT_WARNING);
6118 if (val & G_ALERTDISABLE) {
6119 val &= ~G_ALERTDISABLE;
6120 if (val == G_ALERTALTERNATE)
6121 privacy_inhibit_encrypt_warning(compose->privacy_system,
6122 TRUE);
6125 if (val == G_ALERTALTERNATE) {
6126 return TRUE;
6127 } else {
6128 return FALSE;
6132 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6133 gchar **msgpath, gboolean perform_checks,
6134 gboolean remove_reedit_target)
6136 FolderItem *queue;
6137 gchar *tmp;
6138 FILE *fp;
6139 GSList *cur;
6140 gint num;
6141 PrefsAccount *mailac = NULL, *newsac = NULL;
6142 gboolean err = FALSE;
6144 debug_print("queueing message...\n");
6145 cm_return_val_if_fail(compose->account != NULL, -1);
6147 if (compose_check_entries(compose, perform_checks) == FALSE) {
6148 if (compose->batch) {
6149 gtk_widget_show_all(compose->window);
6151 return COMPOSE_QUEUE_ERROR_NO_MSG;
6154 if (!compose->to_list && !compose->newsgroup_list) {
6155 g_warning("can't get recipient list");
6156 return COMPOSE_QUEUE_ERROR_NO_MSG;
6159 if (compose->to_list) {
6160 mailac = compose->account;
6161 if (!mailac && cur_account && cur_account->protocol != A_NNTP)
6162 mailac = cur_account;
6163 else if (!mailac && !(mailac = compose_current_mail_account())) {
6164 alertpanel_error(_("No account for sending mails available!"));
6165 return COMPOSE_QUEUE_ERROR_NO_MSG;
6169 if (compose->newsgroup_list) {
6170 if (compose->account->protocol == A_NNTP)
6171 newsac = compose->account;
6172 else {
6173 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6174 return COMPOSE_QUEUE_ERROR_NO_MSG;
6178 /* write queue header */
6179 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6180 G_DIR_SEPARATOR, compose, (guint) rand());
6181 debug_print("queuing to %s\n", tmp);
6182 if ((fp = claws_fopen(tmp, "w+b")) == NULL) {
6183 FILE_OP_ERROR(tmp, "claws_fopen");
6184 g_free(tmp);
6185 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6188 if (change_file_mode_rw(fp, tmp) < 0) {
6189 FILE_OP_ERROR(tmp, "chmod");
6190 g_warning("can't change file mode");
6193 /* queueing variables */
6194 err |= (fprintf(fp, "AF:\n") < 0);
6195 err |= (fprintf(fp, "NF:0\n") < 0);
6196 err |= (fprintf(fp, "PS:10\n") < 0);
6197 err |= (fprintf(fp, "SRH:1\n") < 0);
6198 err |= (fprintf(fp, "SFN:\n") < 0);
6199 err |= (fprintf(fp, "DSR:\n") < 0);
6200 if (compose->msgid)
6201 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6202 else
6203 err |= (fprintf(fp, "MID:\n") < 0);
6204 err |= (fprintf(fp, "CFG:\n") < 0);
6205 err |= (fprintf(fp, "PT:0\n") < 0);
6206 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6207 err |= (fprintf(fp, "RQ:\n") < 0);
6208 if (mailac)
6209 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6210 else
6211 err |= (fprintf(fp, "SSV:\n") < 0);
6212 if (newsac)
6213 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6214 else
6215 err |= (fprintf(fp, "NSV:\n") < 0);
6216 err |= (fprintf(fp, "SSH:\n") < 0);
6217 /* write recipient list */
6218 if (compose->to_list) {
6219 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6220 for (cur = compose->to_list->next; cur != NULL;
6221 cur = cur->next)
6222 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6223 err |= (fprintf(fp, "\n") < 0);
6225 /* write newsgroup list */
6226 if (compose->newsgroup_list) {
6227 err |= (fprintf(fp, "NG:") < 0);
6228 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6229 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6230 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6231 err |= (fprintf(fp, "\n") < 0);
6233 /* account IDs */
6234 if (mailac)
6235 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6236 if (newsac)
6237 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6240 if (compose->privacy_system != NULL) {
6241 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6242 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6243 if (compose->use_encryption) {
6244 if (!compose_warn_encryption(compose)) {
6245 claws_fclose(fp);
6246 claws_unlink(tmp);
6247 g_free(tmp);
6248 return COMPOSE_QUEUE_ERROR_NO_MSG;
6250 if (mailac && mailac->encrypt_to_self) {
6251 GSList *tmp_list = g_slist_copy(compose->to_list);
6252 tmp_list = g_slist_append(tmp_list, compose->account->address);
6253 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6254 g_slist_free(tmp_list);
6255 } else {
6256 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6258 if (compose->encdata != NULL) {
6259 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6260 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6261 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6262 compose->encdata) < 0);
6263 } /* else we finally dont want to encrypt */
6264 } else {
6265 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6266 /* and if encdata was null, it means there's been a problem in
6267 * key selection */
6268 if (err == TRUE)
6269 g_warning("failed to write queue message");
6270 claws_fclose(fp);
6271 claws_unlink(tmp);
6272 g_free(tmp);
6273 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6278 /* Save copy folder */
6279 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6280 gchar *savefolderid;
6282 savefolderid = compose_get_save_to(compose);
6283 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6284 g_free(savefolderid);
6286 /* Save copy folder */
6287 if (compose->return_receipt) {
6288 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6290 /* Message-ID of message replying to */
6291 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6292 gchar *folderid = NULL;
6294 if (compose->replyinfo->folder)
6295 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6296 if (folderid == NULL)
6297 folderid = g_strdup("NULL");
6299 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6300 g_free(folderid);
6302 /* Message-ID of message forwarding to */
6303 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6304 gchar *folderid = NULL;
6306 if (compose->fwdinfo->folder)
6307 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6308 if (folderid == NULL)
6309 folderid = g_strdup("NULL");
6311 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6312 g_free(folderid);
6315 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6316 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6318 /* end of headers */
6319 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6321 if (compose->redirect_filename != NULL) {
6322 if (compose_redirect_write_to_file(compose, fp) < 0) {
6323 claws_fclose(fp);
6324 claws_unlink(tmp);
6325 g_free(tmp);
6326 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6328 } else {
6329 gint result = 0;
6330 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6331 claws_fclose(fp);
6332 claws_unlink(tmp);
6333 g_free(tmp);
6334 return result;
6337 if (err == TRUE) {
6338 g_warning("failed to write queue message");
6339 claws_fclose(fp);
6340 claws_unlink(tmp);
6341 g_free(tmp);
6342 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6344 if (claws_safe_fclose(fp) == EOF) {
6345 FILE_OP_ERROR(tmp, "claws_fclose");
6346 claws_unlink(tmp);
6347 g_free(tmp);
6348 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6351 if (item && *item) {
6352 queue = *item;
6353 } else {
6354 queue = account_get_special_folder(compose->account, F_QUEUE);
6356 if (!queue) {
6357 g_warning("can't find queue folder");
6358 claws_unlink(tmp);
6359 g_free(tmp);
6360 return COMPOSE_QUEUE_ERROR_NO_MSG;
6362 folder_item_scan(queue);
6363 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6364 g_warning("can't queue the message");
6365 claws_unlink(tmp);
6366 g_free(tmp);
6367 return COMPOSE_QUEUE_ERROR_NO_MSG;
6370 if (msgpath == NULL) {
6371 claws_unlink(tmp);
6372 g_free(tmp);
6373 } else
6374 *msgpath = tmp;
6376 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6377 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6378 if (mi) {
6379 procmsg_msginfo_change_flags(mi,
6380 compose->targetinfo->flags.perm_flags,
6381 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6382 0, 0);
6384 g_slist_free(mi->tags);
6385 mi->tags = g_slist_copy(compose->targetinfo->tags);
6386 procmsg_msginfo_free(&mi);
6390 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6391 compose_remove_reedit_target(compose, FALSE);
6394 if ((msgnum != NULL) && (item != NULL)) {
6395 *msgnum = num;
6396 *item = queue;
6399 return COMPOSE_QUEUE_SUCCESS;
6402 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6404 AttachInfo *ainfo;
6405 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6406 MimeInfo *mimepart;
6407 #ifdef G_OS_WIN32
6408 GFile *f;
6409 GFileInfo *fi;
6410 GError *error = NULL;
6411 #else
6412 GStatBuf statbuf;
6413 #endif
6414 goffset size;
6415 gchar *type, *subtype;
6416 GtkTreeModel *model;
6417 GtkTreeIter iter;
6419 model = gtk_tree_view_get_model(tree_view);
6421 if (!gtk_tree_model_get_iter_first(model, &iter))
6422 return 0;
6423 do {
6424 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6426 if (!is_file_exist(ainfo->file)) {
6427 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6428 AlertValue val = alertpanel_full(_("Warning"), msg,
6429 NULL, _("Cancel sending"),
6430 NULL, _("Ignore attachment"), NULL, NULL,
6431 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6432 g_free(msg);
6433 if (val == G_ALERTDEFAULT) {
6434 return -1;
6436 continue;
6438 #ifdef G_OS_WIN32
6439 f = g_file_new_for_path(ainfo->file);
6440 fi = g_file_query_info(f, "standard::size",
6441 G_FILE_QUERY_INFO_NONE, NULL, &error);
6442 if (error != NULL) {
6443 g_warning(error->message);
6444 g_error_free(error);
6445 g_object_unref(f);
6446 return -1;
6448 size = g_file_info_get_size(fi);
6449 g_object_unref(fi);
6450 g_object_unref(f);
6451 #else
6452 if (g_stat(ainfo->file, &statbuf) < 0)
6453 return -1;
6454 size = statbuf.st_size;
6455 #endif
6457 mimepart = procmime_mimeinfo_new();
6458 mimepart->content = MIMECONTENT_FILE;
6459 mimepart->data.filename = g_strdup(ainfo->file);
6460 mimepart->tmp = FALSE; /* or we destroy our attachment */
6461 mimepart->offset = 0;
6462 mimepart->length = size;
6464 type = g_strdup(ainfo->content_type);
6466 if (!strchr(type, '/')) {
6467 g_free(type);
6468 type = g_strdup("application/octet-stream");
6471 subtype = strchr(type, '/') + 1;
6472 *(subtype - 1) = '\0';
6473 mimepart->type = procmime_get_media_type(type);
6474 mimepart->subtype = g_strdup(subtype);
6475 g_free(type);
6477 if (mimepart->type == MIMETYPE_MESSAGE &&
6478 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6479 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6480 } else if (mimepart->type == MIMETYPE_TEXT) {
6481 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6482 /* Text parts with no name come from multipart/alternative
6483 * forwards. Make sure the recipient won't look at the
6484 * original HTML part by mistake. */
6485 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6486 ainfo->name = g_strdup_printf(_("Original %s part"),
6487 mimepart->subtype);
6489 if (ainfo->charset)
6490 g_hash_table_insert(mimepart->typeparameters,
6491 g_strdup("charset"), g_strdup(ainfo->charset));
6493 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6494 if (mimepart->type == MIMETYPE_APPLICATION &&
6495 !g_strcmp0(mimepart->subtype, "octet-stream"))
6496 g_hash_table_insert(mimepart->typeparameters,
6497 g_strdup("name"), g_strdup(ainfo->name));
6498 g_hash_table_insert(mimepart->dispositionparameters,
6499 g_strdup("filename"), g_strdup(ainfo->name));
6500 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6503 if (mimepart->type == MIMETYPE_MESSAGE
6504 || mimepart->type == MIMETYPE_MULTIPART)
6505 ainfo->encoding = ENC_BINARY;
6506 else if (compose->use_signing || compose->fwdinfo != NULL) {
6507 if (ainfo->encoding == ENC_7BIT)
6508 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6509 else if (ainfo->encoding == ENC_8BIT)
6510 ainfo->encoding = ENC_BASE64;
6513 procmime_encode_content(mimepart, ainfo->encoding);
6515 g_node_append(parent->node, mimepart->node);
6516 } while (gtk_tree_model_iter_next(model, &iter));
6518 return 0;
6521 static gchar *compose_quote_list_of_addresses(gchar *str)
6523 GSList *list = NULL, *item = NULL;
6524 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6526 list = address_list_append_with_comments(list, str);
6527 for (item = list; item != NULL; item = item->next) {
6528 gchar *spec = item->data;
6529 gchar *endofname = strstr(spec, " <");
6530 if (endofname != NULL) {
6531 gchar * qqname;
6532 *endofname = '\0';
6533 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6534 qqname = escape_internal_quotes(qname, '"');
6535 *endofname = ' ';
6536 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6537 gchar *addr = g_strdup(endofname);
6538 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6539 faddr = g_strconcat(name, addr, NULL);
6540 g_free(name);
6541 g_free(addr);
6542 debug_print("new auto-quoted address: '%s'\n", faddr);
6545 if (result == NULL)
6546 result = g_strdup((faddr != NULL)? faddr: spec);
6547 else {
6548 gchar *tmp = g_strconcat(result,
6549 ", ",
6550 (faddr != NULL)? faddr: spec,
6551 NULL);
6552 g_free(result);
6553 result = tmp;
6555 if (faddr != NULL) {
6556 g_free(faddr);
6557 faddr = NULL;
6560 slist_free_strings_full(list);
6562 return result;
6565 #define IS_IN_CUSTOM_HEADER(header) \
6566 (compose->account->add_customhdr && \
6567 custom_header_find(compose->account->customhdr_list, header) != NULL)
6569 static const gchar *compose_untranslated_header_name(gchar *header_name)
6571 /* return the untranslated header name, if header_name is a known
6572 header name, in either its translated or untranslated form, with
6573 or without trailing colon. otherwise, returns header_name. */
6574 gchar *translated_header_name;
6575 gchar *translated_header_name_wcolon;
6576 const gchar *untranslated_header_name;
6577 const gchar *untranslated_header_name_wcolon;
6578 gint i;
6580 cm_return_val_if_fail(header_name != NULL, NULL);
6582 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6583 untranslated_header_name = HEADERS[i].header_name;
6584 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6586 translated_header_name = gettext(untranslated_header_name);
6587 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6589 if (!strcmp(header_name, untranslated_header_name) ||
6590 !strcmp(header_name, translated_header_name)) {
6591 return untranslated_header_name;
6592 } else {
6593 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6594 !strcmp(header_name, translated_header_name_wcolon)) {
6595 return untranslated_header_name_wcolon;
6599 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6600 return header_name;
6603 static void compose_add_headerfield_from_headerlist(Compose *compose,
6604 GString *header,
6605 const gchar *fieldname,
6606 const gchar *seperator)
6608 gchar *str, *fieldname_w_colon;
6609 gboolean add_field = FALSE;
6610 GSList *list;
6611 ComposeHeaderEntry *headerentry;
6612 const gchar *headerentryname;
6613 const gchar *trans_fieldname;
6614 GString *fieldstr;
6616 if (IS_IN_CUSTOM_HEADER(fieldname))
6617 return;
6619 debug_print("Adding %s-fields\n", fieldname);
6621 fieldstr = g_string_sized_new(64);
6623 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6624 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6626 for (list = compose->header_list; list; list = list->next) {
6627 headerentry = ((ComposeHeaderEntry *)list->data);
6628 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6630 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6631 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6632 g_strstrip(ustr);
6633 str = compose_quote_list_of_addresses(ustr);
6634 g_free(ustr);
6635 if (str != NULL && str[0] != '\0') {
6636 if (add_field)
6637 g_string_append(fieldstr, seperator);
6638 g_string_append(fieldstr, str);
6639 add_field = TRUE;
6641 g_free(str);
6644 if (add_field) {
6645 gchar *buf;
6647 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6648 compose_convert_header
6649 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6650 strlen(fieldname) + 2, TRUE);
6651 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6652 g_free(buf);
6655 g_free(fieldname_w_colon);
6656 g_string_free(fieldstr, TRUE);
6658 return;
6661 static gchar *compose_get_manual_headers_info(Compose *compose)
6663 GString *sh_header = g_string_new(" ");
6664 GSList *list;
6665 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6667 for (list = compose->header_list; list; list = list->next) {
6668 ComposeHeaderEntry *headerentry;
6669 gchar *tmp;
6670 gchar *headername;
6671 gchar *headername_wcolon;
6672 const gchar *headername_trans;
6673 gchar **string;
6674 gboolean standard_header = FALSE;
6676 headerentry = ((ComposeHeaderEntry *)list->data);
6678 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6679 g_strstrip(tmp);
6680 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6681 g_free(tmp);
6682 continue;
6685 if (!strstr(tmp, ":")) {
6686 headername_wcolon = g_strconcat(tmp, ":", NULL);
6687 headername = g_strdup(tmp);
6688 } else {
6689 headername_wcolon = g_strdup(tmp);
6690 headername = g_strdup(strtok(tmp, ":"));
6692 g_free(tmp);
6694 string = std_headers;
6695 while (*string != NULL) {
6696 headername_trans = prefs_common_translated_header_name(*string);
6697 if (!strcmp(headername_trans, headername_wcolon))
6698 standard_header = TRUE;
6699 string++;
6701 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6702 g_string_append_printf(sh_header, "%s ", headername);
6703 g_free(headername);
6704 g_free(headername_wcolon);
6706 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6707 return g_string_free(sh_header, FALSE);
6710 static gchar *compose_get_header(Compose *compose)
6712 gchar date[RFC822_DATE_BUFFSIZE];
6713 gchar buf[BUFFSIZE];
6714 const gchar *entry_str;
6715 gchar *str;
6716 gchar *name;
6717 GSList *list;
6718 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6719 GString *header;
6720 gchar *from_name = NULL, *from_address = NULL;
6721 gchar *tmp;
6723 cm_return_val_if_fail(compose->account != NULL, NULL);
6724 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6726 header = g_string_sized_new(64);
6728 /* Date */
6729 if (prefs_common.hide_timezone)
6730 get_rfc822_date_hide_tz(date, sizeof(date));
6731 else
6732 get_rfc822_date(date, sizeof(date));
6733 g_string_append_printf(header, "Date: %s\n", date);
6735 /* From */
6737 if (compose->account->name && *compose->account->name) {
6738 gchar *buf;
6739 QUOTE_IF_REQUIRED(buf, compose->account->name);
6740 tmp = g_strdup_printf("%s <%s>",
6741 buf, compose->account->address);
6742 } else {
6743 tmp = g_strdup_printf("%s",
6744 compose->account->address);
6746 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6747 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6748 /* use default */
6749 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6750 from_address = g_strdup(compose->account->address);
6751 } else {
6752 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6753 /* extract name and address */
6754 if (strstr(spec, " <") && strstr(spec, ">")) {
6755 from_address = g_strdup(strrchr(spec, '<')+1);
6756 *(strrchr(from_address, '>')) = '\0';
6757 from_name = g_strdup(spec);
6758 *(strrchr(from_name, '<')) = '\0';
6759 } else {
6760 from_name = NULL;
6761 from_address = g_strdup(spec);
6763 g_free(spec);
6765 g_free(tmp);
6768 if (from_name && *from_name) {
6769 gchar *qname;
6770 compose_convert_header
6771 (compose, buf, sizeof(buf), from_name,
6772 strlen("From: "), TRUE);
6773 QUOTE_IF_REQUIRED(name, buf);
6774 qname = escape_internal_quotes(name, '"');
6776 g_string_append_printf(header, "From: %s <%s>\n",
6777 qname, from_address);
6778 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6779 compose->return_receipt) {
6780 compose_convert_header(compose, buf, sizeof(buf), from_name,
6781 strlen("Disposition-Notification-To: "),
6782 TRUE);
6783 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6785 if (qname != name)
6786 g_free(qname);
6787 } else {
6788 g_string_append_printf(header, "From: %s\n", from_address);
6789 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6790 compose->return_receipt)
6791 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6794 g_free(from_name);
6795 g_free(from_address);
6797 /* To */
6798 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6800 /* Newsgroups */
6801 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6803 /* Cc */
6804 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6806 /* Bcc */
6808 * If this account is a NNTP account remove Bcc header from
6809 * message body since it otherwise will be publicly shown
6811 if (compose->account->protocol != A_NNTP)
6812 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6814 /* Subject */
6815 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6817 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6818 g_strstrip(str);
6819 if (*str != '\0') {
6820 compose_convert_header(compose, buf, sizeof(buf), str,
6821 strlen("Subject: "), FALSE);
6822 g_string_append_printf(header, "Subject: %s\n", buf);
6825 g_free(str);
6827 /* Message-ID */
6828 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6829 g_string_append_printf(header, "Message-ID: <%s>\n",
6830 compose->msgid);
6833 if (compose->remove_references == FALSE) {
6834 /* In-Reply-To */
6835 if (compose->inreplyto && compose->to_list)
6836 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6838 /* References */
6839 if (compose->references)
6840 g_string_append_printf(header, "References: %s\n", compose->references);
6843 /* Followup-To */
6844 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6846 /* Reply-To */
6847 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6849 /* Organization */
6850 if (compose->account->organization &&
6851 strlen(compose->account->organization) &&
6852 !IS_IN_CUSTOM_HEADER("Organization")) {
6853 compose_convert_header(compose, buf, sizeof(buf),
6854 compose->account->organization,
6855 strlen("Organization: "), FALSE);
6856 g_string_append_printf(header, "Organization: %s\n", buf);
6859 /* Program version and system info */
6860 if (compose->account->gen_xmailer &&
6861 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6862 !compose->newsgroup_list) {
6863 g_string_append_printf(header, "X-Mailer: %s (GTK %d.%d.%d; %s)\n",
6864 prog_version,
6865 gtk_major_version, gtk_minor_version, gtk_micro_version,
6866 TARGET_ALIAS);
6868 if (compose->account->gen_xmailer &&
6869 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6870 g_string_append_printf(header, "X-Newsreader: %s (GTK %d.%d.%d; %s)\n",
6871 prog_version,
6872 gtk_major_version, gtk_minor_version, gtk_micro_version,
6873 TARGET_ALIAS);
6876 /* custom headers */
6877 if (compose->account->add_customhdr) {
6878 GSList *cur;
6880 for (cur = compose->account->customhdr_list; cur != NULL;
6881 cur = cur->next) {
6882 CustomHeader *chdr = (CustomHeader *)cur->data;
6884 if (custom_header_is_allowed(chdr->name)
6885 && chdr->value != NULL
6886 && *(chdr->value) != '\0') {
6887 compose_convert_header
6888 (compose, buf, sizeof(buf),
6889 chdr->value,
6890 strlen(chdr->name) + 2, FALSE);
6891 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6896 /* Automatic Faces and X-Faces */
6897 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6898 g_string_append_printf(header, "X-Face: %s\n", buf);
6900 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6901 g_string_append_printf(header, "X-Face: %s\n", buf);
6903 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6904 g_string_append_printf(header, "Face: %s\n", buf);
6906 else if (get_default_face (buf, sizeof(buf)) == 0) {
6907 g_string_append_printf(header, "Face: %s\n", buf);
6910 /* PRIORITY */
6911 switch (compose->priority) {
6912 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6913 "X-Priority: 1 (Highest)\n");
6914 break;
6915 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6916 "X-Priority: 2 (High)\n");
6917 break;
6918 case PRIORITY_NORMAL: break;
6919 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6920 "X-Priority: 4 (Low)\n");
6921 break;
6922 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6923 "X-Priority: 5 (Lowest)\n");
6924 break;
6925 default: debug_print("compose: priority unknown : %d\n",
6926 compose->priority);
6929 /* get special headers */
6930 for (list = compose->header_list; list; list = list->next) {
6931 ComposeHeaderEntry *headerentry;
6932 gchar *tmp;
6933 gchar *headername;
6934 gchar *headername_wcolon;
6935 const gchar *headername_trans;
6936 gchar *headervalue;
6937 gchar **string;
6938 gboolean standard_header = FALSE;
6940 headerentry = ((ComposeHeaderEntry *)list->data);
6942 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6943 g_strstrip(tmp);
6944 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6945 g_free(tmp);
6946 continue;
6949 if (!strstr(tmp, ":")) {
6950 headername_wcolon = g_strconcat(tmp, ":", NULL);
6951 headername = g_strdup(tmp);
6952 } else {
6953 headername_wcolon = g_strdup(tmp);
6954 headername = g_strdup(strtok(tmp, ":"));
6956 g_free(tmp);
6958 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6959 Xstrdup_a(headervalue, entry_str, {
6960 g_free(headername);
6961 g_free(headername_wcolon);
6962 g_string_free(header, TRUE);
6963 return NULL;
6965 subst_char(headervalue, '\r', ' ');
6966 subst_char(headervalue, '\n', ' ');
6967 g_strstrip(headervalue);
6968 if (*headervalue != '\0') {
6969 string = std_headers;
6970 while (*string != NULL && !standard_header) {
6971 headername_trans = prefs_common_translated_header_name(*string);
6972 /* support mixed translated and untranslated headers */
6973 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6974 standard_header = TRUE;
6975 string++;
6977 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6978 /* store untranslated header name */
6979 g_string_append_printf(header, "%s %s\n",
6980 compose_untranslated_header_name(headername_wcolon), headervalue);
6983 g_free(headername);
6984 g_free(headername_wcolon);
6987 str = header->str;
6988 g_string_free(header, FALSE);
6990 return str;
6993 #undef IS_IN_CUSTOM_HEADER
6995 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6996 gint header_len, gboolean addr_field)
6998 gchar *tmpstr = NULL;
6999 const gchar *out_codeset = NULL;
7001 cm_return_if_fail(src != NULL);
7002 cm_return_if_fail(dest != NULL);
7004 if (len < 1) return;
7006 tmpstr = g_strdup(src);
7008 subst_char(tmpstr, '\n', ' ');
7009 subst_char(tmpstr, '\r', ' ');
7010 g_strchomp(tmpstr);
7012 if (!g_utf8_validate(tmpstr, -1, NULL)) {
7013 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
7014 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
7015 g_free(tmpstr);
7016 tmpstr = mybuf;
7019 codeconv_set_strict(TRUE);
7020 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7021 conv_get_charset_str(compose->out_encoding));
7022 codeconv_set_strict(FALSE);
7024 if (!dest || *dest == '\0') {
7025 gchar *test_conv_global_out = NULL;
7026 gchar *test_conv_reply = NULL;
7028 /* automatic mode. be automatic. */
7029 codeconv_set_strict(TRUE);
7031 out_codeset = conv_get_outgoing_charset_str();
7032 if (out_codeset) {
7033 debug_print("trying to convert to %s\n", out_codeset);
7034 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7037 if (!test_conv_global_out && compose->orig_charset
7038 && strcmp(compose->orig_charset, CS_US_ASCII)) {
7039 out_codeset = compose->orig_charset;
7040 debug_print("failure; trying to convert to %s\n", out_codeset);
7041 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7044 if (!test_conv_global_out && !test_conv_reply) {
7045 /* we're lost */
7046 out_codeset = CS_INTERNAL;
7047 debug_print("finally using %s\n", out_codeset);
7049 g_free(test_conv_global_out);
7050 g_free(test_conv_reply);
7051 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7052 out_codeset);
7053 codeconv_set_strict(FALSE);
7055 g_free(tmpstr);
7058 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7060 gchar *address;
7062 cm_return_if_fail(user_data != NULL);
7064 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7065 g_strstrip(address);
7066 if (*address != '\0') {
7067 gchar *name = procheader_get_fromname(address);
7068 extract_address(address);
7069 #ifndef USE_ALT_ADDRBOOK
7070 addressbook_add_contact(name, address, NULL, NULL);
7071 #else
7072 debug_print("%s: %s\n", name, address);
7073 if (addressadd_selection(name, address, NULL, NULL)) {
7074 debug_print( "addressbook_add_contact - added\n" );
7076 #endif
7078 g_free(address);
7081 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7083 GtkWidget *menuitem;
7084 gchar *address;
7086 cm_return_if_fail(menu != NULL);
7087 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7089 menuitem = gtk_separator_menu_item_new();
7090 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7091 gtk_widget_show(menuitem);
7093 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7094 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7096 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7097 g_strstrip(address);
7098 if (*address == '\0') {
7099 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7102 g_signal_connect(G_OBJECT(menuitem), "activate",
7103 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7104 gtk_widget_show(menuitem);
7107 void compose_add_extra_header(gchar *header, GtkListStore *model)
7109 GtkTreeIter iter;
7110 if (strcmp(header, "")) {
7111 COMBOBOX_ADD(model, header, COMPOSE_TO);
7115 void compose_add_extra_header_entries(GtkListStore *model)
7117 FILE *exh;
7118 gchar *exhrc;
7119 gchar buf[BUFFSIZE];
7120 gint lastc;
7122 if (extra_headers == NULL) {
7123 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7124 if ((exh = claws_fopen(exhrc, "rb")) == NULL) {
7125 debug_print("extra headers file not found\n");
7126 goto extra_headers_done;
7128 while (claws_fgets(buf, BUFFSIZE, exh) != NULL) {
7129 lastc = strlen(buf) - 1; /* remove trailing control chars */
7130 while (lastc >= 0 && buf[lastc] != ':')
7131 buf[lastc--] = '\0';
7132 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7133 buf[lastc] = '\0'; /* remove trailing : for comparison */
7134 if (custom_header_is_allowed(buf)) {
7135 buf[lastc] = ':';
7136 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7138 else
7139 g_message("disallowed extra header line: %s\n", buf);
7141 else {
7142 if (buf[0] != '#')
7143 g_message("invalid extra header line: %s\n", buf);
7146 claws_fclose(exh);
7147 extra_headers_done:
7148 g_free(exhrc);
7149 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7150 extra_headers = g_slist_reverse(extra_headers);
7152 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7155 #ifdef USE_LDAP
7156 static void _ldap_srv_func(gpointer data, gpointer user_data)
7158 LdapServer *server = (LdapServer *)data;
7159 gboolean *enable = (gboolean *)user_data;
7161 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7162 server->searchFlag = *enable;
7164 #endif
7166 static void compose_create_header_entry(Compose *compose)
7168 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7170 GtkWidget *combo;
7171 GtkWidget *entry;
7172 GtkWidget *button;
7173 GtkWidget *hbox;
7174 gchar **string;
7175 const gchar *header = NULL;
7176 ComposeHeaderEntry *headerentry;
7177 gboolean standard_header = FALSE;
7178 GtkListStore *model;
7179 GtkTreeIter iter;
7181 headerentry = g_new0(ComposeHeaderEntry, 1);
7183 /* Combo box model */
7184 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7185 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7186 COMPOSE_TO);
7187 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7188 COMPOSE_CC);
7189 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7190 COMPOSE_BCC);
7191 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7192 COMPOSE_NEWSGROUPS);
7193 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7194 COMPOSE_REPLYTO);
7195 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7196 COMPOSE_FOLLOWUPTO);
7197 compose_add_extra_header_entries(model);
7199 /* Combo box */
7200 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7201 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7202 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7203 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7204 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7205 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7206 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7207 G_CALLBACK(compose_grab_focus_cb), compose);
7208 gtk_widget_show(combo);
7210 gtk_grid_attach(GTK_GRID(compose->header_table), combo, 0, compose->header_nextrow,
7211 1, 1);
7212 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7213 const gchar *last_header_entry = gtk_entry_get_text(
7214 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7215 string = headers;
7216 while (*string != NULL) {
7217 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7218 standard_header = TRUE;
7219 string++;
7221 if (standard_header)
7222 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7224 if (!compose->header_last || !standard_header) {
7225 switch(compose->account->protocol) {
7226 case A_NNTP:
7227 header = prefs_common_translated_header_name("Newsgroups:");
7228 break;
7229 default:
7230 header = prefs_common_translated_header_name("To:");
7231 break;
7234 if (header)
7235 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7237 gtk_editable_set_editable(
7238 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7239 prefs_common.type_any_header);
7241 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7242 G_CALLBACK(compose_grab_focus_cb), compose);
7244 /* Entry field with cleanup button */
7245 button = gtk_button_new_from_icon_name("edit-clear", GTK_ICON_SIZE_MENU);
7246 gtk_widget_show(button);
7247 CLAWS_SET_TIP(button,
7248 _("Delete entry contents"));
7249 entry = gtk_entry_new();
7250 gtk_widget_show(entry);
7251 CLAWS_SET_TIP(entry,
7252 _("Use <tab> to autocomplete from addressbook"));
7253 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
7254 gtk_widget_show(hbox);
7255 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7256 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7257 gtk_grid_attach(GTK_GRID(compose->header_table), hbox, 1, compose->header_nextrow,
7258 1, 1);
7259 gtk_widget_set_hexpand(hbox, TRUE);
7260 gtk_widget_set_halign(hbox, GTK_ALIGN_FILL);
7262 g_signal_connect(G_OBJECT(entry), "key-press-event",
7263 G_CALLBACK(compose_headerentry_key_press_event_cb),
7264 headerentry);
7265 g_signal_connect(G_OBJECT(entry), "changed",
7266 G_CALLBACK(compose_headerentry_changed_cb),
7267 headerentry);
7268 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7269 G_CALLBACK(compose_grab_focus_cb), compose);
7271 g_signal_connect(G_OBJECT(button), "clicked",
7272 G_CALLBACK(compose_headerentry_button_clicked_cb),
7273 headerentry);
7275 /* email dnd */
7276 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7277 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7278 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7279 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7280 G_CALLBACK(compose_header_drag_received_cb),
7281 entry);
7282 g_signal_connect(G_OBJECT(entry), "drag-drop",
7283 G_CALLBACK(compose_drag_drop),
7284 compose);
7285 g_signal_connect(G_OBJECT(entry), "populate-popup",
7286 G_CALLBACK(compose_entry_popup_extend),
7287 NULL);
7289 #ifdef USE_LDAP
7290 #ifndef PASSWORD_CRYPTO_OLD
7291 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7292 if (pwd_servers != NULL && primary_passphrase() == NULL) {
7293 gboolean enable = FALSE;
7294 debug_print("Primary passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7295 /* Temporarily disable password-protected LDAP servers,
7296 * because user did not provide a primary passphrase.
7297 * We can safely enable searchFlag on all servers in this list
7298 * later, since addrindex_get_password_protected_ldap_servers()
7299 * includes servers which have it enabled initially. */
7300 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7301 compose->passworded_ldap_servers = pwd_servers;
7303 #endif /* PASSWORD_CRYPTO_OLD */
7304 #endif /* USE_LDAP */
7306 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7308 headerentry->compose = compose;
7309 headerentry->combo = combo;
7310 headerentry->entry = entry;
7311 headerentry->button = button;
7312 headerentry->hbox = hbox;
7313 headerentry->headernum = compose->header_nextrow;
7314 headerentry->type = PREF_NONE;
7316 compose->header_nextrow++;
7317 compose->header_last = headerentry;
7318 compose->header_list =
7319 g_slist_append(compose->header_list,
7320 headerentry);
7323 static void compose_add_header_entry(Compose *compose, const gchar *header,
7324 gchar *text, ComposePrefType pref_type)
7326 ComposeHeaderEntry *last_header = compose->header_last;
7327 gchar *tmp = g_strdup(text), *email;
7328 gboolean replyto_hdr;
7330 replyto_hdr = (!strcasecmp(header,
7331 prefs_common_translated_header_name("Reply-To:")) ||
7332 !strcasecmp(header,
7333 prefs_common_translated_header_name("Followup-To:")) ||
7334 !strcasecmp(header,
7335 prefs_common_translated_header_name("In-Reply-To:")));
7337 extract_address(tmp);
7338 email = g_utf8_strdown(tmp, -1);
7340 if (replyto_hdr == FALSE &&
7341 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7343 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7344 header, text, (gint) pref_type);
7345 g_free(email);
7346 g_free(tmp);
7347 return;
7350 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7351 gtk_entry_set_text(GTK_ENTRY(
7352 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7353 else
7354 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7355 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7356 last_header->type = pref_type;
7358 if (replyto_hdr == FALSE)
7359 g_hash_table_insert(compose->email_hashtable, email,
7360 GUINT_TO_POINTER(1));
7361 else
7362 g_free(email);
7364 g_free(tmp);
7367 static void compose_destroy_headerentry(Compose *compose,
7368 ComposeHeaderEntry *headerentry)
7370 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7371 gchar *email;
7373 extract_address(text);
7374 email = g_utf8_strdown(text, -1);
7375 g_hash_table_remove(compose->email_hashtable, email);
7376 g_free(text);
7377 g_free(email);
7379 gtk_widget_destroy(headerentry->combo);
7380 gtk_widget_destroy(headerentry->entry);
7381 gtk_widget_destroy(headerentry->button);
7382 gtk_widget_destroy(headerentry->hbox);
7383 g_free(headerentry);
7386 static void compose_remove_header_entries(Compose *compose)
7388 GSList *list;
7389 for (list = compose->header_list; list; list = list->next)
7390 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7392 compose->header_last = NULL;
7393 g_slist_free(compose->header_list);
7394 compose->header_list = NULL;
7395 compose->header_nextrow = 1;
7396 compose_create_header_entry(compose);
7399 static GtkWidget *compose_create_header(Compose *compose)
7401 GtkWidget *from_optmenu_hbox;
7402 GtkWidget *header_table_main;
7403 GtkWidget *header_scrolledwin;
7404 GtkWidget *header_table;
7406 /* parent with account selection and from header */
7407 header_table_main = gtk_grid_new();
7408 gtk_widget_show(header_table_main);
7409 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7411 from_optmenu_hbox = compose_account_option_menu_create(compose);
7412 gtk_grid_attach(GTK_GRID(header_table_main),from_optmenu_hbox, 0, 0, 1, 1);
7413 gtk_widget_set_hexpand(from_optmenu_hbox, TRUE);
7414 gtk_widget_set_halign(from_optmenu_hbox, GTK_ALIGN_FILL);
7416 /* child with header labels and entries */
7417 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7418 gtk_widget_show(header_scrolledwin);
7419 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7421 header_table = gtk_grid_new();
7422 gtk_widget_show(header_table);
7423 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7424 gtk_container_add(GTK_CONTAINER(header_scrolledwin), header_table);
7425 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7426 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7427 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7429 gtk_grid_attach(GTK_GRID(header_table_main), header_scrolledwin, 0, 1, 1, 1);
7430 gtk_widget_set_vexpand(header_scrolledwin, TRUE);
7431 gtk_widget_set_valign(header_scrolledwin, GTK_ALIGN_FILL);
7433 compose->header_table = header_table;
7434 compose->header_list = NULL;
7435 compose->header_nextrow = 0;
7437 compose_create_header_entry(compose);
7439 compose->table = NULL;
7441 return header_table_main;
7444 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7446 Compose *compose = (Compose *)data;
7447 GdkEventButton event;
7449 event.button = 3;
7450 event.time = gtk_get_current_event_time();
7452 return attach_button_pressed(compose->attach_clist, &event, compose);
7455 static GtkWidget *compose_create_attach(Compose *compose)
7457 GtkWidget *attach_scrwin;
7458 GtkWidget *attach_clist;
7460 GtkListStore *store;
7461 GtkCellRenderer *renderer;
7462 GtkTreeViewColumn *column;
7463 GtkTreeSelection *selection;
7465 /* attachment list */
7466 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7467 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7468 GTK_POLICY_AUTOMATIC,
7469 GTK_POLICY_AUTOMATIC);
7470 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7472 store = gtk_list_store_new(N_ATTACH_COLS,
7473 G_TYPE_STRING,
7474 G_TYPE_STRING,
7475 G_TYPE_STRING,
7476 G_TYPE_STRING,
7477 G_TYPE_POINTER,
7478 G_TYPE_AUTO_POINTER,
7479 -1);
7480 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7481 (GTK_TREE_MODEL(store)));
7482 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7483 g_object_unref(store);
7485 renderer = gtk_cell_renderer_text_new();
7486 column = gtk_tree_view_column_new_with_attributes
7487 (_("Mime type"), renderer, "text",
7488 COL_MIMETYPE, NULL);
7489 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7491 renderer = gtk_cell_renderer_text_new();
7492 column = gtk_tree_view_column_new_with_attributes
7493 (_("Size"), renderer, "text",
7494 COL_SIZE, NULL);
7495 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7497 renderer = gtk_cell_renderer_text_new();
7498 column = gtk_tree_view_column_new_with_attributes
7499 (_("Name"), renderer, "text",
7500 COL_NAME, NULL);
7501 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7503 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7504 prefs_common.use_stripes_everywhere);
7505 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7506 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7508 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7509 G_CALLBACK(attach_selected), compose);
7510 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7511 G_CALLBACK(attach_button_pressed), compose);
7512 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7513 G_CALLBACK(popup_attach_button_pressed), compose);
7514 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7515 G_CALLBACK(attach_key_pressed), compose);
7517 /* drag and drop */
7518 gtk_drag_dest_set(attach_clist,
7519 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7520 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7521 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7522 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7523 G_CALLBACK(compose_attach_drag_received_cb),
7524 compose);
7525 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7526 G_CALLBACK(compose_drag_drop),
7527 compose);
7529 compose->attach_scrwin = attach_scrwin;
7530 compose->attach_clist = attach_clist;
7532 return attach_scrwin;
7535 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7537 static GtkWidget *compose_create_others(Compose *compose)
7539 GtkWidget *table;
7540 GtkWidget *savemsg_checkbtn;
7541 GtkWidget *savemsg_combo;
7542 GtkWidget *savemsg_select;
7544 guint rowcount = 0;
7545 gchar *folderidentifier;
7547 /* Table for settings */
7548 table = gtk_grid_new();
7549 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7550 gtk_widget_show(table);
7551 gtk_grid_set_row_spacing(GTK_GRID(table), VSPACING_NARROW);
7552 rowcount = 0;
7554 /* Save Message to folder */
7555 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7556 gtk_widget_show(savemsg_checkbtn);
7557 gtk_grid_attach(GTK_GRID(table), savemsg_checkbtn, 0, rowcount, 1, 1);
7558 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7559 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7562 savemsg_combo = gtk_combo_box_text_new_with_entry();
7563 compose->savemsg_checkbtn = savemsg_checkbtn;
7564 compose->savemsg_combo = savemsg_combo;
7565 gtk_widget_show(savemsg_combo);
7567 if (prefs_common.compose_save_to_history)
7568 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7569 prefs_common.compose_save_to_history);
7570 gtk_grid_attach(GTK_GRID(table), savemsg_combo, 1, rowcount, 1, 1);
7571 gtk_widget_set_hexpand(savemsg_combo, TRUE);
7572 gtk_widget_set_halign(savemsg_combo, GTK_ALIGN_FILL);
7573 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7574 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7575 G_CALLBACK(compose_grab_focus_cb), compose);
7576 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7577 if (compose->account->set_sent_folder || prefs_common.savemsg)
7578 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), TRUE);
7579 else
7580 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), FALSE);
7581 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), TRUE);
7582 folderidentifier = folder_item_get_identifier(account_get_special_folder
7583 (compose->account, F_OUTBOX));
7584 compose_set_save_to(compose, folderidentifier);
7585 g_free(folderidentifier);
7588 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7589 gtk_widget_show(savemsg_select);
7590 gtk_grid_attach(GTK_GRID(table), savemsg_select, 2, rowcount, 1, 1);
7591 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7592 G_CALLBACK(compose_savemsg_select_cb),
7593 compose);
7595 return table;
7598 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7600 FolderItem *dest;
7601 gchar * path;
7603 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7604 _("Select folder to save message to"));
7605 if (!dest) return;
7607 path = folder_item_get_identifier(dest);
7609 compose_set_save_to(compose, path);
7610 g_free(path);
7613 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7614 GdkAtom clip, GtkTextIter *insert_place);
7617 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7618 Compose *compose)
7620 gint prev_autowrap;
7621 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7622 #if USE_ENCHANT
7623 if (event->button == 3) {
7624 GtkTextIter iter;
7625 GtkTextIter sel_start, sel_end;
7626 gboolean stuff_selected;
7627 gint x, y;
7628 /* move the cursor to allow GtkAspell to check the word
7629 * under the mouse */
7630 if (event->x && event->y) {
7631 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7632 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7633 &x, &y);
7634 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7635 &iter, x, y);
7636 } else {
7637 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7638 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7640 /* get selection */
7641 stuff_selected = gtk_text_buffer_get_selection_bounds(
7642 buffer,
7643 &sel_start, &sel_end);
7645 gtk_text_buffer_place_cursor (buffer, &iter);
7646 /* reselect stuff */
7647 if (stuff_selected
7648 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7649 gtk_text_buffer_select_range(buffer,
7650 &sel_start, &sel_end);
7652 return FALSE; /* pass the event so that the right-click goes through */
7654 #endif
7655 if (event->button == 2) {
7656 GtkTextIter iter;
7657 gint x, y;
7658 BLOCK_WRAP();
7660 /* get the middle-click position to paste at the correct place */
7661 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7662 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7663 &x, &y);
7664 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7665 &iter, x, y);
7667 entry_paste_clipboard(compose, text,
7668 prefs_common.linewrap_pastes,
7669 GDK_SELECTION_PRIMARY, &iter);
7670 UNBLOCK_WRAP();
7671 return TRUE;
7673 return FALSE;
7676 #if USE_ENCHANT
7677 static void compose_spell_menu_changed(void *data)
7679 Compose *compose = (Compose *)data;
7680 GSList *items;
7681 GtkWidget *menuitem;
7682 GtkWidget *parent_item;
7683 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7684 GSList *spell_menu;
7686 if (compose->gtkaspell == NULL)
7687 return;
7689 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7690 "/Menu/Spelling/Options");
7692 /* setting the submenu removes /Spelling/Options from the factory
7693 * so we need to save it */
7695 if (parent_item == NULL) {
7696 parent_item = compose->aspell_options_menu;
7697 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7698 } else
7699 compose->aspell_options_menu = parent_item;
7701 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7703 spell_menu = g_slist_reverse(spell_menu);
7704 for (items = spell_menu;
7705 items; items = items->next) {
7706 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7707 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7708 gtk_widget_show(GTK_WIDGET(menuitem));
7710 g_slist_free(spell_menu);
7712 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7713 gtk_widget_show(parent_item);
7716 static void compose_dict_changed(void *data)
7718 Compose *compose = (Compose *) data;
7720 if(!compose->gtkaspell)
7721 return;
7722 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7723 return;
7725 gtkaspell_highlight_all(compose->gtkaspell);
7726 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7728 #endif
7730 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7732 Compose *compose = (Compose *)data;
7733 GdkEventButton event;
7735 event.button = 3;
7736 event.time = gtk_get_current_event_time();
7737 event.x = 0;
7738 event.y = 0;
7740 return text_clicked(compose->text, &event, compose);
7743 static gboolean compose_force_window_origin = TRUE;
7744 static Compose *compose_create(PrefsAccount *account,
7745 FolderItem *folder,
7746 ComposeMode mode,
7747 gboolean batch)
7749 Compose *compose;
7750 GtkWidget *window;
7751 GtkWidget *vbox;
7752 GtkWidget *menubar;
7753 GtkWidget *handlebox;
7755 GtkWidget *notebook;
7757 GtkWidget *attach_hbox;
7758 GtkWidget *attach_lab1;
7759 GtkWidget *attach_lab2;
7761 GtkWidget *vbox2;
7763 GtkWidget *label;
7764 GtkWidget *subject_hbox;
7765 GtkWidget *subject_frame;
7766 GtkWidget *subject_entry;
7767 GtkWidget *subject;
7768 GtkWidget *paned;
7770 GtkWidget *edit_vbox;
7771 GtkWidget *ruler_hbox;
7772 GtkWidget *ruler;
7773 GtkWidget *scrolledwin;
7774 GtkWidget *text;
7775 GtkTextBuffer *buffer;
7776 GtkClipboard *clipboard;
7778 UndoMain *undostruct;
7780 GtkWidget *popupmenu;
7781 GtkWidget *tmpl_menu;
7782 GtkActionGroup *action_group = NULL;
7784 #if USE_ENCHANT
7785 GtkAspell * gtkaspell = NULL;
7786 #endif
7788 static GdkGeometry geometry;
7789 GdkRectangle workarea = {0};
7791 cm_return_val_if_fail(account != NULL, NULL);
7793 default_header_bgcolor = prefs_common.color[COL_DEFAULT_HEADER_BG],
7794 default_header_color = prefs_common.color[COL_DEFAULT_HEADER],
7796 debug_print("Creating compose window...\n");
7797 compose = g_new0(Compose, 1);
7799 compose->batch = batch;
7800 compose->account = account;
7801 compose->folder = folder;
7803 g_mutex_init(&compose->mutex);
7804 compose->set_cursor_pos = -1;
7806 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7808 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7809 gtk_window_set_default_size(GTK_WINDOW(window), prefs_common.compose_width,
7810 prefs_common.compose_height);
7812 gdk_monitor_get_workarea(gdk_display_get_primary_monitor(gdk_display_get_default()),
7813 &workarea);
7815 if (!geometry.max_width) {
7816 geometry.max_width = workarea.width;
7817 geometry.max_height = workarea.height;
7820 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7821 &geometry, GDK_HINT_MAX_SIZE);
7822 if (!geometry.min_width) {
7823 geometry.min_width = 600;
7824 geometry.min_height = 440;
7826 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7827 &geometry, GDK_HINT_MIN_SIZE);
7829 #ifndef GENERIC_UMPC
7830 if (compose_force_window_origin)
7831 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7832 prefs_common.compose_y);
7833 #endif
7834 g_signal_connect(G_OBJECT(window), "delete_event",
7835 G_CALLBACK(compose_delete_cb), compose);
7836 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7837 gtk_widget_realize(window);
7839 gtkut_widget_set_composer_icon(window);
7841 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
7842 gtk_container_add(GTK_CONTAINER(window), vbox);
7844 compose->ui_manager = gtk_ui_manager_new();
7845 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7846 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7847 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7848 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7849 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7850 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7851 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7852 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7853 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7854 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7860 #ifdef USE_ENCHANT
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7862 #endif
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7867 /* Compose menu */
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7882 /* Edit menu */
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7885 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7898 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7908 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7922 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7925 #if USE_ENCHANT
7926 /* Spelling menu */
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7933 #endif
7935 /* Options menu */
7936 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7937 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7938 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7940 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7942 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7943 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7944 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7946 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7949 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7950 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7951 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7952 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7953 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7954 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7955 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7957 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7958 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7959 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7960 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7961 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7963 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7965 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7966 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7967 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7968 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7969 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7971 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7972 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)
7973 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)
7974 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7976 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7978 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7979 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)
7980 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)
7982 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7984 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7985 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)
7986 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7988 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7989 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)
7990 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7992 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7994 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7995 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)
7996 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7997 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7998 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7999 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
8001 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
8002 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)
8003 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)
8004 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
8005 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
8007 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
8008 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
8009 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
8010 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
8011 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
8012 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
8014 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
8015 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
8016 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)
8018 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
8019 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
8020 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
8021 /* phew. */
8023 /* Tools menu */
8024 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
8025 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
8026 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
8027 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8028 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
8029 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8031 /* Help menu */
8032 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
8034 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
8035 gtk_widget_show_all(menubar);
8037 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
8038 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
8040 handlebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8041 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8043 gtk_widget_realize(handlebox);
8044 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8045 (gpointer)compose);
8047 vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
8048 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8049 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8051 /* Notebook */
8052 notebook = gtk_notebook_new();
8053 gtk_widget_show(notebook);
8055 /* header labels and entries */
8056 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8057 compose_create_header(compose),
8058 gtk_label_new_with_mnemonic(_("Hea_der")));
8059 /* attachment list */
8060 attach_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8061 gtk_widget_show(attach_hbox);
8063 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8064 gtk_widget_show(attach_lab1);
8065 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8067 attach_lab2 = gtk_label_new("");
8068 gtk_widget_show(attach_lab2);
8069 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8071 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8072 compose_create_attach(compose),
8073 attach_hbox);
8074 /* Others Tab */
8075 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8076 compose_create_others(compose),
8077 gtk_label_new_with_mnemonic(_("Othe_rs")));
8079 /* Subject */
8080 subject_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8081 gtk_widget_show(subject_hbox);
8083 subject_frame = gtk_frame_new(NULL);
8084 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8085 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8086 gtk_widget_show(subject_frame);
8088 subject = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, HSPACING_NARROW);
8089 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8090 gtk_widget_show(subject);
8092 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8093 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8094 gtk_widget_show(label);
8096 #ifdef USE_ENCHANT
8097 subject_entry = claws_spell_entry_new();
8098 #else
8099 subject_entry = gtk_entry_new();
8100 #endif
8101 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8102 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8103 G_CALLBACK(compose_grab_focus_cb), compose);
8104 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8105 gtk_widget_show(subject_entry);
8106 compose->subject_entry = subject_entry;
8107 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8109 edit_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
8111 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8113 /* ruler */
8114 ruler_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8115 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8117 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8118 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8119 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8120 BORDER_WIDTH);
8122 /* text widget */
8123 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8124 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8125 GTK_POLICY_AUTOMATIC,
8126 GTK_POLICY_AUTOMATIC);
8127 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8128 GTK_SHADOW_IN);
8129 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8131 text = gtk_text_view_new();
8132 if (prefs_common.show_compose_margin) {
8133 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8134 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8136 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8137 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8138 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8139 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8140 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8142 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8143 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8144 G_CALLBACK(compose_edit_size_alloc),
8145 ruler);
8146 g_signal_connect(G_OBJECT(buffer), "changed",
8147 G_CALLBACK(compose_changed_cb), compose);
8148 g_signal_connect(G_OBJECT(text), "grab_focus",
8149 G_CALLBACK(compose_grab_focus_cb), compose);
8150 g_signal_connect(G_OBJECT(buffer), "insert_text",
8151 G_CALLBACK(text_inserted), compose);
8152 g_signal_connect(G_OBJECT(text), "button_press_event",
8153 G_CALLBACK(text_clicked), compose);
8154 g_signal_connect(G_OBJECT(text), "popup-menu",
8155 G_CALLBACK(compose_popup_menu), compose);
8156 g_signal_connect(G_OBJECT(subject_entry), "changed",
8157 G_CALLBACK(compose_changed_cb), compose);
8158 g_signal_connect(G_OBJECT(subject_entry), "activate",
8159 G_CALLBACK(compose_subject_entry_activated), compose);
8161 /* drag and drop */
8162 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8163 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8164 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8165 g_signal_connect(G_OBJECT(text), "drag_data_received",
8166 G_CALLBACK(compose_insert_drag_received_cb),
8167 compose);
8168 g_signal_connect(G_OBJECT(text), "drag-drop",
8169 G_CALLBACK(compose_drag_drop),
8170 compose);
8171 g_signal_connect(G_OBJECT(text), "key-press-event",
8172 G_CALLBACK(completion_set_focus_to_subject),
8173 compose);
8174 gtk_widget_show_all(vbox);
8176 /* pane between attach clist and text */
8177 paned = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
8178 gtk_box_pack_start(GTK_BOX(vbox2), paned, TRUE, TRUE, 0);
8179 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8180 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8181 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8182 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8183 G_CALLBACK(compose_notebook_size_alloc), paned);
8185 gtk_widget_show_all(paned);
8188 if (prefs_common.textfont) {
8189 PangoFontDescription *font_desc;
8191 font_desc = pango_font_description_from_string
8192 (prefs_common.textfont);
8193 if (font_desc) {
8194 gtk_widget_override_font(text, font_desc);
8195 pango_font_description_free(font_desc);
8199 gtk_action_group_add_actions(action_group, compose_popup_entries,
8200 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8201 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8202 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8203 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8204 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8205 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8206 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8208 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8210 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8211 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8212 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8214 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8216 undostruct = undo_init(text);
8217 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8218 compose);
8220 address_completion_start(window);
8222 compose->window = window;
8223 compose->vbox = vbox;
8224 compose->menubar = menubar;
8225 compose->handlebox = handlebox;
8227 compose->vbox2 = vbox2;
8229 compose->paned = paned;
8231 compose->attach_label = attach_lab2;
8233 compose->notebook = notebook;
8234 compose->edit_vbox = edit_vbox;
8235 compose->ruler_hbox = ruler_hbox;
8236 compose->ruler = ruler;
8237 compose->scrolledwin = scrolledwin;
8238 compose->text = text;
8240 compose->focused_editable = NULL;
8242 compose->popupmenu = popupmenu;
8244 compose->tmpl_menu = tmpl_menu;
8246 compose->mode = mode;
8247 compose->rmode = mode;
8249 compose->targetinfo = NULL;
8250 compose->replyinfo = NULL;
8251 compose->fwdinfo = NULL;
8253 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8254 g_str_equal, (GDestroyNotify) g_free, NULL);
8256 compose->replyto = NULL;
8257 compose->cc = NULL;
8258 compose->bcc = NULL;
8259 compose->followup_to = NULL;
8261 compose->ml_post = NULL;
8263 compose->inreplyto = NULL;
8264 compose->references = NULL;
8265 compose->msgid = NULL;
8266 compose->boundary = NULL;
8268 compose->autowrap = prefs_common.autowrap;
8269 compose->autoindent = prefs_common.auto_indent;
8270 compose->use_signing = FALSE;
8271 compose->use_encryption = FALSE;
8272 compose->privacy_system = NULL;
8273 compose->encdata = NULL;
8275 compose->modified = FALSE;
8277 compose->return_receipt = FALSE;
8279 compose->to_list = NULL;
8280 compose->newsgroup_list = NULL;
8282 compose->undostruct = undostruct;
8284 compose->sig_str = NULL;
8286 compose->exteditor_file = NULL;
8287 compose->exteditor_pid = INVALID_PID;
8288 compose->exteditor_tag = -1;
8289 compose->exteditor_socket = NULL;
8290 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8292 compose->folder_update_callback_id =
8293 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8294 compose_update_folder_hook,
8295 (gpointer) compose);
8297 #if USE_ENCHANT
8298 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8299 if (mode != COMPOSE_REDIRECT) {
8300 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8301 strcmp(prefs_common.dictionary, "")) {
8302 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8303 prefs_common.alt_dictionary,
8304 conv_get_locale_charset_str(),
8305 prefs_common.color[COL_MISSPELLED],
8306 prefs_common.check_while_typing,
8307 prefs_common.recheck_when_changing_dict,
8308 prefs_common.use_alternate,
8309 prefs_common.use_both_dicts,
8310 GTK_TEXT_VIEW(text),
8311 GTK_WINDOW(compose->window),
8312 compose_dict_changed,
8313 compose_spell_menu_changed,
8314 compose);
8315 if (!gtkaspell) {
8316 alertpanel_error(_("Spell checker could not "
8317 "be started.\n%s"),
8318 gtkaspell_checkers_strerror());
8319 gtkaspell_checkers_reset_error();
8320 } else {
8321 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8325 compose->gtkaspell = gtkaspell;
8326 compose_spell_menu_changed(compose);
8327 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8328 #endif
8330 compose_select_account(compose, account, TRUE);
8332 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8333 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8335 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8336 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8338 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8339 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8341 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8342 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8344 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8345 if (account->protocol != A_NNTP)
8346 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8347 prefs_common_translated_header_name("To:"));
8348 else
8349 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8350 prefs_common_translated_header_name("Newsgroups:"));
8352 #ifndef USE_ALT_ADDRBOOK
8353 addressbook_set_target_compose(compose);
8354 #endif
8355 if (mode != COMPOSE_REDIRECT)
8356 compose_set_template_menu(compose);
8357 else {
8358 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8361 compose_list = g_list_append(compose_list, compose);
8363 if (!prefs_common.show_ruler)
8364 gtk_widget_hide(ruler_hbox);
8366 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8368 /* Priority */
8369 compose->priority = PRIORITY_NORMAL;
8370 compose_update_priority_menu_item(compose);
8372 compose_set_out_encoding(compose);
8374 /* Actions menu */
8375 compose_update_actions_menu(compose);
8377 /* Privacy Systems menu */
8378 compose_update_privacy_systems_menu(compose);
8379 compose_activate_privacy_system(compose, account, TRUE);
8381 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8382 if (batch) {
8383 gtk_widget_realize(window);
8384 } else {
8385 gtk_widget_show(window);
8388 return compose;
8391 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8393 GList *accounts;
8394 GtkWidget *hbox;
8395 GtkWidget *optmenu;
8396 GtkWidget *optmenubox;
8397 GtkWidget *fromlabel;
8398 GtkListStore *menu;
8399 GtkTreeIter iter;
8400 GtkWidget *from_name = NULL;
8402 gint num = 0, def_menu = 0;
8404 accounts = account_get_list();
8405 cm_return_val_if_fail(accounts != NULL, NULL);
8407 optmenubox = gtk_event_box_new();
8408 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8409 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8411 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
8412 from_name = gtk_entry_new();
8414 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8415 G_CALLBACK(compose_grab_focus_cb), compose);
8416 g_signal_connect_after(G_OBJECT(from_name), "activate",
8417 G_CALLBACK(from_name_activate_cb), optmenu);
8419 for (; accounts != NULL; accounts = accounts->next, num++) {
8420 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8421 gchar *name, *from = NULL;
8423 if (ac == compose->account) def_menu = num;
8425 name = g_markup_printf_escaped("<i>%s</i>",
8426 ac->account_name);
8428 if (ac == compose->account) {
8429 if (ac->name && *ac->name) {
8430 gchar *buf;
8431 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8432 from = g_strdup_printf("%s <%s>",
8433 buf, ac->address);
8434 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8435 } else {
8436 from = g_strdup_printf("%s",
8437 ac->address);
8438 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8440 if (cur_account != compose->account) {
8441 GdkColor color;
8443 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_bgcolor, color);
8444 gtk_widget_modify_base(
8445 GTK_WIDGET(from_name),
8446 GTK_STATE_NORMAL, &color);
8447 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_color, color);
8448 gtk_widget_modify_text(
8449 GTK_WIDGET(from_name),
8450 GTK_STATE_NORMAL, &color);
8453 COMBOBOX_ADD(menu, name, ac->account_id);
8454 g_free(name);
8455 g_free(from);
8458 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8460 g_signal_connect(G_OBJECT(optmenu), "changed",
8461 G_CALLBACK(account_activated),
8462 compose);
8463 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8464 G_CALLBACK(compose_entry_popup_extend),
8465 NULL);
8467 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8468 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8470 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8471 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8472 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8474 CLAWS_SET_TIP(optmenubox,
8475 _("Account to use for this email"));
8476 CLAWS_SET_TIP(from_name,
8477 _("Sender address to be used"));
8479 compose->account_combo = optmenu;
8480 compose->from_name = from_name;
8482 return hbox;
8485 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8487 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8488 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8489 Compose *compose = (Compose *) data;
8490 if (active) {
8491 compose->priority = value;
8495 static void compose_reply_change_mode(Compose *compose,
8496 ComposeMode action)
8498 gboolean was_modified = compose->modified;
8500 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8502 cm_return_if_fail(compose->replyinfo != NULL);
8504 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8505 ml = TRUE;
8506 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8507 followup = TRUE;
8508 if (action == COMPOSE_REPLY_TO_ALL)
8509 all = TRUE;
8510 if (action == COMPOSE_REPLY_TO_SENDER)
8511 sender = TRUE;
8512 if (action == COMPOSE_REPLY_TO_LIST)
8513 ml = TRUE;
8515 compose_remove_header_entries(compose);
8516 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8517 if (compose->account->set_autocc && compose->account->auto_cc)
8518 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8520 if (compose->account->set_autobcc && compose->account->auto_bcc)
8521 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8523 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8524 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8525 compose_show_first_last_header(compose, TRUE);
8526 compose->modified = was_modified;
8527 compose_set_title(compose);
8530 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8532 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8533 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8534 Compose *compose = (Compose *) data;
8536 if (active)
8537 compose_reply_change_mode(compose, value);
8540 static void compose_update_priority_menu_item(Compose * compose)
8542 GtkWidget *menuitem = NULL;
8543 switch (compose->priority) {
8544 case PRIORITY_HIGHEST:
8545 menuitem = gtk_ui_manager_get_widget
8546 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8547 break;
8548 case PRIORITY_HIGH:
8549 menuitem = gtk_ui_manager_get_widget
8550 (compose->ui_manager, "/Menu/Options/Priority/High");
8551 break;
8552 case PRIORITY_NORMAL:
8553 menuitem = gtk_ui_manager_get_widget
8554 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8555 break;
8556 case PRIORITY_LOW:
8557 menuitem = gtk_ui_manager_get_widget
8558 (compose->ui_manager, "/Menu/Options/Priority/Low");
8559 break;
8560 case PRIORITY_LOWEST:
8561 menuitem = gtk_ui_manager_get_widget
8562 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8563 break;
8565 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8568 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8570 Compose *compose = (Compose *) data;
8571 gchar *systemid;
8572 gboolean can_sign = FALSE, can_encrypt = FALSE;
8574 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8576 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8577 return;
8579 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8580 g_free(compose->privacy_system);
8581 compose->privacy_system = NULL;
8582 g_free(compose->encdata);
8583 compose->encdata = NULL;
8584 if (systemid != NULL) {
8585 compose->privacy_system = g_strdup(systemid);
8587 can_sign = privacy_system_can_sign(systemid);
8588 can_encrypt = privacy_system_can_encrypt(systemid);
8591 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8593 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8594 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8595 if (compose->toolbar->privacy_sign_btn != NULL) {
8596 gtk_widget_set_sensitive(
8597 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8598 can_sign);
8599 gtk_toggle_tool_button_set_active(
8600 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8601 can_sign ? compose->use_signing : FALSE);
8603 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8604 gtk_widget_set_sensitive(
8605 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8606 can_encrypt);
8607 gtk_toggle_tool_button_set_active(
8608 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8609 can_encrypt ? compose->use_encryption : FALSE);
8613 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8615 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8616 GtkWidget *menuitem = NULL;
8617 GList *children, *amenu;
8618 gboolean can_sign = FALSE, can_encrypt = FALSE;
8619 gboolean found = FALSE;
8621 if (compose->privacy_system != NULL) {
8622 gchar *systemid;
8623 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8624 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8625 cm_return_if_fail(menuitem != NULL);
8627 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8628 amenu = children;
8629 menuitem = NULL;
8630 while (amenu != NULL) {
8631 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8632 if (systemid != NULL) {
8633 if (strcmp(systemid, compose->privacy_system) == 0 &&
8634 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8635 menuitem = GTK_WIDGET(amenu->data);
8637 can_sign = privacy_system_can_sign(systemid);
8638 can_encrypt = privacy_system_can_encrypt(systemid);
8639 found = TRUE;
8640 break;
8642 } else if (strlen(compose->privacy_system) == 0 &&
8643 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8644 menuitem = GTK_WIDGET(amenu->data);
8646 can_sign = FALSE;
8647 can_encrypt = FALSE;
8648 found = TRUE;
8649 break;
8652 amenu = amenu->next;
8654 g_list_free(children);
8655 if (menuitem != NULL)
8656 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8658 if (warn && !found && strlen(compose->privacy_system)) {
8659 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8660 "will not be able to sign or encrypt this message."),
8661 compose->privacy_system);
8665 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8666 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8667 if (compose->toolbar->privacy_sign_btn != NULL) {
8668 gtk_widget_set_sensitive(
8669 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8670 can_sign);
8672 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8673 gtk_widget_set_sensitive(
8674 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8675 can_encrypt);
8679 static void compose_set_out_encoding(Compose *compose)
8681 CharSet out_encoding;
8682 const gchar *branch = NULL;
8683 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8685 switch(out_encoding) {
8686 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8687 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8688 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8689 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8690 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8691 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8692 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8693 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8694 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8695 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8696 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8697 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8698 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8699 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8700 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8701 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8702 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8703 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8704 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8705 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8706 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8707 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8708 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8709 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8710 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8711 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8712 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8713 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8714 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8715 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8716 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8717 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8718 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8719 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8721 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8724 static void compose_set_template_menu(Compose *compose)
8726 GSList *tmpl_list, *cur;
8727 GtkWidget *menu;
8728 GtkWidget *item;
8730 tmpl_list = template_get_config();
8732 menu = gtk_menu_new();
8734 gtk_menu_set_accel_group (GTK_MENU (menu),
8735 gtk_ui_manager_get_accel_group(compose->ui_manager));
8736 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8737 Template *tmpl = (Template *)cur->data;
8738 gchar *accel_path = NULL;
8739 item = gtk_menu_item_new_with_label(tmpl->name);
8740 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8741 g_signal_connect(G_OBJECT(item), "activate",
8742 G_CALLBACK(compose_template_activate_cb),
8743 compose);
8744 g_object_set_data(G_OBJECT(item), "template", tmpl);
8745 gtk_widget_show(item);
8746 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8747 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8748 g_free(accel_path);
8751 gtk_widget_show(menu);
8752 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8755 void compose_update_actions_menu(Compose *compose)
8757 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8760 static void compose_update_privacy_systems_menu(Compose *compose)
8762 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8763 GSList *systems, *cur;
8764 GtkWidget *widget;
8765 GtkWidget *system_none;
8766 GSList *group;
8767 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8768 GtkWidget *privacy_menu = gtk_menu_new();
8770 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8771 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8773 g_signal_connect(G_OBJECT(system_none), "activate",
8774 G_CALLBACK(compose_set_privacy_system_cb), compose);
8776 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8777 gtk_widget_show(system_none);
8779 systems = privacy_get_system_ids();
8780 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8781 gchar *systemid = cur->data;
8783 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8784 widget = gtk_radio_menu_item_new_with_label(group,
8785 privacy_system_get_name(systemid));
8786 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8787 g_strdup(systemid), g_free);
8788 g_signal_connect(G_OBJECT(widget), "activate",
8789 G_CALLBACK(compose_set_privacy_system_cb), compose);
8791 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8792 gtk_widget_show(widget);
8793 g_free(systemid);
8795 g_slist_free(systems);
8796 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8797 gtk_widget_show_all(privacy_menu);
8798 gtk_widget_show_all(privacy_menuitem);
8801 void compose_reflect_prefs_all(void)
8803 GList *cur;
8804 Compose *compose;
8806 for (cur = compose_list; cur != NULL; cur = cur->next) {
8807 compose = (Compose *)cur->data;
8808 compose_set_template_menu(compose);
8812 void compose_reflect_prefs_pixmap_theme(void)
8814 GList *cur;
8815 Compose *compose;
8817 for (cur = compose_list; cur != NULL; cur = cur->next) {
8818 compose = (Compose *)cur->data;
8819 toolbar_update(TOOLBAR_COMPOSE, compose);
8823 static const gchar *compose_quote_char_from_context(Compose *compose)
8825 const gchar *qmark = NULL;
8827 cm_return_val_if_fail(compose != NULL, NULL);
8829 switch (compose->mode) {
8830 /* use forward-specific quote char */
8831 case COMPOSE_FORWARD:
8832 case COMPOSE_FORWARD_AS_ATTACH:
8833 case COMPOSE_FORWARD_INLINE:
8834 if (compose->folder && compose->folder->prefs &&
8835 compose->folder->prefs->forward_with_format)
8836 qmark = compose->folder->prefs->forward_quotemark;
8837 else if (compose->account->forward_with_format)
8838 qmark = compose->account->forward_quotemark;
8839 else
8840 qmark = prefs_common.fw_quotemark;
8841 break;
8843 /* use reply-specific quote char in all other modes */
8844 default:
8845 if (compose->folder && compose->folder->prefs &&
8846 compose->folder->prefs->reply_with_format)
8847 qmark = compose->folder->prefs->reply_quotemark;
8848 else if (compose->account->reply_with_format)
8849 qmark = compose->account->reply_quotemark;
8850 else
8851 qmark = prefs_common.quotemark;
8852 break;
8855 if (qmark == NULL || *qmark == '\0')
8856 qmark = "> ";
8858 return qmark;
8861 static void compose_template_apply(Compose *compose, Template *tmpl,
8862 gboolean replace)
8864 GtkTextView *text;
8865 GtkTextBuffer *buffer;
8866 GtkTextMark *mark;
8867 GtkTextIter iter;
8868 const gchar *qmark;
8869 gchar *parsed_str = NULL;
8870 gint cursor_pos = 0;
8871 const gchar *err_msg = _("The body of the template has an error at line %d.");
8872 if (!tmpl) return;
8874 /* process the body */
8876 text = GTK_TEXT_VIEW(compose->text);
8877 buffer = gtk_text_view_get_buffer(text);
8879 if (tmpl->value) {
8880 qmark = compose_quote_char_from_context(compose);
8882 if (compose->replyinfo != NULL) {
8884 if (replace)
8885 gtk_text_buffer_set_text(buffer, "", -1);
8886 mark = gtk_text_buffer_get_insert(buffer);
8887 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8889 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8890 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8892 } else if (compose->fwdinfo != NULL) {
8894 if (replace)
8895 gtk_text_buffer_set_text(buffer, "", -1);
8896 mark = gtk_text_buffer_get_insert(buffer);
8897 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8899 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8900 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8902 } else {
8903 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8905 GtkTextIter start, end;
8906 gchar *tmp = NULL;
8908 gtk_text_buffer_get_start_iter(buffer, &start);
8909 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8910 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8912 /* clear the buffer now */
8913 if (replace)
8914 gtk_text_buffer_set_text(buffer, "", -1);
8916 parsed_str = compose_quote_fmt(compose, dummyinfo,
8917 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8918 procmsg_msginfo_free( &dummyinfo );
8920 g_free( tmp );
8922 } else {
8923 if (replace)
8924 gtk_text_buffer_set_text(buffer, "", -1);
8925 mark = gtk_text_buffer_get_insert(buffer);
8926 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8929 if (replace && parsed_str && compose->account->auto_sig)
8930 compose_insert_sig(compose, FALSE);
8932 if (replace && parsed_str) {
8933 gtk_text_buffer_get_start_iter(buffer, &iter);
8934 gtk_text_buffer_place_cursor(buffer, &iter);
8937 if (parsed_str) {
8938 cursor_pos = quote_fmt_get_cursor_pos();
8939 compose->set_cursor_pos = cursor_pos;
8940 if (cursor_pos == -1)
8941 cursor_pos = 0;
8942 gtk_text_buffer_get_start_iter(buffer, &iter);
8943 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8944 gtk_text_buffer_place_cursor(buffer, &iter);
8947 /* process the other fields */
8949 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8950 compose_template_apply_fields(compose, tmpl);
8951 quote_fmt_reset_vartable();
8952 quote_fmtlex_destroy();
8954 compose_changed_cb(NULL, compose);
8956 #ifdef USE_ENCHANT
8957 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8958 gtkaspell_highlight_all(compose->gtkaspell);
8959 #endif
8962 static void compose_template_apply_fields_error(const gchar *header)
8964 gchar *tr;
8965 gchar *text;
8967 tr = g_strdup(C_("'%s' stands for a header name",
8968 "Template '%s' format error."));
8969 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8970 alertpanel_error("%s", text);
8972 g_free(text);
8973 g_free(tr);
8976 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8978 MsgInfo* dummyinfo = NULL;
8979 MsgInfo *msginfo = NULL;
8980 gchar *buf = NULL;
8982 if (compose->replyinfo != NULL)
8983 msginfo = compose->replyinfo;
8984 else if (compose->fwdinfo != NULL)
8985 msginfo = compose->fwdinfo;
8986 else {
8987 dummyinfo = compose_msginfo_new_from_compose(compose);
8988 msginfo = dummyinfo;
8991 if (tmpl->from && *tmpl->from != '\0') {
8992 #ifdef USE_ENCHANT
8993 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8994 compose->gtkaspell);
8995 #else
8996 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8997 #endif
8998 quote_fmt_scan_string(tmpl->from);
8999 quote_fmt_parse();
9001 buf = quote_fmt_get_buffer();
9002 if (buf == NULL) {
9003 compose_template_apply_fields_error("From");
9004 } else {
9005 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
9008 quote_fmt_reset_vartable();
9009 quote_fmtlex_destroy();
9012 if (tmpl->to && *tmpl->to != '\0') {
9013 #ifdef USE_ENCHANT
9014 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9015 compose->gtkaspell);
9016 #else
9017 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9018 #endif
9019 quote_fmt_scan_string(tmpl->to);
9020 quote_fmt_parse();
9022 buf = quote_fmt_get_buffer();
9023 if (buf == NULL) {
9024 compose_template_apply_fields_error("To");
9025 } else {
9026 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
9029 quote_fmt_reset_vartable();
9030 quote_fmtlex_destroy();
9033 if (tmpl->cc && *tmpl->cc != '\0') {
9034 #ifdef USE_ENCHANT
9035 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9036 compose->gtkaspell);
9037 #else
9038 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9039 #endif
9040 quote_fmt_scan_string(tmpl->cc);
9041 quote_fmt_parse();
9043 buf = quote_fmt_get_buffer();
9044 if (buf == NULL) {
9045 compose_template_apply_fields_error("Cc");
9046 } else {
9047 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9050 quote_fmt_reset_vartable();
9051 quote_fmtlex_destroy();
9054 if (tmpl->bcc && *tmpl->bcc != '\0') {
9055 #ifdef USE_ENCHANT
9056 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9057 compose->gtkaspell);
9058 #else
9059 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9060 #endif
9061 quote_fmt_scan_string(tmpl->bcc);
9062 quote_fmt_parse();
9064 buf = quote_fmt_get_buffer();
9065 if (buf == NULL) {
9066 compose_template_apply_fields_error("Bcc");
9067 } else {
9068 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9071 quote_fmt_reset_vartable();
9072 quote_fmtlex_destroy();
9075 if (tmpl->replyto && *tmpl->replyto != '\0') {
9076 #ifdef USE_ENCHANT
9077 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9078 compose->gtkaspell);
9079 #else
9080 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9081 #endif
9082 quote_fmt_scan_string(tmpl->replyto);
9083 quote_fmt_parse();
9085 buf = quote_fmt_get_buffer();
9086 if (buf == NULL) {
9087 compose_template_apply_fields_error("Reply-To");
9088 } else {
9089 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9092 quote_fmt_reset_vartable();
9093 quote_fmtlex_destroy();
9096 /* process the subject */
9097 if (tmpl->subject && *tmpl->subject != '\0') {
9098 #ifdef USE_ENCHANT
9099 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9100 compose->gtkaspell);
9101 #else
9102 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9103 #endif
9104 quote_fmt_scan_string(tmpl->subject);
9105 quote_fmt_parse();
9107 buf = quote_fmt_get_buffer();
9108 if (buf == NULL) {
9109 compose_template_apply_fields_error("Subject");
9110 } else {
9111 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9114 quote_fmt_reset_vartable();
9115 quote_fmtlex_destroy();
9118 procmsg_msginfo_free( &dummyinfo );
9121 static void compose_destroy(Compose *compose)
9123 GtkAllocation allocation;
9124 GtkTextBuffer *buffer;
9125 GtkClipboard *clipboard;
9127 compose_list = g_list_remove(compose_list, compose);
9129 #ifdef USE_LDAP
9130 gboolean enable = TRUE;
9131 g_slist_foreach(compose->passworded_ldap_servers,
9132 _ldap_srv_func, &enable);
9133 g_slist_free(compose->passworded_ldap_servers);
9134 #endif
9136 if (compose->updating) {
9137 debug_print("danger, not destroying anything now\n");
9138 compose->deferred_destroy = TRUE;
9139 return;
9142 /* NOTE: address_completion_end() does nothing with the window
9143 * however this may change. */
9144 address_completion_end(compose->window);
9146 slist_free_strings_full(compose->to_list);
9147 slist_free_strings_full(compose->newsgroup_list);
9148 slist_free_strings_full(compose->header_list);
9150 slist_free_strings_full(extra_headers);
9151 extra_headers = NULL;
9153 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9155 g_hash_table_destroy(compose->email_hashtable);
9157 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9158 compose->folder_update_callback_id);
9160 procmsg_msginfo_free(&(compose->targetinfo));
9161 procmsg_msginfo_free(&(compose->replyinfo));
9162 procmsg_msginfo_free(&(compose->fwdinfo));
9164 g_free(compose->replyto);
9165 g_free(compose->cc);
9166 g_free(compose->bcc);
9167 g_free(compose->newsgroups);
9168 g_free(compose->followup_to);
9170 g_free(compose->ml_post);
9172 g_free(compose->inreplyto);
9173 g_free(compose->references);
9174 g_free(compose->msgid);
9175 g_free(compose->boundary);
9177 g_free(compose->redirect_filename);
9178 if (compose->undostruct)
9179 undo_destroy(compose->undostruct);
9181 g_free(compose->sig_str);
9183 g_free(compose->exteditor_file);
9185 g_free(compose->orig_charset);
9187 g_free(compose->privacy_system);
9188 g_free(compose->encdata);
9190 #ifndef USE_ALT_ADDRBOOK
9191 if (addressbook_get_target_compose() == compose)
9192 addressbook_set_target_compose(NULL);
9193 #endif
9194 #if USE_ENCHANT
9195 if (compose->gtkaspell) {
9196 gtkaspell_delete(compose->gtkaspell);
9197 compose->gtkaspell = NULL;
9199 #endif
9201 if (!compose->batch) {
9202 gtk_window_get_size(GTK_WINDOW(compose->window),
9203 &allocation.width, &allocation.height);
9204 prefs_common.compose_width = allocation.width;
9205 prefs_common.compose_height = allocation.height;
9208 if (!gtk_widget_get_parent(compose->paned))
9209 gtk_widget_destroy(compose->paned);
9210 gtk_widget_destroy(compose->popupmenu);
9212 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9213 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9214 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9216 message_search_close(compose);
9217 gtk_widget_destroy(compose->window);
9218 toolbar_destroy(compose->toolbar);
9219 g_free(compose->toolbar);
9220 g_mutex_clear(&compose->mutex);
9221 g_free(compose);
9224 static void compose_attach_info_free(AttachInfo *ainfo)
9226 g_free(ainfo->file);
9227 g_free(ainfo->content_type);
9228 g_free(ainfo->name);
9229 g_free(ainfo->charset);
9230 g_free(ainfo);
9233 static void compose_attach_update_label(Compose *compose)
9235 GtkTreeIter iter;
9236 gint i = 1;
9237 gchar *text;
9238 GtkTreeModel *model;
9239 goffset total_size;
9240 AttachInfo *ainfo;
9242 if (compose == NULL)
9243 return;
9245 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9246 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9247 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9248 return;
9251 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9252 total_size = ainfo->size;
9253 while(gtk_tree_model_iter_next(model, &iter)) {
9254 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9255 total_size += ainfo->size;
9256 i++;
9258 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9259 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9260 g_free(text);
9263 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9265 Compose *compose = (Compose *)data;
9266 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9267 GtkTreeSelection *selection;
9268 GList *sel, *cur;
9269 GtkTreeModel *model;
9271 selection = gtk_tree_view_get_selection(tree_view);
9272 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9273 cm_return_if_fail(sel);
9275 for (cur = sel; cur != NULL; cur = cur->next) {
9276 GtkTreePath *path = cur->data;
9277 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9278 (model, cur->data);
9279 cur->data = ref;
9280 gtk_tree_path_free(path);
9283 for (cur = sel; cur != NULL; cur = cur->next) {
9284 GtkTreeRowReference *ref = cur->data;
9285 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9286 GtkTreeIter iter;
9288 if (gtk_tree_model_get_iter(model, &iter, path))
9289 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9291 gtk_tree_path_free(path);
9292 gtk_tree_row_reference_free(ref);
9295 g_list_free(sel);
9296 compose_attach_update_label(compose);
9299 static struct _AttachProperty
9301 GtkWidget *window;
9302 GtkWidget *mimetype_entry;
9303 GtkWidget *encoding_optmenu;
9304 GtkWidget *path_entry;
9305 GtkWidget *filename_entry;
9306 GtkWidget *ok_btn;
9307 GtkWidget *cancel_btn;
9308 } attach_prop;
9310 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9312 gtk_tree_path_free((GtkTreePath *)ptr);
9315 static void compose_attach_property(GtkAction *action, gpointer data)
9317 Compose *compose = (Compose *)data;
9318 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9319 AttachInfo *ainfo;
9320 GtkComboBox *optmenu;
9321 GtkTreeSelection *selection;
9322 GList *sel;
9323 GtkTreeModel *model;
9324 GtkTreeIter iter;
9325 GtkTreePath *path;
9326 static gboolean cancelled;
9328 /* only if one selected */
9329 selection = gtk_tree_view_get_selection(tree_view);
9330 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9331 return;
9333 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9334 cm_return_if_fail(sel);
9336 path = (GtkTreePath *) sel->data;
9337 gtk_tree_model_get_iter(model, &iter, path);
9338 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9340 if (!ainfo) {
9341 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9342 g_list_free(sel);
9343 return;
9345 g_list_free(sel);
9347 if (!attach_prop.window)
9348 compose_attach_property_create(&cancelled);
9349 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9350 gtk_widget_grab_focus(attach_prop.ok_btn);
9351 gtk_widget_show(attach_prop.window);
9352 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9353 GTK_WINDOW(compose->window));
9355 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9356 if (ainfo->encoding == ENC_UNKNOWN)
9357 combobox_select_by_data(optmenu, ENC_BASE64);
9358 else
9359 combobox_select_by_data(optmenu, ainfo->encoding);
9361 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9362 ainfo->content_type ? ainfo->content_type : "");
9363 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9364 ainfo->file ? ainfo->file : "");
9365 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9366 ainfo->name ? ainfo->name : "");
9368 for (;;) {
9369 const gchar *entry_text;
9370 gchar *text;
9371 gchar *cnttype = NULL;
9372 gchar *file = NULL;
9373 off_t size = 0;
9375 cancelled = FALSE;
9376 gtk_main();
9378 gtk_widget_hide(attach_prop.window);
9379 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9381 if (cancelled)
9382 break;
9384 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9385 if (*entry_text != '\0') {
9386 gchar *p;
9388 text = g_strstrip(g_strdup(entry_text));
9389 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9390 cnttype = g_strdup(text);
9391 g_free(text);
9392 } else {
9393 alertpanel_error(_("Invalid MIME type."));
9394 g_free(text);
9395 continue;
9399 ainfo->encoding = combobox_get_active_data(optmenu);
9401 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9402 if (*entry_text != '\0') {
9403 if (is_file_exist(entry_text) &&
9404 (size = get_file_size(entry_text)) > 0)
9405 file = g_strdup(entry_text);
9406 else {
9407 alertpanel_error
9408 (_("File doesn't exist or is empty."));
9409 g_free(cnttype);
9410 continue;
9414 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9415 if (*entry_text != '\0') {
9416 g_free(ainfo->name);
9417 ainfo->name = g_strdup(entry_text);
9420 if (cnttype) {
9421 g_free(ainfo->content_type);
9422 ainfo->content_type = cnttype;
9424 if (file) {
9425 g_free(ainfo->file);
9426 ainfo->file = file;
9428 if (size)
9429 ainfo->size = (goffset)size;
9431 /* update tree store */
9432 text = to_human_readable(ainfo->size);
9433 gtk_tree_model_get_iter(model, &iter, path);
9434 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9435 COL_MIMETYPE, ainfo->content_type,
9436 COL_SIZE, text,
9437 COL_NAME, ainfo->name,
9438 COL_CHARSET, ainfo->charset,
9439 -1);
9441 break;
9444 gtk_tree_path_free(path);
9447 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9449 label = gtk_label_new(str); \
9450 gtk_grid_attach(GTK_GRID(table), label, 0, top, 1, 1); \
9451 gtk_label_set_xalign(GTK_LABEL(label), 0.0); \
9452 entry = gtk_entry_new(); \
9453 gtk_grid_attach(GTK_GRID(table), entry, 1, top, 1, 1); \
9456 static void compose_attach_property_create(gboolean *cancelled)
9458 GtkWidget *window;
9459 GtkWidget *vbox;
9460 GtkWidget *table;
9461 GtkWidget *label;
9462 GtkWidget *mimetype_entry;
9463 GtkWidget *hbox;
9464 GtkWidget *optmenu;
9465 GtkListStore *optmenu_menu;
9466 GtkWidget *path_entry;
9467 GtkWidget *filename_entry;
9468 GtkWidget *hbbox;
9469 GtkWidget *ok_btn;
9470 GtkWidget *cancel_btn;
9471 GList *mime_type_list, *strlist;
9472 GtkTreeIter iter;
9474 debug_print("Creating attach_property window...\n");
9476 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9477 gtk_widget_set_size_request(window, 480, -1);
9478 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9479 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9480 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9481 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
9482 g_signal_connect(G_OBJECT(window), "delete_event",
9483 G_CALLBACK(attach_property_delete_event),
9484 cancelled);
9485 g_signal_connect(G_OBJECT(window), "key_press_event",
9486 G_CALLBACK(attach_property_key_pressed),
9487 cancelled);
9489 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
9490 gtk_container_add(GTK_CONTAINER(window), vbox);
9492 table = gtk_grid_new();
9493 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9494 gtk_grid_set_row_spacing(GTK_GRID(table), 8);
9495 gtk_grid_set_column_spacing(GTK_GRID(table), 8);
9497 label = gtk_label_new(_("MIME type"));
9498 gtk_grid_attach(GTK_GRID(table), label, 0, 0, 1, 1);
9499 gtk_label_set_xalign(GTK_LABEL(label), 0.0);
9500 mimetype_entry = gtk_combo_box_text_new_with_entry();
9501 gtk_grid_attach(GTK_GRID(table), mimetype_entry, 1, 0, 1, 1);
9502 gtk_widget_set_hexpand(mimetype_entry, TRUE);
9503 gtk_widget_set_halign(mimetype_entry, GTK_ALIGN_FILL);
9505 /* stuff with list */
9506 mime_type_list = procmime_get_mime_type_list();
9507 strlist = NULL;
9508 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9509 MimeType *type = (MimeType *) mime_type_list->data;
9510 gchar *tmp;
9512 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9514 if (g_list_find_custom(strlist, tmp, (GCompareFunc)g_strcmp0))
9515 g_free(tmp);
9516 else
9517 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9518 (GCompareFunc)g_strcmp0);
9521 for (mime_type_list = strlist; mime_type_list != NULL;
9522 mime_type_list = mime_type_list->next) {
9523 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9524 g_free(mime_type_list->data);
9526 g_list_free(strlist);
9527 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9528 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9530 label = gtk_label_new(_("Encoding"));
9531 gtk_grid_attach(GTK_GRID(table), label, 0, 1, 1, 1);
9532 gtk_label_set_xalign(GTK_LABEL(label), 0.0);
9534 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
9535 gtk_grid_attach(GTK_GRID(table), hbox, 1, 1, 1, 1);
9536 gtk_widget_set_hexpand(hbox, TRUE);
9537 gtk_widget_set_halign(hbox, GTK_ALIGN_FILL);
9539 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9540 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9542 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9543 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9544 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9545 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9546 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9548 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9550 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9551 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9553 gtkut_stock_button_set_create(&hbbox, &cancel_btn, NULL, _("_Cancel"),
9554 &ok_btn, NULL, _("_OK"),
9555 NULL, NULL, NULL);
9556 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9557 gtk_widget_grab_default(ok_btn);
9559 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9560 G_CALLBACK(attach_property_ok),
9561 cancelled);
9562 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9563 G_CALLBACK(attach_property_cancel),
9564 cancelled);
9566 gtk_widget_show_all(vbox);
9568 attach_prop.window = window;
9569 attach_prop.mimetype_entry = mimetype_entry;
9570 attach_prop.encoding_optmenu = optmenu;
9571 attach_prop.path_entry = path_entry;
9572 attach_prop.filename_entry = filename_entry;
9573 attach_prop.ok_btn = ok_btn;
9574 attach_prop.cancel_btn = cancel_btn;
9577 #undef SET_LABEL_AND_ENTRY
9579 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9581 *cancelled = FALSE;
9582 gtk_main_quit();
9585 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9587 *cancelled = TRUE;
9588 gtk_main_quit();
9591 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9592 gboolean *cancelled)
9594 *cancelled = TRUE;
9595 gtk_main_quit();
9597 return TRUE;
9600 static gboolean attach_property_key_pressed(GtkWidget *widget,
9601 GdkEventKey *event,
9602 gboolean *cancelled)
9604 if (event && event->keyval == GDK_KEY_Escape) {
9605 *cancelled = TRUE;
9606 gtk_main_quit();
9608 if (event && (event->keyval == GDK_KEY_KP_Enter ||
9609 event->keyval == GDK_KEY_Return)) {
9610 *cancelled = FALSE;
9611 gtk_main_quit();
9612 return TRUE;
9614 return FALSE;
9617 static gboolean compose_can_autosave(Compose *compose)
9619 if (compose->privacy_system && compose->use_encryption)
9620 return prefs_common.autosave && prefs_common.autosave_encrypted;
9621 else
9622 return prefs_common.autosave;
9626 * compose_exec_ext_editor:
9628 * Open (and optionally embed) external editor
9630 static void compose_exec_ext_editor(Compose *compose)
9632 gchar *tmp;
9633 #ifndef G_OS_WIN32
9634 GtkWidget *socket;
9635 Window socket_wid = 0;
9636 gchar *p, *s;
9637 #endif /* G_OS_WIN32 */
9638 GPid pid;
9639 GError *error = NULL;
9640 gchar *cmd = NULL;
9641 gchar **argv;
9643 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9644 G_DIR_SEPARATOR, compose);
9646 if (compose_write_body_to_file(compose, tmp) < 0) {
9647 alertpanel_error(_("Could not write the body to file:\n%s"),
9648 tmp);
9649 g_free(tmp);
9650 return;
9653 if (compose_get_ext_editor_uses_socket()) {
9654 #ifndef G_OS_WIN32
9655 /* Only allow one socket */
9656 if (compose->exteditor_socket != NULL) {
9657 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9658 /* Move the focus off of the socket */
9659 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9661 g_free(tmp);
9662 return;
9664 /* Create the receiving GtkSocket */
9665 socket = gtk_socket_new ();
9666 g_signal_connect (G_OBJECT(socket), "plug-removed",
9667 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9668 compose);
9669 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9670 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9671 /* Realize the socket so that we can use its ID */
9672 gtk_widget_realize(socket);
9673 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9674 compose->exteditor_socket = socket;
9675 #else
9676 alertpanel_error(_("Socket communication with an external editor is not available on Windows."));
9677 g_free(tmp);
9678 return;
9679 #endif /* G_OS_WIN32 */
9682 if (compose_get_ext_editor_cmd_valid()) {
9683 if (compose_get_ext_editor_uses_socket()) {
9684 #ifndef G_OS_WIN32
9685 p = g_strdup(prefs_common_get_ext_editor_cmd());
9686 s = strstr(p, "%w");
9687 s[1] = 'u';
9688 if (strstr(p, "%s") < s)
9689 cmd = g_strdup_printf(p, tmp, socket_wid);
9690 else
9691 cmd = g_strdup_printf(p, socket_wid, tmp);
9692 g_free(p);
9693 #endif /* G_OS_WIN32 */
9694 } else {
9695 cmd = g_strdup_printf(prefs_common_get_ext_editor_cmd(), tmp);
9697 } else {
9698 if (prefs_common_get_ext_editor_cmd())
9699 g_warning("external editor command-line is invalid: '%s'",
9700 prefs_common_get_ext_editor_cmd());
9701 cmd = g_strdup_printf(DEFAULT_EDITOR_CMD, tmp);
9704 argv = strsplit_with_quote(cmd, " ", 0);
9706 if (!g_spawn_async(NULL, argv, NULL,
9707 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
9708 NULL, NULL, &pid, &error)) {
9709 alertpanel_error(_("Could not spawn the following "
9710 "external editor command:\n%s\n%s"),
9711 cmd, error ? error->message : _("Unknown error"));
9712 if (error)
9713 g_error_free(error);
9714 g_free(tmp);
9715 g_free(cmd);
9716 g_strfreev(argv);
9717 return;
9719 g_free(cmd);
9720 g_strfreev(argv);
9722 compose->exteditor_file = g_strdup(tmp);
9723 compose->exteditor_pid = pid;
9724 compose->exteditor_tag = g_child_watch_add(pid,
9725 compose_ext_editor_closed_cb,
9726 compose);
9728 compose_set_ext_editor_sensitive(compose, FALSE);
9730 g_free(tmp);
9734 * compose_ext_editor_cb:
9736 * External editor has closed (called by g_child_watch)
9738 static void compose_ext_editor_closed_cb(GPid pid, gint exit_status, gpointer data)
9740 Compose *compose = (Compose *)data;
9741 GError *error = NULL;
9742 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9743 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9744 GtkTextIter start, end;
9745 gchar *chars;
9747 #if GLIB_CHECK_VERSION(2,70,0)
9748 if (!g_spawn_check_wait_status(exit_status, &error)) {
9749 #else
9750 if (!g_spawn_check_exit_status(exit_status, &error)) {
9751 #endif
9752 alertpanel_error(
9753 _("External editor stopped with an error: %s"),
9754 error ? error->message : _("Unknown error"));
9755 if (error)
9756 g_error_free(error);
9758 g_spawn_close_pid(compose->exteditor_pid);
9760 gtk_text_buffer_set_text(buffer, "", -1);
9761 compose_insert_file(compose, compose->exteditor_file);
9762 compose_changed_cb(NULL, compose);
9764 /* Check if we should save the draft or not */
9765 if (compose_can_autosave(compose))
9766 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9768 if (claws_unlink(compose->exteditor_file) < 0)
9769 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9771 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9772 gtk_text_buffer_get_start_iter(buffer, &start);
9773 gtk_text_buffer_get_end_iter(buffer, &end);
9774 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9775 if (chars && strlen(chars) > 0)
9776 compose->modified = TRUE;
9777 g_free(chars);
9779 compose_set_ext_editor_sensitive(compose, TRUE);
9781 g_free(compose->exteditor_file);
9782 compose->exteditor_file = NULL;
9783 compose->exteditor_pid = INVALID_PID;
9784 compose->exteditor_tag = -1;
9785 if (compose->exteditor_socket) {
9786 gtk_widget_destroy(compose->exteditor_socket);
9787 compose->exteditor_socket = NULL;
9792 static gboolean compose_get_ext_editor_cmd_valid()
9794 gboolean has_s = FALSE;
9795 gboolean has_w = FALSE;
9796 const gchar *p = prefs_common_get_ext_editor_cmd();
9797 if (!p)
9798 return FALSE;
9799 while ((p = strchr(p, '%'))) {
9800 p++;
9801 if (*p == 's') {
9802 if (has_s)
9803 return FALSE;
9804 has_s = TRUE;
9805 } else if (*p == 'w') {
9806 if (has_w)
9807 return FALSE;
9808 has_w = TRUE;
9809 } else {
9810 return FALSE;
9813 return TRUE;
9816 static gboolean compose_ext_editor_kill(Compose *compose)
9818 GPid pid = compose->exteditor_pid;
9819 gchar *pidmsg = NULL;
9821 if (pid > 0) {
9822 AlertValue val;
9823 gchar *msg;
9825 pidmsg = g_strdup_printf(_("process id: %" G_PID_FORMAT), pid);
9827 msg = g_strdup_printf
9828 (_("The external editor is still working.\n"
9829 "Force terminating the process?\n"
9830 "%s"), pidmsg);
9831 val = alertpanel_full(_("Notice"), msg, NULL, _("_No"), NULL, _("_Yes"),
9832 NULL, NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9833 ALERT_WARNING);
9834 g_free(msg);
9836 if (val == G_ALERTALTERNATE) {
9837 g_source_remove(compose->exteditor_tag);
9839 #ifdef G_OS_WIN32
9840 if (!TerminateProcess(compose->exteditor_pid, 0))
9841 perror("TerminateProcess");
9842 #else
9843 if (kill(pid, SIGTERM) < 0) perror("kill");
9844 waitpid(compose->exteditor_pid, NULL, 0);
9845 #endif /* G_OS_WIN32 */
9847 g_warning("terminated %s, temporary file: %s",
9848 pidmsg, compose->exteditor_file);
9849 g_spawn_close_pid(compose->exteditor_pid);
9851 compose_set_ext_editor_sensitive(compose, TRUE);
9853 g_free(compose->exteditor_file);
9854 compose->exteditor_file = NULL;
9855 compose->exteditor_pid = INVALID_PID;
9856 compose->exteditor_tag = -1;
9857 } else {
9858 g_free(pidmsg);
9859 return FALSE;
9863 if (pidmsg)
9864 g_free(pidmsg);
9865 return TRUE;
9868 static char *ext_editor_menu_entries[] = {
9869 "Menu/Message/Send",
9870 "Menu/Message/SendLater",
9871 "Menu/Message/InsertFile",
9872 "Menu/Message/InsertSig",
9873 "Menu/Message/ReplaceSig",
9874 "Menu/Message/Save",
9875 "Menu/Message/Print",
9876 "Menu/Edit",
9877 #if USE_ENCHANT
9878 "Menu/Spelling",
9879 #endif
9880 "Menu/Tools/ShowRuler",
9881 "Menu/Tools/Actions",
9882 "Menu/Help",
9883 NULL
9886 static void compose_set_ext_editor_sensitive(Compose *compose,
9887 gboolean sensitive)
9889 int i;
9891 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9892 cm_menu_set_sensitive_full(compose->ui_manager,
9893 ext_editor_menu_entries[i], sensitive);
9896 if (compose_get_ext_editor_uses_socket()) {
9897 if (sensitive) {
9898 if (compose->exteditor_socket)
9899 gtk_widget_hide(compose->exteditor_socket);
9900 gtk_widget_show(compose->scrolledwin);
9901 if (prefs_common.show_ruler)
9902 gtk_widget_show(compose->ruler_hbox);
9903 /* Fix the focus, as it doesn't go anywhere when the
9904 * socket is hidden or destroyed */
9905 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9906 } else {
9907 g_assert (compose->exteditor_socket != NULL);
9908 /* Fix the focus, as it doesn't go anywhere when the
9909 * edit box is hidden */
9910 if (gtk_widget_is_focus(compose->text))
9911 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9912 gtk_widget_hide(compose->scrolledwin);
9913 gtk_widget_hide(compose->ruler_hbox);
9914 gtk_widget_show(compose->exteditor_socket);
9916 } else {
9917 gtk_widget_set_sensitive(compose->text, sensitive);
9919 if (compose->toolbar->send_btn)
9920 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9921 if (compose->toolbar->sendl_btn)
9922 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9923 if (compose->toolbar->draft_btn)
9924 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9925 if (compose->toolbar->insert_btn)
9926 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9927 if (compose->toolbar->sig_btn)
9928 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9929 if (compose->toolbar->exteditor_btn)
9930 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9931 if (compose->toolbar->linewrap_current_btn)
9932 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9933 if (compose->toolbar->linewrap_all_btn)
9934 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9937 static gboolean compose_get_ext_editor_uses_socket()
9939 return (prefs_common_get_ext_editor_cmd() &&
9940 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9943 #ifndef G_OS_WIN32
9944 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9946 compose->exteditor_socket = NULL;
9947 /* returning FALSE allows destruction of the socket */
9948 return FALSE;
9950 #endif /* G_OS_WIN32 */
9953 * compose_undo_state_changed:
9955 * Change the sensivity of the menuentries undo and redo
9957 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9958 gint redo_state, gpointer data)
9960 Compose *compose = (Compose *)data;
9962 switch (undo_state) {
9963 case UNDO_STATE_TRUE:
9964 if (!undostruct->undo_state) {
9965 undostruct->undo_state = TRUE;
9966 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9968 break;
9969 case UNDO_STATE_FALSE:
9970 if (undostruct->undo_state) {
9971 undostruct->undo_state = FALSE;
9972 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9974 break;
9975 case UNDO_STATE_UNCHANGED:
9976 break;
9977 case UNDO_STATE_REFRESH:
9978 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9979 break;
9980 default:
9981 g_warning("undo state not recognized");
9982 break;
9985 switch (redo_state) {
9986 case UNDO_STATE_TRUE:
9987 if (!undostruct->redo_state) {
9988 undostruct->redo_state = TRUE;
9989 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9991 break;
9992 case UNDO_STATE_FALSE:
9993 if (undostruct->redo_state) {
9994 undostruct->redo_state = FALSE;
9995 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9997 break;
9998 case UNDO_STATE_UNCHANGED:
9999 break;
10000 case UNDO_STATE_REFRESH:
10001 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
10002 break;
10003 default:
10004 g_warning("redo state not recognized");
10005 break;
10009 /* callback functions */
10011 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10012 GtkAllocation *allocation,
10013 GtkPaned *paned)
10015 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10018 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10019 * includes "non-client" (windows-izm) in calculation, so this calculation
10020 * may not be accurate.
10022 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10023 GtkAllocation *allocation,
10024 GtkSHRuler *shruler)
10026 if (prefs_common.show_ruler) {
10027 gint char_width = 0, char_height = 0;
10028 gint line_width_in_chars;
10030 gtkut_get_font_size(GTK_WIDGET(widget),
10031 &char_width, &char_height);
10032 line_width_in_chars =
10033 (allocation->width - allocation->x) / char_width;
10035 /* got the maximum */
10036 gtk_shruler_set_range(GTK_SHRULER(shruler),
10037 0.0, line_width_in_chars, 0);
10040 return TRUE;
10043 typedef struct {
10044 gchar *header;
10045 gchar *entry;
10046 ComposePrefType type;
10047 gboolean entry_marked;
10048 } HeaderEntryState;
10050 static void account_activated(GtkComboBox *optmenu, gpointer data)
10052 Compose *compose = (Compose *)data;
10054 PrefsAccount *ac;
10055 gchar *folderidentifier;
10056 gint account_id = 0;
10057 GtkTreeModel *menu;
10058 GtkTreeIter iter;
10059 GSList *list, *saved_list = NULL;
10060 HeaderEntryState *state;
10062 /* Get ID of active account in the combo box */
10063 menu = gtk_combo_box_get_model(optmenu);
10064 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10065 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10067 ac = account_find_from_id(account_id);
10068 cm_return_if_fail(ac != NULL);
10070 if (ac != compose->account) {
10071 compose_select_account(compose, ac, FALSE);
10073 for (list = compose->header_list; list; list = list->next) {
10074 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10076 if (hentry->type == PREF_ACCOUNT || !list->next) {
10077 compose_destroy_headerentry(compose, hentry);
10078 continue;
10080 state = g_malloc0(sizeof(HeaderEntryState));
10081 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10082 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10083 state->entry = gtk_editable_get_chars(
10084 GTK_EDITABLE(hentry->entry), 0, -1);
10085 state->type = hentry->type;
10087 saved_list = g_slist_append(saved_list, state);
10088 compose_destroy_headerentry(compose, hentry);
10091 compose->header_last = NULL;
10092 g_slist_free(compose->header_list);
10093 compose->header_list = NULL;
10094 compose->header_nextrow = 1;
10095 compose_create_header_entry(compose);
10097 if (ac->set_autocc && ac->auto_cc)
10098 compose_entry_append(compose, ac->auto_cc,
10099 COMPOSE_CC, PREF_ACCOUNT);
10100 if (ac->set_autobcc && ac->auto_bcc)
10101 compose_entry_append(compose, ac->auto_bcc,
10102 COMPOSE_BCC, PREF_ACCOUNT);
10103 if (ac->set_autoreplyto && ac->auto_replyto)
10104 compose_entry_append(compose, ac->auto_replyto,
10105 COMPOSE_REPLYTO, PREF_ACCOUNT);
10107 for (list = saved_list; list; list = list->next) {
10108 state = (HeaderEntryState *) list->data;
10110 compose_add_header_entry(compose, state->header,
10111 state->entry, state->type);
10113 g_free(state->header);
10114 g_free(state->entry);
10115 g_free(state);
10117 g_slist_free(saved_list);
10119 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10120 (ac->protocol == A_NNTP) ?
10121 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10124 /* Set message save folder */
10125 compose_set_save_to(compose, NULL);
10126 if (compose->folder && compose->folder->prefs && compose->folder->prefs->save_copy_to_folder) {
10127 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10128 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10129 folderidentifier = folder_item_get_identifier(compose->folder);
10130 compose_set_save_to(compose, folderidentifier);
10131 g_free(folderidentifier);
10132 } else if (account_get_special_folder(compose->account, F_OUTBOX)) {
10133 if (compose->account->set_sent_folder || prefs_common.savemsg)
10134 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10135 else
10136 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE);
10137 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10138 folderidentifier = folder_item_get_identifier(account_get_special_folder
10139 (compose->account, F_OUTBOX));
10140 compose_set_save_to(compose, folderidentifier);
10141 g_free(folderidentifier);
10145 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10146 GtkTreeViewColumn *column, Compose *compose)
10148 compose_attach_property(NULL, compose);
10151 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10152 gpointer data)
10154 Compose *compose = (Compose *)data;
10155 GtkTreeSelection *attach_selection;
10156 gint attach_nr_selected;
10157 GtkTreePath *path;
10159 if (!event) return FALSE;
10161 if (event->button == 3) {
10162 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10163 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10165 /* If no rows, or just one row is selected, right-click should
10166 * open menu relevant to the row being right-clicked on. We
10167 * achieve that by selecting the clicked row first. If more
10168 * than one row is selected, we shouldn't modify the selection,
10169 * as user may want to remove selected rows (attachments). */
10170 if (attach_nr_selected < 2) {
10171 gtk_tree_selection_unselect_all(attach_selection);
10172 attach_nr_selected = 0;
10173 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10174 event->x, event->y, &path, NULL, NULL, NULL);
10175 if (path != NULL) {
10176 gtk_tree_selection_select_path(attach_selection, path);
10177 gtk_tree_path_free(path);
10178 attach_nr_selected++;
10182 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10183 /* Properties menu item makes no sense with more than one row
10184 * selected, the properties dialog can only edit one attachment. */
10185 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10187 gtk_menu_popup_at_pointer(GTK_MENU(compose->popupmenu), NULL);
10189 return TRUE;
10192 return FALSE;
10195 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10196 gpointer data)
10198 Compose *compose = (Compose *)data;
10200 if (!event) return FALSE;
10202 switch (event->keyval) {
10203 case GDK_KEY_Delete:
10204 compose_attach_remove_selected(NULL, compose);
10205 break;
10207 return FALSE;
10210 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10212 toolbar_comp_set_sensitive(compose, allow);
10213 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10214 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10215 #if USE_ENCHANT
10216 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10217 #endif
10218 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10219 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10220 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10222 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10226 static void compose_send_cb(GtkAction *action, gpointer data)
10228 Compose *compose = (Compose *)data;
10230 #ifdef G_OS_UNIX
10231 if (compose->exteditor_tag != -1) {
10232 debug_print("ignoring send: external editor still open\n");
10233 return;
10235 #endif
10236 if (prefs_common.work_offline &&
10237 !inc_offline_should_override(TRUE,
10238 _("Claws Mail needs network access in order "
10239 "to send this email.")))
10240 return;
10242 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10243 g_source_remove(compose->draft_timeout_tag);
10244 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10247 compose_send(compose);
10250 static void compose_send_later_cb(GtkAction *action, gpointer data)
10252 Compose *compose = (Compose *)data;
10253 ComposeQueueResult val;
10255 inc_lock();
10256 compose_allow_user_actions(compose, FALSE);
10257 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10258 compose_allow_user_actions(compose, TRUE);
10259 inc_unlock();
10261 if (val == COMPOSE_QUEUE_SUCCESS) {
10262 compose_close(compose);
10263 } else {
10264 _display_queue_error(val);
10267 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10270 #define DRAFTED_AT_EXIT "drafted_at_exit"
10271 static void compose_register_draft(MsgInfo *info)
10273 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10274 DRAFTED_AT_EXIT, NULL);
10275 FILE *fp = claws_fopen(filepath, "ab");
10277 if (fp) {
10278 gchar *name = folder_item_get_identifier(info->folder);
10279 fprintf(fp, "%s\t%d\n", name, info->msgnum);
10280 g_free(name);
10281 claws_fclose(fp);
10284 g_free(filepath);
10287 gboolean compose_draft (gpointer data, guint action)
10289 Compose *compose = (Compose *)data;
10290 FolderItem *draft;
10291 FolderItemPrefs *prefs;
10292 gchar *tmp;
10293 gchar *sheaders;
10294 gint msgnum;
10295 MsgFlags flag = {0, 0};
10296 static gboolean lock = FALSE;
10297 MsgInfo *newmsginfo;
10298 FILE *fp;
10299 gboolean target_locked = FALSE;
10300 gboolean err = FALSE;
10301 gint filemode = 0;
10303 if (lock) return FALSE;
10305 if (compose->sending)
10306 return TRUE;
10308 draft = account_get_special_folder(compose->account, F_DRAFT);
10309 cm_return_val_if_fail(draft != NULL, FALSE);
10311 if (!g_mutex_trylock(&compose->mutex)) {
10312 /* we don't want to lock the mutex once it's available,
10313 * because as the only other part of compose.c locking
10314 * it is compose_close - which means once unlocked,
10315 * the compose struct will be freed */
10316 debug_print("couldn't lock mutex, probably sending\n");
10317 return FALSE;
10320 lock = TRUE;
10322 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10323 G_DIR_SEPARATOR, compose);
10324 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10325 FILE_OP_ERROR(tmp, "claws_fopen");
10326 goto warn_err;
10329 /* chmod for security unless folder chmod is set */
10330 prefs = draft->prefs;
10331 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) {
10332 filemode = prefs->folder_chmod;
10333 if (filemode & S_IRGRP) filemode |= S_IWGRP;
10334 if (filemode & S_IROTH) filemode |= S_IWOTH;
10335 if (chmod(tmp, filemode) < 0)
10336 FILE_OP_ERROR(tmp, "chmod");
10337 } else if (change_file_mode_rw(fp, tmp) < 0) {
10338 FILE_OP_ERROR(tmp, "chmod");
10339 g_warning("can't change file mode");
10342 /* Save draft infos */
10343 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10344 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10346 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10347 gchar *savefolderid;
10349 savefolderid = compose_get_save_to(compose);
10350 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10351 g_free(savefolderid);
10353 if (compose->return_receipt) {
10354 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10356 if (compose->privacy_system) {
10357 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10358 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10359 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10362 /* Message-ID of message replying to */
10363 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10364 gchar *folderid = NULL;
10366 if (compose->replyinfo->folder)
10367 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10368 if (folderid == NULL)
10369 folderid = g_strdup("NULL");
10371 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10372 g_free(folderid);
10374 /* Message-ID of message forwarding to */
10375 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10376 gchar *folderid = NULL;
10378 if (compose->fwdinfo->folder)
10379 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10380 if (folderid == NULL)
10381 folderid = g_strdup("NULL");
10383 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10384 g_free(folderid);
10387 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10388 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10390 sheaders = compose_get_manual_headers_info(compose);
10391 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10392 g_free(sheaders);
10394 /* end of headers */
10395 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10397 if (err) {
10398 claws_fclose(fp);
10399 goto warn_err;
10402 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10403 claws_fclose(fp);
10404 goto warn_err;
10406 if (claws_safe_fclose(fp) == EOF) {
10407 goto warn_err;
10410 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10411 if (compose->targetinfo) {
10412 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10413 if (target_locked)
10414 flag.perm_flags |= MSG_LOCKED;
10416 flag.tmp_flags = MSG_DRAFT;
10418 folder_item_scan(draft);
10419 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10420 MsgInfo *tmpinfo = NULL;
10421 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10422 if (compose->msgid) {
10423 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10425 if (tmpinfo) {
10426 msgnum = tmpinfo->msgnum;
10427 procmsg_msginfo_free(&tmpinfo);
10428 debug_print("got draft msgnum %d from scanning\n", msgnum);
10429 } else {
10430 debug_print("didn't get draft msgnum after scanning\n");
10432 } else {
10433 debug_print("got draft msgnum %d from adding\n", msgnum);
10435 if (msgnum < 0) {
10436 warn_err:
10437 claws_unlink(tmp);
10438 g_free(tmp);
10439 if (action != COMPOSE_AUTO_SAVE) {
10440 if (action != COMPOSE_DRAFT_FOR_EXIT)
10441 alertpanel_error(_("Could not save draft."));
10442 else {
10443 AlertValue val;
10444 gtkut_window_popup(compose->window);
10445 val = alertpanel_full(_("Could not save draft"),
10446 _("Could not save draft.\n"
10447 "Do you want to cancel exit or discard this email?"),
10448 NULL, _("_Cancel exit"), NULL, _("_Discard email"),
10449 NULL, NULL, ALERTFOCUS_FIRST, FALSE, NULL, ALERT_QUESTION);
10450 if (val == G_ALERTALTERNATE) {
10451 lock = FALSE;
10452 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10453 compose_close(compose);
10454 return TRUE;
10455 } else {
10456 lock = FALSE;
10457 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10458 return FALSE;
10462 goto unlock;
10464 g_free(tmp);
10466 if (compose->mode == COMPOSE_REEDIT) {
10467 compose_remove_reedit_target(compose, TRUE);
10470 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10472 if (newmsginfo) {
10473 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10474 if (target_locked)
10475 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10476 else
10477 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10478 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10479 procmsg_msginfo_set_flags(newmsginfo, 0,
10480 MSG_HAS_ATTACHMENT);
10482 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10483 compose_register_draft(newmsginfo);
10485 procmsg_msginfo_free(&newmsginfo);
10488 folder_item_scan(draft);
10490 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10491 lock = FALSE;
10492 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10493 compose_close(compose);
10494 return TRUE;
10495 } else {
10496 #ifdef G_OS_WIN32
10497 GFile *f;
10498 GFileInfo *fi;
10499 GTimeVal tv;
10500 GError *error = NULL;
10501 #else
10502 GStatBuf s;
10503 #endif
10504 gchar *path;
10505 goffset size, mtime;
10507 path = folder_item_fetch_msg(draft, msgnum);
10508 if (path == NULL) {
10509 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10510 goto unlock;
10512 #ifdef G_OS_WIN32
10513 f = g_file_new_for_path(path);
10514 fi = g_file_query_info(f, "standard::size,time::modified",
10515 G_FILE_QUERY_INFO_NONE, NULL, &error);
10516 if (error != NULL) {
10517 debug_print("couldn't query file info for '%s': %s\n",
10518 path, error->message);
10519 g_error_free(error);
10520 g_free(path);
10521 g_object_unref(f);
10522 goto unlock;
10524 size = g_file_info_get_size(fi);
10525 g_file_info_get_modification_time(fi, &tv);
10526 mtime = tv.tv_sec;
10527 g_object_unref(fi);
10528 g_object_unref(f);
10529 #else
10530 if (g_stat(path, &s) < 0) {
10531 FILE_OP_ERROR(path, "stat");
10532 g_free(path);
10533 goto unlock;
10535 size = s.st_size;
10536 mtime = s.st_mtime;
10537 #endif
10538 g_free(path);
10540 procmsg_msginfo_free(&(compose->targetinfo));
10541 compose->targetinfo = procmsg_msginfo_new();
10542 compose->targetinfo->msgnum = msgnum;
10543 compose->targetinfo->size = size;
10544 compose->targetinfo->mtime = mtime;
10545 compose->targetinfo->folder = draft;
10546 if (target_locked)
10547 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10548 compose->mode = COMPOSE_REEDIT;
10550 if (action == COMPOSE_AUTO_SAVE) {
10551 compose->modified = FALSE;
10552 compose->autosaved_draft = compose->targetinfo;
10554 compose_set_title(compose);
10556 unlock:
10557 lock = FALSE;
10558 g_mutex_unlock(&compose->mutex);
10559 return TRUE;
10562 void compose_clear_exit_drafts(void)
10564 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10565 DRAFTED_AT_EXIT, NULL);
10566 if (is_file_exist(filepath))
10567 claws_unlink(filepath);
10569 g_free(filepath);
10572 void compose_reopen_exit_drafts(void)
10574 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10575 DRAFTED_AT_EXIT, NULL);
10576 FILE *fp = claws_fopen(filepath, "rb");
10577 gchar buf[1024];
10579 if (fp) {
10580 while (claws_fgets(buf, sizeof(buf), fp)) {
10581 gchar **parts = g_strsplit(buf, "\t", 2);
10582 const gchar *folder = parts[0];
10583 int msgnum = parts[1] ? atoi(parts[1]):-1;
10585 if (folder && *folder && msgnum > -1) {
10586 FolderItem *item = folder_find_item_from_identifier(folder);
10587 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10588 if (info)
10589 compose_reedit(info, FALSE);
10591 g_strfreev(parts);
10593 claws_fclose(fp);
10595 g_free(filepath);
10596 compose_clear_exit_drafts();
10599 static void compose_save_cb(GtkAction *action, gpointer data)
10601 Compose *compose = (Compose *)data;
10602 compose_draft(compose, COMPOSE_KEEP_EDITING);
10603 compose->rmode = COMPOSE_REEDIT;
10604 compose->modified = FALSE;
10605 compose_set_title(compose);
10608 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10610 if (compose && file_list) {
10611 GList *tmp;
10613 for ( tmp = file_list; tmp; tmp = tmp->next) {
10614 gchar *file = (gchar *) tmp->data;
10615 gchar *utf8_filename = conv_filename_to_utf8(file);
10616 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10617 compose_changed_cb(NULL, compose);
10618 if (free_data) {
10619 g_free(file);
10620 tmp->data = NULL;
10622 g_free(utf8_filename);
10627 static void compose_attach_cb(GtkAction *action, gpointer data)
10629 Compose *compose = (Compose *)data;
10630 GList *file_list;
10632 if (compose->redirect_filename != NULL)
10633 return;
10635 /* Set focus_window properly, in case we were called via popup menu,
10636 * which unsets it (via focus_out_event callback on compose window). */
10637 manage_window_focus_in(compose->window, NULL, NULL);
10639 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10641 if (file_list) {
10642 compose_attach_from_list(compose, file_list, TRUE);
10643 g_list_free(file_list);
10647 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10649 Compose *compose = (Compose *)data;
10650 GList *file_list;
10651 gint files_inserted = 0;
10653 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10655 if (file_list) {
10656 GList *tmp;
10658 for ( tmp = file_list; tmp; tmp = tmp->next) {
10659 gchar *file = (gchar *) tmp->data;
10660 gchar *filedup = g_strdup(file);
10661 gchar *shortfile = g_path_get_basename(filedup);
10662 ComposeInsertResult res;
10663 /* insert the file if the file is short or if the user confirmed that
10664 he/she wants to insert the large file */
10665 res = compose_insert_file(compose, file);
10666 if (res == COMPOSE_INSERT_READ_ERROR) {
10667 alertpanel_error(_("File '%s' could not be read."), shortfile);
10668 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10669 alertpanel_error(_("File '%s' contained invalid characters\n"
10670 "for the current encoding, insertion may be incorrect."),
10671 shortfile);
10672 } else if (res == COMPOSE_INSERT_SUCCESS)
10673 files_inserted++;
10675 g_free(shortfile);
10676 g_free(filedup);
10677 g_free(file);
10679 g_list_free(file_list);
10682 #ifdef USE_ENCHANT
10683 if (files_inserted > 0 && compose->gtkaspell &&
10684 compose->gtkaspell->check_while_typing)
10685 gtkaspell_highlight_all(compose->gtkaspell);
10686 #endif
10689 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10691 Compose *compose = (Compose *)data;
10693 compose_insert_sig(compose, FALSE);
10696 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10698 Compose *compose = (Compose *)data;
10700 compose_insert_sig(compose, TRUE);
10703 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10704 gpointer data)
10706 gint x, y;
10707 Compose *compose = (Compose *)data;
10709 gtk_window_get_position(GTK_WINDOW(widget), &x, &y);
10710 if (!compose->batch) {
10711 prefs_common.compose_x = x;
10712 prefs_common.compose_y = y;
10714 if (compose->sending || compose->updating)
10715 return TRUE;
10716 compose_close_cb(NULL, compose);
10717 return TRUE;
10720 void compose_close_toolbar(Compose *compose)
10722 compose_close_cb(NULL, compose);
10725 static void compose_close_cb(GtkAction *action, gpointer data)
10727 Compose *compose = (Compose *)data;
10728 AlertValue val;
10730 if (compose->exteditor_tag != -1) {
10731 if (!compose_ext_editor_kill(compose))
10732 return;
10735 if (compose->modified) {
10736 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10737 if (!g_mutex_trylock(&compose->mutex)) {
10738 /* we don't want to lock the mutex once it's available,
10739 * because as the only other part of compose.c locking
10740 * it is compose_close - which means once unlocked,
10741 * the compose struct will be freed */
10742 debug_print("couldn't lock mutex, probably sending\n");
10743 return;
10745 if (!reedit || (compose->folder != NULL && compose->folder->stype == F_DRAFT)) {
10746 val = alertpanel(_("Discard message"),
10747 _("This message has been modified. Discard it?"),
10748 NULL, _("_Discard"), NULL, _("_Save to Drafts"), NULL, _("_Cancel"),
10749 ALERTFOCUS_FIRST);
10750 } else {
10751 val = alertpanel(_("Save changes"),
10752 _("This message has been modified. Save the latest changes?"),
10753 NULL, _("_Don't save"), NULL, _("_Save to Drafts"), NULL, _("_Cancel"),
10754 ALERTFOCUS_SECOND);
10756 g_mutex_unlock(&compose->mutex);
10757 switch (val) {
10758 case G_ALERTDEFAULT:
10759 if (compose_can_autosave(compose) && !reedit)
10760 compose_remove_draft(compose);
10761 break;
10762 case G_ALERTALTERNATE:
10763 compose_draft(data, COMPOSE_QUIT_EDITING);
10764 return;
10765 default:
10766 return;
10770 compose_close(compose);
10773 static void compose_print_cb(GtkAction *action, gpointer data)
10775 Compose *compose = (Compose *) data;
10777 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10778 if (compose->targetinfo)
10779 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10782 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10784 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10785 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10786 Compose *compose = (Compose *) data;
10788 if (active)
10789 compose->out_encoding = (CharSet)value;
10792 static void compose_address_cb(GtkAction *action, gpointer data)
10794 Compose *compose = (Compose *)data;
10796 #ifndef USE_ALT_ADDRBOOK
10797 addressbook_open(compose);
10798 #else
10799 GError* error = NULL;
10800 addressbook_connect_signals(compose);
10801 addressbook_dbus_open(TRUE, &error);
10802 if (error) {
10803 g_warning("%s", error->message);
10804 g_error_free(error);
10806 #endif
10809 static void about_show_cb(GtkAction *action, gpointer data)
10811 about_show();
10814 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10816 Compose *compose = (Compose *)data;
10817 Template *tmpl;
10818 gchar *msg;
10819 AlertValue val;
10821 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10822 cm_return_if_fail(tmpl != NULL);
10824 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10825 tmpl->name);
10826 val = alertpanel(_("Apply template"), msg,
10827 NULL, _("_Replace"), NULL, _("_Insert"), NULL, _("_Cancel"),
10828 ALERTFOCUS_FIRST);
10829 g_free(msg);
10831 if (val == G_ALERTDEFAULT)
10832 compose_template_apply(compose, tmpl, TRUE);
10833 else if (val == G_ALERTALTERNATE)
10834 compose_template_apply(compose, tmpl, FALSE);
10837 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10839 Compose *compose = (Compose *)data;
10841 if (compose->exteditor_tag != -1) {
10842 debug_print("ignoring open external editor: external editor still open\n");
10843 return;
10845 compose_exec_ext_editor(compose);
10848 static void compose_undo_cb(GtkAction *action, gpointer data)
10850 Compose *compose = (Compose *)data;
10851 gboolean prev_autowrap = compose->autowrap;
10853 compose->autowrap = FALSE;
10854 undo_undo(compose->undostruct);
10855 compose->autowrap = prev_autowrap;
10858 static void compose_redo_cb(GtkAction *action, gpointer data)
10860 Compose *compose = (Compose *)data;
10861 gboolean prev_autowrap = compose->autowrap;
10863 compose->autowrap = FALSE;
10864 undo_redo(compose->undostruct);
10865 compose->autowrap = prev_autowrap;
10868 static void entry_cut_clipboard(GtkWidget *entry)
10870 if (GTK_IS_EDITABLE(entry))
10871 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10872 else if (GTK_IS_TEXT_VIEW(entry))
10873 gtk_text_buffer_cut_clipboard(
10874 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10875 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10876 TRUE);
10879 static void entry_copy_clipboard(GtkWidget *entry)
10881 if (GTK_IS_EDITABLE(entry))
10882 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10883 else if (GTK_IS_TEXT_VIEW(entry))
10884 gtk_text_buffer_copy_clipboard(
10885 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10886 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10889 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10890 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10892 if (GTK_IS_TEXT_VIEW(entry)) {
10893 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10894 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10895 GtkTextIter start_iter, end_iter;
10896 gint start, end;
10897 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10899 if (contents == NULL)
10900 return;
10902 glong len = g_utf8_strlen(contents, -1);
10903 if (len > MAX_ALLOCA_MEM_SIZE) {
10904 alertpanel_error(_("Size of pasted text exceeds limit (%dKiB) for paste.\n"
10905 "Attach as file instead."),
10906 (MAX_ALLOCA_MEM_SIZE / 1024));
10907 return;
10909 /* we shouldn't delete the selection when middle-click-pasting, or we
10910 * can't mid-click-paste our own selection */
10911 if (clip != GDK_SELECTION_PRIMARY) {
10912 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10913 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10916 if (insert_place == NULL) {
10917 /* if insert_place isn't specified, insert at the cursor.
10918 * used for Ctrl-V pasting */
10919 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10920 start = gtk_text_iter_get_offset(&start_iter);
10921 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10922 } else {
10923 /* if insert_place is specified, paste here.
10924 * used for mid-click-pasting */
10925 start = gtk_text_iter_get_offset(insert_place);
10926 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10927 if (prefs_common.primary_paste_unselects)
10928 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10931 if (!wrap) {
10932 /* paste unwrapped: mark the paste so it's not wrapped later */
10933 end = start + strlen(contents);
10934 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10935 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10936 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10937 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10938 /* rewrap paragraph now (after a mid-click-paste) */
10939 mark_start = gtk_text_buffer_get_insert(buffer);
10940 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10941 gtk_text_iter_backward_char(&start_iter);
10942 compose_beautify_paragraph(compose, &start_iter, TRUE);
10944 } else if (GTK_IS_EDITABLE(entry))
10945 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10947 compose->modified = TRUE;
10950 static void entry_allsel(GtkWidget *entry)
10952 if (GTK_IS_EDITABLE(entry))
10953 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10954 else if (GTK_IS_TEXT_VIEW(entry)) {
10955 GtkTextIter startiter, enditer;
10956 GtkTextBuffer *textbuf;
10958 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10959 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10960 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10962 gtk_text_buffer_move_mark_by_name(textbuf,
10963 "selection_bound", &startiter);
10964 gtk_text_buffer_move_mark_by_name(textbuf,
10965 "insert", &enditer);
10969 static void compose_cut_cb(GtkAction *action, gpointer data)
10971 Compose *compose = (Compose *)data;
10972 if (compose->focused_editable
10973 #ifndef GENERIC_UMPC
10974 && gtk_widget_has_focus(compose->focused_editable)
10975 #endif
10977 entry_cut_clipboard(compose->focused_editable);
10980 static void compose_copy_cb(GtkAction *action, gpointer data)
10982 Compose *compose = (Compose *)data;
10983 if (compose->focused_editable
10984 #ifndef GENERIC_UMPC
10985 && gtk_widget_has_focus(compose->focused_editable)
10986 #endif
10988 entry_copy_clipboard(compose->focused_editable);
10991 static void compose_paste_cb(GtkAction *action, gpointer data)
10993 Compose *compose = (Compose *)data;
10994 gint prev_autowrap;
10995 GtkTextBuffer *buffer;
10996 BLOCK_WRAP();
10997 if (compose->focused_editable
10998 #ifndef GENERIC_UMPC
10999 && gtk_widget_has_focus(compose->focused_editable)
11000 #endif
11002 entry_paste_clipboard(compose, compose->focused_editable,
11003 prefs_common.linewrap_pastes,
11004 GDK_SELECTION_CLIPBOARD, NULL);
11005 UNBLOCK_WRAP();
11007 #ifdef USE_ENCHANT
11008 if (
11009 #ifndef GENERIC_UMPC
11010 gtk_widget_has_focus(compose->text) &&
11011 #endif
11012 compose->gtkaspell &&
11013 compose->gtkaspell->check_while_typing)
11014 gtkaspell_highlight_all(compose->gtkaspell);
11015 #endif
11018 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11020 Compose *compose = (Compose *)data;
11021 gint wrap_quote = prefs_common.linewrap_quote;
11022 if (compose->focused_editable
11023 #ifndef GENERIC_UMPC
11024 && gtk_widget_has_focus(compose->focused_editable)
11025 #endif
11027 /* let text_insert() (called directly or at a later time
11028 * after the gtk_editable_paste_clipboard) know that
11029 * text is to be inserted as a quotation. implemented
11030 * by using a simple refcount... */
11031 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11032 G_OBJECT(compose->focused_editable),
11033 "paste_as_quotation"));
11034 g_object_set_data(G_OBJECT(compose->focused_editable),
11035 "paste_as_quotation",
11036 GINT_TO_POINTER(paste_as_quotation + 1));
11037 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11038 entry_paste_clipboard(compose, compose->focused_editable,
11039 prefs_common.linewrap_pastes,
11040 GDK_SELECTION_CLIPBOARD, NULL);
11041 prefs_common.linewrap_quote = wrap_quote;
11045 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11047 Compose *compose = (Compose *)data;
11048 gint prev_autowrap;
11049 GtkTextBuffer *buffer;
11050 BLOCK_WRAP();
11051 if (compose->focused_editable
11052 #ifndef GENERIC_UMPC
11053 && gtk_widget_has_focus(compose->focused_editable)
11054 #endif
11056 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11057 GDK_SELECTION_CLIPBOARD, NULL);
11058 UNBLOCK_WRAP();
11060 #ifdef USE_ENCHANT
11061 if (
11062 #ifndef GENERIC_UMPC
11063 gtk_widget_has_focus(compose->text) &&
11064 #endif
11065 compose->gtkaspell &&
11066 compose->gtkaspell->check_while_typing)
11067 gtkaspell_highlight_all(compose->gtkaspell);
11068 #endif
11071 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11073 Compose *compose = (Compose *)data;
11074 gint prev_autowrap;
11075 GtkTextBuffer *buffer;
11076 BLOCK_WRAP();
11077 if (compose->focused_editable
11078 #ifndef GENERIC_UMPC
11079 && gtk_widget_has_focus(compose->focused_editable)
11080 #endif
11082 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11083 GDK_SELECTION_CLIPBOARD, NULL);
11084 UNBLOCK_WRAP();
11086 #ifdef USE_ENCHANT
11087 if (
11088 #ifndef GENERIC_UMPC
11089 gtk_widget_has_focus(compose->text) &&
11090 #endif
11091 compose->gtkaspell &&
11092 compose->gtkaspell->check_while_typing)
11093 gtkaspell_highlight_all(compose->gtkaspell);
11094 #endif
11097 static void compose_allsel_cb(GtkAction *action, gpointer data)
11099 Compose *compose = (Compose *)data;
11100 if (compose->focused_editable
11101 #ifndef GENERIC_UMPC
11102 && gtk_widget_has_focus(compose->focused_editable)
11103 #endif
11105 entry_allsel(compose->focused_editable);
11108 static void textview_move_beginning_of_line (GtkTextView *text)
11110 GtkTextBuffer *buffer;
11111 GtkTextMark *mark;
11112 GtkTextIter ins;
11114 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11116 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11117 mark = gtk_text_buffer_get_insert(buffer);
11118 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11119 gtk_text_iter_set_line_offset(&ins, 0);
11120 gtk_text_buffer_place_cursor(buffer, &ins);
11123 static void textview_move_forward_character (GtkTextView *text)
11125 GtkTextBuffer *buffer;
11126 GtkTextMark *mark;
11127 GtkTextIter ins;
11129 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11131 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11132 mark = gtk_text_buffer_get_insert(buffer);
11133 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11134 if (gtk_text_iter_forward_cursor_position(&ins))
11135 gtk_text_buffer_place_cursor(buffer, &ins);
11138 static void textview_move_backward_character (GtkTextView *text)
11140 GtkTextBuffer *buffer;
11141 GtkTextMark *mark;
11142 GtkTextIter ins;
11144 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11146 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11147 mark = gtk_text_buffer_get_insert(buffer);
11148 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11149 if (gtk_text_iter_backward_cursor_position(&ins))
11150 gtk_text_buffer_place_cursor(buffer, &ins);
11153 static void textview_move_forward_word (GtkTextView *text)
11155 GtkTextBuffer *buffer;
11156 GtkTextMark *mark;
11157 GtkTextIter ins;
11158 gint count;
11160 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11162 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11163 mark = gtk_text_buffer_get_insert(buffer);
11164 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11165 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11166 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11167 gtk_text_iter_backward_word_start(&ins);
11168 gtk_text_buffer_place_cursor(buffer, &ins);
11172 static void textview_move_backward_word (GtkTextView *text)
11174 GtkTextBuffer *buffer;
11175 GtkTextMark *mark;
11176 GtkTextIter ins;
11178 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11180 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11181 mark = gtk_text_buffer_get_insert(buffer);
11182 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11183 if (gtk_text_iter_backward_word_starts(&ins, 1))
11184 gtk_text_buffer_place_cursor(buffer, &ins);
11187 static void textview_move_end_of_line (GtkTextView *text)
11189 GtkTextBuffer *buffer;
11190 GtkTextMark *mark;
11191 GtkTextIter ins;
11193 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11195 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11196 mark = gtk_text_buffer_get_insert(buffer);
11197 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11198 if (gtk_text_iter_forward_to_line_end(&ins))
11199 gtk_text_buffer_place_cursor(buffer, &ins);
11202 static void textview_move_next_line (GtkTextView *text)
11204 GtkTextBuffer *buffer;
11205 GtkTextMark *mark;
11206 GtkTextIter ins;
11207 gint offset;
11209 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11211 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11212 mark = gtk_text_buffer_get_insert(buffer);
11213 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11214 offset = gtk_text_iter_get_line_offset(&ins);
11215 if (gtk_text_iter_forward_line(&ins)) {
11216 gtk_text_iter_set_line_offset(&ins, offset);
11217 gtk_text_buffer_place_cursor(buffer, &ins);
11221 static void textview_move_previous_line (GtkTextView *text)
11223 GtkTextBuffer *buffer;
11224 GtkTextMark *mark;
11225 GtkTextIter ins;
11226 gint offset;
11228 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11230 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11231 mark = gtk_text_buffer_get_insert(buffer);
11232 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11233 offset = gtk_text_iter_get_line_offset(&ins);
11234 if (gtk_text_iter_backward_line(&ins)) {
11235 gtk_text_iter_set_line_offset(&ins, offset);
11236 gtk_text_buffer_place_cursor(buffer, &ins);
11240 static void textview_delete_forward_character (GtkTextView *text)
11242 GtkTextBuffer *buffer;
11243 GtkTextMark *mark;
11244 GtkTextIter ins, end_iter;
11246 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11248 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11249 mark = gtk_text_buffer_get_insert(buffer);
11250 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11251 end_iter = ins;
11252 if (gtk_text_iter_forward_char(&end_iter)) {
11253 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11257 static void textview_delete_backward_character (GtkTextView *text)
11259 GtkTextBuffer *buffer;
11260 GtkTextMark *mark;
11261 GtkTextIter ins, end_iter;
11263 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11265 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11266 mark = gtk_text_buffer_get_insert(buffer);
11267 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11268 end_iter = ins;
11269 if (gtk_text_iter_backward_char(&end_iter)) {
11270 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11274 static void textview_delete_forward_word (GtkTextView *text)
11276 GtkTextBuffer *buffer;
11277 GtkTextMark *mark;
11278 GtkTextIter ins, end_iter;
11280 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11282 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11283 mark = gtk_text_buffer_get_insert(buffer);
11284 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11285 end_iter = ins;
11286 if (gtk_text_iter_forward_word_end(&end_iter)) {
11287 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11291 static void textview_delete_backward_word (GtkTextView *text)
11293 GtkTextBuffer *buffer;
11294 GtkTextMark *mark;
11295 GtkTextIter ins, end_iter;
11297 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11299 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11300 mark = gtk_text_buffer_get_insert(buffer);
11301 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11302 end_iter = ins;
11303 if (gtk_text_iter_backward_word_start(&end_iter)) {
11304 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11308 static void textview_delete_line (GtkTextView *text)
11310 GtkTextBuffer *buffer;
11311 GtkTextMark *mark;
11312 GtkTextIter ins, start_iter, end_iter;
11314 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11316 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11317 mark = gtk_text_buffer_get_insert(buffer);
11318 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11320 start_iter = ins;
11321 gtk_text_iter_set_line_offset(&start_iter, 0);
11323 end_iter = ins;
11324 if (gtk_text_iter_ends_line(&end_iter)){
11325 if (!gtk_text_iter_forward_char(&end_iter))
11326 gtk_text_iter_backward_char(&start_iter);
11328 else
11329 gtk_text_iter_forward_to_line_end(&end_iter);
11330 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11333 static void textview_delete_to_line_end (GtkTextView *text)
11335 GtkTextBuffer *buffer;
11336 GtkTextMark *mark;
11337 GtkTextIter ins, end_iter;
11339 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11341 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11342 mark = gtk_text_buffer_get_insert(buffer);
11343 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11344 end_iter = ins;
11345 if (gtk_text_iter_ends_line(&end_iter))
11346 gtk_text_iter_forward_char(&end_iter);
11347 else
11348 gtk_text_iter_forward_to_line_end(&end_iter);
11349 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11352 #define DO_ACTION(name, act) { \
11353 if(!strcmp(name, a_name)) { \
11354 return act; \
11357 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11359 const gchar *a_name = gtk_action_get_name(action);
11360 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11361 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11362 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11363 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11364 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11365 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11366 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11367 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11368 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11369 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11370 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11371 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11372 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11373 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11374 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11377 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11379 Compose *compose = (Compose *)data;
11380 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11381 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11383 action = compose_call_advanced_action_from_path(gaction);
11385 static struct {
11386 void (*do_action) (GtkTextView *text);
11387 } action_table[] = {
11388 {textview_move_beginning_of_line},
11389 {textview_move_forward_character},
11390 {textview_move_backward_character},
11391 {textview_move_forward_word},
11392 {textview_move_backward_word},
11393 {textview_move_end_of_line},
11394 {textview_move_next_line},
11395 {textview_move_previous_line},
11396 {textview_delete_forward_character},
11397 {textview_delete_backward_character},
11398 {textview_delete_forward_word},
11399 {textview_delete_backward_word},
11400 {textview_delete_line},
11401 {textview_delete_to_line_end}
11404 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11406 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11407 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11408 if (action_table[action].do_action)
11409 action_table[action].do_action(text);
11410 else
11411 g_warning("not implemented yet");
11415 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11417 GtkAllocation allocation;
11418 GtkWidget *parent;
11419 gchar *str = NULL;
11421 if (GTK_IS_EDITABLE(widget)) {
11422 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11423 gtk_editable_set_position(GTK_EDITABLE(widget),
11424 strlen(str));
11425 g_free(str);
11426 if ((parent = gtk_widget_get_parent(widget))
11427 && (parent = gtk_widget_get_parent(parent))
11428 && (parent = gtk_widget_get_parent(parent))) {
11429 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11430 gtk_widget_get_allocation(widget, &allocation);
11431 gint y = allocation.y;
11432 gint height = allocation.height;
11433 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11434 (GTK_SCROLLED_WINDOW(parent));
11436 gfloat value = gtk_adjustment_get_value(shown);
11437 gfloat upper = gtk_adjustment_get_upper(shown);
11438 gfloat page_size = gtk_adjustment_get_page_size(shown);
11439 if (y < (int)value) {
11440 gtk_adjustment_set_value(shown, y - 1);
11442 if ((y + height) > ((int)value + (int)page_size)) {
11443 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11444 gtk_adjustment_set_value(shown,
11445 y + height - (int)page_size - 1);
11446 } else {
11447 gtk_adjustment_set_value(shown,
11448 (int)upper - (int)page_size - 1);
11455 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11456 compose->focused_editable = widget;
11458 #ifdef GENERIC_UMPC
11459 if (GTK_IS_TEXT_VIEW(widget)
11460 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11461 g_object_ref(compose->notebook);
11462 g_object_ref(compose->edit_vbox);
11463 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11464 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11465 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11466 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11467 g_object_unref(compose->notebook);
11468 g_object_unref(compose->edit_vbox);
11469 g_signal_handlers_block_by_func(G_OBJECT(widget),
11470 G_CALLBACK(compose_grab_focus_cb),
11471 compose);
11472 gtk_widget_grab_focus(widget);
11473 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11474 G_CALLBACK(compose_grab_focus_cb),
11475 compose);
11476 } else if (!GTK_IS_TEXT_VIEW(widget)
11477 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11478 g_object_ref(compose->notebook);
11479 g_object_ref(compose->edit_vbox);
11480 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11481 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11482 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11483 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11484 g_object_unref(compose->notebook);
11485 g_object_unref(compose->edit_vbox);
11486 g_signal_handlers_block_by_func(G_OBJECT(widget),
11487 G_CALLBACK(compose_grab_focus_cb),
11488 compose);
11489 gtk_widget_grab_focus(widget);
11490 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11491 G_CALLBACK(compose_grab_focus_cb),
11492 compose);
11494 #endif
11497 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11499 compose->modified = TRUE;
11500 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11501 #ifndef GENERIC_UMPC
11502 compose_set_title(compose);
11503 #endif
11506 static void compose_wrap_cb(GtkAction *action, gpointer data)
11508 Compose *compose = (Compose *)data;
11509 compose_beautify_paragraph(compose, NULL, TRUE);
11512 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11514 Compose *compose = (Compose *)data;
11515 compose_wrap_all_full(compose, TRUE);
11518 static void compose_find_cb(GtkAction *action, gpointer data)
11520 Compose *compose = (Compose *)data;
11522 message_search_compose(compose);
11525 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11526 gpointer data)
11528 Compose *compose = (Compose *)data;
11529 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11530 if (compose->autowrap)
11531 compose_wrap_all_full(compose, TRUE);
11532 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11535 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11536 gpointer data)
11538 Compose *compose = (Compose *)data;
11539 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11542 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11544 Compose *compose = (Compose *)data;
11546 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11547 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11550 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11552 Compose *compose = (Compose *)data;
11554 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11555 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11558 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11560 g_free(compose->privacy_system);
11561 g_free(compose->encdata);
11563 compose->privacy_system = g_strdup(account->default_privacy_system);
11564 compose_update_privacy_system_menu_item(compose, warn);
11567 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11569 if (folder_item != NULL) {
11570 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT &&
11571 privacy_system_can_sign(compose->privacy_system)) {
11572 compose_use_signing(compose,
11573 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11575 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT &&
11576 privacy_system_can_encrypt(compose->privacy_system)) {
11577 compose_use_encryption(compose,
11578 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11583 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11585 Compose *compose = (Compose *)data;
11587 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11588 gtk_widget_show(compose->ruler_hbox);
11589 prefs_common.show_ruler = TRUE;
11590 } else {
11591 gtk_widget_hide(compose->ruler_hbox);
11592 gtk_widget_queue_resize(compose->edit_vbox);
11593 prefs_common.show_ruler = FALSE;
11597 static void compose_attach_drag_received_cb (GtkWidget *widget,
11598 GdkDragContext *context,
11599 gint x,
11600 gint y,
11601 GtkSelectionData *data,
11602 guint info,
11603 guint time,
11604 gpointer user_data)
11606 Compose *compose = (Compose *)user_data;
11607 GList *list, *tmp;
11608 GdkAtom type;
11610 type = gtk_selection_data_get_data_type(data);
11611 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11612 && gtk_drag_get_source_widget(context) !=
11613 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11614 list = uri_list_extract_filenames(
11615 (const gchar *)gtk_selection_data_get_data(data));
11616 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11617 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11618 compose_attach_append
11619 (compose, (const gchar *)tmp->data,
11620 utf8_filename, NULL, NULL);
11621 g_free(utf8_filename);
11623 if (list)
11624 compose_changed_cb(NULL, compose);
11625 list_free_strings_full(list);
11626 } else if (gtk_drag_get_source_widget(context)
11627 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11628 /* comes from our summaryview */
11629 SummaryView * summaryview = NULL;
11630 GSList * list = NULL, *cur = NULL;
11632 if (mainwindow_get_mainwindow())
11633 summaryview = mainwindow_get_mainwindow()->summaryview;
11635 if (summaryview)
11636 list = summary_get_selected_msg_list(summaryview);
11638 for (cur = list; cur; cur = cur->next) {
11639 MsgInfo *msginfo = (MsgInfo *)cur->data;
11640 gchar *file = NULL;
11641 if (msginfo)
11642 file = procmsg_get_message_file_full(msginfo,
11643 TRUE, TRUE);
11644 if (file) {
11645 compose_attach_append(compose, (const gchar *)file,
11646 (const gchar *)file, "message/rfc822", NULL);
11647 g_free(file);
11650 g_slist_free(list);
11654 static gboolean compose_drag_drop(GtkWidget *widget,
11655 GdkDragContext *drag_context,
11656 gint x, gint y,
11657 guint time, gpointer user_data)
11659 /* not handling this signal makes compose_insert_drag_received_cb
11660 * called twice */
11661 return TRUE;
11664 static gboolean completion_set_focus_to_subject
11665 (GtkWidget *widget,
11666 GdkEventKey *event,
11667 Compose *compose)
11669 cm_return_val_if_fail(compose != NULL, FALSE);
11671 /* make backtab move to subject field */
11672 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11673 gtk_widget_grab_focus(compose->subject_entry);
11674 return TRUE;
11676 return FALSE;
11679 static void compose_insert_drag_received_cb (GtkWidget *widget,
11680 GdkDragContext *drag_context,
11681 gint x,
11682 gint y,
11683 GtkSelectionData *data,
11684 guint info,
11685 guint time,
11686 gpointer user_data)
11688 Compose *compose = (Compose *)user_data;
11689 GList *list, *tmp;
11690 GdkAtom type;
11691 guint num_files;
11692 gchar *msg;
11694 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11695 * does not work */
11696 type = gtk_selection_data_get_data_type(data);
11697 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11698 AlertValue val = G_ALERTDEFAULT;
11699 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11701 list = uri_list_extract_filenames(ddata);
11702 num_files = g_list_length(list);
11703 if (list == NULL && strstr(ddata, "://")) {
11704 /* Assume a list of no files, and data has ://, is a remote link */
11705 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11706 gchar *tmpfile = get_tmp_file();
11707 str_write_to_file(tmpdata, tmpfile, TRUE);
11708 g_free(tmpdata);
11709 compose_insert_file(compose, tmpfile);
11710 claws_unlink(tmpfile);
11711 g_free(tmpfile);
11712 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11713 compose_beautify_paragraph(compose, NULL, TRUE);
11714 return;
11716 switch (prefs_common.compose_dnd_mode) {
11717 case COMPOSE_DND_ASK:
11718 msg = g_strdup_printf(
11719 ngettext(
11720 "Do you want to insert the contents of the file "
11721 "into the message body, or attach it to the email?",
11722 "Do you want to insert the contents of the %d files "
11723 "into the message body, or attach them to the email?",
11724 num_files),
11725 num_files);
11726 val = alertpanel_full(_("Insert or attach?"), msg,
11727 NULL, _("_Cancel"), NULL, _("_Insert"), NULL, _("_Attach"),
11728 ALERTFOCUS_SECOND, TRUE, NULL, ALERT_QUESTION);
11729 g_free(msg);
11730 break;
11731 case COMPOSE_DND_INSERT:
11732 val = G_ALERTALTERNATE;
11733 break;
11734 case COMPOSE_DND_ATTACH:
11735 val = G_ALERTOTHER;
11736 break;
11737 default:
11738 /* unexpected case */
11739 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11742 if (val & G_ALERTDISABLE) {
11743 val &= ~G_ALERTDISABLE;
11744 /* remember what action to perform by default, only if we don't click Cancel */
11745 if (val == G_ALERTALTERNATE)
11746 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11747 else if (val == G_ALERTOTHER)
11748 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11751 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11752 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11753 list_free_strings_full(list);
11754 return;
11755 } else if (val == G_ALERTOTHER) {
11756 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11757 list_free_strings_full(list);
11758 return;
11761 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11762 compose_insert_file(compose, (const gchar *)tmp->data);
11764 list_free_strings_full(list);
11765 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11766 return;
11770 static void compose_header_drag_received_cb (GtkWidget *widget,
11771 GdkDragContext *drag_context,
11772 gint x,
11773 gint y,
11774 GtkSelectionData *data,
11775 guint info,
11776 guint time,
11777 gpointer user_data)
11779 GtkEditable *entry = (GtkEditable *)user_data;
11780 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11782 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11783 * does not work */
11785 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11786 gchar *decoded=g_new(gchar, strlen(email));
11787 int start = 0;
11789 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11790 gtk_editable_delete_text(entry, 0, -1);
11791 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11792 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11793 g_free(decoded);
11794 return;
11796 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11799 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11801 Compose *compose = (Compose *)data;
11803 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11804 compose->return_receipt = TRUE;
11805 else
11806 compose->return_receipt = FALSE;
11809 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11811 Compose *compose = (Compose *)data;
11813 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11814 compose->remove_references = TRUE;
11815 else
11816 compose->remove_references = FALSE;
11819 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11820 ComposeHeaderEntry *headerentry)
11822 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11823 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11824 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11825 return FALSE;
11828 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11829 GdkEventKey *event,
11830 ComposeHeaderEntry *headerentry)
11832 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11833 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11834 !(event->state & GDK_MODIFIER_MASK) &&
11835 (event->keyval == GDK_KEY_BackSpace) &&
11836 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11837 gtk_container_remove
11838 (GTK_CONTAINER(headerentry->compose->header_table),
11839 headerentry->combo);
11840 gtk_container_remove
11841 (GTK_CONTAINER(headerentry->compose->header_table),
11842 headerentry->entry);
11843 headerentry->compose->header_list =
11844 g_slist_remove(headerentry->compose->header_list,
11845 headerentry);
11846 g_free(headerentry);
11847 } else if (event->keyval == GDK_KEY_Tab) {
11848 if (headerentry->compose->header_last == headerentry) {
11849 /* Override default next focus, and give it to subject_entry
11850 * instead of notebook tabs
11852 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11853 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11854 return TRUE;
11857 return FALSE;
11860 static gboolean scroll_postpone(gpointer data)
11862 Compose *compose = (Compose *)data;
11864 if (compose->batch)
11865 return FALSE;
11867 GTK_EVENTS_FLUSH();
11868 compose_show_first_last_header(compose, FALSE);
11869 return FALSE;
11872 static void compose_headerentry_changed_cb(GtkWidget *entry,
11873 ComposeHeaderEntry *headerentry)
11875 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11876 compose_create_header_entry(headerentry->compose);
11877 g_signal_handlers_disconnect_matched
11878 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11879 0, 0, NULL, NULL, headerentry);
11881 if (!headerentry->compose->batch)
11882 g_timeout_add(0, scroll_postpone, headerentry->compose);
11886 static gboolean compose_defer_auto_save_draft(Compose *compose)
11888 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11889 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11890 return FALSE;
11893 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11895 GtkAdjustment *vadj;
11897 cm_return_if_fail(compose);
11899 if(compose->batch)
11900 return;
11902 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11903 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11904 vadj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(gtk_widget_get_parent(compose->header_table)));
11905 gtk_adjustment_set_value(vadj, (show_first ?
11906 gtk_adjustment_get_lower(vadj) :
11907 (gtk_adjustment_get_upper(vadj) -
11908 gtk_adjustment_get_page_size(vadj))));
11911 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11912 const gchar *text, gint len, Compose *compose)
11914 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11915 (G_OBJECT(compose->text), "paste_as_quotation"));
11916 GtkTextMark *mark;
11918 cm_return_if_fail(text != NULL);
11920 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11921 G_CALLBACK(text_inserted),
11922 compose);
11923 if (paste_as_quotation) {
11924 gchar *new_text;
11925 const gchar *qmark;
11926 guint pos = 0;
11927 GtkTextIter start_iter;
11929 if (len < 0)
11930 len = strlen(text);
11932 new_text = g_strndup(text, len);
11934 qmark = compose_quote_char_from_context(compose);
11936 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11937 gtk_text_buffer_place_cursor(buffer, iter);
11939 pos = gtk_text_iter_get_offset(iter);
11941 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11942 _("Quote format error at line %d."));
11943 quote_fmt_reset_vartable();
11944 g_free(new_text);
11945 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11946 GINT_TO_POINTER(paste_as_quotation - 1));
11948 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11949 gtk_text_buffer_place_cursor(buffer, iter);
11950 gtk_text_buffer_delete_mark(buffer, mark);
11952 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11953 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11954 compose_beautify_paragraph(compose, &start_iter, FALSE);
11955 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11956 gtk_text_buffer_delete_mark(buffer, mark);
11957 } else {
11958 if (strcmp(text, "\n") || compose->automatic_break
11959 || gtk_text_iter_starts_line(iter)) {
11960 GtkTextIter before_ins;
11961 gtk_text_buffer_insert(buffer, iter, text, len);
11962 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11963 before_ins = *iter;
11964 gtk_text_iter_backward_chars(&before_ins, len);
11965 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11967 } else {
11968 /* check if the preceding is just whitespace or quote */
11969 GtkTextIter start_line;
11970 gchar *tmp = NULL, *quote = NULL;
11971 gint quote_len = 0, is_normal = 0;
11972 start_line = *iter;
11973 gtk_text_iter_set_line_offset(&start_line, 0);
11974 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11975 g_strstrip(tmp);
11977 if (*tmp == '\0') {
11978 is_normal = 1;
11979 } else {
11980 quote = compose_get_quote_str(buffer, &start_line, &quote_len);
11981 if (quote)
11982 is_normal = 1;
11983 g_free(quote);
11985 g_free(tmp);
11987 if (is_normal) {
11988 gtk_text_buffer_insert(buffer, iter, text, len);
11989 } else {
11990 gtk_text_buffer_insert_with_tags_by_name(buffer,
11991 iter, text, len, "no_join", NULL);
11996 if (!paste_as_quotation) {
11997 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11998 compose_beautify_paragraph(compose, iter, FALSE);
11999 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12000 gtk_text_buffer_delete_mark(buffer, mark);
12003 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
12004 G_CALLBACK(text_inserted),
12005 compose);
12006 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
12008 if (compose_can_autosave(compose) &&
12009 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
12010 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
12011 compose->draft_timeout_tag = g_timeout_add
12012 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
12015 #if USE_ENCHANT
12016 static void compose_check_all(GtkAction *action, gpointer data)
12018 Compose *compose = (Compose *)data;
12019 if (!compose->gtkaspell)
12020 return;
12022 if (gtk_widget_has_focus(compose->subject_entry))
12023 claws_spell_entry_check_all(
12024 CLAWS_SPELL_ENTRY(compose->subject_entry));
12025 else
12026 gtkaspell_check_all(compose->gtkaspell);
12029 static void compose_highlight_all(GtkAction *action, gpointer data)
12031 Compose *compose = (Compose *)data;
12032 if (compose->gtkaspell) {
12033 claws_spell_entry_recheck_all(
12034 CLAWS_SPELL_ENTRY(compose->subject_entry));
12035 gtkaspell_highlight_all(compose->gtkaspell);
12039 static void compose_check_backwards(GtkAction *action, gpointer data)
12041 Compose *compose = (Compose *)data;
12042 if (!compose->gtkaspell) {
12043 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12044 return;
12047 if (gtk_widget_has_focus(compose->subject_entry))
12048 claws_spell_entry_check_backwards(
12049 CLAWS_SPELL_ENTRY(compose->subject_entry));
12050 else
12051 gtkaspell_check_backwards(compose->gtkaspell);
12054 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12056 Compose *compose = (Compose *)data;
12057 if (!compose->gtkaspell) {
12058 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12059 return;
12062 if (gtk_widget_has_focus(compose->subject_entry))
12063 claws_spell_entry_check_forwards_go(
12064 CLAWS_SPELL_ENTRY(compose->subject_entry));
12065 else
12066 gtkaspell_check_forwards_go(compose->gtkaspell);
12068 #endif
12071 *\brief Guess originating forward account from MsgInfo and several
12072 * "common preference" settings. Return NULL if no guess.
12074 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12076 PrefsAccount *account = NULL;
12078 cm_return_val_if_fail(msginfo, NULL);
12079 cm_return_val_if_fail(msginfo->folder, NULL);
12080 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12082 if (msginfo->folder->prefs->enable_default_account)
12083 account = account_find_from_id(msginfo->folder->prefs->default_account);
12085 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12086 gchar *to;
12087 Xstrdup_a(to, msginfo->to, return NULL);
12088 extract_address(to);
12089 account = account_find_from_address(to, FALSE);
12092 if (!account && prefs_common.forward_account_autosel) {
12093 gchar *cc = NULL;
12094 if (!procheader_get_header_from_msginfo
12095 (msginfo, &cc, "Cc:")) {
12096 gchar *buf = cc + strlen("Cc:");
12097 extract_address(buf);
12098 account = account_find_from_address(buf, FALSE);
12099 g_free(cc);
12103 if (!account && prefs_common.forward_account_autosel) {
12104 gchar *deliveredto = NULL;
12105 if (!procheader_get_header_from_msginfo
12106 (msginfo, &deliveredto, "Delivered-To:")) {
12107 gchar *buf = deliveredto + strlen("Delivered-To:");
12108 extract_address(buf);
12109 account = account_find_from_address(buf, FALSE);
12110 g_free(deliveredto);
12114 if (!account)
12115 account = msginfo->folder->folder->account;
12117 return account;
12120 gboolean compose_close(Compose *compose)
12122 gint x, y;
12124 cm_return_val_if_fail(compose, FALSE);
12126 if (!g_mutex_trylock(&compose->mutex)) {
12127 /* we have to wait for the (possibly deferred by auto-save)
12128 * drafting to be done, before destroying the compose under
12129 * it. */
12130 debug_print("waiting for drafting to finish...\n");
12131 compose_allow_user_actions(compose, FALSE);
12132 if (compose->close_timeout_tag == 0) {
12133 compose->close_timeout_tag =
12134 g_timeout_add (500, (GSourceFunc) compose_close,
12135 compose);
12137 return TRUE;
12140 if (compose->draft_timeout_tag >= 0) {
12141 g_source_remove(compose->draft_timeout_tag);
12142 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12145 gtk_window_get_position(GTK_WINDOW(compose->window), &x, &y);
12146 if (!compose->batch) {
12147 prefs_common.compose_x = x;
12148 prefs_common.compose_y = y;
12150 g_mutex_unlock(&compose->mutex);
12151 compose_destroy(compose);
12152 return FALSE;
12156 * Add entry field for each address in list.
12157 * \param compose E-Mail composition object.
12158 * \param listAddress List of (formatted) E-Mail addresses.
12160 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12161 GList *node;
12162 gchar *addr;
12163 node = listAddress;
12164 while( node ) {
12165 addr = ( gchar * ) node->data;
12166 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12167 node = g_list_next( node );
12171 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12172 guint action, gboolean opening_multiple)
12174 gchar *body = NULL;
12175 GSList *new_msglist = NULL;
12176 MsgInfo *tmp_msginfo = NULL;
12177 gboolean originally_enc = FALSE;
12178 gboolean originally_sig = FALSE;
12179 Compose *compose = NULL;
12180 gchar *s_system = NULL;
12182 cm_return_if_fail(msginfo_list != NULL);
12184 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12185 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12186 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12188 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12189 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12190 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12191 orig_msginfo, mimeinfo);
12192 if (tmp_msginfo != NULL) {
12193 new_msglist = g_slist_append(NULL, tmp_msginfo);
12195 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12196 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12197 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12199 tmp_msginfo->folder = orig_msginfo->folder;
12200 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12201 if (orig_msginfo->tags) {
12202 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12203 tmp_msginfo->folder->tags_dirty = TRUE;
12209 if (!opening_multiple && msgview != NULL)
12210 body = messageview_get_selection(msgview);
12212 if (new_msglist) {
12213 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12214 procmsg_msginfo_free(&tmp_msginfo);
12215 g_slist_free(new_msglist);
12216 } else
12217 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12219 if (compose && originally_enc) {
12220 compose_force_encryption(compose, compose->account, FALSE, s_system);
12223 if (compose && originally_sig && compose->account->default_sign_reply) {
12224 compose_force_signing(compose, compose->account, s_system);
12226 g_free(s_system);
12227 g_free(body);
12228 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12231 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12232 guint action)
12234 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12235 && msginfo_list != NULL
12236 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12237 GSList *cur = msginfo_list;
12238 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12239 "messages. Opening the windows "
12240 "could take some time. Do you "
12241 "want to continue?"),
12242 g_slist_length(msginfo_list));
12243 if (g_slist_length(msginfo_list) > 9
12244 && alertpanel(_("Warning"), msg, NULL, _("_Cancel"), NULL, _("_Yes"),
12245 NULL, NULL, ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12246 g_free(msg);
12247 return;
12249 g_free(msg);
12250 /* We'll open multiple compose windows */
12251 /* let the WM place the next windows */
12252 compose_force_window_origin = FALSE;
12253 for (; cur; cur = cur->next) {
12254 GSList tmplist;
12255 tmplist.data = cur->data;
12256 tmplist.next = NULL;
12257 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12259 compose_force_window_origin = TRUE;
12260 } else {
12261 /* forwarding multiple mails as attachments is done via a
12262 * single compose window */
12263 if (msginfo_list != NULL) {
12264 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12265 } else if (msgview != NULL) {
12266 GSList tmplist;
12267 tmplist.data = msgview->msginfo;
12268 tmplist.next = NULL;
12269 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12270 } else {
12271 debug_print("Nothing to reply to\n");
12276 void compose_check_for_email_account(Compose *compose)
12278 PrefsAccount *ac = NULL, *curr = NULL;
12279 GList *list;
12281 if (!compose)
12282 return;
12284 if (compose->account && compose->account->protocol == A_NNTP) {
12285 ac = account_get_cur_account();
12286 if (ac->protocol == A_NNTP) {
12287 list = account_get_list();
12289 for( ; list != NULL ; list = g_list_next(list)) {
12290 curr = (PrefsAccount *) list->data;
12291 if (curr->protocol != A_NNTP) {
12292 ac = curr;
12293 break;
12297 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12298 ac->account_id);
12302 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12303 const gchar *address)
12305 GSList *msginfo_list = NULL;
12306 gchar *body = messageview_get_selection(msgview);
12307 Compose *compose;
12309 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12311 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12312 compose_check_for_email_account(compose);
12313 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12314 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12315 compose_reply_set_subject(compose, msginfo);
12317 g_free(body);
12318 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12321 void compose_set_position(Compose *compose, gint pos)
12323 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12325 gtkut_text_view_set_position(text, pos);
12328 gboolean compose_search_string(Compose *compose,
12329 const gchar *str, gboolean case_sens)
12331 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12333 return gtkut_text_view_search_string(text, str, case_sens);
12336 gboolean compose_search_string_backward(Compose *compose,
12337 const gchar *str, gboolean case_sens)
12339 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12341 return gtkut_text_view_search_string_backward(text, str, case_sens);
12344 /* allocate a msginfo structure and populate its data from a compose data structure */
12345 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12347 MsgInfo *newmsginfo;
12348 GSList *list;
12349 gchar date[RFC822_DATE_BUFFSIZE];
12351 cm_return_val_if_fail( compose != NULL, NULL );
12353 newmsginfo = procmsg_msginfo_new();
12355 /* date is now */
12356 get_rfc822_date(date, sizeof(date));
12357 newmsginfo->date = g_strdup(date);
12359 /* from */
12360 if (compose->from_name) {
12361 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12362 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12365 /* subject */
12366 if (compose->subject_entry)
12367 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12369 /* to, cc, reply-to, newsgroups */
12370 for (list = compose->header_list; list; list = list->next) {
12371 gchar *header = gtk_editable_get_chars(
12372 GTK_EDITABLE(
12373 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12374 gchar *entry = gtk_editable_get_chars(
12375 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12377 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12378 if ( newmsginfo->to == NULL ) {
12379 newmsginfo->to = g_strdup(entry);
12380 } else if (entry && *entry) {
12381 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12382 g_free(newmsginfo->to);
12383 newmsginfo->to = tmp;
12385 } else
12386 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12387 if ( newmsginfo->cc == NULL ) {
12388 newmsginfo->cc = g_strdup(entry);
12389 } else if (entry && *entry) {
12390 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12391 g_free(newmsginfo->cc);
12392 newmsginfo->cc = tmp;
12394 } else
12395 if ( strcasecmp(header,
12396 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12397 if ( newmsginfo->newsgroups == NULL ) {
12398 newmsginfo->newsgroups = g_strdup(entry);
12399 } else if (entry && *entry) {
12400 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12401 g_free(newmsginfo->newsgroups);
12402 newmsginfo->newsgroups = tmp;
12406 g_free(header);
12407 g_free(entry);
12410 /* other data is unset */
12412 return newmsginfo;
12415 #ifdef USE_ENCHANT
12416 /* update compose's dictionaries from folder dict settings */
12417 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12418 FolderItem *folder_item)
12420 cm_return_if_fail(compose != NULL);
12422 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12423 FolderItemPrefs *prefs = folder_item->prefs;
12425 if (prefs->enable_default_dictionary)
12426 gtkaspell_change_dict(compose->gtkaspell,
12427 prefs->default_dictionary, FALSE);
12428 if (folder_item->prefs->enable_default_alt_dictionary)
12429 gtkaspell_change_alt_dict(compose->gtkaspell,
12430 prefs->default_alt_dictionary);
12431 if (prefs->enable_default_dictionary
12432 || prefs->enable_default_alt_dictionary)
12433 compose_spell_menu_changed(compose);
12436 #endif
12438 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12440 Compose *compose = (Compose *)data;
12442 cm_return_if_fail(compose != NULL);
12444 gtk_widget_grab_focus(compose->text);
12447 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12449 gtk_combo_box_popup(GTK_COMBO_BOX(data));
12454 * End of Source.