fix typo (thanks to Charles A Edwards)
[claws.git] / src / compose.c
blobcfdda3fda6bff7eb291e3f5e8e16f7d6b442ddbf
1 /*
2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2013 Hiroyuki Yamamoto and the Claws Mail team
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/>.
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #include "claws-features.h"
23 #endif
25 #include "defs.h"
27 #ifndef PANGO_ENABLE_ENGINE
28 # define PANGO_ENABLE_ENGINE
29 #endif
31 #include <glib.h>
32 #include <glib/gi18n.h>
33 #include <gdk/gdkkeysyms.h>
34 #include <gtk/gtk.h>
36 #include <pango/pango-break.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <ctype.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <unistd.h>
44 #include <time.h>
45 #include <stdlib.h>
46 #if HAVE_SYS_WAIT_H
47 # include <sys/wait.h>
48 #endif
49 #include <signal.h>
50 #include <errno.h>
51 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
52 #include <libgen.h>
53 #endif
55 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
56 # include <wchar.h>
57 # include <wctype.h>
58 #endif
60 #include "claws.h"
61 #include "main.h"
62 #include "mainwindow.h"
63 #include "compose.h"
64 #ifndef USE_NEW_ADDRBOOK
65 #include "addressbook.h"
66 #else
67 #include "addressbook-dbus.h"
68 #include "addressadd.h"
69 #endif
70 #include "folderview.h"
71 #include "procmsg.h"
72 #include "menu.h"
73 #include "stock_pixmap.h"
74 #include "send_message.h"
75 #include "imap.h"
76 #include "news.h"
77 #include "customheader.h"
78 #include "prefs_common.h"
79 #include "prefs_account.h"
80 #include "action.h"
81 #include "account.h"
82 #include "filesel.h"
83 #include "procheader.h"
84 #include "procmime.h"
85 #include "statusbar.h"
86 #include "about.h"
87 #include "base64.h"
88 #include "quoted-printable.h"
89 #include "codeconv.h"
90 #include "utils.h"
91 #include "gtkutils.h"
92 #include "gtkshruler.h"
93 #include "socket.h"
94 #include "alertpanel.h"
95 #include "manage_window.h"
96 #include "folder.h"
97 #include "folder_item_prefs.h"
98 #include "addr_compl.h"
99 #include "quote_fmt.h"
100 #include "undo.h"
101 #include "foldersel.h"
102 #include "toolbar.h"
103 #include "inc.h"
104 #include "message_search.h"
105 #include "combobox.h"
106 #include "hooks.h"
107 #include "privacy.h"
108 #include "timing.h"
109 #include "autofaces.h"
110 #include "spell_entry.h"
112 enum
114 COL_MIMETYPE = 0,
115 COL_SIZE = 1,
116 COL_NAME = 2,
117 COL_CHARSET = 3,
118 COL_DATA = 4,
119 COL_AUTODATA = 5,
120 N_COL_COLUMNS
123 #define N_ATTACH_COLS (N_COL_COLUMNS)
125 typedef enum
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
141 } ComposeCallAdvancedAction;
143 typedef enum
145 PRIORITY_HIGHEST = 1,
146 PRIORITY_HIGH,
147 PRIORITY_NORMAL,
148 PRIORITY_LOW,
149 PRIORITY_LOWEST
150 } PriorityLevel;
152 typedef enum
154 COMPOSE_INSERT_SUCCESS,
155 COMPOSE_INSERT_READ_ERROR,
156 COMPOSE_INSERT_INVALID_CHARACTER,
157 COMPOSE_INSERT_NO_FILE
158 } ComposeInsertResult;
160 typedef enum
162 COMPOSE_WRITE_FOR_SEND,
163 COMPOSE_WRITE_FOR_STORE
164 } ComposeWriteType;
166 typedef enum
168 COMPOSE_QUOTE_FORCED,
169 COMPOSE_QUOTE_CHECK,
170 COMPOSE_QUOTE_SKIP
171 } ComposeQuoteMode;
173 typedef enum {
174 TO_FIELD_PRESENT,
175 SUBJECT_FIELD_PRESENT,
176 BODY_FIELD_PRESENT,
177 NO_FIELD_PRESENT
178 } MailField;
180 #define B64_LINE_SIZE 57
181 #define B64_BUFFSIZE 77
183 #define MAX_REFERENCES_LEN 999
185 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
186 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
188 static GList *compose_list = NULL;
189 static GSList *extra_headers = NULL;
191 static Compose *compose_generic_new (PrefsAccount *account,
192 const gchar *to,
193 FolderItem *item,
194 GList *attach_files,
195 GList *listAddress );
197 static Compose *compose_create (PrefsAccount *account,
198 FolderItem *item,
199 ComposeMode mode,
200 gboolean batch);
202 static void compose_entry_mark_default_to (Compose *compose,
203 const gchar *address);
204 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
205 ComposeQuoteMode quote_mode,
206 gboolean to_all,
207 gboolean to_sender,
208 const gchar *body);
209 static Compose *compose_forward_multiple (PrefsAccount *account,
210 GSList *msginfo_list);
211 static Compose *compose_reply (MsgInfo *msginfo,
212 ComposeQuoteMode quote_mode,
213 gboolean to_all,
214 gboolean to_ml,
215 gboolean to_sender,
216 const gchar *body);
217 static Compose *compose_reply_mode (ComposeMode mode,
218 GSList *msginfo_list,
219 gchar *body);
220 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
221 static void compose_update_privacy_systems_menu(Compose *compose);
223 static GtkWidget *compose_account_option_menu_create
224 (Compose *compose);
225 static void compose_set_out_encoding (Compose *compose);
226 static void compose_set_template_menu (Compose *compose);
227 static void compose_destroy (Compose *compose);
229 static MailField compose_entries_set (Compose *compose,
230 const gchar *mailto,
231 ComposeEntryType to_type);
232 static gint compose_parse_header (Compose *compose,
233 MsgInfo *msginfo);
234 static gint compose_parse_manual_headers (Compose *compose,
235 MsgInfo *msginfo,
236 HeaderEntry *entries);
237 static gchar *compose_parse_references (const gchar *ref,
238 const gchar *msgid);
240 static gchar *compose_quote_fmt (Compose *compose,
241 MsgInfo *msginfo,
242 const gchar *fmt,
243 const gchar *qmark,
244 const gchar *body,
245 gboolean rewrap,
246 gboolean need_unescape,
247 const gchar *err_msg);
249 static void compose_reply_set_entry (Compose *compose,
250 MsgInfo *msginfo,
251 gboolean to_all,
252 gboolean to_ml,
253 gboolean to_sender,
254 gboolean
255 followup_and_reply_to);
256 static void compose_reedit_set_entry (Compose *compose,
257 MsgInfo *msginfo);
259 static void compose_insert_sig (Compose *compose,
260 gboolean replace);
261 static ComposeInsertResult compose_insert_file (Compose *compose,
262 const gchar *file);
264 static gboolean compose_attach_append (Compose *compose,
265 const gchar *file,
266 const gchar *type,
267 const gchar *content_type,
268 const gchar *charset);
269 static void compose_attach_parts (Compose *compose,
270 MsgInfo *msginfo);
272 static gboolean compose_beautify_paragraph (Compose *compose,
273 GtkTextIter *par_iter,
274 gboolean force);
275 static void compose_wrap_all (Compose *compose);
276 static void compose_wrap_all_full (Compose *compose,
277 gboolean autowrap);
279 static void compose_set_title (Compose *compose);
280 static void compose_select_account (Compose *compose,
281 PrefsAccount *account,
282 gboolean init);
284 static PrefsAccount *compose_current_mail_account(void);
285 /* static gint compose_send (Compose *compose); */
286 static gboolean compose_check_for_valid_recipient
287 (Compose *compose);
288 static gboolean compose_check_entries (Compose *compose,
289 gboolean check_everything);
290 static gint compose_write_to_file (Compose *compose,
291 FILE *fp,
292 gint action,
293 gboolean attach_parts);
294 static gint compose_write_body_to_file (Compose *compose,
295 const gchar *file);
296 static gint compose_remove_reedit_target (Compose *compose,
297 gboolean force);
298 static void compose_remove_draft (Compose *compose);
299 static gint compose_queue_sub (Compose *compose,
300 gint *msgnum,
301 FolderItem **item,
302 gchar **msgpath,
303 gboolean check_subject,
304 gboolean remove_reedit_target);
305 static int compose_add_attachments (Compose *compose,
306 MimeInfo *parent);
307 static gchar *compose_get_header (Compose *compose);
308 static gchar *compose_get_manual_headers_info (Compose *compose);
310 static void compose_convert_header (Compose *compose,
311 gchar *dest,
312 gint len,
313 gchar *src,
314 gint header_len,
315 gboolean addr_field);
317 static void compose_attach_info_free (AttachInfo *ainfo);
318 static void compose_attach_remove_selected (GtkAction *action,
319 gpointer data);
321 static void compose_template_apply (Compose *compose,
322 Template *tmpl,
323 gboolean replace);
324 static void compose_attach_property (GtkAction *action,
325 gpointer data);
326 static void compose_attach_property_create (gboolean *cancelled);
327 static void attach_property_ok (GtkWidget *widget,
328 gboolean *cancelled);
329 static void attach_property_cancel (GtkWidget *widget,
330 gboolean *cancelled);
331 static gint attach_property_delete_event (GtkWidget *widget,
332 GdkEventAny *event,
333 gboolean *cancelled);
334 static gboolean attach_property_key_pressed (GtkWidget *widget,
335 GdkEventKey *event,
336 gboolean *cancelled);
338 static void compose_exec_ext_editor (Compose *compose);
339 #ifdef G_OS_UNIX
340 static gint compose_exec_ext_editor_real (const gchar *file);
341 static gboolean compose_ext_editor_kill (Compose *compose);
342 static gboolean compose_input_cb (GIOChannel *source,
343 GIOCondition condition,
344 gpointer data);
345 static void compose_set_ext_editor_sensitive (Compose *compose,
346 gboolean sensitive);
347 #endif /* G_OS_UNIX */
349 static void compose_undo_state_changed (UndoMain *undostruct,
350 gint undo_state,
351 gint redo_state,
352 gpointer data);
354 static void compose_create_header_entry (Compose *compose);
355 static void compose_add_header_entry (Compose *compose, const gchar *header,
356 gchar *text, ComposePrefType pref_type);
357 static void compose_remove_header_entries(Compose *compose);
359 static void compose_update_priority_menu_item(Compose * compose);
360 #if USE_ENCHANT
361 static void compose_spell_menu_changed (void *data);
362 static void compose_dict_changed (void *data);
363 #endif
364 static void compose_add_field_list ( Compose *compose,
365 GList *listAddress );
367 /* callback functions */
369 static void compose_notebook_size_alloc (GtkNotebook *notebook,
370 GtkAllocation *allocation,
371 GtkPaned *paned);
372 static gboolean compose_edit_size_alloc (GtkEditable *widget,
373 GtkAllocation *allocation,
374 GtkSHRuler *shruler);
375 static void account_activated (GtkComboBox *optmenu,
376 gpointer data);
377 static void attach_selected (GtkTreeView *tree_view,
378 GtkTreePath *tree_path,
379 GtkTreeViewColumn *column,
380 Compose *compose);
381 static gboolean attach_button_pressed (GtkWidget *widget,
382 GdkEventButton *event,
383 gpointer data);
384 static gboolean attach_key_pressed (GtkWidget *widget,
385 GdkEventKey *event,
386 gpointer data);
387 static void compose_send_cb (GtkAction *action, gpointer data);
388 static void compose_send_later_cb (GtkAction *action, gpointer data);
390 static void compose_save_cb (GtkAction *action,
391 gpointer data);
393 static void compose_attach_cb (GtkAction *action,
394 gpointer data);
395 static void compose_insert_file_cb (GtkAction *action,
396 gpointer data);
397 static void compose_insert_sig_cb (GtkAction *action,
398 gpointer data);
399 static void compose_replace_sig_cb (GtkAction *action,
400 gpointer data);
402 static void compose_close_cb (GtkAction *action,
403 gpointer data);
404 static void compose_print_cb (GtkAction *action,
405 gpointer data);
407 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
409 static void compose_address_cb (GtkAction *action,
410 gpointer data);
411 static void about_show_cb (GtkAction *action,
412 gpointer data);
413 static void compose_template_activate_cb(GtkWidget *widget,
414 gpointer data);
416 static void compose_ext_editor_cb (GtkAction *action,
417 gpointer data);
419 static gint compose_delete_cb (GtkWidget *widget,
420 GdkEventAny *event,
421 gpointer data);
423 static void compose_undo_cb (GtkAction *action,
424 gpointer data);
425 static void compose_redo_cb (GtkAction *action,
426 gpointer data);
427 static void compose_cut_cb (GtkAction *action,
428 gpointer data);
429 static void compose_copy_cb (GtkAction *action,
430 gpointer data);
431 static void compose_paste_cb (GtkAction *action,
432 gpointer data);
433 static void compose_paste_as_quote_cb (GtkAction *action,
434 gpointer data);
435 static void compose_paste_no_wrap_cb (GtkAction *action,
436 gpointer data);
437 static void compose_paste_wrap_cb (GtkAction *action,
438 gpointer data);
439 static void compose_allsel_cb (GtkAction *action,
440 gpointer data);
442 static void compose_advanced_action_cb (GtkAction *action,
443 gpointer data);
445 static void compose_grab_focus_cb (GtkWidget *widget,
446 Compose *compose);
448 static void compose_changed_cb (GtkTextBuffer *textbuf,
449 Compose *compose);
451 static void compose_wrap_cb (GtkAction *action,
452 gpointer data);
453 static void compose_wrap_all_cb (GtkAction *action,
454 gpointer data);
455 static void compose_find_cb (GtkAction *action,
456 gpointer data);
457 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
458 gpointer data);
459 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
460 gpointer data);
462 static void compose_toggle_ruler_cb (GtkToggleAction *action,
463 gpointer data);
464 static void compose_toggle_sign_cb (GtkToggleAction *action,
465 gpointer data);
466 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
467 gpointer data);
468 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
469 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
470 static void activate_privacy_system (Compose *compose,
471 PrefsAccount *account,
472 gboolean warn);
473 static void compose_use_signing(Compose *compose, gboolean use_signing);
474 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
475 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
476 gpointer data);
477 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
478 gpointer data);
479 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
480 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
481 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
483 static void compose_attach_drag_received_cb (GtkWidget *widget,
484 GdkDragContext *drag_context,
485 gint x,
486 gint y,
487 GtkSelectionData *data,
488 guint info,
489 guint time,
490 gpointer user_data);
491 static void compose_insert_drag_received_cb (GtkWidget *widget,
492 GdkDragContext *drag_context,
493 gint x,
494 gint y,
495 GtkSelectionData *data,
496 guint info,
497 guint time,
498 gpointer user_data);
499 static void compose_header_drag_received_cb (GtkWidget *widget,
500 GdkDragContext *drag_context,
501 gint x,
502 gint y,
503 GtkSelectionData *data,
504 guint info,
505 guint time,
506 gpointer user_data);
508 static gboolean compose_drag_drop (GtkWidget *widget,
509 GdkDragContext *drag_context,
510 gint x, gint y,
511 guint time, gpointer user_data);
512 static gboolean completion_set_focus_to_subject
513 (GtkWidget *widget,
514 GdkEventKey *event,
515 Compose *user_data);
517 static void text_inserted (GtkTextBuffer *buffer,
518 GtkTextIter *iter,
519 const gchar *text,
520 gint len,
521 Compose *compose);
522 static Compose *compose_generic_reply(MsgInfo *msginfo,
523 ComposeQuoteMode quote_mode,
524 gboolean to_all,
525 gboolean to_ml,
526 gboolean to_sender,
527 gboolean followup_and_reply_to,
528 const gchar *body);
530 static void compose_headerentry_changed_cb (GtkWidget *entry,
531 ComposeHeaderEntry *headerentry);
532 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
533 GdkEventKey *event,
534 ComposeHeaderEntry *headerentry);
535 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
536 ComposeHeaderEntry *headerentry);
538 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
540 static void compose_allow_user_actions (Compose *compose, gboolean allow);
542 static void compose_nothing_cb (GtkAction *action, gpointer data)
547 #if USE_ENCHANT
548 static void compose_check_all (GtkAction *action, gpointer data);
549 static void compose_highlight_all (GtkAction *action, gpointer data);
550 static void compose_check_backwards (GtkAction *action, gpointer data);
551 static void compose_check_forwards_go (GtkAction *action, gpointer data);
552 #endif
554 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
556 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
558 #ifdef USE_ENCHANT
559 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
560 FolderItem *folder_item);
561 #endif
562 static void compose_attach_update_label(Compose *compose);
563 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
564 gboolean respect_default_to);
565 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
567 static GtkActionEntry compose_popup_entries[] =
569 {"Compose", NULL, "Compose" },
570 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
571 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
572 {"Compose/---", NULL, "---", NULL, NULL, NULL },
573 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
576 static GtkActionEntry compose_entries[] =
578 {"Menu", NULL, "Menu" },
579 /* menus */
580 {"Message", NULL, N_("_Message") },
581 {"Edit", NULL, N_("_Edit") },
582 #if USE_ENCHANT
583 {"Spelling", NULL, N_("_Spelling") },
584 #endif
585 {"Options", NULL, N_("_Options") },
586 {"Tools", NULL, N_("_Tools") },
587 {"Help", NULL, N_("_Help") },
588 /* Message menu */
589 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
590 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
591 {"Message/---", NULL, "---" },
593 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
594 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
595 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
596 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
597 /* {"Message/---", NULL, "---" }, */
598 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
599 /* {"Message/---", NULL, "---" }, */
600 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
601 /* {"Message/---", NULL, "---" }, */
602 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
604 /* Edit menu */
605 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
606 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
607 {"Edit/---", NULL, "---" },
609 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
610 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
611 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
613 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
614 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
615 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
616 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
618 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
620 {"Edit/Advanced", NULL, N_("A_dvanced") },
621 {"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*/
622 {"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*/
623 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
624 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
625 {"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*/
626 {"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*/
627 {"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*/
628 {"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*/
629 {"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*/
630 {"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*/
631 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
632 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
633 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
634 {"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*/
636 /* {"Edit/---", NULL, "---" }, */
637 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
639 /* {"Edit/---", NULL, "---" }, */
640 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
641 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
642 /* {"Edit/---", NULL, "---" }, */
643 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
644 #if USE_ENCHANT
645 /* Spelling menu */
646 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
647 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
648 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
649 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
651 {"Spelling/---", NULL, "---" },
652 {"Spelling/Options", NULL, N_("_Options") },
653 #endif
655 /* Options menu */
657 {"Options/ReplyMode", NULL, N_("Reply _mode") },
658 {"Options/---", NULL, "---" },
659 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
660 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
662 /* {"Options/---", NULL, "---" }, */
664 {"Options/Priority", NULL, N_("_Priority") },
666 {"Options/Encoding", NULL, N_("Character _encoding") },
667 {"Options/Encoding/---", NULL, "---" },
668 #define ENC_ACTION(cs_char,c_char,string) \
669 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
671 {"Options/Encoding/Western", NULL, N_("Western European") },
672 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
673 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
674 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
675 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
676 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
677 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
678 {"Options/Encoding/Korean", NULL, N_("Korean") },
679 {"Options/Encoding/Thai", NULL, N_("Thai") },
681 /* Tools menu */
682 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
684 {"Tools/Template", NULL, N_("_Template") },
685 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
686 {"Tools/Actions", NULL, N_("Actio_ns") },
687 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
689 /* Help menu */
690 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
693 static GtkToggleActionEntry compose_toggle_entries[] =
695 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
696 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
697 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
698 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
699 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
700 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
701 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
704 static GtkRadioActionEntry compose_radio_rm_entries[] =
706 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
707 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
708 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
709 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
712 static GtkRadioActionEntry compose_radio_prio_entries[] =
714 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
715 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
716 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
717 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
718 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
721 static GtkRadioActionEntry compose_radio_enc_entries[] =
723 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
724 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
725 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
726 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
727 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
728 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
729 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
757 static GtkTargetEntry compose_mime_types[] =
759 {"text/uri-list", 0, 0},
760 {"UTF8_STRING", 0, 0},
761 {"text/plain", 0, 0}
764 static gboolean compose_put_existing_to_front(MsgInfo *info)
766 const GList *compose_list = compose_get_compose_list();
767 const GList *elem = NULL;
769 if (compose_list) {
770 for (elem = compose_list; elem != NULL && elem->data != NULL;
771 elem = elem->next) {
772 Compose *c = (Compose*)elem->data;
774 if (!c->targetinfo || !c->targetinfo->msgid ||
775 !info->msgid)
776 continue;
778 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
779 gtkut_window_popup(c->window);
780 return TRUE;
784 return FALSE;
787 static GdkColor quote_color1 =
788 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
789 static GdkColor quote_color2 =
790 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
791 static GdkColor quote_color3 =
792 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
794 static GdkColor quote_bgcolor1 =
795 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
796 static GdkColor quote_bgcolor2 =
797 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
798 static GdkColor quote_bgcolor3 =
799 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
801 static GdkColor signature_color = {
802 (gulong)0,
803 (gushort)0x7fff,
804 (gushort)0x7fff,
805 (gushort)0x7fff
808 static GdkColor uri_color = {
809 (gulong)0,
810 (gushort)0,
811 (gushort)0,
812 (gushort)0
815 static void compose_create_tags(GtkTextView *text, Compose *compose)
817 GtkTextBuffer *buffer;
818 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
819 #if !GTK_CHECK_VERSION(2, 24, 0)
820 GdkColormap *cmap;
821 gboolean success[8];
822 int i;
823 GdkColor color[8];
824 #endif
826 buffer = gtk_text_view_get_buffer(text);
828 if (prefs_common.enable_color) {
829 /* grab the quote colors, converting from an int to a GdkColor */
830 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
831 &quote_color1);
832 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
833 &quote_color2);
834 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
835 &quote_color3);
836 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
837 &quote_bgcolor1);
838 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
839 &quote_bgcolor2);
840 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
841 &quote_bgcolor3);
842 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
843 &signature_color);
844 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
845 &uri_color);
846 } else {
847 signature_color = quote_color1 = quote_color2 = quote_color3 =
848 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
851 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
852 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
853 "foreground-gdk", &quote_color1,
854 "paragraph-background-gdk", &quote_bgcolor1,
855 NULL);
856 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
857 "foreground-gdk", &quote_color2,
858 "paragraph-background-gdk", &quote_bgcolor2,
859 NULL);
860 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
861 "foreground-gdk", &quote_color3,
862 "paragraph-background-gdk", &quote_bgcolor3,
863 NULL);
864 } else {
865 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
866 "foreground-gdk", &quote_color1,
867 NULL);
868 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
869 "foreground-gdk", &quote_color2,
870 NULL);
871 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
872 "foreground-gdk", &quote_color3,
873 NULL);
876 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
877 "foreground-gdk", &signature_color,
878 NULL);
880 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
881 "foreground-gdk", &uri_color,
882 NULL);
883 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
884 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
886 #if !GTK_CHECK_VERSION(2, 24, 0)
887 color[0] = quote_color1;
888 color[1] = quote_color2;
889 color[2] = quote_color3;
890 color[3] = quote_bgcolor1;
891 color[4] = quote_bgcolor2;
892 color[5] = quote_bgcolor3;
893 color[6] = signature_color;
894 color[7] = uri_color;
896 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
897 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
899 for (i = 0; i < 8; i++) {
900 if (success[i] == FALSE) {
901 g_warning("Compose: color allocation failed.\n");
902 quote_color1 = quote_color2 = quote_color3 =
903 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
904 signature_color = uri_color = black;
907 #endif
910 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
911 GList *attach_files)
913 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
916 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
918 return compose_generic_new(account, mailto, item, NULL, NULL);
921 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
923 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
926 #define SCROLL_TO_CURSOR(compose) { \
927 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
928 gtk_text_view_get_buffer( \
929 GTK_TEXT_VIEW(compose->text))); \
930 gtk_text_view_scroll_mark_onscreen( \
931 GTK_TEXT_VIEW(compose->text), \
932 cmark); \
935 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
937 GtkEditable *entry;
938 if (folderidentifier) {
939 #if !GTK_CHECK_VERSION(2, 24, 0)
940 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
941 #else
942 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
943 #endif
944 prefs_common.compose_save_to_history = add_history(
945 prefs_common.compose_save_to_history, folderidentifier);
946 #if !GTK_CHECK_VERSION(2, 24, 0)
947 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
948 prefs_common.compose_save_to_history);
949 #else
950 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
951 prefs_common.compose_save_to_history);
952 #endif
955 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
956 if (folderidentifier)
957 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
958 else
959 gtk_entry_set_text(GTK_ENTRY(entry), "");
962 static gchar *compose_get_save_to(Compose *compose)
964 GtkEditable *entry;
965 gchar *result = NULL;
966 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
967 result = gtk_editable_get_chars(entry, 0, -1);
969 if (result) {
970 #if !GTK_CHECK_VERSION(2, 24, 0)
971 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
972 #else
973 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
974 #endif
975 prefs_common.compose_save_to_history = add_history(
976 prefs_common.compose_save_to_history, result);
977 #if !GTK_CHECK_VERSION(2, 24, 0)
978 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
979 prefs_common.compose_save_to_history);
980 #else
981 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
982 prefs_common.compose_save_to_history);
983 #endif
985 return result;
988 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
989 GList *attach_files, GList *listAddress )
991 Compose *compose;
992 GtkTextView *textview;
993 GtkTextBuffer *textbuf;
994 GtkTextIter iter;
995 const gchar *subject_format = NULL;
996 const gchar *body_format = NULL;
997 gchar *mailto_from = NULL;
998 PrefsAccount *mailto_account = NULL;
999 MsgInfo* dummyinfo = NULL;
1000 gint cursor_pos = -1;
1001 MailField mfield = NO_FIELD_PRESENT;
1002 gchar* buf;
1003 GtkTextMark *mark;
1005 /* check if mailto defines a from */
1006 if (mailto && *mailto != '\0') {
1007 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1008 /* mailto defines a from, check if we can get account prefs from it,
1009 if not, the account prefs will be guessed using other ways, but we'll keep
1010 the from anyway */
1011 if (mailto_from) {
1012 mailto_account = account_find_from_address(mailto_from, TRUE);
1013 if (mailto_account == NULL) {
1014 gchar *tmp_from;
1015 Xstrdup_a(tmp_from, mailto_from, return NULL);
1016 extract_address(tmp_from);
1017 mailto_account = account_find_from_address(tmp_from, TRUE);
1020 if (mailto_account)
1021 account = mailto_account;
1024 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1025 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1026 account = account_find_from_id(item->prefs->default_account);
1028 /* if no account prefs set, fallback to the current one */
1029 if (!account) account = cur_account;
1030 cm_return_val_if_fail(account != NULL, NULL);
1032 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1034 /* override from name if mailto asked for it */
1035 if (mailto_from) {
1036 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1037 g_free(mailto_from);
1038 } else
1039 /* override from name according to folder properties */
1040 if (item && item->prefs &&
1041 item->prefs->compose_with_format &&
1042 item->prefs->compose_override_from_format &&
1043 *item->prefs->compose_override_from_format != '\0') {
1045 gchar *tmp = NULL;
1046 gchar *buf = NULL;
1048 dummyinfo = compose_msginfo_new_from_compose(compose);
1050 /* decode \-escape sequences in the internal representation of the quote format */
1051 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1052 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1054 #ifdef USE_ENCHANT
1055 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1056 compose->gtkaspell);
1057 #else
1058 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1059 #endif
1060 quote_fmt_scan_string(tmp);
1061 quote_fmt_parse();
1063 buf = quote_fmt_get_buffer();
1064 if (buf == NULL)
1065 alertpanel_error(_("New message From format error."));
1066 else
1067 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1068 quote_fmt_reset_vartable();
1070 g_free(tmp);
1073 compose->replyinfo = NULL;
1074 compose->fwdinfo = NULL;
1076 textview = GTK_TEXT_VIEW(compose->text);
1077 textbuf = gtk_text_view_get_buffer(textview);
1078 compose_create_tags(textview, compose);
1080 undo_block(compose->undostruct);
1081 #ifdef USE_ENCHANT
1082 compose_set_dictionaries_from_folder_prefs(compose, item);
1083 #endif
1085 if (account->auto_sig)
1086 compose_insert_sig(compose, FALSE);
1087 gtk_text_buffer_get_start_iter(textbuf, &iter);
1088 gtk_text_buffer_place_cursor(textbuf, &iter);
1090 if (account->protocol != A_NNTP) {
1091 if (mailto && *mailto != '\0') {
1092 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1094 } else {
1095 compose_set_folder_prefs(compose, item, TRUE);
1097 if (item && item->ret_rcpt) {
1098 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1100 } else {
1101 if (mailto && *mailto != '\0') {
1102 if (!strchr(mailto, '@'))
1103 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1104 else
1105 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1106 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1107 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1108 mfield = TO_FIELD_PRESENT;
1111 * CLAWS: just don't allow return receipt request, even if the user
1112 * may want to send an email. simple but foolproof.
1114 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1116 compose_add_field_list( compose, listAddress );
1118 if (item && item->prefs && item->prefs->compose_with_format) {
1119 subject_format = item->prefs->compose_subject_format;
1120 body_format = item->prefs->compose_body_format;
1121 } else if (account->compose_with_format) {
1122 subject_format = account->compose_subject_format;
1123 body_format = account->compose_body_format;
1124 } else if (prefs_common.compose_with_format) {
1125 subject_format = prefs_common.compose_subject_format;
1126 body_format = prefs_common.compose_body_format;
1129 if (subject_format || body_format) {
1131 if ( subject_format
1132 && *subject_format != '\0' )
1134 gchar *subject = NULL;
1135 gchar *tmp = NULL;
1136 gchar *buf = NULL;
1138 if (!dummyinfo)
1139 dummyinfo = compose_msginfo_new_from_compose(compose);
1141 /* decode \-escape sequences in the internal representation of the quote format */
1142 tmp = g_malloc(strlen(subject_format)+1);
1143 pref_get_unescaped_pref(tmp, subject_format);
1145 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1146 #ifdef USE_ENCHANT
1147 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1148 compose->gtkaspell);
1149 #else
1150 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1151 #endif
1152 quote_fmt_scan_string(tmp);
1153 quote_fmt_parse();
1155 buf = quote_fmt_get_buffer();
1156 if (buf == NULL)
1157 alertpanel_error(_("New message subject format error."));
1158 else
1159 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1160 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1161 quote_fmt_reset_vartable();
1163 g_free(subject);
1164 g_free(tmp);
1165 mfield = SUBJECT_FIELD_PRESENT;
1168 if ( body_format
1169 && *body_format != '\0' )
1171 GtkTextView *text;
1172 GtkTextBuffer *buffer;
1173 GtkTextIter start, end;
1174 gchar *tmp = NULL;
1176 if (!dummyinfo)
1177 dummyinfo = compose_msginfo_new_from_compose(compose);
1179 text = GTK_TEXT_VIEW(compose->text);
1180 buffer = gtk_text_view_get_buffer(text);
1181 gtk_text_buffer_get_start_iter(buffer, &start);
1182 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1183 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1185 compose_quote_fmt(compose, dummyinfo,
1186 body_format,
1187 NULL, tmp, FALSE, TRUE,
1188 _("The body of the \"New message\" template has an error at line %d."));
1189 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1190 quote_fmt_reset_vartable();
1192 g_free(tmp);
1193 #ifdef USE_ENCHANT
1194 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1195 gtkaspell_highlight_all(compose->gtkaspell);
1196 #endif
1197 mfield = BODY_FIELD_PRESENT;
1201 procmsg_msginfo_free( dummyinfo );
1203 if (attach_files) {
1204 GList *curr;
1205 AttachInfo *ainfo;
1207 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1208 ainfo = (AttachInfo *) curr->data;
1209 compose_attach_append(compose, ainfo->file, ainfo->file,
1210 ainfo->content_type, ainfo->charset);
1214 compose_show_first_last_header(compose, TRUE);
1216 /* Set save folder */
1217 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1218 gchar *folderidentifier;
1220 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1221 folderidentifier = folder_item_get_identifier(item);
1222 compose_set_save_to(compose, folderidentifier);
1223 g_free(folderidentifier);
1226 /* Place cursor according to provided input (mfield) */
1227 switch (mfield) {
1228 case NO_FIELD_PRESENT:
1229 if (compose->header_last)
1230 gtk_widget_grab_focus(compose->header_last->entry);
1231 break;
1232 case TO_FIELD_PRESENT:
1233 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1234 if (buf) {
1235 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1236 g_free(buf);
1238 gtk_widget_grab_focus(compose->subject_entry);
1239 break;
1240 case SUBJECT_FIELD_PRESENT:
1241 textview = GTK_TEXT_VIEW(compose->text);
1242 if (!textview)
1243 break;
1244 textbuf = gtk_text_view_get_buffer(textview);
1245 if (!textbuf)
1246 break;
1247 mark = gtk_text_buffer_get_insert(textbuf);
1248 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1249 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1251 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1252 * only defers where it comes to the variable body
1253 * is not null. If no body is present compose->text
1254 * will be null in which case you cannot place the
1255 * cursor inside the component so. An empty component
1256 * is therefore created before placing the cursor
1258 case BODY_FIELD_PRESENT:
1259 cursor_pos = quote_fmt_get_cursor_pos();
1260 if (cursor_pos == -1)
1261 gtk_widget_grab_focus(compose->header_last->entry);
1262 else
1263 gtk_widget_grab_focus(compose->text);
1264 break;
1267 undo_unblock(compose->undostruct);
1269 if (prefs_common.auto_exteditor)
1270 compose_exec_ext_editor(compose);
1272 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1274 SCROLL_TO_CURSOR(compose);
1276 compose->modified = FALSE;
1277 compose_set_title(compose);
1279 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1281 return compose;
1284 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1285 gboolean override_pref, const gchar *system)
1287 const gchar *privacy = NULL;
1289 cm_return_if_fail(compose != NULL);
1290 cm_return_if_fail(account != NULL);
1292 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1293 return;
1295 if (account->default_privacy_system && strlen(account->default_privacy_system))
1296 privacy = account->default_privacy_system;
1297 else if (system)
1298 privacy = system;
1299 else {
1300 GSList *privacy_avail = privacy_get_system_ids();
1301 if (privacy_avail && g_slist_length(privacy_avail)) {
1302 privacy = (gchar *)(privacy_avail->data);
1305 if (privacy != NULL) {
1306 if (system) {
1307 g_free(compose->privacy_system);
1308 compose->privacy_system = NULL;
1310 if (compose->privacy_system == NULL)
1311 compose->privacy_system = g_strdup(privacy);
1312 else if (*(compose->privacy_system) == '\0') {
1313 g_free(compose->privacy_system);
1314 compose->privacy_system = g_strdup(privacy);
1316 compose_update_privacy_system_menu_item(compose, FALSE);
1317 compose_use_encryption(compose, TRUE);
1321 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1323 const gchar *privacy = NULL;
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;
1341 if (compose->privacy_system == NULL)
1342 compose->privacy_system = g_strdup(privacy);
1343 compose_update_privacy_system_menu_item(compose, FALSE);
1344 compose_use_signing(compose, TRUE);
1348 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1350 MsgInfo *msginfo;
1351 guint list_len;
1352 Compose *compose = NULL;
1354 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1356 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1357 cm_return_val_if_fail(msginfo != NULL, NULL);
1359 list_len = g_slist_length(msginfo_list);
1361 switch (mode) {
1362 case COMPOSE_REPLY:
1363 case COMPOSE_REPLY_TO_ADDRESS:
1364 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1365 FALSE, prefs_common.default_reply_list, FALSE, body);
1366 break;
1367 case COMPOSE_REPLY_WITH_QUOTE:
1368 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1369 FALSE, prefs_common.default_reply_list, FALSE, body);
1370 break;
1371 case COMPOSE_REPLY_WITHOUT_QUOTE:
1372 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1373 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1374 break;
1375 case COMPOSE_REPLY_TO_SENDER:
1376 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1377 FALSE, FALSE, TRUE, body);
1378 break;
1379 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1380 compose = compose_followup_and_reply_to(msginfo,
1381 COMPOSE_QUOTE_CHECK,
1382 FALSE, FALSE, body);
1383 break;
1384 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1385 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1386 FALSE, FALSE, TRUE, body);
1387 break;
1388 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1389 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1390 FALSE, FALSE, TRUE, NULL);
1391 break;
1392 case COMPOSE_REPLY_TO_ALL:
1393 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1394 TRUE, FALSE, FALSE, body);
1395 break;
1396 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1397 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1398 TRUE, FALSE, FALSE, body);
1399 break;
1400 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1401 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1402 TRUE, FALSE, FALSE, NULL);
1403 break;
1404 case COMPOSE_REPLY_TO_LIST:
1405 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1406 FALSE, TRUE, FALSE, body);
1407 break;
1408 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1409 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1410 FALSE, TRUE, FALSE, body);
1411 break;
1412 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1413 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1414 FALSE, TRUE, FALSE, NULL);
1415 break;
1416 case COMPOSE_FORWARD:
1417 if (prefs_common.forward_as_attachment) {
1418 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1419 return compose;
1420 } else {
1421 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1422 return compose;
1424 break;
1425 case COMPOSE_FORWARD_INLINE:
1426 /* check if we reply to more than one Message */
1427 if (list_len == 1) {
1428 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1429 break;
1431 /* more messages FALL THROUGH */
1432 case COMPOSE_FORWARD_AS_ATTACH:
1433 compose = compose_forward_multiple(NULL, msginfo_list);
1434 break;
1435 case COMPOSE_REDIRECT:
1436 compose = compose_redirect(NULL, msginfo, FALSE);
1437 break;
1438 default:
1439 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1442 if (compose == NULL) {
1443 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1444 return NULL;
1447 compose->rmode = mode;
1448 switch (compose->rmode) {
1449 case COMPOSE_REPLY:
1450 case COMPOSE_REPLY_WITH_QUOTE:
1451 case COMPOSE_REPLY_WITHOUT_QUOTE:
1452 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1453 debug_print("reply mode Normal\n");
1454 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1455 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1456 break;
1457 case COMPOSE_REPLY_TO_SENDER:
1458 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1459 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1460 debug_print("reply mode Sender\n");
1461 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1462 break;
1463 case COMPOSE_REPLY_TO_ALL:
1464 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1465 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1466 debug_print("reply mode All\n");
1467 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1468 break;
1469 case COMPOSE_REPLY_TO_LIST:
1470 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1471 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1472 debug_print("reply mode List\n");
1473 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1474 break;
1475 case COMPOSE_REPLY_TO_ADDRESS:
1476 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1477 break;
1478 default:
1479 break;
1481 return compose;
1484 static Compose *compose_reply(MsgInfo *msginfo,
1485 ComposeQuoteMode quote_mode,
1486 gboolean to_all,
1487 gboolean to_ml,
1488 gboolean to_sender,
1489 const gchar *body)
1491 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1492 to_sender, FALSE, body);
1495 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1496 ComposeQuoteMode quote_mode,
1497 gboolean to_all,
1498 gboolean to_sender,
1499 const gchar *body)
1501 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1502 to_sender, TRUE, body);
1505 static void compose_extract_original_charset(Compose *compose)
1507 MsgInfo *info = NULL;
1508 if (compose->replyinfo) {
1509 info = compose->replyinfo;
1510 } else if (compose->fwdinfo) {
1511 info = compose->fwdinfo;
1512 } else if (compose->targetinfo) {
1513 info = compose->targetinfo;
1515 if (info) {
1516 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1517 MimeInfo *partinfo = mimeinfo;
1518 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1519 partinfo = procmime_mimeinfo_next(partinfo);
1520 if (partinfo) {
1521 compose->orig_charset =
1522 g_strdup(procmime_mimeinfo_get_parameter(
1523 partinfo, "charset"));
1525 procmime_mimeinfo_free_all(mimeinfo);
1529 #define SIGNAL_BLOCK(buffer) { \
1530 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1531 G_CALLBACK(compose_changed_cb), \
1532 compose); \
1533 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1534 G_CALLBACK(text_inserted), \
1535 compose); \
1538 #define SIGNAL_UNBLOCK(buffer) { \
1539 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1540 G_CALLBACK(compose_changed_cb), \
1541 compose); \
1542 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1543 G_CALLBACK(text_inserted), \
1544 compose); \
1547 static Compose *compose_generic_reply(MsgInfo *msginfo,
1548 ComposeQuoteMode quote_mode,
1549 gboolean to_all, gboolean to_ml,
1550 gboolean to_sender,
1551 gboolean followup_and_reply_to,
1552 const gchar *body)
1554 Compose *compose;
1555 PrefsAccount *account = NULL;
1556 GtkTextView *textview;
1557 GtkTextBuffer *textbuf;
1558 gboolean quote = FALSE;
1559 const gchar *qmark = NULL;
1560 const gchar *body_fmt = NULL;
1561 gchar *s_system = NULL;
1562 START_TIMING("");
1563 cm_return_val_if_fail(msginfo != NULL, NULL);
1564 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1566 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1568 cm_return_val_if_fail(account != NULL, NULL);
1570 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1572 compose->updating = TRUE;
1574 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1575 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1577 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1578 if (!compose->replyinfo)
1579 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1581 compose_extract_original_charset(compose);
1583 if (msginfo->folder && msginfo->folder->ret_rcpt)
1584 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1586 /* Set save folder */
1587 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1588 gchar *folderidentifier;
1590 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1591 folderidentifier = folder_item_get_identifier(msginfo->folder);
1592 compose_set_save_to(compose, folderidentifier);
1593 g_free(folderidentifier);
1596 if (compose_parse_header(compose, msginfo) < 0) {
1597 compose->updating = FALSE;
1598 compose_destroy(compose);
1599 return NULL;
1602 /* override from name according to folder properties */
1603 if (msginfo->folder && msginfo->folder->prefs &&
1604 msginfo->folder->prefs->reply_with_format &&
1605 msginfo->folder->prefs->reply_override_from_format &&
1606 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1608 gchar *tmp = NULL;
1609 gchar *buf = NULL;
1611 /* decode \-escape sequences in the internal representation of the quote format */
1612 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1613 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1615 #ifdef USE_ENCHANT
1616 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1617 compose->gtkaspell);
1618 #else
1619 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1620 #endif
1621 quote_fmt_scan_string(tmp);
1622 quote_fmt_parse();
1624 buf = quote_fmt_get_buffer();
1625 if (buf == NULL)
1626 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1627 else
1628 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1629 quote_fmt_reset_vartable();
1631 g_free(tmp);
1634 textview = (GTK_TEXT_VIEW(compose->text));
1635 textbuf = gtk_text_view_get_buffer(textview);
1636 compose_create_tags(textview, compose);
1638 undo_block(compose->undostruct);
1639 #ifdef USE_ENCHANT
1640 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1641 gtkaspell_block_check(compose->gtkaspell);
1642 #endif
1644 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1645 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1646 /* use the reply format of folder (if enabled), or the account's one
1647 (if enabled) or fallback to the global reply format, which is always
1648 enabled (even if empty), and use the relevant quotemark */
1649 quote = TRUE;
1650 if (msginfo->folder && msginfo->folder->prefs &&
1651 msginfo->folder->prefs->reply_with_format) {
1652 qmark = msginfo->folder->prefs->reply_quotemark;
1653 body_fmt = msginfo->folder->prefs->reply_body_format;
1655 } else if (account->reply_with_format) {
1656 qmark = account->reply_quotemark;
1657 body_fmt = account->reply_body_format;
1659 } else {
1660 qmark = prefs_common.quotemark;
1661 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1662 body_fmt = gettext(prefs_common.quotefmt);
1663 else
1664 body_fmt = "";
1668 if (quote) {
1669 /* empty quotemark is not allowed */
1670 if (qmark == NULL || *qmark == '\0')
1671 qmark = "> ";
1672 compose_quote_fmt(compose, compose->replyinfo,
1673 body_fmt, qmark, body, FALSE, TRUE,
1674 _("The body of the \"Reply\" template has an error at line %d."));
1675 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1676 quote_fmt_reset_vartable();
1679 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1680 compose_force_encryption(compose, account, FALSE, s_system);
1683 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1684 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1685 compose_force_signing(compose, account, s_system);
1687 g_free(s_system);
1689 SIGNAL_BLOCK(textbuf);
1691 if (account->auto_sig)
1692 compose_insert_sig(compose, FALSE);
1694 compose_wrap_all(compose);
1696 #ifdef USE_ENCHANT
1697 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1698 gtkaspell_highlight_all(compose->gtkaspell);
1699 gtkaspell_unblock_check(compose->gtkaspell);
1700 #endif
1701 SIGNAL_UNBLOCK(textbuf);
1703 gtk_widget_grab_focus(compose->text);
1705 undo_unblock(compose->undostruct);
1707 if (prefs_common.auto_exteditor)
1708 compose_exec_ext_editor(compose);
1710 compose->modified = FALSE;
1711 compose_set_title(compose);
1713 compose->updating = FALSE;
1714 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1715 SCROLL_TO_CURSOR(compose);
1717 if (compose->deferred_destroy) {
1718 compose_destroy(compose);
1719 return NULL;
1721 END_TIMING();
1723 return compose;
1726 #define INSERT_FW_HEADER(var, hdr) \
1727 if (msginfo->var && *msginfo->var) { \
1728 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1729 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1730 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1733 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1734 gboolean as_attach, const gchar *body,
1735 gboolean no_extedit,
1736 gboolean batch)
1738 Compose *compose;
1739 GtkTextView *textview;
1740 GtkTextBuffer *textbuf;
1741 gint cursor_pos = -1;
1742 ComposeMode mode;
1744 cm_return_val_if_fail(msginfo != NULL, NULL);
1745 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1747 if (!account &&
1748 !(account = compose_guess_forward_account_from_msginfo
1749 (msginfo)))
1750 account = cur_account;
1752 if (!prefs_common.forward_as_attachment)
1753 mode = COMPOSE_FORWARD_INLINE;
1754 else
1755 mode = COMPOSE_FORWARD;
1756 compose = compose_create(account, msginfo->folder, mode, batch);
1758 compose->updating = TRUE;
1759 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1760 if (!compose->fwdinfo)
1761 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1763 compose_extract_original_charset(compose);
1765 if (msginfo->subject && *msginfo->subject) {
1766 gchar *buf, *buf2, *p;
1768 buf = p = g_strdup(msginfo->subject);
1769 p += subject_get_prefix_length(p);
1770 memmove(buf, p, strlen(p) + 1);
1772 buf2 = g_strdup_printf("Fw: %s", buf);
1773 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1775 g_free(buf);
1776 g_free(buf2);
1779 /* override from name according to folder properties */
1780 if (msginfo->folder && msginfo->folder->prefs &&
1781 msginfo->folder->prefs->forward_with_format &&
1782 msginfo->folder->prefs->forward_override_from_format &&
1783 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1785 gchar *tmp = NULL;
1786 gchar *buf = NULL;
1787 MsgInfo *full_msginfo = NULL;
1789 if (!as_attach)
1790 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1791 if (!full_msginfo)
1792 full_msginfo = procmsg_msginfo_copy(msginfo);
1794 /* decode \-escape sequences in the internal representation of the quote format */
1795 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1796 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1798 #ifdef USE_ENCHANT
1799 gtkaspell_block_check(compose->gtkaspell);
1800 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1801 compose->gtkaspell);
1802 #else
1803 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1804 #endif
1805 quote_fmt_scan_string(tmp);
1806 quote_fmt_parse();
1808 buf = quote_fmt_get_buffer();
1809 if (buf == NULL)
1810 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1811 else
1812 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1813 quote_fmt_reset_vartable();
1815 g_free(tmp);
1816 procmsg_msginfo_free(full_msginfo);
1819 textview = GTK_TEXT_VIEW(compose->text);
1820 textbuf = gtk_text_view_get_buffer(textview);
1821 compose_create_tags(textview, compose);
1823 undo_block(compose->undostruct);
1824 if (as_attach) {
1825 gchar *msgfile;
1827 msgfile = procmsg_get_message_file(msginfo);
1828 if (!is_file_exist(msgfile))
1829 g_warning("%s: file not exist\n", msgfile);
1830 else
1831 compose_attach_append(compose, msgfile, msgfile,
1832 "message/rfc822", NULL);
1834 g_free(msgfile);
1835 } else {
1836 const gchar *qmark = NULL;
1837 const gchar *body_fmt = NULL;
1838 MsgInfo *full_msginfo;
1840 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1841 if (!full_msginfo)
1842 full_msginfo = procmsg_msginfo_copy(msginfo);
1844 /* use the forward format of folder (if enabled), or the account's one
1845 (if enabled) or fallback to the global forward format, which is always
1846 enabled (even if empty), and use the relevant quotemark */
1847 if (msginfo->folder && msginfo->folder->prefs &&
1848 msginfo->folder->prefs->forward_with_format) {
1849 qmark = msginfo->folder->prefs->forward_quotemark;
1850 body_fmt = msginfo->folder->prefs->forward_body_format;
1852 } else if (account->forward_with_format) {
1853 qmark = account->forward_quotemark;
1854 body_fmt = account->forward_body_format;
1856 } else {
1857 qmark = prefs_common.fw_quotemark;
1858 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1859 body_fmt = gettext(prefs_common.fw_quotefmt);
1860 else
1861 body_fmt = "";
1864 /* empty quotemark is not allowed */
1865 if (qmark == NULL || *qmark == '\0')
1866 qmark = "> ";
1868 compose_quote_fmt(compose, full_msginfo,
1869 body_fmt, qmark, body, FALSE, TRUE,
1870 _("The body of the \"Forward\" template has an error at line %d."));
1871 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1872 quote_fmt_reset_vartable();
1873 compose_attach_parts(compose, msginfo);
1875 procmsg_msginfo_free(full_msginfo);
1878 SIGNAL_BLOCK(textbuf);
1880 if (account->auto_sig)
1881 compose_insert_sig(compose, FALSE);
1883 compose_wrap_all(compose);
1885 #ifdef USE_ENCHANT
1886 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1887 gtkaspell_highlight_all(compose->gtkaspell);
1888 gtkaspell_unblock_check(compose->gtkaspell);
1889 #endif
1890 SIGNAL_UNBLOCK(textbuf);
1892 cursor_pos = quote_fmt_get_cursor_pos();
1893 if (cursor_pos == -1)
1894 gtk_widget_grab_focus(compose->header_last->entry);
1895 else
1896 gtk_widget_grab_focus(compose->text);
1898 if (!no_extedit && prefs_common.auto_exteditor)
1899 compose_exec_ext_editor(compose);
1901 /*save folder*/
1902 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1903 gchar *folderidentifier;
1905 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1906 folderidentifier = folder_item_get_identifier(msginfo->folder);
1907 compose_set_save_to(compose, folderidentifier);
1908 g_free(folderidentifier);
1911 undo_unblock(compose->undostruct);
1913 compose->modified = FALSE;
1914 compose_set_title(compose);
1916 compose->updating = FALSE;
1917 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1918 SCROLL_TO_CURSOR(compose);
1920 if (compose->deferred_destroy) {
1921 compose_destroy(compose);
1922 return NULL;
1925 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1927 return compose;
1930 #undef INSERT_FW_HEADER
1932 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1934 Compose *compose;
1935 GtkTextView *textview;
1936 GtkTextBuffer *textbuf;
1937 GtkTextIter iter;
1938 GSList *msginfo;
1939 gchar *msgfile;
1940 gboolean single_mail = TRUE;
1942 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1944 if (g_slist_length(msginfo_list) > 1)
1945 single_mail = FALSE;
1947 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1948 if (((MsgInfo *)msginfo->data)->folder == NULL)
1949 return NULL;
1951 /* guess account from first selected message */
1952 if (!account &&
1953 !(account = compose_guess_forward_account_from_msginfo
1954 (msginfo_list->data)))
1955 account = cur_account;
1957 cm_return_val_if_fail(account != NULL, NULL);
1959 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1960 if (msginfo->data) {
1961 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1962 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1966 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1967 g_warning("no msginfo_list");
1968 return NULL;
1971 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1973 compose->updating = TRUE;
1975 /* override from name according to folder properties */
1976 if (msginfo_list->data) {
1977 MsgInfo *msginfo = msginfo_list->data;
1979 if (msginfo->folder && msginfo->folder->prefs &&
1980 msginfo->folder->prefs->forward_with_format &&
1981 msginfo->folder->prefs->forward_override_from_format &&
1982 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1984 gchar *tmp = NULL;
1985 gchar *buf = NULL;
1987 /* decode \-escape sequences in the internal representation of the quote format */
1988 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1989 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1991 #ifdef USE_ENCHANT
1992 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1993 compose->gtkaspell);
1994 #else
1995 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1996 #endif
1997 quote_fmt_scan_string(tmp);
1998 quote_fmt_parse();
2000 buf = quote_fmt_get_buffer();
2001 if (buf == NULL)
2002 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2003 else
2004 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2005 quote_fmt_reset_vartable();
2007 g_free(tmp);
2011 textview = GTK_TEXT_VIEW(compose->text);
2012 textbuf = gtk_text_view_get_buffer(textview);
2013 compose_create_tags(textview, compose);
2015 undo_block(compose->undostruct);
2016 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2017 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2019 if (!is_file_exist(msgfile))
2020 g_warning("%s: file not exist\n", msgfile);
2021 else
2022 compose_attach_append(compose, msgfile, msgfile,
2023 "message/rfc822", NULL);
2024 g_free(msgfile);
2027 if (single_mail) {
2028 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2029 if (info->subject && *info->subject) {
2030 gchar *buf, *buf2, *p;
2032 buf = p = g_strdup(info->subject);
2033 p += subject_get_prefix_length(p);
2034 memmove(buf, p, strlen(p) + 1);
2036 buf2 = g_strdup_printf("Fw: %s", buf);
2037 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2039 g_free(buf);
2040 g_free(buf2);
2042 } else {
2043 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2044 _("Fw: multiple emails"));
2047 SIGNAL_BLOCK(textbuf);
2049 if (account->auto_sig)
2050 compose_insert_sig(compose, FALSE);
2052 compose_wrap_all(compose);
2054 SIGNAL_UNBLOCK(textbuf);
2056 gtk_text_buffer_get_start_iter(textbuf, &iter);
2057 gtk_text_buffer_place_cursor(textbuf, &iter);
2059 gtk_widget_grab_focus(compose->header_last->entry);
2060 undo_unblock(compose->undostruct);
2061 compose->modified = FALSE;
2062 compose_set_title(compose);
2064 compose->updating = FALSE;
2065 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2066 SCROLL_TO_CURSOR(compose);
2068 if (compose->deferred_destroy) {
2069 compose_destroy(compose);
2070 return NULL;
2073 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2075 return compose;
2078 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2080 GtkTextIter start = *iter;
2081 GtkTextIter end_iter;
2082 int start_pos = gtk_text_iter_get_offset(&start);
2083 gchar *str = NULL;
2084 if (!compose->account->sig_sep)
2085 return FALSE;
2087 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2088 start_pos+strlen(compose->account->sig_sep));
2090 /* check sig separator */
2091 str = gtk_text_iter_get_text(&start, &end_iter);
2092 if (!strcmp(str, compose->account->sig_sep)) {
2093 gchar *tmp = NULL;
2094 /* check end of line (\n) */
2095 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2096 start_pos+strlen(compose->account->sig_sep));
2097 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2098 start_pos+strlen(compose->account->sig_sep)+1);
2099 tmp = gtk_text_iter_get_text(&start, &end_iter);
2100 if (!strcmp(tmp,"\n")) {
2101 g_free(str);
2102 g_free(tmp);
2103 return TRUE;
2105 g_free(tmp);
2107 g_free(str);
2109 return FALSE;
2112 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2114 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2115 Compose *compose = (Compose *)data;
2116 FolderItem *old_item = NULL;
2117 FolderItem *new_item = NULL;
2118 gchar *old_id, *new_id;
2120 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2121 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2122 return FALSE;
2124 old_item = hookdata->item;
2125 new_item = hookdata->item2;
2127 old_id = folder_item_get_identifier(old_item);
2128 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2130 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2131 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2132 compose->targetinfo->folder = new_item;
2135 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2136 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2137 compose->replyinfo->folder = new_item;
2140 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2141 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2142 compose->fwdinfo->folder = new_item;
2145 g_free(old_id);
2146 g_free(new_id);
2147 return FALSE;
2150 static void compose_colorize_signature(Compose *compose)
2152 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2153 GtkTextIter iter;
2154 GtkTextIter end_iter;
2155 gtk_text_buffer_get_start_iter(buffer, &iter);
2156 while (gtk_text_iter_forward_line(&iter))
2157 if (compose_is_sig_separator(compose, buffer, &iter)) {
2158 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2159 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2163 #define BLOCK_WRAP() { \
2164 prev_autowrap = compose->autowrap; \
2165 buffer = gtk_text_view_get_buffer( \
2166 GTK_TEXT_VIEW(compose->text)); \
2167 compose->autowrap = FALSE; \
2169 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2170 G_CALLBACK(compose_changed_cb), \
2171 compose); \
2172 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2173 G_CALLBACK(text_inserted), \
2174 compose); \
2176 #define UNBLOCK_WRAP() { \
2177 compose->autowrap = prev_autowrap; \
2178 if (compose->autowrap) { \
2179 gint old = compose->draft_timeout_tag; \
2180 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2181 compose_wrap_all(compose); \
2182 compose->draft_timeout_tag = old; \
2185 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2186 G_CALLBACK(compose_changed_cb), \
2187 compose); \
2188 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2189 G_CALLBACK(text_inserted), \
2190 compose); \
2193 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2195 Compose *compose = NULL;
2196 PrefsAccount *account = NULL;
2197 GtkTextView *textview;
2198 GtkTextBuffer *textbuf;
2199 GtkTextMark *mark;
2200 GtkTextIter iter;
2201 FILE *fp;
2202 gchar buf[BUFFSIZE];
2203 gboolean use_signing = FALSE;
2204 gboolean use_encryption = FALSE;
2205 gchar *privacy_system = NULL;
2206 int priority = PRIORITY_NORMAL;
2207 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2208 gboolean autowrap = prefs_common.autowrap;
2209 gboolean autoindent = prefs_common.auto_indent;
2210 HeaderEntry *manual_headers = NULL;
2212 cm_return_val_if_fail(msginfo != NULL, NULL);
2213 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2215 if (compose_put_existing_to_front(msginfo)) {
2216 return NULL;
2219 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2220 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2221 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2222 gchar queueheader_buf[BUFFSIZE];
2223 gint id, param;
2225 /* Select Account from queue headers */
2226 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2227 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2228 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2229 account = account_find_from_id(id);
2231 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2232 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2233 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2234 account = account_find_from_id(id);
2236 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2237 sizeof(queueheader_buf), "NAID:")) {
2238 id = atoi(&queueheader_buf[strlen("NAID:")]);
2239 account = account_find_from_id(id);
2241 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2242 sizeof(queueheader_buf), "MAID:")) {
2243 id = atoi(&queueheader_buf[strlen("MAID:")]);
2244 account = account_find_from_id(id);
2246 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2247 sizeof(queueheader_buf), "S:")) {
2248 account = account_find_from_address(queueheader_buf, FALSE);
2250 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2251 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2252 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2253 use_signing = param;
2256 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2257 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2258 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2259 use_signing = param;
2262 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2263 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2264 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2265 use_encryption = param;
2267 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2268 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2269 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2270 use_encryption = param;
2272 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2273 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2274 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2275 autowrap = param;
2277 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2278 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2279 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2280 autoindent = param;
2282 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2283 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2284 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2286 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2287 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2288 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2290 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2291 sizeof(queueheader_buf), "X-Priority: ")) {
2292 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2293 priority = param;
2295 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2296 sizeof(queueheader_buf), "RMID:")) {
2297 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2298 if (tokens[0] && tokens[1] && tokens[2]) {
2299 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2300 if (orig_item != NULL) {
2301 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2304 g_strfreev(tokens);
2306 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2307 sizeof(queueheader_buf), "FMID:")) {
2308 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2309 if (tokens[0] && tokens[1] && tokens[2]) {
2310 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2311 if (orig_item != NULL) {
2312 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2315 g_strfreev(tokens);
2317 /* Get manual headers */
2318 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2319 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2320 if (*listmh != '\0') {
2321 debug_print("Got manual headers: %s\n", listmh);
2322 manual_headers = procheader_entries_from_str(listmh);
2324 g_free(listmh);
2326 } else {
2327 account = msginfo->folder->folder->account;
2330 if (!account && prefs_common.reedit_account_autosel) {
2331 gchar from[BUFFSIZE];
2332 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2333 extract_address(from);
2334 account = account_find_from_address(from, FALSE);
2337 if (!account) {
2338 account = cur_account;
2340 cm_return_val_if_fail(account != NULL, NULL);
2342 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2344 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2345 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2346 compose->autowrap = autowrap;
2347 compose->replyinfo = replyinfo;
2348 compose->fwdinfo = fwdinfo;
2350 compose->updating = TRUE;
2351 compose->priority = priority;
2353 if (privacy_system != NULL) {
2354 compose->privacy_system = privacy_system;
2355 compose_use_signing(compose, use_signing);
2356 compose_use_encryption(compose, use_encryption);
2357 compose_update_privacy_system_menu_item(compose, FALSE);
2358 } else {
2359 activate_privacy_system(compose, account, FALSE);
2362 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2364 compose_extract_original_charset(compose);
2366 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2367 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2368 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2369 gchar queueheader_buf[BUFFSIZE];
2371 /* Set message save folder */
2372 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2373 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2374 compose_set_save_to(compose, &queueheader_buf[4]);
2376 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2377 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2378 if (active) {
2379 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2384 if (compose_parse_header(compose, msginfo) < 0) {
2385 compose->updating = FALSE;
2386 compose_destroy(compose);
2387 return NULL;
2389 compose_reedit_set_entry(compose, msginfo);
2391 textview = GTK_TEXT_VIEW(compose->text);
2392 textbuf = gtk_text_view_get_buffer(textview);
2393 compose_create_tags(textview, compose);
2395 mark = gtk_text_buffer_get_insert(textbuf);
2396 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2398 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2399 G_CALLBACK(compose_changed_cb),
2400 compose);
2402 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2403 fp = procmime_get_first_encrypted_text_content(msginfo);
2404 if (fp) {
2405 compose_force_encryption(compose, account, TRUE, NULL);
2407 } else {
2408 fp = procmime_get_first_text_content(msginfo);
2410 if (fp == NULL) {
2411 g_warning("Can't get text part\n");
2414 if (fp != NULL) {
2415 gboolean prev_autowrap;
2416 GtkTextBuffer *buffer;
2417 BLOCK_WRAP();
2418 while (fgets(buf, sizeof(buf), fp) != NULL) {
2419 strcrchomp(buf);
2420 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2422 UNBLOCK_WRAP();
2423 fclose(fp);
2426 compose_attach_parts(compose, msginfo);
2428 compose_colorize_signature(compose);
2430 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2431 G_CALLBACK(compose_changed_cb),
2432 compose);
2434 if (manual_headers != NULL) {
2435 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2436 procheader_entries_free(manual_headers);
2437 compose->updating = FALSE;
2438 compose_destroy(compose);
2439 return NULL;
2441 procheader_entries_free(manual_headers);
2444 gtk_widget_grab_focus(compose->text);
2446 if (prefs_common.auto_exteditor) {
2447 compose_exec_ext_editor(compose);
2449 compose->modified = FALSE;
2450 compose_set_title(compose);
2452 compose->updating = FALSE;
2453 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2454 SCROLL_TO_CURSOR(compose);
2456 if (compose->deferred_destroy) {
2457 compose_destroy(compose);
2458 return NULL;
2461 compose->sig_str = account_get_signature_str(compose->account);
2463 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2465 return compose;
2468 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2469 gboolean batch)
2471 Compose *compose;
2472 gchar *filename;
2473 FolderItem *item;
2475 cm_return_val_if_fail(msginfo != NULL, NULL);
2477 if (!account)
2478 account = account_get_reply_account(msginfo,
2479 prefs_common.reply_account_autosel);
2480 cm_return_val_if_fail(account != NULL, NULL);
2482 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2484 compose->updating = TRUE;
2486 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2487 compose->replyinfo = NULL;
2488 compose->fwdinfo = NULL;
2490 compose_show_first_last_header(compose, TRUE);
2492 gtk_widget_grab_focus(compose->header_last->entry);
2494 filename = procmsg_get_message_file(msginfo);
2496 if (filename == NULL) {
2497 compose->updating = FALSE;
2498 compose_destroy(compose);
2500 return NULL;
2503 compose->redirect_filename = filename;
2505 /* Set save folder */
2506 item = msginfo->folder;
2507 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2508 gchar *folderidentifier;
2510 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2511 folderidentifier = folder_item_get_identifier(item);
2512 compose_set_save_to(compose, folderidentifier);
2513 g_free(folderidentifier);
2516 compose_attach_parts(compose, msginfo);
2518 if (msginfo->subject)
2519 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2520 msginfo->subject);
2521 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2523 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2524 _("The body of the \"Redirect\" template has an error at line %d."));
2525 quote_fmt_reset_vartable();
2526 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2528 compose_colorize_signature(compose);
2531 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2532 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2533 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2535 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2536 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2537 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2538 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2539 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2540 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2541 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2542 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2543 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2545 if (compose->toolbar->draft_btn)
2546 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2547 if (compose->toolbar->insert_btn)
2548 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2549 if (compose->toolbar->attach_btn)
2550 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2551 if (compose->toolbar->sig_btn)
2552 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2553 if (compose->toolbar->exteditor_btn)
2554 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2555 if (compose->toolbar->linewrap_current_btn)
2556 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2557 if (compose->toolbar->linewrap_all_btn)
2558 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2560 compose->modified = FALSE;
2561 compose_set_title(compose);
2562 compose->updating = FALSE;
2563 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2564 SCROLL_TO_CURSOR(compose);
2566 if (compose->deferred_destroy) {
2567 compose_destroy(compose);
2568 return NULL;
2571 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2573 return compose;
2576 const GList *compose_get_compose_list(void)
2578 return compose_list;
2581 void compose_entry_append(Compose *compose, const gchar *address,
2582 ComposeEntryType type, ComposePrefType pref_type)
2584 const gchar *header;
2585 gchar *cur, *begin;
2586 gboolean in_quote = FALSE;
2587 if (!address || *address == '\0') return;
2589 switch (type) {
2590 case COMPOSE_CC:
2591 header = N_("Cc:");
2592 break;
2593 case COMPOSE_BCC:
2594 header = N_("Bcc:");
2595 break;
2596 case COMPOSE_REPLYTO:
2597 header = N_("Reply-To:");
2598 break;
2599 case COMPOSE_NEWSGROUPS:
2600 header = N_("Newsgroups:");
2601 break;
2602 case COMPOSE_FOLLOWUPTO:
2603 header = N_( "Followup-To:");
2604 break;
2605 case COMPOSE_INREPLYTO:
2606 header = N_( "In-Reply-To:");
2607 break;
2608 case COMPOSE_TO:
2609 default:
2610 header = N_("To:");
2611 break;
2613 header = prefs_common_translated_header_name(header);
2615 cur = begin = (gchar *)address;
2617 /* we separate the line by commas, but not if we're inside a quoted
2618 * string */
2619 while (*cur != '\0') {
2620 if (*cur == '"')
2621 in_quote = !in_quote;
2622 if (*cur == ',' && !in_quote) {
2623 gchar *tmp = g_strdup(begin);
2624 gchar *o_tmp = tmp;
2625 tmp[cur-begin]='\0';
2626 cur++;
2627 begin = cur;
2628 while (*tmp == ' ' || *tmp == '\t')
2629 tmp++;
2630 compose_add_header_entry(compose, header, tmp, pref_type);
2631 g_free(o_tmp);
2632 continue;
2634 cur++;
2636 if (begin < cur) {
2637 gchar *tmp = g_strdup(begin);
2638 gchar *o_tmp = tmp;
2639 tmp[cur-begin]='\0';
2640 while (*tmp == ' ' || *tmp == '\t')
2641 tmp++;
2642 compose_add_header_entry(compose, header, tmp, pref_type);
2643 g_free(o_tmp);
2647 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2649 #if !GTK_CHECK_VERSION(3, 0, 0)
2650 static GdkColor yellow;
2651 static GdkColor black;
2652 static gboolean yellow_initialised = FALSE;
2653 #else
2654 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2655 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2656 #endif
2657 GSList *h_list;
2658 GtkEntry *entry;
2660 #if !GTK_CHECK_VERSION(3, 0, 0)
2661 if (!yellow_initialised) {
2662 gdk_color_parse("#f5f6be", &yellow);
2663 gdk_color_parse("#000000", &black);
2664 yellow_initialised = gdk_colormap_alloc_color(
2665 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2666 yellow_initialised &= gdk_colormap_alloc_color(
2667 gdk_colormap_get_system(), &black, FALSE, TRUE);
2669 #endif
2671 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2672 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2673 if (gtk_entry_get_text(entry) &&
2674 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2675 #if !GTK_CHECK_VERSION(3, 0, 0)
2676 if (yellow_initialised) {
2677 #endif
2678 gtk_widget_modify_base(
2679 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2680 GTK_STATE_NORMAL, &yellow);
2681 gtk_widget_modify_text(
2682 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2683 GTK_STATE_NORMAL, &black);
2684 #if !GTK_CHECK_VERSION(3, 0, 0)
2686 #endif
2691 void compose_toolbar_cb(gint action, gpointer data)
2693 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2694 Compose *compose = (Compose*)toolbar_item->parent;
2696 cm_return_if_fail(compose != NULL);
2698 switch(action) {
2699 case A_SEND:
2700 compose_send_cb(NULL, compose);
2701 break;
2702 case A_SENDL:
2703 compose_send_later_cb(NULL, compose);
2704 break;
2705 case A_DRAFT:
2706 compose_draft(compose, COMPOSE_QUIT_EDITING);
2707 break;
2708 case A_INSERT:
2709 compose_insert_file_cb(NULL, compose);
2710 break;
2711 case A_ATTACH:
2712 compose_attach_cb(NULL, compose);
2713 break;
2714 case A_SIG:
2715 compose_insert_sig(compose, FALSE);
2716 break;
2717 case A_REP_SIG:
2718 compose_insert_sig(compose, TRUE);
2719 break;
2720 case A_EXTEDITOR:
2721 compose_ext_editor_cb(NULL, compose);
2722 break;
2723 case A_LINEWRAP_CURRENT:
2724 compose_beautify_paragraph(compose, NULL, TRUE);
2725 break;
2726 case A_LINEWRAP_ALL:
2727 compose_wrap_all_full(compose, TRUE);
2728 break;
2729 case A_ADDRBOOK:
2730 compose_address_cb(NULL, compose);
2731 break;
2732 #ifdef USE_ENCHANT
2733 case A_CHECK_SPELLING:
2734 compose_check_all(NULL, compose);
2735 break;
2736 #endif
2737 default:
2738 break;
2742 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2744 gchar *to = NULL;
2745 gchar *cc = NULL;
2746 gchar *bcc = NULL;
2747 gchar *subject = NULL;
2748 gchar *body = NULL;
2749 gchar *temp = NULL;
2750 gsize len = 0;
2751 gchar **attach = NULL;
2752 gchar *inreplyto = NULL;
2753 MailField mfield = NO_FIELD_PRESENT;
2755 /* get mailto parts but skip from */
2756 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2758 if (to) {
2759 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2760 mfield = TO_FIELD_PRESENT;
2762 if (cc)
2763 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2764 if (bcc)
2765 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2766 if (subject) {
2767 if (!g_utf8_validate (subject, -1, NULL)) {
2768 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2769 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2770 g_free(temp);
2771 } else {
2772 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2774 mfield = SUBJECT_FIELD_PRESENT;
2776 if (body) {
2777 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2778 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2779 GtkTextMark *mark;
2780 GtkTextIter iter;
2781 gboolean prev_autowrap = compose->autowrap;
2783 compose->autowrap = FALSE;
2785 mark = gtk_text_buffer_get_insert(buffer);
2786 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2788 if (!g_utf8_validate (body, -1, NULL)) {
2789 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2790 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2791 g_free(temp);
2792 } else {
2793 gtk_text_buffer_insert(buffer, &iter, body, -1);
2795 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2797 compose->autowrap = prev_autowrap;
2798 if (compose->autowrap)
2799 compose_wrap_all(compose);
2800 mfield = BODY_FIELD_PRESENT;
2803 if (attach) {
2804 gint i = 0, att = 0;
2805 gchar *warn_files = NULL;
2806 while (attach[i] != NULL) {
2807 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2808 if (utf8_filename) {
2809 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2810 gchar *tmp = g_strdup_printf("%s%s\n",
2811 warn_files?warn_files:"",
2812 utf8_filename);
2813 g_free(warn_files);
2814 warn_files = tmp;
2815 att++;
2817 g_free(utf8_filename);
2818 } else {
2819 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2821 i++;
2823 if (warn_files) {
2824 alertpanel_notice(ngettext(
2825 "The following file has been attached: \n%s",
2826 "The following files have been attached: \n%s", att), warn_files);
2827 g_free(warn_files);
2830 if (inreplyto)
2831 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2833 g_free(to);
2834 g_free(cc);
2835 g_free(bcc);
2836 g_free(subject);
2837 g_free(body);
2838 g_strfreev(attach);
2839 g_free(inreplyto);
2841 return mfield;
2844 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2846 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2847 {"Cc:", NULL, TRUE},
2848 {"References:", NULL, FALSE},
2849 {"Bcc:", NULL, TRUE},
2850 {"Newsgroups:", NULL, TRUE},
2851 {"Followup-To:", NULL, TRUE},
2852 {"List-Post:", NULL, FALSE},
2853 {"X-Priority:", NULL, FALSE},
2854 {NULL, NULL, FALSE}};
2856 enum
2858 H_REPLY_TO = 0,
2859 H_CC = 1,
2860 H_REFERENCES = 2,
2861 H_BCC = 3,
2862 H_NEWSGROUPS = 4,
2863 H_FOLLOWUP_TO = 5,
2864 H_LIST_POST = 6,
2865 H_X_PRIORITY = 7
2868 FILE *fp;
2870 cm_return_val_if_fail(msginfo != NULL, -1);
2872 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2873 procheader_get_header_fields(fp, hentry);
2874 fclose(fp);
2876 if (hentry[H_REPLY_TO].body != NULL) {
2877 if (hentry[H_REPLY_TO].body[0] != '\0') {
2878 compose->replyto =
2879 conv_unmime_header(hentry[H_REPLY_TO].body,
2880 NULL, TRUE);
2882 g_free(hentry[H_REPLY_TO].body);
2883 hentry[H_REPLY_TO].body = NULL;
2885 if (hentry[H_CC].body != NULL) {
2886 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2887 g_free(hentry[H_CC].body);
2888 hentry[H_CC].body = NULL;
2890 if (hentry[H_REFERENCES].body != NULL) {
2891 if (compose->mode == COMPOSE_REEDIT)
2892 compose->references = hentry[H_REFERENCES].body;
2893 else {
2894 compose->references = compose_parse_references
2895 (hentry[H_REFERENCES].body, msginfo->msgid);
2896 g_free(hentry[H_REFERENCES].body);
2898 hentry[H_REFERENCES].body = NULL;
2900 if (hentry[H_BCC].body != NULL) {
2901 if (compose->mode == COMPOSE_REEDIT)
2902 compose->bcc =
2903 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2904 g_free(hentry[H_BCC].body);
2905 hentry[H_BCC].body = NULL;
2907 if (hentry[H_NEWSGROUPS].body != NULL) {
2908 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2909 hentry[H_NEWSGROUPS].body = NULL;
2911 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2912 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2913 compose->followup_to =
2914 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2915 NULL, TRUE);
2917 g_free(hentry[H_FOLLOWUP_TO].body);
2918 hentry[H_FOLLOWUP_TO].body = NULL;
2920 if (hentry[H_LIST_POST].body != NULL) {
2921 gchar *to = NULL, *start = NULL;
2923 extract_address(hentry[H_LIST_POST].body);
2924 if (hentry[H_LIST_POST].body[0] != '\0') {
2925 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2927 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2928 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2930 if (to) {
2931 g_free(compose->ml_post);
2932 compose->ml_post = to;
2935 g_free(hentry[H_LIST_POST].body);
2936 hentry[H_LIST_POST].body = NULL;
2939 /* CLAWS - X-Priority */
2940 if (compose->mode == COMPOSE_REEDIT)
2941 if (hentry[H_X_PRIORITY].body != NULL) {
2942 gint priority;
2944 priority = atoi(hentry[H_X_PRIORITY].body);
2945 g_free(hentry[H_X_PRIORITY].body);
2947 hentry[H_X_PRIORITY].body = NULL;
2949 if (priority < PRIORITY_HIGHEST ||
2950 priority > PRIORITY_LOWEST)
2951 priority = PRIORITY_NORMAL;
2953 compose->priority = priority;
2956 if (compose->mode == COMPOSE_REEDIT) {
2957 if (msginfo->inreplyto && *msginfo->inreplyto)
2958 compose->inreplyto = g_strdup(msginfo->inreplyto);
2959 return 0;
2962 if (msginfo->msgid && *msginfo->msgid)
2963 compose->inreplyto = g_strdup(msginfo->msgid);
2965 if (!compose->references) {
2966 if (msginfo->msgid && *msginfo->msgid) {
2967 if (msginfo->inreplyto && *msginfo->inreplyto)
2968 compose->references =
2969 g_strdup_printf("<%s>\n\t<%s>",
2970 msginfo->inreplyto,
2971 msginfo->msgid);
2972 else
2973 compose->references =
2974 g_strconcat("<", msginfo->msgid, ">",
2975 NULL);
2976 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2977 compose->references =
2978 g_strconcat("<", msginfo->inreplyto, ">",
2979 NULL);
2983 return 0;
2986 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2988 FILE *fp;
2989 HeaderEntry *he;
2991 cm_return_val_if_fail(msginfo != NULL, -1);
2993 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2994 procheader_get_header_fields(fp, entries);
2995 fclose(fp);
2997 he = entries;
2998 while (he != NULL && he->name != NULL) {
2999 GtkTreeIter iter;
3000 GtkListStore *model = NULL;
3002 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3003 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3004 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3005 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3006 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3007 ++he;
3010 return 0;
3013 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3015 GSList *ref_id_list, *cur;
3016 GString *new_ref;
3017 gchar *new_ref_str;
3019 ref_id_list = references_list_append(NULL, ref);
3020 if (!ref_id_list) return NULL;
3021 if (msgid && *msgid)
3022 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3024 for (;;) {
3025 gint len = 0;
3027 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3028 /* "<" + Message-ID + ">" + CR+LF+TAB */
3029 len += strlen((gchar *)cur->data) + 5;
3031 if (len > MAX_REFERENCES_LEN) {
3032 /* remove second message-ID */
3033 if (ref_id_list && ref_id_list->next &&
3034 ref_id_list->next->next) {
3035 g_free(ref_id_list->next->data);
3036 ref_id_list = g_slist_remove
3037 (ref_id_list, ref_id_list->next->data);
3038 } else {
3039 slist_free_strings_full(ref_id_list);
3040 return NULL;
3042 } else
3043 break;
3046 new_ref = g_string_new("");
3047 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3048 if (new_ref->len > 0)
3049 g_string_append(new_ref, "\n\t");
3050 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3053 slist_free_strings_full(ref_id_list);
3055 new_ref_str = new_ref->str;
3056 g_string_free(new_ref, FALSE);
3058 return new_ref_str;
3061 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3062 const gchar *fmt, const gchar *qmark,
3063 const gchar *body, gboolean rewrap,
3064 gboolean need_unescape,
3065 const gchar *err_msg)
3067 MsgInfo* dummyinfo = NULL;
3068 gchar *quote_str = NULL;
3069 gchar *buf;
3070 gboolean prev_autowrap;
3071 const gchar *trimmed_body = body;
3072 gint cursor_pos = -1;
3073 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3074 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3075 GtkTextIter iter;
3076 GtkTextMark *mark;
3079 SIGNAL_BLOCK(buffer);
3081 if (!msginfo) {
3082 dummyinfo = compose_msginfo_new_from_compose(compose);
3083 msginfo = dummyinfo;
3086 if (qmark != NULL) {
3087 #ifdef USE_ENCHANT
3088 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3089 compose->gtkaspell);
3090 #else
3091 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3092 #endif
3093 quote_fmt_scan_string(qmark);
3094 quote_fmt_parse();
3096 buf = quote_fmt_get_buffer();
3097 if (buf == NULL)
3098 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3099 else
3100 Xstrdup_a(quote_str, buf, goto error)
3103 if (fmt && *fmt != '\0') {
3105 if (trimmed_body)
3106 while (*trimmed_body == '\n')
3107 trimmed_body++;
3109 #ifdef USE_ENCHANT
3110 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3111 compose->gtkaspell);
3112 #else
3113 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3114 #endif
3115 if (need_unescape) {
3116 gchar *tmp = NULL;
3118 /* decode \-escape sequences in the internal representation of the quote format */
3119 tmp = g_malloc(strlen(fmt)+1);
3120 pref_get_unescaped_pref(tmp, fmt);
3121 quote_fmt_scan_string(tmp);
3122 quote_fmt_parse();
3123 g_free(tmp);
3124 } else {
3125 quote_fmt_scan_string(fmt);
3126 quote_fmt_parse();
3129 buf = quote_fmt_get_buffer();
3130 if (buf == NULL) {
3131 gint line = quote_fmt_get_line();
3132 alertpanel_error(err_msg, line);
3133 goto error;
3135 } else
3136 buf = "";
3138 prev_autowrap = compose->autowrap;
3139 compose->autowrap = FALSE;
3141 mark = gtk_text_buffer_get_insert(buffer);
3142 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3143 if (g_utf8_validate(buf, -1, NULL)) {
3144 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3145 } else {
3146 gchar *tmpout = NULL;
3147 tmpout = conv_codeset_strdup
3148 (buf, conv_get_locale_charset_str_no_utf8(),
3149 CS_INTERNAL);
3150 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3151 g_free(tmpout);
3152 tmpout = g_malloc(strlen(buf)*2+1);
3153 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3155 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3156 g_free(tmpout);
3159 cursor_pos = quote_fmt_get_cursor_pos();
3160 if (cursor_pos == -1)
3161 cursor_pos = gtk_text_iter_get_offset(&iter);
3162 compose->set_cursor_pos = cursor_pos;
3164 gtk_text_buffer_get_start_iter(buffer, &iter);
3165 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3166 gtk_text_buffer_place_cursor(buffer, &iter);
3168 compose->autowrap = prev_autowrap;
3169 if (compose->autowrap && rewrap)
3170 compose_wrap_all(compose);
3172 goto ok;
3174 error:
3175 buf = NULL;
3177 SIGNAL_UNBLOCK(buffer);
3179 procmsg_msginfo_free( dummyinfo );
3181 return buf;
3184 /* if ml_post is of type addr@host and from is of type
3185 * addr-anything@host, return TRUE
3187 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3189 gchar *left_ml = NULL;
3190 gchar *right_ml = NULL;
3191 gchar *left_from = NULL;
3192 gchar *right_from = NULL;
3193 gboolean result = FALSE;
3195 if (!ml_post || !from)
3196 return FALSE;
3198 left_ml = g_strdup(ml_post);
3199 if (strstr(left_ml, "@")) {
3200 right_ml = strstr(left_ml, "@")+1;
3201 *(strstr(left_ml, "@")) = '\0';
3204 left_from = g_strdup(from);
3205 if (strstr(left_from, "@")) {
3206 right_from = strstr(left_from, "@")+1;
3207 *(strstr(left_from, "@")) = '\0';
3210 if (right_ml && right_from
3211 && !strncmp(left_from, left_ml, strlen(left_ml))
3212 && !strcmp(right_from, right_ml)) {
3213 result = TRUE;
3215 g_free(left_ml);
3216 g_free(left_from);
3218 return result;
3221 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3222 gboolean respect_default_to)
3224 if (!compose)
3225 return;
3226 if (!folder || !folder->prefs)
3227 return;
3229 if (respect_default_to && folder->prefs->enable_default_to) {
3230 compose_entry_append(compose, folder->prefs->default_to,
3231 COMPOSE_TO, PREF_FOLDER);
3232 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3234 if (folder->prefs->enable_default_cc)
3235 compose_entry_append(compose, folder->prefs->default_cc,
3236 COMPOSE_CC, PREF_FOLDER);
3237 if (folder->prefs->enable_default_bcc)
3238 compose_entry_append(compose, folder->prefs->default_bcc,
3239 COMPOSE_BCC, PREF_FOLDER);
3240 if (folder->prefs->enable_default_replyto)
3241 compose_entry_append(compose, folder->prefs->default_replyto,
3242 COMPOSE_REPLYTO, PREF_FOLDER);
3245 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3247 gchar *buf, *buf2;
3248 gchar *p;
3250 if (!compose || !msginfo)
3251 return;
3253 if (msginfo->subject && *msginfo->subject) {
3254 buf = p = g_strdup(msginfo->subject);
3255 p += subject_get_prefix_length(p);
3256 memmove(buf, p, strlen(p) + 1);
3258 buf2 = g_strdup_printf("Re: %s", buf);
3259 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3261 g_free(buf2);
3262 g_free(buf);
3263 } else
3264 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3267 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3268 gboolean to_all, gboolean to_ml,
3269 gboolean to_sender,
3270 gboolean followup_and_reply_to)
3272 GSList *cc_list = NULL;
3273 GSList *cur;
3274 gchar *from = NULL;
3275 gchar *replyto = NULL;
3276 gchar *ac_email = NULL;
3278 gboolean reply_to_ml = FALSE;
3279 gboolean default_reply_to = FALSE;
3281 cm_return_if_fail(compose->account != NULL);
3282 cm_return_if_fail(msginfo != NULL);
3284 reply_to_ml = to_ml && compose->ml_post;
3286 default_reply_to = msginfo->folder &&
3287 msginfo->folder->prefs->enable_default_reply_to;
3289 if (compose->account->protocol != A_NNTP) {
3290 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3292 if (reply_to_ml && !default_reply_to) {
3294 gboolean is_subscr = is_subscription(compose->ml_post,
3295 msginfo->from);
3296 if (!is_subscr) {
3297 /* normal answer to ml post with a reply-to */
3298 compose_entry_append(compose,
3299 compose->ml_post,
3300 COMPOSE_TO, PREF_ML);
3301 if (compose->replyto)
3302 compose_entry_append(compose,
3303 compose->replyto,
3304 COMPOSE_CC, PREF_ML);
3305 } else {
3306 /* answer to subscription confirmation */
3307 if (compose->replyto)
3308 compose_entry_append(compose,
3309 compose->replyto,
3310 COMPOSE_TO, PREF_ML);
3311 else if (msginfo->from)
3312 compose_entry_append(compose,
3313 msginfo->from,
3314 COMPOSE_TO, PREF_ML);
3317 else if (!(to_all || to_sender) && default_reply_to) {
3318 compose_entry_append(compose,
3319 msginfo->folder->prefs->default_reply_to,
3320 COMPOSE_TO, PREF_FOLDER);
3321 compose_entry_mark_default_to(compose,
3322 msginfo->folder->prefs->default_reply_to);
3323 } else {
3324 gchar *tmp1 = NULL;
3325 if (!msginfo->from)
3326 return;
3327 if (to_sender)
3328 compose_entry_append(compose, msginfo->from,
3329 COMPOSE_TO, PREF_NONE);
3330 else if (to_all) {
3331 Xstrdup_a(tmp1, msginfo->from, return);
3332 extract_address(tmp1);
3333 compose_entry_append(compose,
3334 (!account_find_from_address(tmp1, FALSE))
3335 ? msginfo->from :
3336 msginfo->to,
3337 COMPOSE_TO, PREF_NONE);
3338 } else {
3339 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3340 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3341 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3342 if (compose->replyto) {
3343 compose_entry_append(compose,
3344 compose->replyto,
3345 COMPOSE_TO, PREF_NONE);
3346 } else {
3347 compose_entry_append(compose,
3348 msginfo->from ? msginfo->from : "",
3349 COMPOSE_TO, PREF_NONE);
3351 } else {
3352 /* replying to own mail, use original recp */
3353 compose_entry_append(compose,
3354 msginfo->to ? msginfo->to : "",
3355 COMPOSE_TO, PREF_NONE);
3356 compose_entry_append(compose,
3357 msginfo->cc ? msginfo->cc : "",
3358 COMPOSE_CC, PREF_NONE);
3362 } else {
3363 if (to_sender || (compose->followup_to &&
3364 !strncmp(compose->followup_to, "poster", 6)))
3365 compose_entry_append
3366 (compose,
3367 (compose->replyto ? compose->replyto :
3368 msginfo->from ? msginfo->from : ""),
3369 COMPOSE_TO, PREF_NONE);
3371 else if (followup_and_reply_to || to_all) {
3372 compose_entry_append
3373 (compose,
3374 (compose->replyto ? compose->replyto :
3375 msginfo->from ? msginfo->from : ""),
3376 COMPOSE_TO, PREF_NONE);
3378 compose_entry_append
3379 (compose,
3380 compose->followup_to ? compose->followup_to :
3381 compose->newsgroups ? compose->newsgroups : "",
3382 COMPOSE_NEWSGROUPS, PREF_NONE);
3384 else
3385 compose_entry_append
3386 (compose,
3387 compose->followup_to ? compose->followup_to :
3388 compose->newsgroups ? compose->newsgroups : "",
3389 COMPOSE_NEWSGROUPS, PREF_NONE);
3391 compose_reply_set_subject(compose, msginfo);
3393 if (to_ml && compose->ml_post) return;
3394 if (!to_all || compose->account->protocol == A_NNTP) return;
3396 if (compose->replyto) {
3397 Xstrdup_a(replyto, compose->replyto, return);
3398 extract_address(replyto);
3400 if (msginfo->from) {
3401 Xstrdup_a(from, msginfo->from, return);
3402 extract_address(from);
3405 if (replyto && from)
3406 cc_list = address_list_append_with_comments(cc_list, from);
3407 if (to_all && msginfo->folder &&
3408 msginfo->folder->prefs->enable_default_reply_to)
3409 cc_list = address_list_append_with_comments(cc_list,
3410 msginfo->folder->prefs->default_reply_to);
3411 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3412 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3414 ac_email = g_utf8_strdown(compose->account->address, -1);
3416 if (cc_list) {
3417 for (cur = cc_list; cur != NULL; cur = cur->next) {
3418 gchar *addr = g_utf8_strdown(cur->data, -1);
3419 extract_address(addr);
3421 if (strcmp(ac_email, addr))
3422 compose_entry_append(compose, (gchar *)cur->data,
3423 COMPOSE_CC, PREF_NONE);
3424 else
3425 debug_print("Cc address same as compose account's, ignoring\n");
3427 g_free(addr);
3430 slist_free_strings_full(cc_list);
3433 g_free(ac_email);
3436 #define SET_ENTRY(entry, str) \
3438 if (str && *str) \
3439 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3442 #define SET_ADDRESS(type, str) \
3444 if (str && *str) \
3445 compose_entry_append(compose, str, type, PREF_NONE); \
3448 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3450 cm_return_if_fail(msginfo != NULL);
3452 SET_ENTRY(subject_entry, msginfo->subject);
3453 SET_ENTRY(from_name, msginfo->from);
3454 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3455 SET_ADDRESS(COMPOSE_CC, compose->cc);
3456 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3457 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3458 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3459 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3461 compose_update_priority_menu_item(compose);
3462 compose_update_privacy_system_menu_item(compose, FALSE);
3463 compose_show_first_last_header(compose, TRUE);
3466 #undef SET_ENTRY
3467 #undef SET_ADDRESS
3469 static void compose_insert_sig(Compose *compose, gboolean replace)
3471 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3472 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3473 GtkTextMark *mark;
3474 GtkTextIter iter, iter_end;
3475 gint cur_pos, ins_pos;
3476 gboolean prev_autowrap;
3477 gboolean found = FALSE;
3478 gboolean exists = FALSE;
3480 cm_return_if_fail(compose->account != NULL);
3482 BLOCK_WRAP();
3484 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3485 G_CALLBACK(compose_changed_cb),
3486 compose);
3488 mark = gtk_text_buffer_get_insert(buffer);
3489 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3490 cur_pos = gtk_text_iter_get_offset (&iter);
3491 ins_pos = cur_pos;
3493 gtk_text_buffer_get_end_iter(buffer, &iter);
3495 exists = (compose->sig_str != NULL);
3497 if (replace) {
3498 GtkTextIter first_iter, start_iter, end_iter;
3500 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3502 if (!exists || compose->sig_str[0] == '\0')
3503 found = FALSE;
3504 else
3505 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3506 compose->signature_tag);
3508 if (found) {
3509 /* include previous \n\n */
3510 gtk_text_iter_backward_chars(&first_iter, 1);
3511 start_iter = first_iter;
3512 end_iter = first_iter;
3513 /* skip re-start */
3514 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3515 compose->signature_tag);
3516 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3517 compose->signature_tag);
3518 if (found) {
3519 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3520 iter = start_iter;
3525 g_free(compose->sig_str);
3526 compose->sig_str = account_get_signature_str(compose->account);
3528 cur_pos = gtk_text_iter_get_offset(&iter);
3530 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3531 g_free(compose->sig_str);
3532 compose->sig_str = NULL;
3533 } else {
3534 if (compose->sig_inserted == FALSE)
3535 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3536 compose->sig_inserted = TRUE;
3538 cur_pos = gtk_text_iter_get_offset(&iter);
3539 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3540 /* remove \n\n */
3541 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3542 gtk_text_iter_forward_chars(&iter, 1);
3543 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3544 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3546 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3547 cur_pos = gtk_text_buffer_get_char_count (buffer);
3550 /* put the cursor where it should be
3551 * either where the quote_fmt says, either where it was */
3552 if (compose->set_cursor_pos < 0)
3553 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3554 else
3555 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3556 compose->set_cursor_pos);
3558 compose->set_cursor_pos = -1;
3559 gtk_text_buffer_place_cursor(buffer, &iter);
3560 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3561 G_CALLBACK(compose_changed_cb),
3562 compose);
3564 UNBLOCK_WRAP();
3567 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3569 GtkTextView *text;
3570 GtkTextBuffer *buffer;
3571 GtkTextMark *mark;
3572 GtkTextIter iter;
3573 const gchar *cur_encoding;
3574 gchar buf[BUFFSIZE];
3575 gint len;
3576 FILE *fp;
3577 gboolean prev_autowrap;
3578 struct stat file_stat;
3579 int ret;
3580 GString *file_contents = NULL;
3582 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3584 /* get the size of the file we are about to insert */
3585 ret = g_stat(file, &file_stat);
3586 if (ret != 0) {
3587 gchar *shortfile = g_path_get_basename(file);
3588 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3589 g_free(shortfile);
3590 return COMPOSE_INSERT_NO_FILE;
3591 } else if (prefs_common.warn_large_insert == TRUE) {
3593 /* ask user for confirmation if the file is large */
3594 if (prefs_common.warn_large_insert_size < 0 ||
3595 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3596 AlertValue aval;
3597 gchar *msg;
3599 msg = g_strdup_printf(_("You are about to insert a file of %s "
3600 "in the message body. Are you sure you want to do that?"),
3601 to_human_readable(file_stat.st_size));
3602 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3603 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3604 g_free(msg);
3606 /* do we ask for confirmation next time? */
3607 if (aval & G_ALERTDISABLE) {
3608 /* no confirmation next time, disable feature in preferences */
3609 aval &= ~G_ALERTDISABLE;
3610 prefs_common.warn_large_insert = FALSE;
3613 /* abort file insertion if user canceled action */
3614 if (aval != G_ALERTALTERNATE) {
3615 return COMPOSE_INSERT_NO_FILE;
3621 if ((fp = g_fopen(file, "rb")) == NULL) {
3622 FILE_OP_ERROR(file, "fopen");
3623 return COMPOSE_INSERT_READ_ERROR;
3626 prev_autowrap = compose->autowrap;
3627 compose->autowrap = FALSE;
3629 text = GTK_TEXT_VIEW(compose->text);
3630 buffer = gtk_text_view_get_buffer(text);
3631 mark = gtk_text_buffer_get_insert(buffer);
3632 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3634 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3635 G_CALLBACK(text_inserted),
3636 compose);
3638 cur_encoding = conv_get_locale_charset_str_no_utf8();
3640 file_contents = g_string_new("");
3641 while (fgets(buf, sizeof(buf), fp) != NULL) {
3642 gchar *str;
3644 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3645 str = g_strdup(buf);
3646 else
3647 str = conv_codeset_strdup
3648 (buf, cur_encoding, CS_INTERNAL);
3649 if (!str) continue;
3651 /* strip <CR> if DOS/Windows file,
3652 replace <CR> with <LF> if Macintosh file. */
3653 strcrchomp(str);
3654 len = strlen(str);
3655 if (len > 0 && str[len - 1] != '\n') {
3656 while (--len >= 0)
3657 if (str[len] == '\r') str[len] = '\n';
3660 file_contents = g_string_append(file_contents, str);
3661 g_free(str);
3664 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3665 g_string_free(file_contents, TRUE);
3667 compose_changed_cb(NULL, compose);
3668 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3669 G_CALLBACK(text_inserted),
3670 compose);
3671 compose->autowrap = prev_autowrap;
3672 if (compose->autowrap)
3673 compose_wrap_all(compose);
3675 fclose(fp);
3677 return COMPOSE_INSERT_SUCCESS;
3680 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3681 const gchar *filename,
3682 const gchar *content_type,
3683 const gchar *charset)
3685 AttachInfo *ainfo;
3686 GtkTreeIter iter;
3687 FILE *fp;
3688 off_t size;
3689 GAuto *auto_ainfo;
3690 gchar *size_text;
3691 GtkListStore *store;
3692 gchar *name;
3693 gboolean has_binary = FALSE;
3695 if (!is_file_exist(file)) {
3696 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3697 gboolean result = FALSE;
3698 if (file_from_uri && is_file_exist(file_from_uri)) {
3699 result = compose_attach_append(
3700 compose, file_from_uri,
3701 filename, content_type,
3702 charset);
3704 g_free(file_from_uri);
3705 if (result)
3706 return TRUE;
3707 alertpanel_error("File %s doesn't exist\n", filename);
3708 return FALSE;
3710 if ((size = get_file_size(file)) < 0) {
3711 alertpanel_error("Can't get file size of %s\n", filename);
3712 return FALSE;
3715 /* In batch mode, we allow 0-length files to be attached no questions asked */
3716 if (size == 0 && !compose->batch) {
3717 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3718 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3719 GTK_STOCK_CANCEL, _("+_Attach anyway"), NULL, FALSE,
3720 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3721 g_free(msg);
3723 if (aval != G_ALERTALTERNATE) {
3724 return FALSE;
3727 if ((fp = g_fopen(file, "rb")) == NULL) {
3728 alertpanel_error(_("Can't read %s."), filename);
3729 return FALSE;
3731 fclose(fp);
3733 ainfo = g_new0(AttachInfo, 1);
3734 auto_ainfo = g_auto_pointer_new_with_free
3735 (ainfo, (GFreeFunc) compose_attach_info_free);
3736 ainfo->file = g_strdup(file);
3738 if (content_type) {
3739 ainfo->content_type = g_strdup(content_type);
3740 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3741 MsgInfo *msginfo;
3742 MsgFlags flags = {0, 0};
3744 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3745 ainfo->encoding = ENC_7BIT;
3746 else
3747 ainfo->encoding = ENC_8BIT;
3749 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3750 if (msginfo && msginfo->subject)
3751 name = g_strdup(msginfo->subject);
3752 else
3753 name = g_path_get_basename(filename ? filename : file);
3755 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3757 procmsg_msginfo_free(msginfo);
3758 } else {
3759 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3760 ainfo->charset = g_strdup(charset);
3761 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3762 } else {
3763 ainfo->encoding = ENC_BASE64;
3765 name = g_path_get_basename(filename ? filename : file);
3766 ainfo->name = g_strdup(name);
3768 g_free(name);
3769 } else {
3770 ainfo->content_type = procmime_get_mime_type(file);
3771 if (!ainfo->content_type) {
3772 ainfo->content_type =
3773 g_strdup("application/octet-stream");
3774 ainfo->encoding = ENC_BASE64;
3775 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3776 ainfo->encoding =
3777 procmime_get_encoding_for_text_file(file, &has_binary);
3778 else
3779 ainfo->encoding = ENC_BASE64;
3780 name = g_path_get_basename(filename ? filename : file);
3781 ainfo->name = g_strdup(name);
3782 g_free(name);
3785 if (ainfo->name != NULL
3786 && !strcmp(ainfo->name, ".")) {
3787 g_free(ainfo->name);
3788 ainfo->name = NULL;
3791 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3792 g_free(ainfo->content_type);
3793 ainfo->content_type = g_strdup("application/octet-stream");
3794 g_free(ainfo->charset);
3795 ainfo->charset = NULL;
3798 ainfo->size = (goffset)size;
3799 size_text = to_human_readable((goffset)size);
3801 store = GTK_LIST_STORE(gtk_tree_view_get_model
3802 (GTK_TREE_VIEW(compose->attach_clist)));
3804 gtk_list_store_append(store, &iter);
3805 gtk_list_store_set(store, &iter,
3806 COL_MIMETYPE, ainfo->content_type,
3807 COL_SIZE, size_text,
3808 COL_NAME, ainfo->name,
3809 COL_CHARSET, ainfo->charset,
3810 COL_DATA, ainfo,
3811 COL_AUTODATA, auto_ainfo,
3812 -1);
3814 g_auto_pointer_free(auto_ainfo);
3815 compose_attach_update_label(compose);
3816 return TRUE;
3819 static void compose_use_signing(Compose *compose, gboolean use_signing)
3821 compose->use_signing = use_signing;
3822 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3825 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3827 compose->use_encryption = use_encryption;
3828 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3831 #define NEXT_PART_NOT_CHILD(info) \
3833 node = info->node; \
3834 while (node->children) \
3835 node = g_node_last_child(node); \
3836 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3839 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3841 MimeInfo *mimeinfo;
3842 MimeInfo *child;
3843 MimeInfo *firsttext = NULL;
3844 MimeInfo *encrypted = NULL;
3845 GNode *node;
3846 gchar *outfile;
3847 const gchar *partname = NULL;
3849 mimeinfo = procmime_scan_message(msginfo);
3850 if (!mimeinfo) return;
3852 if (mimeinfo->node->children == NULL) {
3853 procmime_mimeinfo_free_all(mimeinfo);
3854 return;
3857 /* find first content part */
3858 child = (MimeInfo *) mimeinfo->node->children->data;
3859 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3860 child = (MimeInfo *)child->node->children->data;
3862 if (child) {
3863 if (child->type == MIMETYPE_TEXT) {
3864 firsttext = child;
3865 debug_print("First text part found\n");
3866 } else if (compose->mode == COMPOSE_REEDIT &&
3867 child->type == MIMETYPE_APPLICATION &&
3868 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3869 encrypted = (MimeInfo *)child->node->parent->data;
3872 child = (MimeInfo *) mimeinfo->node->children->data;
3873 while (child != NULL) {
3874 gint err;
3876 if (child == encrypted) {
3877 /* skip this part of tree */
3878 NEXT_PART_NOT_CHILD(child);
3879 continue;
3882 if (child->type == MIMETYPE_MULTIPART) {
3883 /* get the actual content */
3884 child = procmime_mimeinfo_next(child);
3885 continue;
3888 if (child == firsttext) {
3889 child = procmime_mimeinfo_next(child);
3890 continue;
3893 outfile = procmime_get_tmp_file_name(child);
3894 if ((err = procmime_get_part(outfile, child)) < 0)
3895 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3896 else {
3897 gchar *content_type;
3899 content_type = procmime_get_content_type_str(child->type, child->subtype);
3901 /* if we meet a pgp signature, we don't attach it, but
3902 * we force signing. */
3903 if ((strcmp(content_type, "application/pgp-signature") &&
3904 strcmp(content_type, "application/pkcs7-signature") &&
3905 strcmp(content_type, "application/x-pkcs7-signature"))
3906 || compose->mode == COMPOSE_REDIRECT) {
3907 partname = procmime_mimeinfo_get_parameter(child, "filename");
3908 if (partname == NULL)
3909 partname = procmime_mimeinfo_get_parameter(child, "name");
3910 if (partname == NULL)
3911 partname = "";
3912 compose_attach_append(compose, outfile,
3913 partname, content_type,
3914 procmime_mimeinfo_get_parameter(child, "charset"));
3915 } else {
3916 compose_force_signing(compose, compose->account, NULL);
3918 g_free(content_type);
3920 g_free(outfile);
3921 NEXT_PART_NOT_CHILD(child);
3923 procmime_mimeinfo_free_all(mimeinfo);
3926 #undef NEXT_PART_NOT_CHILD
3930 typedef enum {
3931 WAIT_FOR_INDENT_CHAR,
3932 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3933 } IndentState;
3935 /* return indent length, we allow:
3936 indent characters followed by indent characters or spaces/tabs,
3937 alphabets and numbers immediately followed by indent characters,
3938 and the repeating sequences of the above
3939 If quote ends with multiple spaces, only the first one is included. */
3940 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3941 const GtkTextIter *start, gint *len)
3943 GtkTextIter iter = *start;
3944 gunichar wc;
3945 gchar ch[6];
3946 gint clen;
3947 IndentState state = WAIT_FOR_INDENT_CHAR;
3948 gboolean is_space;
3949 gboolean is_indent;
3950 gint alnum_count = 0;
3951 gint space_count = 0;
3952 gint quote_len = 0;
3954 if (prefs_common.quote_chars == NULL) {
3955 return 0 ;
3958 while (!gtk_text_iter_ends_line(&iter)) {
3959 wc = gtk_text_iter_get_char(&iter);
3960 if (g_unichar_iswide(wc))
3961 break;
3962 clen = g_unichar_to_utf8(wc, ch);
3963 if (clen != 1)
3964 break;
3966 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3967 is_space = g_unichar_isspace(wc);
3969 if (state == WAIT_FOR_INDENT_CHAR) {
3970 if (!is_indent && !g_unichar_isalnum(wc))
3971 break;
3972 if (is_indent) {
3973 quote_len += alnum_count + space_count + 1;
3974 alnum_count = space_count = 0;
3975 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3976 } else
3977 alnum_count++;
3978 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3979 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3980 break;
3981 if (is_space)
3982 space_count++;
3983 else if (is_indent) {
3984 quote_len += alnum_count + space_count + 1;
3985 alnum_count = space_count = 0;
3986 } else {
3987 alnum_count++;
3988 state = WAIT_FOR_INDENT_CHAR;
3992 gtk_text_iter_forward_char(&iter);
3995 if (quote_len > 0 && space_count > 0)
3996 quote_len++;
3998 if (len)
3999 *len = quote_len;
4001 if (quote_len > 0) {
4002 iter = *start;
4003 gtk_text_iter_forward_chars(&iter, quote_len);
4004 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4007 return NULL;
4010 /* return >0 if the line is itemized */
4011 static int compose_itemized_length(GtkTextBuffer *buffer,
4012 const GtkTextIter *start)
4014 GtkTextIter iter = *start;
4015 gunichar wc;
4016 gchar ch[6];
4017 gint clen;
4018 gint len = 0;
4019 if (gtk_text_iter_ends_line(&iter))
4020 return 0;
4022 while (1) {
4023 len++;
4024 wc = gtk_text_iter_get_char(&iter);
4025 if (!g_unichar_isspace(wc))
4026 break;
4027 gtk_text_iter_forward_char(&iter);
4028 if (gtk_text_iter_ends_line(&iter))
4029 return 0;
4032 clen = g_unichar_to_utf8(wc, ch);
4033 if (clen != 1)
4034 return 0;
4036 if (!strchr("*-+", ch[0]))
4037 return 0;
4039 gtk_text_iter_forward_char(&iter);
4040 if (gtk_text_iter_ends_line(&iter))
4041 return 0;
4042 wc = gtk_text_iter_get_char(&iter);
4043 if (g_unichar_isspace(wc)) {
4044 return len+1;
4046 return 0;
4049 /* return the string at the start of the itemization */
4050 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4051 const GtkTextIter *start)
4053 GtkTextIter iter = *start;
4054 gunichar wc;
4055 gint len = 0;
4056 GString *item_chars = g_string_new("");
4057 gchar *str = NULL;
4059 if (gtk_text_iter_ends_line(&iter))
4060 return NULL;
4062 while (1) {
4063 len++;
4064 wc = gtk_text_iter_get_char(&iter);
4065 if (!g_unichar_isspace(wc))
4066 break;
4067 gtk_text_iter_forward_char(&iter);
4068 if (gtk_text_iter_ends_line(&iter))
4069 break;
4070 g_string_append_unichar(item_chars, wc);
4073 str = item_chars->str;
4074 g_string_free(item_chars, FALSE);
4075 return str;
4078 /* return the number of spaces at a line's start */
4079 static int compose_left_offset_length(GtkTextBuffer *buffer,
4080 const GtkTextIter *start)
4082 GtkTextIter iter = *start;
4083 gunichar wc;
4084 gint len = 0;
4085 if (gtk_text_iter_ends_line(&iter))
4086 return 0;
4088 while (1) {
4089 wc = gtk_text_iter_get_char(&iter);
4090 if (!g_unichar_isspace(wc))
4091 break;
4092 len++;
4093 gtk_text_iter_forward_char(&iter);
4094 if (gtk_text_iter_ends_line(&iter))
4095 return 0;
4098 gtk_text_iter_forward_char(&iter);
4099 if (gtk_text_iter_ends_line(&iter))
4100 return 0;
4101 return len;
4104 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4105 const GtkTextIter *start,
4106 GtkTextIter *break_pos,
4107 gint max_col,
4108 gint quote_len)
4110 GtkTextIter iter = *start, line_end = *start;
4111 PangoLogAttr *attrs;
4112 gchar *str;
4113 gchar *p;
4114 gint len;
4115 gint i;
4116 gint col = 0;
4117 gint pos = 0;
4118 gboolean can_break = FALSE;
4119 gboolean do_break = FALSE;
4120 gboolean was_white = FALSE;
4121 gboolean prev_dont_break = FALSE;
4123 gtk_text_iter_forward_to_line_end(&line_end);
4124 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4125 len = g_utf8_strlen(str, -1);
4127 if (len == 0) {
4128 g_free(str);
4129 g_warning("compose_get_line_break_pos: len = 0!\n");
4130 return FALSE;
4133 /* g_print("breaking line: %d: %s (len = %d)\n",
4134 gtk_text_iter_get_line(&iter), str, len); */
4136 attrs = g_new(PangoLogAttr, len + 1);
4138 pango_default_break(str, -1, NULL, attrs, len + 1);
4140 p = str;
4142 /* skip quote and leading spaces */
4143 for (i = 0; *p != '\0' && i < len; i++) {
4144 gunichar wc;
4146 wc = g_utf8_get_char(p);
4147 if (i >= quote_len && !g_unichar_isspace(wc))
4148 break;
4149 if (g_unichar_iswide(wc))
4150 col += 2;
4151 else if (*p == '\t')
4152 col += 8;
4153 else
4154 col++;
4155 p = g_utf8_next_char(p);
4158 for (; *p != '\0' && i < len; i++) {
4159 PangoLogAttr *attr = attrs + i;
4160 gunichar wc;
4161 gint uri_len;
4163 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4164 pos = i;
4166 was_white = attr->is_white;
4168 /* don't wrap URI */
4169 if ((uri_len = get_uri_len(p)) > 0) {
4170 col += uri_len;
4171 if (pos > 0 && col > max_col) {
4172 do_break = TRUE;
4173 break;
4175 i += uri_len - 1;
4176 p += uri_len;
4177 can_break = TRUE;
4178 continue;
4181 wc = g_utf8_get_char(p);
4182 if (g_unichar_iswide(wc)) {
4183 col += 2;
4184 if (prev_dont_break && can_break && attr->is_line_break)
4185 pos = i;
4186 } else if (*p == '\t')
4187 col += 8;
4188 else
4189 col++;
4190 if (pos > 0 && col > max_col) {
4191 do_break = TRUE;
4192 break;
4195 if (*p == '-' || *p == '/')
4196 prev_dont_break = TRUE;
4197 else
4198 prev_dont_break = FALSE;
4200 p = g_utf8_next_char(p);
4201 can_break = TRUE;
4204 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4206 g_free(attrs);
4207 g_free(str);
4209 *break_pos = *start;
4210 gtk_text_iter_set_line_offset(break_pos, pos);
4212 return do_break;
4215 static gboolean compose_join_next_line(Compose *compose,
4216 GtkTextBuffer *buffer,
4217 GtkTextIter *iter,
4218 const gchar *quote_str)
4220 GtkTextIter iter_ = *iter, cur, prev, next, end;
4221 PangoLogAttr attrs[3];
4222 gchar *str;
4223 gchar *next_quote_str;
4224 gunichar wc1, wc2;
4225 gint quote_len;
4226 gboolean keep_cursor = FALSE;
4228 if (!gtk_text_iter_forward_line(&iter_) ||
4229 gtk_text_iter_ends_line(&iter_)) {
4230 return FALSE;
4232 next_quote_str = compose_get_quote_str(buffer, &iter_, &quote_len);
4234 if ((quote_str || next_quote_str) &&
4235 strcmp2(quote_str, next_quote_str) != 0) {
4236 g_free(next_quote_str);
4237 return FALSE;
4239 g_free(next_quote_str);
4241 end = iter_;
4242 if (quote_len > 0) {
4243 gtk_text_iter_forward_chars(&end, quote_len);
4244 if (gtk_text_iter_ends_line(&end)) {
4245 return FALSE;
4249 /* don't join itemized lines */
4250 if (compose_itemized_length(buffer, &end) > 0) {
4251 return FALSE;
4254 /* don't join signature separator */
4255 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4256 return FALSE;
4258 /* delete quote str */
4259 if (quote_len > 0)
4260 gtk_text_buffer_delete(buffer, &iter_, &end);
4262 /* don't join line breaks put by the user */
4263 prev = cur = iter_;
4264 gtk_text_iter_backward_char(&cur);
4265 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4266 gtk_text_iter_forward_char(&cur);
4267 *iter = cur;
4268 return FALSE;
4270 gtk_text_iter_forward_char(&cur);
4271 /* delete linebreak and extra spaces */
4272 while (gtk_text_iter_backward_char(&cur)) {
4273 wc1 = gtk_text_iter_get_char(&cur);
4274 if (!g_unichar_isspace(wc1))
4275 break;
4276 prev = cur;
4278 next = cur = iter_;
4279 while (!gtk_text_iter_ends_line(&cur)) {
4280 wc1 = gtk_text_iter_get_char(&cur);
4281 if (!g_unichar_isspace(wc1))
4282 break;
4283 gtk_text_iter_forward_char(&cur);
4284 next = cur;
4286 if (!gtk_text_iter_equal(&prev, &next)) {
4287 GtkTextMark *mark;
4289 mark = gtk_text_buffer_get_insert(buffer);
4290 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4291 if (gtk_text_iter_equal(&prev, &cur))
4292 keep_cursor = TRUE;
4293 gtk_text_buffer_delete(buffer, &prev, &next);
4295 iter_ = prev;
4297 /* insert space if required */
4298 gtk_text_iter_backward_char(&prev);
4299 wc1 = gtk_text_iter_get_char(&prev);
4300 wc2 = gtk_text_iter_get_char(&next);
4301 gtk_text_iter_forward_char(&next);
4302 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4303 pango_default_break(str, -1, NULL, attrs, 3);
4304 if (!attrs[1].is_line_break ||
4305 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4306 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4307 if (keep_cursor) {
4308 gtk_text_iter_backward_char(&iter_);
4309 gtk_text_buffer_place_cursor(buffer, &iter_);
4312 g_free(str);
4314 *iter = iter_;
4315 return TRUE;
4318 #define ADD_TXT_POS(bp_, ep_, pti_) \
4319 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4320 last = last->next; \
4321 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4322 last->next = NULL; \
4323 } else { \
4324 g_warning("alloc error scanning URIs\n"); \
4327 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4329 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4330 GtkTextBuffer *buffer;
4331 GtkTextIter iter, break_pos, end_of_line;
4332 gchar *quote_str = NULL;
4333 gint quote_len;
4334 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4335 gboolean prev_autowrap = compose->autowrap;
4336 gint startq_offset = -1, noq_offset = -1;
4337 gint uri_start = -1, uri_stop = -1;
4338 gint nouri_start = -1, nouri_stop = -1;
4339 gint num_blocks = 0;
4340 gint quotelevel = -1;
4341 gboolean modified = force;
4342 gboolean removed = FALSE;
4343 gboolean modified_before_remove = FALSE;
4344 gint lines = 0;
4345 gboolean start = TRUE;
4346 gint itemized_len = 0, rem_item_len = 0;
4347 gchar *itemized_chars = NULL;
4348 gboolean item_continuation = FALSE;
4350 if (force) {
4351 modified = TRUE;
4353 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4354 modified = TRUE;
4357 compose->autowrap = FALSE;
4359 buffer = gtk_text_view_get_buffer(text);
4360 undo_wrapping(compose->undostruct, TRUE);
4361 if (par_iter) {
4362 iter = *par_iter;
4363 } else {
4364 GtkTextMark *mark;
4365 mark = gtk_text_buffer_get_insert(buffer);
4366 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4370 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4371 if (gtk_text_iter_ends_line(&iter)) {
4372 while (gtk_text_iter_ends_line(&iter) &&
4373 gtk_text_iter_forward_line(&iter))
4375 } else {
4376 while (gtk_text_iter_backward_line(&iter)) {
4377 if (gtk_text_iter_ends_line(&iter)) {
4378 gtk_text_iter_forward_line(&iter);
4379 break;
4383 } else {
4384 /* move to line start */
4385 gtk_text_iter_set_line_offset(&iter, 0);
4388 itemized_len = compose_itemized_length(buffer, &iter);
4390 if (!itemized_len) {
4391 itemized_len = compose_left_offset_length(buffer, &iter);
4392 item_continuation = TRUE;
4395 if (itemized_len)
4396 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4398 /* go until paragraph end (empty line) */
4399 while (start || !gtk_text_iter_ends_line(&iter)) {
4400 gchar *scanpos = NULL;
4401 /* parse table - in order of priority */
4402 struct table {
4403 const gchar *needle; /* token */
4405 /* token search function */
4406 gchar *(*search) (const gchar *haystack,
4407 const gchar *needle);
4408 /* part parsing function */
4409 gboolean (*parse) (const gchar *start,
4410 const gchar *scanpos,
4411 const gchar **bp_,
4412 const gchar **ep_,
4413 gboolean hdr);
4414 /* part to URI function */
4415 gchar *(*build_uri) (const gchar *bp,
4416 const gchar *ep);
4419 static struct table parser[] = {
4420 {"http://", strcasestr, get_uri_part, make_uri_string},
4421 {"https://", strcasestr, get_uri_part, make_uri_string},
4422 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4423 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4424 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4425 {"www.", strcasestr, get_uri_part, make_http_string},
4426 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4427 {"@", strcasestr, get_email_part, make_email_string}
4429 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4430 gint last_index = PARSE_ELEMS;
4431 gint n;
4432 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4433 gint walk_pos;
4435 start = FALSE;
4436 if (!prev_autowrap && num_blocks == 0) {
4437 num_blocks++;
4438 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4439 G_CALLBACK(text_inserted),
4440 compose);
4442 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4443 goto colorize;
4445 uri_start = uri_stop = -1;
4446 quote_len = 0;
4447 quote_str = compose_get_quote_str(buffer, &iter, &quote_len);
4449 if (quote_str) {
4450 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4451 if (startq_offset == -1)
4452 startq_offset = gtk_text_iter_get_offset(&iter);
4453 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4454 if (quotelevel > 2) {
4455 /* recycle colors */
4456 if (prefs_common.recycle_quote_colors)
4457 quotelevel %= 3;
4458 else
4459 quotelevel = 2;
4461 if (!wrap_quote) {
4462 goto colorize;
4464 } else {
4465 if (startq_offset == -1)
4466 noq_offset = gtk_text_iter_get_offset(&iter);
4467 quotelevel = -1;
4470 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4471 goto colorize;
4473 if (gtk_text_iter_ends_line(&iter)) {
4474 goto colorize;
4475 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4476 prefs_common.linewrap_len,
4477 quote_len)) {
4478 GtkTextIter prev, next, cur;
4479 if (prev_autowrap != FALSE || force) {
4480 compose->automatic_break = TRUE;
4481 modified = TRUE;
4482 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4483 compose->automatic_break = FALSE;
4484 if (itemized_len && compose->autoindent) {
4485 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4486 if (!item_continuation)
4487 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4489 } else if (quote_str && wrap_quote) {
4490 compose->automatic_break = TRUE;
4491 modified = TRUE;
4492 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4493 compose->automatic_break = FALSE;
4494 if (itemized_len && compose->autoindent) {
4495 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4496 if (!item_continuation)
4497 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4499 } else
4500 goto colorize;
4501 /* remove trailing spaces */
4502 cur = break_pos;
4503 rem_item_len = itemized_len;
4504 while (compose->autoindent && rem_item_len-- > 0)
4505 gtk_text_iter_backward_char(&cur);
4506 gtk_text_iter_backward_char(&cur);
4508 prev = next = cur;
4509 while (!gtk_text_iter_starts_line(&cur)) {
4510 gunichar wc;
4512 gtk_text_iter_backward_char(&cur);
4513 wc = gtk_text_iter_get_char(&cur);
4514 if (!g_unichar_isspace(wc))
4515 break;
4516 prev = cur;
4518 if (!gtk_text_iter_equal(&prev, &next)) {
4519 gtk_text_buffer_delete(buffer, &prev, &next);
4520 break_pos = next;
4521 gtk_text_iter_forward_char(&break_pos);
4524 if (quote_str)
4525 gtk_text_buffer_insert(buffer, &break_pos,
4526 quote_str, -1);
4528 iter = break_pos;
4529 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4531 /* move iter to current line start */
4532 gtk_text_iter_set_line_offset(&iter, 0);
4533 if (quote_str) {
4534 g_free(quote_str);
4535 quote_str = NULL;
4537 continue;
4538 } else {
4539 /* move iter to next line start */
4540 iter = break_pos;
4541 lines++;
4544 colorize:
4545 if (!prev_autowrap && num_blocks > 0) {
4546 num_blocks--;
4547 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4548 G_CALLBACK(text_inserted),
4549 compose);
4551 end_of_line = iter;
4552 while (!gtk_text_iter_ends_line(&end_of_line)) {
4553 gtk_text_iter_forward_char(&end_of_line);
4555 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4557 nouri_start = gtk_text_iter_get_offset(&iter);
4558 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4560 walk_pos = gtk_text_iter_get_offset(&iter);
4561 /* FIXME: this looks phony. scanning for anything in the parse table */
4562 for (n = 0; n < PARSE_ELEMS; n++) {
4563 gchar *tmp;
4565 tmp = parser[n].search(walk, parser[n].needle);
4566 if (tmp) {
4567 if (scanpos == NULL || tmp < scanpos) {
4568 scanpos = tmp;
4569 last_index = n;
4574 bp = ep = 0;
4575 if (scanpos) {
4576 /* check if URI can be parsed */
4577 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4578 (const gchar **)&ep, FALSE)
4579 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4580 walk = ep;
4581 } else
4582 walk = scanpos +
4583 strlen(parser[last_index].needle);
4585 if (bp && ep) {
4586 uri_start = walk_pos + (bp - o_walk);
4587 uri_stop = walk_pos + (ep - o_walk);
4589 g_free(o_walk);
4590 o_walk = NULL;
4591 gtk_text_iter_forward_line(&iter);
4592 g_free(quote_str);
4593 quote_str = NULL;
4594 if (startq_offset != -1) {
4595 GtkTextIter startquote, endquote;
4596 gtk_text_buffer_get_iter_at_offset(
4597 buffer, &startquote, startq_offset);
4598 endquote = iter;
4600 switch (quotelevel) {
4601 case 0:
4602 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4603 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4604 gtk_text_buffer_apply_tag_by_name(
4605 buffer, "quote0", &startquote, &endquote);
4606 gtk_text_buffer_remove_tag_by_name(
4607 buffer, "quote1", &startquote, &endquote);
4608 gtk_text_buffer_remove_tag_by_name(
4609 buffer, "quote2", &startquote, &endquote);
4610 modified = TRUE;
4612 break;
4613 case 1:
4614 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4615 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4616 gtk_text_buffer_apply_tag_by_name(
4617 buffer, "quote1", &startquote, &endquote);
4618 gtk_text_buffer_remove_tag_by_name(
4619 buffer, "quote0", &startquote, &endquote);
4620 gtk_text_buffer_remove_tag_by_name(
4621 buffer, "quote2", &startquote, &endquote);
4622 modified = TRUE;
4624 break;
4625 case 2:
4626 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4627 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4628 gtk_text_buffer_apply_tag_by_name(
4629 buffer, "quote2", &startquote, &endquote);
4630 gtk_text_buffer_remove_tag_by_name(
4631 buffer, "quote0", &startquote, &endquote);
4632 gtk_text_buffer_remove_tag_by_name(
4633 buffer, "quote1", &startquote, &endquote);
4634 modified = TRUE;
4636 break;
4638 startq_offset = -1;
4639 } else if (noq_offset != -1) {
4640 GtkTextIter startnoquote, endnoquote;
4641 gtk_text_buffer_get_iter_at_offset(
4642 buffer, &startnoquote, noq_offset);
4643 endnoquote = iter;
4645 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4646 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4647 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4648 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4649 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4650 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4651 gtk_text_buffer_remove_tag_by_name(
4652 buffer, "quote0", &startnoquote, &endnoquote);
4653 gtk_text_buffer_remove_tag_by_name(
4654 buffer, "quote1", &startnoquote, &endnoquote);
4655 gtk_text_buffer_remove_tag_by_name(
4656 buffer, "quote2", &startnoquote, &endnoquote);
4657 modified = TRUE;
4659 noq_offset = -1;
4662 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4663 GtkTextIter nouri_start_iter, nouri_end_iter;
4664 gtk_text_buffer_get_iter_at_offset(
4665 buffer, &nouri_start_iter, nouri_start);
4666 gtk_text_buffer_get_iter_at_offset(
4667 buffer, &nouri_end_iter, nouri_stop);
4668 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4669 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4670 gtk_text_buffer_remove_tag_by_name(
4671 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4672 modified_before_remove = modified;
4673 modified = TRUE;
4674 removed = TRUE;
4677 if (uri_start >= 0 && uri_stop > 0) {
4678 GtkTextIter uri_start_iter, uri_end_iter, back;
4679 gtk_text_buffer_get_iter_at_offset(
4680 buffer, &uri_start_iter, uri_start);
4681 gtk_text_buffer_get_iter_at_offset(
4682 buffer, &uri_end_iter, uri_stop);
4683 back = uri_end_iter;
4684 gtk_text_iter_backward_char(&back);
4685 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4686 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4687 gtk_text_buffer_apply_tag_by_name(
4688 buffer, "link", &uri_start_iter, &uri_end_iter);
4689 modified = TRUE;
4690 if (removed && !modified_before_remove) {
4691 modified = FALSE;
4695 if (!modified) {
4696 // debug_print("not modified, out after %d lines\n", lines);
4697 goto end;
4700 // debug_print("modified, out after %d lines\n", lines);
4701 end:
4702 g_free(itemized_chars);
4703 if (par_iter)
4704 *par_iter = iter;
4705 undo_wrapping(compose->undostruct, FALSE);
4706 compose->autowrap = prev_autowrap;
4708 return modified;
4711 void compose_action_cb(void *data)
4713 Compose *compose = (Compose *)data;
4714 compose_wrap_all(compose);
4717 static void compose_wrap_all(Compose *compose)
4719 compose_wrap_all_full(compose, FALSE);
4722 static void compose_wrap_all_full(Compose *compose, gboolean force)
4724 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4725 GtkTextBuffer *buffer;
4726 GtkTextIter iter;
4727 gboolean modified = TRUE;
4729 buffer = gtk_text_view_get_buffer(text);
4731 gtk_text_buffer_get_start_iter(buffer, &iter);
4733 undo_wrapping(compose->undostruct, TRUE);
4735 while (!gtk_text_iter_is_end(&iter) && modified)
4736 modified = compose_beautify_paragraph(compose, &iter, force);
4738 undo_wrapping(compose->undostruct, FALSE);
4742 static void compose_set_title(Compose *compose)
4744 gchar *str;
4745 gchar *edited;
4746 gchar *subject;
4748 edited = compose->modified ? _(" [Edited]") : "";
4750 subject = gtk_editable_get_chars(
4751 GTK_EDITABLE(compose->subject_entry), 0, -1);
4753 #ifndef GENERIC_UMPC
4754 if (subject && strlen(subject))
4755 str = g_strdup_printf(_("%s - Compose message%s"),
4756 subject, edited);
4757 else
4758 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4759 #else
4760 str = g_strdup(_("Compose message"));
4761 #endif
4763 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4764 g_free(str);
4765 g_free(subject);
4769 * compose_current_mail_account:
4771 * Find a current mail account (the currently selected account, or the
4772 * default account, if a news account is currently selected). If a
4773 * mail account cannot be found, display an error message.
4775 * Return value: Mail account, or NULL if not found.
4777 static PrefsAccount *
4778 compose_current_mail_account(void)
4780 PrefsAccount *ac;
4782 if (cur_account && cur_account->protocol != A_NNTP)
4783 ac = cur_account;
4784 else {
4785 ac = account_get_default();
4786 if (!ac || ac->protocol == A_NNTP) {
4787 alertpanel_error(_("Account for sending mail is not specified.\n"
4788 "Please select a mail account before sending."));
4789 return NULL;
4792 return ac;
4795 #define QUOTE_IF_REQUIRED(out, str) \
4797 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4798 gchar *__tmp; \
4799 gint len; \
4801 len = strlen(str) + 3; \
4802 if ((__tmp = alloca(len)) == NULL) { \
4803 g_warning("can't allocate memory\n"); \
4804 g_string_free(header, TRUE); \
4805 return NULL; \
4807 g_snprintf(__tmp, len, "\"%s\"", str); \
4808 out = __tmp; \
4809 } else { \
4810 gchar *__tmp; \
4812 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4813 g_warning("can't allocate memory\n"); \
4814 g_string_free(header, TRUE); \
4815 return NULL; \
4816 } else \
4817 strcpy(__tmp, str); \
4819 out = __tmp; \
4823 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4825 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4826 gchar *__tmp; \
4827 gint len; \
4829 len = strlen(str) + 3; \
4830 if ((__tmp = alloca(len)) == NULL) { \
4831 g_warning("can't allocate memory\n"); \
4832 errret; \
4834 g_snprintf(__tmp, len, "\"%s\"", str); \
4835 out = __tmp; \
4836 } else { \
4837 gchar *__tmp; \
4839 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4840 g_warning("can't allocate memory\n"); \
4841 errret; \
4842 } else \
4843 strcpy(__tmp, str); \
4845 out = __tmp; \
4849 static void compose_select_account(Compose *compose, PrefsAccount *account,
4850 gboolean init)
4852 gchar *from = NULL, *header = NULL;
4853 ComposeHeaderEntry *header_entry;
4854 #if GTK_CHECK_VERSION(2, 24, 0)
4855 GtkTreeIter iter;
4856 #endif
4858 cm_return_if_fail(account != NULL);
4860 compose->account = account;
4861 if (account->name && *account->name) {
4862 gchar *buf, *qbuf;
4863 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4864 qbuf = escape_internal_quotes(buf, '"');
4865 from = g_strdup_printf("%s <%s>",
4866 qbuf, account->address);
4867 if (qbuf != buf)
4868 g_free(qbuf);
4869 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4870 } else {
4871 from = g_strdup_printf("<%s>",
4872 account->address);
4873 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4876 g_free(from);
4878 compose_set_title(compose);
4880 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4881 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4882 else
4883 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4884 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4885 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4886 else
4887 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4889 activate_privacy_system(compose, account, FALSE);
4891 if (!init && compose->mode != COMPOSE_REDIRECT) {
4892 undo_block(compose->undostruct);
4893 compose_insert_sig(compose, TRUE);
4894 undo_unblock(compose->undostruct);
4897 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4898 #if !GTK_CHECK_VERSION(2, 24, 0)
4899 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4900 #else
4901 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4902 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4903 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4904 #endif
4906 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4907 if (account->protocol == A_NNTP) {
4908 if (!strcmp(header, _("To:")))
4909 combobox_select_by_text(
4910 GTK_COMBO_BOX(header_entry->combo),
4911 _("Newsgroups:"));
4912 } else {
4913 if (!strcmp(header, _("Newsgroups:")))
4914 combobox_select_by_text(
4915 GTK_COMBO_BOX(header_entry->combo),
4916 _("To:"));
4920 g_free(header);
4922 #ifdef USE_ENCHANT
4923 /* use account's dict info if set */
4924 if (compose->gtkaspell) {
4925 if (account->enable_default_dictionary)
4926 gtkaspell_change_dict(compose->gtkaspell,
4927 account->default_dictionary, FALSE);
4928 if (account->enable_default_alt_dictionary)
4929 gtkaspell_change_alt_dict(compose->gtkaspell,
4930 account->default_alt_dictionary);
4931 if (account->enable_default_dictionary
4932 || account->enable_default_alt_dictionary)
4933 compose_spell_menu_changed(compose);
4935 #endif
4938 gboolean compose_check_for_valid_recipient(Compose *compose) {
4939 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4940 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4941 gboolean recipient_found = FALSE;
4942 GSList *list;
4943 gchar **strptr;
4945 /* free to and newsgroup list */
4946 slist_free_strings_full(compose->to_list);
4947 compose->to_list = NULL;
4949 slist_free_strings_full(compose->newsgroup_list);
4950 compose->newsgroup_list = NULL;
4952 /* search header entries for to and newsgroup entries */
4953 for (list = compose->header_list; list; list = list->next) {
4954 gchar *header;
4955 gchar *entry;
4956 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4957 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4958 g_strstrip(entry);
4959 g_strstrip(header);
4960 if (entry[0] != '\0') {
4961 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4962 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4963 compose->to_list = address_list_append(compose->to_list, entry);
4964 recipient_found = TRUE;
4967 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4968 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4969 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4970 recipient_found = TRUE;
4974 g_free(header);
4975 g_free(entry);
4977 return recipient_found;
4980 static gboolean compose_check_for_set_recipients(Compose *compose)
4982 if (compose->account->set_autocc && compose->account->auto_cc) {
4983 gboolean found_other = FALSE;
4984 GSList *list;
4985 /* search header entries for to and newsgroup entries */
4986 for (list = compose->header_list; list; list = list->next) {
4987 gchar *entry;
4988 gchar *header;
4989 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4990 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4991 g_strstrip(entry);
4992 g_strstrip(header);
4993 if (strcmp(entry, compose->account->auto_cc)
4994 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4995 found_other = TRUE;
4996 g_free(entry);
4997 break;
4999 g_free(entry);
5000 g_free(header);
5002 if (!found_other) {
5003 AlertValue aval;
5004 if (compose->batch) {
5005 gtk_widget_show_all(compose->window);
5007 aval = alertpanel(_("Send"),
5008 _("The only recipient is the default CC address. Send anyway?"),
5009 GTK_STOCK_CANCEL, _("+_Send"), NULL);
5010 if (aval != G_ALERTALTERNATE)
5011 return FALSE;
5014 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5015 gboolean found_other = FALSE;
5016 GSList *list;
5017 /* search header entries for to and newsgroup entries */
5018 for (list = compose->header_list; list; list = list->next) {
5019 gchar *entry;
5020 gchar *header;
5021 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5022 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5023 g_strstrip(entry);
5024 g_strstrip(header);
5025 if (strcmp(entry, compose->account->auto_bcc)
5026 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5027 found_other = TRUE;
5028 g_free(entry);
5029 break;
5031 g_free(entry);
5032 g_free(header);
5034 if (!found_other) {
5035 AlertValue aval;
5036 if (compose->batch) {
5037 gtk_widget_show_all(compose->window);
5039 aval = alertpanel(_("Send"),
5040 _("The only recipient is the default BCC address. Send anyway?"),
5041 GTK_STOCK_CANCEL, _("+_Send"), NULL);
5042 if (aval != G_ALERTALTERNATE)
5043 return FALSE;
5046 return TRUE;
5049 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5051 const gchar *str;
5053 if (compose_check_for_valid_recipient(compose) == FALSE) {
5054 if (compose->batch) {
5055 gtk_widget_show_all(compose->window);
5057 alertpanel_error(_("Recipient is not specified."));
5058 return FALSE;
5061 if (compose_check_for_set_recipients(compose) == FALSE) {
5062 return FALSE;
5065 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5066 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5067 if (*str == '\0' && check_everything == TRUE &&
5068 compose->mode != COMPOSE_REDIRECT) {
5069 AlertValue aval;
5070 gchar *button_label;
5071 gchar *message;
5073 if (compose->sending)
5074 button_label = _("+_Send");
5075 else
5076 button_label = _("+_Queue");
5077 message = g_strdup_printf(_("Subject is empty. %s"),
5078 compose->sending?_("Send it anyway?"):
5079 _("Queue it anyway?"));
5081 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5082 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5083 ALERT_QUESTION, G_ALERTDEFAULT);
5084 g_free(message);
5085 if (aval & G_ALERTDISABLE) {
5086 aval &= ~G_ALERTDISABLE;
5087 prefs_common.warn_empty_subj = FALSE;
5089 if (aval != G_ALERTALTERNATE)
5090 return FALSE;
5094 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5095 return FALSE;
5097 return TRUE;
5100 gint compose_send(Compose *compose)
5102 gint msgnum;
5103 FolderItem *folder = NULL;
5104 gint val = -1;
5105 gchar *msgpath = NULL;
5106 gboolean discard_window = FALSE;
5107 gchar *errstr = NULL;
5108 gchar *tmsgid = NULL;
5109 MainWindow *mainwin = mainwindow_get_mainwindow();
5110 gboolean queued_removed = FALSE;
5112 if (prefs_common.send_dialog_invisible
5113 || compose->batch == TRUE)
5114 discard_window = TRUE;
5116 compose_allow_user_actions (compose, FALSE);
5117 compose->sending = TRUE;
5119 if (compose_check_entries(compose, TRUE) == FALSE) {
5120 if (compose->batch) {
5121 gtk_widget_show_all(compose->window);
5123 goto bail;
5126 inc_lock();
5127 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5129 if (val) {
5130 if (compose->batch) {
5131 gtk_widget_show_all(compose->window);
5133 if (val == -4) {
5134 alertpanel_error(_("Could not queue message for sending:\n\n"
5135 "Charset conversion failed."));
5136 } else if (val == -5) {
5137 alertpanel_error(_("Could not queue message for sending:\n\n"
5138 "Couldn't get recipient encryption key."));
5139 } else if (val == -6) {
5140 /* silent error */
5141 } else if (val == -3) {
5142 if (privacy_peek_error())
5143 alertpanel_error(_("Could not queue message for sending:\n\n"
5144 "Signature failed: %s"), privacy_get_error());
5145 } else if (val == -2 && errno != 0) {
5146 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5147 } else {
5148 alertpanel_error(_("Could not queue message for sending."));
5150 goto bail;
5153 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5154 if (discard_window) {
5155 compose->sending = FALSE;
5156 compose_close(compose);
5157 /* No more compose access in the normal codepath
5158 * after this point! */
5159 compose = NULL;
5162 if (msgnum == 0) {
5163 alertpanel_error(_("The message was queued but could not be "
5164 "sent.\nUse \"Send queued messages\" from "
5165 "the main window to retry."));
5166 if (!discard_window) {
5167 goto bail;
5169 inc_unlock();
5170 g_free(tmsgid);
5171 return -1;
5173 if (msgpath == NULL) {
5174 msgpath = folder_item_fetch_msg(folder, msgnum);
5175 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5176 g_free(msgpath);
5177 } else {
5178 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5179 claws_unlink(msgpath);
5180 g_free(msgpath);
5182 if (!discard_window) {
5183 if (val != 0) {
5184 if (!queued_removed)
5185 folder_item_remove_msg(folder, msgnum);
5186 folder_item_scan(folder);
5187 if (tmsgid) {
5188 /* make sure we delete that */
5189 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5190 if (tmp) {
5191 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5192 folder_item_remove_msg(folder, tmp->msgnum);
5193 procmsg_msginfo_free(tmp);
5199 if (val == 0) {
5200 if (!queued_removed)
5201 folder_item_remove_msg(folder, msgnum);
5202 folder_item_scan(folder);
5203 if (tmsgid) {
5204 /* make sure we delete that */
5205 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5206 if (tmp) {
5207 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5208 folder_item_remove_msg(folder, tmp->msgnum);
5209 procmsg_msginfo_free(tmp);
5212 if (!discard_window) {
5213 compose->sending = FALSE;
5214 compose_allow_user_actions (compose, TRUE);
5215 compose_close(compose);
5217 } else {
5218 if (errstr) {
5219 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5220 "the main window to retry."), errstr);
5221 g_free(errstr);
5222 } else {
5223 alertpanel_error_log(_("The message was queued but could not be "
5224 "sent.\nUse \"Send queued messages\" from "
5225 "the main window to retry."));
5227 if (!discard_window) {
5228 goto bail;
5230 inc_unlock();
5231 g_free(tmsgid);
5232 return -1;
5234 g_free(tmsgid);
5235 inc_unlock();
5236 toolbar_main_set_sensitive(mainwin);
5237 main_window_set_menu_sensitive(mainwin);
5238 return 0;
5240 bail:
5241 inc_unlock();
5242 g_free(tmsgid);
5243 compose_allow_user_actions (compose, TRUE);
5244 compose->sending = FALSE;
5245 compose->modified = TRUE;
5246 toolbar_main_set_sensitive(mainwin);
5247 main_window_set_menu_sensitive(mainwin);
5249 return -1;
5252 static gboolean compose_use_attach(Compose *compose)
5254 GtkTreeModel *model = gtk_tree_view_get_model
5255 (GTK_TREE_VIEW(compose->attach_clist));
5256 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5259 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5260 FILE *fp)
5262 gchar buf[BUFFSIZE];
5263 gchar *str;
5264 gboolean first_to_address;
5265 gboolean first_cc_address;
5266 GSList *list;
5267 ComposeHeaderEntry *headerentry;
5268 const gchar *headerentryname;
5269 const gchar *cc_hdr;
5270 const gchar *to_hdr;
5271 gboolean err = FALSE;
5273 debug_print("Writing redirect header\n");
5275 cc_hdr = prefs_common_translated_header_name("Cc:");
5276 to_hdr = prefs_common_translated_header_name("To:");
5278 first_to_address = TRUE;
5279 for (list = compose->header_list; list; list = list->next) {
5280 headerentry = ((ComposeHeaderEntry *)list->data);
5281 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5283 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5284 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5285 Xstrdup_a(str, entstr, return -1);
5286 g_strstrip(str);
5287 if (str[0] != '\0') {
5288 compose_convert_header
5289 (compose, buf, sizeof(buf), str,
5290 strlen("Resent-To") + 2, TRUE);
5292 if (first_to_address) {
5293 err |= (fprintf(fp, "Resent-To: ") < 0);
5294 first_to_address = FALSE;
5295 } else {
5296 err |= (fprintf(fp, ",") < 0);
5298 err |= (fprintf(fp, "%s", buf) < 0);
5302 if (!first_to_address) {
5303 err |= (fprintf(fp, "\n") < 0);
5306 first_cc_address = TRUE;
5307 for (list = compose->header_list; list; list = list->next) {
5308 headerentry = ((ComposeHeaderEntry *)list->data);
5309 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5311 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5312 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5313 Xstrdup_a(str, strg, return -1);
5314 g_strstrip(str);
5315 if (str[0] != '\0') {
5316 compose_convert_header
5317 (compose, buf, sizeof(buf), str,
5318 strlen("Resent-Cc") + 2, TRUE);
5320 if (first_cc_address) {
5321 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5322 first_cc_address = FALSE;
5323 } else {
5324 err |= (fprintf(fp, ",") < 0);
5326 err |= (fprintf(fp, "%s", buf) < 0);
5330 if (!first_cc_address) {
5331 err |= (fprintf(fp, "\n") < 0);
5334 return (err ? -1:0);
5337 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5339 gchar buf[BUFFSIZE];
5340 gchar *str;
5341 const gchar *entstr;
5342 /* struct utsname utsbuf; */
5343 gboolean err = FALSE;
5345 cm_return_val_if_fail(fp != NULL, -1);
5346 cm_return_val_if_fail(compose->account != NULL, -1);
5347 cm_return_val_if_fail(compose->account->address != NULL, -1);
5349 /* Resent-Date */
5350 get_rfc822_date(buf, sizeof(buf));
5351 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5353 /* Resent-From */
5354 if (compose->account->name && *compose->account->name) {
5355 compose_convert_header
5356 (compose, buf, sizeof(buf), compose->account->name,
5357 strlen("From: "), TRUE);
5358 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5359 buf, compose->account->address) < 0);
5360 } else
5361 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5363 /* Subject */
5364 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5365 if (*entstr != '\0') {
5366 Xstrdup_a(str, entstr, return -1);
5367 g_strstrip(str);
5368 if (*str != '\0') {
5369 compose_convert_header(compose, buf, sizeof(buf), str,
5370 strlen("Subject: "), FALSE);
5371 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5375 /* Resent-Message-ID */
5376 if (compose->account->set_domain && compose->account->domain) {
5377 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5378 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5379 g_snprintf(buf, sizeof(buf), "%s",
5380 strchr(compose->account->address, '@') ?
5381 strchr(compose->account->address, '@')+1 :
5382 compose->account->address);
5383 } else {
5384 g_snprintf(buf, sizeof(buf), "%s", "");
5387 if (compose->account->gen_msgid) {
5388 gchar *addr = NULL;
5389 if (compose->account->msgid_with_addr) {
5390 addr = compose->account->address;
5392 generate_msgid(buf, sizeof(buf), addr);
5393 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5394 if (compose->msgid)
5395 g_free(compose->msgid);
5396 compose->msgid = g_strdup(buf);
5397 } else {
5398 compose->msgid = NULL;
5401 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5402 return -1;
5404 /* separator between header and body */
5405 err |= (fputs("\n", fp) == EOF);
5407 return (err ? -1:0);
5410 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5412 FILE *fp;
5413 size_t len;
5414 gchar buf[BUFFSIZE];
5415 int i = 0;
5416 gboolean skip = FALSE;
5417 gboolean err = FALSE;
5418 gchar *not_included[]={
5419 "Return-Path:", "Delivered-To:", "Received:",
5420 "Subject:", "X-UIDL:", "AF:",
5421 "NF:", "PS:", "SRH:",
5422 "SFN:", "DSR:", "MID:",
5423 "CFG:", "PT:", "S:",
5424 "RQ:", "SSV:", "NSV:",
5425 "SSH:", "R:", "MAID:",
5426 "NAID:", "RMID:", "FMID:",
5427 "SCF:", "RRCPT:", "NG:",
5428 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5429 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5430 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5431 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5432 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5433 NULL
5435 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5436 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5437 return -1;
5440 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5441 skip = FALSE;
5442 for (i = 0; not_included[i] != NULL; i++) {
5443 if (g_ascii_strncasecmp(buf, not_included[i],
5444 strlen(not_included[i])) == 0) {
5445 skip = TRUE;
5446 break;
5449 if (skip)
5450 continue;
5451 if (fputs(buf, fdest) == -1)
5452 goto error;
5454 if (!prefs_common.redirect_keep_from) {
5455 if (g_ascii_strncasecmp(buf, "From:",
5456 strlen("From:")) == 0) {
5457 err |= (fputs(" (by way of ", fdest) == EOF);
5458 if (compose->account->name
5459 && *compose->account->name) {
5460 compose_convert_header
5461 (compose, buf, sizeof(buf),
5462 compose->account->name,
5463 strlen("From: "),
5464 FALSE);
5465 err |= (fprintf(fdest, "%s <%s>",
5466 buf,
5467 compose->account->address) < 0);
5468 } else
5469 err |= (fprintf(fdest, "%s",
5470 compose->account->address) < 0);
5471 err |= (fputs(")", fdest) == EOF);
5475 if (fputs("\n", fdest) == -1)
5476 goto error;
5479 if (err)
5480 goto error;
5482 if (compose_redirect_write_headers(compose, fdest))
5483 goto error;
5485 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5486 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5487 goto error;
5490 fclose(fp);
5492 return 0;
5493 error:
5494 fclose(fp);
5496 return -1;
5499 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5501 GtkTextBuffer *buffer;
5502 GtkTextIter start, end;
5503 gchar *chars;
5504 gchar *buf;
5505 const gchar *out_codeset;
5506 EncodingType encoding = ENC_UNKNOWN;
5507 MimeInfo *mimemsg, *mimetext;
5508 gint line;
5509 const gchar *src_codeset = CS_INTERNAL;
5510 gchar *from_addr = NULL;
5511 gchar *from_name = NULL;
5513 if (action == COMPOSE_WRITE_FOR_SEND)
5514 attach_parts = TRUE;
5516 /* create message MimeInfo */
5517 mimemsg = procmime_mimeinfo_new();
5518 mimemsg->type = MIMETYPE_MESSAGE;
5519 mimemsg->subtype = g_strdup("rfc822");
5520 mimemsg->content = MIMECONTENT_MEM;
5521 mimemsg->tmp = TRUE; /* must free content later */
5522 mimemsg->data.mem = compose_get_header(compose);
5524 /* Create text part MimeInfo */
5525 /* get all composed text */
5526 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5527 gtk_text_buffer_get_start_iter(buffer, &start);
5528 gtk_text_buffer_get_end_iter(buffer, &end);
5529 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5531 out_codeset = conv_get_charset_str(compose->out_encoding);
5533 if (!out_codeset && is_ascii_str(chars)) {
5534 out_codeset = CS_US_ASCII;
5535 } else if (prefs_common.outgoing_fallback_to_ascii &&
5536 is_ascii_str(chars)) {
5537 out_codeset = CS_US_ASCII;
5538 encoding = ENC_7BIT;
5541 if (!out_codeset) {
5542 gchar *test_conv_global_out = NULL;
5543 gchar *test_conv_reply = NULL;
5545 /* automatic mode. be automatic. */
5546 codeconv_set_strict(TRUE);
5548 out_codeset = conv_get_outgoing_charset_str();
5549 if (out_codeset) {
5550 debug_print("trying to convert to %s\n", out_codeset);
5551 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5554 if (!test_conv_global_out && compose->orig_charset
5555 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5556 out_codeset = compose->orig_charset;
5557 debug_print("failure; trying to convert to %s\n", out_codeset);
5558 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5561 if (!test_conv_global_out && !test_conv_reply) {
5562 /* we're lost */
5563 out_codeset = CS_INTERNAL;
5564 debug_print("failure; finally using %s\n", out_codeset);
5566 g_free(test_conv_global_out);
5567 g_free(test_conv_reply);
5568 codeconv_set_strict(FALSE);
5571 if (encoding == ENC_UNKNOWN) {
5572 if (prefs_common.encoding_method == CTE_BASE64)
5573 encoding = ENC_BASE64;
5574 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5575 encoding = ENC_QUOTED_PRINTABLE;
5576 else if (prefs_common.encoding_method == CTE_8BIT)
5577 encoding = ENC_8BIT;
5578 else
5579 encoding = procmime_get_encoding_for_charset(out_codeset);
5582 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5583 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5585 if (action == COMPOSE_WRITE_FOR_SEND) {
5586 codeconv_set_strict(TRUE);
5587 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5588 codeconv_set_strict(FALSE);
5590 if (!buf) {
5591 AlertValue aval;
5592 gchar *msg;
5594 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5595 "to the specified %s charset.\n"
5596 "Send it as %s?"), out_codeset, src_codeset);
5597 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5598 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5599 g_free(msg);
5601 if (aval != G_ALERTALTERNATE) {
5602 g_free(chars);
5603 return -3;
5604 } else {
5605 buf = chars;
5606 out_codeset = src_codeset;
5607 chars = NULL;
5610 } else {
5611 buf = chars;
5612 out_codeset = src_codeset;
5613 chars = NULL;
5615 g_free(chars);
5617 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5618 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5619 strstr(buf, "\nFrom ") != NULL) {
5620 encoding = ENC_QUOTED_PRINTABLE;
5624 mimetext = procmime_mimeinfo_new();
5625 mimetext->content = MIMECONTENT_MEM;
5626 mimetext->tmp = TRUE; /* must free content later */
5627 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5628 * and free the data, which we need later. */
5629 mimetext->data.mem = g_strdup(buf);
5630 mimetext->type = MIMETYPE_TEXT;
5631 mimetext->subtype = g_strdup("plain");
5632 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5633 g_strdup(out_codeset));
5635 /* protect trailing spaces when signing message */
5636 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5637 privacy_system_can_sign(compose->privacy_system)) {
5638 encoding = ENC_QUOTED_PRINTABLE;
5641 debug_print("main text: %zd bytes encoded as %s in %d\n",
5642 strlen(buf), out_codeset, encoding);
5644 /* check for line length limit */
5645 if (action == COMPOSE_WRITE_FOR_SEND &&
5646 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5647 check_line_length(buf, 1000, &line) < 0) {
5648 AlertValue aval;
5649 gchar *msg;
5651 msg = g_strdup_printf
5652 (_("Line %d exceeds the line length limit (998 bytes).\n"
5653 "The contents of the message might be broken on the way to the delivery.\n"
5654 "\n"
5655 "Send it anyway?"), line + 1);
5656 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5657 g_free(msg);
5658 if (aval != G_ALERTALTERNATE) {
5659 g_free(buf);
5660 return -1;
5664 if (encoding != ENC_UNKNOWN)
5665 procmime_encode_content(mimetext, encoding);
5667 /* append attachment parts */
5668 if (compose_use_attach(compose) && attach_parts) {
5669 MimeInfo *mimempart;
5670 gchar *boundary = NULL;
5671 mimempart = procmime_mimeinfo_new();
5672 mimempart->content = MIMECONTENT_EMPTY;
5673 mimempart->type = MIMETYPE_MULTIPART;
5674 mimempart->subtype = g_strdup("mixed");
5676 do {
5677 g_free(boundary);
5678 boundary = generate_mime_boundary(NULL);
5679 } while (strstr(buf, boundary) != NULL);
5681 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5682 boundary);
5684 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5686 g_node_append(mimempart->node, mimetext->node);
5687 g_node_append(mimemsg->node, mimempart->node);
5689 if (compose_add_attachments(compose, mimempart) < 0)
5690 return -1;
5691 } else
5692 g_node_append(mimemsg->node, mimetext->node);
5694 g_free(buf);
5696 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5697 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5698 /* extract name and address */
5699 if (strstr(spec, " <") && strstr(spec, ">")) {
5700 from_addr = g_strdup(strrchr(spec, '<')+1);
5701 *(strrchr(from_addr, '>')) = '\0';
5702 from_name = g_strdup(spec);
5703 *(strrchr(from_name, '<')) = '\0';
5704 } else {
5705 from_name = NULL;
5706 from_addr = NULL;
5708 g_free(spec);
5710 /* sign message if sending */
5711 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5712 privacy_system_can_sign(compose->privacy_system))
5713 if (!privacy_sign(compose->privacy_system, mimemsg,
5714 compose->account, from_addr)) {
5715 g_free(from_name);
5716 g_free(from_addr);
5717 return -2;
5719 g_free(from_name);
5720 g_free(from_addr);
5721 procmime_write_mimeinfo(mimemsg, fp);
5723 procmime_mimeinfo_free_all(mimemsg);
5725 return 0;
5728 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5730 GtkTextBuffer *buffer;
5731 GtkTextIter start, end;
5732 FILE *fp;
5733 size_t len;
5734 gchar *chars, *tmp;
5736 if ((fp = g_fopen(file, "wb")) == NULL) {
5737 FILE_OP_ERROR(file, "fopen");
5738 return -1;
5741 /* chmod for security */
5742 if (change_file_mode_rw(fp, file) < 0) {
5743 FILE_OP_ERROR(file, "chmod");
5744 g_warning("can't change file mode\n");
5747 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5748 gtk_text_buffer_get_start_iter(buffer, &start);
5749 gtk_text_buffer_get_end_iter(buffer, &end);
5750 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5752 chars = conv_codeset_strdup
5753 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5755 g_free(tmp);
5756 if (!chars) {
5757 fclose(fp);
5758 claws_unlink(file);
5759 return -1;
5761 /* write body */
5762 len = strlen(chars);
5763 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5764 FILE_OP_ERROR(file, "fwrite");
5765 g_free(chars);
5766 fclose(fp);
5767 claws_unlink(file);
5768 return -1;
5771 g_free(chars);
5773 if (fclose(fp) == EOF) {
5774 FILE_OP_ERROR(file, "fclose");
5775 claws_unlink(file);
5776 return -1;
5778 return 0;
5781 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5783 FolderItem *item;
5784 MsgInfo *msginfo = compose->targetinfo;
5786 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5787 if (!msginfo) return -1;
5789 if (!force && MSG_IS_LOCKED(msginfo->flags))
5790 return 0;
5792 item = msginfo->folder;
5793 cm_return_val_if_fail(item != NULL, -1);
5795 if (procmsg_msg_exist(msginfo) &&
5796 (folder_has_parent_of_type(item, F_QUEUE) ||
5797 folder_has_parent_of_type(item, F_DRAFT)
5798 || msginfo == compose->autosaved_draft)) {
5799 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5800 g_warning("can't remove the old message\n");
5801 return -1;
5802 } else {
5803 debug_print("removed reedit target %d\n", msginfo->msgnum);
5807 return 0;
5810 static void compose_remove_draft(Compose *compose)
5812 FolderItem *drafts;
5813 MsgInfo *msginfo = compose->targetinfo;
5814 drafts = account_get_special_folder(compose->account, F_DRAFT);
5816 if (procmsg_msg_exist(msginfo)) {
5817 folder_item_remove_msg(drafts, msginfo->msgnum);
5822 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5823 gboolean remove_reedit_target)
5825 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5828 static gboolean compose_warn_encryption(Compose *compose)
5830 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5831 AlertValue val = G_ALERTALTERNATE;
5833 if (warning == NULL)
5834 return TRUE;
5836 val = alertpanel_full(_("Encryption warning"), warning,
5837 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5838 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5839 if (val & G_ALERTDISABLE) {
5840 val &= ~G_ALERTDISABLE;
5841 if (val == G_ALERTALTERNATE)
5842 privacy_inhibit_encrypt_warning(compose->privacy_system,
5843 TRUE);
5846 if (val == G_ALERTALTERNATE) {
5847 return TRUE;
5848 } else {
5849 return FALSE;
5853 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5854 gchar **msgpath, gboolean check_subject,
5855 gboolean remove_reedit_target)
5857 FolderItem *queue;
5858 gchar *tmp;
5859 FILE *fp;
5860 GSList *cur;
5861 gint num;
5862 PrefsAccount *mailac = NULL, *newsac = NULL;
5863 gboolean err = FALSE;
5865 debug_print("queueing message...\n");
5866 cm_return_val_if_fail(compose->account != NULL, -1);
5868 if (compose_check_entries(compose, check_subject) == FALSE) {
5869 if (compose->batch) {
5870 gtk_widget_show_all(compose->window);
5872 return -1;
5875 if (!compose->to_list && !compose->newsgroup_list) {
5876 g_warning("can't get recipient list.");
5877 return -1;
5880 if (compose->to_list) {
5881 if (compose->account->protocol != A_NNTP)
5882 mailac = compose->account;
5883 else if (cur_account && cur_account->protocol != A_NNTP)
5884 mailac = cur_account;
5885 else if (!(mailac = compose_current_mail_account())) {
5886 alertpanel_error(_("No account for sending mails available!"));
5887 return -1;
5891 if (compose->newsgroup_list) {
5892 if (compose->account->protocol == A_NNTP)
5893 newsac = compose->account;
5894 else {
5895 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5896 return -1;
5900 /* write queue header */
5901 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5902 G_DIR_SEPARATOR, compose, (guint) rand());
5903 debug_print("queuing to %s\n", tmp);
5904 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5905 FILE_OP_ERROR(tmp, "fopen");
5906 g_free(tmp);
5907 return -2;
5910 if (change_file_mode_rw(fp, tmp) < 0) {
5911 FILE_OP_ERROR(tmp, "chmod");
5912 g_warning("can't change file mode\n");
5915 /* queueing variables */
5916 err |= (fprintf(fp, "AF:\n") < 0);
5917 err |= (fprintf(fp, "NF:0\n") < 0);
5918 err |= (fprintf(fp, "PS:10\n") < 0);
5919 err |= (fprintf(fp, "SRH:1\n") < 0);
5920 err |= (fprintf(fp, "SFN:\n") < 0);
5921 err |= (fprintf(fp, "DSR:\n") < 0);
5922 if (compose->msgid)
5923 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5924 else
5925 err |= (fprintf(fp, "MID:\n") < 0);
5926 err |= (fprintf(fp, "CFG:\n") < 0);
5927 err |= (fprintf(fp, "PT:0\n") < 0);
5928 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5929 err |= (fprintf(fp, "RQ:\n") < 0);
5930 if (mailac)
5931 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5932 else
5933 err |= (fprintf(fp, "SSV:\n") < 0);
5934 if (newsac)
5935 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5936 else
5937 err |= (fprintf(fp, "NSV:\n") < 0);
5938 err |= (fprintf(fp, "SSH:\n") < 0);
5939 /* write recepient list */
5940 if (compose->to_list) {
5941 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5942 for (cur = compose->to_list->next; cur != NULL;
5943 cur = cur->next)
5944 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5945 err |= (fprintf(fp, "\n") < 0);
5947 /* write newsgroup list */
5948 if (compose->newsgroup_list) {
5949 err |= (fprintf(fp, "NG:") < 0);
5950 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5951 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5952 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5953 err |= (fprintf(fp, "\n") < 0);
5955 /* Sylpheed account IDs */
5956 if (mailac)
5957 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5958 if (newsac)
5959 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5962 if (compose->privacy_system != NULL) {
5963 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5964 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5965 if (compose->use_encryption) {
5966 gchar *encdata;
5967 if (!compose_warn_encryption(compose)) {
5968 fclose(fp);
5969 claws_unlink(tmp);
5970 g_free(tmp);
5971 return -6;
5973 if (mailac && mailac->encrypt_to_self) {
5974 GSList *tmp_list = g_slist_copy(compose->to_list);
5975 tmp_list = g_slist_append(tmp_list, compose->account->address);
5976 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5977 g_slist_free(tmp_list);
5978 } else {
5979 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5981 if (encdata != NULL) {
5982 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5983 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5984 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5985 encdata) < 0);
5986 } /* else we finally dont want to encrypt */
5987 } else {
5988 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5989 /* and if encdata was null, it means there's been a problem in
5990 * key selection */
5991 if (err == TRUE)
5992 g_warning("failed to write queue message");
5993 fclose(fp);
5994 claws_unlink(tmp);
5995 g_free(tmp);
5996 return -5;
5998 g_free(encdata);
6002 /* Save copy folder */
6003 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6004 gchar *savefolderid;
6006 savefolderid = compose_get_save_to(compose);
6007 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6008 g_free(savefolderid);
6010 /* Save copy folder */
6011 if (compose->return_receipt) {
6012 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6014 /* Message-ID of message replying to */
6015 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6016 gchar *folderid = NULL;
6018 if (compose->replyinfo->folder)
6019 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6020 if (folderid == NULL)
6021 folderid = g_strdup("NULL");
6023 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6024 g_free(folderid);
6026 /* Message-ID of message forwarding to */
6027 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6028 gchar *folderid = NULL;
6030 if (compose->fwdinfo->folder)
6031 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6032 if (folderid == NULL)
6033 folderid = g_strdup("NULL");
6035 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6036 g_free(folderid);
6039 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6040 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6042 /* end of headers */
6043 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6045 if (compose->redirect_filename != NULL) {
6046 if (compose_redirect_write_to_file(compose, fp) < 0) {
6047 fclose(fp);
6048 claws_unlink(tmp);
6049 g_free(tmp);
6050 return -2;
6052 } else {
6053 gint result = 0;
6054 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6055 fclose(fp);
6056 claws_unlink(tmp);
6057 g_free(tmp);
6058 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6061 if (err == TRUE) {
6062 g_warning("failed to write queue message\n");
6063 fclose(fp);
6064 claws_unlink(tmp);
6065 g_free(tmp);
6066 return -2;
6068 if (fclose(fp) == EOF) {
6069 FILE_OP_ERROR(tmp, "fclose");
6070 claws_unlink(tmp);
6071 g_free(tmp);
6072 return -2;
6075 if (item && *item) {
6076 queue = *item;
6077 } else {
6078 queue = account_get_special_folder(compose->account, F_QUEUE);
6080 if (!queue) {
6081 g_warning("can't find queue folder\n");
6082 claws_unlink(tmp);
6083 g_free(tmp);
6084 return -1;
6086 folder_item_scan(queue);
6087 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6088 g_warning("can't queue the message\n");
6089 claws_unlink(tmp);
6090 g_free(tmp);
6091 return -1;
6094 if (msgpath == NULL) {
6095 claws_unlink(tmp);
6096 g_free(tmp);
6097 } else
6098 *msgpath = tmp;
6100 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6101 compose_remove_reedit_target(compose, FALSE);
6104 if ((msgnum != NULL) && (item != NULL)) {
6105 *msgnum = num;
6106 *item = queue;
6109 return 0;
6112 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6114 AttachInfo *ainfo;
6115 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6116 MimeInfo *mimepart;
6117 struct stat statbuf;
6118 gchar *type, *subtype;
6119 GtkTreeModel *model;
6120 GtkTreeIter iter;
6122 model = gtk_tree_view_get_model(tree_view);
6124 if (!gtk_tree_model_get_iter_first(model, &iter))
6125 return 0;
6126 do {
6127 gtk_tree_model_get(model, &iter,
6128 COL_DATA, &ainfo,
6129 -1);
6131 if (!is_file_exist(ainfo->file)) {
6132 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6133 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6134 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6135 g_free(msg);
6136 if (val == G_ALERTDEFAULT) {
6137 return -1;
6139 continue;
6141 if (g_stat(ainfo->file, &statbuf) < 0)
6142 return -1;
6144 mimepart = procmime_mimeinfo_new();
6145 mimepart->content = MIMECONTENT_FILE;
6146 mimepart->data.filename = g_strdup(ainfo->file);
6147 mimepart->tmp = FALSE; /* or we destroy our attachment */
6148 mimepart->offset = 0;
6149 mimepart->length = statbuf.st_size;
6151 type = g_strdup(ainfo->content_type);
6153 if (!strchr(type, '/')) {
6154 g_free(type);
6155 type = g_strdup("application/octet-stream");
6158 subtype = strchr(type, '/') + 1;
6159 *(subtype - 1) = '\0';
6160 mimepart->type = procmime_get_media_type(type);
6161 mimepart->subtype = g_strdup(subtype);
6162 g_free(type);
6164 if (mimepart->type == MIMETYPE_MESSAGE &&
6165 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6166 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6167 } else if (mimepart->type == MIMETYPE_TEXT) {
6168 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6169 /* Text parts with no name come from multipart/alternative
6170 * forwards. Make sure the recipient won't look at the
6171 * original HTML part by mistake. */
6172 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6173 ainfo->name = g_strdup_printf(_("Original %s part"),
6174 mimepart->subtype);
6176 if (ainfo->charset)
6177 g_hash_table_insert(mimepart->typeparameters,
6178 g_strdup("charset"), g_strdup(ainfo->charset));
6180 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6181 if (mimepart->type == MIMETYPE_APPLICATION &&
6182 !strcmp2(mimepart->subtype, "octet-stream"))
6183 g_hash_table_insert(mimepart->typeparameters,
6184 g_strdup("name"), g_strdup(ainfo->name));
6185 g_hash_table_insert(mimepart->dispositionparameters,
6186 g_strdup("filename"), g_strdup(ainfo->name));
6187 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6190 if (mimepart->type == MIMETYPE_MESSAGE
6191 || mimepart->type == MIMETYPE_MULTIPART)
6192 ainfo->encoding = ENC_BINARY;
6193 else if (compose->use_signing) {
6194 if (ainfo->encoding == ENC_7BIT)
6195 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6196 else if (ainfo->encoding == ENC_8BIT)
6197 ainfo->encoding = ENC_BASE64;
6202 procmime_encode_content(mimepart, ainfo->encoding);
6204 g_node_append(parent->node, mimepart->node);
6205 } while (gtk_tree_model_iter_next(model, &iter));
6207 return 0;
6210 static gchar *compose_quote_list_of_addresses(gchar *str)
6212 GSList *list = NULL, *item = NULL;
6213 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6215 list = address_list_append_with_comments(list, str);
6216 for (item = list; item != NULL; item = item->next) {
6217 gchar *spec = item->data;
6218 gchar *endofname = strstr(spec, " <");
6219 if (endofname != NULL) {
6220 gchar * qqname;
6221 *endofname = '\0';
6222 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6223 qqname = escape_internal_quotes(qname, '"');
6224 *endofname = ' ';
6225 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6226 gchar *addr = g_strdup(endofname);
6227 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6228 faddr = g_strconcat(name, addr, NULL);
6229 g_free(name);
6230 g_free(addr);
6231 debug_print("new auto-quoted address: '%s'", faddr);
6234 if (result == NULL)
6235 result = g_strdup((faddr != NULL)? faddr: spec);
6236 else {
6237 result = g_strconcat(result,
6238 ", ",
6239 (faddr != NULL)? faddr: spec,
6240 NULL);
6242 if (faddr != NULL) {
6243 g_free(faddr);
6244 faddr = NULL;
6247 slist_free_strings_full(list);
6249 return result;
6252 #define IS_IN_CUSTOM_HEADER(header) \
6253 (compose->account->add_customhdr && \
6254 custom_header_find(compose->account->customhdr_list, header) != NULL)
6256 static void compose_add_headerfield_from_headerlist(Compose *compose,
6257 GString *header,
6258 const gchar *fieldname,
6259 const gchar *seperator)
6261 gchar *str, *fieldname_w_colon;
6262 gboolean add_field = FALSE;
6263 GSList *list;
6264 ComposeHeaderEntry *headerentry;
6265 const gchar *headerentryname;
6266 const gchar *trans_fieldname;
6267 GString *fieldstr;
6269 if (IS_IN_CUSTOM_HEADER(fieldname))
6270 return;
6272 debug_print("Adding %s-fields\n", fieldname);
6274 fieldstr = g_string_sized_new(64);
6276 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6277 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6279 for (list = compose->header_list; list; list = list->next) {
6280 headerentry = ((ComposeHeaderEntry *)list->data);
6281 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6283 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6284 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6285 g_strstrip(ustr);
6286 str = compose_quote_list_of_addresses(ustr);
6287 g_free(ustr);
6288 if (str != NULL && str[0] != '\0') {
6289 if (add_field)
6290 g_string_append(fieldstr, seperator);
6291 g_string_append(fieldstr, str);
6292 add_field = TRUE;
6294 g_free(str);
6297 if (add_field) {
6298 gchar *buf;
6300 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6301 compose_convert_header
6302 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6303 strlen(fieldname) + 2, TRUE);
6304 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6305 g_free(buf);
6308 g_free(fieldname_w_colon);
6309 g_string_free(fieldstr, TRUE);
6311 return;
6314 static gchar *compose_get_manual_headers_info(Compose *compose)
6316 GString *sh_header = g_string_new(" ");
6317 GSList *list;
6318 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6320 for (list = compose->header_list; list; list = list->next) {
6321 ComposeHeaderEntry *headerentry;
6322 gchar *tmp;
6323 gchar *headername;
6324 gchar *headername_wcolon;
6325 const gchar *headername_trans;
6326 gchar **string;
6327 gboolean standard_header = FALSE;
6329 headerentry = ((ComposeHeaderEntry *)list->data);
6331 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6332 g_strstrip(tmp);
6333 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6334 g_free(tmp);
6335 continue;
6338 if (!strstr(tmp, ":")) {
6339 headername_wcolon = g_strconcat(tmp, ":", NULL);
6340 headername = g_strdup(tmp);
6341 } else {
6342 headername_wcolon = g_strdup(tmp);
6343 headername = g_strdup(strtok(tmp, ":"));
6345 g_free(tmp);
6347 string = std_headers;
6348 while (*string != NULL) {
6349 headername_trans = prefs_common_translated_header_name(*string);
6350 if (!strcmp(headername_trans, headername_wcolon))
6351 standard_header = TRUE;
6352 string++;
6354 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6355 g_string_append_printf(sh_header, "%s ", headername);
6356 g_free(headername);
6357 g_free(headername_wcolon);
6359 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6360 return g_string_free(sh_header, FALSE);
6363 static gchar *compose_get_header(Compose *compose)
6365 gchar buf[BUFFSIZE];
6366 const gchar *entry_str;
6367 gchar *str;
6368 gchar *name;
6369 GSList *list;
6370 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6371 GString *header;
6372 gchar *from_name = NULL, *from_address = NULL;
6373 gchar *tmp;
6375 cm_return_val_if_fail(compose->account != NULL, NULL);
6376 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6378 header = g_string_sized_new(64);
6380 /* Date */
6381 get_rfc822_date(buf, sizeof(buf));
6382 g_string_append_printf(header, "Date: %s\n", buf);
6384 /* From */
6386 if (compose->account->name && *compose->account->name) {
6387 gchar *buf;
6388 QUOTE_IF_REQUIRED(buf, compose->account->name);
6389 tmp = g_strdup_printf("%s <%s>",
6390 buf, compose->account->address);
6391 } else {
6392 tmp = g_strdup_printf("%s",
6393 compose->account->address);
6395 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6396 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6397 /* use default */
6398 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6399 from_address = g_strdup(compose->account->address);
6400 } else {
6401 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6402 /* extract name and address */
6403 if (strstr(spec, " <") && strstr(spec, ">")) {
6404 from_address = g_strdup(strrchr(spec, '<')+1);
6405 *(strrchr(from_address, '>')) = '\0';
6406 from_name = g_strdup(spec);
6407 *(strrchr(from_name, '<')) = '\0';
6408 } else {
6409 from_name = NULL;
6410 from_address = g_strdup(spec);
6412 g_free(spec);
6414 g_free(tmp);
6417 if (from_name && *from_name) {
6418 gchar *qname;
6419 compose_convert_header
6420 (compose, buf, sizeof(buf), from_name,
6421 strlen("From: "), TRUE);
6422 QUOTE_IF_REQUIRED(name, buf);
6423 qname = escape_internal_quotes(name, '"');
6425 g_string_append_printf(header, "From: %s <%s>\n",
6426 qname, from_address);
6427 if (qname != name)
6428 g_free(qname);
6429 } else
6430 g_string_append_printf(header, "From: %s\n", from_address);
6432 g_free(from_name);
6433 g_free(from_address);
6435 /* To */
6436 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6438 /* Newsgroups */
6439 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6441 /* Cc */
6442 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6444 /* Bcc */
6446 * If this account is a NNTP account remove Bcc header from
6447 * message body since it otherwise will be publicly shown
6449 if (compose->account->protocol != A_NNTP)
6450 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6452 /* Subject */
6453 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6455 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6456 g_strstrip(str);
6457 if (*str != '\0') {
6458 compose_convert_header(compose, buf, sizeof(buf), str,
6459 strlen("Subject: "), FALSE);
6460 g_string_append_printf(header, "Subject: %s\n", buf);
6463 g_free(str);
6465 /* Message-ID */
6466 if (compose->account->set_domain && compose->account->domain) {
6467 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6468 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6469 g_snprintf(buf, sizeof(buf), "%s",
6470 strchr(compose->account->address, '@') ?
6471 strchr(compose->account->address, '@')+1 :
6472 compose->account->address);
6473 } else {
6474 g_snprintf(buf, sizeof(buf), "%s", "");
6477 if (compose->account->gen_msgid) {
6478 gchar *addr = NULL;
6479 if (compose->account->msgid_with_addr) {
6480 addr = compose->account->address;
6482 generate_msgid(buf, sizeof(buf), addr);
6483 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6484 if (compose->msgid)
6485 g_free(compose->msgid);
6486 compose->msgid = g_strdup(buf);
6487 } else {
6488 compose->msgid = NULL;
6491 if (compose->remove_references == FALSE) {
6492 /* In-Reply-To */
6493 if (compose->inreplyto && compose->to_list)
6494 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6496 /* References */
6497 if (compose->references)
6498 g_string_append_printf(header, "References: %s\n", compose->references);
6501 /* Followup-To */
6502 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6504 /* Reply-To */
6505 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6507 /* Organization */
6508 if (compose->account->organization &&
6509 strlen(compose->account->organization) &&
6510 !IS_IN_CUSTOM_HEADER("Organization")) {
6511 compose_convert_header(compose, buf, sizeof(buf),
6512 compose->account->organization,
6513 strlen("Organization: "), FALSE);
6514 g_string_append_printf(header, "Organization: %s\n", buf);
6517 /* Program version and system info */
6518 if (compose->account->gen_xmailer &&
6519 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6520 !compose->newsgroup_list) {
6521 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6522 prog_version,
6523 gtk_major_version, gtk_minor_version, gtk_micro_version,
6524 TARGET_ALIAS);
6526 if (compose->account->gen_xmailer &&
6527 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6528 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6529 prog_version,
6530 gtk_major_version, gtk_minor_version, gtk_micro_version,
6531 TARGET_ALIAS);
6534 /* custom headers */
6535 if (compose->account->add_customhdr) {
6536 GSList *cur;
6538 for (cur = compose->account->customhdr_list; cur != NULL;
6539 cur = cur->next) {
6540 CustomHeader *chdr = (CustomHeader *)cur->data;
6542 if (custom_header_is_allowed(chdr->name)
6543 && chdr->value != NULL
6544 && *(chdr->value) != '\0') {
6545 compose_convert_header
6546 (compose, buf, sizeof(buf),
6547 chdr->value,
6548 strlen(chdr->name) + 2, FALSE);
6549 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6554 /* Automatic Faces and X-Faces */
6555 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6556 g_string_append_printf(header, "X-Face: %s\n", buf);
6558 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6559 g_string_append_printf(header, "X-Face: %s\n", buf);
6561 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6562 g_string_append_printf(header, "Face: %s\n", buf);
6564 else if (get_default_face (buf, sizeof(buf)) == 0) {
6565 g_string_append_printf(header, "Face: %s\n", buf);
6568 /* PRIORITY */
6569 switch (compose->priority) {
6570 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6571 "X-Priority: 1 (Highest)\n");
6572 break;
6573 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6574 "X-Priority: 2 (High)\n");
6575 break;
6576 case PRIORITY_NORMAL: break;
6577 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6578 "X-Priority: 4 (Low)\n");
6579 break;
6580 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6581 "X-Priority: 5 (Lowest)\n");
6582 break;
6583 default: debug_print("compose: priority unknown : %d\n",
6584 compose->priority);
6587 /* Request Return Receipt */
6588 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6589 if (compose->return_receipt) {
6590 if (compose->account->name
6591 && *compose->account->name) {
6592 compose_convert_header(compose, buf, sizeof(buf),
6593 compose->account->name,
6594 strlen("Disposition-Notification-To: "),
6595 TRUE);
6596 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6597 } else
6598 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6602 /* get special headers */
6603 for (list = compose->header_list; list; list = list->next) {
6604 ComposeHeaderEntry *headerentry;
6605 gchar *tmp;
6606 gchar *headername;
6607 gchar *headername_wcolon;
6608 const gchar *headername_trans;
6609 gchar *headervalue;
6610 gchar **string;
6611 gboolean standard_header = FALSE;
6613 headerentry = ((ComposeHeaderEntry *)list->data);
6615 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6616 g_strstrip(tmp);
6617 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6618 g_free(tmp);
6619 continue;
6622 if (!strstr(tmp, ":")) {
6623 headername_wcolon = g_strconcat(tmp, ":", NULL);
6624 headername = g_strdup(tmp);
6625 } else {
6626 headername_wcolon = g_strdup(tmp);
6627 headername = g_strdup(strtok(tmp, ":"));
6629 g_free(tmp);
6631 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6632 Xstrdup_a(headervalue, entry_str, return NULL);
6633 subst_char(headervalue, '\r', ' ');
6634 subst_char(headervalue, '\n', ' ');
6635 string = std_headers;
6636 while (*string != NULL) {
6637 headername_trans = prefs_common_translated_header_name(*string);
6638 if (!strcmp(headername_trans, headername_wcolon))
6639 standard_header = TRUE;
6640 string++;
6642 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6643 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6645 g_free(headername);
6646 g_free(headername_wcolon);
6649 str = header->str;
6650 g_string_free(header, FALSE);
6652 return str;
6655 #undef IS_IN_CUSTOM_HEADER
6657 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6658 gint header_len, gboolean addr_field)
6660 gchar *tmpstr = NULL;
6661 const gchar *out_codeset = NULL;
6663 cm_return_if_fail(src != NULL);
6664 cm_return_if_fail(dest != NULL);
6666 if (len < 1) return;
6668 tmpstr = g_strdup(src);
6670 subst_char(tmpstr, '\n', ' ');
6671 subst_char(tmpstr, '\r', ' ');
6672 g_strchomp(tmpstr);
6674 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6675 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6676 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6677 g_free(tmpstr);
6678 tmpstr = mybuf;
6681 codeconv_set_strict(TRUE);
6682 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6683 conv_get_charset_str(compose->out_encoding));
6684 codeconv_set_strict(FALSE);
6686 if (!dest || *dest == '\0') {
6687 gchar *test_conv_global_out = NULL;
6688 gchar *test_conv_reply = NULL;
6690 /* automatic mode. be automatic. */
6691 codeconv_set_strict(TRUE);
6693 out_codeset = conv_get_outgoing_charset_str();
6694 if (out_codeset) {
6695 debug_print("trying to convert to %s\n", out_codeset);
6696 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6699 if (!test_conv_global_out && compose->orig_charset
6700 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6701 out_codeset = compose->orig_charset;
6702 debug_print("failure; trying to convert to %s\n", out_codeset);
6703 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6706 if (!test_conv_global_out && !test_conv_reply) {
6707 /* we're lost */
6708 out_codeset = CS_INTERNAL;
6709 debug_print("finally using %s\n", out_codeset);
6711 g_free(test_conv_global_out);
6712 g_free(test_conv_reply);
6713 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6714 out_codeset);
6715 codeconv_set_strict(FALSE);
6717 g_free(tmpstr);
6720 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6722 gchar *address;
6724 cm_return_if_fail(user_data != NULL);
6726 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6727 g_strstrip(address);
6728 if (*address != '\0') {
6729 gchar *name = procheader_get_fromname(address);
6730 extract_address(address);
6731 #ifndef USE_NEW_ADDRBOOK
6732 addressbook_add_contact(name, address, NULL, NULL);
6733 #else
6734 debug_print("%s: %s\n", name, address);
6735 if (addressadd_selection(name, address, NULL, NULL)) {
6736 debug_print( "addressbook_add_contact - added\n" );
6738 #endif
6740 g_free(address);
6743 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6745 GtkWidget *menuitem;
6746 gchar *address;
6748 cm_return_if_fail(menu != NULL);
6749 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6751 menuitem = gtk_separator_menu_item_new();
6752 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6753 gtk_widget_show(menuitem);
6755 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6756 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6758 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6759 g_strstrip(address);
6760 if (*address == '\0') {
6761 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6764 g_signal_connect(G_OBJECT(menuitem), "activate",
6765 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6766 gtk_widget_show(menuitem);
6769 void compose_add_extra_header(gchar *header, GtkListStore *model)
6771 GtkTreeIter iter;
6772 if (strcmp(header, "")) {
6773 COMBOBOX_ADD(model, header, COMPOSE_TO);
6777 void compose_add_extra_header_entries(GtkListStore *model)
6779 FILE *exh;
6780 gchar *exhrc;
6781 gchar buf[BUFFSIZE];
6782 gint lastc;
6784 if (extra_headers == NULL) {
6785 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6786 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6787 debug_print("extra headers file not found\n");
6788 goto extra_headers_done;
6790 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6791 lastc = strlen(buf) - 1; /* remove trailing control chars */
6792 while (lastc >= 0 && buf[lastc] != ':')
6793 buf[lastc--] = '\0';
6794 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6795 buf[lastc] = '\0'; /* remove trailing : for comparison */
6796 if (custom_header_is_allowed(buf)) {
6797 buf[lastc] = ':';
6798 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6800 else
6801 g_message("disallowed extra header line: %s\n", buf);
6803 else {
6804 if (buf[0] != '#')
6805 g_message("invalid extra header line: %s\n", buf);
6808 fclose(exh);
6809 extra_headers_done:
6810 g_free(exhrc);
6811 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6812 extra_headers = g_slist_reverse(extra_headers);
6814 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6817 static void compose_create_header_entry(Compose *compose)
6819 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6821 GtkWidget *combo;
6822 GtkWidget *entry;
6823 GtkWidget *button;
6824 GtkWidget *hbox;
6825 gchar **string;
6826 const gchar *header = NULL;
6827 ComposeHeaderEntry *headerentry;
6828 gboolean standard_header = FALSE;
6829 GtkListStore *model;
6830 GtkTreeIter iter;
6832 headerentry = g_new0(ComposeHeaderEntry, 1);
6834 /* Combo box model */
6835 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6836 #if !GTK_CHECK_VERSION(2, 24, 0)
6837 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6838 #endif
6839 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6840 COMPOSE_TO);
6841 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6842 COMPOSE_CC);
6843 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6844 COMPOSE_BCC);
6845 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6846 COMPOSE_NEWSGROUPS);
6847 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6848 COMPOSE_REPLYTO);
6849 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6850 COMPOSE_FOLLOWUPTO);
6851 compose_add_extra_header_entries(model);
6853 /* Combo box */
6854 #if GTK_CHECK_VERSION(2, 24, 0)
6855 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6856 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6857 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6858 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6859 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6860 #endif
6861 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6862 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6863 G_CALLBACK(compose_grab_focus_cb), compose);
6864 gtk_widget_show(combo);
6866 GList *l = NULL;
6867 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6868 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6869 g_list_free(l);
6871 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6872 compose->header_nextrow, compose->header_nextrow+1,
6873 GTK_SHRINK, GTK_FILL, 0, 0);
6874 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6875 const gchar *last_header_entry = gtk_entry_get_text(
6876 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6877 string = headers;
6878 while (*string != NULL) {
6879 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6880 standard_header = TRUE;
6881 string++;
6883 if (standard_header)
6884 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6886 if (!compose->header_last || !standard_header) {
6887 switch(compose->account->protocol) {
6888 case A_NNTP:
6889 header = prefs_common_translated_header_name("Newsgroups:");
6890 break;
6891 default:
6892 header = prefs_common_translated_header_name("To:");
6893 break;
6896 if (header)
6897 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6899 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6900 G_CALLBACK(compose_grab_focus_cb), compose);
6902 /* Entry field with cleanup button */
6903 button = gtk_button_new();
6904 gtk_button_set_image(GTK_BUTTON(button),
6905 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6906 gtk_widget_show(button);
6907 CLAWS_SET_TIP(button,
6908 _("Delete entry contents"));
6909 entry = gtk_entry_new();
6910 gtk_widget_show(entry);
6911 CLAWS_SET_TIP(entry,
6912 _("Use <tab> to autocomplete from addressbook"));
6913 hbox = gtk_hbox_new (FALSE, 0);
6914 gtk_widget_show(hbox);
6915 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6916 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6917 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6918 compose->header_nextrow, compose->header_nextrow+1,
6919 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6921 g_signal_connect(G_OBJECT(entry), "key-press-event",
6922 G_CALLBACK(compose_headerentry_key_press_event_cb),
6923 headerentry);
6924 g_signal_connect(G_OBJECT(entry), "changed",
6925 G_CALLBACK(compose_headerentry_changed_cb),
6926 headerentry);
6927 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6928 G_CALLBACK(compose_grab_focus_cb), compose);
6930 g_signal_connect(G_OBJECT(button), "clicked",
6931 G_CALLBACK(compose_headerentry_button_clicked_cb),
6932 headerentry);
6934 /* email dnd */
6935 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6936 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6937 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6938 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6939 G_CALLBACK(compose_header_drag_received_cb),
6940 entry);
6941 g_signal_connect(G_OBJECT(entry), "drag-drop",
6942 G_CALLBACK(compose_drag_drop),
6943 compose);
6944 g_signal_connect(G_OBJECT(entry), "populate-popup",
6945 G_CALLBACK(compose_entry_popup_extend),
6946 NULL);
6948 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6950 headerentry->compose = compose;
6951 headerentry->combo = combo;
6952 headerentry->entry = entry;
6953 headerentry->button = button;
6954 headerentry->hbox = hbox;
6955 headerentry->headernum = compose->header_nextrow;
6956 headerentry->type = PREF_NONE;
6958 compose->header_nextrow++;
6959 compose->header_last = headerentry;
6960 compose->header_list =
6961 g_slist_append(compose->header_list,
6962 headerentry);
6965 static void compose_add_header_entry(Compose *compose, const gchar *header,
6966 gchar *text, ComposePrefType pref_type)
6968 ComposeHeaderEntry *last_header = compose->header_last;
6969 gchar *tmp = g_strdup(text), *email;
6970 gboolean replyto_hdr;
6972 replyto_hdr = (!strcasecmp(header,
6973 prefs_common_translated_header_name("Reply-To:")) ||
6974 !strcasecmp(header,
6975 prefs_common_translated_header_name("Followup-To:")) ||
6976 !strcasecmp(header,
6977 prefs_common_translated_header_name("In-Reply-To:")));
6979 extract_address(tmp);
6980 email = g_utf8_strdown(tmp, -1);
6982 if (replyto_hdr == FALSE &&
6983 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6985 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6986 header, text, (gint) pref_type);
6987 g_free(email);
6988 g_free(tmp);
6989 return;
6992 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6993 gtk_entry_set_text(GTK_ENTRY(
6994 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6995 else
6996 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6997 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6998 last_header->type = pref_type;
7000 if (replyto_hdr == FALSE)
7001 g_hash_table_insert(compose->email_hashtable, email,
7002 GUINT_TO_POINTER(1));
7003 else
7004 g_free(email);
7006 g_free(tmp);
7009 static void compose_destroy_headerentry(Compose *compose,
7010 ComposeHeaderEntry *headerentry)
7012 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7013 gchar *email;
7015 extract_address(text);
7016 email = g_utf8_strdown(text, -1);
7017 g_hash_table_remove(compose->email_hashtable, email);
7018 g_free(text);
7019 g_free(email);
7021 gtk_widget_destroy(headerentry->combo);
7022 gtk_widget_destroy(headerentry->entry);
7023 gtk_widget_destroy(headerentry->button);
7024 gtk_widget_destroy(headerentry->hbox);
7025 g_free(headerentry);
7028 static void compose_remove_header_entries(Compose *compose)
7030 GSList *list;
7031 for (list = compose->header_list; list; list = list->next)
7032 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7034 compose->header_last = NULL;
7035 g_slist_free(compose->header_list);
7036 compose->header_list = NULL;
7037 compose->header_nextrow = 1;
7038 compose_create_header_entry(compose);
7041 static GtkWidget *compose_create_header(Compose *compose)
7043 GtkWidget *from_optmenu_hbox;
7044 GtkWidget *header_scrolledwin_main;
7045 GtkWidget *header_table_main;
7046 GtkWidget *header_scrolledwin;
7047 GtkWidget *header_table;
7049 /* parent with account selection and from header */
7050 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
7051 gtk_widget_show(header_scrolledwin_main);
7052 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7054 header_table_main = gtk_table_new(2, 2, FALSE);
7055 gtk_widget_show(header_table_main);
7056 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7057 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
7058 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
7060 from_optmenu_hbox = compose_account_option_menu_create(compose);
7061 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7062 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7064 /* child with header labels and entries */
7065 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7066 gtk_widget_show(header_scrolledwin);
7067 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7069 header_table = gtk_table_new(2, 2, FALSE);
7070 gtk_widget_show(header_table);
7071 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
7072 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7073 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
7075 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7076 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7078 compose->header_table = header_table;
7079 compose->header_list = NULL;
7080 compose->header_nextrow = 0;
7082 compose_create_header_entry(compose);
7084 compose->table = NULL;
7086 return header_scrolledwin_main;
7089 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7091 Compose *compose = (Compose *)data;
7092 GdkEventButton event;
7094 event.button = 3;
7095 event.time = gtk_get_current_event_time();
7097 return attach_button_pressed(compose->attach_clist, &event, compose);
7100 static GtkWidget *compose_create_attach(Compose *compose)
7102 GtkWidget *attach_scrwin;
7103 GtkWidget *attach_clist;
7105 GtkListStore *store;
7106 GtkCellRenderer *renderer;
7107 GtkTreeViewColumn *column;
7108 GtkTreeSelection *selection;
7110 /* attachment list */
7111 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7112 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7113 GTK_POLICY_AUTOMATIC,
7114 GTK_POLICY_AUTOMATIC);
7115 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7117 store = gtk_list_store_new(N_ATTACH_COLS,
7118 G_TYPE_STRING,
7119 G_TYPE_STRING,
7120 G_TYPE_STRING,
7121 G_TYPE_STRING,
7122 G_TYPE_POINTER,
7123 G_TYPE_AUTO_POINTER,
7124 -1);
7125 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7126 (GTK_TREE_MODEL(store)));
7127 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7128 g_object_unref(store);
7130 renderer = gtk_cell_renderer_text_new();
7131 column = gtk_tree_view_column_new_with_attributes
7132 (_("Mime type"), renderer, "text",
7133 COL_MIMETYPE, NULL);
7134 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7136 renderer = gtk_cell_renderer_text_new();
7137 column = gtk_tree_view_column_new_with_attributes
7138 (_("Size"), renderer, "text",
7139 COL_SIZE, NULL);
7140 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7142 renderer = gtk_cell_renderer_text_new();
7143 column = gtk_tree_view_column_new_with_attributes
7144 (_("Name"), renderer, "text",
7145 COL_NAME, NULL);
7146 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7148 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7149 prefs_common.use_stripes_everywhere);
7150 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7151 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7153 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7154 G_CALLBACK(attach_selected), compose);
7155 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7156 G_CALLBACK(attach_button_pressed), compose);
7157 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7158 G_CALLBACK(popup_attach_button_pressed), compose);
7159 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7160 G_CALLBACK(attach_key_pressed), compose);
7162 /* drag and drop */
7163 gtk_drag_dest_set(attach_clist,
7164 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7165 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7166 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7167 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7168 G_CALLBACK(compose_attach_drag_received_cb),
7169 compose);
7170 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7171 G_CALLBACK(compose_drag_drop),
7172 compose);
7174 compose->attach_scrwin = attach_scrwin;
7175 compose->attach_clist = attach_clist;
7177 return attach_scrwin;
7180 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7181 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7183 static GtkWidget *compose_create_others(Compose *compose)
7185 GtkWidget *table;
7186 GtkWidget *savemsg_checkbtn;
7187 GtkWidget *savemsg_combo;
7188 GtkWidget *savemsg_select;
7190 guint rowcount = 0;
7191 gchar *folderidentifier;
7193 /* Table for settings */
7194 table = gtk_table_new(3, 1, FALSE);
7195 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7196 gtk_widget_show(table);
7197 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7198 rowcount = 0;
7200 /* Save Message to folder */
7201 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7202 gtk_widget_show(savemsg_checkbtn);
7203 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7204 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7205 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7207 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7208 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7210 #if !GTK_CHECK_VERSION(2, 24, 0)
7211 savemsg_combo = gtk_combo_box_entry_new_text();
7212 #else
7213 savemsg_combo = gtk_combo_box_text_new_with_entry();
7214 #endif
7215 compose->savemsg_checkbtn = savemsg_checkbtn;
7216 compose->savemsg_combo = savemsg_combo;
7217 gtk_widget_show(savemsg_combo);
7219 if (prefs_common.compose_save_to_history)
7220 #if !GTK_CHECK_VERSION(2, 24, 0)
7221 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7222 prefs_common.compose_save_to_history);
7223 #else
7224 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7225 prefs_common.compose_save_to_history);
7226 #endif
7227 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7228 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7229 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7230 G_CALLBACK(compose_grab_focus_cb), compose);
7231 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7232 folderidentifier = folder_item_get_identifier(account_get_special_folder
7233 (compose->account, F_OUTBOX));
7234 compose_set_save_to(compose, folderidentifier);
7235 g_free(folderidentifier);
7238 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7239 gtk_widget_show(savemsg_select);
7240 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7241 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7242 G_CALLBACK(compose_savemsg_select_cb),
7243 compose);
7245 return table;
7248 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7250 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7251 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7254 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7256 FolderItem *dest;
7257 gchar * path;
7259 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7260 if (!dest) return;
7262 path = folder_item_get_identifier(dest);
7264 compose_set_save_to(compose, path);
7265 g_free(path);
7268 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7269 GdkAtom clip, GtkTextIter *insert_place);
7272 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7273 Compose *compose)
7275 gint prev_autowrap;
7276 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7277 #if USE_ENCHANT
7278 if (event->button == 3) {
7279 GtkTextIter iter;
7280 GtkTextIter sel_start, sel_end;
7281 gboolean stuff_selected;
7282 gint x, y;
7283 /* move the cursor to allow GtkAspell to check the word
7284 * under the mouse */
7285 if (event->x && event->y) {
7286 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7287 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7288 &x, &y);
7289 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7290 &iter, x, y);
7291 } else {
7292 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7293 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7295 /* get selection */
7296 stuff_selected = gtk_text_buffer_get_selection_bounds(
7297 buffer,
7298 &sel_start, &sel_end);
7300 gtk_text_buffer_place_cursor (buffer, &iter);
7301 /* reselect stuff */
7302 if (stuff_selected
7303 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7304 gtk_text_buffer_select_range(buffer,
7305 &sel_start, &sel_end);
7307 return FALSE; /* pass the event so that the right-click goes through */
7309 #endif
7310 if (event->button == 2) {
7311 GtkTextIter iter;
7312 gint x, y;
7313 BLOCK_WRAP();
7315 /* get the middle-click position to paste at the correct place */
7316 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7317 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7318 &x, &y);
7319 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7320 &iter, x, y);
7322 entry_paste_clipboard(compose, text,
7323 prefs_common.linewrap_pastes,
7324 GDK_SELECTION_PRIMARY, &iter);
7325 UNBLOCK_WRAP();
7326 return TRUE;
7328 return FALSE;
7331 #if USE_ENCHANT
7332 static void compose_spell_menu_changed(void *data)
7334 Compose *compose = (Compose *)data;
7335 GSList *items;
7336 GtkWidget *menuitem;
7337 GtkWidget *parent_item;
7338 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7339 GSList *spell_menu;
7341 if (compose->gtkaspell == NULL)
7342 return;
7344 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7345 "/Menu/Spelling/Options");
7347 /* setting the submenu removes /Spelling/Options from the factory
7348 * so we need to save it */
7350 if (parent_item == NULL) {
7351 parent_item = compose->aspell_options_menu;
7352 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7353 } else
7354 compose->aspell_options_menu = parent_item;
7356 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7358 spell_menu = g_slist_reverse(spell_menu);
7359 for (items = spell_menu;
7360 items; items = items->next) {
7361 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7362 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7363 gtk_widget_show(GTK_WIDGET(menuitem));
7365 g_slist_free(spell_menu);
7367 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7368 gtk_widget_show(parent_item);
7371 static void compose_dict_changed(void *data)
7373 Compose *compose = (Compose *) data;
7375 if(!compose->gtkaspell)
7376 return;
7377 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7378 return;
7380 gtkaspell_highlight_all(compose->gtkaspell);
7381 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7383 #endif
7385 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7387 Compose *compose = (Compose *)data;
7388 GdkEventButton event;
7390 event.button = 3;
7391 event.time = gtk_get_current_event_time();
7392 event.x = 0;
7393 event.y = 0;
7395 return text_clicked(compose->text, &event, compose);
7398 static gboolean compose_force_window_origin = TRUE;
7399 static Compose *compose_create(PrefsAccount *account,
7400 FolderItem *folder,
7401 ComposeMode mode,
7402 gboolean batch)
7404 Compose *compose;
7405 GtkWidget *window;
7406 GtkWidget *vbox;
7407 GtkWidget *menubar;
7408 GtkWidget *handlebox;
7410 GtkWidget *notebook;
7412 GtkWidget *attach_hbox;
7413 GtkWidget *attach_lab1;
7414 GtkWidget *attach_lab2;
7416 GtkWidget *vbox2;
7418 GtkWidget *label;
7419 GtkWidget *subject_hbox;
7420 GtkWidget *subject_frame;
7421 GtkWidget *subject_entry;
7422 GtkWidget *subject;
7423 GtkWidget *paned;
7425 GtkWidget *edit_vbox;
7426 GtkWidget *ruler_hbox;
7427 GtkWidget *ruler;
7428 GtkWidget *scrolledwin;
7429 GtkWidget *text;
7430 GtkTextBuffer *buffer;
7431 GtkClipboard *clipboard;
7433 UndoMain *undostruct;
7435 GtkWidget *popupmenu;
7436 GtkWidget *tmpl_menu;
7437 GtkActionGroup *action_group = NULL;
7439 #if USE_ENCHANT
7440 GtkAspell * gtkaspell = NULL;
7441 #endif
7443 static GdkGeometry geometry;
7445 cm_return_val_if_fail(account != NULL, NULL);
7447 debug_print("Creating compose window...\n");
7448 compose = g_new0(Compose, 1);
7450 compose->batch = batch;
7451 compose->account = account;
7452 compose->folder = folder;
7454 compose->mutex = cm_mutex_new();
7455 compose->set_cursor_pos = -1;
7457 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7459 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7460 gtk_widget_set_size_request(window, prefs_common.compose_width,
7461 prefs_common.compose_height);
7463 if (!geometry.max_width) {
7464 geometry.max_width = gdk_screen_width();
7465 geometry.max_height = gdk_screen_height();
7468 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7469 &geometry, GDK_HINT_MAX_SIZE);
7470 if (!geometry.min_width) {
7471 geometry.min_width = 600;
7472 geometry.min_height = 440;
7474 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7475 &geometry, GDK_HINT_MIN_SIZE);
7477 #ifndef GENERIC_UMPC
7478 if (compose_force_window_origin)
7479 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7480 prefs_common.compose_y);
7481 #endif
7482 g_signal_connect(G_OBJECT(window), "delete_event",
7483 G_CALLBACK(compose_delete_cb), compose);
7484 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7485 gtk_widget_realize(window);
7487 gtkut_widget_set_composer_icon(window);
7489 vbox = gtk_vbox_new(FALSE, 0);
7490 gtk_container_add(GTK_CONTAINER(window), vbox);
7492 compose->ui_manager = gtk_ui_manager_new();
7493 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7494 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7495 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7496 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7497 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7498 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7499 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7500 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7501 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7502 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7504 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7506 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7507 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7508 #ifdef USE_ENCHANT
7509 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7510 #endif
7511 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7512 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7513 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7515 /* Compose menu */
7516 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7517 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7518 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7519 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7520 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7521 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7522 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7523 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7524 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7525 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7526 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7527 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7528 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7530 /* Edit menu */
7531 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7532 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7533 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7535 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7536 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7537 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7539 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7540 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7541 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7542 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7544 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7546 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7547 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7548 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7549 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7550 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7551 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7552 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7553 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7554 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7555 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7556 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7557 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7558 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7559 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7560 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7562 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7564 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7565 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7566 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7567 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7568 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7570 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7572 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7574 #if USE_ENCHANT
7575 /* Spelling menu */
7576 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7577 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7578 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7579 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7580 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7581 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7582 #endif
7584 /* Options menu */
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7586 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7587 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7588 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7601 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7602 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7615 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7617 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7621 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)
7622 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)
7623 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7625 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7628 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)
7629 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)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7633 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7634 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)
7635 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7637 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7638 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)
7639 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7641 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7643 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7644 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)
7645 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7646 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7647 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7649 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7650 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)
7651 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)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7655 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7656 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7657 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7658 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7660 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7662 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7664 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)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7667 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7668 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7669 /* phew. */
7671 /* Tools menu */
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7673 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7676 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7677 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7679 /* Help menu */
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7682 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7683 gtk_widget_show_all(menubar);
7685 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7686 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7688 if (prefs_common.toolbar_detachable) {
7689 handlebox = gtk_handle_box_new();
7690 } else {
7691 handlebox = gtk_hbox_new(FALSE, 0);
7693 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7695 gtk_widget_realize(handlebox);
7696 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7697 (gpointer)compose);
7699 vbox2 = gtk_vbox_new(FALSE, 2);
7700 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7701 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7703 /* Notebook */
7704 notebook = gtk_notebook_new();
7705 gtk_widget_show(notebook);
7707 /* header labels and entries */
7708 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7709 compose_create_header(compose),
7710 gtk_label_new_with_mnemonic(_("Hea_der")));
7711 /* attachment list */
7712 attach_hbox = gtk_hbox_new(FALSE, 0);
7713 gtk_widget_show(attach_hbox);
7715 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7716 gtk_widget_show(attach_lab1);
7717 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7719 attach_lab2 = gtk_label_new("");
7720 gtk_widget_show(attach_lab2);
7721 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7723 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7724 compose_create_attach(compose),
7725 attach_hbox);
7726 /* Others Tab */
7727 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7728 compose_create_others(compose),
7729 gtk_label_new_with_mnemonic(_("Othe_rs")));
7731 /* Subject */
7732 subject_hbox = gtk_hbox_new(FALSE, 0);
7733 gtk_widget_show(subject_hbox);
7735 subject_frame = gtk_frame_new(NULL);
7736 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7737 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7738 gtk_widget_show(subject_frame);
7740 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7741 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7742 gtk_widget_show(subject);
7744 label = gtk_label_new_with_mnemonic(_("Subject:"));
7745 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7746 gtk_widget_show(label);
7748 #ifdef USE_ENCHANT
7749 subject_entry = claws_spell_entry_new();
7750 #else
7751 subject_entry = gtk_entry_new();
7752 #endif
7753 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7754 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7755 G_CALLBACK(compose_grab_focus_cb), compose);
7756 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7757 gtk_widget_show(subject_entry);
7758 compose->subject_entry = subject_entry;
7759 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7761 edit_vbox = gtk_vbox_new(FALSE, 0);
7763 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7765 /* ruler */
7766 ruler_hbox = gtk_hbox_new(FALSE, 0);
7767 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7769 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7770 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7771 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7772 BORDER_WIDTH);
7774 /* text widget */
7775 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7776 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7777 GTK_POLICY_AUTOMATIC,
7778 GTK_POLICY_AUTOMATIC);
7779 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7780 GTK_SHADOW_IN);
7781 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7783 text = gtk_text_view_new();
7784 if (prefs_common.show_compose_margin) {
7785 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7786 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7788 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7789 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7790 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7791 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7792 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7794 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7795 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7796 G_CALLBACK(compose_edit_size_alloc),
7797 ruler);
7798 g_signal_connect(G_OBJECT(buffer), "changed",
7799 G_CALLBACK(compose_changed_cb), compose);
7800 g_signal_connect(G_OBJECT(text), "grab_focus",
7801 G_CALLBACK(compose_grab_focus_cb), compose);
7802 g_signal_connect(G_OBJECT(buffer), "insert_text",
7803 G_CALLBACK(text_inserted), compose);
7804 g_signal_connect(G_OBJECT(text), "button_press_event",
7805 G_CALLBACK(text_clicked), compose);
7806 g_signal_connect(G_OBJECT(text), "popup-menu",
7807 G_CALLBACK(compose_popup_menu), compose);
7808 g_signal_connect(G_OBJECT(subject_entry), "changed",
7809 G_CALLBACK(compose_changed_cb), compose);
7810 g_signal_connect(G_OBJECT(subject_entry), "activate",
7811 G_CALLBACK(compose_subject_entry_activated), compose);
7813 /* drag and drop */
7814 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7815 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7816 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7817 g_signal_connect(G_OBJECT(text), "drag_data_received",
7818 G_CALLBACK(compose_insert_drag_received_cb),
7819 compose);
7820 g_signal_connect(G_OBJECT(text), "drag-drop",
7821 G_CALLBACK(compose_drag_drop),
7822 compose);
7823 g_signal_connect(G_OBJECT(text), "key-press-event",
7824 G_CALLBACK(completion_set_focus_to_subject),
7825 compose);
7826 gtk_widget_show_all(vbox);
7828 /* pane between attach clist and text */
7829 paned = gtk_vpaned_new();
7830 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7831 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7832 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7833 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7834 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7835 G_CALLBACK(compose_notebook_size_alloc), paned);
7837 gtk_widget_show_all(paned);
7840 if (prefs_common.textfont) {
7841 PangoFontDescription *font_desc;
7843 font_desc = pango_font_description_from_string
7844 (prefs_common.textfont);
7845 if (font_desc) {
7846 gtk_widget_modify_font(text, font_desc);
7847 pango_font_description_free(font_desc);
7851 gtk_action_group_add_actions(action_group, compose_popup_entries,
7852 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7854 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7860 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7862 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7863 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7864 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7866 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7868 undostruct = undo_init(text);
7869 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7870 compose);
7872 address_completion_start(window);
7874 compose->window = window;
7875 compose->vbox = vbox;
7876 compose->menubar = menubar;
7877 compose->handlebox = handlebox;
7879 compose->vbox2 = vbox2;
7881 compose->paned = paned;
7883 compose->attach_label = attach_lab2;
7885 compose->notebook = notebook;
7886 compose->edit_vbox = edit_vbox;
7887 compose->ruler_hbox = ruler_hbox;
7888 compose->ruler = ruler;
7889 compose->scrolledwin = scrolledwin;
7890 compose->text = text;
7892 compose->focused_editable = NULL;
7894 compose->popupmenu = popupmenu;
7896 compose->tmpl_menu = tmpl_menu;
7898 compose->mode = mode;
7899 compose->rmode = mode;
7901 compose->targetinfo = NULL;
7902 compose->replyinfo = NULL;
7903 compose->fwdinfo = NULL;
7905 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7906 g_str_equal, (GDestroyNotify) g_free, NULL);
7908 compose->replyto = NULL;
7909 compose->cc = NULL;
7910 compose->bcc = NULL;
7911 compose->followup_to = NULL;
7913 compose->ml_post = NULL;
7915 compose->inreplyto = NULL;
7916 compose->references = NULL;
7917 compose->msgid = NULL;
7918 compose->boundary = NULL;
7920 compose->autowrap = prefs_common.autowrap;
7921 compose->autoindent = prefs_common.auto_indent;
7922 compose->use_signing = FALSE;
7923 compose->use_encryption = FALSE;
7924 compose->privacy_system = NULL;
7926 compose->modified = FALSE;
7928 compose->return_receipt = FALSE;
7930 compose->to_list = NULL;
7931 compose->newsgroup_list = NULL;
7933 compose->undostruct = undostruct;
7935 compose->sig_str = NULL;
7937 compose->exteditor_file = NULL;
7938 compose->exteditor_pid = -1;
7939 compose->exteditor_tag = -1;
7940 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
7942 compose->folder_update_callback_id =
7943 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
7944 compose_update_folder_hook,
7945 (gpointer) compose);
7947 #if USE_ENCHANT
7948 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7949 if (mode != COMPOSE_REDIRECT) {
7950 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7951 strcmp(prefs_common.dictionary, "")) {
7952 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7953 prefs_common.alt_dictionary,
7954 conv_get_locale_charset_str(),
7955 prefs_common.misspelled_col,
7956 prefs_common.check_while_typing,
7957 prefs_common.recheck_when_changing_dict,
7958 prefs_common.use_alternate,
7959 prefs_common.use_both_dicts,
7960 GTK_TEXT_VIEW(text),
7961 GTK_WINDOW(compose->window),
7962 compose_dict_changed,
7963 compose_spell_menu_changed,
7964 compose);
7965 if (!gtkaspell) {
7966 alertpanel_error(_("Spell checker could not "
7967 "be started.\n%s"),
7968 gtkaspell_checkers_strerror());
7969 gtkaspell_checkers_reset_error();
7970 } else {
7971 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7975 compose->gtkaspell = gtkaspell;
7976 compose_spell_menu_changed(compose);
7977 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7978 #endif
7980 compose_select_account(compose, account, TRUE);
7982 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7983 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7985 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7986 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7988 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7989 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7991 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7992 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7994 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7995 if (account->protocol != A_NNTP)
7996 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7997 prefs_common_translated_header_name("To:"));
7998 else
7999 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8000 prefs_common_translated_header_name("Newsgroups:"));
8002 #ifndef USE_NEW_ADDRBOOK
8003 addressbook_set_target_compose(compose);
8004 #endif
8005 if (mode != COMPOSE_REDIRECT)
8006 compose_set_template_menu(compose);
8007 else {
8008 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8011 compose_list = g_list_append(compose_list, compose);
8013 if (!prefs_common.show_ruler)
8014 gtk_widget_hide(ruler_hbox);
8016 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8018 /* Priority */
8019 compose->priority = PRIORITY_NORMAL;
8020 compose_update_priority_menu_item(compose);
8022 compose_set_out_encoding(compose);
8024 /* Actions menu */
8025 compose_update_actions_menu(compose);
8027 /* Privacy Systems menu */
8028 compose_update_privacy_systems_menu(compose);
8030 activate_privacy_system(compose, account, TRUE);
8031 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8032 if (batch) {
8033 gtk_widget_realize(window);
8034 } else {
8035 gtk_widget_show(window);
8038 return compose;
8041 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8043 GList *accounts;
8044 GtkWidget *hbox;
8045 GtkWidget *optmenu;
8046 GtkWidget *optmenubox;
8047 GtkListStore *menu;
8048 GtkTreeIter iter;
8049 GtkWidget *from_name = NULL;
8051 gint num = 0, def_menu = 0;
8053 accounts = account_get_list();
8054 cm_return_val_if_fail(accounts != NULL, NULL);
8056 optmenubox = gtk_event_box_new();
8057 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8058 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8060 hbox = gtk_hbox_new(FALSE, 6);
8061 from_name = gtk_entry_new();
8063 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8064 G_CALLBACK(compose_grab_focus_cb), compose);
8066 for (; accounts != NULL; accounts = accounts->next, num++) {
8067 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8068 gchar *name, *from = NULL;
8070 if (ac == compose->account) def_menu = num;
8072 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
8073 ac->account_name);
8075 if (ac == compose->account) {
8076 if (ac->name && *ac->name) {
8077 gchar *buf;
8078 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8079 from = g_strdup_printf("%s <%s>",
8080 buf, ac->address);
8081 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8082 } else {
8083 from = g_strdup_printf("%s",
8084 ac->address);
8085 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8088 COMBOBOX_ADD(menu, name, ac->account_id);
8089 g_free(name);
8090 g_free(from);
8093 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8095 g_signal_connect(G_OBJECT(optmenu), "changed",
8096 G_CALLBACK(account_activated),
8097 compose);
8098 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8099 G_CALLBACK(compose_entry_popup_extend),
8100 NULL);
8102 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8103 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8105 CLAWS_SET_TIP(optmenubox,
8106 _("Account to use for this email"));
8107 CLAWS_SET_TIP(from_name,
8108 _("Sender address to be used"));
8110 compose->account_combo = optmenu;
8111 compose->from_name = from_name;
8113 return hbox;
8116 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8118 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8119 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8120 Compose *compose = (Compose *) data;
8121 if (active) {
8122 compose->priority = value;
8126 static void compose_reply_change_mode(Compose *compose,
8127 ComposeMode action)
8129 gboolean was_modified = compose->modified;
8131 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8133 cm_return_if_fail(compose->replyinfo != NULL);
8135 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8136 ml = TRUE;
8137 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8138 followup = TRUE;
8139 if (action == COMPOSE_REPLY_TO_ALL)
8140 all = TRUE;
8141 if (action == COMPOSE_REPLY_TO_SENDER)
8142 sender = TRUE;
8143 if (action == COMPOSE_REPLY_TO_LIST)
8144 ml = TRUE;
8146 compose_remove_header_entries(compose);
8147 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8148 if (compose->account->set_autocc && compose->account->auto_cc)
8149 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8151 if (compose->account->set_autobcc && compose->account->auto_bcc)
8152 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8154 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8155 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8156 compose_show_first_last_header(compose, TRUE);
8157 compose->modified = was_modified;
8158 compose_set_title(compose);
8161 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8163 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8164 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8165 Compose *compose = (Compose *) data;
8167 if (active)
8168 compose_reply_change_mode(compose, value);
8171 static void compose_update_priority_menu_item(Compose * compose)
8173 GtkWidget *menuitem = NULL;
8174 switch (compose->priority) {
8175 case PRIORITY_HIGHEST:
8176 menuitem = gtk_ui_manager_get_widget
8177 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8178 break;
8179 case PRIORITY_HIGH:
8180 menuitem = gtk_ui_manager_get_widget
8181 (compose->ui_manager, "/Menu/Options/Priority/High");
8182 break;
8183 case PRIORITY_NORMAL:
8184 menuitem = gtk_ui_manager_get_widget
8185 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8186 break;
8187 case PRIORITY_LOW:
8188 menuitem = gtk_ui_manager_get_widget
8189 (compose->ui_manager, "/Menu/Options/Priority/Low");
8190 break;
8191 case PRIORITY_LOWEST:
8192 menuitem = gtk_ui_manager_get_widget
8193 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8194 break;
8196 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8199 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8201 Compose *compose = (Compose *) data;
8202 gchar *systemid;
8203 gboolean can_sign = FALSE, can_encrypt = FALSE;
8205 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8207 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8208 return;
8210 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8211 g_free(compose->privacy_system);
8212 compose->privacy_system = NULL;
8213 if (systemid != NULL) {
8214 compose->privacy_system = g_strdup(systemid);
8216 can_sign = privacy_system_can_sign(systemid);
8217 can_encrypt = privacy_system_can_encrypt(systemid);
8220 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8222 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8223 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8226 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8228 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8229 GtkWidget *menuitem = NULL;
8230 GList *children, *amenu;
8231 gboolean can_sign = FALSE, can_encrypt = FALSE;
8232 gboolean found = FALSE;
8234 if (compose->privacy_system != NULL) {
8235 gchar *systemid;
8236 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8237 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8238 cm_return_if_fail(menuitem != NULL);
8240 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8241 amenu = children;
8242 menuitem = NULL;
8243 while (amenu != NULL) {
8244 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8245 if (systemid != NULL) {
8246 if (strcmp(systemid, compose->privacy_system) == 0 &&
8247 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8248 menuitem = GTK_WIDGET(amenu->data);
8250 can_sign = privacy_system_can_sign(systemid);
8251 can_encrypt = privacy_system_can_encrypt(systemid);
8252 found = TRUE;
8253 break;
8255 } else if (strlen(compose->privacy_system) == 0 &&
8256 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8257 menuitem = GTK_WIDGET(amenu->data);
8259 can_sign = FALSE;
8260 can_encrypt = FALSE;
8261 found = TRUE;
8262 break;
8265 amenu = amenu->next;
8267 g_list_free(children);
8268 if (menuitem != NULL)
8269 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8271 if (warn && !found && strlen(compose->privacy_system)) {
8272 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8273 "will not be able to sign or encrypt this message."),
8274 compose->privacy_system);
8278 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8279 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8282 static void compose_set_out_encoding(Compose *compose)
8284 CharSet out_encoding;
8285 const gchar *branch = NULL;
8286 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8288 switch(out_encoding) {
8289 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8290 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8291 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8292 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8293 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8294 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8295 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8296 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8297 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8298 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8299 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8300 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8301 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8302 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8303 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8304 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8305 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8306 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8307 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8308 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8309 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8310 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8311 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8312 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8313 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8314 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8315 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8316 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8317 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8318 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8319 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8320 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8321 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8323 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8326 static void compose_set_template_menu(Compose *compose)
8328 GSList *tmpl_list, *cur;
8329 GtkWidget *menu;
8330 GtkWidget *item;
8332 tmpl_list = template_get_config();
8334 menu = gtk_menu_new();
8336 gtk_menu_set_accel_group (GTK_MENU (menu),
8337 gtk_ui_manager_get_accel_group(compose->ui_manager));
8338 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8339 Template *tmpl = (Template *)cur->data;
8340 gchar *accel_path = NULL;
8341 item = gtk_menu_item_new_with_label(tmpl->name);
8342 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8343 g_signal_connect(G_OBJECT(item), "activate",
8344 G_CALLBACK(compose_template_activate_cb),
8345 compose);
8346 g_object_set_data(G_OBJECT(item), "template", tmpl);
8347 gtk_widget_show(item);
8348 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8349 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8350 g_free(accel_path);
8353 gtk_widget_show(menu);
8354 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8357 void compose_update_actions_menu(Compose *compose)
8359 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8362 static void compose_update_privacy_systems_menu(Compose *compose)
8364 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8365 GSList *systems, *cur;
8366 GtkWidget *widget;
8367 GtkWidget *system_none;
8368 GSList *group;
8369 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8370 GtkWidget *privacy_menu = gtk_menu_new();
8372 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8373 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8375 g_signal_connect(G_OBJECT(system_none), "activate",
8376 G_CALLBACK(compose_set_privacy_system_cb), compose);
8378 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8379 gtk_widget_show(system_none);
8381 systems = privacy_get_system_ids();
8382 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8383 gchar *systemid = cur->data;
8385 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8386 widget = gtk_radio_menu_item_new_with_label(group,
8387 privacy_system_get_name(systemid));
8388 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8389 g_strdup(systemid), g_free);
8390 g_signal_connect(G_OBJECT(widget), "activate",
8391 G_CALLBACK(compose_set_privacy_system_cb), compose);
8393 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8394 gtk_widget_show(widget);
8395 g_free(systemid);
8397 g_slist_free(systems);
8398 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8399 gtk_widget_show_all(privacy_menu);
8400 gtk_widget_show_all(privacy_menuitem);
8403 void compose_reflect_prefs_all(void)
8405 GList *cur;
8406 Compose *compose;
8408 for (cur = compose_list; cur != NULL; cur = cur->next) {
8409 compose = (Compose *)cur->data;
8410 compose_set_template_menu(compose);
8414 void compose_reflect_prefs_pixmap_theme(void)
8416 GList *cur;
8417 Compose *compose;
8419 for (cur = compose_list; cur != NULL; cur = cur->next) {
8420 compose = (Compose *)cur->data;
8421 toolbar_update(TOOLBAR_COMPOSE, compose);
8425 static const gchar *compose_quote_char_from_context(Compose *compose)
8427 const gchar *qmark = NULL;
8429 cm_return_val_if_fail(compose != NULL, NULL);
8431 switch (compose->mode) {
8432 /* use forward-specific quote char */
8433 case COMPOSE_FORWARD:
8434 case COMPOSE_FORWARD_AS_ATTACH:
8435 case COMPOSE_FORWARD_INLINE:
8436 if (compose->folder && compose->folder->prefs &&
8437 compose->folder->prefs->forward_with_format)
8438 qmark = compose->folder->prefs->forward_quotemark;
8439 else if (compose->account->forward_with_format)
8440 qmark = compose->account->forward_quotemark;
8441 else
8442 qmark = prefs_common.fw_quotemark;
8443 break;
8445 /* use reply-specific quote char in all other modes */
8446 default:
8447 if (compose->folder && compose->folder->prefs &&
8448 compose->folder->prefs->reply_with_format)
8449 qmark = compose->folder->prefs->reply_quotemark;
8450 else if (compose->account->reply_with_format)
8451 qmark = compose->account->reply_quotemark;
8452 else
8453 qmark = prefs_common.quotemark;
8454 break;
8457 if (qmark == NULL || *qmark == '\0')
8458 qmark = "> ";
8460 return qmark;
8463 static void compose_template_apply(Compose *compose, Template *tmpl,
8464 gboolean replace)
8466 GtkTextView *text;
8467 GtkTextBuffer *buffer;
8468 GtkTextMark *mark;
8469 GtkTextIter iter;
8470 const gchar *qmark;
8471 gchar *parsed_str = NULL;
8472 gint cursor_pos = 0;
8473 const gchar *err_msg = _("The body of the template has an error at line %d.");
8474 if (!tmpl) return;
8476 /* process the body */
8478 text = GTK_TEXT_VIEW(compose->text);
8479 buffer = gtk_text_view_get_buffer(text);
8481 if (tmpl->value) {
8482 qmark = compose_quote_char_from_context(compose);
8484 if (compose->replyinfo != NULL) {
8486 if (replace)
8487 gtk_text_buffer_set_text(buffer, "", -1);
8488 mark = gtk_text_buffer_get_insert(buffer);
8489 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8491 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8492 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8494 } else if (compose->fwdinfo != NULL) {
8496 if (replace)
8497 gtk_text_buffer_set_text(buffer, "", -1);
8498 mark = gtk_text_buffer_get_insert(buffer);
8499 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8501 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8502 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8504 } else {
8505 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8507 GtkTextIter start, end;
8508 gchar *tmp = NULL;
8510 gtk_text_buffer_get_start_iter(buffer, &start);
8511 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8512 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8514 /* clear the buffer now */
8515 if (replace)
8516 gtk_text_buffer_set_text(buffer, "", -1);
8518 parsed_str = compose_quote_fmt(compose, dummyinfo,
8519 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8520 procmsg_msginfo_free( dummyinfo );
8522 g_free( tmp );
8524 } else {
8525 if (replace)
8526 gtk_text_buffer_set_text(buffer, "", -1);
8527 mark = gtk_text_buffer_get_insert(buffer);
8528 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8531 if (replace && parsed_str && compose->account->auto_sig)
8532 compose_insert_sig(compose, FALSE);
8534 if (replace && parsed_str) {
8535 gtk_text_buffer_get_start_iter(buffer, &iter);
8536 gtk_text_buffer_place_cursor(buffer, &iter);
8539 if (parsed_str) {
8540 cursor_pos = quote_fmt_get_cursor_pos();
8541 compose->set_cursor_pos = cursor_pos;
8542 if (cursor_pos == -1)
8543 cursor_pos = 0;
8544 gtk_text_buffer_get_start_iter(buffer, &iter);
8545 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8546 gtk_text_buffer_place_cursor(buffer, &iter);
8549 /* process the other fields */
8551 compose_template_apply_fields(compose, tmpl);
8552 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8553 quote_fmt_reset_vartable();
8554 compose_changed_cb(NULL, compose);
8556 #ifdef USE_ENCHANT
8557 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8558 gtkaspell_highlight_all(compose->gtkaspell);
8559 #endif
8562 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8564 MsgInfo* dummyinfo = NULL;
8565 MsgInfo *msginfo = NULL;
8566 gchar *buf = NULL;
8568 if (compose->replyinfo != NULL)
8569 msginfo = compose->replyinfo;
8570 else if (compose->fwdinfo != NULL)
8571 msginfo = compose->fwdinfo;
8572 else {
8573 dummyinfo = compose_msginfo_new_from_compose(compose);
8574 msginfo = dummyinfo;
8577 if (tmpl->from && *tmpl->from != '\0') {
8578 #ifdef USE_ENCHANT
8579 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8580 compose->gtkaspell);
8581 #else
8582 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8583 #endif
8584 quote_fmt_scan_string(tmpl->from);
8585 quote_fmt_parse();
8587 buf = quote_fmt_get_buffer();
8588 if (buf == NULL) {
8589 alertpanel_error(_("Template From format error."));
8590 } else {
8591 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8595 if (tmpl->to && *tmpl->to != '\0') {
8596 #ifdef USE_ENCHANT
8597 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8598 compose->gtkaspell);
8599 #else
8600 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8601 #endif
8602 quote_fmt_scan_string(tmpl->to);
8603 quote_fmt_parse();
8605 buf = quote_fmt_get_buffer();
8606 if (buf == NULL) {
8607 alertpanel_error(_("Template To format error."));
8608 } else {
8609 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8613 if (tmpl->cc && *tmpl->cc != '\0') {
8614 #ifdef USE_ENCHANT
8615 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8616 compose->gtkaspell);
8617 #else
8618 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8619 #endif
8620 quote_fmt_scan_string(tmpl->cc);
8621 quote_fmt_parse();
8623 buf = quote_fmt_get_buffer();
8624 if (buf == NULL) {
8625 alertpanel_error(_("Template Cc format error."));
8626 } else {
8627 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8631 if (tmpl->bcc && *tmpl->bcc != '\0') {
8632 #ifdef USE_ENCHANT
8633 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8634 compose->gtkaspell);
8635 #else
8636 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8637 #endif
8638 quote_fmt_scan_string(tmpl->bcc);
8639 quote_fmt_parse();
8641 buf = quote_fmt_get_buffer();
8642 if (buf == NULL) {
8643 alertpanel_error(_("Template Bcc format error."));
8644 } else {
8645 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8649 /* process the subject */
8650 if (tmpl->subject && *tmpl->subject != '\0') {
8651 #ifdef USE_ENCHANT
8652 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8653 compose->gtkaspell);
8654 #else
8655 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8656 #endif
8657 quote_fmt_scan_string(tmpl->subject);
8658 quote_fmt_parse();
8660 buf = quote_fmt_get_buffer();
8661 if (buf == NULL) {
8662 alertpanel_error(_("Template subject format error."));
8663 } else {
8664 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8668 procmsg_msginfo_free( dummyinfo );
8671 static void compose_destroy(Compose *compose)
8673 GtkAllocation allocation;
8674 GtkTextBuffer *buffer;
8675 GtkClipboard *clipboard;
8677 compose_list = g_list_remove(compose_list, compose);
8679 if (compose->updating) {
8680 debug_print("danger, not destroying anything now\n");
8681 compose->deferred_destroy = TRUE;
8682 return;
8685 /* NOTE: address_completion_end() does nothing with the window
8686 * however this may change. */
8687 address_completion_end(compose->window);
8689 slist_free_strings_full(compose->to_list);
8690 slist_free_strings_full(compose->newsgroup_list);
8691 slist_free_strings_full(compose->header_list);
8693 slist_free_strings_full(extra_headers);
8694 extra_headers = NULL;
8696 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8698 g_hash_table_destroy(compose->email_hashtable);
8700 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8701 compose->folder_update_callback_id);
8703 procmsg_msginfo_free(compose->targetinfo);
8704 procmsg_msginfo_free(compose->replyinfo);
8705 procmsg_msginfo_free(compose->fwdinfo);
8707 g_free(compose->replyto);
8708 g_free(compose->cc);
8709 g_free(compose->bcc);
8710 g_free(compose->newsgroups);
8711 g_free(compose->followup_to);
8713 g_free(compose->ml_post);
8715 g_free(compose->inreplyto);
8716 g_free(compose->references);
8717 g_free(compose->msgid);
8718 g_free(compose->boundary);
8720 g_free(compose->redirect_filename);
8721 if (compose->undostruct)
8722 undo_destroy(compose->undostruct);
8724 g_free(compose->sig_str);
8726 g_free(compose->exteditor_file);
8728 g_free(compose->orig_charset);
8730 g_free(compose->privacy_system);
8732 #ifndef USE_NEW_ADDRBOOK
8733 if (addressbook_get_target_compose() == compose)
8734 addressbook_set_target_compose(NULL);
8735 #endif
8736 #if USE_ENCHANT
8737 if (compose->gtkaspell) {
8738 gtkaspell_delete(compose->gtkaspell);
8739 compose->gtkaspell = NULL;
8741 #endif
8743 if (!compose->batch) {
8744 gtk_widget_get_allocation(compose->window, &allocation);
8745 prefs_common.compose_width = allocation.width;
8746 prefs_common.compose_height = allocation.height;
8749 if (!gtk_widget_get_parent(compose->paned))
8750 gtk_widget_destroy(compose->paned);
8751 gtk_widget_destroy(compose->popupmenu);
8753 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8754 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8755 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8757 gtk_widget_destroy(compose->window);
8758 toolbar_destroy(compose->toolbar);
8759 g_free(compose->toolbar);
8760 cm_mutex_free(compose->mutex);
8761 g_free(compose);
8764 static void compose_attach_info_free(AttachInfo *ainfo)
8766 g_free(ainfo->file);
8767 g_free(ainfo->content_type);
8768 g_free(ainfo->name);
8769 g_free(ainfo->charset);
8770 g_free(ainfo);
8773 static void compose_attach_update_label(Compose *compose)
8775 GtkTreeIter iter;
8776 gint i = 1;
8777 gchar *text;
8778 GtkTreeModel *model;
8780 if(compose == NULL)
8781 return;
8783 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8784 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8785 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8786 return;
8789 while(gtk_tree_model_iter_next(model, &iter))
8790 i++;
8792 text = g_strdup_printf("(%d)", i);
8793 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8794 g_free(text);
8797 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8799 Compose *compose = (Compose *)data;
8800 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8801 GtkTreeSelection *selection;
8802 GList *sel, *cur;
8803 GtkTreeModel *model;
8805 selection = gtk_tree_view_get_selection(tree_view);
8806 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8808 if (!sel)
8809 return;
8811 for (cur = sel; cur != NULL; cur = cur->next) {
8812 GtkTreePath *path = cur->data;
8813 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8814 (model, cur->data);
8815 cur->data = ref;
8816 gtk_tree_path_free(path);
8819 for (cur = sel; cur != NULL; cur = cur->next) {
8820 GtkTreeRowReference *ref = cur->data;
8821 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8822 GtkTreeIter iter;
8824 if (gtk_tree_model_get_iter(model, &iter, path))
8825 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8827 gtk_tree_path_free(path);
8828 gtk_tree_row_reference_free(ref);
8831 g_list_free(sel);
8832 compose_attach_update_label(compose);
8835 static struct _AttachProperty
8837 GtkWidget *window;
8838 GtkWidget *mimetype_entry;
8839 GtkWidget *encoding_optmenu;
8840 GtkWidget *path_entry;
8841 GtkWidget *filename_entry;
8842 GtkWidget *ok_btn;
8843 GtkWidget *cancel_btn;
8844 } attach_prop;
8846 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8848 gtk_tree_path_free((GtkTreePath *)ptr);
8851 static void compose_attach_property(GtkAction *action, gpointer data)
8853 Compose *compose = (Compose *)data;
8854 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8855 AttachInfo *ainfo;
8856 GtkComboBox *optmenu;
8857 GtkTreeSelection *selection;
8858 GList *sel;
8859 GtkTreeModel *model;
8860 GtkTreeIter iter;
8861 GtkTreePath *path;
8862 static gboolean cancelled;
8864 /* only if one selected */
8865 selection = gtk_tree_view_get_selection(tree_view);
8866 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8867 return;
8869 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8870 if (!sel)
8871 return;
8873 path = (GtkTreePath *) sel->data;
8874 gtk_tree_model_get_iter(model, &iter, path);
8875 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8877 if (!ainfo) {
8878 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8879 g_list_free(sel);
8880 return;
8882 g_list_free(sel);
8884 if (!attach_prop.window)
8885 compose_attach_property_create(&cancelled);
8886 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8887 gtk_widget_grab_focus(attach_prop.ok_btn);
8888 gtk_widget_show(attach_prop.window);
8889 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8890 GTK_WINDOW(compose->window));
8892 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8893 if (ainfo->encoding == ENC_UNKNOWN)
8894 combobox_select_by_data(optmenu, ENC_BASE64);
8895 else
8896 combobox_select_by_data(optmenu, ainfo->encoding);
8898 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8899 ainfo->content_type ? ainfo->content_type : "");
8900 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8901 ainfo->file ? ainfo->file : "");
8902 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8903 ainfo->name ? ainfo->name : "");
8905 for (;;) {
8906 const gchar *entry_text;
8907 gchar *text;
8908 gchar *cnttype = NULL;
8909 gchar *file = NULL;
8910 off_t size = 0;
8912 cancelled = FALSE;
8913 gtk_main();
8915 gtk_widget_hide(attach_prop.window);
8916 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8918 if (cancelled)
8919 break;
8921 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8922 if (*entry_text != '\0') {
8923 gchar *p;
8925 text = g_strstrip(g_strdup(entry_text));
8926 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8927 cnttype = g_strdup(text);
8928 g_free(text);
8929 } else {
8930 alertpanel_error(_("Invalid MIME type."));
8931 g_free(text);
8932 continue;
8936 ainfo->encoding = combobox_get_active_data(optmenu);
8938 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8939 if (*entry_text != '\0') {
8940 if (is_file_exist(entry_text) &&
8941 (size = get_file_size(entry_text)) > 0)
8942 file = g_strdup(entry_text);
8943 else {
8944 alertpanel_error
8945 (_("File doesn't exist or is empty."));
8946 g_free(cnttype);
8947 continue;
8951 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8952 if (*entry_text != '\0') {
8953 g_free(ainfo->name);
8954 ainfo->name = g_strdup(entry_text);
8957 if (cnttype) {
8958 g_free(ainfo->content_type);
8959 ainfo->content_type = cnttype;
8961 if (file) {
8962 g_free(ainfo->file);
8963 ainfo->file = file;
8965 if (size)
8966 ainfo->size = (goffset)size;
8968 /* update tree store */
8969 text = to_human_readable(ainfo->size);
8970 gtk_tree_model_get_iter(model, &iter, path);
8971 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8972 COL_MIMETYPE, ainfo->content_type,
8973 COL_SIZE, text,
8974 COL_NAME, ainfo->name,
8975 COL_CHARSET, ainfo->charset,
8976 -1);
8978 break;
8981 gtk_tree_path_free(path);
8984 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8986 label = gtk_label_new(str); \
8987 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8988 GTK_FILL, 0, 0, 0); \
8989 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8991 entry = gtk_entry_new(); \
8992 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8993 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8996 static void compose_attach_property_create(gboolean *cancelled)
8998 GtkWidget *window;
8999 GtkWidget *vbox;
9000 GtkWidget *table;
9001 GtkWidget *label;
9002 GtkWidget *mimetype_entry;
9003 GtkWidget *hbox;
9004 GtkWidget *optmenu;
9005 GtkListStore *optmenu_menu;
9006 GtkWidget *path_entry;
9007 GtkWidget *filename_entry;
9008 GtkWidget *hbbox;
9009 GtkWidget *ok_btn;
9010 GtkWidget *cancel_btn;
9011 GList *mime_type_list, *strlist;
9012 GtkTreeIter iter;
9014 debug_print("Creating attach_property window...\n");
9016 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9017 gtk_widget_set_size_request(window, 480, -1);
9018 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9019 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9020 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9021 g_signal_connect(G_OBJECT(window), "delete_event",
9022 G_CALLBACK(attach_property_delete_event),
9023 cancelled);
9024 g_signal_connect(G_OBJECT(window), "key_press_event",
9025 G_CALLBACK(attach_property_key_pressed),
9026 cancelled);
9028 vbox = gtk_vbox_new(FALSE, 8);
9029 gtk_container_add(GTK_CONTAINER(window), vbox);
9031 table = gtk_table_new(4, 2, FALSE);
9032 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9033 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9034 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9036 label = gtk_label_new(_("MIME type"));
9037 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9038 GTK_FILL, 0, 0, 0);
9039 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9040 #if !GTK_CHECK_VERSION(2, 24, 0)
9041 mimetype_entry = gtk_combo_box_entry_new_text();
9042 #else
9043 mimetype_entry = gtk_combo_box_text_new_with_entry();
9044 #endif
9045 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9046 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9048 /* stuff with list */
9049 mime_type_list = procmime_get_mime_type_list();
9050 strlist = NULL;
9051 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9052 MimeType *type = (MimeType *) mime_type_list->data;
9053 gchar *tmp;
9055 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9057 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9058 g_free(tmp);
9059 else
9060 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9061 (GCompareFunc)strcmp2);
9064 for (mime_type_list = strlist; mime_type_list != NULL;
9065 mime_type_list = mime_type_list->next) {
9066 #if !GTK_CHECK_VERSION(2, 24, 0)
9067 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9068 #else
9069 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9070 #endif
9071 g_free(mime_type_list->data);
9073 g_list_free(strlist);
9074 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9075 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9077 label = gtk_label_new(_("Encoding"));
9078 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9079 GTK_FILL, 0, 0, 0);
9080 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9082 hbox = gtk_hbox_new(FALSE, 0);
9083 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9084 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9086 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9087 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9089 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9090 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9091 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9092 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9093 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9095 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9097 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9098 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9100 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9101 &ok_btn, GTK_STOCK_OK,
9102 NULL, NULL);
9103 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9104 gtk_widget_grab_default(ok_btn);
9106 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9107 G_CALLBACK(attach_property_ok),
9108 cancelled);
9109 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9110 G_CALLBACK(attach_property_cancel),
9111 cancelled);
9113 gtk_widget_show_all(vbox);
9115 attach_prop.window = window;
9116 attach_prop.mimetype_entry = mimetype_entry;
9117 attach_prop.encoding_optmenu = optmenu;
9118 attach_prop.path_entry = path_entry;
9119 attach_prop.filename_entry = filename_entry;
9120 attach_prop.ok_btn = ok_btn;
9121 attach_prop.cancel_btn = cancel_btn;
9124 #undef SET_LABEL_AND_ENTRY
9126 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9128 *cancelled = FALSE;
9129 gtk_main_quit();
9132 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9134 *cancelled = TRUE;
9135 gtk_main_quit();
9138 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9139 gboolean *cancelled)
9141 *cancelled = TRUE;
9142 gtk_main_quit();
9144 return TRUE;
9147 static gboolean attach_property_key_pressed(GtkWidget *widget,
9148 GdkEventKey *event,
9149 gboolean *cancelled)
9151 if (event && event->keyval == GDK_KEY_Escape) {
9152 *cancelled = TRUE;
9153 gtk_main_quit();
9155 if (event && event->keyval == GDK_KEY_Return) {
9156 *cancelled = FALSE;
9157 gtk_main_quit();
9158 return TRUE;
9160 return FALSE;
9163 static void compose_exec_ext_editor(Compose *compose)
9165 #ifdef G_OS_UNIX
9166 gchar *tmp;
9167 pid_t pid;
9168 gint pipe_fds[2];
9170 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9171 G_DIR_SEPARATOR, compose);
9173 if (pipe(pipe_fds) < 0) {
9174 perror("pipe");
9175 g_free(tmp);
9176 return;
9179 if ((pid = fork()) < 0) {
9180 perror("fork");
9181 g_free(tmp);
9182 return;
9185 if (pid != 0) {
9186 /* close the write side of the pipe */
9187 close(pipe_fds[1]);
9189 compose->exteditor_file = g_strdup(tmp);
9190 compose->exteditor_pid = pid;
9192 compose_set_ext_editor_sensitive(compose, FALSE);
9194 #ifndef G_OS_WIN32
9195 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9196 #else
9197 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9198 #endif
9199 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9200 G_IO_IN,
9201 compose_input_cb,
9202 compose);
9203 } else { /* process-monitoring process */
9204 pid_t pid_ed;
9206 if (setpgid(0, 0))
9207 perror("setpgid");
9209 /* close the read side of the pipe */
9210 close(pipe_fds[0]);
9212 if (compose_write_body_to_file(compose, tmp) < 0) {
9213 fd_write_all(pipe_fds[1], "2\n", 2);
9214 _exit(1);
9217 pid_ed = compose_exec_ext_editor_real(tmp);
9218 if (pid_ed < 0) {
9219 fd_write_all(pipe_fds[1], "1\n", 2);
9220 _exit(1);
9223 /* wait until editor is terminated */
9224 waitpid(pid_ed, NULL, 0);
9226 fd_write_all(pipe_fds[1], "0\n", 2);
9228 close(pipe_fds[1]);
9229 _exit(0);
9232 g_free(tmp);
9233 #endif /* G_OS_UNIX */
9236 #ifdef G_OS_UNIX
9237 static gint compose_exec_ext_editor_real(const gchar *file)
9239 gchar buf[1024];
9240 gchar *p;
9241 gchar **cmdline;
9242 pid_t pid;
9244 cm_return_val_if_fail(file != NULL, -1);
9246 if ((pid = fork()) < 0) {
9247 perror("fork");
9248 return -1;
9251 if (pid != 0) return pid;
9253 /* grandchild process */
9255 if (setpgid(0, getppid()))
9256 perror("setpgid");
9258 if (prefs_common_get_ext_editor_cmd() &&
9259 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9260 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9261 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9262 } else {
9263 if (prefs_common_get_ext_editor_cmd())
9264 g_warning("External editor command-line is invalid: '%s'\n",
9265 prefs_common_get_ext_editor_cmd());
9266 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9269 cmdline = strsplit_with_quote(buf, " ", 1024);
9270 execvp(cmdline[0], cmdline);
9272 perror("execvp");
9273 g_strfreev(cmdline);
9275 _exit(1);
9278 static gboolean compose_ext_editor_kill(Compose *compose)
9280 pid_t pgid = compose->exteditor_pid * -1;
9281 gint ret;
9283 ret = kill(pgid, 0);
9285 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9286 AlertValue val;
9287 gchar *msg;
9289 msg = g_strdup_printf
9290 (_("The external editor is still working.\n"
9291 "Force terminating the process?\n"
9292 "process group id: %d"), -pgid);
9293 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9294 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9296 g_free(msg);
9298 if (val == G_ALERTALTERNATE) {
9299 g_source_remove(compose->exteditor_tag);
9300 g_io_channel_shutdown(compose->exteditor_ch,
9301 FALSE, NULL);
9302 g_io_channel_unref(compose->exteditor_ch);
9304 if (kill(pgid, SIGTERM) < 0) perror("kill");
9305 waitpid(compose->exteditor_pid, NULL, 0);
9307 g_warning("Terminated process group id: %d", -pgid);
9308 g_warning("Temporary file: %s",
9309 compose->exteditor_file);
9311 compose_set_ext_editor_sensitive(compose, TRUE);
9313 g_free(compose->exteditor_file);
9314 compose->exteditor_file = NULL;
9315 compose->exteditor_pid = -1;
9316 compose->exteditor_ch = NULL;
9317 compose->exteditor_tag = -1;
9318 } else
9319 return FALSE;
9322 return TRUE;
9325 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9326 gpointer data)
9328 gchar buf[3] = "3";
9329 Compose *compose = (Compose *)data;
9330 gsize bytes_read;
9332 debug_print("Compose: input from monitoring process\n");
9334 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9336 g_io_channel_shutdown(source, FALSE, NULL);
9337 g_io_channel_unref(source);
9339 waitpid(compose->exteditor_pid, NULL, 0);
9341 if (buf[0] == '0') { /* success */
9342 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9343 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9345 gtk_text_buffer_set_text(buffer, "", -1);
9346 compose_insert_file(compose, compose->exteditor_file);
9347 compose_changed_cb(NULL, compose);
9348 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9350 if (claws_unlink(compose->exteditor_file) < 0)
9351 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9352 } else if (buf[0] == '1') { /* failed */
9353 g_warning("Couldn't exec external editor\n");
9354 if (claws_unlink(compose->exteditor_file) < 0)
9355 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9356 } else if (buf[0] == '2') {
9357 g_warning("Couldn't write to file\n");
9358 } else if (buf[0] == '3') {
9359 g_warning("Pipe read failed\n");
9362 compose_set_ext_editor_sensitive(compose, TRUE);
9364 g_free(compose->exteditor_file);
9365 compose->exteditor_file = NULL;
9366 compose->exteditor_pid = -1;
9367 compose->exteditor_ch = NULL;
9368 compose->exteditor_tag = -1;
9370 return FALSE;
9373 static void compose_set_ext_editor_sensitive(Compose *compose,
9374 gboolean sensitive)
9376 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9377 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9378 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9379 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9380 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", sensitive);
9381 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9382 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9383 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9385 gtk_widget_set_sensitive(compose->text, sensitive);
9386 if (compose->toolbar->send_btn)
9387 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9388 if (compose->toolbar->sendl_btn)
9389 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9390 if (compose->toolbar->draft_btn)
9391 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9392 if (compose->toolbar->insert_btn)
9393 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9394 if (compose->toolbar->sig_btn)
9395 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9396 if (compose->toolbar->exteditor_btn)
9397 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9398 if (compose->toolbar->linewrap_current_btn)
9399 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9400 if (compose->toolbar->linewrap_all_btn)
9401 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9403 #endif /* G_OS_UNIX */
9406 * compose_undo_state_changed:
9408 * Change the sensivity of the menuentries undo and redo
9410 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9411 gint redo_state, gpointer data)
9413 Compose *compose = (Compose *)data;
9415 switch (undo_state) {
9416 case UNDO_STATE_TRUE:
9417 if (!undostruct->undo_state) {
9418 undostruct->undo_state = TRUE;
9419 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9421 break;
9422 case UNDO_STATE_FALSE:
9423 if (undostruct->undo_state) {
9424 undostruct->undo_state = FALSE;
9425 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9427 break;
9428 case UNDO_STATE_UNCHANGED:
9429 break;
9430 case UNDO_STATE_REFRESH:
9431 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9432 break;
9433 default:
9434 g_warning("Undo state not recognized");
9435 break;
9438 switch (redo_state) {
9439 case UNDO_STATE_TRUE:
9440 if (!undostruct->redo_state) {
9441 undostruct->redo_state = TRUE;
9442 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9444 break;
9445 case UNDO_STATE_FALSE:
9446 if (undostruct->redo_state) {
9447 undostruct->redo_state = FALSE;
9448 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9450 break;
9451 case UNDO_STATE_UNCHANGED:
9452 break;
9453 case UNDO_STATE_REFRESH:
9454 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9455 break;
9456 default:
9457 g_warning("Redo state not recognized");
9458 break;
9462 /* callback functions */
9464 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9465 GtkAllocation *allocation,
9466 GtkPaned *paned)
9468 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9471 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9472 * includes "non-client" (windows-izm) in calculation, so this calculation
9473 * may not be accurate.
9475 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9476 GtkAllocation *allocation,
9477 GtkSHRuler *shruler)
9479 if (prefs_common.show_ruler) {
9480 gint char_width = 0, char_height = 0;
9481 gint line_width_in_chars;
9483 gtkut_get_font_size(GTK_WIDGET(widget),
9484 &char_width, &char_height);
9485 line_width_in_chars =
9486 (allocation->width - allocation->x) / char_width;
9488 /* got the maximum */
9489 gtk_shruler_set_range(GTK_SHRULER(shruler),
9490 0.0, line_width_in_chars, 0);
9493 return TRUE;
9496 typedef struct {
9497 gchar *header;
9498 gchar *entry;
9499 ComposePrefType type;
9500 gboolean entry_marked;
9501 } HeaderEntryState;
9503 static void account_activated(GtkComboBox *optmenu, gpointer data)
9505 Compose *compose = (Compose *)data;
9507 PrefsAccount *ac;
9508 gchar *folderidentifier;
9509 gint account_id = 0;
9510 GtkTreeModel *menu;
9511 GtkTreeIter iter;
9512 GSList *list, *saved_list = NULL;
9513 HeaderEntryState *state;
9514 GtkRcStyle *style = NULL;
9515 #if !GTK_CHECK_VERSION(3, 0, 0)
9516 static GdkColor yellow;
9517 static gboolean color_set = FALSE;
9518 #else
9519 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9520 #endif
9522 /* Get ID of active account in the combo box */
9523 menu = gtk_combo_box_get_model(optmenu);
9524 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9525 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9527 ac = account_find_from_id(account_id);
9528 cm_return_if_fail(ac != NULL);
9530 if (ac != compose->account) {
9531 compose_select_account(compose, ac, FALSE);
9533 for (list = compose->header_list; list; list = list->next) {
9534 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9536 if (hentry->type == PREF_ACCOUNT || !list->next) {
9537 compose_destroy_headerentry(compose, hentry);
9538 continue;
9541 state = g_malloc0(sizeof(HeaderEntryState));
9542 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9543 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9544 state->entry = gtk_editable_get_chars(
9545 GTK_EDITABLE(hentry->entry), 0, -1);
9546 state->type = hentry->type;
9548 #if !GTK_CHECK_VERSION(3, 0, 0)
9549 if (!color_set) {
9550 gdk_color_parse("#f5f6be", &yellow);
9551 color_set = gdk_colormap_alloc_color(
9552 gdk_colormap_get_system(),
9553 &yellow, FALSE, TRUE);
9555 #endif
9557 style = gtk_widget_get_modifier_style(hentry->entry);
9558 state->entry_marked = gdk_color_equal(&yellow,
9559 &style->base[GTK_STATE_NORMAL]);
9561 saved_list = g_slist_append(saved_list, state);
9562 compose_destroy_headerentry(compose, hentry);
9565 compose->header_last = NULL;
9566 g_slist_free(compose->header_list);
9567 compose->header_list = NULL;
9568 compose->header_nextrow = 1;
9569 compose_create_header_entry(compose);
9571 if (ac->set_autocc && ac->auto_cc)
9572 compose_entry_append(compose, ac->auto_cc,
9573 COMPOSE_CC, PREF_ACCOUNT);
9575 if (ac->set_autobcc && ac->auto_bcc)
9576 compose_entry_append(compose, ac->auto_bcc,
9577 COMPOSE_BCC, PREF_ACCOUNT);
9579 if (ac->set_autoreplyto && ac->auto_replyto)
9580 compose_entry_append(compose, ac->auto_replyto,
9581 COMPOSE_REPLYTO, PREF_ACCOUNT);
9583 for (list = saved_list; list; list = list->next) {
9584 state = (HeaderEntryState *) list->data;
9586 compose_add_header_entry(compose, state->header,
9587 state->entry, state->type);
9588 if (state->entry_marked)
9589 compose_entry_mark_default_to(compose, state->entry);
9591 g_free(state->header);
9592 g_free(state->entry);
9593 g_free(state);
9595 g_slist_free(saved_list);
9597 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9598 (ac->protocol == A_NNTP) ?
9599 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9602 /* Set message save folder */
9603 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9604 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9606 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9607 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9609 compose_set_save_to(compose, NULL);
9610 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9611 folderidentifier = folder_item_get_identifier(account_get_special_folder
9612 (compose->account, F_OUTBOX));
9613 compose_set_save_to(compose, folderidentifier);
9614 g_free(folderidentifier);
9618 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9619 GtkTreeViewColumn *column, Compose *compose)
9621 compose_attach_property(NULL, compose);
9624 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9625 gpointer data)
9627 Compose *compose = (Compose *)data;
9628 GtkTreeSelection *attach_selection;
9629 gint attach_nr_selected;
9631 if (!event) return FALSE;
9633 if (event->button == 3) {
9634 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9635 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9637 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9638 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9640 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9641 NULL, NULL, event->button, event->time);
9642 return TRUE;
9645 return FALSE;
9648 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9649 gpointer data)
9651 Compose *compose = (Compose *)data;
9653 if (!event) return FALSE;
9655 switch (event->keyval) {
9656 case GDK_KEY_Delete:
9657 compose_attach_remove_selected(NULL, compose);
9658 break;
9660 return FALSE;
9663 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9665 toolbar_comp_set_sensitive(compose, allow);
9666 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9667 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9668 #if USE_ENCHANT
9669 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9670 #endif
9671 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9672 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9673 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9675 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9679 static void compose_send_cb(GtkAction *action, gpointer data)
9681 Compose *compose = (Compose *)data;
9683 if (prefs_common.work_offline &&
9684 !inc_offline_should_override(TRUE,
9685 _("Claws Mail needs network access in order "
9686 "to send this email.")))
9687 return;
9689 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9690 g_source_remove(compose->draft_timeout_tag);
9691 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9694 compose_send(compose);
9697 static void compose_send_later_cb(GtkAction *action, gpointer data)
9699 Compose *compose = (Compose *)data;
9700 gint val;
9702 inc_lock();
9703 compose_allow_user_actions(compose, FALSE);
9704 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9705 compose_allow_user_actions(compose, TRUE);
9706 inc_unlock();
9708 if (!val) {
9709 compose_close(compose);
9710 } else if (val == -1) {
9711 alertpanel_error(_("Could not queue message."));
9712 } else if (val == -2) {
9713 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9714 } else if (val == -3) {
9715 if (privacy_peek_error())
9716 alertpanel_error(_("Could not queue message for sending:\n\n"
9717 "Signature failed: %s"), privacy_get_error());
9718 } else if (val == -4) {
9719 alertpanel_error(_("Could not queue message for sending:\n\n"
9720 "Charset conversion failed."));
9721 } else if (val == -5) {
9722 alertpanel_error(_("Could not queue message for sending:\n\n"
9723 "Couldn't get recipient encryption key."));
9724 } else if (val == -6) {
9725 /* silent error */
9727 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9730 #define DRAFTED_AT_EXIT "drafted_at_exit"
9731 static void compose_register_draft(MsgInfo *info)
9733 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9734 DRAFTED_AT_EXIT, NULL);
9735 FILE *fp = g_fopen(filepath, "ab");
9737 if (fp) {
9738 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9739 info->msgnum);
9740 fclose(fp);
9743 g_free(filepath);
9746 gboolean compose_draft (gpointer data, guint action)
9748 Compose *compose = (Compose *)data;
9749 FolderItem *draft;
9750 gchar *tmp;
9751 gchar *sheaders;
9752 gint msgnum;
9753 MsgFlags flag = {0, 0};
9754 static gboolean lock = FALSE;
9755 MsgInfo *newmsginfo;
9756 FILE *fp;
9757 gboolean target_locked = FALSE;
9758 gboolean err = FALSE;
9760 if (lock) return FALSE;
9762 if (compose->sending)
9763 return TRUE;
9765 draft = account_get_special_folder(compose->account, F_DRAFT);
9766 cm_return_val_if_fail(draft != NULL, FALSE);
9768 if (!g_mutex_trylock(compose->mutex)) {
9769 /* we don't want to lock the mutex once it's available,
9770 * because as the only other part of compose.c locking
9771 * it is compose_close - which means once unlocked,
9772 * the compose struct will be freed */
9773 debug_print("couldn't lock mutex, probably sending\n");
9774 return FALSE;
9777 lock = TRUE;
9779 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9780 G_DIR_SEPARATOR, compose);
9781 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9782 FILE_OP_ERROR(tmp, "fopen");
9783 goto warn_err;
9786 /* chmod for security */
9787 if (change_file_mode_rw(fp, tmp) < 0) {
9788 FILE_OP_ERROR(tmp, "chmod");
9789 g_warning("can't change file mode\n");
9792 /* Save draft infos */
9793 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9794 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9796 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9797 gchar *savefolderid;
9799 savefolderid = compose_get_save_to(compose);
9800 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9801 g_free(savefolderid);
9803 if (compose->return_receipt) {
9804 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9806 if (compose->privacy_system) {
9807 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9808 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9809 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9812 /* Message-ID of message replying to */
9813 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9814 gchar *folderid = NULL;
9816 if (compose->replyinfo->folder)
9817 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9818 if (folderid == NULL)
9819 folderid = g_strdup("NULL");
9821 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9822 g_free(folderid);
9824 /* Message-ID of message forwarding to */
9825 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9826 gchar *folderid = NULL;
9828 if (compose->fwdinfo->folder)
9829 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9830 if (folderid == NULL)
9831 folderid = g_strdup("NULL");
9833 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9834 g_free(folderid);
9837 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9838 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9840 sheaders = compose_get_manual_headers_info(compose);
9841 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9842 g_free(sheaders);
9844 /* end of headers */
9845 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9847 if (err) {
9848 fclose(fp);
9849 goto warn_err;
9852 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9853 fclose(fp);
9854 goto warn_err;
9856 if (fclose(fp) == EOF) {
9857 goto warn_err;
9860 flag.perm_flags = MSG_NEW|MSG_UNREAD;
9861 if (compose->targetinfo) {
9862 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9863 if (target_locked)
9864 flag.perm_flags |= MSG_LOCKED;
9866 flag.tmp_flags = MSG_DRAFT;
9868 folder_item_scan(draft);
9869 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9870 MsgInfo *tmpinfo = NULL;
9871 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9872 if (compose->msgid) {
9873 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9875 if (tmpinfo) {
9876 msgnum = tmpinfo->msgnum;
9877 procmsg_msginfo_free(tmpinfo);
9878 debug_print("got draft msgnum %d from scanning\n", msgnum);
9879 } else {
9880 debug_print("didn't get draft msgnum after scanning\n");
9882 } else {
9883 debug_print("got draft msgnum %d from adding\n", msgnum);
9885 if (msgnum < 0) {
9886 warn_err:
9887 claws_unlink(tmp);
9888 g_free(tmp);
9889 if (action != COMPOSE_AUTO_SAVE) {
9890 if (action != COMPOSE_DRAFT_FOR_EXIT)
9891 alertpanel_error(_("Could not save draft."));
9892 else {
9893 AlertValue val;
9894 gtkut_window_popup(compose->window);
9895 val = alertpanel_full(_("Could not save draft"),
9896 _("Could not save draft.\n"
9897 "Do you want to cancel exit or discard this email?"),
9898 _("_Cancel exit"), _("_Discard email"), NULL,
9899 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9900 if (val == G_ALERTALTERNATE) {
9901 lock = FALSE;
9902 g_mutex_unlock(compose->mutex); /* must be done before closing */
9903 compose_close(compose);
9904 return TRUE;
9905 } else {
9906 lock = FALSE;
9907 g_mutex_unlock(compose->mutex); /* must be done before closing */
9908 return FALSE;
9912 goto unlock;
9914 g_free(tmp);
9916 if (compose->mode == COMPOSE_REEDIT) {
9917 compose_remove_reedit_target(compose, TRUE);
9920 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9922 if (newmsginfo) {
9923 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9924 if (target_locked)
9925 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
9926 else
9927 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
9928 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9929 procmsg_msginfo_set_flags(newmsginfo, 0,
9930 MSG_HAS_ATTACHMENT);
9932 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9933 compose_register_draft(newmsginfo);
9935 procmsg_msginfo_free(newmsginfo);
9938 folder_item_scan(draft);
9940 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9941 lock = FALSE;
9942 g_mutex_unlock(compose->mutex); /* must be done before closing */
9943 compose_close(compose);
9944 return TRUE;
9945 } else {
9946 struct stat s;
9947 gchar *path;
9949 path = folder_item_fetch_msg(draft, msgnum);
9950 if (path == NULL) {
9951 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9952 goto unlock;
9954 if (g_stat(path, &s) < 0) {
9955 FILE_OP_ERROR(path, "stat");
9956 g_free(path);
9957 goto unlock;
9959 g_free(path);
9961 procmsg_msginfo_free(compose->targetinfo);
9962 compose->targetinfo = procmsg_msginfo_new();
9963 compose->targetinfo->msgnum = msgnum;
9964 compose->targetinfo->size = (goffset)s.st_size;
9965 compose->targetinfo->mtime = s.st_mtime;
9966 compose->targetinfo->folder = draft;
9967 if (target_locked)
9968 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9969 compose->mode = COMPOSE_REEDIT;
9971 if (action == COMPOSE_AUTO_SAVE) {
9972 compose->autosaved_draft = compose->targetinfo;
9974 compose->modified = FALSE;
9975 compose_set_title(compose);
9977 unlock:
9978 lock = FALSE;
9979 g_mutex_unlock(compose->mutex);
9980 return TRUE;
9983 void compose_clear_exit_drafts(void)
9985 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9986 DRAFTED_AT_EXIT, NULL);
9987 if (is_file_exist(filepath))
9988 claws_unlink(filepath);
9990 g_free(filepath);
9993 void compose_reopen_exit_drafts(void)
9995 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9996 DRAFTED_AT_EXIT, NULL);
9997 FILE *fp = g_fopen(filepath, "rb");
9998 gchar buf[1024];
10000 if (fp) {
10001 while (fgets(buf, sizeof(buf), fp)) {
10002 gchar **parts = g_strsplit(buf, "\t", 2);
10003 const gchar *folder = parts[0];
10004 int msgnum = parts[1] ? atoi(parts[1]):-1;
10006 if (folder && *folder && msgnum > -1) {
10007 FolderItem *item = folder_find_item_from_identifier(folder);
10008 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10009 if (info)
10010 compose_reedit(info, FALSE);
10012 g_strfreev(parts);
10014 fclose(fp);
10016 g_free(filepath);
10017 compose_clear_exit_drafts();
10020 static void compose_save_cb(GtkAction *action, gpointer data)
10022 Compose *compose = (Compose *)data;
10023 compose_draft(compose, COMPOSE_KEEP_EDITING);
10024 compose->rmode = COMPOSE_REEDIT;
10027 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10029 if (compose && file_list) {
10030 GList *tmp;
10032 for ( tmp = file_list; tmp; tmp = tmp->next) {
10033 gchar *file = (gchar *) tmp->data;
10034 gchar *utf8_filename = conv_filename_to_utf8(file);
10035 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10036 compose_changed_cb(NULL, compose);
10037 if (free_data) {
10038 g_free(file);
10039 tmp->data = NULL;
10041 g_free(utf8_filename);
10046 static void compose_attach_cb(GtkAction *action, gpointer data)
10048 Compose *compose = (Compose *)data;
10049 GList *file_list;
10051 if (compose->redirect_filename != NULL)
10052 return;
10054 /* Set focus_window properly, in case we were called via popup menu,
10055 * which unsets it (via focus_out_event callback on compose window). */
10056 manage_window_focus_in(compose->window, NULL, NULL);
10058 file_list = filesel_select_multiple_files_open(_("Select file"));
10060 if (file_list) {
10061 compose_attach_from_list(compose, file_list, TRUE);
10062 g_list_free(file_list);
10066 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10068 Compose *compose = (Compose *)data;
10069 GList *file_list;
10070 gint files_inserted = 0;
10072 file_list = filesel_select_multiple_files_open(_("Select file"));
10074 if (file_list) {
10075 GList *tmp;
10077 for ( tmp = file_list; tmp; tmp = tmp->next) {
10078 gchar *file = (gchar *) tmp->data;
10079 gchar *filedup = g_strdup(file);
10080 gchar *shortfile = g_path_get_basename(filedup);
10081 ComposeInsertResult res;
10082 /* insert the file if the file is short or if the user confirmed that
10083 he/she wants to insert the large file */
10084 res = compose_insert_file(compose, file);
10085 if (res == COMPOSE_INSERT_READ_ERROR) {
10086 alertpanel_error(_("File '%s' could not be read."), shortfile);
10087 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10088 alertpanel_error(_("File '%s' contained invalid characters\n"
10089 "for the current encoding, insertion may be incorrect."),
10090 shortfile);
10091 } else if (res == COMPOSE_INSERT_SUCCESS)
10092 files_inserted++;
10094 g_free(shortfile);
10095 g_free(filedup);
10096 g_free(file);
10098 g_list_free(file_list);
10101 #ifdef USE_ENCHANT
10102 if (files_inserted > 0 && compose->gtkaspell &&
10103 compose->gtkaspell->check_while_typing)
10104 gtkaspell_highlight_all(compose->gtkaspell);
10105 #endif
10108 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10110 Compose *compose = (Compose *)data;
10112 compose_insert_sig(compose, FALSE);
10115 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10117 Compose *compose = (Compose *)data;
10119 compose_insert_sig(compose, TRUE);
10122 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10123 gpointer data)
10125 gint x, y;
10126 Compose *compose = (Compose *)data;
10128 gtkut_widget_get_uposition(widget, &x, &y);
10129 if (!compose->batch) {
10130 prefs_common.compose_x = x;
10131 prefs_common.compose_y = y;
10133 if (compose->sending || compose->updating)
10134 return TRUE;
10135 compose_close_cb(NULL, compose);
10136 return TRUE;
10139 void compose_close_toolbar(Compose *compose)
10141 compose_close_cb(NULL, compose);
10144 static gboolean compose_can_autosave(Compose *compose)
10146 if (compose->privacy_system && compose->use_encryption)
10147 return prefs_common.autosave && prefs_common.autosave_encrypted;
10148 else
10149 return prefs_common.autosave;
10152 static void compose_close_cb(GtkAction *action, gpointer data)
10154 Compose *compose = (Compose *)data;
10155 AlertValue val;
10157 #ifdef G_OS_UNIX
10158 if (compose->exteditor_tag != -1) {
10159 if (!compose_ext_editor_kill(compose))
10160 return;
10162 #endif
10164 if (compose->modified) {
10165 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10166 if (!g_mutex_trylock(compose->mutex)) {
10167 /* we don't want to lock the mutex once it's available,
10168 * because as the only other part of compose.c locking
10169 * it is compose_close - which means once unlocked,
10170 * the compose struct will be freed */
10171 debug_print("couldn't lock mutex, probably sending\n");
10172 return;
10174 if (!reedit) {
10175 val = alertpanel(_("Discard message"),
10176 _("This message has been modified. Discard it?"),
10177 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10178 } else {
10179 val = alertpanel(_("Save changes"),
10180 _("This message has been modified. Save the latest changes?"),
10181 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10183 g_mutex_unlock(compose->mutex);
10184 switch (val) {
10185 case G_ALERTDEFAULT:
10186 if (compose_can_autosave(compose) && !reedit)
10187 compose_remove_draft(compose);
10188 break;
10189 case G_ALERTALTERNATE:
10190 compose_draft(data, COMPOSE_QUIT_EDITING);
10191 return;
10192 default:
10193 return;
10197 compose_close(compose);
10200 static void compose_print_cb(GtkAction *action, gpointer data)
10202 Compose *compose = (Compose *) data;
10204 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10205 if (compose->targetinfo)
10206 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10209 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10211 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10212 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10213 Compose *compose = (Compose *) data;
10215 if (active)
10216 compose->out_encoding = (CharSet)value;
10219 static void compose_address_cb(GtkAction *action, gpointer data)
10221 Compose *compose = (Compose *)data;
10223 #ifndef USE_NEW_ADDRBOOK
10224 addressbook_open(compose);
10225 #else
10226 GError* error = NULL;
10227 addressbook_connect_signals(compose);
10228 addressbook_dbus_open(TRUE, &error);
10229 if (error) {
10230 g_warning("%s", error->message);
10231 g_error_free(error);
10233 #endif
10236 static void about_show_cb(GtkAction *action, gpointer data)
10238 about_show();
10241 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10243 Compose *compose = (Compose *)data;
10244 Template *tmpl;
10245 gchar *msg;
10246 AlertValue val;
10248 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10249 cm_return_if_fail(tmpl != NULL);
10251 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10252 tmpl->name);
10253 val = alertpanel(_("Apply template"), msg,
10254 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10255 g_free(msg);
10257 if (val == G_ALERTDEFAULT)
10258 compose_template_apply(compose, tmpl, TRUE);
10259 else if (val == G_ALERTALTERNATE)
10260 compose_template_apply(compose, tmpl, FALSE);
10263 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10265 Compose *compose = (Compose *)data;
10267 compose_exec_ext_editor(compose);
10270 static void compose_undo_cb(GtkAction *action, gpointer data)
10272 Compose *compose = (Compose *)data;
10273 gboolean prev_autowrap = compose->autowrap;
10275 compose->autowrap = FALSE;
10276 undo_undo(compose->undostruct);
10277 compose->autowrap = prev_autowrap;
10280 static void compose_redo_cb(GtkAction *action, gpointer data)
10282 Compose *compose = (Compose *)data;
10283 gboolean prev_autowrap = compose->autowrap;
10285 compose->autowrap = FALSE;
10286 undo_redo(compose->undostruct);
10287 compose->autowrap = prev_autowrap;
10290 static void entry_cut_clipboard(GtkWidget *entry)
10292 if (GTK_IS_EDITABLE(entry))
10293 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10294 else if (GTK_IS_TEXT_VIEW(entry))
10295 gtk_text_buffer_cut_clipboard(
10296 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10297 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10298 TRUE);
10301 static void entry_copy_clipboard(GtkWidget *entry)
10303 if (GTK_IS_EDITABLE(entry))
10304 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10305 else if (GTK_IS_TEXT_VIEW(entry))
10306 gtk_text_buffer_copy_clipboard(
10307 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10308 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10311 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10312 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10314 if (GTK_IS_TEXT_VIEW(entry)) {
10315 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10316 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10317 GtkTextIter start_iter, end_iter;
10318 gint start, end;
10319 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10321 if (contents == NULL)
10322 return;
10324 /* we shouldn't delete the selection when middle-click-pasting, or we
10325 * can't mid-click-paste our own selection */
10326 if (clip != GDK_SELECTION_PRIMARY) {
10327 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10328 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10331 if (insert_place == NULL) {
10332 /* if insert_place isn't specified, insert at the cursor.
10333 * used for Ctrl-V pasting */
10334 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10335 start = gtk_text_iter_get_offset(&start_iter);
10336 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10337 } else {
10338 /* if insert_place is specified, paste here.
10339 * used for mid-click-pasting */
10340 start = gtk_text_iter_get_offset(insert_place);
10341 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10342 if (prefs_common.primary_paste_unselects)
10343 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10346 if (!wrap) {
10347 /* paste unwrapped: mark the paste so it's not wrapped later */
10348 end = start + strlen(contents);
10349 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10350 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10351 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10352 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10353 /* rewrap paragraph now (after a mid-click-paste) */
10354 mark_start = gtk_text_buffer_get_insert(buffer);
10355 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10356 gtk_text_iter_backward_char(&start_iter);
10357 compose_beautify_paragraph(compose, &start_iter, TRUE);
10359 } else if (GTK_IS_EDITABLE(entry))
10360 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10362 compose->modified = TRUE;
10365 static void entry_allsel(GtkWidget *entry)
10367 if (GTK_IS_EDITABLE(entry))
10368 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10369 else if (GTK_IS_TEXT_VIEW(entry)) {
10370 GtkTextIter startiter, enditer;
10371 GtkTextBuffer *textbuf;
10373 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10374 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10375 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10377 gtk_text_buffer_move_mark_by_name(textbuf,
10378 "selection_bound", &startiter);
10379 gtk_text_buffer_move_mark_by_name(textbuf,
10380 "insert", &enditer);
10384 static void compose_cut_cb(GtkAction *action, gpointer data)
10386 Compose *compose = (Compose *)data;
10387 if (compose->focused_editable
10388 #ifndef GENERIC_UMPC
10389 && gtk_widget_has_focus(compose->focused_editable)
10390 #endif
10392 entry_cut_clipboard(compose->focused_editable);
10395 static void compose_copy_cb(GtkAction *action, gpointer data)
10397 Compose *compose = (Compose *)data;
10398 if (compose->focused_editable
10399 #ifndef GENERIC_UMPC
10400 && gtk_widget_has_focus(compose->focused_editable)
10401 #endif
10403 entry_copy_clipboard(compose->focused_editable);
10406 static void compose_paste_cb(GtkAction *action, gpointer data)
10408 Compose *compose = (Compose *)data;
10409 gint prev_autowrap;
10410 GtkTextBuffer *buffer;
10411 BLOCK_WRAP();
10412 if (compose->focused_editable &&
10413 #ifndef GENERIC_UMPC
10414 gtk_widget_has_focus(compose->focused_editable)
10415 #endif
10417 entry_paste_clipboard(compose, compose->focused_editable,
10418 prefs_common.linewrap_pastes,
10419 GDK_SELECTION_CLIPBOARD, NULL);
10420 UNBLOCK_WRAP();
10422 #ifdef USE_ENCHANT
10423 if (
10424 #ifndef GENERIC_UMPC
10425 gtk_widget_has_focus(compose->text) &&
10426 #endif
10427 compose->gtkaspell &&
10428 compose->gtkaspell->check_while_typing)
10429 gtkaspell_highlight_all(compose->gtkaspell);
10430 #endif
10433 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10435 Compose *compose = (Compose *)data;
10436 gint wrap_quote = prefs_common.linewrap_quote;
10437 if (compose->focused_editable
10438 #ifndef GENERIC_UMPC
10439 && gtk_widget_has_focus(compose->focused_editable)
10440 #endif
10442 /* let text_insert() (called directly or at a later time
10443 * after the gtk_editable_paste_clipboard) know that
10444 * text is to be inserted as a quotation. implemented
10445 * by using a simple refcount... */
10446 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10447 G_OBJECT(compose->focused_editable),
10448 "paste_as_quotation"));
10449 g_object_set_data(G_OBJECT(compose->focused_editable),
10450 "paste_as_quotation",
10451 GINT_TO_POINTER(paste_as_quotation + 1));
10452 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10453 entry_paste_clipboard(compose, compose->focused_editable,
10454 prefs_common.linewrap_pastes,
10455 GDK_SELECTION_CLIPBOARD, NULL);
10456 prefs_common.linewrap_quote = wrap_quote;
10460 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10462 Compose *compose = (Compose *)data;
10463 gint prev_autowrap;
10464 GtkTextBuffer *buffer;
10465 BLOCK_WRAP();
10466 if (compose->focused_editable
10467 #ifndef GENERIC_UMPC
10468 && gtk_widget_has_focus(compose->focused_editable)
10469 #endif
10471 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10472 GDK_SELECTION_CLIPBOARD, NULL);
10473 UNBLOCK_WRAP();
10475 #ifdef USE_ENCHANT
10476 if (
10477 #ifndef GENERIC_UMPC
10478 gtk_widget_has_focus(compose->text) &&
10479 #endif
10480 compose->gtkaspell &&
10481 compose->gtkaspell->check_while_typing)
10482 gtkaspell_highlight_all(compose->gtkaspell);
10483 #endif
10486 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10488 Compose *compose = (Compose *)data;
10489 gint prev_autowrap;
10490 GtkTextBuffer *buffer;
10491 BLOCK_WRAP();
10492 if (compose->focused_editable
10493 #ifndef GENERIC_UMPC
10494 && gtk_widget_has_focus(compose->focused_editable)
10495 #endif
10497 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10498 GDK_SELECTION_CLIPBOARD, NULL);
10499 UNBLOCK_WRAP();
10501 #ifdef USE_ENCHANT
10502 if (
10503 #ifndef GENERIC_UMPC
10504 gtk_widget_has_focus(compose->text) &&
10505 #endif
10506 compose->gtkaspell &&
10507 compose->gtkaspell->check_while_typing)
10508 gtkaspell_highlight_all(compose->gtkaspell);
10509 #endif
10512 static void compose_allsel_cb(GtkAction *action, gpointer data)
10514 Compose *compose = (Compose *)data;
10515 if (compose->focused_editable
10516 #ifndef GENERIC_UMPC
10517 && gtk_widget_has_focus(compose->focused_editable)
10518 #endif
10520 entry_allsel(compose->focused_editable);
10523 static void textview_move_beginning_of_line (GtkTextView *text)
10525 GtkTextBuffer *buffer;
10526 GtkTextMark *mark;
10527 GtkTextIter ins;
10529 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10531 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10532 mark = gtk_text_buffer_get_insert(buffer);
10533 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10534 gtk_text_iter_set_line_offset(&ins, 0);
10535 gtk_text_buffer_place_cursor(buffer, &ins);
10538 static void textview_move_forward_character (GtkTextView *text)
10540 GtkTextBuffer *buffer;
10541 GtkTextMark *mark;
10542 GtkTextIter ins;
10544 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10546 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10547 mark = gtk_text_buffer_get_insert(buffer);
10548 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10549 if (gtk_text_iter_forward_cursor_position(&ins))
10550 gtk_text_buffer_place_cursor(buffer, &ins);
10553 static void textview_move_backward_character (GtkTextView *text)
10555 GtkTextBuffer *buffer;
10556 GtkTextMark *mark;
10557 GtkTextIter ins;
10559 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10561 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10562 mark = gtk_text_buffer_get_insert(buffer);
10563 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10564 if (gtk_text_iter_backward_cursor_position(&ins))
10565 gtk_text_buffer_place_cursor(buffer, &ins);
10568 static void textview_move_forward_word (GtkTextView *text)
10570 GtkTextBuffer *buffer;
10571 GtkTextMark *mark;
10572 GtkTextIter ins;
10573 gint count;
10575 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10577 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10578 mark = gtk_text_buffer_get_insert(buffer);
10579 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10580 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10581 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10582 gtk_text_iter_backward_word_start(&ins);
10583 gtk_text_buffer_place_cursor(buffer, &ins);
10587 static void textview_move_backward_word (GtkTextView *text)
10589 GtkTextBuffer *buffer;
10590 GtkTextMark *mark;
10591 GtkTextIter ins;
10593 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10595 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10596 mark = gtk_text_buffer_get_insert(buffer);
10597 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10598 if (gtk_text_iter_backward_word_starts(&ins, 1))
10599 gtk_text_buffer_place_cursor(buffer, &ins);
10602 static void textview_move_end_of_line (GtkTextView *text)
10604 GtkTextBuffer *buffer;
10605 GtkTextMark *mark;
10606 GtkTextIter ins;
10608 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10610 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10611 mark = gtk_text_buffer_get_insert(buffer);
10612 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10613 if (gtk_text_iter_forward_to_line_end(&ins))
10614 gtk_text_buffer_place_cursor(buffer, &ins);
10617 static void textview_move_next_line (GtkTextView *text)
10619 GtkTextBuffer *buffer;
10620 GtkTextMark *mark;
10621 GtkTextIter ins;
10622 gint offset;
10624 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10626 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10627 mark = gtk_text_buffer_get_insert(buffer);
10628 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10629 offset = gtk_text_iter_get_line_offset(&ins);
10630 if (gtk_text_iter_forward_line(&ins)) {
10631 gtk_text_iter_set_line_offset(&ins, offset);
10632 gtk_text_buffer_place_cursor(buffer, &ins);
10636 static void textview_move_previous_line (GtkTextView *text)
10638 GtkTextBuffer *buffer;
10639 GtkTextMark *mark;
10640 GtkTextIter ins;
10641 gint offset;
10643 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10645 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10646 mark = gtk_text_buffer_get_insert(buffer);
10647 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10648 offset = gtk_text_iter_get_line_offset(&ins);
10649 if (gtk_text_iter_backward_line(&ins)) {
10650 gtk_text_iter_set_line_offset(&ins, offset);
10651 gtk_text_buffer_place_cursor(buffer, &ins);
10655 static void textview_delete_forward_character (GtkTextView *text)
10657 GtkTextBuffer *buffer;
10658 GtkTextMark *mark;
10659 GtkTextIter ins, end_iter;
10661 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10663 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10664 mark = gtk_text_buffer_get_insert(buffer);
10665 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10666 end_iter = ins;
10667 if (gtk_text_iter_forward_char(&end_iter)) {
10668 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10672 static void textview_delete_backward_character (GtkTextView *text)
10674 GtkTextBuffer *buffer;
10675 GtkTextMark *mark;
10676 GtkTextIter ins, end_iter;
10678 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10680 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10681 mark = gtk_text_buffer_get_insert(buffer);
10682 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10683 end_iter = ins;
10684 if (gtk_text_iter_backward_char(&end_iter)) {
10685 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10689 static void textview_delete_forward_word (GtkTextView *text)
10691 GtkTextBuffer *buffer;
10692 GtkTextMark *mark;
10693 GtkTextIter ins, end_iter;
10695 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10697 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10698 mark = gtk_text_buffer_get_insert(buffer);
10699 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10700 end_iter = ins;
10701 if (gtk_text_iter_forward_word_end(&end_iter)) {
10702 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10706 static void textview_delete_backward_word (GtkTextView *text)
10708 GtkTextBuffer *buffer;
10709 GtkTextMark *mark;
10710 GtkTextIter ins, end_iter;
10712 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10714 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10715 mark = gtk_text_buffer_get_insert(buffer);
10716 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10717 end_iter = ins;
10718 if (gtk_text_iter_backward_word_start(&end_iter)) {
10719 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10723 static void textview_delete_line (GtkTextView *text)
10725 GtkTextBuffer *buffer;
10726 GtkTextMark *mark;
10727 GtkTextIter ins, start_iter, end_iter;
10729 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10731 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10732 mark = gtk_text_buffer_get_insert(buffer);
10733 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10735 start_iter = ins;
10736 gtk_text_iter_set_line_offset(&start_iter, 0);
10738 end_iter = ins;
10739 if (gtk_text_iter_ends_line(&end_iter)){
10740 if (!gtk_text_iter_forward_char(&end_iter))
10741 gtk_text_iter_backward_char(&start_iter);
10743 else
10744 gtk_text_iter_forward_to_line_end(&end_iter);
10745 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10748 static void textview_delete_to_line_end (GtkTextView *text)
10750 GtkTextBuffer *buffer;
10751 GtkTextMark *mark;
10752 GtkTextIter ins, end_iter;
10754 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10756 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10757 mark = gtk_text_buffer_get_insert(buffer);
10758 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10759 end_iter = ins;
10760 if (gtk_text_iter_ends_line(&end_iter))
10761 gtk_text_iter_forward_char(&end_iter);
10762 else
10763 gtk_text_iter_forward_to_line_end(&end_iter);
10764 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10767 #define DO_ACTION(name, act) { \
10768 if(!strcmp(name, a_name)) { \
10769 return act; \
10772 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10774 const gchar *a_name = gtk_action_get_name(action);
10775 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10776 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10777 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10778 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10779 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10780 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10781 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10782 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10783 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10784 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10785 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10786 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10787 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10788 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10789 return -1;
10792 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10794 Compose *compose = (Compose *)data;
10795 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10796 ComposeCallAdvancedAction action = -1;
10798 action = compose_call_advanced_action_from_path(gaction);
10800 static struct {
10801 void (*do_action) (GtkTextView *text);
10802 } action_table[] = {
10803 {textview_move_beginning_of_line},
10804 {textview_move_forward_character},
10805 {textview_move_backward_character},
10806 {textview_move_forward_word},
10807 {textview_move_backward_word},
10808 {textview_move_end_of_line},
10809 {textview_move_next_line},
10810 {textview_move_previous_line},
10811 {textview_delete_forward_character},
10812 {textview_delete_backward_character},
10813 {textview_delete_forward_word},
10814 {textview_delete_backward_word},
10815 {textview_delete_line},
10816 {textview_delete_to_line_end}
10819 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10821 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10822 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10823 if (action_table[action].do_action)
10824 action_table[action].do_action(text);
10825 else
10826 g_warning("Not implemented yet.");
10830 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10832 GtkAllocation allocation;
10833 GtkWidget *parent;
10834 gchar *str = NULL;
10836 if (GTK_IS_EDITABLE(widget)) {
10837 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10838 gtk_editable_set_position(GTK_EDITABLE(widget),
10839 strlen(str));
10840 g_free(str);
10841 if ((parent = gtk_widget_get_parent(widget))
10842 && (parent = gtk_widget_get_parent(parent))
10843 && (parent = gtk_widget_get_parent(parent))) {
10844 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10845 gtk_widget_get_allocation(widget, &allocation);
10846 gint y = allocation.y;
10847 gint height = allocation.height;
10848 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10849 (GTK_SCROLLED_WINDOW(parent));
10851 gfloat value = gtk_adjustment_get_value(shown);
10852 gfloat upper = gtk_adjustment_get_upper(shown);
10853 gfloat page_size = gtk_adjustment_get_page_size(shown);
10854 if (y < (int)value) {
10855 gtk_adjustment_set_value(shown, y - 1);
10857 if ((y + height) > ((int)value + (int)page_size)) {
10858 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10859 gtk_adjustment_set_value(shown,
10860 y + height - (int)page_size - 1);
10861 } else {
10862 gtk_adjustment_set_value(shown,
10863 (int)upper - (int)page_size - 1);
10870 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10871 compose->focused_editable = widget;
10873 #ifdef GENERIC_UMPC
10874 if (GTK_IS_TEXT_VIEW(widget)
10875 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10876 g_object_ref(compose->notebook);
10877 g_object_ref(compose->edit_vbox);
10878 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10879 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10880 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10881 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10882 g_object_unref(compose->notebook);
10883 g_object_unref(compose->edit_vbox);
10884 g_signal_handlers_block_by_func(G_OBJECT(widget),
10885 G_CALLBACK(compose_grab_focus_cb),
10886 compose);
10887 gtk_widget_grab_focus(widget);
10888 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10889 G_CALLBACK(compose_grab_focus_cb),
10890 compose);
10891 } else if (!GTK_IS_TEXT_VIEW(widget)
10892 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10893 g_object_ref(compose->notebook);
10894 g_object_ref(compose->edit_vbox);
10895 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10896 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10897 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10898 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10899 g_object_unref(compose->notebook);
10900 g_object_unref(compose->edit_vbox);
10901 g_signal_handlers_block_by_func(G_OBJECT(widget),
10902 G_CALLBACK(compose_grab_focus_cb),
10903 compose);
10904 gtk_widget_grab_focus(widget);
10905 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10906 G_CALLBACK(compose_grab_focus_cb),
10907 compose);
10909 #endif
10912 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10914 compose->modified = TRUE;
10915 // compose_beautify_paragraph(compose, NULL, TRUE);
10916 #ifndef GENERIC_UMPC
10917 compose_set_title(compose);
10918 #endif
10921 static void compose_wrap_cb(GtkAction *action, gpointer data)
10923 Compose *compose = (Compose *)data;
10924 compose_beautify_paragraph(compose, NULL, TRUE);
10927 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10929 Compose *compose = (Compose *)data;
10930 compose_wrap_all_full(compose, TRUE);
10933 static void compose_find_cb(GtkAction *action, gpointer data)
10935 Compose *compose = (Compose *)data;
10937 message_search_compose(compose);
10940 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10941 gpointer data)
10943 Compose *compose = (Compose *)data;
10944 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10945 if (compose->autowrap)
10946 compose_wrap_all_full(compose, TRUE);
10947 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10950 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10951 gpointer data)
10953 Compose *compose = (Compose *)data;
10954 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10957 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10959 Compose *compose = (Compose *)data;
10961 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10964 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10966 Compose *compose = (Compose *)data;
10968 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10971 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10973 g_free(compose->privacy_system);
10975 compose->privacy_system = g_strdup(account->default_privacy_system);
10976 compose_update_privacy_system_menu_item(compose, warn);
10979 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10981 Compose *compose = (Compose *)data;
10983 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10984 gtk_widget_show(compose->ruler_hbox);
10985 prefs_common.show_ruler = TRUE;
10986 } else {
10987 gtk_widget_hide(compose->ruler_hbox);
10988 gtk_widget_queue_resize(compose->edit_vbox);
10989 prefs_common.show_ruler = FALSE;
10993 static void compose_attach_drag_received_cb (GtkWidget *widget,
10994 GdkDragContext *context,
10995 gint x,
10996 gint y,
10997 GtkSelectionData *data,
10998 guint info,
10999 guint time,
11000 gpointer user_data)
11002 Compose *compose = (Compose *)user_data;
11003 GList *list, *tmp;
11004 GdkAtom type;
11006 type = gtk_selection_data_get_data_type(data);
11007 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11008 #ifdef G_OS_WIN32
11009 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
11010 #endif
11011 ) && gtk_drag_get_source_widget(context) !=
11012 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11013 list = uri_list_extract_filenames(
11014 (const gchar *)gtk_selection_data_get_data(data));
11015 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11016 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11017 compose_attach_append
11018 (compose, (const gchar *)tmp->data,
11019 utf8_filename, NULL, NULL);
11020 g_free(utf8_filename);
11022 if (list) compose_changed_cb(NULL, compose);
11023 list_free_strings(list);
11024 g_list_free(list);
11025 } else if (gtk_drag_get_source_widget(context)
11026 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11027 /* comes from our summaryview */
11028 SummaryView * summaryview = NULL;
11029 GSList * list = NULL, *cur = NULL;
11031 if (mainwindow_get_mainwindow())
11032 summaryview = mainwindow_get_mainwindow()->summaryview;
11034 if (summaryview)
11035 list = summary_get_selected_msg_list(summaryview);
11037 for (cur = list; cur; cur = cur->next) {
11038 MsgInfo *msginfo = (MsgInfo *)cur->data;
11039 gchar *file = NULL;
11040 if (msginfo)
11041 file = procmsg_get_message_file_full(msginfo,
11042 TRUE, TRUE);
11043 if (file) {
11044 compose_attach_append(compose, (const gchar *)file,
11045 (const gchar *)file, "message/rfc822", NULL);
11046 g_free(file);
11049 g_slist_free(list);
11053 static gboolean compose_drag_drop(GtkWidget *widget,
11054 GdkDragContext *drag_context,
11055 gint x, gint y,
11056 guint time, gpointer user_data)
11058 /* not handling this signal makes compose_insert_drag_received_cb
11059 * called twice */
11060 return TRUE;
11063 static gboolean completion_set_focus_to_subject
11064 (GtkWidget *widget,
11065 GdkEventKey *event,
11066 Compose *compose)
11068 GtkTextBuffer *buffer;
11069 GtkTextMark *mark;
11070 GtkTextIter iter;
11072 cm_return_val_if_fail(compose != NULL, FALSE);
11074 /* make backtab move to subject field */
11075 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11076 gtk_widget_grab_focus(compose->subject_entry);
11077 return TRUE;
11080 // Up key should also move the focus to subject field, if the cursor
11081 // is on the first line.
11082 if ((event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up)
11083 && (event->state & (GDK_SHIFT_MASK|GDK_CONTROL_MASK)) == 0) {
11084 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
11085 g_return_val_if_fail(buffer != NULL, FALSE);
11087 mark = gtk_text_buffer_get_mark(buffer, "insert");
11088 g_return_val_if_fail(mark != NULL, FALSE);
11090 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
11092 if (gtk_text_iter_get_line(&iter) == 0) {
11093 gtk_widget_grab_focus(compose->subject_entry);
11094 return TRUE;
11098 return FALSE;
11101 static void compose_insert_drag_received_cb (GtkWidget *widget,
11102 GdkDragContext *drag_context,
11103 gint x,
11104 gint y,
11105 GtkSelectionData *data,
11106 guint info,
11107 guint time,
11108 gpointer user_data)
11110 Compose *compose = (Compose *)user_data;
11111 GList *list, *tmp;
11112 GdkAtom type;
11114 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11115 * does not work */
11116 type = gtk_selection_data_get_data_type(data);
11117 #ifndef G_OS_WIN32
11118 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11119 #else
11120 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
11121 #endif
11122 AlertValue val = G_ALERTDEFAULT;
11123 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11125 list = uri_list_extract_filenames(ddata);
11126 if (list == NULL && strstr(ddata, "://")) {
11127 /* Assume a list of no files, and data has ://, is a remote link */
11128 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11129 gchar *tmpfile = get_tmp_file();
11130 str_write_to_file(tmpdata, tmpfile);
11131 g_free(tmpdata);
11132 compose_insert_file(compose, tmpfile);
11133 claws_unlink(tmpfile);
11134 g_free(tmpfile);
11135 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11136 compose_beautify_paragraph(compose, NULL, TRUE);
11137 return;
11139 switch (prefs_common.compose_dnd_mode) {
11140 case COMPOSE_DND_ASK:
11141 val = alertpanel_full(_("Insert or attach?"),
11142 _("Do you want to insert the contents of the file(s) "
11143 "into the message body, or attach it to the email?"),
11144 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
11145 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11146 break;
11147 case COMPOSE_DND_INSERT:
11148 val = G_ALERTALTERNATE;
11149 break;
11150 case COMPOSE_DND_ATTACH:
11151 val = G_ALERTOTHER;
11152 break;
11153 default:
11154 /* unexpected case */
11155 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11158 if (val & G_ALERTDISABLE) {
11159 val &= ~G_ALERTDISABLE;
11160 /* remember what action to perform by default, only if we don't click Cancel */
11161 if (val == G_ALERTALTERNATE)
11162 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11163 else if (val == G_ALERTOTHER)
11164 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11167 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11168 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11169 list_free_strings(list);
11170 g_list_free(list);
11171 return;
11172 } else if (val == G_ALERTOTHER) {
11173 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11174 list_free_strings(list);
11175 g_list_free(list);
11176 return;
11179 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11180 compose_insert_file(compose, (const gchar *)tmp->data);
11182 list_free_strings(list);
11183 g_list_free(list);
11184 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11185 return;
11189 static void compose_header_drag_received_cb (GtkWidget *widget,
11190 GdkDragContext *drag_context,
11191 gint x,
11192 gint y,
11193 GtkSelectionData *data,
11194 guint info,
11195 guint time,
11196 gpointer user_data)
11198 GtkEditable *entry = (GtkEditable *)user_data;
11199 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11201 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11202 * does not work */
11204 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11205 gchar *decoded=g_new(gchar, strlen(email));
11206 int start = 0;
11208 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11209 gtk_editable_delete_text(entry, 0, -1);
11210 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11211 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11212 g_free(decoded);
11213 return;
11215 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11218 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11220 Compose *compose = (Compose *)data;
11222 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11223 compose->return_receipt = TRUE;
11224 else
11225 compose->return_receipt = FALSE;
11228 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11230 Compose *compose = (Compose *)data;
11232 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11233 compose->remove_references = TRUE;
11234 else
11235 compose->remove_references = FALSE;
11238 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11239 ComposeHeaderEntry *headerentry)
11241 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11242 return FALSE;
11245 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11246 GdkEventKey *event,
11247 ComposeHeaderEntry *headerentry)
11249 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11250 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11251 !(event->state & GDK_MODIFIER_MASK) &&
11252 (event->keyval == GDK_KEY_BackSpace) &&
11253 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11254 gtk_container_remove
11255 (GTK_CONTAINER(headerentry->compose->header_table),
11256 headerentry->combo);
11257 gtk_container_remove
11258 (GTK_CONTAINER(headerentry->compose->header_table),
11259 headerentry->entry);
11260 headerentry->compose->header_list =
11261 g_slist_remove(headerentry->compose->header_list,
11262 headerentry);
11263 g_free(headerentry);
11264 } else if (event->keyval == GDK_KEY_Tab) {
11265 if (headerentry->compose->header_last == headerentry) {
11266 /* Override default next focus, and give it to subject_entry
11267 * instead of notebook tabs
11269 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11270 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11271 return TRUE;
11274 return FALSE;
11277 static gboolean scroll_postpone(gpointer data)
11279 Compose *compose = (Compose *)data;
11281 if (compose->batch)
11282 return FALSE;
11284 GTK_EVENTS_FLUSH();
11285 compose_show_first_last_header(compose, FALSE);
11286 return FALSE;
11289 static void compose_headerentry_changed_cb(GtkWidget *entry,
11290 ComposeHeaderEntry *headerentry)
11292 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11293 compose_create_header_entry(headerentry->compose);
11294 g_signal_handlers_disconnect_matched
11295 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11296 0, 0, NULL, NULL, headerentry);
11298 if (!headerentry->compose->batch)
11299 g_timeout_add(0, scroll_postpone, headerentry->compose);
11303 static gboolean compose_defer_auto_save_draft(Compose *compose)
11305 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11306 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11307 return FALSE;
11310 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11312 GtkAdjustment *vadj;
11314 cm_return_if_fail(compose);
11316 if(compose->batch)
11317 return;
11319 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11320 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11321 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11322 gtk_widget_get_parent(compose->header_table)));
11323 gtk_adjustment_set_value(vadj, (show_first ?
11324 gtk_adjustment_get_lower(vadj) :
11325 (gtk_adjustment_get_upper(vadj) -
11326 gtk_adjustment_get_page_size(vadj))));
11327 gtk_adjustment_changed(vadj);
11330 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11331 const gchar *text, gint len, Compose *compose)
11333 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11334 (G_OBJECT(compose->text), "paste_as_quotation"));
11335 GtkTextMark *mark;
11337 cm_return_if_fail(text != NULL);
11339 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11340 G_CALLBACK(text_inserted),
11341 compose);
11342 if (paste_as_quotation) {
11343 gchar *new_text;
11344 const gchar *qmark;
11345 guint pos = 0;
11346 GtkTextIter start_iter;
11348 if (len < 0)
11349 len = strlen(text);
11351 new_text = g_strndup(text, len);
11353 qmark = compose_quote_char_from_context(compose);
11355 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11356 gtk_text_buffer_place_cursor(buffer, iter);
11358 pos = gtk_text_iter_get_offset(iter);
11360 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11361 _("Quote format error at line %d."));
11362 quote_fmt_reset_vartable();
11363 g_free(new_text);
11364 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11365 GINT_TO_POINTER(paste_as_quotation - 1));
11367 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11368 gtk_text_buffer_place_cursor(buffer, iter);
11369 gtk_text_buffer_delete_mark(buffer, mark);
11371 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11372 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11373 compose_beautify_paragraph(compose, &start_iter, FALSE);
11374 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11375 gtk_text_buffer_delete_mark(buffer, mark);
11376 } else {
11377 if (strcmp(text, "\n") || compose->automatic_break
11378 || gtk_text_iter_starts_line(iter)) {
11379 GtkTextIter before_ins;
11380 gtk_text_buffer_insert(buffer, iter, text, len);
11381 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11382 before_ins = *iter;
11383 gtk_text_iter_backward_chars(&before_ins, len);
11384 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11386 } else {
11387 /* check if the preceding is just whitespace or quote */
11388 GtkTextIter start_line;
11389 gchar *tmp = NULL, *quote = NULL;
11390 gint quote_len = 0, is_normal = 0;
11391 start_line = *iter;
11392 gtk_text_iter_set_line_offset(&start_line, 0);
11393 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11394 g_strstrip(tmp);
11396 if (*tmp == '\0') {
11397 is_normal = 1;
11398 } else {
11399 quote = compose_get_quote_str(buffer, &start_line, &quote_len);
11400 if (quote)
11401 is_normal = 1;
11402 g_free(quote);
11404 g_free(tmp);
11406 if (is_normal) {
11407 gtk_text_buffer_insert(buffer, iter, text, len);
11408 } else {
11409 gtk_text_buffer_insert_with_tags_by_name(buffer,
11410 iter, text, len, "no_join", NULL);
11415 if (!paste_as_quotation) {
11416 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11417 compose_beautify_paragraph(compose, iter, FALSE);
11418 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11419 gtk_text_buffer_delete_mark(buffer, mark);
11422 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11423 G_CALLBACK(text_inserted),
11424 compose);
11425 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11427 if (compose_can_autosave(compose) &&
11428 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11429 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11430 compose->draft_timeout_tag = g_timeout_add
11431 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11434 #if USE_ENCHANT
11435 static void compose_check_all(GtkAction *action, gpointer data)
11437 Compose *compose = (Compose *)data;
11438 if (!compose->gtkaspell)
11439 return;
11441 if (gtk_widget_has_focus(compose->subject_entry))
11442 claws_spell_entry_check_all(
11443 CLAWS_SPELL_ENTRY(compose->subject_entry));
11444 else
11445 gtkaspell_check_all(compose->gtkaspell);
11448 static void compose_highlight_all(GtkAction *action, gpointer data)
11450 Compose *compose = (Compose *)data;
11451 if (compose->gtkaspell) {
11452 claws_spell_entry_recheck_all(
11453 CLAWS_SPELL_ENTRY(compose->subject_entry));
11454 gtkaspell_highlight_all(compose->gtkaspell);
11458 static void compose_check_backwards(GtkAction *action, gpointer data)
11460 Compose *compose = (Compose *)data;
11461 if (!compose->gtkaspell) {
11462 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11463 return;
11466 if (gtk_widget_has_focus(compose->subject_entry))
11467 claws_spell_entry_check_backwards(
11468 CLAWS_SPELL_ENTRY(compose->subject_entry));
11469 else
11470 gtkaspell_check_backwards(compose->gtkaspell);
11473 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11475 Compose *compose = (Compose *)data;
11476 if (!compose->gtkaspell) {
11477 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11478 return;
11481 if (gtk_widget_has_focus(compose->subject_entry))
11482 claws_spell_entry_check_forwards_go(
11483 CLAWS_SPELL_ENTRY(compose->subject_entry));
11484 else
11485 gtkaspell_check_forwards_go(compose->gtkaspell);
11487 #endif
11490 *\brief Guess originating forward account from MsgInfo and several
11491 * "common preference" settings. Return NULL if no guess.
11493 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11495 PrefsAccount *account = NULL;
11497 cm_return_val_if_fail(msginfo, NULL);
11498 cm_return_val_if_fail(msginfo->folder, NULL);
11499 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11501 if (msginfo->folder->prefs->enable_default_account)
11502 account = account_find_from_id(msginfo->folder->prefs->default_account);
11504 if (!account)
11505 account = msginfo->folder->folder->account;
11507 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11508 gchar *to;
11509 Xstrdup_a(to, msginfo->to, return NULL);
11510 extract_address(to);
11511 account = account_find_from_address(to, FALSE);
11514 if (!account && prefs_common.forward_account_autosel) {
11515 gchar cc[BUFFSIZE];
11516 if (!procheader_get_header_from_msginfo
11517 (msginfo, cc,sizeof cc , "Cc:")) {
11518 gchar *buf = cc + strlen("Cc:");
11519 extract_address(buf);
11520 account = account_find_from_address(buf, FALSE);
11524 if (!account && prefs_common.forward_account_autosel) {
11525 gchar deliveredto[BUFFSIZE];
11526 if (!procheader_get_header_from_msginfo
11527 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11528 gchar *buf = deliveredto + strlen("Delivered-To:");
11529 extract_address(buf);
11530 account = account_find_from_address(buf, FALSE);
11534 return account;
11537 gboolean compose_close(Compose *compose)
11539 gint x, y;
11541 cm_return_val_if_fail(compose, FALSE);
11543 if (!g_mutex_trylock(compose->mutex)) {
11544 /* we have to wait for the (possibly deferred by auto-save)
11545 * drafting to be done, before destroying the compose under
11546 * it. */
11547 debug_print("waiting for drafting to finish...\n");
11548 compose_allow_user_actions(compose, FALSE);
11549 if (compose->close_timeout_tag == 0) {
11550 compose->close_timeout_tag =
11551 g_timeout_add (500, (GSourceFunc) compose_close,
11552 compose);
11554 return TRUE;
11557 if (compose->draft_timeout_tag >= 0) {
11558 g_source_remove(compose->draft_timeout_tag);
11559 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11562 gtkut_widget_get_uposition(compose->window, &x, &y);
11563 if (!compose->batch) {
11564 prefs_common.compose_x = x;
11565 prefs_common.compose_y = y;
11567 g_mutex_unlock(compose->mutex);
11568 compose_destroy(compose);
11569 return FALSE;
11573 * Add entry field for each address in list.
11574 * \param compose E-Mail composition object.
11575 * \param listAddress List of (formatted) E-Mail addresses.
11577 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11578 GList *node;
11579 gchar *addr;
11580 node = listAddress;
11581 while( node ) {
11582 addr = ( gchar * ) node->data;
11583 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11584 node = g_list_next( node );
11588 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11589 guint action, gboolean opening_multiple)
11591 gchar *body = NULL;
11592 GSList *new_msglist = NULL;
11593 MsgInfo *tmp_msginfo = NULL;
11594 gboolean originally_enc = FALSE;
11595 gboolean originally_sig = FALSE;
11596 Compose *compose = NULL;
11597 gchar *s_system = NULL;
11599 cm_return_if_fail(msgview != NULL);
11601 cm_return_if_fail(msginfo_list != NULL);
11603 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11604 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11605 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11607 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11608 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11609 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11610 orig_msginfo, mimeinfo);
11611 if (tmp_msginfo != NULL) {
11612 new_msglist = g_slist_append(NULL, tmp_msginfo);
11614 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11615 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11616 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11618 tmp_msginfo->folder = orig_msginfo->folder;
11619 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11620 if (orig_msginfo->tags) {
11621 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11622 tmp_msginfo->folder->tags_dirty = TRUE;
11628 if (!opening_multiple)
11629 body = messageview_get_selection(msgview);
11631 if (new_msglist) {
11632 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11633 procmsg_msginfo_free(tmp_msginfo);
11634 g_slist_free(new_msglist);
11635 } else
11636 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11638 if (compose && originally_enc) {
11639 compose_force_encryption(compose, compose->account, FALSE, s_system);
11642 if (compose && originally_sig && compose->account->default_sign_reply) {
11643 compose_force_signing(compose, compose->account, s_system);
11645 g_free(s_system);
11646 g_free(body);
11647 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11650 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11651 guint action)
11653 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11654 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11655 GSList *cur = msginfo_list;
11656 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11657 "messages. Opening the windows "
11658 "could take some time. Do you "
11659 "want to continue?"),
11660 g_slist_length(msginfo_list));
11661 if (g_slist_length(msginfo_list) > 9
11662 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11663 != G_ALERTALTERNATE) {
11664 g_free(msg);
11665 return;
11667 g_free(msg);
11668 /* We'll open multiple compose windows */
11669 /* let the WM place the next windows */
11670 compose_force_window_origin = FALSE;
11671 for (; cur; cur = cur->next) {
11672 GSList tmplist;
11673 tmplist.data = cur->data;
11674 tmplist.next = NULL;
11675 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11677 compose_force_window_origin = TRUE;
11678 } else {
11679 /* forwarding multiple mails as attachments is done via a
11680 * single compose window */
11681 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11685 void compose_check_for_email_account(Compose *compose)
11687 PrefsAccount *ac = NULL, *curr = NULL;
11688 GList *list;
11690 if (!compose)
11691 return;
11693 if (compose->account && compose->account->protocol == A_NNTP) {
11694 ac = account_get_cur_account();
11695 if (ac->protocol == A_NNTP) {
11696 list = account_get_list();
11698 for( ; list != NULL ; list = g_list_next(list)) {
11699 curr = (PrefsAccount *) list->data;
11700 if (curr->protocol != A_NNTP) {
11701 ac = curr;
11702 break;
11706 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11707 ac->account_id);
11711 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11712 const gchar *address)
11714 GSList *msginfo_list = NULL;
11715 gchar *body = messageview_get_selection(msgview);
11716 Compose *compose;
11718 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11720 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11721 compose_check_for_email_account(compose);
11722 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11723 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11724 compose_reply_set_subject(compose, msginfo);
11726 g_free(body);
11727 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11730 void compose_set_position(Compose *compose, gint pos)
11732 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11734 gtkut_text_view_set_position(text, pos);
11737 gboolean compose_search_string(Compose *compose,
11738 const gchar *str, gboolean case_sens)
11740 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11742 return gtkut_text_view_search_string(text, str, case_sens);
11745 gboolean compose_search_string_backward(Compose *compose,
11746 const gchar *str, gboolean case_sens)
11748 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11750 return gtkut_text_view_search_string_backward(text, str, case_sens);
11753 /* allocate a msginfo structure and populate its data from a compose data structure */
11754 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11756 MsgInfo *newmsginfo;
11757 GSList *list;
11758 gchar buf[BUFFSIZE];
11760 cm_return_val_if_fail( compose != NULL, NULL );
11762 newmsginfo = procmsg_msginfo_new();
11764 /* date is now */
11765 get_rfc822_date(buf, sizeof(buf));
11766 newmsginfo->date = g_strdup(buf);
11768 /* from */
11769 if (compose->from_name) {
11770 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11771 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11774 /* subject */
11775 if (compose->subject_entry)
11776 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11778 /* to, cc, reply-to, newsgroups */
11779 for (list = compose->header_list; list; list = list->next) {
11780 gchar *header = gtk_editable_get_chars(
11781 GTK_EDITABLE(
11782 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11783 gchar *entry = gtk_editable_get_chars(
11784 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11786 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11787 if ( newmsginfo->to == NULL ) {
11788 newmsginfo->to = g_strdup(entry);
11789 } else if (entry && *entry) {
11790 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11791 g_free(newmsginfo->to);
11792 newmsginfo->to = tmp;
11794 } else
11795 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11796 if ( newmsginfo->cc == NULL ) {
11797 newmsginfo->cc = g_strdup(entry);
11798 } else if (entry && *entry) {
11799 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11800 g_free(newmsginfo->cc);
11801 newmsginfo->cc = tmp;
11803 } else
11804 if ( strcasecmp(header,
11805 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11806 if ( newmsginfo->newsgroups == NULL ) {
11807 newmsginfo->newsgroups = g_strdup(entry);
11808 } else if (entry && *entry) {
11809 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11810 g_free(newmsginfo->newsgroups);
11811 newmsginfo->newsgroups = tmp;
11815 g_free(header);
11816 g_free(entry);
11819 /* other data is unset */
11821 return newmsginfo;
11824 #ifdef USE_ENCHANT
11825 /* update compose's dictionaries from folder dict settings */
11826 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11827 FolderItem *folder_item)
11829 cm_return_if_fail(compose != NULL);
11831 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11832 FolderItemPrefs *prefs = folder_item->prefs;
11834 if (prefs->enable_default_dictionary)
11835 gtkaspell_change_dict(compose->gtkaspell,
11836 prefs->default_dictionary, FALSE);
11837 if (folder_item->prefs->enable_default_alt_dictionary)
11838 gtkaspell_change_alt_dict(compose->gtkaspell,
11839 prefs->default_alt_dictionary);
11840 if (prefs->enable_default_dictionary
11841 || prefs->enable_default_alt_dictionary)
11842 compose_spell_menu_changed(compose);
11845 #endif
11847 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11849 Compose *compose = (Compose *)data;
11851 cm_return_if_fail(compose != NULL);
11853 gtk_widget_grab_focus(compose->text);
11857 * End of Source.