when user cancels the signing passphrase dialogue, don't bother the user with an...
[claws.git] / src / compose.c
blob7a0ab8302a33b15803b033a8af0deeb5e39a1d1b
1 /*
2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2019 the Claws Mail team and Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #include "claws-features.h"
22 #endif
24 #include "defs.h"
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
28 #endif
30 #include <glib.h>
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtk.h>
35 #include <pango/pango-break.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <ctype.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <unistd.h>
43 #include <time.h>
44 #include <stdlib.h>
45 #if HAVE_SYS_WAIT_H
46 # include <sys/wait.h>
47 #endif
48 #include <signal.h>
49 #include <errno.h>
50 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
51 #include <libgen.h>
52 #endif
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
55 # include <wchar.h>
56 # include <wctype.h>
57 #endif
59 #include "claws.h"
60 #include "main.h"
61 #include "mainwindow.h"
62 #include "compose.h"
63 #ifndef USE_ALT_ADDRBOOK
64 #include "addressbook.h"
65 #else
66 #include "addressbook-dbus.h"
67 #include "addressadd.h"
68 #endif
69 #include "folderview.h"
70 #include "procmsg.h"
71 #include "menu.h"
72 #include "stock_pixmap.h"
73 #include "send_message.h"
74 #include "imap.h"
75 #include "news.h"
76 #include "customheader.h"
77 #include "prefs_common.h"
78 #include "prefs_account.h"
79 #include "action.h"
80 #include "account.h"
81 #include "filesel.h"
82 #include "procheader.h"
83 #include "procmime.h"
84 #include "statusbar.h"
85 #include "about.h"
86 #include "quoted-printable.h"
87 #include "codeconv.h"
88 #include "utils.h"
89 #include "gtkutils.h"
90 #include "gtkshruler.h"
91 #include "socket.h"
92 #include "alertpanel.h"
93 #include "manage_window.h"
94 #include "folder.h"
95 #include "folder_item_prefs.h"
96 #include "addr_compl.h"
97 #include "quote_fmt.h"
98 #include "undo.h"
99 #include "foldersel.h"
100 #include "toolbar.h"
101 #include "inc.h"
102 #include "message_search.h"
103 #include "combobox.h"
104 #include "hooks.h"
105 #include "privacy.h"
106 #include "timing.h"
107 #include "autofaces.h"
108 #include "spell_entry.h"
109 #include "headers.h"
110 #include "file-utils.h"
112 #ifdef USE_LDAP
113 #include "password.h"
114 #include "ldapserver.h"
115 #endif
117 enum
119 COL_MIMETYPE = 0,
120 COL_SIZE = 1,
121 COL_NAME = 2,
122 COL_CHARSET = 3,
123 COL_DATA = 4,
124 COL_AUTODATA = 5,
125 N_COL_COLUMNS
128 #define N_ATTACH_COLS (N_COL_COLUMNS)
130 typedef enum
132 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
135 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
137 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
139 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
141 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
142 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
143 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
144 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
145 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
146 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
147 } ComposeCallAdvancedAction;
149 typedef enum
151 PRIORITY_HIGHEST = 1,
152 PRIORITY_HIGH,
153 PRIORITY_NORMAL,
154 PRIORITY_LOW,
155 PRIORITY_LOWEST
156 } PriorityLevel;
158 typedef enum
160 COMPOSE_INSERT_SUCCESS,
161 COMPOSE_INSERT_READ_ERROR,
162 COMPOSE_INSERT_INVALID_CHARACTER,
163 COMPOSE_INSERT_NO_FILE
164 } ComposeInsertResult;
166 typedef enum
168 COMPOSE_WRITE_FOR_SEND,
169 COMPOSE_WRITE_FOR_STORE
170 } ComposeWriteType;
172 typedef enum
174 COMPOSE_QUOTE_FORCED,
175 COMPOSE_QUOTE_CHECK,
176 COMPOSE_QUOTE_SKIP
177 } ComposeQuoteMode;
179 typedef enum {
180 TO_FIELD_PRESENT,
181 SUBJECT_FIELD_PRESENT,
182 BODY_FIELD_PRESENT,
183 NO_FIELD_PRESENT
184 } MailField;
186 #define B64_LINE_SIZE 57
187 #define B64_BUFFSIZE 77
189 #define MAX_REFERENCES_LEN 999
191 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
192 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
194 static GdkColor default_header_bgcolor = {
195 (gulong)0,
196 (gushort)0,
197 (gushort)0,
198 (gushort)0
201 static GdkColor default_header_color = {
202 (gulong)0,
203 (gushort)0,
204 (gushort)0,
205 (gushort)0
208 static GList *compose_list = NULL;
209 static GSList *extra_headers = NULL;
211 static Compose *compose_generic_new (PrefsAccount *account,
212 const gchar *to,
213 FolderItem *item,
214 GList *attach_files,
215 GList *listAddress );
217 static Compose *compose_create (PrefsAccount *account,
218 FolderItem *item,
219 ComposeMode mode,
220 gboolean batch);
222 static void compose_entry_indicate (Compose *compose,
223 const gchar *address);
224 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
225 ComposeQuoteMode quote_mode,
226 gboolean to_all,
227 gboolean to_sender,
228 const gchar *body);
229 static Compose *compose_forward_multiple (PrefsAccount *account,
230 GSList *msginfo_list);
231 static Compose *compose_reply (MsgInfo *msginfo,
232 ComposeQuoteMode quote_mode,
233 gboolean to_all,
234 gboolean to_ml,
235 gboolean to_sender,
236 const gchar *body);
237 static Compose *compose_reply_mode (ComposeMode mode,
238 GSList *msginfo_list,
239 gchar *body);
240 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
241 static void compose_update_privacy_systems_menu(Compose *compose);
243 static GtkWidget *compose_account_option_menu_create
244 (Compose *compose);
245 static void compose_set_out_encoding (Compose *compose);
246 static void compose_set_template_menu (Compose *compose);
247 static void compose_destroy (Compose *compose);
249 static MailField compose_entries_set (Compose *compose,
250 const gchar *mailto,
251 ComposeEntryType to_type);
252 static gint compose_parse_header (Compose *compose,
253 MsgInfo *msginfo);
254 static gint compose_parse_manual_headers (Compose *compose,
255 MsgInfo *msginfo,
256 HeaderEntry *entries);
257 static gchar *compose_parse_references (const gchar *ref,
258 const gchar *msgid);
260 static gchar *compose_quote_fmt (Compose *compose,
261 MsgInfo *msginfo,
262 const gchar *fmt,
263 const gchar *qmark,
264 const gchar *body,
265 gboolean rewrap,
266 gboolean need_unescape,
267 const gchar *err_msg);
269 static void compose_reply_set_entry (Compose *compose,
270 MsgInfo *msginfo,
271 gboolean to_all,
272 gboolean to_ml,
273 gboolean to_sender,
274 gboolean
275 followup_and_reply_to);
276 static void compose_reedit_set_entry (Compose *compose,
277 MsgInfo *msginfo);
279 static void compose_insert_sig (Compose *compose,
280 gboolean replace);
281 static ComposeInsertResult compose_insert_file (Compose *compose,
282 const gchar *file);
284 static gboolean compose_attach_append (Compose *compose,
285 const gchar *file,
286 const gchar *type,
287 const gchar *content_type,
288 const gchar *charset);
289 static void compose_attach_parts (Compose *compose,
290 MsgInfo *msginfo);
292 static gboolean compose_beautify_paragraph (Compose *compose,
293 GtkTextIter *par_iter,
294 gboolean force);
295 static void compose_wrap_all (Compose *compose);
296 static void compose_wrap_all_full (Compose *compose,
297 gboolean autowrap);
299 static void compose_set_title (Compose *compose);
300 static void compose_select_account (Compose *compose,
301 PrefsAccount *account,
302 gboolean init);
304 static PrefsAccount *compose_current_mail_account(void);
305 /* static gint compose_send (Compose *compose); */
306 static gboolean compose_check_for_valid_recipient
307 (Compose *compose);
308 static gboolean compose_check_entries (Compose *compose,
309 gboolean check_everything);
310 static gint compose_write_to_file (Compose *compose,
311 FILE *fp,
312 gint action,
313 gboolean attach_parts);
314 static gint compose_write_body_to_file (Compose *compose,
315 const gchar *file);
316 static gint compose_remove_reedit_target (Compose *compose,
317 gboolean force);
318 static void compose_remove_draft (Compose *compose);
319 static ComposeQueueResult compose_queue_sub (Compose *compose,
320 gint *msgnum,
321 FolderItem **item,
322 gchar **msgpath,
323 gboolean perform_checks,
324 gboolean remove_reedit_target);
325 static int compose_add_attachments (Compose *compose,
326 MimeInfo *parent);
327 static gchar *compose_get_header (Compose *compose);
328 static gchar *compose_get_manual_headers_info (Compose *compose);
330 static void compose_convert_header (Compose *compose,
331 gchar *dest,
332 gint len,
333 gchar *src,
334 gint header_len,
335 gboolean addr_field);
337 static void compose_attach_info_free (AttachInfo *ainfo);
338 static void compose_attach_remove_selected (GtkAction *action,
339 gpointer data);
341 static void compose_template_apply (Compose *compose,
342 Template *tmpl,
343 gboolean replace);
344 static void compose_attach_property (GtkAction *action,
345 gpointer data);
346 static void compose_attach_property_create (gboolean *cancelled);
347 static void attach_property_ok (GtkWidget *widget,
348 gboolean *cancelled);
349 static void attach_property_cancel (GtkWidget *widget,
350 gboolean *cancelled);
351 static gint attach_property_delete_event (GtkWidget *widget,
352 GdkEventAny *event,
353 gboolean *cancelled);
354 static gboolean attach_property_key_pressed (GtkWidget *widget,
355 GdkEventKey *event,
356 gboolean *cancelled);
358 static void compose_exec_ext_editor (Compose *compose);
359 #ifdef G_OS_UNIX
360 static gint compose_exec_ext_editor_real (const gchar *file,
361 GdkNativeWindow socket_wid);
362 static gboolean compose_ext_editor_kill (Compose *compose);
363 static gboolean compose_input_cb (GIOChannel *source,
364 GIOCondition condition,
365 gpointer data);
366 static void compose_set_ext_editor_sensitive (Compose *compose,
367 gboolean sensitive);
368 static gboolean compose_get_ext_editor_cmd_valid();
369 static gboolean compose_get_ext_editor_uses_socket();
370 static gboolean compose_ext_editor_plug_removed_cb
371 (GtkSocket *socket,
372 Compose *compose);
373 #endif /* G_OS_UNIX */
375 static void compose_undo_state_changed (UndoMain *undostruct,
376 gint undo_state,
377 gint redo_state,
378 gpointer data);
380 static void compose_create_header_entry (Compose *compose);
381 static void compose_add_header_entry (Compose *compose, const gchar *header,
382 gchar *text, ComposePrefType pref_type);
383 static void compose_remove_header_entries(Compose *compose);
385 static void compose_update_priority_menu_item(Compose * compose);
386 #if USE_ENCHANT
387 static void compose_spell_menu_changed (void *data);
388 static void compose_dict_changed (void *data);
389 #endif
390 static void compose_add_field_list ( Compose *compose,
391 GList *listAddress );
393 /* callback functions */
395 static void compose_notebook_size_alloc (GtkNotebook *notebook,
396 GtkAllocation *allocation,
397 GtkPaned *paned);
398 static gboolean compose_edit_size_alloc (GtkEditable *widget,
399 GtkAllocation *allocation,
400 GtkSHRuler *shruler);
401 static void account_activated (GtkComboBox *optmenu,
402 gpointer data);
403 static void attach_selected (GtkTreeView *tree_view,
404 GtkTreePath *tree_path,
405 GtkTreeViewColumn *column,
406 Compose *compose);
407 static gboolean attach_button_pressed (GtkWidget *widget,
408 GdkEventButton *event,
409 gpointer data);
410 static gboolean attach_key_pressed (GtkWidget *widget,
411 GdkEventKey *event,
412 gpointer data);
413 static void compose_send_cb (GtkAction *action, gpointer data);
414 static void compose_send_later_cb (GtkAction *action, gpointer data);
416 static void compose_save_cb (GtkAction *action,
417 gpointer data);
419 static void compose_attach_cb (GtkAction *action,
420 gpointer data);
421 static void compose_insert_file_cb (GtkAction *action,
422 gpointer data);
423 static void compose_insert_sig_cb (GtkAction *action,
424 gpointer data);
425 static void compose_replace_sig_cb (GtkAction *action,
426 gpointer data);
428 static void compose_close_cb (GtkAction *action,
429 gpointer data);
430 static void compose_print_cb (GtkAction *action,
431 gpointer data);
433 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
435 static void compose_address_cb (GtkAction *action,
436 gpointer data);
437 static void about_show_cb (GtkAction *action,
438 gpointer data);
439 static void compose_template_activate_cb(GtkWidget *widget,
440 gpointer data);
442 static void compose_ext_editor_cb (GtkAction *action,
443 gpointer data);
445 static gint compose_delete_cb (GtkWidget *widget,
446 GdkEventAny *event,
447 gpointer data);
449 static void compose_undo_cb (GtkAction *action,
450 gpointer data);
451 static void compose_redo_cb (GtkAction *action,
452 gpointer data);
453 static void compose_cut_cb (GtkAction *action,
454 gpointer data);
455 static void compose_copy_cb (GtkAction *action,
456 gpointer data);
457 static void compose_paste_cb (GtkAction *action,
458 gpointer data);
459 static void compose_paste_as_quote_cb (GtkAction *action,
460 gpointer data);
461 static void compose_paste_no_wrap_cb (GtkAction *action,
462 gpointer data);
463 static void compose_paste_wrap_cb (GtkAction *action,
464 gpointer data);
465 static void compose_allsel_cb (GtkAction *action,
466 gpointer data);
468 static void compose_advanced_action_cb (GtkAction *action,
469 gpointer data);
471 static void compose_grab_focus_cb (GtkWidget *widget,
472 Compose *compose);
474 static void compose_changed_cb (GtkTextBuffer *textbuf,
475 Compose *compose);
477 static void compose_wrap_cb (GtkAction *action,
478 gpointer data);
479 static void compose_wrap_all_cb (GtkAction *action,
480 gpointer data);
481 static void compose_find_cb (GtkAction *action,
482 gpointer data);
483 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
484 gpointer data);
485 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
486 gpointer data);
488 static void compose_toggle_ruler_cb (GtkToggleAction *action,
489 gpointer data);
490 static void compose_toggle_sign_cb (GtkToggleAction *action,
491 gpointer data);
492 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
493 gpointer data);
494 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
495 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
496 static void compose_activate_privacy_system (Compose *compose,
497 PrefsAccount *account,
498 gboolean warn);
499 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item);
500 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
501 gpointer data);
502 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
503 gpointer data);
504 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
505 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
506 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
508 static void compose_attach_drag_received_cb (GtkWidget *widget,
509 GdkDragContext *drag_context,
510 gint x,
511 gint y,
512 GtkSelectionData *data,
513 guint info,
514 guint time,
515 gpointer user_data);
516 static void compose_insert_drag_received_cb (GtkWidget *widget,
517 GdkDragContext *drag_context,
518 gint x,
519 gint y,
520 GtkSelectionData *data,
521 guint info,
522 guint time,
523 gpointer user_data);
524 static void compose_header_drag_received_cb (GtkWidget *widget,
525 GdkDragContext *drag_context,
526 gint x,
527 gint y,
528 GtkSelectionData *data,
529 guint info,
530 guint time,
531 gpointer user_data);
533 static gboolean compose_drag_drop (GtkWidget *widget,
534 GdkDragContext *drag_context,
535 gint x, gint y,
536 guint time, gpointer user_data);
537 static gboolean completion_set_focus_to_subject
538 (GtkWidget *widget,
539 GdkEventKey *event,
540 Compose *user_data);
542 static void text_inserted (GtkTextBuffer *buffer,
543 GtkTextIter *iter,
544 const gchar *text,
545 gint len,
546 Compose *compose);
547 static Compose *compose_generic_reply(MsgInfo *msginfo,
548 ComposeQuoteMode quote_mode,
549 gboolean to_all,
550 gboolean to_ml,
551 gboolean to_sender,
552 gboolean followup_and_reply_to,
553 const gchar *body);
555 static void compose_headerentry_changed_cb (GtkWidget *entry,
556 ComposeHeaderEntry *headerentry);
557 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
558 GdkEventKey *event,
559 ComposeHeaderEntry *headerentry);
560 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
561 ComposeHeaderEntry *headerentry);
563 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
565 static void compose_allow_user_actions (Compose *compose, gboolean allow);
567 static void compose_nothing_cb (GtkAction *action, gpointer data)
572 #if USE_ENCHANT
573 static void compose_check_all (GtkAction *action, gpointer data);
574 static void compose_highlight_all (GtkAction *action, gpointer data);
575 static void compose_check_backwards (GtkAction *action, gpointer data);
576 static void compose_check_forwards_go (GtkAction *action, gpointer data);
577 #endif
579 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
581 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
583 #ifdef USE_ENCHANT
584 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
585 FolderItem *folder_item);
586 #endif
587 static void compose_attach_update_label(Compose *compose);
588 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
589 gboolean respect_default_to);
590 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
591 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
593 static GtkActionEntry compose_popup_entries[] =
595 {"Compose", NULL, "Compose", NULL, NULL, NULL },
596 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
597 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
598 {"Compose/---", NULL, "---", NULL, NULL, NULL },
599 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
602 static GtkActionEntry compose_entries[] =
604 {"Menu", NULL, "Menu", NULL, NULL, NULL },
605 /* menus */
606 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
607 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
608 #if USE_ENCHANT
609 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
610 #endif
611 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
612 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
613 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
614 /* Message menu */
615 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
616 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
617 {"Message/---", NULL, "---", NULL, NULL, NULL },
619 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
620 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
621 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
622 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
623 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
624 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
625 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
626 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
627 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
628 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
630 /* Edit menu */
631 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
632 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
633 {"Edit/---", NULL, "---", NULL, NULL, NULL },
635 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
636 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
637 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
639 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
640 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
641 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
642 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
644 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
646 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
647 {"Edit/Advanced/BackChar", NULL, N_("Move a character backward"), "<shift><control>B", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
648 {"Edit/Advanced/ForwChar", NULL, N_("Move a character forward"), "<shift><control>F", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
649 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
650 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
651 {"Edit/Advanced/BegLine", NULL, N_("Move to beginning of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
652 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
653 {"Edit/Advanced/PrevLine", NULL, N_("Move to previous line"), "<control>P", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
654 {"Edit/Advanced/NextLine", NULL, N_("Move to next line"), "<control>N", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
655 {"Edit/Advanced/DelBackChar", NULL, N_("Delete a character backward"), "<control>H", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
656 {"Edit/Advanced/DelForwChar", NULL, N_("Delete a character forward"), "<control>D", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
657 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
658 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
659 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
660 {"Edit/Advanced/DelEndLine", NULL, N_("Delete to end of line"), "<control>K", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
662 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
663 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
665 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
666 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
667 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
668 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
669 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
670 #if USE_ENCHANT
671 /* Spelling menu */
672 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
673 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
674 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
675 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
677 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
678 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
679 #endif
681 /* Options menu */
682 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
683 {"Options/---", NULL, "---", NULL, NULL, NULL },
684 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
685 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
687 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
688 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
690 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
691 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
692 #define ENC_ACTION(cs_char,c_char,string) \
693 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
695 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
696 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
697 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
698 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
699 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
700 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
701 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
702 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
703 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
705 /* Tools menu */
706 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
708 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
709 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
710 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
711 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
713 /* Help menu */
714 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
717 static GtkToggleActionEntry compose_toggle_entries[] =
719 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
720 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
721 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
722 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
723 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
724 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
725 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
728 static GtkRadioActionEntry compose_radio_rm_entries[] =
730 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
731 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
732 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
733 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
736 static GtkRadioActionEntry compose_radio_prio_entries[] =
738 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
739 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
740 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
741 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
742 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
745 static GtkRadioActionEntry compose_radio_enc_entries[] =
747 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
777 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
778 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
779 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
782 static GtkTargetEntry compose_mime_types[] =
784 {"text/uri-list", 0, 0},
785 {"UTF8_STRING", 0, 0},
786 {"text/plain", 0, 0}
789 static gboolean compose_put_existing_to_front(MsgInfo *info)
791 const GList *compose_list = compose_get_compose_list();
792 const GList *elem = NULL;
794 if (compose_list) {
795 for (elem = compose_list; elem != NULL && elem->data != NULL;
796 elem = elem->next) {
797 Compose *c = (Compose*)elem->data;
799 if (!c->targetinfo || !c->targetinfo->msgid ||
800 !info->msgid)
801 continue;
803 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
804 gtkut_window_popup(c->window);
805 return TRUE;
809 return FALSE;
812 static GdkColor quote_color1 =
813 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
814 static GdkColor quote_color2 =
815 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
816 static GdkColor quote_color3 =
817 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
819 static GdkColor quote_bgcolor1 =
820 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
821 static GdkColor quote_bgcolor2 =
822 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
823 static GdkColor quote_bgcolor3 =
824 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
826 static GdkColor signature_color = {
827 (gulong)0,
828 (gushort)0x7fff,
829 (gushort)0x7fff,
830 (gushort)0x7fff
833 static GdkColor uri_color = {
834 (gulong)0,
835 (gushort)0,
836 (gushort)0,
837 (gushort)0
840 static void compose_create_tags(GtkTextView *text, Compose *compose)
842 GtkTextBuffer *buffer;
843 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
845 buffer = gtk_text_view_get_buffer(text);
847 if (prefs_common.enable_color) {
848 /* grab the quote colors, converting from an int to a GdkColor */
849 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1],
850 &quote_color1);
851 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2],
852 &quote_color2);
853 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3],
854 &quote_color3);
855 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1_BG],
856 &quote_bgcolor1);
857 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2_BG],
858 &quote_bgcolor2);
859 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3_BG],
860 &quote_bgcolor3);
861 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_SIGNATURE],
862 &signature_color);
863 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_URI],
864 &uri_color);
865 } else {
866 signature_color = quote_color1 = quote_color2 = quote_color3 =
867 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
870 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
871 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
872 "foreground-gdk", &quote_color1,
873 "paragraph-background-gdk", &quote_bgcolor1,
874 NULL);
875 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
876 "foreground-gdk", &quote_color2,
877 "paragraph-background-gdk", &quote_bgcolor2,
878 NULL);
879 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
880 "foreground-gdk", &quote_color3,
881 "paragraph-background-gdk", &quote_bgcolor3,
882 NULL);
883 } else {
884 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
885 "foreground-gdk", &quote_color1,
886 NULL);
887 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
888 "foreground-gdk", &quote_color2,
889 NULL);
890 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
891 "foreground-gdk", &quote_color3,
892 NULL);
895 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
896 "foreground-gdk", &signature_color,
897 NULL);
899 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
900 "foreground-gdk", &uri_color,
901 NULL);
902 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
903 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
906 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
907 GList *attach_files)
909 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
912 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
914 return compose_generic_new(account, mailto, item, NULL, NULL);
917 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
919 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
922 #define SCROLL_TO_CURSOR(compose) { \
923 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
924 gtk_text_view_get_buffer( \
925 GTK_TEXT_VIEW(compose->text))); \
926 gtk_text_view_scroll_mark_onscreen( \
927 GTK_TEXT_VIEW(compose->text), \
928 cmark); \
931 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
933 GtkEditable *entry;
934 if (folderidentifier) {
935 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
936 prefs_common.compose_save_to_history = add_history(
937 prefs_common.compose_save_to_history, folderidentifier);
938 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
939 prefs_common.compose_save_to_history);
942 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
943 if (folderidentifier)
944 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
945 else
946 gtk_entry_set_text(GTK_ENTRY(entry), "");
949 static gchar *compose_get_save_to(Compose *compose)
951 GtkEditable *entry;
952 gchar *result = NULL;
953 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
954 result = gtk_editable_get_chars(entry, 0, -1);
956 if (result) {
957 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
958 prefs_common.compose_save_to_history = add_history(
959 prefs_common.compose_save_to_history, result);
960 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
961 prefs_common.compose_save_to_history);
963 return result;
966 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
967 GList *attach_files, GList *listAddress )
969 Compose *compose;
970 GtkTextView *textview;
971 GtkTextBuffer *textbuf;
972 GtkTextIter iter;
973 const gchar *subject_format = NULL;
974 const gchar *body_format = NULL;
975 gchar *mailto_from = NULL;
976 PrefsAccount *mailto_account = NULL;
977 MsgInfo* dummyinfo = NULL;
978 gint cursor_pos = -1;
979 MailField mfield = NO_FIELD_PRESENT;
980 gchar* buf;
981 GtkTextMark *mark;
983 /* check if mailto defines a from */
984 if (mailto && *mailto != '\0') {
985 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
986 /* mailto defines a from, check if we can get account prefs from it,
987 if not, the account prefs will be guessed using other ways, but we'll keep
988 the from anyway */
989 if (mailto_from) {
990 mailto_account = account_find_from_address(mailto_from, TRUE);
991 if (mailto_account == NULL) {
992 gchar *tmp_from;
993 Xstrdup_a(tmp_from, mailto_from, return NULL);
994 extract_address(tmp_from);
995 mailto_account = account_find_from_address(tmp_from, TRUE);
998 if (mailto_account)
999 account = mailto_account;
1002 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1003 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1004 account = account_find_from_id(item->prefs->default_account);
1006 /* if no account prefs set, fallback to the current one */
1007 if (!account) account = cur_account;
1008 cm_return_val_if_fail(account != NULL, NULL);
1010 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1011 compose_apply_folder_privacy_settings(compose, item);
1013 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1014 (account->default_encrypt || account->default_sign))
1015 alertpanel_error(_("You have opted to sign and/or encrypt this "
1016 "message but have not selected a privacy system.\n\n"
1017 "Signing and encrypting have been disabled for this "
1018 "message."));
1020 /* override from name if mailto asked for it */
1021 if (mailto_from) {
1022 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1023 g_free(mailto_from);
1024 } else
1025 /* override from name according to folder properties */
1026 if (item && item->prefs &&
1027 item->prefs->compose_with_format &&
1028 item->prefs->compose_override_from_format &&
1029 *item->prefs->compose_override_from_format != '\0') {
1031 gchar *tmp = NULL;
1032 gchar *buf = NULL;
1034 dummyinfo = compose_msginfo_new_from_compose(compose);
1036 /* decode \-escape sequences in the internal representation of the quote format */
1037 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1038 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1040 #ifdef USE_ENCHANT
1041 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1042 compose->gtkaspell);
1043 #else
1044 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1045 #endif
1046 quote_fmt_scan_string(tmp);
1047 quote_fmt_parse();
1049 buf = quote_fmt_get_buffer();
1050 if (buf == NULL)
1051 alertpanel_error(_("New message From format error."));
1052 else
1053 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1054 quote_fmt_reset_vartable();
1055 quote_fmtlex_destroy();
1057 g_free(tmp);
1060 compose->replyinfo = NULL;
1061 compose->fwdinfo = NULL;
1063 textview = GTK_TEXT_VIEW(compose->text);
1064 textbuf = gtk_text_view_get_buffer(textview);
1065 compose_create_tags(textview, compose);
1067 undo_block(compose->undostruct);
1068 #ifdef USE_ENCHANT
1069 compose_set_dictionaries_from_folder_prefs(compose, item);
1070 #endif
1072 if (account->auto_sig)
1073 compose_insert_sig(compose, FALSE);
1074 gtk_text_buffer_get_start_iter(textbuf, &iter);
1075 gtk_text_buffer_place_cursor(textbuf, &iter);
1077 if (account->protocol != A_NNTP) {
1078 if (mailto && *mailto != '\0') {
1079 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1081 } else {
1082 compose_set_folder_prefs(compose, item, TRUE);
1084 if (item && item->ret_rcpt) {
1085 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1087 } else {
1088 if (mailto && *mailto != '\0') {
1089 if (!strchr(mailto, '@'))
1090 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1091 else
1092 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1093 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1094 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1095 mfield = TO_FIELD_PRESENT;
1098 * CLAWS: just don't allow return receipt request, even if the user
1099 * may want to send an email. simple but foolproof.
1101 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1103 compose_add_field_list( compose, listAddress );
1105 if (item && item->prefs && item->prefs->compose_with_format) {
1106 subject_format = item->prefs->compose_subject_format;
1107 body_format = item->prefs->compose_body_format;
1108 } else if (account->compose_with_format) {
1109 subject_format = account->compose_subject_format;
1110 body_format = account->compose_body_format;
1111 } else if (prefs_common.compose_with_format) {
1112 subject_format = prefs_common.compose_subject_format;
1113 body_format = prefs_common.compose_body_format;
1116 if (subject_format || body_format) {
1118 if ( subject_format
1119 && *subject_format != '\0' )
1121 gchar *subject = NULL;
1122 gchar *tmp = NULL;
1123 gchar *buf = NULL;
1125 if (!dummyinfo)
1126 dummyinfo = compose_msginfo_new_from_compose(compose);
1128 /* decode \-escape sequences in the internal representation of the quote format */
1129 tmp = g_malloc(strlen(subject_format)+1);
1130 pref_get_unescaped_pref(tmp, subject_format);
1132 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1133 #ifdef USE_ENCHANT
1134 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1135 compose->gtkaspell);
1136 #else
1137 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1138 #endif
1139 quote_fmt_scan_string(tmp);
1140 quote_fmt_parse();
1142 buf = quote_fmt_get_buffer();
1143 if (buf == NULL)
1144 alertpanel_error(_("New message subject format error."));
1145 else
1146 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1147 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1148 quote_fmt_reset_vartable();
1149 quote_fmtlex_destroy();
1151 g_free(subject);
1152 g_free(tmp);
1153 mfield = SUBJECT_FIELD_PRESENT;
1156 if ( body_format
1157 && *body_format != '\0' )
1159 GtkTextView *text;
1160 GtkTextBuffer *buffer;
1161 GtkTextIter start, end;
1162 gchar *tmp = NULL;
1164 if (!dummyinfo)
1165 dummyinfo = compose_msginfo_new_from_compose(compose);
1167 text = GTK_TEXT_VIEW(compose->text);
1168 buffer = gtk_text_view_get_buffer(text);
1169 gtk_text_buffer_get_start_iter(buffer, &start);
1170 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1171 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1173 compose_quote_fmt(compose, dummyinfo,
1174 body_format,
1175 NULL, tmp, FALSE, TRUE,
1176 _("The body of the \"New message\" template has an error at line %d."));
1177 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1178 quote_fmt_reset_vartable();
1180 g_free(tmp);
1181 #ifdef USE_ENCHANT
1182 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1183 gtkaspell_highlight_all(compose->gtkaspell);
1184 #endif
1185 mfield = BODY_FIELD_PRESENT;
1189 procmsg_msginfo_free( &dummyinfo );
1191 if (attach_files) {
1192 GList *curr;
1193 AttachInfo *ainfo;
1195 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1196 ainfo = (AttachInfo *) curr->data;
1197 if (ainfo->insert)
1198 compose_insert_file(compose, ainfo->file);
1199 else
1200 compose_attach_append(compose, ainfo->file, ainfo->file,
1201 ainfo->content_type, ainfo->charset);
1205 compose_show_first_last_header(compose, TRUE);
1207 /* Set save folder */
1208 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1209 gchar *folderidentifier;
1211 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1212 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1213 folderidentifier = folder_item_get_identifier(item);
1214 compose_set_save_to(compose, folderidentifier);
1215 g_free(folderidentifier);
1218 /* Place cursor according to provided input (mfield) */
1219 switch (mfield) {
1220 case NO_FIELD_PRESENT:
1221 if (compose->header_last)
1222 gtk_widget_grab_focus(compose->header_last->entry);
1223 break;
1224 case TO_FIELD_PRESENT:
1225 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1226 if (buf) {
1227 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1228 g_free(buf);
1230 gtk_widget_grab_focus(compose->subject_entry);
1231 break;
1232 case SUBJECT_FIELD_PRESENT:
1233 textview = GTK_TEXT_VIEW(compose->text);
1234 if (!textview)
1235 break;
1236 textbuf = gtk_text_view_get_buffer(textview);
1237 if (!textbuf)
1238 break;
1239 mark = gtk_text_buffer_get_insert(textbuf);
1240 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1241 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1243 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1244 * only defers where it comes to the variable body
1245 * is not null. If no body is present compose->text
1246 * will be null in which case you cannot place the
1247 * cursor inside the component so. An empty component
1248 * is therefore created before placing the cursor
1250 case BODY_FIELD_PRESENT:
1251 cursor_pos = quote_fmt_get_cursor_pos();
1252 if (cursor_pos == -1)
1253 gtk_widget_grab_focus(compose->header_last->entry);
1254 else
1255 gtk_widget_grab_focus(compose->text);
1256 break;
1259 undo_unblock(compose->undostruct);
1261 if (prefs_common.auto_exteditor)
1262 compose_exec_ext_editor(compose);
1264 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1266 SCROLL_TO_CURSOR(compose);
1268 compose->modified = FALSE;
1269 compose_set_title(compose);
1271 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1273 return compose;
1276 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1277 gboolean override_pref, const gchar *system)
1279 const gchar *privacy = NULL;
1281 cm_return_if_fail(compose != NULL);
1282 cm_return_if_fail(account != NULL);
1284 if (privacy_system_can_encrypt(compose->privacy_system) == FALSE ||
1285 (override_pref == FALSE && account->default_encrypt_reply == FALSE))
1286 return;
1288 if (account->default_privacy_system && strlen(account->default_privacy_system))
1289 privacy = account->default_privacy_system;
1290 else if (system)
1291 privacy = system;
1292 else {
1293 GSList *privacy_avail = privacy_get_system_ids();
1294 if (privacy_avail && g_slist_length(privacy_avail)) {
1295 privacy = (gchar *)(privacy_avail->data);
1297 g_slist_free_full(privacy_avail, g_free);
1299 if (privacy != NULL) {
1300 if (system) {
1301 g_free(compose->privacy_system);
1302 compose->privacy_system = NULL;
1303 g_free(compose->encdata);
1304 compose->encdata = NULL;
1306 if (compose->privacy_system == NULL)
1307 compose->privacy_system = g_strdup(privacy);
1308 else if (*(compose->privacy_system) == '\0') {
1309 g_free(compose->privacy_system);
1310 g_free(compose->encdata);
1311 compose->encdata = NULL;
1312 compose->privacy_system = g_strdup(privacy);
1314 compose_update_privacy_system_menu_item(compose, FALSE);
1315 compose_use_encryption(compose, TRUE);
1319 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1321 const gchar *privacy = NULL;
1322 if (privacy_system_can_sign(compose->privacy_system) == FALSE)
1323 return;
1325 if (account->default_privacy_system && strlen(account->default_privacy_system))
1326 privacy = account->default_privacy_system;
1327 else if (system)
1328 privacy = system;
1329 else {
1330 GSList *privacy_avail = privacy_get_system_ids();
1331 if (privacy_avail && g_slist_length(privacy_avail)) {
1332 privacy = (gchar *)(privacy_avail->data);
1336 if (privacy != NULL) {
1337 if (system) {
1338 g_free(compose->privacy_system);
1339 compose->privacy_system = NULL;
1340 g_free(compose->encdata);
1341 compose->encdata = NULL;
1343 if (compose->privacy_system == NULL)
1344 compose->privacy_system = g_strdup(privacy);
1345 compose_update_privacy_system_menu_item(compose, FALSE);
1346 compose_use_signing(compose, TRUE);
1350 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1352 MsgInfo *msginfo;
1353 guint list_len;
1354 Compose *compose = NULL;
1356 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1358 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1359 cm_return_val_if_fail(msginfo != NULL, NULL);
1361 list_len = g_slist_length(msginfo_list);
1363 switch (mode) {
1364 case COMPOSE_REPLY:
1365 case COMPOSE_REPLY_TO_ADDRESS:
1366 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1367 FALSE, prefs_common.default_reply_list, FALSE, body);
1368 break;
1369 case COMPOSE_REPLY_WITH_QUOTE:
1370 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1371 FALSE, prefs_common.default_reply_list, FALSE, body);
1372 break;
1373 case COMPOSE_REPLY_WITHOUT_QUOTE:
1374 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1375 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1376 break;
1377 case COMPOSE_REPLY_TO_SENDER:
1378 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1379 FALSE, FALSE, TRUE, body);
1380 break;
1381 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1382 compose = compose_followup_and_reply_to(msginfo,
1383 COMPOSE_QUOTE_CHECK,
1384 FALSE, FALSE, body);
1385 break;
1386 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1387 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1388 FALSE, FALSE, TRUE, body);
1389 break;
1390 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1391 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1392 FALSE, FALSE, TRUE, NULL);
1393 break;
1394 case COMPOSE_REPLY_TO_ALL:
1395 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1396 TRUE, FALSE, FALSE, body);
1397 break;
1398 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1399 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1400 TRUE, FALSE, FALSE, body);
1401 break;
1402 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1403 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1404 TRUE, FALSE, FALSE, NULL);
1405 break;
1406 case COMPOSE_REPLY_TO_LIST:
1407 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1408 FALSE, TRUE, FALSE, body);
1409 break;
1410 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1411 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1412 FALSE, TRUE, FALSE, body);
1413 break;
1414 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1415 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1416 FALSE, TRUE, FALSE, NULL);
1417 break;
1418 case COMPOSE_FORWARD:
1419 if (prefs_common.forward_as_attachment) {
1420 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1421 return compose;
1422 } else {
1423 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1424 return compose;
1426 break;
1427 case COMPOSE_FORWARD_INLINE:
1428 /* check if we reply to more than one Message */
1429 if (list_len == 1) {
1430 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1431 break;
1433 /* more messages FALL THROUGH */
1434 case COMPOSE_FORWARD_AS_ATTACH:
1435 compose = compose_forward_multiple(NULL, msginfo_list);
1436 break;
1437 case COMPOSE_REDIRECT:
1438 compose = compose_redirect(NULL, msginfo, FALSE);
1439 break;
1440 default:
1441 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1444 if (compose == NULL) {
1445 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1446 return NULL;
1449 compose->rmode = mode;
1450 switch (compose->rmode) {
1451 case COMPOSE_REPLY:
1452 case COMPOSE_REPLY_WITH_QUOTE:
1453 case COMPOSE_REPLY_WITHOUT_QUOTE:
1454 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1455 debug_print("reply mode Normal\n");
1456 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1457 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1458 break;
1459 case COMPOSE_REPLY_TO_SENDER:
1460 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1461 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1462 debug_print("reply mode Sender\n");
1463 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1464 break;
1465 case COMPOSE_REPLY_TO_ALL:
1466 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1467 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1468 debug_print("reply mode All\n");
1469 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1470 break;
1471 case COMPOSE_REPLY_TO_LIST:
1472 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1473 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1474 debug_print("reply mode List\n");
1475 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1476 break;
1477 case COMPOSE_REPLY_TO_ADDRESS:
1478 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1479 break;
1480 default:
1481 break;
1483 return compose;
1486 static Compose *compose_reply(MsgInfo *msginfo,
1487 ComposeQuoteMode quote_mode,
1488 gboolean to_all,
1489 gboolean to_ml,
1490 gboolean to_sender,
1491 const gchar *body)
1493 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1494 to_sender, FALSE, body);
1497 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1498 ComposeQuoteMode quote_mode,
1499 gboolean to_all,
1500 gboolean to_sender,
1501 const gchar *body)
1503 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1504 to_sender, TRUE, body);
1507 static void compose_extract_original_charset(Compose *compose)
1509 MsgInfo *info = NULL;
1510 if (compose->replyinfo) {
1511 info = compose->replyinfo;
1512 } else if (compose->fwdinfo) {
1513 info = compose->fwdinfo;
1514 } else if (compose->targetinfo) {
1515 info = compose->targetinfo;
1517 if (info) {
1518 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1519 MimeInfo *partinfo = mimeinfo;
1520 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1521 partinfo = procmime_mimeinfo_next(partinfo);
1522 if (partinfo) {
1523 compose->orig_charset =
1524 g_strdup(procmime_mimeinfo_get_parameter(
1525 partinfo, "charset"));
1527 procmime_mimeinfo_free_all(&mimeinfo);
1531 #define SIGNAL_BLOCK(buffer) { \
1532 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1533 G_CALLBACK(compose_changed_cb), \
1534 compose); \
1535 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1536 G_CALLBACK(text_inserted), \
1537 compose); \
1540 #define SIGNAL_UNBLOCK(buffer) { \
1541 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1542 G_CALLBACK(compose_changed_cb), \
1543 compose); \
1544 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1545 G_CALLBACK(text_inserted), \
1546 compose); \
1549 static Compose *compose_generic_reply(MsgInfo *msginfo,
1550 ComposeQuoteMode quote_mode,
1551 gboolean to_all, gboolean to_ml,
1552 gboolean to_sender,
1553 gboolean followup_and_reply_to,
1554 const gchar *body)
1556 Compose *compose;
1557 PrefsAccount *account = NULL;
1558 GtkTextView *textview;
1559 GtkTextBuffer *textbuf;
1560 gboolean quote = FALSE;
1561 const gchar *qmark = NULL;
1562 const gchar *body_fmt = NULL;
1563 gchar *s_system = NULL;
1564 START_TIMING("");
1565 cm_return_val_if_fail(msginfo != NULL, NULL);
1566 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1568 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1570 cm_return_val_if_fail(account != NULL, NULL);
1572 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1573 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1575 compose->updating = TRUE;
1577 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1578 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1580 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1581 if (!compose->replyinfo)
1582 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1584 compose_extract_original_charset(compose);
1586 if (msginfo->folder && msginfo->folder->ret_rcpt)
1587 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1589 /* Set save folder */
1590 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1591 gchar *folderidentifier;
1593 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1594 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1595 folderidentifier = folder_item_get_identifier(msginfo->folder);
1596 compose_set_save_to(compose, folderidentifier);
1597 g_free(folderidentifier);
1600 if (compose_parse_header(compose, msginfo) < 0) {
1601 compose->updating = FALSE;
1602 compose_destroy(compose);
1603 return NULL;
1606 /* override from name according to folder properties */
1607 if (msginfo->folder && msginfo->folder->prefs &&
1608 msginfo->folder->prefs->reply_with_format &&
1609 msginfo->folder->prefs->reply_override_from_format &&
1610 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1612 gchar *tmp = NULL;
1613 gchar *buf = NULL;
1615 /* decode \-escape sequences in the internal representation of the quote format */
1616 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1617 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1619 #ifdef USE_ENCHANT
1620 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1621 compose->gtkaspell);
1622 #else
1623 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1624 #endif
1625 quote_fmt_scan_string(tmp);
1626 quote_fmt_parse();
1628 buf = quote_fmt_get_buffer();
1629 if (buf == NULL)
1630 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1631 else
1632 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1633 quote_fmt_reset_vartable();
1634 quote_fmtlex_destroy();
1636 g_free(tmp);
1639 textview = (GTK_TEXT_VIEW(compose->text));
1640 textbuf = gtk_text_view_get_buffer(textview);
1641 compose_create_tags(textview, compose);
1643 undo_block(compose->undostruct);
1644 #ifdef USE_ENCHANT
1645 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1646 gtkaspell_block_check(compose->gtkaspell);
1647 #endif
1649 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1650 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1651 /* use the reply format of folder (if enabled), or the account's one
1652 (if enabled) or fallback to the global reply format, which is always
1653 enabled (even if empty), and use the relevant quotemark */
1654 quote = TRUE;
1655 if (msginfo->folder && msginfo->folder->prefs &&
1656 msginfo->folder->prefs->reply_with_format) {
1657 qmark = msginfo->folder->prefs->reply_quotemark;
1658 body_fmt = msginfo->folder->prefs->reply_body_format;
1660 } else if (account->reply_with_format) {
1661 qmark = account->reply_quotemark;
1662 body_fmt = account->reply_body_format;
1664 } else {
1665 qmark = prefs_common.quotemark;
1666 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1667 body_fmt = gettext(prefs_common.quotefmt);
1668 else
1669 body_fmt = "";
1673 if (quote) {
1674 /* empty quotemark is not allowed */
1675 if (qmark == NULL || *qmark == '\0')
1676 qmark = "> ";
1677 compose_quote_fmt(compose, compose->replyinfo,
1678 body_fmt, qmark, body, FALSE, TRUE,
1679 _("The body of the \"Reply\" template has an error at line %d."));
1680 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1681 quote_fmt_reset_vartable();
1684 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1685 compose_force_encryption(compose, account, FALSE, s_system);
1688 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1689 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1690 compose_force_signing(compose, account, s_system);
1692 g_free(s_system);
1694 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1695 ((account->default_encrypt_reply && MSG_IS_ENCRYPTED(compose->replyinfo->flags)) ||
1696 (account->default_sign_reply && MSG_IS_SIGNED(compose->replyinfo->flags))))
1697 alertpanel_error(_("You have opted to sign and/or encrypt this "
1698 "message but have not selected a privacy system.\n\n"
1699 "Signing and encrypting have been disabled for this "
1700 "message."));
1702 SIGNAL_BLOCK(textbuf);
1704 if (account->auto_sig)
1705 compose_insert_sig(compose, FALSE);
1707 compose_wrap_all(compose);
1709 #ifdef USE_ENCHANT
1710 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1711 gtkaspell_highlight_all(compose->gtkaspell);
1712 gtkaspell_unblock_check(compose->gtkaspell);
1713 #endif
1714 SIGNAL_UNBLOCK(textbuf);
1716 gtk_widget_grab_focus(compose->text);
1718 undo_unblock(compose->undostruct);
1720 if (prefs_common.auto_exteditor)
1721 compose_exec_ext_editor(compose);
1723 compose->modified = FALSE;
1724 compose_set_title(compose);
1726 compose->updating = FALSE;
1727 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1728 SCROLL_TO_CURSOR(compose);
1730 if (compose->deferred_destroy) {
1731 compose_destroy(compose);
1732 return NULL;
1734 END_TIMING();
1736 return compose;
1739 #define INSERT_FW_HEADER(var, hdr) \
1740 if (msginfo->var && *msginfo->var) { \
1741 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1742 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1743 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1746 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1747 gboolean as_attach, const gchar *body,
1748 gboolean no_extedit,
1749 gboolean batch)
1751 Compose *compose;
1752 GtkTextView *textview;
1753 GtkTextBuffer *textbuf;
1754 gint cursor_pos = -1;
1755 ComposeMode mode;
1757 cm_return_val_if_fail(msginfo != NULL, NULL);
1758 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1760 if (!account && !(account = compose_find_account(msginfo)))
1761 account = cur_account;
1763 if (!prefs_common.forward_as_attachment)
1764 mode = COMPOSE_FORWARD_INLINE;
1765 else
1766 mode = COMPOSE_FORWARD;
1767 compose = compose_create(account, msginfo->folder, mode, batch);
1768 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1770 compose->updating = TRUE;
1771 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1772 if (!compose->fwdinfo)
1773 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1775 compose_extract_original_charset(compose);
1777 if (msginfo->subject && *msginfo->subject) {
1778 gchar *buf, *buf2, *p;
1780 buf = p = g_strdup(msginfo->subject);
1781 p += subject_get_prefix_length(p);
1782 memmove(buf, p, strlen(p) + 1);
1784 buf2 = g_strdup_printf("Fw: %s", buf);
1785 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1787 g_free(buf);
1788 g_free(buf2);
1791 /* override from name according to folder properties */
1792 if (msginfo->folder && msginfo->folder->prefs &&
1793 msginfo->folder->prefs->forward_with_format &&
1794 msginfo->folder->prefs->forward_override_from_format &&
1795 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1797 gchar *tmp = NULL;
1798 gchar *buf = NULL;
1799 MsgInfo *full_msginfo = NULL;
1801 if (!as_attach)
1802 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1803 if (!full_msginfo)
1804 full_msginfo = procmsg_msginfo_copy(msginfo);
1806 /* decode \-escape sequences in the internal representation of the quote format */
1807 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1808 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1810 #ifdef USE_ENCHANT
1811 gtkaspell_block_check(compose->gtkaspell);
1812 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1813 compose->gtkaspell);
1814 #else
1815 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1816 #endif
1817 quote_fmt_scan_string(tmp);
1818 quote_fmt_parse();
1820 buf = quote_fmt_get_buffer();
1821 if (buf == NULL)
1822 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1823 else
1824 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1825 quote_fmt_reset_vartable();
1826 quote_fmtlex_destroy();
1828 g_free(tmp);
1829 procmsg_msginfo_free(&full_msginfo);
1832 textview = GTK_TEXT_VIEW(compose->text);
1833 textbuf = gtk_text_view_get_buffer(textview);
1834 compose_create_tags(textview, compose);
1836 undo_block(compose->undostruct);
1837 if (as_attach) {
1838 gchar *msgfile;
1840 msgfile = procmsg_get_message_file(msginfo);
1841 if (!is_file_exist(msgfile))
1842 g_warning("%s: file does not exist", msgfile);
1843 else
1844 compose_attach_append(compose, msgfile, msgfile,
1845 "message/rfc822", NULL);
1847 g_free(msgfile);
1848 } else {
1849 const gchar *qmark = NULL;
1850 const gchar *body_fmt = NULL;
1851 MsgInfo *full_msginfo;
1853 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1854 if (!full_msginfo)
1855 full_msginfo = procmsg_msginfo_copy(msginfo);
1857 /* use the forward format of folder (if enabled), or the account's one
1858 (if enabled) or fallback to the global forward format, which is always
1859 enabled (even if empty), and use the relevant quotemark */
1860 if (msginfo->folder && msginfo->folder->prefs &&
1861 msginfo->folder->prefs->forward_with_format) {
1862 qmark = msginfo->folder->prefs->forward_quotemark;
1863 body_fmt = msginfo->folder->prefs->forward_body_format;
1865 } else if (account->forward_with_format) {
1866 qmark = account->forward_quotemark;
1867 body_fmt = account->forward_body_format;
1869 } else {
1870 qmark = prefs_common.fw_quotemark;
1871 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1872 body_fmt = gettext(prefs_common.fw_quotefmt);
1873 else
1874 body_fmt = "";
1877 /* empty quotemark is not allowed */
1878 if (qmark == NULL || *qmark == '\0')
1879 qmark = "> ";
1881 compose_quote_fmt(compose, full_msginfo,
1882 body_fmt, qmark, body, FALSE, TRUE,
1883 _("The body of the \"Forward\" template has an error at line %d."));
1884 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1885 quote_fmt_reset_vartable();
1886 compose_attach_parts(compose, msginfo);
1888 procmsg_msginfo_free(&full_msginfo);
1891 SIGNAL_BLOCK(textbuf);
1893 if (account->auto_sig)
1894 compose_insert_sig(compose, FALSE);
1896 compose_wrap_all(compose);
1898 #ifdef USE_ENCHANT
1899 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1900 gtkaspell_highlight_all(compose->gtkaspell);
1901 gtkaspell_unblock_check(compose->gtkaspell);
1902 #endif
1903 SIGNAL_UNBLOCK(textbuf);
1905 cursor_pos = quote_fmt_get_cursor_pos();
1906 if (cursor_pos == -1)
1907 gtk_widget_grab_focus(compose->header_last->entry);
1908 else
1909 gtk_widget_grab_focus(compose->text);
1911 if (!no_extedit && prefs_common.auto_exteditor)
1912 compose_exec_ext_editor(compose);
1914 /*save folder*/
1915 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1916 gchar *folderidentifier;
1918 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1919 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1920 folderidentifier = folder_item_get_identifier(msginfo->folder);
1921 compose_set_save_to(compose, folderidentifier);
1922 g_free(folderidentifier);
1925 undo_unblock(compose->undostruct);
1927 compose->modified = FALSE;
1928 compose_set_title(compose);
1930 compose->updating = FALSE;
1931 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1932 SCROLL_TO_CURSOR(compose);
1934 if (compose->deferred_destroy) {
1935 compose_destroy(compose);
1936 return NULL;
1939 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1941 return compose;
1944 #undef INSERT_FW_HEADER
1946 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1948 Compose *compose;
1949 GtkTextView *textview;
1950 GtkTextBuffer *textbuf;
1951 GtkTextIter iter;
1952 GSList *msginfo;
1953 gchar *msgfile;
1954 gboolean single_mail = TRUE;
1956 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1958 if (g_slist_length(msginfo_list) > 1)
1959 single_mail = FALSE;
1961 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1962 if (((MsgInfo *)msginfo->data)->folder == NULL)
1963 return NULL;
1965 /* guess account from first selected message */
1966 if (!account &&
1967 !(account = compose_find_account(msginfo_list->data)))
1968 account = cur_account;
1970 cm_return_val_if_fail(account != NULL, NULL);
1972 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1973 if (msginfo->data) {
1974 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1975 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1979 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1980 g_warning("no msginfo_list");
1981 return NULL;
1984 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1985 compose_apply_folder_privacy_settings(compose, ((MsgInfo *)msginfo_list->data)->folder);
1987 compose->updating = TRUE;
1989 /* override from name according to folder properties */
1990 if (msginfo_list->data) {
1991 MsgInfo *msginfo = msginfo_list->data;
1993 if (msginfo->folder && msginfo->folder->prefs &&
1994 msginfo->folder->prefs->forward_with_format &&
1995 msginfo->folder->prefs->forward_override_from_format &&
1996 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1998 gchar *tmp = NULL;
1999 gchar *buf = NULL;
2001 /* decode \-escape sequences in the internal representation of the quote format */
2002 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2003 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2005 #ifdef USE_ENCHANT
2006 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2007 compose->gtkaspell);
2008 #else
2009 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2010 #endif
2011 quote_fmt_scan_string(tmp);
2012 quote_fmt_parse();
2014 buf = quote_fmt_get_buffer();
2015 if (buf == NULL)
2016 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2017 else
2018 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2019 quote_fmt_reset_vartable();
2020 quote_fmtlex_destroy();
2022 g_free(tmp);
2026 textview = GTK_TEXT_VIEW(compose->text);
2027 textbuf = gtk_text_view_get_buffer(textview);
2028 compose_create_tags(textview, compose);
2030 undo_block(compose->undostruct);
2031 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2032 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2034 if (!is_file_exist(msgfile))
2035 g_warning("%s: file does not exist", msgfile);
2036 else
2037 compose_attach_append(compose, msgfile, msgfile,
2038 "message/rfc822", NULL);
2039 g_free(msgfile);
2042 if (single_mail) {
2043 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2044 if (info->subject && *info->subject) {
2045 gchar *buf, *buf2, *p;
2047 buf = p = g_strdup(info->subject);
2048 p += subject_get_prefix_length(p);
2049 memmove(buf, p, strlen(p) + 1);
2051 buf2 = g_strdup_printf("Fw: %s", buf);
2052 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2054 g_free(buf);
2055 g_free(buf2);
2057 } else {
2058 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2059 _("Fw: multiple emails"));
2062 SIGNAL_BLOCK(textbuf);
2064 if (account->auto_sig)
2065 compose_insert_sig(compose, FALSE);
2067 compose_wrap_all(compose);
2069 SIGNAL_UNBLOCK(textbuf);
2071 gtk_text_buffer_get_start_iter(textbuf, &iter);
2072 gtk_text_buffer_place_cursor(textbuf, &iter);
2074 if (prefs_common.auto_exteditor)
2075 compose_exec_ext_editor(compose);
2077 gtk_widget_grab_focus(compose->header_last->entry);
2078 undo_unblock(compose->undostruct);
2079 compose->modified = FALSE;
2080 compose_set_title(compose);
2082 compose->updating = FALSE;
2083 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2084 SCROLL_TO_CURSOR(compose);
2086 if (compose->deferred_destroy) {
2087 compose_destroy(compose);
2088 return NULL;
2091 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2093 return compose;
2096 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2098 GtkTextIter start = *iter;
2099 GtkTextIter end_iter;
2100 int start_pos = gtk_text_iter_get_offset(&start);
2101 gchar *str = NULL;
2102 if (!compose->account->sig_sep)
2103 return FALSE;
2105 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2106 start_pos+strlen(compose->account->sig_sep));
2108 /* check sig separator */
2109 str = gtk_text_iter_get_text(&start, &end_iter);
2110 if (!strcmp(str, compose->account->sig_sep)) {
2111 gchar *tmp = NULL;
2112 /* check end of line (\n) */
2113 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2114 start_pos+strlen(compose->account->sig_sep));
2115 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2116 start_pos+strlen(compose->account->sig_sep)+1);
2117 tmp = gtk_text_iter_get_text(&start, &end_iter);
2118 if (!strcmp(tmp,"\n")) {
2119 g_free(str);
2120 g_free(tmp);
2121 return TRUE;
2123 g_free(tmp);
2125 g_free(str);
2127 return FALSE;
2130 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2132 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2133 Compose *compose = (Compose *)data;
2134 FolderItem *old_item = NULL;
2135 FolderItem *new_item = NULL;
2136 gchar *old_id, *new_id;
2138 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2139 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2140 return FALSE;
2142 old_item = hookdata->item;
2143 new_item = hookdata->item2;
2145 old_id = folder_item_get_identifier(old_item);
2146 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2148 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2149 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2150 compose->targetinfo->folder = new_item;
2153 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2154 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2155 compose->replyinfo->folder = new_item;
2158 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2159 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2160 compose->fwdinfo->folder = new_item;
2163 g_free(old_id);
2164 g_free(new_id);
2165 return FALSE;
2168 static void compose_colorize_signature(Compose *compose)
2170 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2171 GtkTextIter iter;
2172 GtkTextIter end_iter;
2173 gtk_text_buffer_get_start_iter(buffer, &iter);
2174 while (gtk_text_iter_forward_line(&iter))
2175 if (compose_is_sig_separator(compose, buffer, &iter)) {
2176 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2177 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2181 #define BLOCK_WRAP() { \
2182 prev_autowrap = compose->autowrap; \
2183 buffer = gtk_text_view_get_buffer( \
2184 GTK_TEXT_VIEW(compose->text)); \
2185 compose->autowrap = FALSE; \
2187 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2188 G_CALLBACK(compose_changed_cb), \
2189 compose); \
2190 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2191 G_CALLBACK(text_inserted), \
2192 compose); \
2194 #define UNBLOCK_WRAP() { \
2195 compose->autowrap = prev_autowrap; \
2196 if (compose->autowrap) { \
2197 gint old = compose->draft_timeout_tag; \
2198 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2199 compose_wrap_all(compose); \
2200 compose->draft_timeout_tag = old; \
2203 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2204 G_CALLBACK(compose_changed_cb), \
2205 compose); \
2206 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2207 G_CALLBACK(text_inserted), \
2208 compose); \
2211 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2213 Compose *compose = NULL;
2214 PrefsAccount *account = NULL;
2215 GtkTextView *textview;
2216 GtkTextBuffer *textbuf;
2217 GtkTextMark *mark;
2218 GtkTextIter iter;
2219 FILE *fp;
2220 gboolean use_signing = FALSE;
2221 gboolean use_encryption = FALSE;
2222 gchar *privacy_system = NULL;
2223 int priority = PRIORITY_NORMAL;
2224 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2225 gboolean autowrap = prefs_common.autowrap;
2226 gboolean autoindent = prefs_common.auto_indent;
2227 HeaderEntry *manual_headers = NULL;
2229 cm_return_val_if_fail(msginfo != NULL, NULL);
2230 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2232 if (compose_put_existing_to_front(msginfo)) {
2233 return NULL;
2236 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2237 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2238 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2239 gchar *queueheader_buf = NULL;
2240 gint id, param;
2242 /* Select Account from queue headers */
2243 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2244 "X-Claws-Account-Id:")) {
2245 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2246 account = account_find_from_id(id);
2247 g_free(queueheader_buf);
2249 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2250 "X-Sylpheed-Account-Id:")) {
2251 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2252 account = account_find_from_id(id);
2253 g_free(queueheader_buf);
2255 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2256 "NAID:")) {
2257 id = atoi(&queueheader_buf[strlen("NAID:")]);
2258 account = account_find_from_id(id);
2259 g_free(queueheader_buf);
2261 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2262 "MAID:")) {
2263 id = atoi(&queueheader_buf[strlen("MAID:")]);
2264 account = account_find_from_id(id);
2265 g_free(queueheader_buf);
2267 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2268 "S:")) {
2269 account = account_find_from_address(queueheader_buf, FALSE);
2270 g_free(queueheader_buf);
2272 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2273 "X-Claws-Sign:")) {
2274 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2275 use_signing = param;
2276 g_free(queueheader_buf);
2278 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2279 "X-Sylpheed-Sign:")) {
2280 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2281 use_signing = param;
2282 g_free(queueheader_buf);
2284 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2285 "X-Claws-Encrypt:")) {
2286 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2287 use_encryption = param;
2288 g_free(queueheader_buf);
2290 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2291 "X-Sylpheed-Encrypt:")) {
2292 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2293 use_encryption = param;
2294 g_free(queueheader_buf);
2296 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2297 "X-Claws-Auto-Wrapping:")) {
2298 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2299 autowrap = param;
2300 g_free(queueheader_buf);
2302 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2303 "X-Claws-Auto-Indent:")) {
2304 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2305 autoindent = param;
2306 g_free(queueheader_buf);
2308 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2309 "X-Claws-Privacy-System:")) {
2310 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2311 g_free(queueheader_buf);
2313 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2314 "X-Sylpheed-Privacy-System:")) {
2315 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2316 g_free(queueheader_buf);
2318 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2319 "X-Priority: ")) {
2320 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2321 priority = param;
2322 g_free(queueheader_buf);
2324 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2325 "RMID:")) {
2326 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2327 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2328 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2329 if (orig_item != NULL) {
2330 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2332 g_strfreev(tokens);
2334 g_free(queueheader_buf);
2336 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2337 "FMID:")) {
2338 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2339 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2340 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2341 if (orig_item != NULL) {
2342 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2344 g_strfreev(tokens);
2346 g_free(queueheader_buf);
2348 /* Get manual headers */
2349 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2350 "X-Claws-Manual-Headers:")) {
2351 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2352 if (listmh && *listmh != '\0') {
2353 debug_print("Got manual headers: %s\n", listmh);
2354 manual_headers = procheader_entries_from_str(listmh);
2355 g_free(listmh);
2357 g_free(queueheader_buf);
2359 } else {
2360 account = msginfo->folder->folder->account;
2363 if (!account && prefs_common.reedit_account_autosel) {
2364 gchar *from = NULL;
2365 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2366 extract_address(from);
2367 account = account_find_from_address(from, FALSE);
2368 g_free(from);
2371 if (!account) {
2372 account = cur_account;
2374 cm_return_val_if_fail(account != NULL, NULL);
2376 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2378 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2379 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2380 compose->autowrap = autowrap;
2381 compose->replyinfo = replyinfo;
2382 compose->fwdinfo = fwdinfo;
2384 compose->updating = TRUE;
2385 compose->priority = priority;
2387 if (privacy_system != NULL) {
2388 compose->privacy_system = privacy_system;
2389 compose_use_signing(compose, use_signing);
2390 compose_use_encryption(compose, use_encryption);
2391 compose_update_privacy_system_menu_item(compose, FALSE);
2392 } else {
2393 compose_activate_privacy_system(compose, account, FALSE);
2395 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2397 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2398 compose->targetinfo->tags = g_slist_copy(msginfo->tags);
2400 compose_extract_original_charset(compose);
2402 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2403 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2404 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2405 gchar *queueheader_buf = NULL;
2407 /* Set message save folder */
2408 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2409 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2410 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2411 compose_set_save_to(compose, &queueheader_buf[4]);
2412 g_free(queueheader_buf);
2414 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2415 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2416 if (active) {
2417 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2419 g_free(queueheader_buf);
2423 if (compose_parse_header(compose, msginfo) < 0) {
2424 compose->updating = FALSE;
2425 compose_destroy(compose);
2426 return NULL;
2428 compose_reedit_set_entry(compose, msginfo);
2430 textview = GTK_TEXT_VIEW(compose->text);
2431 textbuf = gtk_text_view_get_buffer(textview);
2432 compose_create_tags(textview, compose);
2434 mark = gtk_text_buffer_get_insert(textbuf);
2435 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2437 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2438 G_CALLBACK(compose_changed_cb),
2439 compose);
2441 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2442 fp = procmime_get_first_encrypted_text_content(msginfo);
2443 if (fp) {
2444 compose_force_encryption(compose, account, TRUE, NULL);
2446 } else {
2447 fp = procmime_get_first_text_content(msginfo);
2449 if (fp == NULL) {
2450 g_warning("Can't get text part");
2453 if (fp != NULL) {
2454 gchar buf[BUFFSIZE];
2455 gboolean prev_autowrap;
2456 GtkTextBuffer *buffer;
2457 BLOCK_WRAP();
2458 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2459 strcrchomp(buf);
2460 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2462 UNBLOCK_WRAP();
2463 claws_fclose(fp);
2466 compose_attach_parts(compose, msginfo);
2468 compose_colorize_signature(compose);
2470 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2471 G_CALLBACK(compose_changed_cb),
2472 compose);
2474 if (manual_headers != NULL) {
2475 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2476 procheader_entries_free(manual_headers);
2477 compose->updating = FALSE;
2478 compose_destroy(compose);
2479 return NULL;
2481 procheader_entries_free(manual_headers);
2484 gtk_widget_grab_focus(compose->text);
2486 if (prefs_common.auto_exteditor) {
2487 compose_exec_ext_editor(compose);
2489 compose->modified = FALSE;
2490 compose_set_title(compose);
2492 compose->updating = FALSE;
2493 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2494 SCROLL_TO_CURSOR(compose);
2496 if (compose->deferred_destroy) {
2497 compose_destroy(compose);
2498 return NULL;
2501 compose->sig_str = account_get_signature_str(compose->account);
2503 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2505 return compose;
2508 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2509 gboolean batch)
2511 Compose *compose;
2512 gchar *filename;
2513 FolderItem *item;
2515 cm_return_val_if_fail(msginfo != NULL, NULL);
2517 if (!account)
2518 account = account_get_reply_account(msginfo,
2519 prefs_common.reply_account_autosel);
2520 cm_return_val_if_fail(account != NULL, NULL);
2522 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2523 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2525 compose->updating = TRUE;
2527 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2528 compose->replyinfo = NULL;
2529 compose->fwdinfo = NULL;
2531 compose_show_first_last_header(compose, TRUE);
2533 gtk_widget_grab_focus(compose->header_last->entry);
2535 filename = procmsg_get_message_file(msginfo);
2537 if (filename == NULL) {
2538 compose->updating = FALSE;
2539 compose_destroy(compose);
2541 return NULL;
2544 compose->redirect_filename = filename;
2546 /* Set save folder */
2547 item = msginfo->folder;
2548 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2549 gchar *folderidentifier;
2551 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2552 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2553 folderidentifier = folder_item_get_identifier(item);
2554 compose_set_save_to(compose, folderidentifier);
2555 g_free(folderidentifier);
2558 compose_attach_parts(compose, msginfo);
2560 if (msginfo->subject)
2561 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2562 msginfo->subject);
2563 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2565 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2566 _("The body of the \"Redirect\" template has an error at line %d."));
2567 quote_fmt_reset_vartable();
2568 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2570 compose_colorize_signature(compose);
2573 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2574 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2575 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2577 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2578 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2579 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2580 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2581 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2582 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2583 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2584 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2585 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2587 if (compose->toolbar->draft_btn)
2588 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2589 if (compose->toolbar->insert_btn)
2590 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2591 if (compose->toolbar->attach_btn)
2592 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2593 if (compose->toolbar->sig_btn)
2594 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2595 if (compose->toolbar->exteditor_btn)
2596 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2597 if (compose->toolbar->linewrap_current_btn)
2598 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2599 if (compose->toolbar->linewrap_all_btn)
2600 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2602 compose->modified = FALSE;
2603 compose_set_title(compose);
2604 compose->updating = FALSE;
2605 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2606 SCROLL_TO_CURSOR(compose);
2608 if (compose->deferred_destroy) {
2609 compose_destroy(compose);
2610 return NULL;
2613 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2615 return compose;
2618 const GList *compose_get_compose_list(void)
2620 return compose_list;
2623 void compose_entry_append(Compose *compose, const gchar *address,
2624 ComposeEntryType type, ComposePrefType pref_type)
2626 const gchar *header;
2627 gchar *cur, *begin;
2628 gboolean in_quote = FALSE;
2629 if (!address || *address == '\0') return;
2631 switch (type) {
2632 case COMPOSE_CC:
2633 header = N_("Cc:");
2634 break;
2635 case COMPOSE_BCC:
2636 header = N_("Bcc:");
2637 break;
2638 case COMPOSE_REPLYTO:
2639 header = N_("Reply-To:");
2640 break;
2641 case COMPOSE_NEWSGROUPS:
2642 header = N_("Newsgroups:");
2643 break;
2644 case COMPOSE_FOLLOWUPTO:
2645 header = N_( "Followup-To:");
2646 break;
2647 case COMPOSE_INREPLYTO:
2648 header = N_( "In-Reply-To:");
2649 break;
2650 case COMPOSE_TO:
2651 default:
2652 header = N_("To:");
2653 break;
2655 header = prefs_common_translated_header_name(header);
2657 cur = begin = (gchar *)address;
2659 /* we separate the line by commas, but not if we're inside a quoted
2660 * string */
2661 while (*cur != '\0') {
2662 if (*cur == '"')
2663 in_quote = !in_quote;
2664 if (*cur == ',' && !in_quote) {
2665 gchar *tmp = g_strdup(begin);
2666 gchar *o_tmp = tmp;
2667 tmp[cur-begin]='\0';
2668 cur++;
2669 begin = cur;
2670 while (*tmp == ' ' || *tmp == '\t')
2671 tmp++;
2672 compose_add_header_entry(compose, header, tmp, pref_type);
2673 compose_entry_indicate(compose, tmp);
2674 g_free(o_tmp);
2675 continue;
2677 cur++;
2679 if (begin < cur) {
2680 gchar *tmp = g_strdup(begin);
2681 gchar *o_tmp = tmp;
2682 tmp[cur-begin]='\0';
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);
2691 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2693 GSList *h_list;
2694 GtkEntry *entry;
2696 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2697 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2698 if (gtk_entry_get_text(entry) &&
2699 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2700 gtk_widget_modify_base(
2701 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2702 GTK_STATE_NORMAL, &default_header_bgcolor);
2703 gtk_widget_modify_text(
2704 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2705 GTK_STATE_NORMAL, &default_header_color);
2710 void compose_toolbar_cb(gint action, gpointer data)
2712 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2713 Compose *compose = (Compose*)toolbar_item->parent;
2715 cm_return_if_fail(compose != NULL);
2717 switch(action) {
2718 case A_SEND:
2719 compose_send_cb(NULL, compose);
2720 break;
2721 case A_SEND_LATER:
2722 compose_send_later_cb(NULL, compose);
2723 break;
2724 case A_DRAFT:
2725 compose_draft(compose, COMPOSE_QUIT_EDITING);
2726 break;
2727 case A_INSERT:
2728 compose_insert_file_cb(NULL, compose);
2729 break;
2730 case A_ATTACH:
2731 compose_attach_cb(NULL, compose);
2732 break;
2733 case A_SIG:
2734 compose_insert_sig(compose, FALSE);
2735 break;
2736 case A_REP_SIG:
2737 compose_insert_sig(compose, TRUE);
2738 break;
2739 case A_EXTEDITOR:
2740 compose_ext_editor_cb(NULL, compose);
2741 break;
2742 case A_LINEWRAP_CURRENT:
2743 compose_beautify_paragraph(compose, NULL, TRUE);
2744 break;
2745 case A_LINEWRAP_ALL:
2746 compose_wrap_all_full(compose, TRUE);
2747 break;
2748 case A_ADDRBOOK:
2749 compose_address_cb(NULL, compose);
2750 break;
2751 #ifdef USE_ENCHANT
2752 case A_CHECK_SPELLING:
2753 compose_check_all(NULL, compose);
2754 break;
2755 #endif
2756 case A_PRIVACY_SIGN:
2757 break;
2758 case A_PRIVACY_ENCRYPT:
2759 break;
2760 default:
2761 break;
2765 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2767 gchar *to = NULL;
2768 gchar *cc = NULL;
2769 gchar *bcc = NULL;
2770 gchar *subject = NULL;
2771 gchar *body = NULL;
2772 gchar *temp = NULL;
2773 gsize len = 0;
2774 gchar **attach = NULL;
2775 gchar *inreplyto = NULL;
2776 MailField mfield = NO_FIELD_PRESENT;
2778 /* get mailto parts but skip from */
2779 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2781 if (to) {
2782 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2783 mfield = TO_FIELD_PRESENT;
2785 if (cc)
2786 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2787 if (bcc)
2788 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2789 if (subject) {
2790 if (!g_utf8_validate (subject, -1, NULL)) {
2791 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2792 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2793 g_free(temp);
2794 } else {
2795 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2797 mfield = SUBJECT_FIELD_PRESENT;
2799 if (body) {
2800 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2801 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2802 GtkTextMark *mark;
2803 GtkTextIter iter;
2804 gboolean prev_autowrap = compose->autowrap;
2806 compose->autowrap = FALSE;
2808 mark = gtk_text_buffer_get_insert(buffer);
2809 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2811 if (!g_utf8_validate (body, -1, NULL)) {
2812 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2813 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2814 g_free(temp);
2815 } else {
2816 gtk_text_buffer_insert(buffer, &iter, body, -1);
2818 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2820 compose->autowrap = prev_autowrap;
2821 if (compose->autowrap)
2822 compose_wrap_all(compose);
2823 mfield = BODY_FIELD_PRESENT;
2826 if (attach) {
2827 gint i = 0, att = 0;
2828 gchar *warn_files = NULL;
2829 while (attach[i] != NULL) {
2830 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2831 if (utf8_filename) {
2832 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2833 gchar *tmp = g_strdup_printf("%s%s\n",
2834 warn_files?warn_files:"",
2835 utf8_filename);
2836 g_free(warn_files);
2837 warn_files = tmp;
2838 att++;
2840 g_free(utf8_filename);
2841 } else {
2842 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2844 i++;
2846 if (warn_files) {
2847 alertpanel_notice(ngettext(
2848 "The following file has been attached: \n%s",
2849 "The following files have been attached: \n%s", att), warn_files);
2850 g_free(warn_files);
2853 if (inreplyto)
2854 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2856 g_free(to);
2857 g_free(cc);
2858 g_free(bcc);
2859 g_free(subject);
2860 g_free(body);
2861 g_strfreev(attach);
2862 g_free(inreplyto);
2864 return mfield;
2867 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2869 static HeaderEntry hentry[] = {
2870 {"Reply-To:", NULL, TRUE },
2871 {"Cc:", NULL, TRUE },
2872 {"References:", NULL, FALSE },
2873 {"Bcc:", NULL, TRUE },
2874 {"Newsgroups:", NULL, TRUE },
2875 {"Followup-To:", NULL, TRUE },
2876 {"List-Post:", NULL, FALSE },
2877 {"X-Priority:", NULL, FALSE },
2878 {NULL, NULL, FALSE }
2881 enum
2883 H_REPLY_TO = 0,
2884 H_CC = 1,
2885 H_REFERENCES = 2,
2886 H_BCC = 3,
2887 H_NEWSGROUPS = 4,
2888 H_FOLLOWUP_TO = 5,
2889 H_LIST_POST = 6,
2890 H_X_PRIORITY = 7
2893 FILE *fp;
2895 cm_return_val_if_fail(msginfo != NULL, -1);
2897 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
2898 procheader_get_header_fields(fp, hentry);
2899 claws_fclose(fp);
2901 if (hentry[H_REPLY_TO].body != NULL) {
2902 if (hentry[H_REPLY_TO].body[0] != '\0') {
2903 compose->replyto =
2904 conv_unmime_header(hentry[H_REPLY_TO].body,
2905 NULL, TRUE);
2907 g_free(hentry[H_REPLY_TO].body);
2908 hentry[H_REPLY_TO].body = NULL;
2910 if (hentry[H_CC].body != NULL) {
2911 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2912 g_free(hentry[H_CC].body);
2913 hentry[H_CC].body = NULL;
2915 if (hentry[H_REFERENCES].body != NULL) {
2916 if (compose->mode == COMPOSE_REEDIT)
2917 compose->references = hentry[H_REFERENCES].body;
2918 else {
2919 compose->references = compose_parse_references
2920 (hentry[H_REFERENCES].body, msginfo->msgid);
2921 g_free(hentry[H_REFERENCES].body);
2923 hentry[H_REFERENCES].body = NULL;
2925 if (hentry[H_BCC].body != NULL) {
2926 if (compose->mode == COMPOSE_REEDIT)
2927 compose->bcc =
2928 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2929 g_free(hentry[H_BCC].body);
2930 hentry[H_BCC].body = NULL;
2932 if (hentry[H_NEWSGROUPS].body != NULL) {
2933 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2934 hentry[H_NEWSGROUPS].body = NULL;
2936 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2937 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2938 compose->followup_to =
2939 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2940 NULL, TRUE);
2942 g_free(hentry[H_FOLLOWUP_TO].body);
2943 hentry[H_FOLLOWUP_TO].body = NULL;
2945 if (hentry[H_LIST_POST].body != NULL) {
2946 gchar *to = NULL, *start = NULL;
2948 extract_address(hentry[H_LIST_POST].body);
2949 if (hentry[H_LIST_POST].body[0] != '\0') {
2950 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2952 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2953 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2955 if (to) {
2956 g_free(compose->ml_post);
2957 compose->ml_post = to;
2960 g_free(hentry[H_LIST_POST].body);
2961 hentry[H_LIST_POST].body = NULL;
2964 /* CLAWS - X-Priority */
2965 if (compose->mode == COMPOSE_REEDIT)
2966 if (hentry[H_X_PRIORITY].body != NULL) {
2967 gint priority;
2969 priority = atoi(hentry[H_X_PRIORITY].body);
2970 g_free(hentry[H_X_PRIORITY].body);
2972 hentry[H_X_PRIORITY].body = NULL;
2974 if (priority < PRIORITY_HIGHEST ||
2975 priority > PRIORITY_LOWEST)
2976 priority = PRIORITY_NORMAL;
2978 compose->priority = priority;
2981 if (compose->mode == COMPOSE_REEDIT) {
2982 if (msginfo->inreplyto && *msginfo->inreplyto)
2983 compose->inreplyto = g_strdup(msginfo->inreplyto);
2985 if (msginfo->msgid && *msginfo->msgid &&
2986 compose->folder != NULL &&
2987 compose->folder->stype == F_DRAFT)
2988 compose->msgid = g_strdup(msginfo->msgid);
2989 } else {
2990 if (msginfo->msgid && *msginfo->msgid)
2991 compose->inreplyto = g_strdup(msginfo->msgid);
2993 if (!compose->references) {
2994 if (msginfo->msgid && *msginfo->msgid) {
2995 if (msginfo->inreplyto && *msginfo->inreplyto)
2996 compose->references =
2997 g_strdup_printf("<%s>\n\t<%s>",
2998 msginfo->inreplyto,
2999 msginfo->msgid);
3000 else
3001 compose->references =
3002 g_strconcat("<", msginfo->msgid, ">",
3003 NULL);
3004 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3005 compose->references =
3006 g_strconcat("<", msginfo->inreplyto, ">",
3007 NULL);
3012 return 0;
3015 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3017 FILE *fp;
3018 HeaderEntry *he;
3020 cm_return_val_if_fail(msginfo != NULL, -1);
3022 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
3023 procheader_get_header_fields(fp, entries);
3024 claws_fclose(fp);
3026 he = entries;
3027 while (he != NULL && he->name != NULL) {
3028 GtkTreeIter iter;
3029 GtkListStore *model = NULL;
3031 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3032 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3033 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3034 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3035 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3036 ++he;
3039 return 0;
3042 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3044 GSList *ref_id_list, *cur;
3045 GString *new_ref;
3046 gchar *new_ref_str;
3048 ref_id_list = references_list_append(NULL, ref);
3049 if (!ref_id_list) return NULL;
3050 if (msgid && *msgid)
3051 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3053 for (;;) {
3054 gint len = 0;
3056 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3057 /* "<" + Message-ID + ">" + CR+LF+TAB */
3058 len += strlen((gchar *)cur->data) + 5;
3060 if (len > MAX_REFERENCES_LEN) {
3061 /* remove second message-ID */
3062 if (ref_id_list && ref_id_list->next &&
3063 ref_id_list->next->next) {
3064 g_free(ref_id_list->next->data);
3065 ref_id_list = g_slist_remove
3066 (ref_id_list, ref_id_list->next->data);
3067 } else {
3068 slist_free_strings_full(ref_id_list);
3069 return NULL;
3071 } else
3072 break;
3075 new_ref = g_string_new("");
3076 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3077 if (new_ref->len > 0)
3078 g_string_append(new_ref, "\n\t");
3079 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3082 slist_free_strings_full(ref_id_list);
3084 new_ref_str = new_ref->str;
3085 g_string_free(new_ref, FALSE);
3087 return new_ref_str;
3090 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3091 const gchar *fmt, const gchar *qmark,
3092 const gchar *body, gboolean rewrap,
3093 gboolean need_unescape,
3094 const gchar *err_msg)
3096 MsgInfo* dummyinfo = NULL;
3097 gchar *quote_str = NULL;
3098 gchar *buf;
3099 gboolean prev_autowrap;
3100 const gchar *trimmed_body = body;
3101 gint cursor_pos = -1;
3102 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3103 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3104 GtkTextIter iter;
3105 GtkTextMark *mark;
3108 SIGNAL_BLOCK(buffer);
3110 if (!msginfo) {
3111 dummyinfo = compose_msginfo_new_from_compose(compose);
3112 msginfo = dummyinfo;
3115 if (qmark != NULL) {
3116 #ifdef USE_ENCHANT
3117 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3118 compose->gtkaspell);
3119 #else
3120 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3121 #endif
3122 quote_fmt_scan_string(qmark);
3123 quote_fmt_parse();
3125 buf = quote_fmt_get_buffer();
3127 if (buf == NULL)
3128 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3129 else
3130 Xstrdup_a(quote_str, buf, goto error)
3133 if (fmt && *fmt != '\0') {
3135 if (trimmed_body)
3136 while (*trimmed_body == '\n')
3137 trimmed_body++;
3139 #ifdef USE_ENCHANT
3140 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3141 compose->gtkaspell);
3142 #else
3143 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3144 #endif
3145 if (need_unescape) {
3146 gchar *tmp = NULL;
3148 /* decode \-escape sequences in the internal representation of the quote format */
3149 tmp = g_malloc(strlen(fmt)+1);
3150 pref_get_unescaped_pref(tmp, fmt);
3151 quote_fmt_scan_string(tmp);
3152 quote_fmt_parse();
3153 g_free(tmp);
3154 } else {
3155 quote_fmt_scan_string(fmt);
3156 quote_fmt_parse();
3159 buf = quote_fmt_get_buffer();
3161 if (buf == NULL) {
3162 gint line = quote_fmt_get_line();
3163 alertpanel_error(err_msg, line);
3165 goto error;
3168 } else
3169 buf = "";
3171 prev_autowrap = compose->autowrap;
3172 compose->autowrap = FALSE;
3174 mark = gtk_text_buffer_get_insert(buffer);
3175 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3176 if (g_utf8_validate(buf, -1, NULL)) {
3177 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3178 } else {
3179 gchar *tmpout = NULL;
3180 tmpout = conv_codeset_strdup
3181 (buf, conv_get_locale_charset_str_no_utf8(),
3182 CS_INTERNAL);
3183 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3184 g_free(tmpout);
3185 tmpout = g_malloc(strlen(buf)*2+1);
3186 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3188 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3189 g_free(tmpout);
3192 cursor_pos = quote_fmt_get_cursor_pos();
3193 if (cursor_pos == -1)
3194 cursor_pos = gtk_text_iter_get_offset(&iter);
3195 compose->set_cursor_pos = cursor_pos;
3197 gtk_text_buffer_get_start_iter(buffer, &iter);
3198 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3199 gtk_text_buffer_place_cursor(buffer, &iter);
3201 compose->autowrap = prev_autowrap;
3202 if (compose->autowrap && rewrap)
3203 compose_wrap_all(compose);
3205 goto ok;
3207 error:
3208 buf = NULL;
3210 SIGNAL_UNBLOCK(buffer);
3212 procmsg_msginfo_free( &dummyinfo );
3214 return buf;
3217 /* if ml_post is of type addr@host and from is of type
3218 * addr-anything@host, return TRUE
3220 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3222 gchar *left_ml = NULL;
3223 gchar *right_ml = NULL;
3224 gchar *left_from = NULL;
3225 gchar *right_from = NULL;
3226 gboolean result = FALSE;
3228 if (!ml_post || !from)
3229 return FALSE;
3231 left_ml = g_strdup(ml_post);
3232 if (strstr(left_ml, "@")) {
3233 right_ml = strstr(left_ml, "@")+1;
3234 *(strstr(left_ml, "@")) = '\0';
3237 left_from = g_strdup(from);
3238 if (strstr(left_from, "@")) {
3239 right_from = strstr(left_from, "@")+1;
3240 *(strstr(left_from, "@")) = '\0';
3243 if (right_ml && right_from
3244 && !strncmp(left_from, left_ml, strlen(left_ml))
3245 && !strcmp(right_from, right_ml)) {
3246 result = TRUE;
3248 g_free(left_ml);
3249 g_free(left_from);
3251 return result;
3254 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3255 gboolean respect_default_to)
3257 if (!compose)
3258 return;
3259 if (!folder || !folder->prefs)
3260 return;
3262 if (respect_default_to && folder->prefs->enable_default_to) {
3263 compose_entry_append(compose, folder->prefs->default_to,
3264 COMPOSE_TO, PREF_FOLDER);
3265 compose_entry_indicate(compose, folder->prefs->default_to);
3267 if (folder->prefs->enable_default_cc) {
3268 compose_entry_append(compose, folder->prefs->default_cc,
3269 COMPOSE_CC, PREF_FOLDER);
3270 compose_entry_indicate(compose, folder->prefs->default_cc);
3272 if (folder->prefs->enable_default_bcc) {
3273 compose_entry_append(compose, folder->prefs->default_bcc,
3274 COMPOSE_BCC, PREF_FOLDER);
3275 compose_entry_indicate(compose, folder->prefs->default_bcc);
3277 if (folder->prefs->enable_default_replyto) {
3278 compose_entry_append(compose, folder->prefs->default_replyto,
3279 COMPOSE_REPLYTO, PREF_FOLDER);
3280 compose_entry_indicate(compose, folder->prefs->default_replyto);
3284 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3286 gchar *buf, *buf2;
3287 gchar *p;
3289 if (!compose || !msginfo)
3290 return;
3292 if (msginfo->subject && *msginfo->subject) {
3293 buf = p = g_strdup(msginfo->subject);
3294 p += subject_get_prefix_length(p);
3295 memmove(buf, p, strlen(p) + 1);
3297 buf2 = g_strdup_printf("Re: %s", buf);
3298 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3300 g_free(buf2);
3301 g_free(buf);
3302 } else
3303 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3306 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3307 gboolean to_all, gboolean to_ml,
3308 gboolean to_sender,
3309 gboolean followup_and_reply_to)
3311 GSList *cc_list = NULL;
3312 GSList *cur;
3313 gchar *from = NULL;
3314 gchar *replyto = NULL;
3315 gchar *ac_email = NULL;
3317 gboolean reply_to_ml = FALSE;
3318 gboolean default_reply_to = FALSE;
3320 cm_return_if_fail(compose->account != NULL);
3321 cm_return_if_fail(msginfo != NULL);
3323 reply_to_ml = to_ml && compose->ml_post;
3325 default_reply_to = msginfo->folder &&
3326 msginfo->folder->prefs->enable_default_reply_to;
3328 if (compose->account->protocol != A_NNTP) {
3329 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3331 if (reply_to_ml && !default_reply_to) {
3333 gboolean is_subscr = is_subscription(compose->ml_post,
3334 msginfo->from);
3335 if (!is_subscr) {
3336 /* normal answer to ml post with a reply-to */
3337 compose_entry_append(compose,
3338 compose->ml_post,
3339 COMPOSE_TO, PREF_ML);
3340 if (compose->replyto)
3341 compose_entry_append(compose,
3342 compose->replyto,
3343 COMPOSE_CC, PREF_ML);
3344 } else {
3345 /* answer to subscription confirmation */
3346 if (compose->replyto)
3347 compose_entry_append(compose,
3348 compose->replyto,
3349 COMPOSE_TO, PREF_ML);
3350 else if (msginfo->from)
3351 compose_entry_append(compose,
3352 msginfo->from,
3353 COMPOSE_TO, PREF_ML);
3356 else if (!(to_all || to_sender) && default_reply_to) {
3357 compose_entry_append(compose,
3358 msginfo->folder->prefs->default_reply_to,
3359 COMPOSE_TO, PREF_FOLDER);
3360 compose_entry_indicate(compose,
3361 msginfo->folder->prefs->default_reply_to);
3362 } else {
3363 gchar *tmp1 = NULL;
3364 if (!msginfo->from)
3365 return;
3366 if (to_sender)
3367 compose_entry_append(compose, msginfo->from,
3368 COMPOSE_TO, PREF_NONE);
3369 else if (to_all) {
3370 Xstrdup_a(tmp1, msginfo->from, return);
3371 extract_address(tmp1);
3372 compose_entry_append(compose,
3373 (!account_find_from_address(tmp1, FALSE))
3374 ? msginfo->from :
3375 msginfo->to,
3376 COMPOSE_TO, PREF_NONE);
3377 if (compose->replyto)
3378 compose_entry_append(compose,
3379 compose->replyto,
3380 COMPOSE_CC, PREF_NONE);
3381 } else {
3382 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3383 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3384 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3385 if (compose->replyto) {
3386 compose_entry_append(compose,
3387 compose->replyto,
3388 COMPOSE_TO, PREF_NONE);
3389 } else {
3390 compose_entry_append(compose,
3391 msginfo->from ? msginfo->from : "",
3392 COMPOSE_TO, PREF_NONE);
3394 } else {
3395 /* replying to own mail, use original recp */
3396 compose_entry_append(compose,
3397 msginfo->to ? msginfo->to : "",
3398 COMPOSE_TO, PREF_NONE);
3399 compose_entry_append(compose,
3400 msginfo->cc ? msginfo->cc : "",
3401 COMPOSE_CC, PREF_NONE);
3405 } else {
3406 if (to_sender || (compose->followup_to &&
3407 !strncmp(compose->followup_to, "poster", 6)))
3408 compose_entry_append
3409 (compose,
3410 (compose->replyto ? compose->replyto :
3411 msginfo->from ? msginfo->from : ""),
3412 COMPOSE_TO, PREF_NONE);
3414 else if (followup_and_reply_to || to_all) {
3415 compose_entry_append
3416 (compose,
3417 (compose->replyto ? compose->replyto :
3418 msginfo->from ? msginfo->from : ""),
3419 COMPOSE_TO, PREF_NONE);
3421 compose_entry_append
3422 (compose,
3423 compose->followup_to ? compose->followup_to :
3424 compose->newsgroups ? compose->newsgroups : "",
3425 COMPOSE_NEWSGROUPS, PREF_NONE);
3427 compose_entry_append
3428 (compose,
3429 msginfo->cc ? msginfo->cc : "",
3430 COMPOSE_CC, PREF_NONE);
3432 else
3433 compose_entry_append
3434 (compose,
3435 compose->followup_to ? compose->followup_to :
3436 compose->newsgroups ? compose->newsgroups : "",
3437 COMPOSE_NEWSGROUPS, PREF_NONE);
3439 compose_reply_set_subject(compose, msginfo);
3441 if (to_ml && compose->ml_post) return;
3442 if (!to_all || compose->account->protocol == A_NNTP) return;
3444 if (compose->replyto) {
3445 Xstrdup_a(replyto, compose->replyto, return);
3446 extract_address(replyto);
3448 if (msginfo->from) {
3449 Xstrdup_a(from, msginfo->from, return);
3450 extract_address(from);
3453 if (replyto && from)
3454 cc_list = address_list_append_with_comments(cc_list, from);
3455 if (to_all && msginfo->folder &&
3456 msginfo->folder->prefs->enable_default_reply_to)
3457 cc_list = address_list_append_with_comments(cc_list,
3458 msginfo->folder->prefs->default_reply_to);
3459 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3460 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3462 ac_email = g_utf8_strdown(compose->account->address, -1);
3464 if (cc_list) {
3465 for (cur = cc_list; cur != NULL; cur = cur->next) {
3466 gchar *addr = g_utf8_strdown(cur->data, -1);
3467 extract_address(addr);
3469 if (strcmp(ac_email, addr))
3470 compose_entry_append(compose, (gchar *)cur->data,
3471 COMPOSE_CC, PREF_NONE);
3472 else
3473 debug_print("Cc address same as compose account's, ignoring\n");
3475 g_free(addr);
3478 slist_free_strings_full(cc_list);
3481 g_free(ac_email);
3484 #define SET_ENTRY(entry, str) \
3486 if (str && *str) \
3487 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3490 #define SET_ADDRESS(type, str) \
3492 if (str && *str) \
3493 compose_entry_append(compose, str, type, PREF_NONE); \
3496 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3498 cm_return_if_fail(msginfo != NULL);
3500 SET_ENTRY(subject_entry, msginfo->subject);
3501 SET_ENTRY(from_name, msginfo->from);
3502 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3503 SET_ADDRESS(COMPOSE_CC, compose->cc);
3504 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3505 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3506 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3507 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3509 compose_update_priority_menu_item(compose);
3510 compose_update_privacy_system_menu_item(compose, FALSE);
3511 compose_show_first_last_header(compose, TRUE);
3514 #undef SET_ENTRY
3515 #undef SET_ADDRESS
3517 static void compose_insert_sig(Compose *compose, gboolean replace)
3519 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3520 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3521 GtkTextMark *mark;
3522 GtkTextIter iter, iter_end;
3523 gint cur_pos, ins_pos;
3524 gboolean prev_autowrap;
3525 gboolean found = FALSE;
3526 gboolean exists = FALSE;
3528 cm_return_if_fail(compose->account != NULL);
3530 BLOCK_WRAP();
3532 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3533 G_CALLBACK(compose_changed_cb),
3534 compose);
3536 mark = gtk_text_buffer_get_insert(buffer);
3537 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3538 cur_pos = gtk_text_iter_get_offset (&iter);
3539 ins_pos = cur_pos;
3541 gtk_text_buffer_get_end_iter(buffer, &iter);
3543 exists = (compose->sig_str != NULL);
3545 if (replace) {
3546 GtkTextIter first_iter, start_iter, end_iter;
3548 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3550 if (!exists || compose->sig_str[0] == '\0')
3551 found = FALSE;
3552 else
3553 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3554 compose->signature_tag);
3556 if (found) {
3557 /* include previous \n\n */
3558 gtk_text_iter_backward_chars(&first_iter, 1);
3559 start_iter = first_iter;
3560 end_iter = first_iter;
3561 /* skip re-start */
3562 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3563 compose->signature_tag);
3564 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3565 compose->signature_tag);
3566 if (found) {
3567 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3568 iter = start_iter;
3573 g_free(compose->sig_str);
3574 compose->sig_str = account_get_signature_str(compose->account);
3576 cur_pos = gtk_text_iter_get_offset(&iter);
3578 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3579 g_free(compose->sig_str);
3580 compose->sig_str = NULL;
3581 } else {
3582 if (compose->sig_inserted == FALSE)
3583 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3584 compose->sig_inserted = TRUE;
3586 cur_pos = gtk_text_iter_get_offset(&iter);
3587 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3588 /* remove \n\n */
3589 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3590 gtk_text_iter_forward_chars(&iter, 1);
3591 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3592 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3594 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3595 cur_pos = gtk_text_buffer_get_char_count (buffer);
3598 /* put the cursor where it should be
3599 * either where the quote_fmt says, either where it was */
3600 if (compose->set_cursor_pos < 0)
3601 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3602 else
3603 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3604 compose->set_cursor_pos);
3606 compose->set_cursor_pos = -1;
3607 gtk_text_buffer_place_cursor(buffer, &iter);
3608 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3609 G_CALLBACK(compose_changed_cb),
3610 compose);
3612 UNBLOCK_WRAP();
3615 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3617 GtkTextView *text;
3618 GtkTextBuffer *buffer;
3619 GtkTextMark *mark;
3620 GtkTextIter iter;
3621 const gchar *cur_encoding;
3622 gchar buf[BUFFSIZE];
3623 gint len;
3624 FILE *fp;
3625 gboolean prev_autowrap;
3626 #ifdef G_OS_WIN32
3627 GFile *f;
3628 GFileInfo *fi;
3629 GError *error = NULL;
3630 #else
3631 GStatBuf file_stat;
3632 #endif
3633 int ret;
3634 goffset size;
3635 GString *file_contents = NULL;
3636 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3638 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3640 /* get the size of the file we are about to insert */
3641 #ifdef G_OS_WIN32
3642 f = g_file_new_for_path(file);
3643 fi = g_file_query_info(f, "standard::size",
3644 G_FILE_QUERY_INFO_NONE, NULL, &error);
3645 ret = 0;
3646 if (error != NULL) {
3647 g_warning(error->message);
3648 ret = 1;
3649 g_error_free(error);
3650 g_object_unref(f);
3652 #else
3653 ret = g_stat(file, &file_stat);
3654 #endif
3655 if (ret != 0) {
3656 gchar *shortfile = g_path_get_basename(file);
3657 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3658 g_free(shortfile);
3659 return COMPOSE_INSERT_NO_FILE;
3660 } else if (prefs_common.warn_large_insert == TRUE) {
3661 #ifdef G_OS_WIN32
3662 size = g_file_info_get_size(fi);
3663 g_object_unref(fi);
3664 g_object_unref(f);
3665 #else
3666 size = file_stat.st_size;
3667 #endif
3669 /* ask user for confirmation if the file is large */
3670 if (prefs_common.warn_large_insert_size < 0 ||
3671 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3672 AlertValue aval;
3673 gchar *msg;
3675 msg = g_strdup_printf(_("You are about to insert a file of %s "
3676 "in the message body. Are you sure you want to do that?"),
3677 to_human_readable(size));
3678 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3679 _("_Insert"), NULL, ALERTFOCUS_SECOND, TRUE,
3680 NULL, ALERT_QUESTION);
3681 g_free(msg);
3683 /* do we ask for confirmation next time? */
3684 if (aval & G_ALERTDISABLE) {
3685 /* no confirmation next time, disable feature in preferences */
3686 aval &= ~G_ALERTDISABLE;
3687 prefs_common.warn_large_insert = FALSE;
3690 /* abort file insertion if user canceled action */
3691 if (aval != G_ALERTALTERNATE) {
3692 return COMPOSE_INSERT_NO_FILE;
3698 if ((fp = claws_fopen(file, "rb")) == NULL) {
3699 FILE_OP_ERROR(file, "claws_fopen");
3700 return COMPOSE_INSERT_READ_ERROR;
3703 prev_autowrap = compose->autowrap;
3704 compose->autowrap = FALSE;
3706 text = GTK_TEXT_VIEW(compose->text);
3707 buffer = gtk_text_view_get_buffer(text);
3708 mark = gtk_text_buffer_get_insert(buffer);
3709 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3711 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3712 G_CALLBACK(text_inserted),
3713 compose);
3715 cur_encoding = conv_get_locale_charset_str_no_utf8();
3717 file_contents = g_string_new("");
3718 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3719 gchar *str;
3721 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3722 str = g_strdup(buf);
3723 else {
3724 codeconv_set_strict(TRUE);
3725 str = conv_codeset_strdup
3726 (buf, cur_encoding, CS_INTERNAL);
3727 codeconv_set_strict(FALSE);
3729 if (!str) {
3730 result = COMPOSE_INSERT_INVALID_CHARACTER;
3731 break;
3734 if (!str) continue;
3736 /* strip <CR> if DOS/Windows file,
3737 replace <CR> with <LF> if Macintosh file. */
3738 strcrchomp(str);
3739 len = strlen(str);
3740 if (len > 0 && str[len - 1] != '\n') {
3741 while (--len >= 0)
3742 if (str[len] == '\r') str[len] = '\n';
3745 file_contents = g_string_append(file_contents, str);
3746 g_free(str);
3749 if (result == COMPOSE_INSERT_SUCCESS) {
3750 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3752 compose_changed_cb(NULL, compose);
3753 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3754 G_CALLBACK(text_inserted),
3755 compose);
3756 compose->autowrap = prev_autowrap;
3757 if (compose->autowrap)
3758 compose_wrap_all(compose);
3761 g_string_free(file_contents, TRUE);
3762 claws_fclose(fp);
3764 return result;
3767 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3768 const gchar *filename,
3769 const gchar *content_type,
3770 const gchar *charset)
3772 AttachInfo *ainfo;
3773 GtkTreeIter iter;
3774 FILE *fp;
3775 off_t size;
3776 GAuto *auto_ainfo;
3777 gchar *size_text;
3778 GtkListStore *store;
3779 gchar *name;
3780 gboolean has_binary = FALSE;
3782 if (!is_file_exist(file)) {
3783 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3784 gboolean result = FALSE;
3785 if (file_from_uri && is_file_exist(file_from_uri)) {
3786 result = compose_attach_append(
3787 compose, file_from_uri,
3788 filename, content_type,
3789 charset);
3791 g_free(file_from_uri);
3792 if (result)
3793 return TRUE;
3794 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3795 return FALSE;
3797 if ((size = get_file_size(file)) < 0) {
3798 alertpanel_error("Can't get file size of %s\n", filename);
3799 return FALSE;
3802 /* In batch mode, we allow 0-length files to be attached no questions asked */
3803 if (size == 0 && !compose->batch) {
3804 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3805 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3806 GTK_STOCK_CANCEL, _("_Attach anyway"), NULL,
3807 ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3808 g_free(msg);
3810 if (aval != G_ALERTALTERNATE) {
3811 return FALSE;
3814 if ((fp = claws_fopen(file, "rb")) == NULL) {
3815 alertpanel_error(_("Can't read %s."), filename);
3816 return FALSE;
3818 claws_fclose(fp);
3820 ainfo = g_new0(AttachInfo, 1);
3821 auto_ainfo = g_auto_pointer_new_with_free
3822 (ainfo, (GFreeFunc) compose_attach_info_free);
3823 ainfo->file = g_strdup(file);
3825 if (content_type) {
3826 ainfo->content_type = g_strdup(content_type);
3827 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3828 MsgInfo *msginfo;
3829 MsgFlags flags = {0, 0};
3831 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3832 ainfo->encoding = ENC_7BIT;
3833 else
3834 ainfo->encoding = ENC_8BIT;
3836 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3837 if (msginfo && msginfo->subject)
3838 name = g_strdup(msginfo->subject);
3839 else
3840 name = g_path_get_basename(filename ? filename : file);
3842 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3844 procmsg_msginfo_free(&msginfo);
3845 } else {
3846 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3847 ainfo->charset = g_strdup(charset);
3848 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3849 } else {
3850 ainfo->encoding = ENC_BASE64;
3852 name = g_path_get_basename(filename ? filename : file);
3853 ainfo->name = g_strdup(name);
3855 g_free(name);
3856 } else {
3857 ainfo->content_type = procmime_get_mime_type(file);
3858 if (!ainfo->content_type) {
3859 ainfo->content_type =
3860 g_strdup("application/octet-stream");
3861 ainfo->encoding = ENC_BASE64;
3862 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3863 ainfo->encoding =
3864 procmime_get_encoding_for_text_file(file, &has_binary);
3865 else
3866 ainfo->encoding = ENC_BASE64;
3867 name = g_path_get_basename(filename ? filename : file);
3868 ainfo->name = g_strdup(name);
3869 g_free(name);
3872 if (ainfo->name != NULL
3873 && !strcmp(ainfo->name, ".")) {
3874 g_free(ainfo->name);
3875 ainfo->name = NULL;
3878 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3879 g_free(ainfo->content_type);
3880 ainfo->content_type = g_strdup("application/octet-stream");
3881 g_free(ainfo->charset);
3882 ainfo->charset = NULL;
3885 ainfo->size = (goffset)size;
3886 size_text = to_human_readable((goffset)size);
3888 store = GTK_LIST_STORE(gtk_tree_view_get_model
3889 (GTK_TREE_VIEW(compose->attach_clist)));
3891 gtk_list_store_append(store, &iter);
3892 gtk_list_store_set(store, &iter,
3893 COL_MIMETYPE, ainfo->content_type,
3894 COL_SIZE, size_text,
3895 COL_NAME, ainfo->name,
3896 COL_CHARSET, ainfo->charset,
3897 COL_DATA, ainfo,
3898 COL_AUTODATA, auto_ainfo,
3899 -1);
3901 g_auto_pointer_free(auto_ainfo);
3902 compose_attach_update_label(compose);
3903 return TRUE;
3906 void compose_use_signing(Compose *compose, gboolean use_signing)
3908 compose->use_signing = use_signing;
3909 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3912 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3914 compose->use_encryption = use_encryption;
3915 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3918 #define NEXT_PART_NOT_CHILD(info) \
3920 node = info->node; \
3921 while (node->children) \
3922 node = g_node_last_child(node); \
3923 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3926 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3928 MimeInfo *mimeinfo;
3929 MimeInfo *child;
3930 MimeInfo *firsttext = NULL;
3931 MimeInfo *encrypted = NULL;
3932 GNode *node;
3933 gchar *outfile;
3934 const gchar *partname = NULL;
3936 mimeinfo = procmime_scan_message(msginfo);
3937 if (!mimeinfo) return;
3939 if (mimeinfo->node->children == NULL) {
3940 procmime_mimeinfo_free_all(&mimeinfo);
3941 return;
3944 /* find first content part */
3945 child = (MimeInfo *) mimeinfo->node->children->data;
3946 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3947 child = (MimeInfo *)child->node->children->data;
3949 if (child) {
3950 if (child->type == MIMETYPE_TEXT) {
3951 firsttext = child;
3952 debug_print("First text part found\n");
3953 } else if (compose->mode == COMPOSE_REEDIT &&
3954 child->type == MIMETYPE_APPLICATION &&
3955 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3956 encrypted = (MimeInfo *)child->node->parent->data;
3959 child = (MimeInfo *) mimeinfo->node->children->data;
3960 while (child != NULL) {
3961 gint err;
3963 if (child == encrypted) {
3964 /* skip this part of tree */
3965 NEXT_PART_NOT_CHILD(child);
3966 continue;
3969 if (child->type == MIMETYPE_MULTIPART) {
3970 /* get the actual content */
3971 child = procmime_mimeinfo_next(child);
3972 continue;
3975 if (child == firsttext) {
3976 child = procmime_mimeinfo_next(child);
3977 continue;
3980 outfile = procmime_get_tmp_file_name(child);
3981 if ((err = procmime_get_part(outfile, child)) < 0)
3982 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3983 else {
3984 gchar *content_type;
3986 content_type = procmime_get_content_type_str(child->type, child->subtype);
3988 /* if we meet a pgp signature, we don't attach it, but
3989 * we force signing. */
3990 if ((strcmp(content_type, "application/pgp-signature") &&
3991 strcmp(content_type, "application/pkcs7-signature") &&
3992 strcmp(content_type, "application/x-pkcs7-signature"))
3993 || compose->mode == COMPOSE_REDIRECT) {
3994 partname = procmime_mimeinfo_get_parameter(child, "filename");
3995 if (partname == NULL)
3996 partname = procmime_mimeinfo_get_parameter(child, "name");
3997 if (partname == NULL)
3998 partname = "";
3999 compose_attach_append(compose, outfile,
4000 partname, content_type,
4001 procmime_mimeinfo_get_parameter(child, "charset"));
4002 } else {
4003 compose_force_signing(compose, compose->account, NULL);
4005 g_free(content_type);
4007 g_free(outfile);
4008 NEXT_PART_NOT_CHILD(child);
4010 procmime_mimeinfo_free_all(&mimeinfo);
4013 #undef NEXT_PART_NOT_CHILD
4017 typedef enum {
4018 WAIT_FOR_INDENT_CHAR,
4019 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4020 } IndentState;
4022 /* return indent length, we allow:
4023 indent characters followed by indent characters or spaces/tabs,
4024 alphabets and numbers immediately followed by indent characters,
4025 and the repeating sequences of the above
4026 If quote ends with multiple spaces, only the first one is included. */
4027 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4028 const GtkTextIter *start, gint *len)
4030 GtkTextIter iter = *start;
4031 gunichar wc;
4032 gchar ch[6];
4033 gint clen;
4034 IndentState state = WAIT_FOR_INDENT_CHAR;
4035 gboolean is_space;
4036 gboolean is_indent;
4037 gint alnum_count = 0;
4038 gint space_count = 0;
4039 gint quote_len = 0;
4041 if (prefs_common.quote_chars == NULL) {
4042 return 0 ;
4045 while (!gtk_text_iter_ends_line(&iter)) {
4046 wc = gtk_text_iter_get_char(&iter);
4047 if (g_unichar_iswide(wc))
4048 break;
4049 clen = g_unichar_to_utf8(wc, ch);
4050 if (clen != 1)
4051 break;
4053 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4054 is_space = g_unichar_isspace(wc);
4056 if (state == WAIT_FOR_INDENT_CHAR) {
4057 if (!is_indent && !g_unichar_isalnum(wc))
4058 break;
4059 if (is_indent) {
4060 quote_len += alnum_count + space_count + 1;
4061 alnum_count = space_count = 0;
4062 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4063 } else
4064 alnum_count++;
4065 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4066 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4067 break;
4068 if (is_space)
4069 space_count++;
4070 else if (is_indent) {
4071 quote_len += alnum_count + space_count + 1;
4072 alnum_count = space_count = 0;
4073 } else {
4074 alnum_count++;
4075 state = WAIT_FOR_INDENT_CHAR;
4079 gtk_text_iter_forward_char(&iter);
4082 if (quote_len > 0 && space_count > 0)
4083 quote_len++;
4085 if (len)
4086 *len = quote_len;
4088 if (quote_len > 0) {
4089 iter = *start;
4090 gtk_text_iter_forward_chars(&iter, quote_len);
4091 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4094 return NULL;
4097 /* return >0 if the line is itemized */
4098 static int compose_itemized_length(GtkTextBuffer *buffer,
4099 const GtkTextIter *start)
4101 GtkTextIter iter = *start;
4102 gunichar wc;
4103 gchar ch[6];
4104 gint clen;
4105 gint len = 0;
4106 if (gtk_text_iter_ends_line(&iter))
4107 return 0;
4109 while (1) {
4110 len++;
4111 wc = gtk_text_iter_get_char(&iter);
4112 if (!g_unichar_isspace(wc))
4113 break;
4114 gtk_text_iter_forward_char(&iter);
4115 if (gtk_text_iter_ends_line(&iter))
4116 return 0;
4119 clen = g_unichar_to_utf8(wc, ch);
4120 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4121 (clen == 3 && (
4122 wc == 0x2022 || /* BULLET */
4123 wc == 0x2023 || /* TRIANGULAR BULLET */
4124 wc == 0x2043 || /* HYPHEN BULLET */
4125 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4126 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4127 wc == 0x2219 || /* BULLET OPERATOR */
4128 wc == 0x25d8 || /* INVERSE BULLET */
4129 wc == 0x25e6 || /* WHITE BULLET */
4130 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4131 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4132 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4133 wc == 0x29be || /* CIRCLED WHITE BULLET */
4134 wc == 0x29bf /* CIRCLED BULLET */
4135 ))))
4136 return 0;
4138 gtk_text_iter_forward_char(&iter);
4139 if (gtk_text_iter_ends_line(&iter))
4140 return 0;
4141 wc = gtk_text_iter_get_char(&iter);
4142 if (g_unichar_isspace(wc)) {
4143 return len+1;
4145 return 0;
4148 /* return the string at the start of the itemization */
4149 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4150 const GtkTextIter *start)
4152 GtkTextIter iter = *start;
4153 gunichar wc;
4154 gint len = 0;
4155 GString *item_chars = g_string_new("");
4156 gchar *str = NULL;
4158 if (gtk_text_iter_ends_line(&iter))
4159 return NULL;
4161 while (1) {
4162 len++;
4163 wc = gtk_text_iter_get_char(&iter);
4164 if (!g_unichar_isspace(wc))
4165 break;
4166 gtk_text_iter_forward_char(&iter);
4167 if (gtk_text_iter_ends_line(&iter))
4168 break;
4169 g_string_append_unichar(item_chars, wc);
4172 str = item_chars->str;
4173 g_string_free(item_chars, FALSE);
4174 return str;
4177 /* return the number of spaces at a line's start */
4178 static int compose_left_offset_length(GtkTextBuffer *buffer,
4179 const GtkTextIter *start)
4181 GtkTextIter iter = *start;
4182 gunichar wc;
4183 gint len = 0;
4184 if (gtk_text_iter_ends_line(&iter))
4185 return 0;
4187 while (1) {
4188 wc = gtk_text_iter_get_char(&iter);
4189 if (!g_unichar_isspace(wc))
4190 break;
4191 len++;
4192 gtk_text_iter_forward_char(&iter);
4193 if (gtk_text_iter_ends_line(&iter))
4194 return 0;
4197 gtk_text_iter_forward_char(&iter);
4198 if (gtk_text_iter_ends_line(&iter))
4199 return 0;
4200 return len;
4203 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4204 const GtkTextIter *start,
4205 GtkTextIter *break_pos,
4206 gint max_col,
4207 gint quote_len)
4209 GtkTextIter iter = *start, line_end = *start;
4210 PangoLogAttr *attrs;
4211 gchar *str;
4212 gchar *p;
4213 gint len;
4214 gint i;
4215 gint col = 0;
4216 gint pos = 0;
4217 gboolean can_break = FALSE;
4218 gboolean do_break = FALSE;
4219 gboolean was_white = FALSE;
4220 gboolean prev_dont_break = FALSE;
4222 gtk_text_iter_forward_to_line_end(&line_end);
4223 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4224 len = g_utf8_strlen(str, -1);
4226 if (len == 0) {
4227 g_free(str);
4228 g_warning("compose_get_line_break_pos: len = 0!");
4229 return FALSE;
4232 /* g_print("breaking line: %d: %s (len = %d)\n",
4233 gtk_text_iter_get_line(&iter), str, len); */
4235 attrs = g_new(PangoLogAttr, len + 1);
4237 pango_default_break(str, -1, NULL, attrs, len + 1);
4239 p = str;
4241 /* skip quote and leading spaces */
4242 for (i = 0; *p != '\0' && i < len; i++) {
4243 gunichar wc;
4245 wc = g_utf8_get_char(p);
4246 if (i >= quote_len && !g_unichar_isspace(wc))
4247 break;
4248 if (g_unichar_iswide(wc))
4249 col += 2;
4250 else if (*p == '\t')
4251 col += 8;
4252 else
4253 col++;
4254 p = g_utf8_next_char(p);
4257 for (; *p != '\0' && i < len; i++) {
4258 PangoLogAttr *attr = attrs + i;
4259 gunichar wc = g_utf8_get_char(p);
4260 gint uri_len;
4262 /* attr->is_line_break will be false for some characters that
4263 * we want to break a line before, like '/' or ':', so we
4264 * also allow breaking on any non-wide character. The
4265 * mentioned pango attribute is still useful to decide on
4266 * line breaks when wide characters are involved. */
4267 if ((!g_unichar_iswide(wc) || attr->is_line_break)
4268 && can_break && was_white && !prev_dont_break)
4269 pos = i;
4271 was_white = attr->is_white;
4273 /* don't wrap URI */
4274 if ((uri_len = get_uri_len(p)) > 0) {
4275 col += uri_len;
4276 if (pos > 0 && col > max_col) {
4277 do_break = TRUE;
4278 break;
4280 i += uri_len - 1;
4281 p += uri_len;
4282 can_break = TRUE;
4283 continue;
4286 if (g_unichar_iswide(wc)) {
4287 col += 2;
4288 if (prev_dont_break && can_break && attr->is_line_break)
4289 pos = i;
4290 } else if (*p == '\t')
4291 col += 8;
4292 else
4293 col++;
4294 if (pos > 0 && col > max_col) {
4295 do_break = TRUE;
4296 break;
4299 if (*p == '-' || *p == '/')
4300 prev_dont_break = TRUE;
4301 else
4302 prev_dont_break = FALSE;
4304 p = g_utf8_next_char(p);
4305 can_break = TRUE;
4308 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4310 g_free(attrs);
4311 g_free(str);
4313 *break_pos = *start;
4314 gtk_text_iter_set_line_offset(break_pos, pos);
4316 return do_break;
4319 static gboolean compose_join_next_line(Compose *compose,
4320 GtkTextBuffer *buffer,
4321 GtkTextIter *iter,
4322 const gchar *quote_str)
4324 GtkTextIter iter_ = *iter, cur, prev, next, end;
4325 PangoLogAttr attrs[3];
4326 gchar *str;
4327 gchar *next_quote_str;
4328 gunichar wc1, wc2;
4329 gint quote_len;
4330 gboolean keep_cursor = FALSE;
4332 if (!gtk_text_iter_forward_line(&iter_) ||
4333 gtk_text_iter_ends_line(&iter_)) {
4334 return FALSE;
4336 next_quote_str = compose_get_quote_str(buffer, &iter_, &quote_len);
4338 if ((quote_str || next_quote_str) &&
4339 g_strcmp0(quote_str, next_quote_str) != 0) {
4340 g_free(next_quote_str);
4341 return FALSE;
4343 g_free(next_quote_str);
4345 end = iter_;
4346 if (quote_len > 0) {
4347 gtk_text_iter_forward_chars(&end, quote_len);
4348 if (gtk_text_iter_ends_line(&end)) {
4349 return FALSE;
4353 /* don't join itemized lines */
4354 if (compose_itemized_length(buffer, &end) > 0) {
4355 return FALSE;
4358 /* don't join signature separator */
4359 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4360 return FALSE;
4362 /* delete quote str */
4363 if (quote_len > 0)
4364 gtk_text_buffer_delete(buffer, &iter_, &end);
4366 /* don't join line breaks put by the user */
4367 prev = cur = iter_;
4368 gtk_text_iter_backward_char(&cur);
4369 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4370 gtk_text_iter_forward_char(&cur);
4371 *iter = cur;
4372 return FALSE;
4374 gtk_text_iter_forward_char(&cur);
4375 /* delete linebreak and extra spaces */
4376 while (gtk_text_iter_backward_char(&cur)) {
4377 wc1 = gtk_text_iter_get_char(&cur);
4378 if (!g_unichar_isspace(wc1))
4379 break;
4380 prev = cur;
4382 next = cur = iter_;
4383 while (!gtk_text_iter_ends_line(&cur)) {
4384 wc1 = gtk_text_iter_get_char(&cur);
4385 if (!g_unichar_isspace(wc1))
4386 break;
4387 gtk_text_iter_forward_char(&cur);
4388 next = cur;
4390 if (!gtk_text_iter_equal(&prev, &next)) {
4391 GtkTextMark *mark;
4393 mark = gtk_text_buffer_get_insert(buffer);
4394 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4395 if (gtk_text_iter_equal(&prev, &cur))
4396 keep_cursor = TRUE;
4397 gtk_text_buffer_delete(buffer, &prev, &next);
4399 iter_ = prev;
4401 /* insert space if required */
4402 gtk_text_iter_backward_char(&prev);
4403 wc1 = gtk_text_iter_get_char(&prev);
4404 wc2 = gtk_text_iter_get_char(&next);
4405 gtk_text_iter_forward_char(&next);
4406 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4407 pango_default_break(str, -1, NULL, attrs, 3);
4408 if (!attrs[1].is_line_break ||
4409 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4410 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4411 if (keep_cursor) {
4412 gtk_text_iter_backward_char(&iter_);
4413 gtk_text_buffer_place_cursor(buffer, &iter_);
4416 g_free(str);
4418 *iter = iter_;
4419 return TRUE;
4422 #define ADD_TXT_POS(bp_, ep_, pti_) \
4423 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4424 last = last->next; \
4425 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4426 last->next = NULL; \
4427 } else { \
4428 g_warning("alloc error scanning URIs"); \
4431 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4433 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4434 GtkTextBuffer *buffer;
4435 GtkTextIter iter, break_pos, end_of_line;
4436 gchar *quote_str = NULL;
4437 gint quote_len;
4438 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4439 gboolean prev_autowrap = compose->autowrap;
4440 gint startq_offset = -1, noq_offset = -1;
4441 gint uri_start = -1, uri_stop = -1;
4442 gint nouri_start = -1, nouri_stop = -1;
4443 gint num_blocks = 0;
4444 gint quotelevel = -1;
4445 gboolean modified = force;
4446 gboolean removed = FALSE;
4447 gboolean modified_before_remove = FALSE;
4448 gint lines = 0;
4449 gboolean start = TRUE;
4450 gint itemized_len = 0, rem_item_len = 0;
4451 gchar *itemized_chars = NULL;
4452 gboolean item_continuation = FALSE;
4454 if (force) {
4455 modified = TRUE;
4457 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4458 modified = TRUE;
4461 compose->autowrap = FALSE;
4463 buffer = gtk_text_view_get_buffer(text);
4464 undo_wrapping(compose->undostruct, TRUE);
4465 if (par_iter) {
4466 iter = *par_iter;
4467 } else {
4468 GtkTextMark *mark;
4469 mark = gtk_text_buffer_get_insert(buffer);
4470 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4474 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4475 if (gtk_text_iter_ends_line(&iter)) {
4476 while (gtk_text_iter_ends_line(&iter) &&
4477 gtk_text_iter_forward_line(&iter))
4479 } else {
4480 while (gtk_text_iter_backward_line(&iter)) {
4481 if (gtk_text_iter_ends_line(&iter)) {
4482 gtk_text_iter_forward_line(&iter);
4483 break;
4487 } else {
4488 /* move to line start */
4489 gtk_text_iter_set_line_offset(&iter, 0);
4492 itemized_len = compose_itemized_length(buffer, &iter);
4494 if (!itemized_len) {
4495 itemized_len = compose_left_offset_length(buffer, &iter);
4496 item_continuation = TRUE;
4499 if (itemized_len)
4500 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4502 /* go until paragraph end (empty line) */
4503 while (start || !gtk_text_iter_ends_line(&iter)) {
4504 gchar *scanpos = NULL;
4505 /* parse table - in order of priority */
4506 struct table {
4507 const gchar *needle; /* token */
4509 /* token search function */
4510 gchar *(*search) (const gchar *haystack,
4511 const gchar *needle);
4512 /* part parsing function */
4513 gboolean (*parse) (const gchar *start,
4514 const gchar *scanpos,
4515 const gchar **bp_,
4516 const gchar **ep_,
4517 gboolean hdr);
4518 /* part to URI function */
4519 gchar *(*build_uri) (const gchar *bp,
4520 const gchar *ep);
4523 static struct table parser[] = {
4524 {"http://", strcasestr, get_uri_part, make_uri_string},
4525 {"https://", strcasestr, get_uri_part, make_uri_string},
4526 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4527 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4528 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4529 {"www.", strcasestr, get_uri_part, make_http_string},
4530 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4531 {"@", strcasestr, get_email_part, make_email_string}
4533 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4534 gint last_index = PARSE_ELEMS;
4535 gint n;
4536 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4537 gint walk_pos;
4539 start = FALSE;
4540 if (!prev_autowrap && num_blocks == 0) {
4541 num_blocks++;
4542 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4543 G_CALLBACK(text_inserted),
4544 compose);
4546 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4547 goto colorize;
4549 uri_start = uri_stop = -1;
4550 quote_len = 0;
4551 quote_str = compose_get_quote_str(buffer, &iter, &quote_len);
4553 if (quote_str) {
4554 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4555 if (startq_offset == -1)
4556 startq_offset = gtk_text_iter_get_offset(&iter);
4557 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4558 if (quotelevel > 2) {
4559 /* recycle colors */
4560 if (prefs_common.recycle_quote_colors)
4561 quotelevel %= 3;
4562 else
4563 quotelevel = 2;
4565 if (!wrap_quote) {
4566 goto colorize;
4568 } else {
4569 if (startq_offset == -1)
4570 noq_offset = gtk_text_iter_get_offset(&iter);
4571 quotelevel = -1;
4574 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4575 goto colorize;
4577 if (gtk_text_iter_ends_line(&iter)) {
4578 goto colorize;
4579 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4580 prefs_common.linewrap_len,
4581 quote_len)) {
4582 GtkTextIter prev, next, cur;
4583 if (prev_autowrap != FALSE || force) {
4584 compose->automatic_break = TRUE;
4585 modified = TRUE;
4586 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4587 compose->automatic_break = FALSE;
4588 if (itemized_len && compose->autoindent) {
4589 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4590 if (!item_continuation)
4591 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4593 } else if (quote_str && wrap_quote) {
4594 compose->automatic_break = TRUE;
4595 modified = TRUE;
4596 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4597 compose->automatic_break = FALSE;
4598 if (itemized_len && compose->autoindent) {
4599 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4600 if (!item_continuation)
4601 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4603 } else
4604 goto colorize;
4605 /* remove trailing spaces */
4606 cur = break_pos;
4607 rem_item_len = itemized_len;
4608 while (compose->autoindent && rem_item_len-- > 0)
4609 gtk_text_iter_backward_char(&cur);
4610 gtk_text_iter_backward_char(&cur);
4612 prev = next = cur;
4613 while (!gtk_text_iter_starts_line(&cur)) {
4614 gunichar wc;
4616 gtk_text_iter_backward_char(&cur);
4617 wc = gtk_text_iter_get_char(&cur);
4618 if (!g_unichar_isspace(wc))
4619 break;
4620 prev = cur;
4622 if (!gtk_text_iter_equal(&prev, &next)) {
4623 gtk_text_buffer_delete(buffer, &prev, &next);
4624 break_pos = next;
4625 gtk_text_iter_forward_char(&break_pos);
4628 if (quote_str)
4629 gtk_text_buffer_insert(buffer, &break_pos,
4630 quote_str, -1);
4632 iter = break_pos;
4633 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4635 /* move iter to current line start */
4636 gtk_text_iter_set_line_offset(&iter, 0);
4637 if (quote_str) {
4638 g_free(quote_str);
4639 quote_str = NULL;
4641 continue;
4642 } else {
4643 /* move iter to next line start */
4644 iter = break_pos;
4645 lines++;
4648 colorize:
4649 if (!prev_autowrap && num_blocks > 0) {
4650 num_blocks--;
4651 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4652 G_CALLBACK(text_inserted),
4653 compose);
4655 end_of_line = iter;
4656 while (!gtk_text_iter_ends_line(&end_of_line)) {
4657 gtk_text_iter_forward_char(&end_of_line);
4659 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4661 nouri_start = gtk_text_iter_get_offset(&iter);
4662 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4664 walk_pos = gtk_text_iter_get_offset(&iter);
4665 /* FIXME: this looks phony. scanning for anything in the parse table */
4666 for (n = 0; n < PARSE_ELEMS; n++) {
4667 gchar *tmp;
4669 tmp = parser[n].search(walk, parser[n].needle);
4670 if (tmp) {
4671 if (scanpos == NULL || tmp < scanpos) {
4672 scanpos = tmp;
4673 last_index = n;
4678 bp = ep = 0;
4679 if (scanpos) {
4680 /* check if URI can be parsed */
4681 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4682 (const gchar **)&ep, FALSE)
4683 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4684 walk = ep;
4685 } else
4686 walk = scanpos +
4687 strlen(parser[last_index].needle);
4689 if (bp && ep) {
4690 uri_start = walk_pos + (bp - o_walk);
4691 uri_stop = walk_pos + (ep - o_walk);
4693 g_free(o_walk);
4694 o_walk = NULL;
4695 gtk_text_iter_forward_line(&iter);
4696 g_free(quote_str);
4697 quote_str = NULL;
4698 if (startq_offset != -1) {
4699 GtkTextIter startquote, endquote;
4700 gtk_text_buffer_get_iter_at_offset(
4701 buffer, &startquote, startq_offset);
4702 endquote = iter;
4704 switch (quotelevel) {
4705 case 0:
4706 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4707 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4708 gtk_text_buffer_apply_tag_by_name(
4709 buffer, "quote0", &startquote, &endquote);
4710 gtk_text_buffer_remove_tag_by_name(
4711 buffer, "quote1", &startquote, &endquote);
4712 gtk_text_buffer_remove_tag_by_name(
4713 buffer, "quote2", &startquote, &endquote);
4714 modified = TRUE;
4716 break;
4717 case 1:
4718 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4719 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4720 gtk_text_buffer_apply_tag_by_name(
4721 buffer, "quote1", &startquote, &endquote);
4722 gtk_text_buffer_remove_tag_by_name(
4723 buffer, "quote0", &startquote, &endquote);
4724 gtk_text_buffer_remove_tag_by_name(
4725 buffer, "quote2", &startquote, &endquote);
4726 modified = TRUE;
4728 break;
4729 case 2:
4730 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4731 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4732 gtk_text_buffer_apply_tag_by_name(
4733 buffer, "quote2", &startquote, &endquote);
4734 gtk_text_buffer_remove_tag_by_name(
4735 buffer, "quote0", &startquote, &endquote);
4736 gtk_text_buffer_remove_tag_by_name(
4737 buffer, "quote1", &startquote, &endquote);
4738 modified = TRUE;
4740 break;
4742 startq_offset = -1;
4743 } else if (noq_offset != -1) {
4744 GtkTextIter startnoquote, endnoquote;
4745 gtk_text_buffer_get_iter_at_offset(
4746 buffer, &startnoquote, noq_offset);
4747 endnoquote = iter;
4749 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4750 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4751 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4752 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4753 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4754 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4755 gtk_text_buffer_remove_tag_by_name(
4756 buffer, "quote0", &startnoquote, &endnoquote);
4757 gtk_text_buffer_remove_tag_by_name(
4758 buffer, "quote1", &startnoquote, &endnoquote);
4759 gtk_text_buffer_remove_tag_by_name(
4760 buffer, "quote2", &startnoquote, &endnoquote);
4761 modified = TRUE;
4763 noq_offset = -1;
4766 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4767 GtkTextIter nouri_start_iter, nouri_end_iter;
4768 gtk_text_buffer_get_iter_at_offset(
4769 buffer, &nouri_start_iter, nouri_start);
4770 gtk_text_buffer_get_iter_at_offset(
4771 buffer, &nouri_end_iter, nouri_stop);
4772 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4773 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4774 gtk_text_buffer_remove_tag_by_name(
4775 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4776 modified_before_remove = modified;
4777 modified = TRUE;
4778 removed = TRUE;
4781 if (uri_start >= 0 && uri_stop > 0) {
4782 GtkTextIter uri_start_iter, uri_end_iter, back;
4783 gtk_text_buffer_get_iter_at_offset(
4784 buffer, &uri_start_iter, uri_start);
4785 gtk_text_buffer_get_iter_at_offset(
4786 buffer, &uri_end_iter, uri_stop);
4787 back = uri_end_iter;
4788 gtk_text_iter_backward_char(&back);
4789 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4790 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4791 gtk_text_buffer_apply_tag_by_name(
4792 buffer, "link", &uri_start_iter, &uri_end_iter);
4793 modified = TRUE;
4794 if (removed && !modified_before_remove) {
4795 modified = FALSE;
4799 if (!modified) {
4800 /* debug_print("not modified, out after %d lines\n", lines); */
4801 goto end;
4804 /* debug_print("modified, out after %d lines\n", lines); */
4805 end:
4806 g_free(itemized_chars);
4807 if (par_iter)
4808 *par_iter = iter;
4809 undo_wrapping(compose->undostruct, FALSE);
4810 compose->autowrap = prev_autowrap;
4812 return modified;
4815 void compose_action_cb(void *data)
4817 Compose *compose = (Compose *)data;
4818 compose_wrap_all(compose);
4821 static void compose_wrap_all(Compose *compose)
4823 compose_wrap_all_full(compose, FALSE);
4826 static void compose_wrap_all_full(Compose *compose, gboolean force)
4828 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4829 GtkTextBuffer *buffer;
4830 GtkTextIter iter;
4831 gboolean modified = TRUE;
4833 buffer = gtk_text_view_get_buffer(text);
4835 gtk_text_buffer_get_start_iter(buffer, &iter);
4837 undo_wrapping(compose->undostruct, TRUE);
4839 while (!gtk_text_iter_is_end(&iter) && modified)
4840 modified = compose_beautify_paragraph(compose, &iter, force);
4842 undo_wrapping(compose->undostruct, FALSE);
4846 static void compose_set_title(Compose *compose)
4848 gchar *str;
4849 gchar *edited;
4850 gchar *subject;
4852 edited = compose->modified ? _(" [Edited]") : "";
4854 subject = gtk_editable_get_chars(
4855 GTK_EDITABLE(compose->subject_entry), 0, -1);
4857 #ifndef GENERIC_UMPC
4858 if (subject && strlen(subject))
4859 str = g_strdup_printf(_("%s - Compose message%s"),
4860 subject, edited);
4861 else
4862 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4863 #else
4864 str = g_strdup(_("Compose message"));
4865 #endif
4867 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4868 g_free(str);
4869 g_free(subject);
4873 * compose_current_mail_account:
4875 * Find a current mail account (the currently selected account, or the
4876 * default account, if a news account is currently selected). If a
4877 * mail account cannot be found, display an error message.
4879 * Return value: Mail account, or NULL if not found.
4881 static PrefsAccount *
4882 compose_current_mail_account(void)
4884 PrefsAccount *ac;
4886 if (cur_account && cur_account->protocol != A_NNTP)
4887 ac = cur_account;
4888 else {
4889 ac = account_get_default();
4890 if (!ac || ac->protocol == A_NNTP) {
4891 alertpanel_error(_("Account for sending mail is not specified.\n"
4892 "Please select a mail account before sending."));
4893 return NULL;
4896 return ac;
4899 #define QUOTE_IF_REQUIRED(out, str) \
4901 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4902 gchar *__tmp; \
4903 gint len; \
4905 len = strlen(str) + 3; \
4906 if ((__tmp = alloca(len)) == NULL) { \
4907 g_warning("can't allocate memory"); \
4908 g_string_free(header, TRUE); \
4909 return NULL; \
4911 g_snprintf(__tmp, len, "\"%s\"", str); \
4912 out = __tmp; \
4913 } else { \
4914 gchar *__tmp; \
4916 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4917 g_warning("can't allocate memory"); \
4918 g_string_free(header, TRUE); \
4919 return NULL; \
4920 } else \
4921 strcpy(__tmp, str); \
4923 out = __tmp; \
4927 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
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 errret; \
4938 g_snprintf(__tmp, len, "\"%s\"", str); \
4939 out = __tmp; \
4940 } else { \
4941 gchar *__tmp; \
4943 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4944 g_warning("can't allocate memory"); \
4945 errret; \
4946 } else \
4947 strcpy(__tmp, str); \
4949 out = __tmp; \
4953 static void compose_select_account(Compose *compose, PrefsAccount *account,
4954 gboolean init)
4956 gchar *from = NULL, *header = NULL;
4957 ComposeHeaderEntry *header_entry;
4958 GtkTreeIter iter;
4960 cm_return_if_fail(account != NULL);
4962 compose->account = account;
4963 if (account->name && *account->name) {
4964 gchar *buf, *qbuf;
4965 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4966 qbuf = escape_internal_quotes(buf, '"');
4967 from = g_strdup_printf("%s <%s>",
4968 qbuf, account->address);
4969 if (qbuf != buf)
4970 g_free(qbuf);
4971 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4972 } else {
4973 from = g_strdup_printf("<%s>",
4974 account->address);
4975 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4978 g_free(from);
4980 compose_set_title(compose);
4982 compose_activate_privacy_system(compose, account, FALSE);
4984 if (account->default_sign && privacy_system_can_sign(compose->privacy_system) &&
4985 compose->mode != COMPOSE_REDIRECT)
4986 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4987 else
4988 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4989 if (account->default_encrypt && privacy_system_can_encrypt(compose->privacy_system) &&
4990 compose->mode != COMPOSE_REDIRECT)
4991 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4992 else
4993 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4995 if (!init && compose->mode != COMPOSE_REDIRECT) {
4996 undo_block(compose->undostruct);
4997 compose_insert_sig(compose, TRUE);
4998 undo_unblock(compose->undostruct);
5001 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
5002 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
5003 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
5004 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5006 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5007 if (account->protocol == A_NNTP) {
5008 if (!strcmp(header, _("To:")))
5009 combobox_select_by_text(
5010 GTK_COMBO_BOX(header_entry->combo),
5011 _("Newsgroups:"));
5012 } else {
5013 if (!strcmp(header, _("Newsgroups:")))
5014 combobox_select_by_text(
5015 GTK_COMBO_BOX(header_entry->combo),
5016 _("To:"));
5020 g_free(header);
5022 #ifdef USE_ENCHANT
5023 /* use account's dict info if set */
5024 if (compose->gtkaspell) {
5025 if (account->enable_default_dictionary)
5026 gtkaspell_change_dict(compose->gtkaspell,
5027 account->default_dictionary, FALSE);
5028 if (account->enable_default_alt_dictionary)
5029 gtkaspell_change_alt_dict(compose->gtkaspell,
5030 account->default_alt_dictionary);
5031 if (account->enable_default_dictionary
5032 || account->enable_default_alt_dictionary)
5033 compose_spell_menu_changed(compose);
5035 #endif
5038 gboolean compose_check_for_valid_recipient(Compose *compose) {
5039 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5040 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5041 gboolean recipient_found = FALSE;
5042 GSList *list;
5043 gchar **strptr;
5045 /* free to and newsgroup list */
5046 slist_free_strings_full(compose->to_list);
5047 compose->to_list = NULL;
5049 slist_free_strings_full(compose->newsgroup_list);
5050 compose->newsgroup_list = NULL;
5052 /* search header entries for to and newsgroup entries */
5053 for (list = compose->header_list; list; list = list->next) {
5054 gchar *header;
5055 gchar *entry;
5056 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5057 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5058 g_strstrip(entry);
5059 g_strstrip(header);
5060 if (entry[0] != '\0') {
5061 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5062 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5063 compose->to_list = address_list_append(compose->to_list, entry);
5064 recipient_found = TRUE;
5067 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5068 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5069 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5070 recipient_found = TRUE;
5074 g_free(header);
5075 g_free(entry);
5077 return recipient_found;
5080 static gboolean compose_check_for_set_recipients(Compose *compose)
5082 if (compose->account->set_autocc && compose->account->auto_cc) {
5083 gboolean found_other = FALSE;
5084 GSList *list;
5085 /* search header entries for to and newsgroup entries */
5086 for (list = compose->header_list; list; list = list->next) {
5087 gchar *entry;
5088 gchar *header;
5089 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5090 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5091 g_strstrip(entry);
5092 g_strstrip(header);
5093 if (strcmp(entry, compose->account->auto_cc)
5094 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5095 found_other = TRUE;
5096 g_free(entry);
5097 break;
5099 g_free(entry);
5100 g_free(header);
5102 if (!found_other) {
5103 AlertValue aval;
5104 gchar *text;
5105 if (compose->batch) {
5106 gtk_widget_show_all(compose->window);
5108 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5109 prefs_common_translated_header_name("Cc"));
5110 aval = alertpanel(_("Send"),
5111 text,
5112 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5113 g_free(text);
5114 if (aval != G_ALERTALTERNATE)
5115 return FALSE;
5118 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5119 gboolean found_other = FALSE;
5120 GSList *list;
5121 /* search header entries for to and newsgroup entries */
5122 for (list = compose->header_list; list; list = list->next) {
5123 gchar *entry;
5124 gchar *header;
5125 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5126 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5127 g_strstrip(entry);
5128 g_strstrip(header);
5129 if (strcmp(entry, compose->account->auto_bcc)
5130 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5131 found_other = TRUE;
5132 g_free(entry);
5133 g_free(header);
5134 break;
5136 g_free(entry);
5137 g_free(header);
5139 if (!found_other) {
5140 AlertValue aval;
5141 gchar *text;
5142 if (compose->batch) {
5143 gtk_widget_show_all(compose->window);
5145 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5146 prefs_common_translated_header_name("Bcc"));
5147 aval = alertpanel(_("Send"),
5148 text,
5149 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5150 g_free(text);
5151 if (aval != G_ALERTALTERNATE)
5152 return FALSE;
5155 return TRUE;
5158 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5160 const gchar *str;
5162 if (compose_check_for_valid_recipient(compose) == FALSE) {
5163 if (compose->batch) {
5164 gtk_widget_show_all(compose->window);
5166 alertpanel_error(_("Recipient is not specified."));
5167 return FALSE;
5170 if (compose_check_for_set_recipients(compose) == FALSE) {
5171 return FALSE;
5174 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5175 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5176 if (*str == '\0' && check_everything == TRUE &&
5177 compose->mode != COMPOSE_REDIRECT) {
5178 AlertValue aval;
5179 gchar *message;
5181 message = g_strdup_printf(_("Subject is empty. %s"),
5182 compose->sending?_("Send it anyway?"):
5183 _("Queue it anyway?"));
5185 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5186 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5187 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5188 g_free(message);
5189 if (aval & G_ALERTDISABLE) {
5190 aval &= ~G_ALERTDISABLE;
5191 prefs_common.warn_empty_subj = FALSE;
5193 if (aval != G_ALERTALTERNATE)
5194 return FALSE;
5198 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5199 && check_everything == TRUE) {
5200 GSList *list;
5201 gint cnt = 0;
5203 /* count To and Cc recipients */
5204 for (list = compose->header_list; list; list = list->next) {
5205 gchar *header;
5206 gchar *entry;
5208 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5209 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5210 g_strstrip(header);
5211 g_strstrip(entry);
5212 if ((entry[0] != '\0') &&
5213 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5214 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5215 cnt++;
5217 g_free(header);
5218 g_free(entry);
5220 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5221 AlertValue aval;
5222 gchar *message;
5224 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5225 compose->sending?_("Send it anyway?"):
5226 _("Queue it anyway?"));
5228 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5229 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5230 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5231 g_free(message);
5232 if (aval & G_ALERTDISABLE) {
5233 aval &= ~G_ALERTDISABLE;
5234 prefs_common.warn_sending_many_recipients_num = 0;
5236 if (aval != G_ALERTALTERNATE)
5237 return FALSE;
5241 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5242 return FALSE;
5244 return TRUE;
5247 static void _display_queue_error(ComposeQueueResult val)
5249 switch (val) {
5250 case COMPOSE_QUEUE_SUCCESS:
5251 break;
5252 case COMPOSE_QUEUE_ERROR_NO_MSG:
5253 alertpanel_error(_("Could not queue message."));
5254 break;
5255 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5256 alertpanel_error(_("Could not queue message:\n\n%s."),
5257 g_strerror(errno));
5258 break;
5259 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5260 alertpanel_error(_("Could not queue message for sending:\n\n"
5261 "Signature failed: %s"),
5262 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5263 break;
5264 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5265 alertpanel_error(_("Could not queue message for sending:\n\n"
5266 "Encryption failed: %s"),
5267 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5268 break;
5269 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5270 alertpanel_error(_("Could not queue message for sending:\n\n"
5271 "Charset conversion failed."));
5272 break;
5273 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5274 alertpanel_error(_("Could not queue message for sending:\n\n"
5275 "Couldn't get recipient encryption key."));
5276 break;
5277 case COMPOSE_QUEUE_SIGNING_CANCELLED:
5278 debug_print("signing cancelled\n");
5279 break;
5280 default:
5281 /* unhandled error */
5282 debug_print("oops, unhandled compose_queue() return value %d\n",
5283 val);
5284 break;
5288 gint compose_send(Compose *compose)
5290 gint msgnum;
5291 FolderItem *folder = NULL;
5292 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5293 gchar *msgpath = NULL;
5294 gboolean discard_window = FALSE;
5295 gchar *errstr = NULL;
5296 gchar *tmsgid = NULL;
5297 MainWindow *mainwin = mainwindow_get_mainwindow();
5298 gboolean queued_removed = FALSE;
5300 if (prefs_common.send_dialog_invisible
5301 || compose->batch == TRUE)
5302 discard_window = TRUE;
5304 compose_allow_user_actions (compose, FALSE);
5305 compose->sending = TRUE;
5307 if (compose_check_entries(compose, TRUE) == FALSE) {
5308 if (compose->batch) {
5309 gtk_widget_show_all(compose->window);
5311 goto bail;
5314 inc_lock();
5315 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5317 if (val != COMPOSE_QUEUE_SUCCESS) {
5318 if (compose->batch) {
5319 gtk_widget_show_all(compose->window);
5322 _display_queue_error(val);
5324 goto bail;
5327 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5328 if (discard_window) {
5329 compose->sending = FALSE;
5330 compose_close(compose);
5331 /* No more compose access in the normal codepath
5332 * after this point! */
5333 compose = NULL;
5336 if (msgnum == 0) {
5337 alertpanel_error(_("The message was queued but could not be "
5338 "sent.\nUse \"Send queued messages\" from "
5339 "the main window to retry."));
5340 if (!discard_window) {
5341 goto bail;
5343 inc_unlock();
5344 g_free(tmsgid);
5345 return -1;
5347 if (msgpath == NULL) {
5348 msgpath = folder_item_fetch_msg(folder, msgnum);
5349 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5350 g_free(msgpath);
5351 } else {
5352 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5353 claws_unlink(msgpath);
5354 g_free(msgpath);
5356 if (!discard_window) {
5357 if (val != 0) {
5358 if (!queued_removed)
5359 folder_item_remove_msg(folder, msgnum);
5360 folder_item_scan(folder);
5361 if (tmsgid) {
5362 /* make sure we delete that */
5363 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5364 if (tmp) {
5365 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5366 folder_item_remove_msg(folder, tmp->msgnum);
5367 procmsg_msginfo_free(&tmp);
5373 if (val == 0) {
5374 if (!queued_removed)
5375 folder_item_remove_msg(folder, msgnum);
5376 folder_item_scan(folder);
5377 if (tmsgid) {
5378 /* make sure we delete that */
5379 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5380 if (tmp) {
5381 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5382 folder_item_remove_msg(folder, tmp->msgnum);
5383 procmsg_msginfo_free(&tmp);
5386 if (!discard_window) {
5387 compose->sending = FALSE;
5388 compose_allow_user_actions (compose, TRUE);
5389 compose_close(compose);
5391 } else {
5392 if (errstr) {
5393 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5394 "the main window to retry."), errstr);
5395 g_free(errstr);
5396 } else {
5397 alertpanel_error_log(_("The message was queued but could not be "
5398 "sent.\nUse \"Send queued messages\" from "
5399 "the main window to retry."));
5401 if (!discard_window) {
5402 goto bail;
5404 inc_unlock();
5405 g_free(tmsgid);
5406 return -1;
5408 g_free(tmsgid);
5409 inc_unlock();
5410 toolbar_main_set_sensitive(mainwin);
5411 main_window_set_menu_sensitive(mainwin);
5412 return 0;
5414 bail:
5415 inc_unlock();
5416 g_free(tmsgid);
5417 compose_allow_user_actions (compose, TRUE);
5418 compose->sending = FALSE;
5419 compose->modified = TRUE;
5420 toolbar_main_set_sensitive(mainwin);
5421 main_window_set_menu_sensitive(mainwin);
5423 return -1;
5426 static gboolean compose_use_attach(Compose *compose)
5428 GtkTreeModel *model = gtk_tree_view_get_model
5429 (GTK_TREE_VIEW(compose->attach_clist));
5430 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5433 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5434 FILE *fp)
5436 gchar buf[BUFFSIZE];
5437 gchar *str;
5438 gboolean first_to_address;
5439 gboolean first_cc_address;
5440 GSList *list;
5441 ComposeHeaderEntry *headerentry;
5442 const gchar *headerentryname;
5443 const gchar *cc_hdr;
5444 const gchar *to_hdr;
5445 gboolean err = FALSE;
5447 debug_print("Writing redirect header\n");
5449 cc_hdr = prefs_common_translated_header_name("Cc:");
5450 to_hdr = prefs_common_translated_header_name("To:");
5452 first_to_address = TRUE;
5453 for (list = compose->header_list; list; list = list->next) {
5454 headerentry = ((ComposeHeaderEntry *)list->data);
5455 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5457 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5458 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5459 Xstrdup_a(str, entstr, return -1);
5460 g_strstrip(str);
5461 if (str[0] != '\0') {
5462 compose_convert_header
5463 (compose, buf, sizeof(buf), str,
5464 strlen("Resent-To") + 2, TRUE);
5466 if (first_to_address) {
5467 err |= (fprintf(fp, "Resent-To: ") < 0);
5468 first_to_address = FALSE;
5469 } else {
5470 err |= (fprintf(fp, ",") < 0);
5472 err |= (fprintf(fp, "%s", buf) < 0);
5476 if (!first_to_address) {
5477 err |= (fprintf(fp, "\n") < 0);
5480 first_cc_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, cc_hdr) == 0) {
5486 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5487 Xstrdup_a(str, strg, return -1);
5488 g_strstrip(str);
5489 if (str[0] != '\0') {
5490 compose_convert_header
5491 (compose, buf, sizeof(buf), str,
5492 strlen("Resent-Cc") + 2, TRUE);
5494 if (first_cc_address) {
5495 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5496 first_cc_address = FALSE;
5497 } else {
5498 err |= (fprintf(fp, ",") < 0);
5500 err |= (fprintf(fp, "%s", buf) < 0);
5504 if (!first_cc_address) {
5505 err |= (fprintf(fp, "\n") < 0);
5508 return (err ? -1:0);
5511 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5513 gchar date[RFC822_DATE_BUFFSIZE];
5514 gchar buf[BUFFSIZE];
5515 gchar *str;
5516 const gchar *entstr;
5517 /* struct utsname utsbuf; */
5518 gboolean err = FALSE;
5520 cm_return_val_if_fail(fp != NULL, -1);
5521 cm_return_val_if_fail(compose->account != NULL, -1);
5522 cm_return_val_if_fail(compose->account->address != NULL, -1);
5524 /* Resent-Date */
5525 if (prefs_common.hide_timezone)
5526 get_rfc822_date_hide_tz(date, sizeof(date));
5527 else
5528 get_rfc822_date(date, sizeof(date));
5529 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5531 /* Resent-From */
5532 if (compose->account->name && *compose->account->name) {
5533 compose_convert_header
5534 (compose, buf, sizeof(buf), compose->account->name,
5535 strlen("From: "), TRUE);
5536 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5537 buf, compose->account->address) < 0);
5538 } else
5539 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5541 /* Subject */
5542 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5543 if (*entstr != '\0') {
5544 Xstrdup_a(str, entstr, return -1);
5545 g_strstrip(str);
5546 if (*str != '\0') {
5547 compose_convert_header(compose, buf, sizeof(buf), str,
5548 strlen("Subject: "), FALSE);
5549 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5553 /* Resent-Message-ID */
5554 if (compose->account->gen_msgid) {
5555 gchar *addr = prefs_account_generate_msgid(compose->account);
5556 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5557 if (compose->msgid)
5558 g_free(compose->msgid);
5559 compose->msgid = addr;
5560 } else {
5561 compose->msgid = NULL;
5564 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5565 return -1;
5567 /* separator between header and body */
5568 err |= (claws_fputs("\n", fp) == EOF);
5570 return (err ? -1:0);
5573 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5575 FILE *fp;
5576 size_t len;
5577 gchar *buf = NULL;
5578 gchar rewrite_buf[BUFFSIZE];
5579 int i = 0;
5580 gboolean skip = FALSE;
5581 gboolean err = FALSE;
5582 gchar *not_included[]={
5583 "Return-Path:", "Delivered-To:", "Received:",
5584 "Subject:", "X-UIDL:", "AF:",
5585 "NF:", "PS:", "SRH:",
5586 "SFN:", "DSR:", "MID:",
5587 "CFG:", "PT:", "S:",
5588 "RQ:", "SSV:", "NSV:",
5589 "SSH:", "R:", "MAID:",
5590 "NAID:", "RMID:", "FMID:",
5591 "SCF:", "RRCPT:", "NG:",
5592 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5593 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5594 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5595 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5596 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5597 NULL
5599 gint ret = 0;
5601 if ((fp = claws_fopen(compose->redirect_filename, "rb")) == NULL) {
5602 FILE_OP_ERROR(compose->redirect_filename, "claws_fopen");
5603 return -1;
5606 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5607 skip = FALSE;
5608 for (i = 0; not_included[i] != NULL; i++) {
5609 if (g_ascii_strncasecmp(buf, not_included[i],
5610 strlen(not_included[i])) == 0) {
5611 skip = TRUE;
5612 break;
5615 if (skip) {
5616 g_free(buf);
5617 buf = NULL;
5618 continue;
5620 if (claws_fputs(buf, fdest) == -1) {
5621 g_free(buf);
5622 buf = NULL;
5623 goto error;
5626 if (!prefs_common.redirect_keep_from) {
5627 if (g_ascii_strncasecmp(buf, "From:",
5628 strlen("From:")) == 0) {
5629 err |= (claws_fputs(" (by way of ", fdest) == EOF);
5630 if (compose->account->name
5631 && *compose->account->name) {
5632 gchar buffer[BUFFSIZE];
5634 compose_convert_header
5635 (compose, buffer, sizeof(buffer),
5636 compose->account->name,
5637 strlen("From: "),
5638 FALSE);
5639 err |= (fprintf(fdest, "%s <%s>",
5640 buffer,
5641 compose->account->address) < 0);
5642 } else
5643 err |= (fprintf(fdest, "%s",
5644 compose->account->address) < 0);
5645 err |= (claws_fputs(")", fdest) == EOF);
5649 g_free(buf);
5650 buf = NULL;
5651 if (claws_fputs("\n", fdest) == -1)
5652 goto error;
5655 if (err)
5656 goto error;
5658 if (compose_redirect_write_headers(compose, fdest))
5659 goto error;
5661 while ((len = claws_fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5662 if (claws_fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5663 goto error;
5666 claws_fclose(fp);
5668 return 0;
5670 error:
5671 claws_fclose(fp);
5673 return -1;
5676 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5678 GtkTextBuffer *buffer;
5679 GtkTextIter start, end, tmp;
5680 gchar *chars, *tmp_enc_file, *content;
5681 gchar *buf, *msg;
5682 const gchar *out_codeset;
5683 EncodingType encoding = ENC_UNKNOWN;
5684 MimeInfo *mimemsg, *mimetext;
5685 gint line;
5686 const gchar *src_codeset = CS_INTERNAL;
5687 gchar *from_addr = NULL;
5688 gchar *from_name = NULL;
5689 FolderItem *outbox;
5691 if (action == COMPOSE_WRITE_FOR_SEND) {
5692 attach_parts = TRUE;
5694 /* We're sending the message, generate a Message-ID
5695 * if necessary. */
5696 if (compose->msgid == NULL &&
5697 compose->account->gen_msgid) {
5698 compose->msgid = prefs_account_generate_msgid(compose->account);
5702 /* create message MimeInfo */
5703 mimemsg = procmime_mimeinfo_new();
5704 mimemsg->type = MIMETYPE_MESSAGE;
5705 mimemsg->subtype = g_strdup("rfc822");
5706 mimemsg->content = MIMECONTENT_MEM;
5707 mimemsg->tmp = TRUE; /* must free content later */
5708 mimemsg->data.mem = compose_get_header(compose);
5710 /* Create text part MimeInfo */
5711 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5712 gtk_text_buffer_get_end_iter(buffer, &end);
5713 tmp = end;
5715 /* We make sure that there is a newline at the end. */
5716 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5717 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5718 if (*chars != '\n') {
5719 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5721 g_free(chars);
5724 /* get all composed text */
5725 gtk_text_buffer_get_start_iter(buffer, &start);
5726 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5728 out_codeset = conv_get_charset_str(compose->out_encoding);
5730 if (!out_codeset && is_ascii_str(chars)) {
5731 out_codeset = CS_US_ASCII;
5732 } else if (prefs_common.outgoing_fallback_to_ascii &&
5733 is_ascii_str(chars)) {
5734 out_codeset = CS_US_ASCII;
5735 encoding = ENC_7BIT;
5738 if (!out_codeset) {
5739 gchar *test_conv_global_out = NULL;
5740 gchar *test_conv_reply = NULL;
5742 /* automatic mode. be automatic. */
5743 codeconv_set_strict(TRUE);
5745 out_codeset = conv_get_outgoing_charset_str();
5746 if (out_codeset) {
5747 debug_print("trying to convert to %s\n", out_codeset);
5748 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5751 if (!test_conv_global_out && compose->orig_charset
5752 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5753 out_codeset = compose->orig_charset;
5754 debug_print("failure; trying to convert to %s\n", out_codeset);
5755 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5758 if (!test_conv_global_out && !test_conv_reply) {
5759 /* we're lost */
5760 out_codeset = CS_INTERNAL;
5761 debug_print("failure; finally using %s\n", out_codeset);
5763 g_free(test_conv_global_out);
5764 g_free(test_conv_reply);
5765 codeconv_set_strict(FALSE);
5768 if (encoding == ENC_UNKNOWN) {
5769 if (prefs_common.encoding_method == CTE_BASE64)
5770 encoding = ENC_BASE64;
5771 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5772 encoding = ENC_QUOTED_PRINTABLE;
5773 else if (prefs_common.encoding_method == CTE_8BIT)
5774 encoding = ENC_8BIT;
5775 else
5776 encoding = procmime_get_encoding_for_charset(out_codeset);
5779 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5780 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5782 if (action == COMPOSE_WRITE_FOR_SEND) {
5783 codeconv_set_strict(TRUE);
5784 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5785 codeconv_set_strict(FALSE);
5787 if (!buf) {
5788 AlertValue aval;
5790 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5791 "to the specified %s charset.\n"
5792 "Send it as %s?"), out_codeset, src_codeset);
5793 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5794 _("_Send"), NULL, ALERTFOCUS_SECOND, FALSE,
5795 NULL, ALERT_ERROR);
5796 g_free(msg);
5798 if (aval != G_ALERTALTERNATE) {
5799 g_free(chars);
5800 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5801 } else {
5802 buf = chars;
5803 out_codeset = src_codeset;
5804 chars = NULL;
5807 } else {
5808 buf = chars;
5809 out_codeset = src_codeset;
5810 chars = NULL;
5812 g_free(chars);
5814 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5815 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5816 strstr(buf, "\nFrom ") != NULL) {
5817 encoding = ENC_QUOTED_PRINTABLE;
5821 mimetext = procmime_mimeinfo_new();
5822 mimetext->content = MIMECONTENT_MEM;
5823 mimetext->tmp = TRUE; /* must free content later */
5824 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5825 * and free the data, which we need later. */
5826 mimetext->data.mem = g_strdup(buf);
5827 mimetext->type = MIMETYPE_TEXT;
5828 mimetext->subtype = g_strdup("plain");
5829 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5830 g_strdup(out_codeset));
5832 /* protect trailing spaces when signing message */
5833 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5834 privacy_system_can_sign(compose->privacy_system)) {
5835 encoding = ENC_QUOTED_PRINTABLE;
5838 #ifdef G_OS_WIN32
5839 debug_print("main text: %Id bytes encoded as %s in %d\n",
5840 #else
5841 debug_print("main text: %zd bytes encoded as %s in %d\n",
5842 #endif
5843 strlen(buf), out_codeset, encoding);
5845 /* check for line length limit */
5846 if (action == COMPOSE_WRITE_FOR_SEND &&
5847 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5848 check_line_length(buf, 1000, &line) < 0) {
5849 AlertValue aval;
5851 msg = g_strdup_printf
5852 (_("Line %d exceeds the line length limit (998 bytes).\n"
5853 "The contents of the message might be broken on the way to the delivery.\n"
5854 "\n"
5855 "Send it anyway?"), line + 1);
5856 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL,
5857 ALERTFOCUS_FIRST);
5858 g_free(msg);
5859 if (aval != G_ALERTALTERNATE) {
5860 g_free(buf);
5861 return COMPOSE_QUEUE_ERROR_NO_MSG;
5865 if (encoding != ENC_UNKNOWN)
5866 procmime_encode_content(mimetext, encoding);
5868 /* append attachment parts */
5869 if (compose_use_attach(compose) && attach_parts) {
5870 MimeInfo *mimempart;
5871 gchar *boundary = NULL;
5872 mimempart = procmime_mimeinfo_new();
5873 mimempart->content = MIMECONTENT_EMPTY;
5874 mimempart->type = MIMETYPE_MULTIPART;
5875 mimempart->subtype = g_strdup("mixed");
5877 do {
5878 g_free(boundary);
5879 boundary = generate_mime_boundary(NULL);
5880 } while (strstr(buf, boundary) != NULL);
5882 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5883 boundary);
5885 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5887 g_node_append(mimempart->node, mimetext->node);
5888 g_node_append(mimemsg->node, mimempart->node);
5890 if (compose_add_attachments(compose, mimempart) < 0)
5891 return COMPOSE_QUEUE_ERROR_NO_MSG;
5892 } else
5893 g_node_append(mimemsg->node, mimetext->node);
5895 g_free(buf);
5897 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5898 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5899 /* extract name and address */
5900 if (strstr(spec, " <") && strstr(spec, ">")) {
5901 from_addr = g_strdup(strrchr(spec, '<')+1);
5902 *(strrchr(from_addr, '>')) = '\0';
5903 from_name = g_strdup(spec);
5904 *(strrchr(from_name, '<')) = '\0';
5905 } else {
5906 from_name = NULL;
5907 from_addr = NULL;
5909 g_free(spec);
5911 /* sign message if sending */
5912 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5913 privacy_system_can_sign(compose->privacy_system))
5914 if (!privacy_sign(compose->privacy_system, mimemsg,
5915 compose->account, from_addr)) {
5916 g_free(from_name);
5917 g_free(from_addr);
5918 if (!privacy_peek_error())
5919 return COMPOSE_QUEUE_SIGNING_CANCELLED;
5920 else
5921 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5923 g_free(from_name);
5924 g_free(from_addr);
5926 if (compose->use_encryption) {
5927 if (compose->encdata != NULL &&
5928 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5930 /* First, write an unencrypted copy and save it to outbox, if
5931 * user wants that. */
5932 if (compose->account->save_encrypted_as_clear_text) {
5933 debug_print("saving sent message unencrypted...\n");
5934 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5935 if (tmpfp) {
5936 claws_fclose(tmpfp);
5938 /* fp now points to a file with headers written,
5939 * let's make a copy. */
5940 rewind(fp);
5941 content = file_read_stream_to_str(fp);
5943 str_write_to_file(content, tmp_enc_file, TRUE);
5944 g_free(content);
5946 /* Now write the unencrypted body. */
5947 if ((tmpfp = claws_fopen(tmp_enc_file, "a")) != NULL) {
5948 procmime_write_mimeinfo(mimemsg, tmpfp);
5949 claws_fclose(tmpfp);
5951 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5952 if (!outbox)
5953 outbox = folder_get_default_outbox();
5955 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5956 claws_unlink(tmp_enc_file);
5957 } else {
5958 g_warning("Can't open file '%s'", tmp_enc_file);
5960 } else {
5961 g_warning("couldn't get tempfile");
5964 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5965 debug_print("Couldn't encrypt mime structure: %s.\n",
5966 privacy_get_error());
5967 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5972 procmime_write_mimeinfo(mimemsg, fp);
5974 procmime_mimeinfo_free_all(&mimemsg);
5976 return 0;
5979 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5981 GtkTextBuffer *buffer;
5982 GtkTextIter start, end;
5983 FILE *fp;
5984 size_t len;
5985 gchar *chars, *tmp;
5987 if ((fp = claws_fopen(file, "wb")) == NULL) {
5988 FILE_OP_ERROR(file, "claws_fopen");
5989 return -1;
5992 /* chmod for security */
5993 if (change_file_mode_rw(fp, file) < 0) {
5994 FILE_OP_ERROR(file, "chmod");
5995 g_warning("can't change file mode");
5998 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5999 gtk_text_buffer_get_start_iter(buffer, &start);
6000 gtk_text_buffer_get_end_iter(buffer, &end);
6001 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
6003 chars = conv_codeset_strdup
6004 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6006 g_free(tmp);
6007 if (!chars) {
6008 claws_fclose(fp);
6009 claws_unlink(file);
6010 return -1;
6012 /* write body */
6013 len = strlen(chars);
6014 if (claws_fwrite(chars, sizeof(gchar), len, fp) != len) {
6015 FILE_OP_ERROR(file, "claws_fwrite");
6016 g_free(chars);
6017 claws_fclose(fp);
6018 claws_unlink(file);
6019 return -1;
6022 g_free(chars);
6024 if (claws_safe_fclose(fp) == EOF) {
6025 FILE_OP_ERROR(file, "claws_fclose");
6026 claws_unlink(file);
6027 return -1;
6029 return 0;
6032 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6034 FolderItem *item;
6035 MsgInfo *msginfo = compose->targetinfo;
6037 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6038 if (!msginfo) return -1;
6040 if (!force && MSG_IS_LOCKED(msginfo->flags))
6041 return 0;
6043 item = msginfo->folder;
6044 cm_return_val_if_fail(item != NULL, -1);
6046 if (procmsg_msg_exist(msginfo) &&
6047 (folder_has_parent_of_type(item, F_QUEUE) ||
6048 folder_has_parent_of_type(item, F_DRAFT)
6049 || msginfo == compose->autosaved_draft)) {
6050 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6051 g_warning("can't remove the old message");
6052 return -1;
6053 } else {
6054 debug_print("removed reedit target %d\n", msginfo->msgnum);
6058 return 0;
6061 static void compose_remove_draft(Compose *compose)
6063 FolderItem *drafts;
6064 MsgInfo *msginfo = compose->targetinfo;
6065 drafts = account_get_special_folder(compose->account, F_DRAFT);
6067 if (procmsg_msg_exist(msginfo)) {
6068 folder_item_remove_msg(drafts, msginfo->msgnum);
6073 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6074 gboolean remove_reedit_target)
6076 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6079 static gboolean compose_warn_encryption(Compose *compose)
6081 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6082 AlertValue val = G_ALERTALTERNATE;
6084 if (warning == NULL)
6085 return TRUE;
6087 val = alertpanel_full(_("Encryption warning"), warning,
6088 GTK_STOCK_CANCEL, _("C_ontinue"), NULL, ALERTFOCUS_SECOND,
6089 TRUE, NULL, ALERT_WARNING);
6090 if (val & G_ALERTDISABLE) {
6091 val &= ~G_ALERTDISABLE;
6092 if (val == G_ALERTALTERNATE)
6093 privacy_inhibit_encrypt_warning(compose->privacy_system,
6094 TRUE);
6097 if (val == G_ALERTALTERNATE) {
6098 return TRUE;
6099 } else {
6100 return FALSE;
6104 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6105 gchar **msgpath, gboolean perform_checks,
6106 gboolean remove_reedit_target)
6108 FolderItem *queue;
6109 gchar *tmp;
6110 FILE *fp;
6111 GSList *cur;
6112 gint num;
6113 PrefsAccount *mailac = NULL, *newsac = NULL;
6114 gboolean err = FALSE;
6116 debug_print("queueing message...\n");
6117 cm_return_val_if_fail(compose->account != NULL, -1);
6119 if (compose_check_entries(compose, perform_checks) == FALSE) {
6120 if (compose->batch) {
6121 gtk_widget_show_all(compose->window);
6123 return COMPOSE_QUEUE_ERROR_NO_MSG;
6126 if (!compose->to_list && !compose->newsgroup_list) {
6127 g_warning("can't get recipient list.");
6128 return COMPOSE_QUEUE_ERROR_NO_MSG;
6131 if (compose->to_list) {
6132 if (compose->account->protocol != A_NNTP)
6133 mailac = compose->account;
6134 else if (cur_account && cur_account->protocol != A_NNTP)
6135 mailac = cur_account;
6136 else if (!(mailac = compose_current_mail_account())) {
6137 alertpanel_error(_("No account for sending mails available!"));
6138 return COMPOSE_QUEUE_ERROR_NO_MSG;
6142 if (compose->newsgroup_list) {
6143 if (compose->account->protocol == A_NNTP)
6144 newsac = compose->account;
6145 else {
6146 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6147 return COMPOSE_QUEUE_ERROR_NO_MSG;
6151 /* write queue header */
6152 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6153 G_DIR_SEPARATOR, compose, (guint) rand());
6154 debug_print("queuing to %s\n", tmp);
6155 if ((fp = claws_fopen(tmp, "w+b")) == NULL) {
6156 FILE_OP_ERROR(tmp, "claws_fopen");
6157 g_free(tmp);
6158 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6161 if (change_file_mode_rw(fp, tmp) < 0) {
6162 FILE_OP_ERROR(tmp, "chmod");
6163 g_warning("can't change file mode");
6166 /* queueing variables */
6167 err |= (fprintf(fp, "AF:\n") < 0);
6168 err |= (fprintf(fp, "NF:0\n") < 0);
6169 err |= (fprintf(fp, "PS:10\n") < 0);
6170 err |= (fprintf(fp, "SRH:1\n") < 0);
6171 err |= (fprintf(fp, "SFN:\n") < 0);
6172 err |= (fprintf(fp, "DSR:\n") < 0);
6173 if (compose->msgid)
6174 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6175 else
6176 err |= (fprintf(fp, "MID:\n") < 0);
6177 err |= (fprintf(fp, "CFG:\n") < 0);
6178 err |= (fprintf(fp, "PT:0\n") < 0);
6179 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6180 err |= (fprintf(fp, "RQ:\n") < 0);
6181 if (mailac)
6182 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6183 else
6184 err |= (fprintf(fp, "SSV:\n") < 0);
6185 if (newsac)
6186 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6187 else
6188 err |= (fprintf(fp, "NSV:\n") < 0);
6189 err |= (fprintf(fp, "SSH:\n") < 0);
6190 /* write recipient list */
6191 if (compose->to_list) {
6192 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6193 for (cur = compose->to_list->next; cur != NULL;
6194 cur = cur->next)
6195 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6196 err |= (fprintf(fp, "\n") < 0);
6198 /* write newsgroup list */
6199 if (compose->newsgroup_list) {
6200 err |= (fprintf(fp, "NG:") < 0);
6201 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6202 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6203 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6204 err |= (fprintf(fp, "\n") < 0);
6206 /* account IDs */
6207 if (mailac)
6208 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6209 if (newsac)
6210 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6213 if (compose->privacy_system != NULL) {
6214 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6215 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6216 if (compose->use_encryption) {
6217 if (!compose_warn_encryption(compose)) {
6218 claws_fclose(fp);
6219 claws_unlink(tmp);
6220 g_free(tmp);
6221 return COMPOSE_QUEUE_ERROR_NO_MSG;
6223 if (mailac && mailac->encrypt_to_self) {
6224 GSList *tmp_list = g_slist_copy(compose->to_list);
6225 tmp_list = g_slist_append(tmp_list, compose->account->address);
6226 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6227 g_slist_free(tmp_list);
6228 } else {
6229 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6231 if (compose->encdata != NULL) {
6232 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6233 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6234 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6235 compose->encdata) < 0);
6236 } /* else we finally dont want to encrypt */
6237 } else {
6238 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6239 /* and if encdata was null, it means there's been a problem in
6240 * key selection */
6241 if (err == TRUE)
6242 g_warning("failed to write queue message");
6243 claws_fclose(fp);
6244 claws_unlink(tmp);
6245 g_free(tmp);
6246 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6251 /* Save copy folder */
6252 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6253 gchar *savefolderid;
6255 savefolderid = compose_get_save_to(compose);
6256 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6257 g_free(savefolderid);
6259 /* Save copy folder */
6260 if (compose->return_receipt) {
6261 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6263 /* Message-ID of message replying to */
6264 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6265 gchar *folderid = NULL;
6267 if (compose->replyinfo->folder)
6268 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6269 if (folderid == NULL)
6270 folderid = g_strdup("NULL");
6272 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6273 g_free(folderid);
6275 /* Message-ID of message forwarding to */
6276 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6277 gchar *folderid = NULL;
6279 if (compose->fwdinfo->folder)
6280 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6281 if (folderid == NULL)
6282 folderid = g_strdup("NULL");
6284 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6285 g_free(folderid);
6288 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6289 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6291 /* end of headers */
6292 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6294 if (compose->redirect_filename != NULL) {
6295 if (compose_redirect_write_to_file(compose, fp) < 0) {
6296 claws_fclose(fp);
6297 claws_unlink(tmp);
6298 g_free(tmp);
6299 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6301 } else {
6302 gint result = 0;
6303 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6304 claws_fclose(fp);
6305 claws_unlink(tmp);
6306 g_free(tmp);
6307 return result;
6310 if (err == TRUE) {
6311 g_warning("failed to write queue message");
6312 claws_fclose(fp);
6313 claws_unlink(tmp);
6314 g_free(tmp);
6315 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6317 if (claws_safe_fclose(fp) == EOF) {
6318 FILE_OP_ERROR(tmp, "claws_fclose");
6319 claws_unlink(tmp);
6320 g_free(tmp);
6321 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6324 if (item && *item) {
6325 queue = *item;
6326 } else {
6327 queue = account_get_special_folder(compose->account, F_QUEUE);
6329 if (!queue) {
6330 g_warning("can't find queue folder");
6331 claws_unlink(tmp);
6332 g_free(tmp);
6333 return COMPOSE_QUEUE_ERROR_NO_MSG;
6335 folder_item_scan(queue);
6336 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6337 g_warning("can't queue the message");
6338 claws_unlink(tmp);
6339 g_free(tmp);
6340 return COMPOSE_QUEUE_ERROR_NO_MSG;
6343 if (msgpath == NULL) {
6344 claws_unlink(tmp);
6345 g_free(tmp);
6346 } else
6347 *msgpath = tmp;
6349 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6350 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6351 if (mi) {
6352 procmsg_msginfo_change_flags(mi,
6353 compose->targetinfo->flags.perm_flags,
6354 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6355 0, 0);
6357 g_slist_free(mi->tags);
6358 mi->tags = g_slist_copy(compose->targetinfo->tags);
6359 procmsg_msginfo_free(&mi);
6363 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6364 compose_remove_reedit_target(compose, FALSE);
6367 if ((msgnum != NULL) && (item != NULL)) {
6368 *msgnum = num;
6369 *item = queue;
6372 return COMPOSE_QUEUE_SUCCESS;
6375 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6377 AttachInfo *ainfo;
6378 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6379 MimeInfo *mimepart;
6380 #ifdef G_OS_WIN32
6381 GFile *f;
6382 GFileInfo *fi;
6383 GError *error = NULL;
6384 #else
6385 GStatBuf statbuf;
6386 #endif
6387 goffset size;
6388 gchar *type, *subtype;
6389 GtkTreeModel *model;
6390 GtkTreeIter iter;
6392 model = gtk_tree_view_get_model(tree_view);
6394 if (!gtk_tree_model_get_iter_first(model, &iter))
6395 return 0;
6396 do {
6397 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6399 if (!is_file_exist(ainfo->file)) {
6400 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6401 AlertValue val = alertpanel_full(_("Warning"), msg,
6402 _("Cancel sending"), _("Ignore attachment"), NULL,
6403 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6404 g_free(msg);
6405 if (val == G_ALERTDEFAULT) {
6406 return -1;
6408 continue;
6410 #ifdef G_OS_WIN32
6411 f = g_file_new_for_path(ainfo->file);
6412 fi = g_file_query_info(f, "standard::size",
6413 G_FILE_QUERY_INFO_NONE, NULL, &error);
6414 if (error != NULL) {
6415 g_warning(error->message);
6416 g_error_free(error);
6417 g_object_unref(f);
6418 return -1;
6420 size = g_file_info_get_size(fi);
6421 g_object_unref(fi);
6422 g_object_unref(f);
6423 #else
6424 if (g_stat(ainfo->file, &statbuf) < 0)
6425 return -1;
6426 size = statbuf.st_size;
6427 #endif
6429 mimepart = procmime_mimeinfo_new();
6430 mimepart->content = MIMECONTENT_FILE;
6431 mimepart->data.filename = g_strdup(ainfo->file);
6432 mimepart->tmp = FALSE; /* or we destroy our attachment */
6433 mimepart->offset = 0;
6434 mimepart->length = size;
6436 type = g_strdup(ainfo->content_type);
6438 if (!strchr(type, '/')) {
6439 g_free(type);
6440 type = g_strdup("application/octet-stream");
6443 subtype = strchr(type, '/') + 1;
6444 *(subtype - 1) = '\0';
6445 mimepart->type = procmime_get_media_type(type);
6446 mimepart->subtype = g_strdup(subtype);
6447 g_free(type);
6449 if (mimepart->type == MIMETYPE_MESSAGE &&
6450 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6451 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6452 } else if (mimepart->type == MIMETYPE_TEXT) {
6453 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6454 /* Text parts with no name come from multipart/alternative
6455 * forwards. Make sure the recipient won't look at the
6456 * original HTML part by mistake. */
6457 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6458 ainfo->name = g_strdup_printf(_("Original %s part"),
6459 mimepart->subtype);
6461 if (ainfo->charset)
6462 g_hash_table_insert(mimepart->typeparameters,
6463 g_strdup("charset"), g_strdup(ainfo->charset));
6465 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6466 if (mimepart->type == MIMETYPE_APPLICATION &&
6467 !g_strcmp0(mimepart->subtype, "octet-stream"))
6468 g_hash_table_insert(mimepart->typeparameters,
6469 g_strdup("name"), g_strdup(ainfo->name));
6470 g_hash_table_insert(mimepart->dispositionparameters,
6471 g_strdup("filename"), g_strdup(ainfo->name));
6472 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6475 if (mimepart->type == MIMETYPE_MESSAGE
6476 || mimepart->type == MIMETYPE_MULTIPART)
6477 ainfo->encoding = ENC_BINARY;
6478 else if (compose->use_signing || compose->fwdinfo != NULL) {
6479 if (ainfo->encoding == ENC_7BIT)
6480 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6481 else if (ainfo->encoding == ENC_8BIT)
6482 ainfo->encoding = ENC_BASE64;
6485 procmime_encode_content(mimepart, ainfo->encoding);
6487 g_node_append(parent->node, mimepart->node);
6488 } while (gtk_tree_model_iter_next(model, &iter));
6490 return 0;
6493 static gchar *compose_quote_list_of_addresses(gchar *str)
6495 GSList *list = NULL, *item = NULL;
6496 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6498 list = address_list_append_with_comments(list, str);
6499 for (item = list; item != NULL; item = item->next) {
6500 gchar *spec = item->data;
6501 gchar *endofname = strstr(spec, " <");
6502 if (endofname != NULL) {
6503 gchar * qqname;
6504 *endofname = '\0';
6505 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6506 qqname = escape_internal_quotes(qname, '"');
6507 *endofname = ' ';
6508 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6509 gchar *addr = g_strdup(endofname);
6510 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6511 faddr = g_strconcat(name, addr, NULL);
6512 g_free(name);
6513 g_free(addr);
6514 debug_print("new auto-quoted address: '%s'\n", faddr);
6517 if (result == NULL)
6518 result = g_strdup((faddr != NULL)? faddr: spec);
6519 else {
6520 result = g_strconcat(result,
6521 ", ",
6522 (faddr != NULL)? faddr: spec,
6523 NULL);
6525 if (faddr != NULL) {
6526 g_free(faddr);
6527 faddr = NULL;
6530 slist_free_strings_full(list);
6532 return result;
6535 #define IS_IN_CUSTOM_HEADER(header) \
6536 (compose->account->add_customhdr && \
6537 custom_header_find(compose->account->customhdr_list, header) != NULL)
6539 static const gchar *compose_untranslated_header_name(gchar *header_name)
6541 /* return the untranslated header name, if header_name is a known
6542 header name, in either its translated or untranslated form, with
6543 or without trailing colon. otherwise, returns header_name. */
6544 gchar *translated_header_name;
6545 gchar *translated_header_name_wcolon;
6546 const gchar *untranslated_header_name;
6547 const gchar *untranslated_header_name_wcolon;
6548 gint i;
6550 cm_return_val_if_fail(header_name != NULL, NULL);
6552 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6553 untranslated_header_name = HEADERS[i].header_name;
6554 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6556 translated_header_name = gettext(untranslated_header_name);
6557 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6559 if (!strcmp(header_name, untranslated_header_name) ||
6560 !strcmp(header_name, translated_header_name)) {
6561 return untranslated_header_name;
6562 } else {
6563 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6564 !strcmp(header_name, translated_header_name_wcolon)) {
6565 return untranslated_header_name_wcolon;
6569 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6570 return header_name;
6573 static void compose_add_headerfield_from_headerlist(Compose *compose,
6574 GString *header,
6575 const gchar *fieldname,
6576 const gchar *seperator)
6578 gchar *str, *fieldname_w_colon;
6579 gboolean add_field = FALSE;
6580 GSList *list;
6581 ComposeHeaderEntry *headerentry;
6582 const gchar *headerentryname;
6583 const gchar *trans_fieldname;
6584 GString *fieldstr;
6586 if (IS_IN_CUSTOM_HEADER(fieldname))
6587 return;
6589 debug_print("Adding %s-fields\n", fieldname);
6591 fieldstr = g_string_sized_new(64);
6593 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6594 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6596 for (list = compose->header_list; list; list = list->next) {
6597 headerentry = ((ComposeHeaderEntry *)list->data);
6598 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6600 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6601 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6602 g_strstrip(ustr);
6603 str = compose_quote_list_of_addresses(ustr);
6604 g_free(ustr);
6605 if (str != NULL && str[0] != '\0') {
6606 if (add_field)
6607 g_string_append(fieldstr, seperator);
6608 g_string_append(fieldstr, str);
6609 add_field = TRUE;
6611 g_free(str);
6614 if (add_field) {
6615 gchar *buf;
6617 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6618 compose_convert_header
6619 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6620 strlen(fieldname) + 2, TRUE);
6621 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6622 g_free(buf);
6625 g_free(fieldname_w_colon);
6626 g_string_free(fieldstr, TRUE);
6628 return;
6631 static gchar *compose_get_manual_headers_info(Compose *compose)
6633 GString *sh_header = g_string_new(" ");
6634 GSList *list;
6635 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6637 for (list = compose->header_list; list; list = list->next) {
6638 ComposeHeaderEntry *headerentry;
6639 gchar *tmp;
6640 gchar *headername;
6641 gchar *headername_wcolon;
6642 const gchar *headername_trans;
6643 gchar **string;
6644 gboolean standard_header = FALSE;
6646 headerentry = ((ComposeHeaderEntry *)list->data);
6648 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6649 g_strstrip(tmp);
6650 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6651 g_free(tmp);
6652 continue;
6655 if (!strstr(tmp, ":")) {
6656 headername_wcolon = g_strconcat(tmp, ":", NULL);
6657 headername = g_strdup(tmp);
6658 } else {
6659 headername_wcolon = g_strdup(tmp);
6660 headername = g_strdup(strtok(tmp, ":"));
6662 g_free(tmp);
6664 string = std_headers;
6665 while (*string != NULL) {
6666 headername_trans = prefs_common_translated_header_name(*string);
6667 if (!strcmp(headername_trans, headername_wcolon))
6668 standard_header = TRUE;
6669 string++;
6671 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6672 g_string_append_printf(sh_header, "%s ", headername);
6673 g_free(headername);
6674 g_free(headername_wcolon);
6676 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6677 return g_string_free(sh_header, FALSE);
6680 static gchar *compose_get_header(Compose *compose)
6682 gchar date[RFC822_DATE_BUFFSIZE];
6683 gchar buf[BUFFSIZE];
6684 const gchar *entry_str;
6685 gchar *str;
6686 gchar *name;
6687 GSList *list;
6688 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6689 GString *header;
6690 gchar *from_name = NULL, *from_address = NULL;
6691 gchar *tmp;
6693 cm_return_val_if_fail(compose->account != NULL, NULL);
6694 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6696 header = g_string_sized_new(64);
6698 /* Date */
6699 if (prefs_common.hide_timezone)
6700 get_rfc822_date_hide_tz(date, sizeof(date));
6701 else
6702 get_rfc822_date(date, sizeof(date));
6703 g_string_append_printf(header, "Date: %s\n", date);
6705 /* From */
6707 if (compose->account->name && *compose->account->name) {
6708 gchar *buf;
6709 QUOTE_IF_REQUIRED(buf, compose->account->name);
6710 tmp = g_strdup_printf("%s <%s>",
6711 buf, compose->account->address);
6712 } else {
6713 tmp = g_strdup_printf("%s",
6714 compose->account->address);
6716 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6717 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6718 /* use default */
6719 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6720 from_address = g_strdup(compose->account->address);
6721 } else {
6722 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6723 /* extract name and address */
6724 if (strstr(spec, " <") && strstr(spec, ">")) {
6725 from_address = g_strdup(strrchr(spec, '<')+1);
6726 *(strrchr(from_address, '>')) = '\0';
6727 from_name = g_strdup(spec);
6728 *(strrchr(from_name, '<')) = '\0';
6729 } else {
6730 from_name = NULL;
6731 from_address = g_strdup(spec);
6733 g_free(spec);
6735 g_free(tmp);
6738 if (from_name && *from_name) {
6739 gchar *qname;
6740 compose_convert_header
6741 (compose, buf, sizeof(buf), from_name,
6742 strlen("From: "), TRUE);
6743 QUOTE_IF_REQUIRED(name, buf);
6744 qname = escape_internal_quotes(name, '"');
6746 g_string_append_printf(header, "From: %s <%s>\n",
6747 qname, from_address);
6748 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6749 compose->return_receipt) {
6750 compose_convert_header(compose, buf, sizeof(buf), from_name,
6751 strlen("Disposition-Notification-To: "),
6752 TRUE);
6753 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6755 if (qname != name)
6756 g_free(qname);
6757 } else {
6758 g_string_append_printf(header, "From: %s\n", from_address);
6759 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6760 compose->return_receipt)
6761 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6764 g_free(from_name);
6765 g_free(from_address);
6767 /* To */
6768 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6770 /* Newsgroups */
6771 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6773 /* Cc */
6774 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6776 /* Bcc */
6778 * If this account is a NNTP account remove Bcc header from
6779 * message body since it otherwise will be publicly shown
6781 if (compose->account->protocol != A_NNTP)
6782 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6784 /* Subject */
6785 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6787 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6788 g_strstrip(str);
6789 if (*str != '\0') {
6790 compose_convert_header(compose, buf, sizeof(buf), str,
6791 strlen("Subject: "), FALSE);
6792 g_string_append_printf(header, "Subject: %s\n", buf);
6795 g_free(str);
6797 /* Message-ID */
6798 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6799 g_string_append_printf(header, "Message-ID: <%s>\n",
6800 compose->msgid);
6803 if (compose->remove_references == FALSE) {
6804 /* In-Reply-To */
6805 if (compose->inreplyto && compose->to_list)
6806 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6808 /* References */
6809 if (compose->references)
6810 g_string_append_printf(header, "References: %s\n", compose->references);
6813 /* Followup-To */
6814 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6816 /* Reply-To */
6817 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6819 /* Organization */
6820 if (compose->account->organization &&
6821 strlen(compose->account->organization) &&
6822 !IS_IN_CUSTOM_HEADER("Organization")) {
6823 compose_convert_header(compose, buf, sizeof(buf),
6824 compose->account->organization,
6825 strlen("Organization: "), FALSE);
6826 g_string_append_printf(header, "Organization: %s\n", buf);
6829 /* Program version and system info */
6830 if (compose->account->gen_xmailer &&
6831 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6832 !compose->newsgroup_list) {
6833 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6834 prog_version,
6835 gtk_major_version, gtk_minor_version, gtk_micro_version,
6836 TARGET_ALIAS);
6838 if (compose->account->gen_xmailer &&
6839 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6840 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6841 prog_version,
6842 gtk_major_version, gtk_minor_version, gtk_micro_version,
6843 TARGET_ALIAS);
6846 /* custom headers */
6847 if (compose->account->add_customhdr) {
6848 GSList *cur;
6850 for (cur = compose->account->customhdr_list; cur != NULL;
6851 cur = cur->next) {
6852 CustomHeader *chdr = (CustomHeader *)cur->data;
6854 if (custom_header_is_allowed(chdr->name)
6855 && chdr->value != NULL
6856 && *(chdr->value) != '\0') {
6857 compose_convert_header
6858 (compose, buf, sizeof(buf),
6859 chdr->value,
6860 strlen(chdr->name) + 2, FALSE);
6861 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6866 /* Automatic Faces and X-Faces */
6867 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6868 g_string_append_printf(header, "X-Face: %s\n", buf);
6870 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6871 g_string_append_printf(header, "X-Face: %s\n", buf);
6873 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6874 g_string_append_printf(header, "Face: %s\n", buf);
6876 else if (get_default_face (buf, sizeof(buf)) == 0) {
6877 g_string_append_printf(header, "Face: %s\n", buf);
6880 /* PRIORITY */
6881 switch (compose->priority) {
6882 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6883 "X-Priority: 1 (Highest)\n");
6884 break;
6885 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6886 "X-Priority: 2 (High)\n");
6887 break;
6888 case PRIORITY_NORMAL: break;
6889 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6890 "X-Priority: 4 (Low)\n");
6891 break;
6892 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6893 "X-Priority: 5 (Lowest)\n");
6894 break;
6895 default: debug_print("compose: priority unknown : %d\n",
6896 compose->priority);
6899 /* get special headers */
6900 for (list = compose->header_list; list; list = list->next) {
6901 ComposeHeaderEntry *headerentry;
6902 gchar *tmp;
6903 gchar *headername;
6904 gchar *headername_wcolon;
6905 const gchar *headername_trans;
6906 gchar *headervalue;
6907 gchar **string;
6908 gboolean standard_header = FALSE;
6910 headerentry = ((ComposeHeaderEntry *)list->data);
6912 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6913 g_strstrip(tmp);
6914 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6915 g_free(tmp);
6916 continue;
6919 if (!strstr(tmp, ":")) {
6920 headername_wcolon = g_strconcat(tmp, ":", NULL);
6921 headername = g_strdup(tmp);
6922 } else {
6923 headername_wcolon = g_strdup(tmp);
6924 headername = g_strdup(strtok(tmp, ":"));
6926 g_free(tmp);
6928 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6929 Xstrdup_a(headervalue, entry_str, return NULL);
6930 subst_char(headervalue, '\r', ' ');
6931 subst_char(headervalue, '\n', ' ');
6932 g_strstrip(headervalue);
6933 if (*headervalue != '\0') {
6934 string = std_headers;
6935 while (*string != NULL && !standard_header) {
6936 headername_trans = prefs_common_translated_header_name(*string);
6937 /* support mixed translated and untranslated headers */
6938 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6939 standard_header = TRUE;
6940 string++;
6942 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6943 /* store untranslated header name */
6944 g_string_append_printf(header, "%s %s\n",
6945 compose_untranslated_header_name(headername_wcolon), headervalue);
6948 g_free(headername);
6949 g_free(headername_wcolon);
6952 str = header->str;
6953 g_string_free(header, FALSE);
6955 return str;
6958 #undef IS_IN_CUSTOM_HEADER
6960 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6961 gint header_len, gboolean addr_field)
6963 gchar *tmpstr = NULL;
6964 const gchar *out_codeset = NULL;
6966 cm_return_if_fail(src != NULL);
6967 cm_return_if_fail(dest != NULL);
6969 if (len < 1) return;
6971 tmpstr = g_strdup(src);
6973 subst_char(tmpstr, '\n', ' ');
6974 subst_char(tmpstr, '\r', ' ');
6975 g_strchomp(tmpstr);
6977 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6978 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6979 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6980 g_free(tmpstr);
6981 tmpstr = mybuf;
6984 codeconv_set_strict(TRUE);
6985 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6986 conv_get_charset_str(compose->out_encoding));
6987 codeconv_set_strict(FALSE);
6989 if (!dest || *dest == '\0') {
6990 gchar *test_conv_global_out = NULL;
6991 gchar *test_conv_reply = NULL;
6993 /* automatic mode. be automatic. */
6994 codeconv_set_strict(TRUE);
6996 out_codeset = conv_get_outgoing_charset_str();
6997 if (out_codeset) {
6998 debug_print("trying to convert to %s\n", out_codeset);
6999 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7002 if (!test_conv_global_out && compose->orig_charset
7003 && strcmp(compose->orig_charset, CS_US_ASCII)) {
7004 out_codeset = compose->orig_charset;
7005 debug_print("failure; trying to convert to %s\n", out_codeset);
7006 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7009 if (!test_conv_global_out && !test_conv_reply) {
7010 /* we're lost */
7011 out_codeset = CS_INTERNAL;
7012 debug_print("finally using %s\n", out_codeset);
7014 g_free(test_conv_global_out);
7015 g_free(test_conv_reply);
7016 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7017 out_codeset);
7018 codeconv_set_strict(FALSE);
7020 g_free(tmpstr);
7023 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7025 gchar *address;
7027 cm_return_if_fail(user_data != NULL);
7029 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7030 g_strstrip(address);
7031 if (*address != '\0') {
7032 gchar *name = procheader_get_fromname(address);
7033 extract_address(address);
7034 #ifndef USE_ALT_ADDRBOOK
7035 addressbook_add_contact(name, address, NULL, NULL);
7036 #else
7037 debug_print("%s: %s\n", name, address);
7038 if (addressadd_selection(name, address, NULL, NULL)) {
7039 debug_print( "addressbook_add_contact - added\n" );
7041 #endif
7043 g_free(address);
7046 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7048 GtkWidget *menuitem;
7049 gchar *address;
7051 cm_return_if_fail(menu != NULL);
7052 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7054 menuitem = gtk_separator_menu_item_new();
7055 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7056 gtk_widget_show(menuitem);
7058 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7059 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7061 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7062 g_strstrip(address);
7063 if (*address == '\0') {
7064 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7067 g_signal_connect(G_OBJECT(menuitem), "activate",
7068 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7069 gtk_widget_show(menuitem);
7072 void compose_add_extra_header(gchar *header, GtkListStore *model)
7074 GtkTreeIter iter;
7075 if (strcmp(header, "")) {
7076 COMBOBOX_ADD(model, header, COMPOSE_TO);
7080 void compose_add_extra_header_entries(GtkListStore *model)
7082 FILE *exh;
7083 gchar *exhrc;
7084 gchar buf[BUFFSIZE];
7085 gint lastc;
7087 if (extra_headers == NULL) {
7088 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7089 if ((exh = claws_fopen(exhrc, "rb")) == NULL) {
7090 debug_print("extra headers file not found\n");
7091 goto extra_headers_done;
7093 while (claws_fgets(buf, BUFFSIZE, exh) != NULL) {
7094 lastc = strlen(buf) - 1; /* remove trailing control chars */
7095 while (lastc >= 0 && buf[lastc] != ':')
7096 buf[lastc--] = '\0';
7097 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7098 buf[lastc] = '\0'; /* remove trailing : for comparison */
7099 if (custom_header_is_allowed(buf)) {
7100 buf[lastc] = ':';
7101 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7103 else
7104 g_message("disallowed extra header line: %s\n", buf);
7106 else {
7107 if (buf[0] != '#')
7108 g_message("invalid extra header line: %s\n", buf);
7111 claws_fclose(exh);
7112 extra_headers_done:
7113 g_free(exhrc);
7114 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7115 extra_headers = g_slist_reverse(extra_headers);
7117 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7120 #ifdef USE_LDAP
7121 static void _ldap_srv_func(gpointer data, gpointer user_data)
7123 LdapServer *server = (LdapServer *)data;
7124 gboolean *enable = (gboolean *)user_data;
7126 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7127 server->searchFlag = *enable;
7129 #endif
7131 static void compose_create_header_entry(Compose *compose)
7133 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7135 GtkWidget *combo;
7136 GtkWidget *entry;
7137 GtkWidget *button;
7138 GtkWidget *hbox;
7139 gchar **string;
7140 const gchar *header = NULL;
7141 ComposeHeaderEntry *headerentry;
7142 gboolean standard_header = FALSE;
7143 GtkListStore *model;
7144 GtkTreeIter iter;
7146 headerentry = g_new0(ComposeHeaderEntry, 1);
7148 /* Combo box model */
7149 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7150 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7151 COMPOSE_TO);
7152 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7153 COMPOSE_CC);
7154 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7155 COMPOSE_BCC);
7156 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7157 COMPOSE_NEWSGROUPS);
7158 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7159 COMPOSE_REPLYTO);
7160 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7161 COMPOSE_FOLLOWUPTO);
7162 compose_add_extra_header_entries(model);
7164 /* Combo box */
7165 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7166 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7167 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7168 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7169 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7170 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7171 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7172 G_CALLBACK(compose_grab_focus_cb), compose);
7173 gtk_widget_show(combo);
7175 /* Putting only the combobox child into focus chain of its parent causes
7176 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7177 * This eliminates need to pres Tab twice in order to really get from the
7178 * combobox to next widget. */
7179 GList *l = NULL;
7180 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7181 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7182 g_list_free(l);
7184 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7185 compose->header_nextrow, compose->header_nextrow+1,
7186 GTK_SHRINK, GTK_FILL, 0, 0);
7187 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7188 const gchar *last_header_entry = gtk_entry_get_text(
7189 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7190 string = headers;
7191 while (*string != NULL) {
7192 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7193 standard_header = TRUE;
7194 string++;
7196 if (standard_header)
7197 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7199 if (!compose->header_last || !standard_header) {
7200 switch(compose->account->protocol) {
7201 case A_NNTP:
7202 header = prefs_common_translated_header_name("Newsgroups:");
7203 break;
7204 default:
7205 header = prefs_common_translated_header_name("To:");
7206 break;
7209 if (header)
7210 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7212 gtk_editable_set_editable(
7213 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7214 prefs_common.type_any_header);
7216 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7217 G_CALLBACK(compose_grab_focus_cb), compose);
7219 /* Entry field with cleanup button */
7220 button = gtk_button_new();
7221 gtk_button_set_image(GTK_BUTTON(button),
7222 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7223 gtk_widget_show(button);
7224 CLAWS_SET_TIP(button,
7225 _("Delete entry contents"));
7226 entry = gtk_entry_new();
7227 gtk_widget_show(entry);
7228 CLAWS_SET_TIP(entry,
7229 _("Use <tab> to autocomplete from addressbook"));
7230 hbox = gtk_hbox_new (FALSE, 0);
7231 gtk_widget_show(hbox);
7232 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7233 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7234 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7235 compose->header_nextrow, compose->header_nextrow+1,
7236 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7238 g_signal_connect(G_OBJECT(entry), "key-press-event",
7239 G_CALLBACK(compose_headerentry_key_press_event_cb),
7240 headerentry);
7241 g_signal_connect(G_OBJECT(entry), "changed",
7242 G_CALLBACK(compose_headerentry_changed_cb),
7243 headerentry);
7244 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7245 G_CALLBACK(compose_grab_focus_cb), compose);
7247 g_signal_connect(G_OBJECT(button), "clicked",
7248 G_CALLBACK(compose_headerentry_button_clicked_cb),
7249 headerentry);
7251 /* email dnd */
7252 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7253 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7254 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7255 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7256 G_CALLBACK(compose_header_drag_received_cb),
7257 entry);
7258 g_signal_connect(G_OBJECT(entry), "drag-drop",
7259 G_CALLBACK(compose_drag_drop),
7260 compose);
7261 g_signal_connect(G_OBJECT(entry), "populate-popup",
7262 G_CALLBACK(compose_entry_popup_extend),
7263 NULL);
7265 #ifdef USE_LDAP
7266 #ifndef PASSWORD_CRYPTO_OLD
7267 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7268 if (pwd_servers != NULL && master_passphrase() == NULL) {
7269 gboolean enable = FALSE;
7270 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7271 /* Temporarily disable password-protected LDAP servers,
7272 * because user did not provide a master passphrase.
7273 * We can safely enable searchFlag on all servers in this list
7274 * later, since addrindex_get_password_protected_ldap_servers()
7275 * includes servers which have it enabled initially. */
7276 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7277 compose->passworded_ldap_servers = pwd_servers;
7279 #endif /* PASSWORD_CRYPTO_OLD */
7280 #endif /* USE_LDAP */
7282 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7284 headerentry->compose = compose;
7285 headerentry->combo = combo;
7286 headerentry->entry = entry;
7287 headerentry->button = button;
7288 headerentry->hbox = hbox;
7289 headerentry->headernum = compose->header_nextrow;
7290 headerentry->type = PREF_NONE;
7292 compose->header_nextrow++;
7293 compose->header_last = headerentry;
7294 compose->header_list =
7295 g_slist_append(compose->header_list,
7296 headerentry);
7299 static void compose_add_header_entry(Compose *compose, const gchar *header,
7300 gchar *text, ComposePrefType pref_type)
7302 ComposeHeaderEntry *last_header = compose->header_last;
7303 gchar *tmp = g_strdup(text), *email;
7304 gboolean replyto_hdr;
7306 replyto_hdr = (!strcasecmp(header,
7307 prefs_common_translated_header_name("Reply-To:")) ||
7308 !strcasecmp(header,
7309 prefs_common_translated_header_name("Followup-To:")) ||
7310 !strcasecmp(header,
7311 prefs_common_translated_header_name("In-Reply-To:")));
7313 extract_address(tmp);
7314 email = g_utf8_strdown(tmp, -1);
7316 if (replyto_hdr == FALSE &&
7317 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7319 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7320 header, text, (gint) pref_type);
7321 g_free(email);
7322 g_free(tmp);
7323 return;
7326 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7327 gtk_entry_set_text(GTK_ENTRY(
7328 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7329 else
7330 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7331 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7332 last_header->type = pref_type;
7334 if (replyto_hdr == FALSE)
7335 g_hash_table_insert(compose->email_hashtable, email,
7336 GUINT_TO_POINTER(1));
7337 else
7338 g_free(email);
7340 g_free(tmp);
7343 static void compose_destroy_headerentry(Compose *compose,
7344 ComposeHeaderEntry *headerentry)
7346 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7347 gchar *email;
7349 extract_address(text);
7350 email = g_utf8_strdown(text, -1);
7351 g_hash_table_remove(compose->email_hashtable, email);
7352 g_free(text);
7353 g_free(email);
7355 gtk_widget_destroy(headerentry->combo);
7356 gtk_widget_destroy(headerentry->entry);
7357 gtk_widget_destroy(headerentry->button);
7358 gtk_widget_destroy(headerentry->hbox);
7359 g_free(headerentry);
7362 static void compose_remove_header_entries(Compose *compose)
7364 GSList *list;
7365 for (list = compose->header_list; list; list = list->next)
7366 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7368 compose->header_last = NULL;
7369 g_slist_free(compose->header_list);
7370 compose->header_list = NULL;
7371 compose->header_nextrow = 1;
7372 compose_create_header_entry(compose);
7375 static GtkWidget *compose_create_header(Compose *compose)
7377 GtkWidget *from_optmenu_hbox;
7378 GtkWidget *header_table_main;
7379 GtkWidget *header_scrolledwin;
7380 GtkWidget *header_table;
7382 /* parent with account selection and from header */
7383 header_table_main = gtk_table_new(2, 2, FALSE);
7384 gtk_widget_show(header_table_main);
7385 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7387 from_optmenu_hbox = compose_account_option_menu_create(compose);
7388 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7389 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7391 /* child with header labels and entries */
7392 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7393 gtk_widget_show(header_scrolledwin);
7394 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7396 header_table = gtk_table_new(2, 2, FALSE);
7397 gtk_widget_show(header_table);
7398 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7399 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7400 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7401 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7402 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7404 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7405 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7407 compose->header_table = header_table;
7408 compose->header_list = NULL;
7409 compose->header_nextrow = 0;
7411 compose_create_header_entry(compose);
7413 compose->table = NULL;
7415 return header_table_main;
7418 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7420 Compose *compose = (Compose *)data;
7421 GdkEventButton event;
7423 event.button = 3;
7424 event.time = gtk_get_current_event_time();
7426 return attach_button_pressed(compose->attach_clist, &event, compose);
7429 static GtkWidget *compose_create_attach(Compose *compose)
7431 GtkWidget *attach_scrwin;
7432 GtkWidget *attach_clist;
7434 GtkListStore *store;
7435 GtkCellRenderer *renderer;
7436 GtkTreeViewColumn *column;
7437 GtkTreeSelection *selection;
7439 /* attachment list */
7440 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7441 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7442 GTK_POLICY_AUTOMATIC,
7443 GTK_POLICY_AUTOMATIC);
7444 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7446 store = gtk_list_store_new(N_ATTACH_COLS,
7447 G_TYPE_STRING,
7448 G_TYPE_STRING,
7449 G_TYPE_STRING,
7450 G_TYPE_STRING,
7451 G_TYPE_POINTER,
7452 G_TYPE_AUTO_POINTER,
7453 -1);
7454 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7455 (GTK_TREE_MODEL(store)));
7456 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7457 g_object_unref(store);
7459 renderer = gtk_cell_renderer_text_new();
7460 column = gtk_tree_view_column_new_with_attributes
7461 (_("Mime type"), renderer, "text",
7462 COL_MIMETYPE, NULL);
7463 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7465 renderer = gtk_cell_renderer_text_new();
7466 column = gtk_tree_view_column_new_with_attributes
7467 (_("Size"), renderer, "text",
7468 COL_SIZE, NULL);
7469 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7471 renderer = gtk_cell_renderer_text_new();
7472 column = gtk_tree_view_column_new_with_attributes
7473 (_("Name"), renderer, "text",
7474 COL_NAME, NULL);
7475 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7477 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7478 prefs_common.use_stripes_everywhere);
7479 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7480 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7482 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7483 G_CALLBACK(attach_selected), compose);
7484 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7485 G_CALLBACK(attach_button_pressed), compose);
7486 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7487 G_CALLBACK(popup_attach_button_pressed), compose);
7488 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7489 G_CALLBACK(attach_key_pressed), compose);
7491 /* drag and drop */
7492 gtk_drag_dest_set(attach_clist,
7493 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7494 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7495 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7496 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7497 G_CALLBACK(compose_attach_drag_received_cb),
7498 compose);
7499 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7500 G_CALLBACK(compose_drag_drop),
7501 compose);
7503 compose->attach_scrwin = attach_scrwin;
7504 compose->attach_clist = attach_clist;
7506 return attach_scrwin;
7509 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7511 static GtkWidget *compose_create_others(Compose *compose)
7513 GtkWidget *table;
7514 GtkWidget *savemsg_checkbtn;
7515 GtkWidget *savemsg_combo;
7516 GtkWidget *savemsg_select;
7518 guint rowcount = 0;
7519 gchar *folderidentifier;
7521 /* Table for settings */
7522 table = gtk_table_new(3, 1, FALSE);
7523 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7524 gtk_widget_show(table);
7525 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7526 rowcount = 0;
7528 /* Save Message to folder */
7529 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7530 gtk_widget_show(savemsg_checkbtn);
7531 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7532 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7533 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7536 savemsg_combo = gtk_combo_box_text_new_with_entry();
7537 compose->savemsg_checkbtn = savemsg_checkbtn;
7538 compose->savemsg_combo = savemsg_combo;
7539 gtk_widget_show(savemsg_combo);
7541 if (prefs_common.compose_save_to_history)
7542 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7543 prefs_common.compose_save_to_history);
7544 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7545 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7546 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7547 G_CALLBACK(compose_grab_focus_cb), compose);
7548 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7549 if (compose->account->set_sent_folder || prefs_common.savemsg)
7550 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), TRUE);
7551 else
7552 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), FALSE);
7553 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), TRUE);
7554 folderidentifier = folder_item_get_identifier(account_get_special_folder
7555 (compose->account, F_OUTBOX));
7556 compose_set_save_to(compose, folderidentifier);
7557 g_free(folderidentifier);
7560 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7561 gtk_widget_show(savemsg_select);
7562 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7563 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7564 G_CALLBACK(compose_savemsg_select_cb),
7565 compose);
7567 return table;
7570 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7572 FolderItem *dest;
7573 gchar * path;
7575 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7576 _("Select folder to save message to"));
7577 if (!dest) return;
7579 path = folder_item_get_identifier(dest);
7581 compose_set_save_to(compose, path);
7582 g_free(path);
7585 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7586 GdkAtom clip, GtkTextIter *insert_place);
7589 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7590 Compose *compose)
7592 gint prev_autowrap;
7593 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7594 #if USE_ENCHANT
7595 if (event->button == 3) {
7596 GtkTextIter iter;
7597 GtkTextIter sel_start, sel_end;
7598 gboolean stuff_selected;
7599 gint x, y;
7600 /* move the cursor to allow GtkAspell to check the word
7601 * under the mouse */
7602 if (event->x && event->y) {
7603 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7604 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7605 &x, &y);
7606 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7607 &iter, x, y);
7608 } else {
7609 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7610 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7612 /* get selection */
7613 stuff_selected = gtk_text_buffer_get_selection_bounds(
7614 buffer,
7615 &sel_start, &sel_end);
7617 gtk_text_buffer_place_cursor (buffer, &iter);
7618 /* reselect stuff */
7619 if (stuff_selected
7620 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7621 gtk_text_buffer_select_range(buffer,
7622 &sel_start, &sel_end);
7624 return FALSE; /* pass the event so that the right-click goes through */
7626 #endif
7627 if (event->button == 2) {
7628 GtkTextIter iter;
7629 gint x, y;
7630 BLOCK_WRAP();
7632 /* get the middle-click position to paste at the correct place */
7633 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7634 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7635 &x, &y);
7636 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7637 &iter, x, y);
7639 entry_paste_clipboard(compose, text,
7640 prefs_common.linewrap_pastes,
7641 GDK_SELECTION_PRIMARY, &iter);
7642 UNBLOCK_WRAP();
7643 return TRUE;
7645 return FALSE;
7648 #if USE_ENCHANT
7649 static void compose_spell_menu_changed(void *data)
7651 Compose *compose = (Compose *)data;
7652 GSList *items;
7653 GtkWidget *menuitem;
7654 GtkWidget *parent_item;
7655 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7656 GSList *spell_menu;
7658 if (compose->gtkaspell == NULL)
7659 return;
7661 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7662 "/Menu/Spelling/Options");
7664 /* setting the submenu removes /Spelling/Options from the factory
7665 * so we need to save it */
7667 if (parent_item == NULL) {
7668 parent_item = compose->aspell_options_menu;
7669 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7670 } else
7671 compose->aspell_options_menu = parent_item;
7673 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7675 spell_menu = g_slist_reverse(spell_menu);
7676 for (items = spell_menu;
7677 items; items = items->next) {
7678 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7679 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7680 gtk_widget_show(GTK_WIDGET(menuitem));
7682 g_slist_free(spell_menu);
7684 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7685 gtk_widget_show(parent_item);
7688 static void compose_dict_changed(void *data)
7690 Compose *compose = (Compose *) data;
7692 if(!compose->gtkaspell)
7693 return;
7694 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7695 return;
7697 gtkaspell_highlight_all(compose->gtkaspell);
7698 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7700 #endif
7702 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7704 Compose *compose = (Compose *)data;
7705 GdkEventButton event;
7707 event.button = 3;
7708 event.time = gtk_get_current_event_time();
7709 event.x = 0;
7710 event.y = 0;
7712 return text_clicked(compose->text, &event, compose);
7715 static gboolean compose_force_window_origin = TRUE;
7716 static Compose *compose_create(PrefsAccount *account,
7717 FolderItem *folder,
7718 ComposeMode mode,
7719 gboolean batch)
7721 Compose *compose;
7722 GtkWidget *window;
7723 GtkWidget *vbox;
7724 GtkWidget *menubar;
7725 GtkWidget *handlebox;
7727 GtkWidget *notebook;
7729 GtkWidget *attach_hbox;
7730 GtkWidget *attach_lab1;
7731 GtkWidget *attach_lab2;
7733 GtkWidget *vbox2;
7735 GtkWidget *label;
7736 GtkWidget *subject_hbox;
7737 GtkWidget *subject_frame;
7738 GtkWidget *subject_entry;
7739 GtkWidget *subject;
7740 GtkWidget *paned;
7742 GtkWidget *edit_vbox;
7743 GtkWidget *ruler_hbox;
7744 GtkWidget *ruler;
7745 GtkWidget *scrolledwin;
7746 GtkWidget *text;
7747 GtkTextBuffer *buffer;
7748 GtkClipboard *clipboard;
7750 UndoMain *undostruct;
7752 GtkWidget *popupmenu;
7753 GtkWidget *tmpl_menu;
7754 GtkActionGroup *action_group = NULL;
7756 #if USE_ENCHANT
7757 GtkAspell * gtkaspell = NULL;
7758 #endif
7760 static GdkGeometry geometry;
7762 cm_return_val_if_fail(account != NULL, NULL);
7764 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7765 &default_header_bgcolor);
7766 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7767 &default_header_color);
7769 debug_print("Creating compose window...\n");
7770 compose = g_new0(Compose, 1);
7772 compose->batch = batch;
7773 compose->account = account;
7774 compose->folder = folder;
7776 compose->mutex = cm_mutex_new();
7777 compose->set_cursor_pos = -1;
7779 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7781 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7782 gtk_widget_set_size_request(window, prefs_common.compose_width,
7783 prefs_common.compose_height);
7785 if (!geometry.max_width) {
7786 geometry.max_width = gdk_screen_width();
7787 geometry.max_height = gdk_screen_height();
7790 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7791 &geometry, GDK_HINT_MAX_SIZE);
7792 if (!geometry.min_width) {
7793 geometry.min_width = 600;
7794 geometry.min_height = 440;
7796 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7797 &geometry, GDK_HINT_MIN_SIZE);
7799 #ifndef GENERIC_UMPC
7800 if (compose_force_window_origin)
7801 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7802 prefs_common.compose_y);
7803 #endif
7804 g_signal_connect(G_OBJECT(window), "delete_event",
7805 G_CALLBACK(compose_delete_cb), compose);
7806 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7807 gtk_widget_realize(window);
7809 gtkut_widget_set_composer_icon(window);
7811 vbox = gtk_vbox_new(FALSE, 0);
7812 gtk_container_add(GTK_CONTAINER(window), vbox);
7814 compose->ui_manager = gtk_ui_manager_new();
7815 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7816 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7817 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7818 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7819 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7820 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7821 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7822 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7823 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7824 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7826 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7828 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7829 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7830 #ifdef USE_ENCHANT
7831 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7832 #endif
7833 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7835 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7837 /* Compose menu */
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7839 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7840 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7841 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7847 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7848 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7852 /* Edit menu */
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7854 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7896 #if USE_ENCHANT
7897 /* Spelling menu */
7898 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7904 #endif
7906 /* Options menu */
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7908 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7915 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7922 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7936 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7937 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7938 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7940 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7942 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7943 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)
7944 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)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7947 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7949 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7950 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)
7951 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)
7953 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7955 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7956 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)
7957 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7959 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7960 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)
7961 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7963 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7965 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7966 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)
7967 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7968 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7969 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7970 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7972 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7973 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)
7974 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)
7975 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7976 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7978 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7979 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7980 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7981 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7982 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7983 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7985 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7986 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7987 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)
7989 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7990 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7991 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7992 /* phew. */
7994 /* Tools menu */
7995 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7996 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7997 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7998 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7999 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
8000 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8002 /* Help menu */
8003 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
8005 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
8006 gtk_widget_show_all(menubar);
8008 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
8009 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
8011 if (prefs_common.toolbar_detachable) {
8012 handlebox = gtk_handle_box_new();
8013 } else {
8014 handlebox = gtk_hbox_new(FALSE, 0);
8016 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8018 gtk_widget_realize(handlebox);
8019 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8020 (gpointer)compose);
8022 vbox2 = gtk_vbox_new(FALSE, 2);
8023 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8024 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8026 /* Notebook */
8027 notebook = gtk_notebook_new();
8028 gtk_widget_show(notebook);
8030 /* header labels and entries */
8031 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8032 compose_create_header(compose),
8033 gtk_label_new_with_mnemonic(_("Hea_der")));
8034 /* attachment list */
8035 attach_hbox = gtk_hbox_new(FALSE, 0);
8036 gtk_widget_show(attach_hbox);
8038 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8039 gtk_widget_show(attach_lab1);
8040 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8042 attach_lab2 = gtk_label_new("");
8043 gtk_widget_show(attach_lab2);
8044 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8046 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8047 compose_create_attach(compose),
8048 attach_hbox);
8049 /* Others Tab */
8050 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8051 compose_create_others(compose),
8052 gtk_label_new_with_mnemonic(_("Othe_rs")));
8054 /* Subject */
8055 subject_hbox = gtk_hbox_new(FALSE, 0);
8056 gtk_widget_show(subject_hbox);
8058 subject_frame = gtk_frame_new(NULL);
8059 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8060 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8061 gtk_widget_show(subject_frame);
8063 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8064 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8065 gtk_widget_show(subject);
8067 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8068 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8069 gtk_widget_show(label);
8071 #ifdef USE_ENCHANT
8072 subject_entry = claws_spell_entry_new();
8073 #else
8074 subject_entry = gtk_entry_new();
8075 #endif
8076 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8077 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8078 G_CALLBACK(compose_grab_focus_cb), compose);
8079 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8080 gtk_widget_show(subject_entry);
8081 compose->subject_entry = subject_entry;
8082 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8084 edit_vbox = gtk_vbox_new(FALSE, 0);
8086 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8088 /* ruler */
8089 ruler_hbox = gtk_hbox_new(FALSE, 0);
8090 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8092 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8093 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8094 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8095 BORDER_WIDTH);
8097 /* text widget */
8098 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8099 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8100 GTK_POLICY_AUTOMATIC,
8101 GTK_POLICY_AUTOMATIC);
8102 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8103 GTK_SHADOW_IN);
8104 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8106 text = gtk_text_view_new();
8107 if (prefs_common.show_compose_margin) {
8108 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8109 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8111 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8112 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8113 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8114 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8115 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8117 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8118 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8119 G_CALLBACK(compose_edit_size_alloc),
8120 ruler);
8121 g_signal_connect(G_OBJECT(buffer), "changed",
8122 G_CALLBACK(compose_changed_cb), compose);
8123 g_signal_connect(G_OBJECT(text), "grab_focus",
8124 G_CALLBACK(compose_grab_focus_cb), compose);
8125 g_signal_connect(G_OBJECT(buffer), "insert_text",
8126 G_CALLBACK(text_inserted), compose);
8127 g_signal_connect(G_OBJECT(text), "button_press_event",
8128 G_CALLBACK(text_clicked), compose);
8129 g_signal_connect(G_OBJECT(text), "popup-menu",
8130 G_CALLBACK(compose_popup_menu), compose);
8131 g_signal_connect(G_OBJECT(subject_entry), "changed",
8132 G_CALLBACK(compose_changed_cb), compose);
8133 g_signal_connect(G_OBJECT(subject_entry), "activate",
8134 G_CALLBACK(compose_subject_entry_activated), compose);
8136 /* drag and drop */
8137 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8138 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8139 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8140 g_signal_connect(G_OBJECT(text), "drag_data_received",
8141 G_CALLBACK(compose_insert_drag_received_cb),
8142 compose);
8143 g_signal_connect(G_OBJECT(text), "drag-drop",
8144 G_CALLBACK(compose_drag_drop),
8145 compose);
8146 g_signal_connect(G_OBJECT(text), "key-press-event",
8147 G_CALLBACK(completion_set_focus_to_subject),
8148 compose);
8149 gtk_widget_show_all(vbox);
8151 /* pane between attach clist and text */
8152 paned = gtk_vpaned_new();
8153 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8154 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8155 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8156 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8157 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8158 G_CALLBACK(compose_notebook_size_alloc), paned);
8160 gtk_widget_show_all(paned);
8163 if (prefs_common.textfont) {
8164 PangoFontDescription *font_desc;
8166 font_desc = pango_font_description_from_string
8167 (prefs_common.textfont);
8168 if (font_desc) {
8169 gtk_widget_modify_font(text, font_desc);
8170 pango_font_description_free(font_desc);
8174 gtk_action_group_add_actions(action_group, compose_popup_entries,
8175 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8176 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8177 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8178 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8179 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8180 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8181 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8183 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8185 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8186 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8187 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8189 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8191 undostruct = undo_init(text);
8192 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8193 compose);
8195 address_completion_start(window);
8197 compose->window = window;
8198 compose->vbox = vbox;
8199 compose->menubar = menubar;
8200 compose->handlebox = handlebox;
8202 compose->vbox2 = vbox2;
8204 compose->paned = paned;
8206 compose->attach_label = attach_lab2;
8208 compose->notebook = notebook;
8209 compose->edit_vbox = edit_vbox;
8210 compose->ruler_hbox = ruler_hbox;
8211 compose->ruler = ruler;
8212 compose->scrolledwin = scrolledwin;
8213 compose->text = text;
8215 compose->focused_editable = NULL;
8217 compose->popupmenu = popupmenu;
8219 compose->tmpl_menu = tmpl_menu;
8221 compose->mode = mode;
8222 compose->rmode = mode;
8224 compose->targetinfo = NULL;
8225 compose->replyinfo = NULL;
8226 compose->fwdinfo = NULL;
8228 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8229 g_str_equal, (GDestroyNotify) g_free, NULL);
8231 compose->replyto = NULL;
8232 compose->cc = NULL;
8233 compose->bcc = NULL;
8234 compose->followup_to = NULL;
8236 compose->ml_post = NULL;
8238 compose->inreplyto = NULL;
8239 compose->references = NULL;
8240 compose->msgid = NULL;
8241 compose->boundary = NULL;
8243 compose->autowrap = prefs_common.autowrap;
8244 compose->autoindent = prefs_common.auto_indent;
8245 compose->use_signing = FALSE;
8246 compose->use_encryption = FALSE;
8247 compose->privacy_system = NULL;
8248 compose->encdata = NULL;
8250 compose->modified = FALSE;
8252 compose->return_receipt = FALSE;
8254 compose->to_list = NULL;
8255 compose->newsgroup_list = NULL;
8257 compose->undostruct = undostruct;
8259 compose->sig_str = NULL;
8261 compose->exteditor_file = NULL;
8262 compose->exteditor_pid = -1;
8263 compose->exteditor_tag = -1;
8264 compose->exteditor_socket = NULL;
8265 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8267 compose->folder_update_callback_id =
8268 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8269 compose_update_folder_hook,
8270 (gpointer) compose);
8272 #if USE_ENCHANT
8273 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8274 if (mode != COMPOSE_REDIRECT) {
8275 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8276 strcmp(prefs_common.dictionary, "")) {
8277 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8278 prefs_common.alt_dictionary,
8279 conv_get_locale_charset_str(),
8280 prefs_common.color[COL_MISSPELLED],
8281 prefs_common.check_while_typing,
8282 prefs_common.recheck_when_changing_dict,
8283 prefs_common.use_alternate,
8284 prefs_common.use_both_dicts,
8285 GTK_TEXT_VIEW(text),
8286 GTK_WINDOW(compose->window),
8287 compose_dict_changed,
8288 compose_spell_menu_changed,
8289 compose);
8290 if (!gtkaspell) {
8291 alertpanel_error(_("Spell checker could not "
8292 "be started.\n%s"),
8293 gtkaspell_checkers_strerror());
8294 gtkaspell_checkers_reset_error();
8295 } else {
8296 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8300 compose->gtkaspell = gtkaspell;
8301 compose_spell_menu_changed(compose);
8302 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8303 #endif
8305 compose_select_account(compose, account, TRUE);
8307 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8308 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8310 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8311 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8313 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8314 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8316 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8317 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8319 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8320 if (account->protocol != A_NNTP)
8321 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8322 prefs_common_translated_header_name("To:"));
8323 else
8324 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8325 prefs_common_translated_header_name("Newsgroups:"));
8327 #ifndef USE_ALT_ADDRBOOK
8328 addressbook_set_target_compose(compose);
8329 #endif
8330 if (mode != COMPOSE_REDIRECT)
8331 compose_set_template_menu(compose);
8332 else {
8333 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8336 compose_list = g_list_append(compose_list, compose);
8338 if (!prefs_common.show_ruler)
8339 gtk_widget_hide(ruler_hbox);
8341 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8343 /* Priority */
8344 compose->priority = PRIORITY_NORMAL;
8345 compose_update_priority_menu_item(compose);
8347 compose_set_out_encoding(compose);
8349 /* Actions menu */
8350 compose_update_actions_menu(compose);
8352 /* Privacy Systems menu */
8353 compose_update_privacy_systems_menu(compose);
8354 compose_activate_privacy_system(compose, account, TRUE);
8356 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8357 if (batch) {
8358 gtk_widget_realize(window);
8359 } else {
8360 gtk_widget_show(window);
8363 return compose;
8366 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8368 GList *accounts;
8369 GtkWidget *hbox;
8370 GtkWidget *optmenu;
8371 GtkWidget *optmenubox;
8372 GtkWidget *fromlabel;
8373 GtkListStore *menu;
8374 GtkTreeIter iter;
8375 GtkWidget *from_name = NULL;
8377 gint num = 0, def_menu = 0;
8379 accounts = account_get_list();
8380 cm_return_val_if_fail(accounts != NULL, NULL);
8382 optmenubox = gtk_event_box_new();
8383 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8384 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8386 hbox = gtk_hbox_new(FALSE, 4);
8387 from_name = gtk_entry_new();
8389 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8390 G_CALLBACK(compose_grab_focus_cb), compose);
8391 g_signal_connect_after(G_OBJECT(from_name), "activate",
8392 G_CALLBACK(from_name_activate_cb), optmenu);
8394 for (; accounts != NULL; accounts = accounts->next, num++) {
8395 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8396 gchar *name, *from = NULL;
8398 if (ac == compose->account) def_menu = num;
8400 name = g_markup_printf_escaped("<i>%s</i>",
8401 ac->account_name);
8403 if (ac == compose->account) {
8404 if (ac->name && *ac->name) {
8405 gchar *buf;
8406 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8407 from = g_strdup_printf("%s <%s>",
8408 buf, ac->address);
8409 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8410 } else {
8411 from = g_strdup_printf("%s",
8412 ac->address);
8413 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8415 if (cur_account != compose->account) {
8416 gtk_widget_modify_base(
8417 GTK_WIDGET(from_name),
8418 GTK_STATE_NORMAL, &default_header_bgcolor);
8419 gtk_widget_modify_text(
8420 GTK_WIDGET(from_name),
8421 GTK_STATE_NORMAL, &default_header_color);
8424 COMBOBOX_ADD(menu, name, ac->account_id);
8425 g_free(name);
8426 g_free(from);
8429 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8431 g_signal_connect(G_OBJECT(optmenu), "changed",
8432 G_CALLBACK(account_activated),
8433 compose);
8434 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8435 G_CALLBACK(compose_entry_popup_extend),
8436 NULL);
8438 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8439 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8441 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8442 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8443 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8445 /* Putting only the GtkEntry into focus chain of parent hbox causes
8446 * the account selector combobox next to it to be unreachable when
8447 * navigating widgets in GtkTable with up/down arrow keys.
8448 * Note: gtk_widget_set_can_focus() was not enough. */
8449 GList *l = NULL;
8450 l = g_list_prepend(l, from_name);
8451 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8452 g_list_free(l);
8454 CLAWS_SET_TIP(optmenubox,
8455 _("Account to use for this email"));
8456 CLAWS_SET_TIP(from_name,
8457 _("Sender address to be used"));
8459 compose->account_combo = optmenu;
8460 compose->from_name = from_name;
8462 return hbox;
8465 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8467 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8468 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8469 Compose *compose = (Compose *) data;
8470 if (active) {
8471 compose->priority = value;
8475 static void compose_reply_change_mode(Compose *compose,
8476 ComposeMode action)
8478 gboolean was_modified = compose->modified;
8480 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8482 cm_return_if_fail(compose->replyinfo != NULL);
8484 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8485 ml = TRUE;
8486 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8487 followup = TRUE;
8488 if (action == COMPOSE_REPLY_TO_ALL)
8489 all = TRUE;
8490 if (action == COMPOSE_REPLY_TO_SENDER)
8491 sender = TRUE;
8492 if (action == COMPOSE_REPLY_TO_LIST)
8493 ml = TRUE;
8495 compose_remove_header_entries(compose);
8496 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8497 if (compose->account->set_autocc && compose->account->auto_cc)
8498 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8500 if (compose->account->set_autobcc && compose->account->auto_bcc)
8501 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8503 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8504 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8505 compose_show_first_last_header(compose, TRUE);
8506 compose->modified = was_modified;
8507 compose_set_title(compose);
8510 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8512 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8513 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8514 Compose *compose = (Compose *) data;
8516 if (active)
8517 compose_reply_change_mode(compose, value);
8520 static void compose_update_priority_menu_item(Compose * compose)
8522 GtkWidget *menuitem = NULL;
8523 switch (compose->priority) {
8524 case PRIORITY_HIGHEST:
8525 menuitem = gtk_ui_manager_get_widget
8526 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8527 break;
8528 case PRIORITY_HIGH:
8529 menuitem = gtk_ui_manager_get_widget
8530 (compose->ui_manager, "/Menu/Options/Priority/High");
8531 break;
8532 case PRIORITY_NORMAL:
8533 menuitem = gtk_ui_manager_get_widget
8534 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8535 break;
8536 case PRIORITY_LOW:
8537 menuitem = gtk_ui_manager_get_widget
8538 (compose->ui_manager, "/Menu/Options/Priority/Low");
8539 break;
8540 case PRIORITY_LOWEST:
8541 menuitem = gtk_ui_manager_get_widget
8542 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8543 break;
8545 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8548 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8550 Compose *compose = (Compose *) data;
8551 gchar *systemid;
8552 gboolean can_sign = FALSE, can_encrypt = FALSE;
8554 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8556 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8557 return;
8559 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8560 g_free(compose->privacy_system);
8561 compose->privacy_system = NULL;
8562 g_free(compose->encdata);
8563 compose->encdata = NULL;
8564 if (systemid != NULL) {
8565 compose->privacy_system = g_strdup(systemid);
8567 can_sign = privacy_system_can_sign(systemid);
8568 can_encrypt = privacy_system_can_encrypt(systemid);
8571 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8573 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8574 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8575 if (compose->toolbar->privacy_sign_btn != NULL) {
8576 gtk_widget_set_sensitive(
8577 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8578 can_sign);
8579 gtk_toggle_tool_button_set_active(
8580 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8581 can_sign ? compose->use_signing : FALSE);
8583 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8584 gtk_widget_set_sensitive(
8585 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8586 can_encrypt);
8587 gtk_toggle_tool_button_set_active(
8588 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8589 can_encrypt ? compose->use_encryption : FALSE);
8593 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8595 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8596 GtkWidget *menuitem = NULL;
8597 GList *children, *amenu;
8598 gboolean can_sign = FALSE, can_encrypt = FALSE;
8599 gboolean found = FALSE;
8601 if (compose->privacy_system != NULL) {
8602 gchar *systemid;
8603 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8604 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8605 cm_return_if_fail(menuitem != NULL);
8607 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8608 amenu = children;
8609 menuitem = NULL;
8610 while (amenu != NULL) {
8611 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8612 if (systemid != NULL) {
8613 if (strcmp(systemid, compose->privacy_system) == 0 &&
8614 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8615 menuitem = GTK_WIDGET(amenu->data);
8617 can_sign = privacy_system_can_sign(systemid);
8618 can_encrypt = privacy_system_can_encrypt(systemid);
8619 found = TRUE;
8620 break;
8622 } else if (strlen(compose->privacy_system) == 0 &&
8623 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8624 menuitem = GTK_WIDGET(amenu->data);
8626 can_sign = FALSE;
8627 can_encrypt = FALSE;
8628 found = TRUE;
8629 break;
8632 amenu = amenu->next;
8634 g_list_free(children);
8635 if (menuitem != NULL)
8636 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8638 if (warn && !found && strlen(compose->privacy_system)) {
8639 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8640 "will not be able to sign or encrypt this message."),
8641 compose->privacy_system);
8645 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8646 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8647 if (compose->toolbar->privacy_sign_btn != NULL) {
8648 gtk_widget_set_sensitive(
8649 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8650 can_sign);
8652 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8653 gtk_widget_set_sensitive(
8654 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8655 can_encrypt);
8659 static void compose_set_out_encoding(Compose *compose)
8661 CharSet out_encoding;
8662 const gchar *branch = NULL;
8663 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8665 switch(out_encoding) {
8666 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8667 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8668 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8669 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8670 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8671 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8672 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8673 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8674 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8675 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8676 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8677 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8678 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8679 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8680 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8681 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8682 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8683 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8684 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8685 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8686 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8687 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8688 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8689 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8690 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8691 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8692 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8693 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8694 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8695 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8696 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8697 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8698 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8699 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8701 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8704 static void compose_set_template_menu(Compose *compose)
8706 GSList *tmpl_list, *cur;
8707 GtkWidget *menu;
8708 GtkWidget *item;
8710 tmpl_list = template_get_config();
8712 menu = gtk_menu_new();
8714 gtk_menu_set_accel_group (GTK_MENU (menu),
8715 gtk_ui_manager_get_accel_group(compose->ui_manager));
8716 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8717 Template *tmpl = (Template *)cur->data;
8718 gchar *accel_path = NULL;
8719 item = gtk_menu_item_new_with_label(tmpl->name);
8720 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8721 g_signal_connect(G_OBJECT(item), "activate",
8722 G_CALLBACK(compose_template_activate_cb),
8723 compose);
8724 g_object_set_data(G_OBJECT(item), "template", tmpl);
8725 gtk_widget_show(item);
8726 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8727 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8728 g_free(accel_path);
8731 gtk_widget_show(menu);
8732 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8735 void compose_update_actions_menu(Compose *compose)
8737 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8740 static void compose_update_privacy_systems_menu(Compose *compose)
8742 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8743 GSList *systems, *cur;
8744 GtkWidget *widget;
8745 GtkWidget *system_none;
8746 GSList *group;
8747 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8748 GtkWidget *privacy_menu = gtk_menu_new();
8750 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8751 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8753 g_signal_connect(G_OBJECT(system_none), "activate",
8754 G_CALLBACK(compose_set_privacy_system_cb), compose);
8756 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8757 gtk_widget_show(system_none);
8759 systems = privacy_get_system_ids();
8760 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8761 gchar *systemid = cur->data;
8763 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8764 widget = gtk_radio_menu_item_new_with_label(group,
8765 privacy_system_get_name(systemid));
8766 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8767 g_strdup(systemid), g_free);
8768 g_signal_connect(G_OBJECT(widget), "activate",
8769 G_CALLBACK(compose_set_privacy_system_cb), compose);
8771 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8772 gtk_widget_show(widget);
8773 g_free(systemid);
8775 g_slist_free(systems);
8776 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8777 gtk_widget_show_all(privacy_menu);
8778 gtk_widget_show_all(privacy_menuitem);
8781 void compose_reflect_prefs_all(void)
8783 GList *cur;
8784 Compose *compose;
8786 for (cur = compose_list; cur != NULL; cur = cur->next) {
8787 compose = (Compose *)cur->data;
8788 compose_set_template_menu(compose);
8792 void compose_reflect_prefs_pixmap_theme(void)
8794 GList *cur;
8795 Compose *compose;
8797 for (cur = compose_list; cur != NULL; cur = cur->next) {
8798 compose = (Compose *)cur->data;
8799 toolbar_update(TOOLBAR_COMPOSE, compose);
8803 static const gchar *compose_quote_char_from_context(Compose *compose)
8805 const gchar *qmark = NULL;
8807 cm_return_val_if_fail(compose != NULL, NULL);
8809 switch (compose->mode) {
8810 /* use forward-specific quote char */
8811 case COMPOSE_FORWARD:
8812 case COMPOSE_FORWARD_AS_ATTACH:
8813 case COMPOSE_FORWARD_INLINE:
8814 if (compose->folder && compose->folder->prefs &&
8815 compose->folder->prefs->forward_with_format)
8816 qmark = compose->folder->prefs->forward_quotemark;
8817 else if (compose->account->forward_with_format)
8818 qmark = compose->account->forward_quotemark;
8819 else
8820 qmark = prefs_common.fw_quotemark;
8821 break;
8823 /* use reply-specific quote char in all other modes */
8824 default:
8825 if (compose->folder && compose->folder->prefs &&
8826 compose->folder->prefs->reply_with_format)
8827 qmark = compose->folder->prefs->reply_quotemark;
8828 else if (compose->account->reply_with_format)
8829 qmark = compose->account->reply_quotemark;
8830 else
8831 qmark = prefs_common.quotemark;
8832 break;
8835 if (qmark == NULL || *qmark == '\0')
8836 qmark = "> ";
8838 return qmark;
8841 static void compose_template_apply(Compose *compose, Template *tmpl,
8842 gboolean replace)
8844 GtkTextView *text;
8845 GtkTextBuffer *buffer;
8846 GtkTextMark *mark;
8847 GtkTextIter iter;
8848 const gchar *qmark;
8849 gchar *parsed_str = NULL;
8850 gint cursor_pos = 0;
8851 const gchar *err_msg = _("The body of the template has an error at line %d.");
8852 if (!tmpl) return;
8854 /* process the body */
8856 text = GTK_TEXT_VIEW(compose->text);
8857 buffer = gtk_text_view_get_buffer(text);
8859 if (tmpl->value) {
8860 qmark = compose_quote_char_from_context(compose);
8862 if (compose->replyinfo != NULL) {
8864 if (replace)
8865 gtk_text_buffer_set_text(buffer, "", -1);
8866 mark = gtk_text_buffer_get_insert(buffer);
8867 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8869 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8870 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8872 } else if (compose->fwdinfo != NULL) {
8874 if (replace)
8875 gtk_text_buffer_set_text(buffer, "", -1);
8876 mark = gtk_text_buffer_get_insert(buffer);
8877 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8879 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8880 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8882 } else {
8883 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8885 GtkTextIter start, end;
8886 gchar *tmp = NULL;
8888 gtk_text_buffer_get_start_iter(buffer, &start);
8889 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8890 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8892 /* clear the buffer now */
8893 if (replace)
8894 gtk_text_buffer_set_text(buffer, "", -1);
8896 parsed_str = compose_quote_fmt(compose, dummyinfo,
8897 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8898 procmsg_msginfo_free( &dummyinfo );
8900 g_free( tmp );
8902 } else {
8903 if (replace)
8904 gtk_text_buffer_set_text(buffer, "", -1);
8905 mark = gtk_text_buffer_get_insert(buffer);
8906 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8909 if (replace && parsed_str && compose->account->auto_sig)
8910 compose_insert_sig(compose, FALSE);
8912 if (replace && parsed_str) {
8913 gtk_text_buffer_get_start_iter(buffer, &iter);
8914 gtk_text_buffer_place_cursor(buffer, &iter);
8917 if (parsed_str) {
8918 cursor_pos = quote_fmt_get_cursor_pos();
8919 compose->set_cursor_pos = cursor_pos;
8920 if (cursor_pos == -1)
8921 cursor_pos = 0;
8922 gtk_text_buffer_get_start_iter(buffer, &iter);
8923 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8924 gtk_text_buffer_place_cursor(buffer, &iter);
8927 /* process the other fields */
8929 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8930 compose_template_apply_fields(compose, tmpl);
8931 quote_fmt_reset_vartable();
8932 quote_fmtlex_destroy();
8934 compose_changed_cb(NULL, compose);
8936 #ifdef USE_ENCHANT
8937 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8938 gtkaspell_highlight_all(compose->gtkaspell);
8939 #endif
8942 static void compose_template_apply_fields_error(const gchar *header)
8944 gchar *tr;
8945 gchar *text;
8947 tr = g_strdup(C_("'%s' stands for a header name",
8948 "Template '%s' format error."));
8949 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8950 alertpanel_error("%s", text);
8952 g_free(text);
8953 g_free(tr);
8956 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8958 MsgInfo* dummyinfo = NULL;
8959 MsgInfo *msginfo = NULL;
8960 gchar *buf = NULL;
8962 if (compose->replyinfo != NULL)
8963 msginfo = compose->replyinfo;
8964 else if (compose->fwdinfo != NULL)
8965 msginfo = compose->fwdinfo;
8966 else {
8967 dummyinfo = compose_msginfo_new_from_compose(compose);
8968 msginfo = dummyinfo;
8971 if (tmpl->from && *tmpl->from != '\0') {
8972 #ifdef USE_ENCHANT
8973 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8974 compose->gtkaspell);
8975 #else
8976 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8977 #endif
8978 quote_fmt_scan_string(tmpl->from);
8979 quote_fmt_parse();
8981 buf = quote_fmt_get_buffer();
8982 if (buf == NULL) {
8983 compose_template_apply_fields_error("From");
8984 } else {
8985 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8988 quote_fmt_reset_vartable();
8989 quote_fmtlex_destroy();
8992 if (tmpl->to && *tmpl->to != '\0') {
8993 #ifdef USE_ENCHANT
8994 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8995 compose->gtkaspell);
8996 #else
8997 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8998 #endif
8999 quote_fmt_scan_string(tmpl->to);
9000 quote_fmt_parse();
9002 buf = quote_fmt_get_buffer();
9003 if (buf == NULL) {
9004 compose_template_apply_fields_error("To");
9005 } else {
9006 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
9009 quote_fmt_reset_vartable();
9010 quote_fmtlex_destroy();
9013 if (tmpl->cc && *tmpl->cc != '\0') {
9014 #ifdef USE_ENCHANT
9015 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9016 compose->gtkaspell);
9017 #else
9018 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9019 #endif
9020 quote_fmt_scan_string(tmpl->cc);
9021 quote_fmt_parse();
9023 buf = quote_fmt_get_buffer();
9024 if (buf == NULL) {
9025 compose_template_apply_fields_error("Cc");
9026 } else {
9027 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9030 quote_fmt_reset_vartable();
9031 quote_fmtlex_destroy();
9034 if (tmpl->bcc && *tmpl->bcc != '\0') {
9035 #ifdef USE_ENCHANT
9036 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9037 compose->gtkaspell);
9038 #else
9039 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9040 #endif
9041 quote_fmt_scan_string(tmpl->bcc);
9042 quote_fmt_parse();
9044 buf = quote_fmt_get_buffer();
9045 if (buf == NULL) {
9046 compose_template_apply_fields_error("Bcc");
9047 } else {
9048 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9051 quote_fmt_reset_vartable();
9052 quote_fmtlex_destroy();
9055 if (tmpl->replyto && *tmpl->replyto != '\0') {
9056 #ifdef USE_ENCHANT
9057 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9058 compose->gtkaspell);
9059 #else
9060 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9061 #endif
9062 quote_fmt_scan_string(tmpl->replyto);
9063 quote_fmt_parse();
9065 buf = quote_fmt_get_buffer();
9066 if (buf == NULL) {
9067 compose_template_apply_fields_error("Reply-To");
9068 } else {
9069 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9072 quote_fmt_reset_vartable();
9073 quote_fmtlex_destroy();
9076 /* process the subject */
9077 if (tmpl->subject && *tmpl->subject != '\0') {
9078 #ifdef USE_ENCHANT
9079 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9080 compose->gtkaspell);
9081 #else
9082 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9083 #endif
9084 quote_fmt_scan_string(tmpl->subject);
9085 quote_fmt_parse();
9087 buf = quote_fmt_get_buffer();
9088 if (buf == NULL) {
9089 compose_template_apply_fields_error("Subject");
9090 } else {
9091 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9094 quote_fmt_reset_vartable();
9095 quote_fmtlex_destroy();
9098 procmsg_msginfo_free( &dummyinfo );
9101 static void compose_destroy(Compose *compose)
9103 GtkAllocation allocation;
9104 GtkTextBuffer *buffer;
9105 GtkClipboard *clipboard;
9107 compose_list = g_list_remove(compose_list, compose);
9109 #ifdef USE_LDAP
9110 gboolean enable = TRUE;
9111 g_slist_foreach(compose->passworded_ldap_servers,
9112 _ldap_srv_func, &enable);
9113 g_slist_free(compose->passworded_ldap_servers);
9114 #endif
9116 if (compose->updating) {
9117 debug_print("danger, not destroying anything now\n");
9118 compose->deferred_destroy = TRUE;
9119 return;
9122 /* NOTE: address_completion_end() does nothing with the window
9123 * however this may change. */
9124 address_completion_end(compose->window);
9126 slist_free_strings_full(compose->to_list);
9127 slist_free_strings_full(compose->newsgroup_list);
9128 slist_free_strings_full(compose->header_list);
9130 slist_free_strings_full(extra_headers);
9131 extra_headers = NULL;
9133 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9135 g_hash_table_destroy(compose->email_hashtable);
9137 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9138 compose->folder_update_callback_id);
9140 procmsg_msginfo_free(&(compose->targetinfo));
9141 procmsg_msginfo_free(&(compose->replyinfo));
9142 procmsg_msginfo_free(&(compose->fwdinfo));
9144 g_free(compose->replyto);
9145 g_free(compose->cc);
9146 g_free(compose->bcc);
9147 g_free(compose->newsgroups);
9148 g_free(compose->followup_to);
9150 g_free(compose->ml_post);
9152 g_free(compose->inreplyto);
9153 g_free(compose->references);
9154 g_free(compose->msgid);
9155 g_free(compose->boundary);
9157 g_free(compose->redirect_filename);
9158 if (compose->undostruct)
9159 undo_destroy(compose->undostruct);
9161 g_free(compose->sig_str);
9163 g_free(compose->exteditor_file);
9165 g_free(compose->orig_charset);
9167 g_free(compose->privacy_system);
9168 g_free(compose->encdata);
9170 #ifndef USE_ALT_ADDRBOOK
9171 if (addressbook_get_target_compose() == compose)
9172 addressbook_set_target_compose(NULL);
9173 #endif
9174 #if USE_ENCHANT
9175 if (compose->gtkaspell) {
9176 gtkaspell_delete(compose->gtkaspell);
9177 compose->gtkaspell = NULL;
9179 #endif
9181 if (!compose->batch) {
9182 gtk_widget_get_allocation(compose->window, &allocation);
9183 prefs_common.compose_width = allocation.width;
9184 prefs_common.compose_height = allocation.height;
9187 if (!gtk_widget_get_parent(compose->paned))
9188 gtk_widget_destroy(compose->paned);
9189 gtk_widget_destroy(compose->popupmenu);
9191 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9192 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9193 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9195 message_search_close(compose);
9196 gtk_widget_destroy(compose->window);
9197 toolbar_destroy(compose->toolbar);
9198 g_free(compose->toolbar);
9199 cm_mutex_free(compose->mutex);
9200 g_free(compose);
9203 static void compose_attach_info_free(AttachInfo *ainfo)
9205 g_free(ainfo->file);
9206 g_free(ainfo->content_type);
9207 g_free(ainfo->name);
9208 g_free(ainfo->charset);
9209 g_free(ainfo);
9212 static void compose_attach_update_label(Compose *compose)
9214 GtkTreeIter iter;
9215 gint i = 1;
9216 gchar *text;
9217 GtkTreeModel *model;
9218 goffset total_size;
9219 AttachInfo *ainfo;
9221 if (compose == NULL)
9222 return;
9224 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9225 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9226 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9227 return;
9230 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9231 total_size = ainfo->size;
9232 while(gtk_tree_model_iter_next(model, &iter)) {
9233 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9234 total_size += ainfo->size;
9235 i++;
9237 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9238 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9239 g_free(text);
9242 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9244 Compose *compose = (Compose *)data;
9245 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9246 GtkTreeSelection *selection;
9247 GList *sel, *cur;
9248 GtkTreeModel *model;
9250 selection = gtk_tree_view_get_selection(tree_view);
9251 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9252 cm_return_if_fail(sel);
9254 for (cur = sel; cur != NULL; cur = cur->next) {
9255 GtkTreePath *path = cur->data;
9256 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9257 (model, cur->data);
9258 cur->data = ref;
9259 gtk_tree_path_free(path);
9262 for (cur = sel; cur != NULL; cur = cur->next) {
9263 GtkTreeRowReference *ref = cur->data;
9264 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9265 GtkTreeIter iter;
9267 if (gtk_tree_model_get_iter(model, &iter, path))
9268 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9270 gtk_tree_path_free(path);
9271 gtk_tree_row_reference_free(ref);
9274 g_list_free(sel);
9275 compose_attach_update_label(compose);
9278 static struct _AttachProperty
9280 GtkWidget *window;
9281 GtkWidget *mimetype_entry;
9282 GtkWidget *encoding_optmenu;
9283 GtkWidget *path_entry;
9284 GtkWidget *filename_entry;
9285 GtkWidget *ok_btn;
9286 GtkWidget *cancel_btn;
9287 } attach_prop;
9289 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9291 gtk_tree_path_free((GtkTreePath *)ptr);
9294 static void compose_attach_property(GtkAction *action, gpointer data)
9296 Compose *compose = (Compose *)data;
9297 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9298 AttachInfo *ainfo;
9299 GtkComboBox *optmenu;
9300 GtkTreeSelection *selection;
9301 GList *sel;
9302 GtkTreeModel *model;
9303 GtkTreeIter iter;
9304 GtkTreePath *path;
9305 static gboolean cancelled;
9307 /* only if one selected */
9308 selection = gtk_tree_view_get_selection(tree_view);
9309 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9310 return;
9312 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9313 cm_return_if_fail(sel);
9315 path = (GtkTreePath *) sel->data;
9316 gtk_tree_model_get_iter(model, &iter, path);
9317 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9319 if (!ainfo) {
9320 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9321 g_list_free(sel);
9322 return;
9324 g_list_free(sel);
9326 if (!attach_prop.window)
9327 compose_attach_property_create(&cancelled);
9328 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9329 gtk_widget_grab_focus(attach_prop.ok_btn);
9330 gtk_widget_show(attach_prop.window);
9331 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9332 GTK_WINDOW(compose->window));
9334 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9335 if (ainfo->encoding == ENC_UNKNOWN)
9336 combobox_select_by_data(optmenu, ENC_BASE64);
9337 else
9338 combobox_select_by_data(optmenu, ainfo->encoding);
9340 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9341 ainfo->content_type ? ainfo->content_type : "");
9342 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9343 ainfo->file ? ainfo->file : "");
9344 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9345 ainfo->name ? ainfo->name : "");
9347 for (;;) {
9348 const gchar *entry_text;
9349 gchar *text;
9350 gchar *cnttype = NULL;
9351 gchar *file = NULL;
9352 off_t size = 0;
9354 cancelled = FALSE;
9355 gtk_main();
9357 gtk_widget_hide(attach_prop.window);
9358 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9360 if (cancelled)
9361 break;
9363 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9364 if (*entry_text != '\0') {
9365 gchar *p;
9367 text = g_strstrip(g_strdup(entry_text));
9368 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9369 cnttype = g_strdup(text);
9370 g_free(text);
9371 } else {
9372 alertpanel_error(_("Invalid MIME type."));
9373 g_free(text);
9374 continue;
9378 ainfo->encoding = combobox_get_active_data(optmenu);
9380 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9381 if (*entry_text != '\0') {
9382 if (is_file_exist(entry_text) &&
9383 (size = get_file_size(entry_text)) > 0)
9384 file = g_strdup(entry_text);
9385 else {
9386 alertpanel_error
9387 (_("File doesn't exist or is empty."));
9388 g_free(cnttype);
9389 continue;
9393 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9394 if (*entry_text != '\0') {
9395 g_free(ainfo->name);
9396 ainfo->name = g_strdup(entry_text);
9399 if (cnttype) {
9400 g_free(ainfo->content_type);
9401 ainfo->content_type = cnttype;
9403 if (file) {
9404 g_free(ainfo->file);
9405 ainfo->file = file;
9407 if (size)
9408 ainfo->size = (goffset)size;
9410 /* update tree store */
9411 text = to_human_readable(ainfo->size);
9412 gtk_tree_model_get_iter(model, &iter, path);
9413 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9414 COL_MIMETYPE, ainfo->content_type,
9415 COL_SIZE, text,
9416 COL_NAME, ainfo->name,
9417 COL_CHARSET, ainfo->charset,
9418 -1);
9420 break;
9423 gtk_tree_path_free(path);
9426 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9428 label = gtk_label_new(str); \
9429 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9430 GTK_FILL, 0, 0, 0); \
9431 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9433 entry = gtk_entry_new(); \
9434 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9435 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9438 static void compose_attach_property_create(gboolean *cancelled)
9440 GtkWidget *window;
9441 GtkWidget *vbox;
9442 GtkWidget *table;
9443 GtkWidget *label;
9444 GtkWidget *mimetype_entry;
9445 GtkWidget *hbox;
9446 GtkWidget *optmenu;
9447 GtkListStore *optmenu_menu;
9448 GtkWidget *path_entry;
9449 GtkWidget *filename_entry;
9450 GtkWidget *hbbox;
9451 GtkWidget *ok_btn;
9452 GtkWidget *cancel_btn;
9453 GList *mime_type_list, *strlist;
9454 GtkTreeIter iter;
9456 debug_print("Creating attach_property window...\n");
9458 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9459 gtk_widget_set_size_request(window, 480, -1);
9460 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9461 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9462 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9463 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
9464 g_signal_connect(G_OBJECT(window), "delete_event",
9465 G_CALLBACK(attach_property_delete_event),
9466 cancelled);
9467 g_signal_connect(G_OBJECT(window), "key_press_event",
9468 G_CALLBACK(attach_property_key_pressed),
9469 cancelled);
9471 vbox = gtk_vbox_new(FALSE, 8);
9472 gtk_container_add(GTK_CONTAINER(window), vbox);
9474 table = gtk_table_new(4, 2, FALSE);
9475 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9476 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9477 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9479 label = gtk_label_new(_("MIME type"));
9480 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9481 GTK_FILL, 0, 0, 0);
9482 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9483 mimetype_entry = gtk_combo_box_text_new_with_entry();
9484 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9485 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9487 /* stuff with list */
9488 mime_type_list = procmime_get_mime_type_list();
9489 strlist = NULL;
9490 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9491 MimeType *type = (MimeType *) mime_type_list->data;
9492 gchar *tmp;
9494 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9496 if (g_list_find_custom(strlist, tmp, (GCompareFunc)g_strcmp0))
9497 g_free(tmp);
9498 else
9499 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9500 (GCompareFunc)g_strcmp0);
9503 for (mime_type_list = strlist; mime_type_list != NULL;
9504 mime_type_list = mime_type_list->next) {
9505 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9506 g_free(mime_type_list->data);
9508 g_list_free(strlist);
9509 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9510 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9512 label = gtk_label_new(_("Encoding"));
9513 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9514 GTK_FILL, 0, 0, 0);
9515 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9517 hbox = gtk_hbox_new(FALSE, 0);
9518 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9519 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9521 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9522 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9524 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9525 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9526 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9527 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9528 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9530 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9532 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9533 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9535 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9536 &ok_btn, GTK_STOCK_OK,
9537 NULL, NULL);
9538 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9539 gtk_widget_grab_default(ok_btn);
9541 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9542 G_CALLBACK(attach_property_ok),
9543 cancelled);
9544 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9545 G_CALLBACK(attach_property_cancel),
9546 cancelled);
9548 gtk_widget_show_all(vbox);
9550 attach_prop.window = window;
9551 attach_prop.mimetype_entry = mimetype_entry;
9552 attach_prop.encoding_optmenu = optmenu;
9553 attach_prop.path_entry = path_entry;
9554 attach_prop.filename_entry = filename_entry;
9555 attach_prop.ok_btn = ok_btn;
9556 attach_prop.cancel_btn = cancel_btn;
9559 #undef SET_LABEL_AND_ENTRY
9561 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9563 *cancelled = FALSE;
9564 gtk_main_quit();
9567 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9569 *cancelled = TRUE;
9570 gtk_main_quit();
9573 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9574 gboolean *cancelled)
9576 *cancelled = TRUE;
9577 gtk_main_quit();
9579 return TRUE;
9582 static gboolean attach_property_key_pressed(GtkWidget *widget,
9583 GdkEventKey *event,
9584 gboolean *cancelled)
9586 if (event && event->keyval == GDK_KEY_Escape) {
9587 *cancelled = TRUE;
9588 gtk_main_quit();
9590 if (event && event->keyval == GDK_KEY_Return) {
9591 *cancelled = FALSE;
9592 gtk_main_quit();
9593 return TRUE;
9595 return FALSE;
9598 static void compose_exec_ext_editor(Compose *compose)
9600 #ifdef G_OS_UNIX
9601 gchar *tmp;
9602 GtkWidget *socket;
9603 GdkNativeWindow socket_wid = 0;
9604 pid_t pid;
9605 gint pipe_fds[2];
9607 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9608 G_DIR_SEPARATOR, compose);
9610 if (compose_get_ext_editor_uses_socket()) {
9611 /* Only allow one socket */
9612 if (compose->exteditor_socket != NULL) {
9613 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9614 /* Move the focus off of the socket */
9615 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9617 g_free(tmp);
9618 return;
9620 /* Create the receiving GtkSocket */
9621 socket = gtk_socket_new ();
9622 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9623 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9624 compose);
9625 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9626 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9627 /* Realize the socket so that we can use its ID */
9628 gtk_widget_realize(socket);
9629 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9630 compose->exteditor_socket = socket;
9633 if (pipe(pipe_fds) < 0) {
9634 perror("pipe");
9635 g_free(tmp);
9636 return;
9639 if ((pid = fork()) < 0) {
9640 perror("fork");
9641 g_free(tmp);
9642 return;
9645 if (pid != 0) {
9646 /* close the write side of the pipe */
9647 close(pipe_fds[1]);
9649 compose->exteditor_file = g_strdup(tmp);
9650 compose->exteditor_pid = pid;
9652 compose_set_ext_editor_sensitive(compose, FALSE);
9654 #ifndef G_OS_WIN32
9655 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9656 #else
9657 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9658 #endif
9659 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9660 G_IO_IN,
9661 compose_input_cb,
9662 compose);
9663 } else { /* process-monitoring process */
9664 pid_t pid_ed;
9666 if (setpgid(0, 0))
9667 perror("setpgid");
9669 /* close the read side of the pipe */
9670 close(pipe_fds[0]);
9672 if (compose_write_body_to_file(compose, tmp) < 0) {
9673 fd_write_all(pipe_fds[1], "2\n", 2);
9674 _exit(1);
9677 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9678 if (pid_ed < 0) {
9679 fd_write_all(pipe_fds[1], "1\n", 2);
9680 _exit(1);
9683 /* wait until editor is terminated */
9684 waitpid(pid_ed, NULL, 0);
9686 fd_write_all(pipe_fds[1], "0\n", 2);
9688 close(pipe_fds[1]);
9689 _exit(0);
9692 g_free(tmp);
9693 #endif /* G_OS_UNIX */
9696 static gboolean compose_can_autosave(Compose *compose)
9698 if (compose->privacy_system && compose->use_encryption)
9699 return prefs_common.autosave && prefs_common.autosave_encrypted;
9700 else
9701 return prefs_common.autosave;
9704 #ifdef G_OS_UNIX
9705 static gboolean compose_get_ext_editor_cmd_valid()
9707 gboolean has_s = FALSE;
9708 gboolean has_w = FALSE;
9709 const gchar *p = prefs_common_get_ext_editor_cmd();
9710 if (!p)
9711 return FALSE;
9712 while ((p = strchr(p, '%'))) {
9713 p++;
9714 if (*p == 's') {
9715 if (has_s)
9716 return FALSE;
9717 has_s = TRUE;
9718 } else if (*p == 'w') {
9719 if (has_w)
9720 return FALSE;
9721 has_w = TRUE;
9722 } else {
9723 return FALSE;
9726 return TRUE;
9729 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9731 gchar *buf;
9732 gchar *p, *s;
9733 gchar **cmdline;
9734 pid_t pid;
9736 cm_return_val_if_fail(file != NULL, -1);
9738 if ((pid = fork()) < 0) {
9739 perror("fork");
9740 return -1;
9743 if (pid != 0) return pid;
9745 /* grandchild process */
9747 if (setpgid(0, getppid()))
9748 perror("setpgid");
9750 if (compose_get_ext_editor_cmd_valid()) {
9751 if (compose_get_ext_editor_uses_socket()) {
9752 p = g_strdup(prefs_common_get_ext_editor_cmd());
9753 s = strstr(p, "%w");
9754 s[1] = 'u';
9755 if (strstr(p, "%s") < s)
9756 buf = g_strdup_printf(p, file, socket_wid);
9757 else
9758 buf = g_strdup_printf(p, socket_wid, file);
9759 g_free(p);
9760 } else {
9761 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9763 } else {
9764 if (prefs_common_get_ext_editor_cmd())
9765 g_warning("External editor command-line is invalid: '%s'",
9766 prefs_common_get_ext_editor_cmd());
9767 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9770 cmdline = strsplit_with_quote(buf, " ", 0);
9771 g_free(buf);
9772 execvp(cmdline[0], cmdline);
9774 perror("execvp");
9775 g_strfreev(cmdline);
9777 _exit(1);
9780 static gboolean compose_ext_editor_kill(Compose *compose)
9782 pid_t pgid = compose->exteditor_pid * -1;
9783 gint ret;
9785 ret = kill(pgid, 0);
9787 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9788 AlertValue val;
9789 gchar *msg;
9791 msg = g_strdup_printf
9792 (_("The external editor is still working.\n"
9793 "Force terminating the process?\n"
9794 "process group id: %d"), -pgid);
9795 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9796 NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9797 ALERT_WARNING);
9799 g_free(msg);
9801 if (val == G_ALERTALTERNATE) {
9802 g_source_remove(compose->exteditor_tag);
9803 g_io_channel_shutdown(compose->exteditor_ch,
9804 FALSE, NULL);
9805 g_io_channel_unref(compose->exteditor_ch);
9807 if (kill(pgid, SIGTERM) < 0) perror("kill");
9808 waitpid(compose->exteditor_pid, NULL, 0);
9810 g_warning("Terminated process group id: %d. "
9811 "Temporary file: %s", -pgid, compose->exteditor_file);
9813 compose_set_ext_editor_sensitive(compose, TRUE);
9815 g_free(compose->exteditor_file);
9816 compose->exteditor_file = NULL;
9817 compose->exteditor_pid = -1;
9818 compose->exteditor_ch = NULL;
9819 compose->exteditor_tag = -1;
9820 } else
9821 return FALSE;
9824 return TRUE;
9827 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9828 gpointer data)
9830 gchar buf[3] = "3";
9831 Compose *compose = (Compose *)data;
9832 gsize bytes_read;
9834 debug_print("Compose: input from monitoring process\n");
9836 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9837 bytes_read = 0;
9838 buf[0] = '\0';
9841 g_io_channel_shutdown(source, FALSE, NULL);
9842 g_io_channel_unref(source);
9844 waitpid(compose->exteditor_pid, NULL, 0);
9846 if (buf[0] == '0') { /* success */
9847 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9848 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9849 GtkTextIter start, end;
9850 gchar *chars;
9852 gtk_text_buffer_set_text(buffer, "", -1);
9853 compose_insert_file(compose, compose->exteditor_file);
9854 compose_changed_cb(NULL, compose);
9856 /* Check if we should save the draft or not */
9857 if (compose_can_autosave(compose))
9858 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9860 if (claws_unlink(compose->exteditor_file) < 0)
9861 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9863 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9864 gtk_text_buffer_get_start_iter(buffer, &start);
9865 gtk_text_buffer_get_end_iter(buffer, &end);
9866 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9867 if (chars && strlen(chars) > 0)
9868 compose->modified = TRUE;
9869 g_free(chars);
9870 } else if (buf[0] == '1') { /* failed */
9871 g_warning("Couldn't exec external editor");
9872 if (claws_unlink(compose->exteditor_file) < 0)
9873 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9874 } else if (buf[0] == '2') {
9875 g_warning("Couldn't write to file");
9876 } else if (buf[0] == '3') {
9877 g_warning("Pipe read failed");
9880 compose_set_ext_editor_sensitive(compose, TRUE);
9882 g_free(compose->exteditor_file);
9883 compose->exteditor_file = NULL;
9884 compose->exteditor_pid = -1;
9885 compose->exteditor_ch = NULL;
9886 compose->exteditor_tag = -1;
9887 if (compose->exteditor_socket) {
9888 gtk_widget_destroy(compose->exteditor_socket);
9889 compose->exteditor_socket = NULL;
9893 return FALSE;
9896 static char *ext_editor_menu_entries[] = {
9897 "Menu/Message/Send",
9898 "Menu/Message/SendLater",
9899 "Menu/Message/InsertFile",
9900 "Menu/Message/InsertSig",
9901 "Menu/Message/ReplaceSig",
9902 "Menu/Message/Save",
9903 "Menu/Message/Print",
9904 "Menu/Edit",
9905 #if USE_ENCHANT
9906 "Menu/Spelling",
9907 #endif
9908 "Menu/Tools/ShowRuler",
9909 "Menu/Tools/Actions",
9910 "Menu/Help",
9911 NULL
9914 static void compose_set_ext_editor_sensitive(Compose *compose,
9915 gboolean sensitive)
9917 int i;
9919 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9920 cm_menu_set_sensitive_full(compose->ui_manager,
9921 ext_editor_menu_entries[i], sensitive);
9924 if (compose_get_ext_editor_uses_socket()) {
9925 if (sensitive) {
9926 if (compose->exteditor_socket)
9927 gtk_widget_hide(compose->exteditor_socket);
9928 gtk_widget_show(compose->scrolledwin);
9929 if (prefs_common.show_ruler)
9930 gtk_widget_show(compose->ruler_hbox);
9931 /* Fix the focus, as it doesn't go anywhere when the
9932 * socket is hidden or destroyed */
9933 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9934 } else {
9935 g_assert (compose->exteditor_socket != NULL);
9936 /* Fix the focus, as it doesn't go anywhere when the
9937 * edit box is hidden */
9938 if (gtk_widget_is_focus(compose->text))
9939 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9940 gtk_widget_hide(compose->scrolledwin);
9941 gtk_widget_hide(compose->ruler_hbox);
9942 gtk_widget_show(compose->exteditor_socket);
9944 } else {
9945 gtk_widget_set_sensitive(compose->text, sensitive);
9947 if (compose->toolbar->send_btn)
9948 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9949 if (compose->toolbar->sendl_btn)
9950 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9951 if (compose->toolbar->draft_btn)
9952 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9953 if (compose->toolbar->insert_btn)
9954 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9955 if (compose->toolbar->sig_btn)
9956 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9957 if (compose->toolbar->exteditor_btn)
9958 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9959 if (compose->toolbar->linewrap_current_btn)
9960 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9961 if (compose->toolbar->linewrap_all_btn)
9962 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9965 static gboolean compose_get_ext_editor_uses_socket()
9967 return (prefs_common_get_ext_editor_cmd() &&
9968 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9971 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9973 compose->exteditor_socket = NULL;
9974 /* returning FALSE allows destruction of the socket */
9975 return FALSE;
9977 #endif /* G_OS_UNIX */
9980 * compose_undo_state_changed:
9982 * Change the sensivity of the menuentries undo and redo
9984 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9985 gint redo_state, gpointer data)
9987 Compose *compose = (Compose *)data;
9989 switch (undo_state) {
9990 case UNDO_STATE_TRUE:
9991 if (!undostruct->undo_state) {
9992 undostruct->undo_state = TRUE;
9993 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9995 break;
9996 case UNDO_STATE_FALSE:
9997 if (undostruct->undo_state) {
9998 undostruct->undo_state = FALSE;
9999 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
10001 break;
10002 case UNDO_STATE_UNCHANGED:
10003 break;
10004 case UNDO_STATE_REFRESH:
10005 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
10006 break;
10007 default:
10008 g_warning("Undo state not recognized");
10009 break;
10012 switch (redo_state) {
10013 case UNDO_STATE_TRUE:
10014 if (!undostruct->redo_state) {
10015 undostruct->redo_state = TRUE;
10016 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
10018 break;
10019 case UNDO_STATE_FALSE:
10020 if (undostruct->redo_state) {
10021 undostruct->redo_state = FALSE;
10022 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
10024 break;
10025 case UNDO_STATE_UNCHANGED:
10026 break;
10027 case UNDO_STATE_REFRESH:
10028 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
10029 break;
10030 default:
10031 g_warning("Redo state not recognized");
10032 break;
10036 /* callback functions */
10038 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10039 GtkAllocation *allocation,
10040 GtkPaned *paned)
10042 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10045 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10046 * includes "non-client" (windows-izm) in calculation, so this calculation
10047 * may not be accurate.
10049 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10050 GtkAllocation *allocation,
10051 GtkSHRuler *shruler)
10053 if (prefs_common.show_ruler) {
10054 gint char_width = 0, char_height = 0;
10055 gint line_width_in_chars;
10057 gtkut_get_font_size(GTK_WIDGET(widget),
10058 &char_width, &char_height);
10059 line_width_in_chars =
10060 (allocation->width - allocation->x) / char_width;
10062 /* got the maximum */
10063 gtk_shruler_set_range(GTK_SHRULER(shruler),
10064 0.0, line_width_in_chars, 0);
10067 return TRUE;
10070 typedef struct {
10071 gchar *header;
10072 gchar *entry;
10073 ComposePrefType type;
10074 gboolean entry_marked;
10075 } HeaderEntryState;
10077 static void account_activated(GtkComboBox *optmenu, gpointer data)
10079 Compose *compose = (Compose *)data;
10081 PrefsAccount *ac;
10082 gchar *folderidentifier;
10083 gint account_id = 0;
10084 GtkTreeModel *menu;
10085 GtkTreeIter iter;
10086 GSList *list, *saved_list = NULL;
10087 HeaderEntryState *state;
10089 /* Get ID of active account in the combo box */
10090 menu = gtk_combo_box_get_model(optmenu);
10091 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10092 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10094 ac = account_find_from_id(account_id);
10095 cm_return_if_fail(ac != NULL);
10097 if (ac != compose->account) {
10098 compose_select_account(compose, ac, FALSE);
10100 for (list = compose->header_list; list; list = list->next) {
10101 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10103 if (hentry->type == PREF_ACCOUNT || !list->next) {
10104 compose_destroy_headerentry(compose, hentry);
10105 continue;
10107 state = g_malloc0(sizeof(HeaderEntryState));
10108 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10109 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10110 state->entry = gtk_editable_get_chars(
10111 GTK_EDITABLE(hentry->entry), 0, -1);
10112 state->type = hentry->type;
10114 saved_list = g_slist_append(saved_list, state);
10115 compose_destroy_headerentry(compose, hentry);
10118 compose->header_last = NULL;
10119 g_slist_free(compose->header_list);
10120 compose->header_list = NULL;
10121 compose->header_nextrow = 1;
10122 compose_create_header_entry(compose);
10124 if (ac->set_autocc && ac->auto_cc)
10125 compose_entry_append(compose, ac->auto_cc,
10126 COMPOSE_CC, PREF_ACCOUNT);
10127 if (ac->set_autobcc && ac->auto_bcc)
10128 compose_entry_append(compose, ac->auto_bcc,
10129 COMPOSE_BCC, PREF_ACCOUNT);
10130 if (ac->set_autoreplyto && ac->auto_replyto)
10131 compose_entry_append(compose, ac->auto_replyto,
10132 COMPOSE_REPLYTO, PREF_ACCOUNT);
10134 for (list = saved_list; list; list = list->next) {
10135 state = (HeaderEntryState *) list->data;
10137 compose_add_header_entry(compose, state->header,
10138 state->entry, state->type);
10140 g_free(state->header);
10141 g_free(state->entry);
10142 g_free(state);
10144 g_slist_free(saved_list);
10146 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10147 (ac->protocol == A_NNTP) ?
10148 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10151 /* Set message save folder */
10152 compose_set_save_to(compose, NULL);
10153 if (compose->folder && compose->folder->prefs && compose->folder->prefs->save_copy_to_folder) {
10154 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10155 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10156 folderidentifier = folder_item_get_identifier(compose->folder);
10157 compose_set_save_to(compose, folderidentifier);
10158 g_free(folderidentifier);
10159 } else if (account_get_special_folder(compose->account, F_OUTBOX)) {
10160 if (compose->account->set_sent_folder || prefs_common.savemsg)
10161 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10162 else
10163 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE);
10164 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10165 folderidentifier = folder_item_get_identifier(account_get_special_folder
10166 (compose->account, F_OUTBOX));
10167 compose_set_save_to(compose, folderidentifier);
10168 g_free(folderidentifier);
10172 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10173 GtkTreeViewColumn *column, Compose *compose)
10175 compose_attach_property(NULL, compose);
10178 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10179 gpointer data)
10181 Compose *compose = (Compose *)data;
10182 GtkTreeSelection *attach_selection;
10183 gint attach_nr_selected;
10184 GtkTreePath *path;
10186 if (!event) return FALSE;
10188 if (event->button == 3) {
10189 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10190 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10192 /* If no rows, or just one row is selected, right-click should
10193 * open menu relevant to the row being right-clicked on. We
10194 * achieve that by selecting the clicked row first. If more
10195 * than one row is selected, we shouldn't modify the selection,
10196 * as user may want to remove selected rows (attachments). */
10197 if (attach_nr_selected < 2) {
10198 gtk_tree_selection_unselect_all(attach_selection);
10199 attach_nr_selected = 0;
10200 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10201 event->x, event->y, &path, NULL, NULL, NULL);
10202 if (path != NULL) {
10203 gtk_tree_selection_select_path(attach_selection, path);
10204 gtk_tree_path_free(path);
10205 attach_nr_selected++;
10209 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10210 /* Properties menu item makes no sense with more than one row
10211 * selected, the properties dialog can only edit one attachment. */
10212 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10214 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10215 NULL, NULL, event->button, event->time);
10216 return TRUE;
10219 return FALSE;
10222 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10223 gpointer data)
10225 Compose *compose = (Compose *)data;
10227 if (!event) return FALSE;
10229 switch (event->keyval) {
10230 case GDK_KEY_Delete:
10231 compose_attach_remove_selected(NULL, compose);
10232 break;
10234 return FALSE;
10237 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10239 toolbar_comp_set_sensitive(compose, allow);
10240 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10241 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10242 #if USE_ENCHANT
10243 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10244 #endif
10245 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10246 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10247 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10249 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10253 static void compose_send_cb(GtkAction *action, gpointer data)
10255 Compose *compose = (Compose *)data;
10257 #ifdef G_OS_UNIX
10258 if (compose->exteditor_tag != -1) {
10259 debug_print("ignoring send: external editor still open\n");
10260 return;
10262 #endif
10263 if (prefs_common.work_offline &&
10264 !inc_offline_should_override(TRUE,
10265 _("Claws Mail needs network access in order "
10266 "to send this email.")))
10267 return;
10269 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10270 g_source_remove(compose->draft_timeout_tag);
10271 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10274 compose_send(compose);
10277 static void compose_send_later_cb(GtkAction *action, gpointer data)
10279 Compose *compose = (Compose *)data;
10280 ComposeQueueResult val;
10282 inc_lock();
10283 compose_allow_user_actions(compose, FALSE);
10284 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10285 compose_allow_user_actions(compose, TRUE);
10286 inc_unlock();
10288 if (val == COMPOSE_QUEUE_SUCCESS) {
10289 compose_close(compose);
10290 } else {
10291 _display_queue_error(val);
10294 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10297 #define DRAFTED_AT_EXIT "drafted_at_exit"
10298 static void compose_register_draft(MsgInfo *info)
10300 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10301 DRAFTED_AT_EXIT, NULL);
10302 FILE *fp = claws_fopen(filepath, "ab");
10304 if (fp) {
10305 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10306 info->msgnum);
10307 claws_fclose(fp);
10310 g_free(filepath);
10313 gboolean compose_draft (gpointer data, guint action)
10315 Compose *compose = (Compose *)data;
10316 FolderItem *draft;
10317 gchar *tmp;
10318 gchar *sheaders;
10319 gint msgnum;
10320 MsgFlags flag = {0, 0};
10321 static gboolean lock = FALSE;
10322 MsgInfo *newmsginfo;
10323 FILE *fp;
10324 gboolean target_locked = FALSE;
10325 gboolean err = FALSE;
10327 if (lock) return FALSE;
10329 if (compose->sending)
10330 return TRUE;
10332 draft = account_get_special_folder(compose->account, F_DRAFT);
10333 cm_return_val_if_fail(draft != NULL, FALSE);
10335 if (!g_mutex_trylock(compose->mutex)) {
10336 /* we don't want to lock the mutex once it's available,
10337 * because as the only other part of compose.c locking
10338 * it is compose_close - which means once unlocked,
10339 * the compose struct will be freed */
10340 debug_print("couldn't lock mutex, probably sending\n");
10341 return FALSE;
10344 lock = TRUE;
10346 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10347 G_DIR_SEPARATOR, compose);
10348 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10349 FILE_OP_ERROR(tmp, "claws_fopen");
10350 goto warn_err;
10353 /* chmod for security */
10354 if (change_file_mode_rw(fp, tmp) < 0) {
10355 FILE_OP_ERROR(tmp, "chmod");
10356 g_warning("can't change file mode");
10359 /* Save draft infos */
10360 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10361 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10363 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10364 gchar *savefolderid;
10366 savefolderid = compose_get_save_to(compose);
10367 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10368 g_free(savefolderid);
10370 if (compose->return_receipt) {
10371 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10373 if (compose->privacy_system) {
10374 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10375 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10376 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10379 /* Message-ID of message replying to */
10380 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10381 gchar *folderid = NULL;
10383 if (compose->replyinfo->folder)
10384 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10385 if (folderid == NULL)
10386 folderid = g_strdup("NULL");
10388 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10389 g_free(folderid);
10391 /* Message-ID of message forwarding to */
10392 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10393 gchar *folderid = NULL;
10395 if (compose->fwdinfo->folder)
10396 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10397 if (folderid == NULL)
10398 folderid = g_strdup("NULL");
10400 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10401 g_free(folderid);
10404 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10405 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10407 sheaders = compose_get_manual_headers_info(compose);
10408 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10409 g_free(sheaders);
10411 /* end of headers */
10412 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10414 if (err) {
10415 claws_fclose(fp);
10416 goto warn_err;
10419 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10420 claws_fclose(fp);
10421 goto warn_err;
10423 if (claws_safe_fclose(fp) == EOF) {
10424 goto warn_err;
10427 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10428 if (compose->targetinfo) {
10429 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10430 if (target_locked)
10431 flag.perm_flags |= MSG_LOCKED;
10433 flag.tmp_flags = MSG_DRAFT;
10435 folder_item_scan(draft);
10436 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10437 MsgInfo *tmpinfo = NULL;
10438 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10439 if (compose->msgid) {
10440 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10442 if (tmpinfo) {
10443 msgnum = tmpinfo->msgnum;
10444 procmsg_msginfo_free(&tmpinfo);
10445 debug_print("got draft msgnum %d from scanning\n", msgnum);
10446 } else {
10447 debug_print("didn't get draft msgnum after scanning\n");
10449 } else {
10450 debug_print("got draft msgnum %d from adding\n", msgnum);
10452 if (msgnum < 0) {
10453 warn_err:
10454 claws_unlink(tmp);
10455 g_free(tmp);
10456 if (action != COMPOSE_AUTO_SAVE) {
10457 if (action != COMPOSE_DRAFT_FOR_EXIT)
10458 alertpanel_error(_("Could not save draft."));
10459 else {
10460 AlertValue val;
10461 gtkut_window_popup(compose->window);
10462 val = alertpanel_full(_("Could not save draft"),
10463 _("Could not save draft.\n"
10464 "Do you want to cancel exit or discard this email?"),
10465 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10466 FALSE, NULL, ALERT_QUESTION);
10467 if (val == G_ALERTALTERNATE) {
10468 lock = FALSE;
10469 g_mutex_unlock(compose->mutex); /* must be done before closing */
10470 compose_close(compose);
10471 return TRUE;
10472 } else {
10473 lock = FALSE;
10474 g_mutex_unlock(compose->mutex); /* must be done before closing */
10475 return FALSE;
10479 goto unlock;
10481 g_free(tmp);
10483 if (compose->mode == COMPOSE_REEDIT) {
10484 compose_remove_reedit_target(compose, TRUE);
10487 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10489 if (newmsginfo) {
10490 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10491 if (target_locked)
10492 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10493 else
10494 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10495 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10496 procmsg_msginfo_set_flags(newmsginfo, 0,
10497 MSG_HAS_ATTACHMENT);
10499 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10500 compose_register_draft(newmsginfo);
10502 procmsg_msginfo_free(&newmsginfo);
10505 folder_item_scan(draft);
10507 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10508 lock = FALSE;
10509 g_mutex_unlock(compose->mutex); /* must be done before closing */
10510 compose_close(compose);
10511 return TRUE;
10512 } else {
10513 #ifdef G_OS_WIN32
10514 GFile *f;
10515 GFileInfo *fi;
10516 GTimeVal tv;
10517 GError *error = NULL;
10518 #else
10519 GStatBuf s;
10520 #endif
10521 gchar *path;
10522 goffset size, mtime;
10524 path = folder_item_fetch_msg(draft, msgnum);
10525 if (path == NULL) {
10526 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10527 goto unlock;
10529 #ifdef G_OS_WIN32
10530 f = g_file_new_for_path(path);
10531 fi = g_file_query_info(f, "standard::size,time::modified",
10532 G_FILE_QUERY_INFO_NONE, NULL, &error);
10533 if (error != NULL) {
10534 debug_print("couldn't query file info for '%s': %s\n",
10535 path, error->message);
10536 g_error_free(error);
10537 g_free(path);
10538 g_object_unref(f);
10539 goto unlock;
10541 size = g_file_info_get_size(fi);
10542 g_file_info_get_modification_time(fi, &tv);
10543 mtime = tv.tv_sec;
10544 g_object_unref(fi);
10545 g_object_unref(f);
10546 #else
10547 if (g_stat(path, &s) < 0) {
10548 FILE_OP_ERROR(path, "stat");
10549 g_free(path);
10550 goto unlock;
10552 size = s.st_size;
10553 mtime = s.st_mtime;
10554 #endif
10555 g_free(path);
10557 procmsg_msginfo_free(&(compose->targetinfo));
10558 compose->targetinfo = procmsg_msginfo_new();
10559 compose->targetinfo->msgnum = msgnum;
10560 compose->targetinfo->size = size;
10561 compose->targetinfo->mtime = mtime;
10562 compose->targetinfo->folder = draft;
10563 if (target_locked)
10564 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10565 compose->mode = COMPOSE_REEDIT;
10567 if (action == COMPOSE_AUTO_SAVE) {
10568 compose->autosaved_draft = compose->targetinfo;
10570 compose->modified = FALSE;
10571 compose_set_title(compose);
10573 unlock:
10574 lock = FALSE;
10575 g_mutex_unlock(compose->mutex);
10576 return TRUE;
10579 void compose_clear_exit_drafts(void)
10581 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10582 DRAFTED_AT_EXIT, NULL);
10583 if (is_file_exist(filepath))
10584 claws_unlink(filepath);
10586 g_free(filepath);
10589 void compose_reopen_exit_drafts(void)
10591 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10592 DRAFTED_AT_EXIT, NULL);
10593 FILE *fp = claws_fopen(filepath, "rb");
10594 gchar buf[1024];
10596 if (fp) {
10597 while (claws_fgets(buf, sizeof(buf), fp)) {
10598 gchar **parts = g_strsplit(buf, "\t", 2);
10599 const gchar *folder = parts[0];
10600 int msgnum = parts[1] ? atoi(parts[1]):-1;
10602 if (folder && *folder && msgnum > -1) {
10603 FolderItem *item = folder_find_item_from_identifier(folder);
10604 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10605 if (info)
10606 compose_reedit(info, FALSE);
10608 g_strfreev(parts);
10610 claws_fclose(fp);
10612 g_free(filepath);
10613 compose_clear_exit_drafts();
10616 static void compose_save_cb(GtkAction *action, gpointer data)
10618 Compose *compose = (Compose *)data;
10619 compose_draft(compose, COMPOSE_KEEP_EDITING);
10620 compose->rmode = COMPOSE_REEDIT;
10623 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10625 if (compose && file_list) {
10626 GList *tmp;
10628 for ( tmp = file_list; tmp; tmp = tmp->next) {
10629 gchar *file = (gchar *) tmp->data;
10630 gchar *utf8_filename = conv_filename_to_utf8(file);
10631 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10632 compose_changed_cb(NULL, compose);
10633 if (free_data) {
10634 g_free(file);
10635 tmp->data = NULL;
10637 g_free(utf8_filename);
10642 static void compose_attach_cb(GtkAction *action, gpointer data)
10644 Compose *compose = (Compose *)data;
10645 GList *file_list;
10647 if (compose->redirect_filename != NULL)
10648 return;
10650 /* Set focus_window properly, in case we were called via popup menu,
10651 * which unsets it (via focus_out_event callback on compose window). */
10652 manage_window_focus_in(compose->window, NULL, NULL);
10654 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10656 if (file_list) {
10657 compose_attach_from_list(compose, file_list, TRUE);
10658 g_list_free(file_list);
10662 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10664 Compose *compose = (Compose *)data;
10665 GList *file_list;
10666 gint files_inserted = 0;
10668 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10670 if (file_list) {
10671 GList *tmp;
10673 for ( tmp = file_list; tmp; tmp = tmp->next) {
10674 gchar *file = (gchar *) tmp->data;
10675 gchar *filedup = g_strdup(file);
10676 gchar *shortfile = g_path_get_basename(filedup);
10677 ComposeInsertResult res;
10678 /* insert the file if the file is short or if the user confirmed that
10679 he/she wants to insert the large file */
10680 res = compose_insert_file(compose, file);
10681 if (res == COMPOSE_INSERT_READ_ERROR) {
10682 alertpanel_error(_("File '%s' could not be read."), shortfile);
10683 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10684 alertpanel_error(_("File '%s' contained invalid characters\n"
10685 "for the current encoding, insertion may be incorrect."),
10686 shortfile);
10687 } else if (res == COMPOSE_INSERT_SUCCESS)
10688 files_inserted++;
10690 g_free(shortfile);
10691 g_free(filedup);
10692 g_free(file);
10694 g_list_free(file_list);
10697 #ifdef USE_ENCHANT
10698 if (files_inserted > 0 && compose->gtkaspell &&
10699 compose->gtkaspell->check_while_typing)
10700 gtkaspell_highlight_all(compose->gtkaspell);
10701 #endif
10704 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10706 Compose *compose = (Compose *)data;
10708 compose_insert_sig(compose, FALSE);
10711 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10713 Compose *compose = (Compose *)data;
10715 compose_insert_sig(compose, TRUE);
10718 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10719 gpointer data)
10721 gint x, y;
10722 Compose *compose = (Compose *)data;
10724 gtkut_widget_get_uposition(widget, &x, &y);
10725 if (!compose->batch) {
10726 prefs_common.compose_x = x;
10727 prefs_common.compose_y = y;
10729 if (compose->sending || compose->updating)
10730 return TRUE;
10731 compose_close_cb(NULL, compose);
10732 return TRUE;
10735 void compose_close_toolbar(Compose *compose)
10737 compose_close_cb(NULL, compose);
10740 static void compose_close_cb(GtkAction *action, gpointer data)
10742 Compose *compose = (Compose *)data;
10743 AlertValue val;
10745 #ifdef G_OS_UNIX
10746 if (compose->exteditor_tag != -1) {
10747 if (!compose_ext_editor_kill(compose))
10748 return;
10750 #endif
10752 if (compose->modified) {
10753 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10754 if (!g_mutex_trylock(compose->mutex)) {
10755 /* we don't want to lock the mutex once it's available,
10756 * because as the only other part of compose.c locking
10757 * it is compose_close - which means once unlocked,
10758 * the compose struct will be freed */
10759 debug_print("couldn't lock mutex, probably sending\n");
10760 return;
10762 if (!reedit || compose->folder->stype == F_DRAFT) {
10763 val = alertpanel(_("Discard message"),
10764 _("This message has been modified. Discard it?"),
10765 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10766 ALERTFOCUS_FIRST);
10767 } else {
10768 val = alertpanel(_("Save changes"),
10769 _("This message has been modified. Save the latest changes?"),
10770 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10771 ALERTFOCUS_SECOND);
10773 g_mutex_unlock(compose->mutex);
10774 switch (val) {
10775 case G_ALERTDEFAULT:
10776 if (compose_can_autosave(compose) && !reedit)
10777 compose_remove_draft(compose);
10778 break;
10779 case G_ALERTALTERNATE:
10780 compose_draft(data, COMPOSE_QUIT_EDITING);
10781 return;
10782 default:
10783 return;
10787 compose_close(compose);
10790 static void compose_print_cb(GtkAction *action, gpointer data)
10792 Compose *compose = (Compose *) data;
10794 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10795 if (compose->targetinfo)
10796 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10799 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10801 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10802 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10803 Compose *compose = (Compose *) data;
10805 if (active)
10806 compose->out_encoding = (CharSet)value;
10809 static void compose_address_cb(GtkAction *action, gpointer data)
10811 Compose *compose = (Compose *)data;
10813 #ifndef USE_ALT_ADDRBOOK
10814 addressbook_open(compose);
10815 #else
10816 GError* error = NULL;
10817 addressbook_connect_signals(compose);
10818 addressbook_dbus_open(TRUE, &error);
10819 if (error) {
10820 g_warning("%s", error->message);
10821 g_error_free(error);
10823 #endif
10826 static void about_show_cb(GtkAction *action, gpointer data)
10828 about_show();
10831 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10833 Compose *compose = (Compose *)data;
10834 Template *tmpl;
10835 gchar *msg;
10836 AlertValue val;
10838 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10839 cm_return_if_fail(tmpl != NULL);
10841 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10842 tmpl->name);
10843 val = alertpanel(_("Apply template"), msg,
10844 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10845 g_free(msg);
10847 if (val == G_ALERTDEFAULT)
10848 compose_template_apply(compose, tmpl, TRUE);
10849 else if (val == G_ALERTALTERNATE)
10850 compose_template_apply(compose, tmpl, FALSE);
10853 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10855 Compose *compose = (Compose *)data;
10857 #ifdef G_OS_UNIX
10858 if (compose->exteditor_tag != -1) {
10859 debug_print("ignoring open external editor: external editor still open\n");
10860 return;
10862 #endif
10863 compose_exec_ext_editor(compose);
10866 static void compose_undo_cb(GtkAction *action, gpointer data)
10868 Compose *compose = (Compose *)data;
10869 gboolean prev_autowrap = compose->autowrap;
10871 compose->autowrap = FALSE;
10872 undo_undo(compose->undostruct);
10873 compose->autowrap = prev_autowrap;
10876 static void compose_redo_cb(GtkAction *action, gpointer data)
10878 Compose *compose = (Compose *)data;
10879 gboolean prev_autowrap = compose->autowrap;
10881 compose->autowrap = FALSE;
10882 undo_redo(compose->undostruct);
10883 compose->autowrap = prev_autowrap;
10886 static void entry_cut_clipboard(GtkWidget *entry)
10888 if (GTK_IS_EDITABLE(entry))
10889 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10890 else if (GTK_IS_TEXT_VIEW(entry))
10891 gtk_text_buffer_cut_clipboard(
10892 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10893 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10894 TRUE);
10897 static void entry_copy_clipboard(GtkWidget *entry)
10899 if (GTK_IS_EDITABLE(entry))
10900 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10901 else if (GTK_IS_TEXT_VIEW(entry))
10902 gtk_text_buffer_copy_clipboard(
10903 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10904 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10907 static void paste_text(Compose *compose, GtkWidget *entry,
10908 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place,
10909 const gchar *contents)
10911 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10912 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10913 GtkTextIter start_iter, end_iter;
10914 gint start, end;
10916 if (contents == NULL)
10917 return;
10919 /* we shouldn't delete the selection when middle-click-pasting, or we
10920 * can't mid-click-paste our own selection */
10921 if (clip != GDK_SELECTION_PRIMARY) {
10922 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10923 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10926 if (insert_place == NULL) {
10927 /* if insert_place isn't specified, insert at the cursor.
10928 * used for Ctrl-V pasting */
10929 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10930 start = gtk_text_iter_get_offset(&start_iter);
10931 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10932 } else {
10933 /* if insert_place is specified, paste here.
10934 * used for mid-click-pasting */
10935 start = gtk_text_iter_get_offset(insert_place);
10936 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10937 if (prefs_common.primary_paste_unselects)
10938 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10941 if (!wrap) {
10942 /* paste unwrapped: mark the paste so it's not wrapped later */
10943 end = start + strlen(contents);
10944 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10945 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10946 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10947 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10948 /* rewrap paragraph now (after a mid-click-paste) */
10949 mark_start = gtk_text_buffer_get_insert(buffer);
10950 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10951 gtk_text_iter_backward_char(&start_iter);
10952 compose_beautify_paragraph(compose, &start_iter, TRUE);
10954 compose->modified = TRUE;
10957 static void attach_uri_list(Compose *compose, GtkSelectionData *data)
10959 GList *list, *tmp;
10961 list = uri_list_extract_filenames(
10962 (const gchar *)gtk_selection_data_get_data(data));
10963 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10964 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10965 compose_attach_append
10966 (compose, (const gchar *)tmp->data,
10967 utf8_filename, NULL, NULL);
10968 g_free(utf8_filename);
10970 if (list)
10971 compose_changed_cb(NULL, compose);
10973 list_free_strings_full(list);
10976 int attach_image(Compose *compose, GtkSelectionData *data, const gchar *subtype)
10978 FILE *fp;
10979 const guchar *contents;
10980 gchar *file;
10981 gchar *type;
10982 size_t len;
10983 int r;
10985 cm_return_val_if_fail(data != NULL, -1);
10987 contents = gtk_selection_data_get_data(data);
10988 len = gtk_selection_data_get_length(data);
10990 file = g_strconcat(get_tmp_file(), "-image.", subtype, NULL);
10992 debug_print("writing image to %s\n", file);
10994 if ((fp = claws_fopen(file, "wb")) == NULL) {
10995 FILE_OP_ERROR(file, "claws_fopen");
10996 return -1;
10999 if (claws_fwrite(contents, 1, len, fp) != len) {
11000 FILE_OP_ERROR(file, "claws_fwrite");
11001 claws_fclose(fp);
11002 claws_unlink(file);
11003 return -1;
11006 r = claws_safe_fclose(fp);
11008 if (r == EOF) {
11009 FILE_OP_ERROR(file, "claws_fclose");
11010 claws_unlink(file);
11011 return -1;
11014 type = g_strconcat("image/", subtype, NULL);
11016 compose_attach_append(compose, (const gchar *)file,
11017 (const gchar *)file, type, NULL);
11019 alertpanel_notice(_("The pasted image has been attached as: \n%s"), file);
11021 g_free(file);
11022 g_free(type);
11024 return 0;
11027 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
11028 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
11030 if (GTK_IS_TEXT_VIEW(entry)) {
11031 GdkAtom types = gdk_atom_intern ("MULTIPLE", FALSE);
11032 GdkAtom *targets = NULL;
11033 int n_targets = 0, i;
11034 gboolean paste_done = FALSE;
11035 GtkClipboard *clipboard = gtk_clipboard_get(clip);
11037 GtkSelectionData *contents = gtk_clipboard_wait_for_contents(
11038 clipboard, types);
11040 if (contents != NULL) {
11041 gtk_selection_data_get_targets(contents, &targets, &n_targets);
11042 gtk_selection_data_free(contents);
11045 for (i = 0; i < n_targets; i++) {
11046 GdkAtom atom = targets[i];
11047 gchar *atom_type = gdk_atom_name(atom);
11049 if (atom_type != NULL) {
11050 GtkSelectionData *data = gtk_clipboard_wait_for_contents(
11051 clipboard, atom);
11052 debug_print("got contents of type %s\n", atom_type);
11053 if (!strcmp(atom_type, "text/plain")) {
11054 /* let the default text handler handle it */
11055 break;
11056 } else if (!strcmp(atom_type, "text/uri-list")) {
11057 attach_uri_list(compose, data);
11059 paste_done = TRUE;
11060 break;
11061 } else if (!strncmp(atom_type, "image/", strlen("image/"))) {
11062 gchar *subtype = g_strdup((gchar *)(strstr(atom_type, "/")+1));
11063 debug_print("image of type %s\n", subtype);
11065 attach_image(compose, data, subtype);
11066 g_free(subtype);
11068 paste_done = TRUE;
11069 break;
11073 if (!paste_done) {
11074 gchar *def_text = gtk_clipboard_wait_for_text(clipboard);
11075 paste_text(compose, entry, wrap, clip,
11076 insert_place, def_text);
11077 g_free(def_text);
11079 g_free(targets);
11081 } else if (GTK_IS_EDITABLE(entry)) {
11082 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
11083 compose->modified = TRUE;
11087 static void entry_allsel(GtkWidget *entry)
11089 if (GTK_IS_EDITABLE(entry))
11090 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
11091 else if (GTK_IS_TEXT_VIEW(entry)) {
11092 GtkTextIter startiter, enditer;
11093 GtkTextBuffer *textbuf;
11095 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
11096 gtk_text_buffer_get_start_iter(textbuf, &startiter);
11097 gtk_text_buffer_get_end_iter(textbuf, &enditer);
11099 gtk_text_buffer_move_mark_by_name(textbuf,
11100 "selection_bound", &startiter);
11101 gtk_text_buffer_move_mark_by_name(textbuf,
11102 "insert", &enditer);
11106 static void compose_cut_cb(GtkAction *action, gpointer data)
11108 Compose *compose = (Compose *)data;
11109 if (compose->focused_editable
11110 #ifndef GENERIC_UMPC
11111 && gtk_widget_has_focus(compose->focused_editable)
11112 #endif
11114 entry_cut_clipboard(compose->focused_editable);
11117 static void compose_copy_cb(GtkAction *action, gpointer data)
11119 Compose *compose = (Compose *)data;
11120 if (compose->focused_editable
11121 #ifndef GENERIC_UMPC
11122 && gtk_widget_has_focus(compose->focused_editable)
11123 #endif
11125 entry_copy_clipboard(compose->focused_editable);
11128 static void compose_paste_cb(GtkAction *action, gpointer data)
11130 Compose *compose = (Compose *)data;
11131 gint prev_autowrap;
11132 GtkTextBuffer *buffer;
11133 BLOCK_WRAP();
11134 if (compose->focused_editable
11135 #ifndef GENERIC_UMPC
11136 && gtk_widget_has_focus(compose->focused_editable)
11137 #endif
11139 entry_paste_clipboard(compose, compose->focused_editable,
11140 prefs_common.linewrap_pastes,
11141 GDK_SELECTION_CLIPBOARD, NULL);
11142 UNBLOCK_WRAP();
11144 #ifdef USE_ENCHANT
11145 if (
11146 #ifndef GENERIC_UMPC
11147 gtk_widget_has_focus(compose->text) &&
11148 #endif
11149 compose->gtkaspell &&
11150 compose->gtkaspell->check_while_typing)
11151 gtkaspell_highlight_all(compose->gtkaspell);
11152 #endif
11155 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11157 Compose *compose = (Compose *)data;
11158 gint wrap_quote = prefs_common.linewrap_quote;
11159 if (compose->focused_editable
11160 #ifndef GENERIC_UMPC
11161 && gtk_widget_has_focus(compose->focused_editable)
11162 #endif
11164 /* let text_insert() (called directly or at a later time
11165 * after the gtk_editable_paste_clipboard) know that
11166 * text is to be inserted as a quotation. implemented
11167 * by using a simple refcount... */
11168 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11169 G_OBJECT(compose->focused_editable),
11170 "paste_as_quotation"));
11171 g_object_set_data(G_OBJECT(compose->focused_editable),
11172 "paste_as_quotation",
11173 GINT_TO_POINTER(paste_as_quotation + 1));
11174 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11175 entry_paste_clipboard(compose, compose->focused_editable,
11176 prefs_common.linewrap_pastes,
11177 GDK_SELECTION_CLIPBOARD, NULL);
11178 prefs_common.linewrap_quote = wrap_quote;
11182 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11184 Compose *compose = (Compose *)data;
11185 gint prev_autowrap;
11186 GtkTextBuffer *buffer;
11187 BLOCK_WRAP();
11188 if (compose->focused_editable
11189 #ifndef GENERIC_UMPC
11190 && gtk_widget_has_focus(compose->focused_editable)
11191 #endif
11193 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11194 GDK_SELECTION_CLIPBOARD, NULL);
11195 UNBLOCK_WRAP();
11197 #ifdef USE_ENCHANT
11198 if (
11199 #ifndef GENERIC_UMPC
11200 gtk_widget_has_focus(compose->text) &&
11201 #endif
11202 compose->gtkaspell &&
11203 compose->gtkaspell->check_while_typing)
11204 gtkaspell_highlight_all(compose->gtkaspell);
11205 #endif
11208 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11210 Compose *compose = (Compose *)data;
11211 gint prev_autowrap;
11212 GtkTextBuffer *buffer;
11213 BLOCK_WRAP();
11214 if (compose->focused_editable
11215 #ifndef GENERIC_UMPC
11216 && gtk_widget_has_focus(compose->focused_editable)
11217 #endif
11219 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11220 GDK_SELECTION_CLIPBOARD, NULL);
11221 UNBLOCK_WRAP();
11223 #ifdef USE_ENCHANT
11224 if (
11225 #ifndef GENERIC_UMPC
11226 gtk_widget_has_focus(compose->text) &&
11227 #endif
11228 compose->gtkaspell &&
11229 compose->gtkaspell->check_while_typing)
11230 gtkaspell_highlight_all(compose->gtkaspell);
11231 #endif
11234 static void compose_allsel_cb(GtkAction *action, gpointer data)
11236 Compose *compose = (Compose *)data;
11237 if (compose->focused_editable
11238 #ifndef GENERIC_UMPC
11239 && gtk_widget_has_focus(compose->focused_editable)
11240 #endif
11242 entry_allsel(compose->focused_editable);
11245 static void textview_move_beginning_of_line (GtkTextView *text)
11247 GtkTextBuffer *buffer;
11248 GtkTextMark *mark;
11249 GtkTextIter ins;
11251 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11253 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11254 mark = gtk_text_buffer_get_insert(buffer);
11255 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11256 gtk_text_iter_set_line_offset(&ins, 0);
11257 gtk_text_buffer_place_cursor(buffer, &ins);
11260 static void textview_move_forward_character (GtkTextView *text)
11262 GtkTextBuffer *buffer;
11263 GtkTextMark *mark;
11264 GtkTextIter ins;
11266 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11268 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11269 mark = gtk_text_buffer_get_insert(buffer);
11270 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11271 if (gtk_text_iter_forward_cursor_position(&ins))
11272 gtk_text_buffer_place_cursor(buffer, &ins);
11275 static void textview_move_backward_character (GtkTextView *text)
11277 GtkTextBuffer *buffer;
11278 GtkTextMark *mark;
11279 GtkTextIter ins;
11281 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11283 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11284 mark = gtk_text_buffer_get_insert(buffer);
11285 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11286 if (gtk_text_iter_backward_cursor_position(&ins))
11287 gtk_text_buffer_place_cursor(buffer, &ins);
11290 static void textview_move_forward_word (GtkTextView *text)
11292 GtkTextBuffer *buffer;
11293 GtkTextMark *mark;
11294 GtkTextIter ins;
11295 gint count;
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 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11303 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11304 gtk_text_iter_backward_word_start(&ins);
11305 gtk_text_buffer_place_cursor(buffer, &ins);
11309 static void textview_move_backward_word (GtkTextView *text)
11311 GtkTextBuffer *buffer;
11312 GtkTextMark *mark;
11313 GtkTextIter ins;
11315 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11317 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11318 mark = gtk_text_buffer_get_insert(buffer);
11319 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11320 if (gtk_text_iter_backward_word_starts(&ins, 1))
11321 gtk_text_buffer_place_cursor(buffer, &ins);
11324 static void textview_move_end_of_line (GtkTextView *text)
11326 GtkTextBuffer *buffer;
11327 GtkTextMark *mark;
11328 GtkTextIter ins;
11330 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11332 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11333 mark = gtk_text_buffer_get_insert(buffer);
11334 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11335 if (gtk_text_iter_forward_to_line_end(&ins))
11336 gtk_text_buffer_place_cursor(buffer, &ins);
11339 static void textview_move_next_line (GtkTextView *text)
11341 GtkTextBuffer *buffer;
11342 GtkTextMark *mark;
11343 GtkTextIter ins;
11344 gint offset;
11346 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11348 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11349 mark = gtk_text_buffer_get_insert(buffer);
11350 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11351 offset = gtk_text_iter_get_line_offset(&ins);
11352 if (gtk_text_iter_forward_line(&ins)) {
11353 gtk_text_iter_set_line_offset(&ins, offset);
11354 gtk_text_buffer_place_cursor(buffer, &ins);
11358 static void textview_move_previous_line (GtkTextView *text)
11360 GtkTextBuffer *buffer;
11361 GtkTextMark *mark;
11362 GtkTextIter ins;
11363 gint offset;
11365 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11367 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11368 mark = gtk_text_buffer_get_insert(buffer);
11369 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11370 offset = gtk_text_iter_get_line_offset(&ins);
11371 if (gtk_text_iter_backward_line(&ins)) {
11372 gtk_text_iter_set_line_offset(&ins, offset);
11373 gtk_text_buffer_place_cursor(buffer, &ins);
11377 static void textview_delete_forward_character (GtkTextView *text)
11379 GtkTextBuffer *buffer;
11380 GtkTextMark *mark;
11381 GtkTextIter ins, end_iter;
11383 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11385 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11386 mark = gtk_text_buffer_get_insert(buffer);
11387 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11388 end_iter = ins;
11389 if (gtk_text_iter_forward_char(&end_iter)) {
11390 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11394 static void textview_delete_backward_character (GtkTextView *text)
11396 GtkTextBuffer *buffer;
11397 GtkTextMark *mark;
11398 GtkTextIter ins, end_iter;
11400 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11402 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11403 mark = gtk_text_buffer_get_insert(buffer);
11404 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11405 end_iter = ins;
11406 if (gtk_text_iter_backward_char(&end_iter)) {
11407 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11411 static void textview_delete_forward_word (GtkTextView *text)
11413 GtkTextBuffer *buffer;
11414 GtkTextMark *mark;
11415 GtkTextIter ins, end_iter;
11417 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11419 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11420 mark = gtk_text_buffer_get_insert(buffer);
11421 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11422 end_iter = ins;
11423 if (gtk_text_iter_forward_word_end(&end_iter)) {
11424 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11428 static void textview_delete_backward_word (GtkTextView *text)
11430 GtkTextBuffer *buffer;
11431 GtkTextMark *mark;
11432 GtkTextIter ins, end_iter;
11434 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11436 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11437 mark = gtk_text_buffer_get_insert(buffer);
11438 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11439 end_iter = ins;
11440 if (gtk_text_iter_backward_word_start(&end_iter)) {
11441 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11445 static void textview_delete_line (GtkTextView *text)
11447 GtkTextBuffer *buffer;
11448 GtkTextMark *mark;
11449 GtkTextIter ins, start_iter, end_iter;
11451 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11453 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11454 mark = gtk_text_buffer_get_insert(buffer);
11455 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11457 start_iter = ins;
11458 gtk_text_iter_set_line_offset(&start_iter, 0);
11460 end_iter = ins;
11461 if (gtk_text_iter_ends_line(&end_iter)){
11462 if (!gtk_text_iter_forward_char(&end_iter))
11463 gtk_text_iter_backward_char(&start_iter);
11465 else
11466 gtk_text_iter_forward_to_line_end(&end_iter);
11467 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11470 static void textview_delete_to_line_end (GtkTextView *text)
11472 GtkTextBuffer *buffer;
11473 GtkTextMark *mark;
11474 GtkTextIter ins, end_iter;
11476 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11478 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11479 mark = gtk_text_buffer_get_insert(buffer);
11480 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11481 end_iter = ins;
11482 if (gtk_text_iter_ends_line(&end_iter))
11483 gtk_text_iter_forward_char(&end_iter);
11484 else
11485 gtk_text_iter_forward_to_line_end(&end_iter);
11486 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11489 #define DO_ACTION(name, act) { \
11490 if(!strcmp(name, a_name)) { \
11491 return act; \
11494 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11496 const gchar *a_name = gtk_action_get_name(action);
11497 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11498 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11499 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11500 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11501 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11502 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11503 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11504 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11505 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11506 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11507 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11508 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11509 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11510 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11511 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11514 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11516 Compose *compose = (Compose *)data;
11517 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11518 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11520 action = compose_call_advanced_action_from_path(gaction);
11522 static struct {
11523 void (*do_action) (GtkTextView *text);
11524 } action_table[] = {
11525 {textview_move_beginning_of_line},
11526 {textview_move_forward_character},
11527 {textview_move_backward_character},
11528 {textview_move_forward_word},
11529 {textview_move_backward_word},
11530 {textview_move_end_of_line},
11531 {textview_move_next_line},
11532 {textview_move_previous_line},
11533 {textview_delete_forward_character},
11534 {textview_delete_backward_character},
11535 {textview_delete_forward_word},
11536 {textview_delete_backward_word},
11537 {textview_delete_line},
11538 {textview_delete_to_line_end}
11541 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11543 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11544 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11545 if (action_table[action].do_action)
11546 action_table[action].do_action(text);
11547 else
11548 g_warning("Not implemented yet.");
11552 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11554 GtkAllocation allocation;
11555 GtkWidget *parent;
11556 gchar *str = NULL;
11558 if (GTK_IS_EDITABLE(widget)) {
11559 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11560 gtk_editable_set_position(GTK_EDITABLE(widget),
11561 strlen(str));
11562 g_free(str);
11563 if ((parent = gtk_widget_get_parent(widget))
11564 && (parent = gtk_widget_get_parent(parent))
11565 && (parent = gtk_widget_get_parent(parent))) {
11566 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11567 gtk_widget_get_allocation(widget, &allocation);
11568 gint y = allocation.y;
11569 gint height = allocation.height;
11570 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11571 (GTK_SCROLLED_WINDOW(parent));
11573 gfloat value = gtk_adjustment_get_value(shown);
11574 gfloat upper = gtk_adjustment_get_upper(shown);
11575 gfloat page_size = gtk_adjustment_get_page_size(shown);
11576 if (y < (int)value) {
11577 gtk_adjustment_set_value(shown, y - 1);
11579 if ((y + height) > ((int)value + (int)page_size)) {
11580 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11581 gtk_adjustment_set_value(shown,
11582 y + height - (int)page_size - 1);
11583 } else {
11584 gtk_adjustment_set_value(shown,
11585 (int)upper - (int)page_size - 1);
11592 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11593 compose->focused_editable = widget;
11595 #ifdef GENERIC_UMPC
11596 if (GTK_IS_TEXT_VIEW(widget)
11597 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11598 g_object_ref(compose->notebook);
11599 g_object_ref(compose->edit_vbox);
11600 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11601 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11602 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11603 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11604 g_object_unref(compose->notebook);
11605 g_object_unref(compose->edit_vbox);
11606 g_signal_handlers_block_by_func(G_OBJECT(widget),
11607 G_CALLBACK(compose_grab_focus_cb),
11608 compose);
11609 gtk_widget_grab_focus(widget);
11610 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11611 G_CALLBACK(compose_grab_focus_cb),
11612 compose);
11613 } else if (!GTK_IS_TEXT_VIEW(widget)
11614 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11615 g_object_ref(compose->notebook);
11616 g_object_ref(compose->edit_vbox);
11617 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11618 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11619 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11620 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11621 g_object_unref(compose->notebook);
11622 g_object_unref(compose->edit_vbox);
11623 g_signal_handlers_block_by_func(G_OBJECT(widget),
11624 G_CALLBACK(compose_grab_focus_cb),
11625 compose);
11626 gtk_widget_grab_focus(widget);
11627 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11628 G_CALLBACK(compose_grab_focus_cb),
11629 compose);
11631 #endif
11634 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11636 compose->modified = TRUE;
11637 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11638 #ifndef GENERIC_UMPC
11639 compose_set_title(compose);
11640 #endif
11643 static void compose_wrap_cb(GtkAction *action, gpointer data)
11645 Compose *compose = (Compose *)data;
11646 compose_beautify_paragraph(compose, NULL, TRUE);
11649 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11651 Compose *compose = (Compose *)data;
11652 compose_wrap_all_full(compose, TRUE);
11655 static void compose_find_cb(GtkAction *action, gpointer data)
11657 Compose *compose = (Compose *)data;
11659 message_search_compose(compose);
11662 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11663 gpointer data)
11665 Compose *compose = (Compose *)data;
11666 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11667 if (compose->autowrap)
11668 compose_wrap_all_full(compose, TRUE);
11669 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11672 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11673 gpointer data)
11675 Compose *compose = (Compose *)data;
11676 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11679 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11681 Compose *compose = (Compose *)data;
11683 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11684 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11687 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11689 Compose *compose = (Compose *)data;
11691 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11692 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11695 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11697 g_free(compose->privacy_system);
11698 g_free(compose->encdata);
11700 compose->privacy_system = g_strdup(account->default_privacy_system);
11701 compose_update_privacy_system_menu_item(compose, warn);
11704 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11706 if (folder_item != NULL) {
11707 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT &&
11708 privacy_system_can_sign(compose->privacy_system)) {
11709 compose_use_signing(compose,
11710 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11712 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT &&
11713 privacy_system_can_encrypt(compose->privacy_system)) {
11714 compose_use_encryption(compose,
11715 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11720 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11722 Compose *compose = (Compose *)data;
11724 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11725 gtk_widget_show(compose->ruler_hbox);
11726 prefs_common.show_ruler = TRUE;
11727 } else {
11728 gtk_widget_hide(compose->ruler_hbox);
11729 gtk_widget_queue_resize(compose->edit_vbox);
11730 prefs_common.show_ruler = FALSE;
11734 static void compose_attach_drag_received_cb (GtkWidget *widget,
11735 GdkDragContext *context,
11736 gint x,
11737 gint y,
11738 GtkSelectionData *data,
11739 guint info,
11740 guint time,
11741 gpointer user_data)
11743 Compose *compose = (Compose *)user_data;
11744 GdkAtom type;
11746 type = gtk_selection_data_get_data_type(data);
11747 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11748 && gtk_drag_get_source_widget(context) !=
11749 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11750 attach_uri_list(compose, data);
11751 } else if (gtk_drag_get_source_widget(context)
11752 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11753 /* comes from our summaryview */
11754 SummaryView * summaryview = NULL;
11755 GSList * list = NULL, *cur = NULL;
11757 if (mainwindow_get_mainwindow())
11758 summaryview = mainwindow_get_mainwindow()->summaryview;
11760 if (summaryview)
11761 list = summary_get_selected_msg_list(summaryview);
11763 for (cur = list; cur; cur = cur->next) {
11764 MsgInfo *msginfo = (MsgInfo *)cur->data;
11765 gchar *file = NULL;
11766 if (msginfo)
11767 file = procmsg_get_message_file_full(msginfo,
11768 TRUE, TRUE);
11769 if (file) {
11770 compose_attach_append(compose, (const gchar *)file,
11771 (const gchar *)file, "message/rfc822", NULL);
11772 g_free(file);
11775 g_slist_free(list);
11779 static gboolean compose_drag_drop(GtkWidget *widget,
11780 GdkDragContext *drag_context,
11781 gint x, gint y,
11782 guint time, gpointer user_data)
11784 /* not handling this signal makes compose_insert_drag_received_cb
11785 * called twice */
11786 return TRUE;
11789 static gboolean completion_set_focus_to_subject
11790 (GtkWidget *widget,
11791 GdkEventKey *event,
11792 Compose *compose)
11794 cm_return_val_if_fail(compose != NULL, FALSE);
11796 /* make backtab move to subject field */
11797 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11798 gtk_widget_grab_focus(compose->subject_entry);
11799 return TRUE;
11801 return FALSE;
11804 static void compose_insert_drag_received_cb (GtkWidget *widget,
11805 GdkDragContext *drag_context,
11806 gint x,
11807 gint y,
11808 GtkSelectionData *data,
11809 guint info,
11810 guint time,
11811 gpointer user_data)
11813 Compose *compose = (Compose *)user_data;
11814 GList *list, *tmp;
11815 GdkAtom type;
11816 guint num_files;
11817 gchar *msg;
11819 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11820 * does not work */
11821 type = gtk_selection_data_get_data_type(data);
11822 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11823 AlertValue val = G_ALERTDEFAULT;
11824 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11826 list = uri_list_extract_filenames(ddata);
11827 num_files = g_list_length(list);
11828 if (list == NULL && strstr(ddata, "://")) {
11829 /* Assume a list of no files, and data has ://, is a remote link */
11830 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11831 gchar *tmpfile = get_tmp_file();
11832 str_write_to_file(tmpdata, tmpfile, TRUE);
11833 g_free(tmpdata);
11834 compose_insert_file(compose, tmpfile);
11835 claws_unlink(tmpfile);
11836 g_free(tmpfile);
11837 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11838 compose_beautify_paragraph(compose, NULL, TRUE);
11839 return;
11841 switch (prefs_common.compose_dnd_mode) {
11842 case COMPOSE_DND_ASK:
11843 msg = g_strdup_printf(
11844 ngettext(
11845 "Do you want to insert the contents of the file "
11846 "into the message body, or attach it to the email?",
11847 "Do you want to insert the contents of the %d files "
11848 "into the message body, or attach them to the email?",
11849 num_files),
11850 num_files);
11851 val = alertpanel_full(_("Insert or attach?"), msg,
11852 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11853 ALERTFOCUS_SECOND,
11854 TRUE, NULL, ALERT_QUESTION);
11855 g_free(msg);
11856 break;
11857 case COMPOSE_DND_INSERT:
11858 val = G_ALERTALTERNATE;
11859 break;
11860 case COMPOSE_DND_ATTACH:
11861 val = G_ALERTOTHER;
11862 break;
11863 default:
11864 /* unexpected case */
11865 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11868 if (val & G_ALERTDISABLE) {
11869 val &= ~G_ALERTDISABLE;
11870 /* remember what action to perform by default, only if we don't click Cancel */
11871 if (val == G_ALERTALTERNATE)
11872 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11873 else if (val == G_ALERTOTHER)
11874 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11877 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11878 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11879 list_free_strings_full(list);
11880 return;
11881 } else if (val == G_ALERTOTHER) {
11882 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11883 list_free_strings_full(list);
11884 return;
11887 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11888 compose_insert_file(compose, (const gchar *)tmp->data);
11890 list_free_strings_full(list);
11891 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11892 return;
11896 static void compose_header_drag_received_cb (GtkWidget *widget,
11897 GdkDragContext *drag_context,
11898 gint x,
11899 gint y,
11900 GtkSelectionData *data,
11901 guint info,
11902 guint time,
11903 gpointer user_data)
11905 GtkEditable *entry = (GtkEditable *)user_data;
11906 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11908 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11909 * does not work */
11911 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11912 gchar *decoded=g_new(gchar, strlen(email));
11913 int start = 0;
11915 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11916 gtk_editable_delete_text(entry, 0, -1);
11917 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11918 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11919 g_free(decoded);
11920 return;
11922 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11925 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11927 Compose *compose = (Compose *)data;
11929 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11930 compose->return_receipt = TRUE;
11931 else
11932 compose->return_receipt = FALSE;
11935 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11937 Compose *compose = (Compose *)data;
11939 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11940 compose->remove_references = TRUE;
11941 else
11942 compose->remove_references = FALSE;
11945 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11946 ComposeHeaderEntry *headerentry)
11948 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11949 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11950 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11951 return FALSE;
11954 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11955 GdkEventKey *event,
11956 ComposeHeaderEntry *headerentry)
11958 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11959 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11960 !(event->state & GDK_MODIFIER_MASK) &&
11961 (event->keyval == GDK_KEY_BackSpace) &&
11962 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11963 gtk_container_remove
11964 (GTK_CONTAINER(headerentry->compose->header_table),
11965 headerentry->combo);
11966 gtk_container_remove
11967 (GTK_CONTAINER(headerentry->compose->header_table),
11968 headerentry->entry);
11969 headerentry->compose->header_list =
11970 g_slist_remove(headerentry->compose->header_list,
11971 headerentry);
11972 g_free(headerentry);
11973 } else if (event->keyval == GDK_KEY_Tab) {
11974 if (headerentry->compose->header_last == headerentry) {
11975 /* Override default next focus, and give it to subject_entry
11976 * instead of notebook tabs
11978 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11979 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11980 return TRUE;
11983 return FALSE;
11986 static gboolean scroll_postpone(gpointer data)
11988 Compose *compose = (Compose *)data;
11990 if (compose->batch)
11991 return FALSE;
11993 GTK_EVENTS_FLUSH();
11994 compose_show_first_last_header(compose, FALSE);
11995 return FALSE;
11998 static void compose_headerentry_changed_cb(GtkWidget *entry,
11999 ComposeHeaderEntry *headerentry)
12001 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
12002 compose_create_header_entry(headerentry->compose);
12003 g_signal_handlers_disconnect_matched
12004 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
12005 0, 0, NULL, NULL, headerentry);
12007 if (!headerentry->compose->batch)
12008 g_timeout_add(0, scroll_postpone, headerentry->compose);
12012 static gboolean compose_defer_auto_save_draft(Compose *compose)
12014 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
12015 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
12016 return FALSE;
12019 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
12021 GtkAdjustment *vadj;
12023 cm_return_if_fail(compose);
12025 if(compose->batch)
12026 return;
12028 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
12029 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
12030 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
12031 gtk_widget_get_parent(compose->header_table)));
12032 gtk_adjustment_set_value(vadj, (show_first ?
12033 gtk_adjustment_get_lower(vadj) :
12034 (gtk_adjustment_get_upper(vadj) -
12035 gtk_adjustment_get_page_size(vadj))));
12036 gtk_adjustment_changed(vadj);
12039 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
12040 const gchar *text, gint len, Compose *compose)
12042 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
12043 (G_OBJECT(compose->text), "paste_as_quotation"));
12044 GtkTextMark *mark;
12046 cm_return_if_fail(text != NULL);
12048 g_signal_handlers_block_by_func(G_OBJECT(buffer),
12049 G_CALLBACK(text_inserted),
12050 compose);
12051 if (paste_as_quotation) {
12052 gchar *new_text;
12053 const gchar *qmark;
12054 guint pos = 0;
12055 GtkTextIter start_iter;
12057 if (len < 0)
12058 len = strlen(text);
12060 new_text = g_strndup(text, len);
12062 qmark = compose_quote_char_from_context(compose);
12064 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
12065 gtk_text_buffer_place_cursor(buffer, iter);
12067 pos = gtk_text_iter_get_offset(iter);
12069 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
12070 _("Quote format error at line %d."));
12071 quote_fmt_reset_vartable();
12072 g_free(new_text);
12073 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
12074 GINT_TO_POINTER(paste_as_quotation - 1));
12076 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12077 gtk_text_buffer_place_cursor(buffer, iter);
12078 gtk_text_buffer_delete_mark(buffer, mark);
12080 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
12081 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
12082 compose_beautify_paragraph(compose, &start_iter, FALSE);
12083 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
12084 gtk_text_buffer_delete_mark(buffer, mark);
12085 } else {
12086 if (strcmp(text, "\n") || compose->automatic_break
12087 || gtk_text_iter_starts_line(iter)) {
12088 GtkTextIter before_ins;
12089 gtk_text_buffer_insert(buffer, iter, text, len);
12090 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
12091 before_ins = *iter;
12092 gtk_text_iter_backward_chars(&before_ins, len);
12093 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
12095 } else {
12096 /* check if the preceding is just whitespace or quote */
12097 GtkTextIter start_line;
12098 gchar *tmp = NULL, *quote = NULL;
12099 gint quote_len = 0, is_normal = 0;
12100 start_line = *iter;
12101 gtk_text_iter_set_line_offset(&start_line, 0);
12102 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
12103 g_strstrip(tmp);
12105 if (*tmp == '\0') {
12106 is_normal = 1;
12107 } else {
12108 quote = compose_get_quote_str(buffer, &start_line, &quote_len);
12109 if (quote)
12110 is_normal = 1;
12111 g_free(quote);
12113 g_free(tmp);
12115 if (is_normal) {
12116 gtk_text_buffer_insert(buffer, iter, text, len);
12117 } else {
12118 gtk_text_buffer_insert_with_tags_by_name(buffer,
12119 iter, text, len, "no_join", NULL);
12124 if (!paste_as_quotation) {
12125 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
12126 compose_beautify_paragraph(compose, iter, FALSE);
12127 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12128 gtk_text_buffer_delete_mark(buffer, mark);
12131 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
12132 G_CALLBACK(text_inserted),
12133 compose);
12134 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
12136 if (compose_can_autosave(compose) &&
12137 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
12138 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
12139 compose->draft_timeout_tag = g_timeout_add
12140 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
12143 #if USE_ENCHANT
12144 static void compose_check_all(GtkAction *action, gpointer data)
12146 Compose *compose = (Compose *)data;
12147 if (!compose->gtkaspell)
12148 return;
12150 if (gtk_widget_has_focus(compose->subject_entry))
12151 claws_spell_entry_check_all(
12152 CLAWS_SPELL_ENTRY(compose->subject_entry));
12153 else
12154 gtkaspell_check_all(compose->gtkaspell);
12157 static void compose_highlight_all(GtkAction *action, gpointer data)
12159 Compose *compose = (Compose *)data;
12160 if (compose->gtkaspell) {
12161 claws_spell_entry_recheck_all(
12162 CLAWS_SPELL_ENTRY(compose->subject_entry));
12163 gtkaspell_highlight_all(compose->gtkaspell);
12167 static void compose_check_backwards(GtkAction *action, gpointer data)
12169 Compose *compose = (Compose *)data;
12170 if (!compose->gtkaspell) {
12171 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12172 return;
12175 if (gtk_widget_has_focus(compose->subject_entry))
12176 claws_spell_entry_check_backwards(
12177 CLAWS_SPELL_ENTRY(compose->subject_entry));
12178 else
12179 gtkaspell_check_backwards(compose->gtkaspell);
12182 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12184 Compose *compose = (Compose *)data;
12185 if (!compose->gtkaspell) {
12186 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12187 return;
12190 if (gtk_widget_has_focus(compose->subject_entry))
12191 claws_spell_entry_check_forwards_go(
12192 CLAWS_SPELL_ENTRY(compose->subject_entry));
12193 else
12194 gtkaspell_check_forwards_go(compose->gtkaspell);
12196 #endif
12199 *\brief Guess originating forward account from MsgInfo and several
12200 * "common preference" settings. Return NULL if no guess.
12202 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12204 PrefsAccount *account = NULL;
12206 cm_return_val_if_fail(msginfo, NULL);
12207 cm_return_val_if_fail(msginfo->folder, NULL);
12208 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12210 if (msginfo->folder->prefs->enable_default_account)
12211 account = account_find_from_id(msginfo->folder->prefs->default_account);
12213 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12214 gchar *to;
12215 Xstrdup_a(to, msginfo->to, return NULL);
12216 extract_address(to);
12217 account = account_find_from_address(to, FALSE);
12220 if (!account && prefs_common.forward_account_autosel) {
12221 gchar *cc = NULL;
12222 if (!procheader_get_header_from_msginfo
12223 (msginfo, &cc, "Cc:")) {
12224 gchar *buf = cc + strlen("Cc:");
12225 extract_address(buf);
12226 account = account_find_from_address(buf, FALSE);
12227 g_free(cc);
12231 if (!account && prefs_common.forward_account_autosel) {
12232 gchar *deliveredto = NULL;
12233 if (!procheader_get_header_from_msginfo
12234 (msginfo, &deliveredto, "Delivered-To:")) {
12235 gchar *buf = deliveredto + strlen("Delivered-To:");
12236 extract_address(buf);
12237 account = account_find_from_address(buf, FALSE);
12238 g_free(deliveredto);
12242 if (!account)
12243 account = msginfo->folder->folder->account;
12245 return account;
12248 gboolean compose_close(Compose *compose)
12250 gint x, y;
12252 cm_return_val_if_fail(compose, FALSE);
12254 if (!g_mutex_trylock(compose->mutex)) {
12255 /* we have to wait for the (possibly deferred by auto-save)
12256 * drafting to be done, before destroying the compose under
12257 * it. */
12258 debug_print("waiting for drafting to finish...\n");
12259 compose_allow_user_actions(compose, FALSE);
12260 if (compose->close_timeout_tag == 0) {
12261 compose->close_timeout_tag =
12262 g_timeout_add (500, (GSourceFunc) compose_close,
12263 compose);
12265 return TRUE;
12268 if (compose->draft_timeout_tag >= 0) {
12269 g_source_remove(compose->draft_timeout_tag);
12270 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12273 gtkut_widget_get_uposition(compose->window, &x, &y);
12274 if (!compose->batch) {
12275 prefs_common.compose_x = x;
12276 prefs_common.compose_y = y;
12278 g_mutex_unlock(compose->mutex);
12279 compose_destroy(compose);
12280 return FALSE;
12284 * Add entry field for each address in list.
12285 * \param compose E-Mail composition object.
12286 * \param listAddress List of (formatted) E-Mail addresses.
12288 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12289 GList *node;
12290 gchar *addr;
12291 node = listAddress;
12292 while( node ) {
12293 addr = ( gchar * ) node->data;
12294 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12295 node = g_list_next( node );
12299 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12300 guint action, gboolean opening_multiple)
12302 gchar *body = NULL;
12303 GSList *new_msglist = NULL;
12304 MsgInfo *tmp_msginfo = NULL;
12305 gboolean originally_enc = FALSE;
12306 gboolean originally_sig = FALSE;
12307 Compose *compose = NULL;
12308 gchar *s_system = NULL;
12310 cm_return_if_fail(msginfo_list != NULL);
12312 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12313 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12314 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12316 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12317 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12318 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12319 orig_msginfo, mimeinfo);
12320 if (tmp_msginfo != NULL) {
12321 new_msglist = g_slist_append(NULL, tmp_msginfo);
12323 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12324 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12325 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12327 tmp_msginfo->folder = orig_msginfo->folder;
12328 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12329 if (orig_msginfo->tags) {
12330 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12331 tmp_msginfo->folder->tags_dirty = TRUE;
12337 if (!opening_multiple && msgview != NULL)
12338 body = messageview_get_selection(msgview);
12340 if (new_msglist) {
12341 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12342 procmsg_msginfo_free(&tmp_msginfo);
12343 g_slist_free(new_msglist);
12344 } else
12345 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12347 if (compose && originally_enc) {
12348 compose_force_encryption(compose, compose->account, FALSE, s_system);
12351 if (compose && originally_sig && compose->account->default_sign_reply) {
12352 compose_force_signing(compose, compose->account, s_system);
12354 g_free(s_system);
12355 g_free(body);
12356 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12359 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12360 guint action)
12362 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12363 && msginfo_list != NULL
12364 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12365 GSList *cur = msginfo_list;
12366 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12367 "messages. Opening the windows "
12368 "could take some time. Do you "
12369 "want to continue?"),
12370 g_slist_length(msginfo_list));
12371 if (g_slist_length(msginfo_list) > 9
12372 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12373 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12374 g_free(msg);
12375 return;
12377 g_free(msg);
12378 /* We'll open multiple compose windows */
12379 /* let the WM place the next windows */
12380 compose_force_window_origin = FALSE;
12381 for (; cur; cur = cur->next) {
12382 GSList tmplist;
12383 tmplist.data = cur->data;
12384 tmplist.next = NULL;
12385 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12387 compose_force_window_origin = TRUE;
12388 } else {
12389 /* forwarding multiple mails as attachments is done via a
12390 * single compose window */
12391 if (msginfo_list != NULL) {
12392 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12393 } else if (msgview != NULL) {
12394 GSList tmplist;
12395 tmplist.data = msgview->msginfo;
12396 tmplist.next = NULL;
12397 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12398 } else {
12399 debug_print("Nothing to reply to\n");
12404 void compose_check_for_email_account(Compose *compose)
12406 PrefsAccount *ac = NULL, *curr = NULL;
12407 GList *list;
12409 if (!compose)
12410 return;
12412 if (compose->account && compose->account->protocol == A_NNTP) {
12413 ac = account_get_cur_account();
12414 if (ac->protocol == A_NNTP) {
12415 list = account_get_list();
12417 for( ; list != NULL ; list = g_list_next(list)) {
12418 curr = (PrefsAccount *) list->data;
12419 if (curr->protocol != A_NNTP) {
12420 ac = curr;
12421 break;
12425 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12426 ac->account_id);
12430 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12431 const gchar *address)
12433 GSList *msginfo_list = NULL;
12434 gchar *body = messageview_get_selection(msgview);
12435 Compose *compose;
12437 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12439 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12440 compose_check_for_email_account(compose);
12441 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12442 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12443 compose_reply_set_subject(compose, msginfo);
12445 g_free(body);
12446 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12449 void compose_set_position(Compose *compose, gint pos)
12451 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12453 gtkut_text_view_set_position(text, pos);
12456 gboolean compose_search_string(Compose *compose,
12457 const gchar *str, gboolean case_sens)
12459 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12461 return gtkut_text_view_search_string(text, str, case_sens);
12464 gboolean compose_search_string_backward(Compose *compose,
12465 const gchar *str, gboolean case_sens)
12467 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12469 return gtkut_text_view_search_string_backward(text, str, case_sens);
12472 /* allocate a msginfo structure and populate its data from a compose data structure */
12473 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12475 MsgInfo *newmsginfo;
12476 GSList *list;
12477 gchar date[RFC822_DATE_BUFFSIZE];
12479 cm_return_val_if_fail( compose != NULL, NULL );
12481 newmsginfo = procmsg_msginfo_new();
12483 /* date is now */
12484 get_rfc822_date(date, sizeof(date));
12485 newmsginfo->date = g_strdup(date);
12487 /* from */
12488 if (compose->from_name) {
12489 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12490 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12493 /* subject */
12494 if (compose->subject_entry)
12495 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12497 /* to, cc, reply-to, newsgroups */
12498 for (list = compose->header_list; list; list = list->next) {
12499 gchar *header = gtk_editable_get_chars(
12500 GTK_EDITABLE(
12501 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12502 gchar *entry = gtk_editable_get_chars(
12503 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12505 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12506 if ( newmsginfo->to == NULL ) {
12507 newmsginfo->to = g_strdup(entry);
12508 } else if (entry && *entry) {
12509 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12510 g_free(newmsginfo->to);
12511 newmsginfo->to = tmp;
12513 } else
12514 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12515 if ( newmsginfo->cc == NULL ) {
12516 newmsginfo->cc = g_strdup(entry);
12517 } else if (entry && *entry) {
12518 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12519 g_free(newmsginfo->cc);
12520 newmsginfo->cc = tmp;
12522 } else
12523 if ( strcasecmp(header,
12524 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12525 if ( newmsginfo->newsgroups == NULL ) {
12526 newmsginfo->newsgroups = g_strdup(entry);
12527 } else if (entry && *entry) {
12528 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12529 g_free(newmsginfo->newsgroups);
12530 newmsginfo->newsgroups = tmp;
12534 g_free(header);
12535 g_free(entry);
12538 /* other data is unset */
12540 return newmsginfo;
12543 #ifdef USE_ENCHANT
12544 /* update compose's dictionaries from folder dict settings */
12545 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12546 FolderItem *folder_item)
12548 cm_return_if_fail(compose != NULL);
12550 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12551 FolderItemPrefs *prefs = folder_item->prefs;
12553 if (prefs->enable_default_dictionary)
12554 gtkaspell_change_dict(compose->gtkaspell,
12555 prefs->default_dictionary, FALSE);
12556 if (folder_item->prefs->enable_default_alt_dictionary)
12557 gtkaspell_change_alt_dict(compose->gtkaspell,
12558 prefs->default_alt_dictionary);
12559 if (prefs->enable_default_dictionary
12560 || prefs->enable_default_alt_dictionary)
12561 compose_spell_menu_changed(compose);
12564 #endif
12566 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12568 Compose *compose = (Compose *)data;
12570 cm_return_if_fail(compose != NULL);
12572 gtk_widget_grab_focus(compose->text);
12575 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12577 gtk_combo_box_popup(GTK_COMBO_BOX(data));
12582 * End of Source.