A bit more room for alertpanel messages
[claws.git] / src / compose.c
blob2a2a438ec037a9adeee374dcec5e7d7c0fe4a02a
1 /*
2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2016 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/>.
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #include "claws-features.h"
22 #endif
24 #include "defs.h"
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
28 #endif
30 #include <glib.h>
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtk.h>
35 #include <pango/pango-break.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <ctype.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <unistd.h>
43 #include <time.h>
44 #include <stdlib.h>
45 #if HAVE_SYS_WAIT_H
46 # include <sys/wait.h>
47 #endif
48 #include <signal.h>
49 #include <errno.h>
50 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
51 #include <libgen.h>
52 #endif
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
55 # include <wchar.h>
56 # include <wctype.h>
57 #endif
59 #include "claws.h"
60 #include "main.h"
61 #include "mainwindow.h"
62 #include "compose.h"
63 #ifndef USE_ALT_ADDRBOOK
64 #include "addressbook.h"
65 #else
66 #include "addressbook-dbus.h"
67 #include "addressadd.h"
68 #endif
69 #include "folderview.h"
70 #include "procmsg.h"
71 #include "menu.h"
72 #include "stock_pixmap.h"
73 #include "send_message.h"
74 #include "imap.h"
75 #include "news.h"
76 #include "customheader.h"
77 #include "prefs_common.h"
78 #include "prefs_account.h"
79 #include "action.h"
80 #include "account.h"
81 #include "filesel.h"
82 #include "procheader.h"
83 #include "procmime.h"
84 #include "statusbar.h"
85 #include "about.h"
86 #include "quoted-printable.h"
87 #include "codeconv.h"
88 #include "utils.h"
89 #include "gtkutils.h"
90 #include "gtkshruler.h"
91 #include "socket.h"
92 #include "alertpanel.h"
93 #include "manage_window.h"
94 #include "folder.h"
95 #include "folder_item_prefs.h"
96 #include "addr_compl.h"
97 #include "quote_fmt.h"
98 #include "undo.h"
99 #include "foldersel.h"
100 #include "toolbar.h"
101 #include "inc.h"
102 #include "message_search.h"
103 #include "combobox.h"
104 #include "hooks.h"
105 #include "privacy.h"
106 #include "timing.h"
107 #include "autofaces.h"
108 #include "spell_entry.h"
109 #include "headers.h"
111 enum
113 COL_MIMETYPE = 0,
114 COL_SIZE = 1,
115 COL_NAME = 2,
116 COL_CHARSET = 3,
117 COL_DATA = 4,
118 COL_AUTODATA = 5,
119 N_COL_COLUMNS
122 #define N_ATTACH_COLS (N_COL_COLUMNS)
124 typedef enum
126 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
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 GdkColor default_header_bgcolor = {
189 (gulong)0,
190 (gushort)0,
191 (gushort)0,
192 (gushort)0
195 static GdkColor default_header_color = {
196 (gulong)0,
197 (gushort)0,
198 (gushort)0,
199 (gushort)0
202 static GList *compose_list = NULL;
203 static GSList *extra_headers = NULL;
205 static Compose *compose_generic_new (PrefsAccount *account,
206 const gchar *to,
207 FolderItem *item,
208 GList *attach_files,
209 GList *listAddress );
211 static Compose *compose_create (PrefsAccount *account,
212 FolderItem *item,
213 ComposeMode mode,
214 gboolean batch);
216 static void compose_entry_indicate (Compose *compose,
217 const gchar *address);
218 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
219 ComposeQuoteMode quote_mode,
220 gboolean to_all,
221 gboolean to_sender,
222 const gchar *body);
223 static Compose *compose_forward_multiple (PrefsAccount *account,
224 GSList *msginfo_list);
225 static Compose *compose_reply (MsgInfo *msginfo,
226 ComposeQuoteMode quote_mode,
227 gboolean to_all,
228 gboolean to_ml,
229 gboolean to_sender,
230 const gchar *body);
231 static Compose *compose_reply_mode (ComposeMode mode,
232 GSList *msginfo_list,
233 gchar *body);
234 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
235 static void compose_update_privacy_systems_menu(Compose *compose);
237 static GtkWidget *compose_account_option_menu_create
238 (Compose *compose);
239 static void compose_set_out_encoding (Compose *compose);
240 static void compose_set_template_menu (Compose *compose);
241 static void compose_destroy (Compose *compose);
243 static MailField compose_entries_set (Compose *compose,
244 const gchar *mailto,
245 ComposeEntryType to_type);
246 static gint compose_parse_header (Compose *compose,
247 MsgInfo *msginfo);
248 static gint compose_parse_manual_headers (Compose *compose,
249 MsgInfo *msginfo,
250 HeaderEntry *entries);
251 static gchar *compose_parse_references (const gchar *ref,
252 const gchar *msgid);
254 static gchar *compose_quote_fmt (Compose *compose,
255 MsgInfo *msginfo,
256 const gchar *fmt,
257 const gchar *qmark,
258 const gchar *body,
259 gboolean rewrap,
260 gboolean need_unescape,
261 const gchar *err_msg);
263 static void compose_reply_set_entry (Compose *compose,
264 MsgInfo *msginfo,
265 gboolean to_all,
266 gboolean to_ml,
267 gboolean to_sender,
268 gboolean
269 followup_and_reply_to);
270 static void compose_reedit_set_entry (Compose *compose,
271 MsgInfo *msginfo);
273 static void compose_insert_sig (Compose *compose,
274 gboolean replace);
275 static ComposeInsertResult compose_insert_file (Compose *compose,
276 const gchar *file);
278 static gboolean compose_attach_append (Compose *compose,
279 const gchar *file,
280 const gchar *type,
281 const gchar *content_type,
282 const gchar *charset);
283 static void compose_attach_parts (Compose *compose,
284 MsgInfo *msginfo);
286 static gboolean compose_beautify_paragraph (Compose *compose,
287 GtkTextIter *par_iter,
288 gboolean force);
289 static void compose_wrap_all (Compose *compose);
290 static void compose_wrap_all_full (Compose *compose,
291 gboolean autowrap);
293 static void compose_set_title (Compose *compose);
294 static void compose_select_account (Compose *compose,
295 PrefsAccount *account,
296 gboolean init);
298 static PrefsAccount *compose_current_mail_account(void);
299 /* static gint compose_send (Compose *compose); */
300 static gboolean compose_check_for_valid_recipient
301 (Compose *compose);
302 static gboolean compose_check_entries (Compose *compose,
303 gboolean check_everything);
304 static gint compose_write_to_file (Compose *compose,
305 FILE *fp,
306 gint action,
307 gboolean attach_parts);
308 static gint compose_write_body_to_file (Compose *compose,
309 const gchar *file);
310 static gint compose_remove_reedit_target (Compose *compose,
311 gboolean force);
312 static void compose_remove_draft (Compose *compose);
313 static gint compose_queue_sub (Compose *compose,
314 gint *msgnum,
315 FolderItem **item,
316 gchar **msgpath,
317 gboolean perform_checks,
318 gboolean remove_reedit_target);
319 static int compose_add_attachments (Compose *compose,
320 MimeInfo *parent);
321 static gchar *compose_get_header (Compose *compose);
322 static gchar *compose_get_manual_headers_info (Compose *compose);
324 static void compose_convert_header (Compose *compose,
325 gchar *dest,
326 gint len,
327 gchar *src,
328 gint header_len,
329 gboolean addr_field);
331 static void compose_attach_info_free (AttachInfo *ainfo);
332 static void compose_attach_remove_selected (GtkAction *action,
333 gpointer data);
335 static void compose_template_apply (Compose *compose,
336 Template *tmpl,
337 gboolean replace);
338 static void compose_attach_property (GtkAction *action,
339 gpointer data);
340 static void compose_attach_property_create (gboolean *cancelled);
341 static void attach_property_ok (GtkWidget *widget,
342 gboolean *cancelled);
343 static void attach_property_cancel (GtkWidget *widget,
344 gboolean *cancelled);
345 static gint attach_property_delete_event (GtkWidget *widget,
346 GdkEventAny *event,
347 gboolean *cancelled);
348 static gboolean attach_property_key_pressed (GtkWidget *widget,
349 GdkEventKey *event,
350 gboolean *cancelled);
352 static void compose_exec_ext_editor (Compose *compose);
353 #ifdef G_OS_UNIX
354 static gint compose_exec_ext_editor_real (const gchar *file,
355 GdkNativeWindow socket_wid);
356 static gboolean compose_ext_editor_kill (Compose *compose);
357 static gboolean compose_input_cb (GIOChannel *source,
358 GIOCondition condition,
359 gpointer data);
360 static void compose_set_ext_editor_sensitive (Compose *compose,
361 gboolean sensitive);
362 static gboolean compose_get_ext_editor_cmd_valid();
363 static gboolean compose_get_ext_editor_uses_socket();
364 static gboolean compose_ext_editor_plug_removed_cb
365 (GtkSocket *socket,
366 Compose *compose);
367 #endif /* G_OS_UNIX */
369 static void compose_undo_state_changed (UndoMain *undostruct,
370 gint undo_state,
371 gint redo_state,
372 gpointer data);
374 static void compose_create_header_entry (Compose *compose);
375 static void compose_add_header_entry (Compose *compose, const gchar *header,
376 gchar *text, ComposePrefType pref_type);
377 static void compose_remove_header_entries(Compose *compose);
379 static void compose_update_priority_menu_item(Compose * compose);
380 #if USE_ENCHANT
381 static void compose_spell_menu_changed (void *data);
382 static void compose_dict_changed (void *data);
383 #endif
384 static void compose_add_field_list ( Compose *compose,
385 GList *listAddress );
387 /* callback functions */
389 static void compose_notebook_size_alloc (GtkNotebook *notebook,
390 GtkAllocation *allocation,
391 GtkPaned *paned);
392 static gboolean compose_edit_size_alloc (GtkEditable *widget,
393 GtkAllocation *allocation,
394 GtkSHRuler *shruler);
395 static void account_activated (GtkComboBox *optmenu,
396 gpointer data);
397 static void attach_selected (GtkTreeView *tree_view,
398 GtkTreePath *tree_path,
399 GtkTreeViewColumn *column,
400 Compose *compose);
401 static gboolean attach_button_pressed (GtkWidget *widget,
402 GdkEventButton *event,
403 gpointer data);
404 static gboolean attach_key_pressed (GtkWidget *widget,
405 GdkEventKey *event,
406 gpointer data);
407 static void compose_send_cb (GtkAction *action, gpointer data);
408 static void compose_send_later_cb (GtkAction *action, gpointer data);
410 static void compose_save_cb (GtkAction *action,
411 gpointer data);
413 static void compose_attach_cb (GtkAction *action,
414 gpointer data);
415 static void compose_insert_file_cb (GtkAction *action,
416 gpointer data);
417 static void compose_insert_sig_cb (GtkAction *action,
418 gpointer data);
419 static void compose_replace_sig_cb (GtkAction *action,
420 gpointer data);
422 static void compose_close_cb (GtkAction *action,
423 gpointer data);
424 static void compose_print_cb (GtkAction *action,
425 gpointer data);
427 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
429 static void compose_address_cb (GtkAction *action,
430 gpointer data);
431 static void about_show_cb (GtkAction *action,
432 gpointer data);
433 static void compose_template_activate_cb(GtkWidget *widget,
434 gpointer data);
436 static void compose_ext_editor_cb (GtkAction *action,
437 gpointer data);
439 static gint compose_delete_cb (GtkWidget *widget,
440 GdkEventAny *event,
441 gpointer data);
443 static void compose_undo_cb (GtkAction *action,
444 gpointer data);
445 static void compose_redo_cb (GtkAction *action,
446 gpointer data);
447 static void compose_cut_cb (GtkAction *action,
448 gpointer data);
449 static void compose_copy_cb (GtkAction *action,
450 gpointer data);
451 static void compose_paste_cb (GtkAction *action,
452 gpointer data);
453 static void compose_paste_as_quote_cb (GtkAction *action,
454 gpointer data);
455 static void compose_paste_no_wrap_cb (GtkAction *action,
456 gpointer data);
457 static void compose_paste_wrap_cb (GtkAction *action,
458 gpointer data);
459 static void compose_allsel_cb (GtkAction *action,
460 gpointer data);
462 static void compose_advanced_action_cb (GtkAction *action,
463 gpointer data);
465 static void compose_grab_focus_cb (GtkWidget *widget,
466 Compose *compose);
468 static void compose_changed_cb (GtkTextBuffer *textbuf,
469 Compose *compose);
471 static void compose_wrap_cb (GtkAction *action,
472 gpointer data);
473 static void compose_wrap_all_cb (GtkAction *action,
474 gpointer data);
475 static void compose_find_cb (GtkAction *action,
476 gpointer data);
477 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
478 gpointer data);
479 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
480 gpointer data);
482 static void compose_toggle_ruler_cb (GtkToggleAction *action,
483 gpointer data);
484 static void compose_toggle_sign_cb (GtkToggleAction *action,
485 gpointer data);
486 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
487 gpointer data);
488 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
489 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
490 static void activate_privacy_system (Compose *compose,
491 PrefsAccount *account,
492 gboolean warn);
493 static void compose_use_signing(Compose *compose, gboolean use_signing);
494 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
495 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
496 gpointer data);
497 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
498 gpointer data);
499 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
500 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
501 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
503 static void compose_attach_drag_received_cb (GtkWidget *widget,
504 GdkDragContext *drag_context,
505 gint x,
506 gint y,
507 GtkSelectionData *data,
508 guint info,
509 guint time,
510 gpointer user_data);
511 static void compose_insert_drag_received_cb (GtkWidget *widget,
512 GdkDragContext *drag_context,
513 gint x,
514 gint y,
515 GtkSelectionData *data,
516 guint info,
517 guint time,
518 gpointer user_data);
519 static void compose_header_drag_received_cb (GtkWidget *widget,
520 GdkDragContext *drag_context,
521 gint x,
522 gint y,
523 GtkSelectionData *data,
524 guint info,
525 guint time,
526 gpointer user_data);
528 static gboolean compose_drag_drop (GtkWidget *widget,
529 GdkDragContext *drag_context,
530 gint x, gint y,
531 guint time, gpointer user_data);
532 static gboolean completion_set_focus_to_subject
533 (GtkWidget *widget,
534 GdkEventKey *event,
535 Compose *user_data);
537 static void text_inserted (GtkTextBuffer *buffer,
538 GtkTextIter *iter,
539 const gchar *text,
540 gint len,
541 Compose *compose);
542 static Compose *compose_generic_reply(MsgInfo *msginfo,
543 ComposeQuoteMode quote_mode,
544 gboolean to_all,
545 gboolean to_ml,
546 gboolean to_sender,
547 gboolean followup_and_reply_to,
548 const gchar *body);
550 static void compose_headerentry_changed_cb (GtkWidget *entry,
551 ComposeHeaderEntry *headerentry);
552 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
553 GdkEventKey *event,
554 ComposeHeaderEntry *headerentry);
555 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
556 ComposeHeaderEntry *headerentry);
558 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
560 static void compose_allow_user_actions (Compose *compose, gboolean allow);
562 static void compose_nothing_cb (GtkAction *action, gpointer data)
567 #if USE_ENCHANT
568 static void compose_check_all (GtkAction *action, gpointer data);
569 static void compose_highlight_all (GtkAction *action, gpointer data);
570 static void compose_check_backwards (GtkAction *action, gpointer data);
571 static void compose_check_forwards_go (GtkAction *action, gpointer data);
572 #endif
574 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
576 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
578 #ifdef USE_ENCHANT
579 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
580 FolderItem *folder_item);
581 #endif
582 static void compose_attach_update_label(Compose *compose);
583 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
584 gboolean respect_default_to);
585 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
586 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
588 static GtkActionEntry compose_popup_entries[] =
590 {"Compose", NULL, "Compose", NULL, NULL, NULL },
591 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
592 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
593 {"Compose/---", NULL, "---", NULL, NULL, NULL },
594 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
597 static GtkActionEntry compose_entries[] =
599 {"Menu", NULL, "Menu", NULL, NULL, NULL },
600 /* menus */
601 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
602 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
603 #if USE_ENCHANT
604 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
605 #endif
606 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
607 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
608 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
609 /* Message menu */
610 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
611 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
612 {"Message/---", NULL, "---", NULL, NULL, NULL },
614 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
615 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
616 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
617 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
618 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
619 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
620 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
621 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
622 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
623 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
625 /* Edit menu */
626 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
627 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
628 {"Edit/---", NULL, "---", NULL, NULL, NULL },
630 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
631 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
632 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
634 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
635 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
636 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
637 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
639 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
641 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
642 {"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*/
643 {"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*/
644 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
645 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
646 {"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*/
647 {"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*/
648 {"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*/
649 {"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*/
650 {"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*/
651 {"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*/
652 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
653 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
654 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
655 {"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*/
657 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
658 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
660 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
661 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
662 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
663 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
664 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
665 #if USE_ENCHANT
666 /* Spelling menu */
667 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
668 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
669 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
670 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
672 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
673 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
674 #endif
676 /* Options menu */
677 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
678 {"Options/---", NULL, "---", NULL, NULL, NULL },
679 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
680 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
682 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
683 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
685 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
686 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
687 #define ENC_ACTION(cs_char,c_char,string) \
688 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
690 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
691 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
692 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
693 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
694 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
695 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
696 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
697 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
698 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
700 /* Tools menu */
701 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
703 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
704 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
705 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
706 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
708 /* Help menu */
709 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
712 static GtkToggleActionEntry compose_toggle_entries[] =
714 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
715 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
716 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
717 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
718 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
719 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
720 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
723 static GtkRadioActionEntry compose_radio_rm_entries[] =
725 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
726 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
727 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
728 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
731 static GtkRadioActionEntry compose_radio_prio_entries[] =
733 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
734 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
735 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
736 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
737 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
740 static GtkRadioActionEntry compose_radio_enc_entries[] =
742 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
777 static GtkTargetEntry compose_mime_types[] =
779 {"text/uri-list", 0, 0},
780 {"UTF8_STRING", 0, 0},
781 {"text/plain", 0, 0}
784 static gboolean compose_put_existing_to_front(MsgInfo *info)
786 const GList *compose_list = compose_get_compose_list();
787 const GList *elem = NULL;
789 if (compose_list) {
790 for (elem = compose_list; elem != NULL && elem->data != NULL;
791 elem = elem->next) {
792 Compose *c = (Compose*)elem->data;
794 if (!c->targetinfo || !c->targetinfo->msgid ||
795 !info->msgid)
796 continue;
798 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
799 gtkut_window_popup(c->window);
800 return TRUE;
804 return FALSE;
807 static GdkColor quote_color1 =
808 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
809 static GdkColor quote_color2 =
810 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
811 static GdkColor quote_color3 =
812 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
814 static GdkColor quote_bgcolor1 =
815 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
816 static GdkColor quote_bgcolor2 =
817 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
818 static GdkColor quote_bgcolor3 =
819 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
821 static GdkColor signature_color = {
822 (gulong)0,
823 (gushort)0x7fff,
824 (gushort)0x7fff,
825 (gushort)0x7fff
828 static GdkColor uri_color = {
829 (gulong)0,
830 (gushort)0,
831 (gushort)0,
832 (gushort)0
835 static void compose_create_tags(GtkTextView *text, Compose *compose)
837 GtkTextBuffer *buffer;
838 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
839 #if !GTK_CHECK_VERSION(2, 24, 0)
840 GdkColormap *cmap;
841 gboolean success[8];
842 int i;
843 GdkColor color[8];
844 #endif
846 buffer = gtk_text_view_get_buffer(text);
848 if (prefs_common.enable_color) {
849 /* grab the quote colors, converting from an int to a GdkColor */
850 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
851 &quote_color1);
852 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
853 &quote_color2);
854 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
855 &quote_color3);
856 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
857 &quote_bgcolor1);
858 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
859 &quote_bgcolor2);
860 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
861 &quote_bgcolor3);
862 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
863 &signature_color);
864 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
865 &uri_color);
866 } else {
867 signature_color = quote_color1 = quote_color2 = quote_color3 =
868 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
871 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
872 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
873 "foreground-gdk", &quote_color1,
874 "paragraph-background-gdk", &quote_bgcolor1,
875 NULL);
876 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
877 "foreground-gdk", &quote_color2,
878 "paragraph-background-gdk", &quote_bgcolor2,
879 NULL);
880 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
881 "foreground-gdk", &quote_color3,
882 "paragraph-background-gdk", &quote_bgcolor3,
883 NULL);
884 } else {
885 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
886 "foreground-gdk", &quote_color1,
887 NULL);
888 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
889 "foreground-gdk", &quote_color2,
890 NULL);
891 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
892 "foreground-gdk", &quote_color3,
893 NULL);
896 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
897 "foreground-gdk", &signature_color,
898 NULL);
900 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
901 "foreground-gdk", &uri_color,
902 NULL);
903 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
904 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
906 #if !GTK_CHECK_VERSION(2, 24, 0)
907 color[0] = quote_color1;
908 color[1] = quote_color2;
909 color[2] = quote_color3;
910 color[3] = quote_bgcolor1;
911 color[4] = quote_bgcolor2;
912 color[5] = quote_bgcolor3;
913 color[6] = signature_color;
914 color[7] = uri_color;
916 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
917 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
919 for (i = 0; i < 8; i++) {
920 if (success[i] == FALSE) {
921 g_warning("Compose: color allocation failed.");
922 quote_color1 = quote_color2 = quote_color3 =
923 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
924 signature_color = uri_color = black;
927 #endif
930 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
931 GList *attach_files)
933 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
936 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
938 return compose_generic_new(account, mailto, item, NULL, NULL);
941 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
943 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
946 #define SCROLL_TO_CURSOR(compose) { \
947 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
948 gtk_text_view_get_buffer( \
949 GTK_TEXT_VIEW(compose->text))); \
950 gtk_text_view_scroll_mark_onscreen( \
951 GTK_TEXT_VIEW(compose->text), \
952 cmark); \
955 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
957 GtkEditable *entry;
958 if (folderidentifier) {
959 #if !GTK_CHECK_VERSION(2, 24, 0)
960 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
961 #else
962 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
963 #endif
964 prefs_common.compose_save_to_history = add_history(
965 prefs_common.compose_save_to_history, folderidentifier);
966 #if !GTK_CHECK_VERSION(2, 24, 0)
967 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
968 prefs_common.compose_save_to_history);
969 #else
970 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
971 prefs_common.compose_save_to_history);
972 #endif
975 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
976 if (folderidentifier)
977 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
978 else
979 gtk_entry_set_text(GTK_ENTRY(entry), "");
982 static gchar *compose_get_save_to(Compose *compose)
984 GtkEditable *entry;
985 gchar *result = NULL;
986 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
987 result = gtk_editable_get_chars(entry, 0, -1);
989 if (result) {
990 #if !GTK_CHECK_VERSION(2, 24, 0)
991 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
992 #else
993 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
994 #endif
995 prefs_common.compose_save_to_history = add_history(
996 prefs_common.compose_save_to_history, result);
997 #if !GTK_CHECK_VERSION(2, 24, 0)
998 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
999 prefs_common.compose_save_to_history);
1000 #else
1001 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
1002 prefs_common.compose_save_to_history);
1003 #endif
1005 return result;
1008 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
1009 GList *attach_files, GList *listAddress )
1011 Compose *compose;
1012 GtkTextView *textview;
1013 GtkTextBuffer *textbuf;
1014 GtkTextIter iter;
1015 const gchar *subject_format = NULL;
1016 const gchar *body_format = NULL;
1017 gchar *mailto_from = NULL;
1018 PrefsAccount *mailto_account = NULL;
1019 MsgInfo* dummyinfo = NULL;
1020 gint cursor_pos = -1;
1021 MailField mfield = NO_FIELD_PRESENT;
1022 gchar* buf;
1023 GtkTextMark *mark;
1025 /* check if mailto defines a from */
1026 if (mailto && *mailto != '\0') {
1027 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1028 /* mailto defines a from, check if we can get account prefs from it,
1029 if not, the account prefs will be guessed using other ways, but we'll keep
1030 the from anyway */
1031 if (mailto_from) {
1032 mailto_account = account_find_from_address(mailto_from, TRUE);
1033 if (mailto_account == NULL) {
1034 gchar *tmp_from;
1035 Xstrdup_a(tmp_from, mailto_from, return NULL);
1036 extract_address(tmp_from);
1037 mailto_account = account_find_from_address(tmp_from, TRUE);
1040 if (mailto_account)
1041 account = mailto_account;
1044 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1045 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1046 account = account_find_from_id(item->prefs->default_account);
1048 /* if no account prefs set, fallback to the current one */
1049 if (!account) account = cur_account;
1050 cm_return_val_if_fail(account != NULL, NULL);
1052 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1054 /* override from name if mailto asked for it */
1055 if (mailto_from) {
1056 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1057 g_free(mailto_from);
1058 } else
1059 /* override from name according to folder properties */
1060 if (item && item->prefs &&
1061 item->prefs->compose_with_format &&
1062 item->prefs->compose_override_from_format &&
1063 *item->prefs->compose_override_from_format != '\0') {
1065 gchar *tmp = NULL;
1066 gchar *buf = NULL;
1068 dummyinfo = compose_msginfo_new_from_compose(compose);
1070 /* decode \-escape sequences in the internal representation of the quote format */
1071 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1072 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1074 #ifdef USE_ENCHANT
1075 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1076 compose->gtkaspell);
1077 #else
1078 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1079 #endif
1080 quote_fmt_scan_string(tmp);
1081 quote_fmt_parse();
1083 buf = quote_fmt_get_buffer();
1084 if (buf == NULL)
1085 alertpanel_error(_("New message From format error."));
1086 else
1087 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1088 quote_fmt_reset_vartable();
1090 g_free(tmp);
1093 compose->replyinfo = NULL;
1094 compose->fwdinfo = NULL;
1096 textview = GTK_TEXT_VIEW(compose->text);
1097 textbuf = gtk_text_view_get_buffer(textview);
1098 compose_create_tags(textview, compose);
1100 undo_block(compose->undostruct);
1101 #ifdef USE_ENCHANT
1102 compose_set_dictionaries_from_folder_prefs(compose, item);
1103 #endif
1105 if (account->auto_sig)
1106 compose_insert_sig(compose, FALSE);
1107 gtk_text_buffer_get_start_iter(textbuf, &iter);
1108 gtk_text_buffer_place_cursor(textbuf, &iter);
1110 if (account->protocol != A_NNTP) {
1111 if (mailto && *mailto != '\0') {
1112 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1114 } else {
1115 compose_set_folder_prefs(compose, item, TRUE);
1117 if (item && item->ret_rcpt) {
1118 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1120 } else {
1121 if (mailto && *mailto != '\0') {
1122 if (!strchr(mailto, '@'))
1123 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1124 else
1125 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1126 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1127 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1128 mfield = TO_FIELD_PRESENT;
1131 * CLAWS: just don't allow return receipt request, even if the user
1132 * may want to send an email. simple but foolproof.
1134 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1136 compose_add_field_list( compose, listAddress );
1138 if (item && item->prefs && item->prefs->compose_with_format) {
1139 subject_format = item->prefs->compose_subject_format;
1140 body_format = item->prefs->compose_body_format;
1141 } else if (account->compose_with_format) {
1142 subject_format = account->compose_subject_format;
1143 body_format = account->compose_body_format;
1144 } else if (prefs_common.compose_with_format) {
1145 subject_format = prefs_common.compose_subject_format;
1146 body_format = prefs_common.compose_body_format;
1149 if (subject_format || body_format) {
1151 if ( subject_format
1152 && *subject_format != '\0' )
1154 gchar *subject = NULL;
1155 gchar *tmp = NULL;
1156 gchar *buf = NULL;
1158 if (!dummyinfo)
1159 dummyinfo = compose_msginfo_new_from_compose(compose);
1161 /* decode \-escape sequences in the internal representation of the quote format */
1162 tmp = g_malloc(strlen(subject_format)+1);
1163 pref_get_unescaped_pref(tmp, subject_format);
1165 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1166 #ifdef USE_ENCHANT
1167 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1168 compose->gtkaspell);
1169 #else
1170 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1171 #endif
1172 quote_fmt_scan_string(tmp);
1173 quote_fmt_parse();
1175 buf = quote_fmt_get_buffer();
1176 if (buf == NULL)
1177 alertpanel_error(_("New message subject format error."));
1178 else
1179 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1180 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1181 quote_fmt_reset_vartable();
1183 g_free(subject);
1184 g_free(tmp);
1185 mfield = SUBJECT_FIELD_PRESENT;
1188 if ( body_format
1189 && *body_format != '\0' )
1191 GtkTextView *text;
1192 GtkTextBuffer *buffer;
1193 GtkTextIter start, end;
1194 gchar *tmp = NULL;
1196 if (!dummyinfo)
1197 dummyinfo = compose_msginfo_new_from_compose(compose);
1199 text = GTK_TEXT_VIEW(compose->text);
1200 buffer = gtk_text_view_get_buffer(text);
1201 gtk_text_buffer_get_start_iter(buffer, &start);
1202 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1203 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1205 compose_quote_fmt(compose, dummyinfo,
1206 body_format,
1207 NULL, tmp, FALSE, TRUE,
1208 _("The body of the \"New message\" template has an error at line %d."));
1209 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1210 quote_fmt_reset_vartable();
1212 g_free(tmp);
1213 #ifdef USE_ENCHANT
1214 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1215 gtkaspell_highlight_all(compose->gtkaspell);
1216 #endif
1217 mfield = BODY_FIELD_PRESENT;
1221 procmsg_msginfo_free( &dummyinfo );
1223 if (attach_files) {
1224 GList *curr;
1225 AttachInfo *ainfo;
1227 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1228 ainfo = (AttachInfo *) curr->data;
1229 compose_attach_append(compose, ainfo->file, ainfo->file,
1230 ainfo->content_type, ainfo->charset);
1234 compose_show_first_last_header(compose, TRUE);
1236 /* Set save folder */
1237 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1238 gchar *folderidentifier;
1240 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1241 folderidentifier = folder_item_get_identifier(item);
1242 compose_set_save_to(compose, folderidentifier);
1243 g_free(folderidentifier);
1246 /* Place cursor according to provided input (mfield) */
1247 switch (mfield) {
1248 case NO_FIELD_PRESENT:
1249 if (compose->header_last)
1250 gtk_widget_grab_focus(compose->header_last->entry);
1251 break;
1252 case TO_FIELD_PRESENT:
1253 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1254 if (buf) {
1255 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1256 g_free(buf);
1258 gtk_widget_grab_focus(compose->subject_entry);
1259 break;
1260 case SUBJECT_FIELD_PRESENT:
1261 textview = GTK_TEXT_VIEW(compose->text);
1262 if (!textview)
1263 break;
1264 textbuf = gtk_text_view_get_buffer(textview);
1265 if (!textbuf)
1266 break;
1267 mark = gtk_text_buffer_get_insert(textbuf);
1268 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1269 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1271 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1272 * only defers where it comes to the variable body
1273 * is not null. If no body is present compose->text
1274 * will be null in which case you cannot place the
1275 * cursor inside the component so. An empty component
1276 * is therefore created before placing the cursor
1278 case BODY_FIELD_PRESENT:
1279 cursor_pos = quote_fmt_get_cursor_pos();
1280 if (cursor_pos == -1)
1281 gtk_widget_grab_focus(compose->header_last->entry);
1282 else
1283 gtk_widget_grab_focus(compose->text);
1284 break;
1287 undo_unblock(compose->undostruct);
1289 if (prefs_common.auto_exteditor)
1290 compose_exec_ext_editor(compose);
1292 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1294 SCROLL_TO_CURSOR(compose);
1296 compose->modified = FALSE;
1297 compose_set_title(compose);
1299 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1301 return compose;
1304 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1305 gboolean override_pref, const gchar *system)
1307 const gchar *privacy = NULL;
1309 cm_return_if_fail(compose != NULL);
1310 cm_return_if_fail(account != NULL);
1312 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1313 return;
1315 if (account->default_privacy_system && strlen(account->default_privacy_system))
1316 privacy = account->default_privacy_system;
1317 else if (system)
1318 privacy = system;
1319 else {
1320 GSList *privacy_avail = privacy_get_system_ids();
1321 if (privacy_avail && g_slist_length(privacy_avail)) {
1322 privacy = (gchar *)(privacy_avail->data);
1325 if (privacy != NULL) {
1326 if (system) {
1327 g_free(compose->privacy_system);
1328 compose->privacy_system = NULL;
1329 g_free(compose->encdata);
1330 compose->encdata = NULL;
1332 if (compose->privacy_system == NULL)
1333 compose->privacy_system = g_strdup(privacy);
1334 else if (*(compose->privacy_system) == '\0') {
1335 g_free(compose->privacy_system);
1336 g_free(compose->encdata);
1337 compose->encdata = NULL;
1338 compose->privacy_system = g_strdup(privacy);
1340 compose_update_privacy_system_menu_item(compose, FALSE);
1341 compose_use_encryption(compose, TRUE);
1345 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1347 const gchar *privacy = NULL;
1349 if (account->default_privacy_system && strlen(account->default_privacy_system))
1350 privacy = account->default_privacy_system;
1351 else if (system)
1352 privacy = system;
1353 else {
1354 GSList *privacy_avail = privacy_get_system_ids();
1355 if (privacy_avail && g_slist_length(privacy_avail)) {
1356 privacy = (gchar *)(privacy_avail->data);
1360 if (privacy != NULL) {
1361 if (system) {
1362 g_free(compose->privacy_system);
1363 compose->privacy_system = NULL;
1364 g_free(compose->encdata);
1365 compose->encdata = NULL;
1367 if (compose->privacy_system == NULL)
1368 compose->privacy_system = g_strdup(privacy);
1369 compose_update_privacy_system_menu_item(compose, FALSE);
1370 compose_use_signing(compose, TRUE);
1374 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1376 MsgInfo *msginfo;
1377 guint list_len;
1378 Compose *compose = NULL;
1380 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1382 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1383 cm_return_val_if_fail(msginfo != NULL, NULL);
1385 list_len = g_slist_length(msginfo_list);
1387 switch (mode) {
1388 case COMPOSE_REPLY:
1389 case COMPOSE_REPLY_TO_ADDRESS:
1390 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1391 FALSE, prefs_common.default_reply_list, FALSE, body);
1392 break;
1393 case COMPOSE_REPLY_WITH_QUOTE:
1394 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1395 FALSE, prefs_common.default_reply_list, FALSE, body);
1396 break;
1397 case COMPOSE_REPLY_WITHOUT_QUOTE:
1398 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1399 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1400 break;
1401 case COMPOSE_REPLY_TO_SENDER:
1402 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1403 FALSE, FALSE, TRUE, body);
1404 break;
1405 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1406 compose = compose_followup_and_reply_to(msginfo,
1407 COMPOSE_QUOTE_CHECK,
1408 FALSE, FALSE, body);
1409 break;
1410 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1411 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1412 FALSE, FALSE, TRUE, body);
1413 break;
1414 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1415 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1416 FALSE, FALSE, TRUE, NULL);
1417 break;
1418 case COMPOSE_REPLY_TO_ALL:
1419 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1420 TRUE, FALSE, FALSE, body);
1421 break;
1422 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1423 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1424 TRUE, FALSE, FALSE, body);
1425 break;
1426 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1427 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1428 TRUE, FALSE, FALSE, NULL);
1429 break;
1430 case COMPOSE_REPLY_TO_LIST:
1431 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1432 FALSE, TRUE, FALSE, body);
1433 break;
1434 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1435 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1436 FALSE, TRUE, FALSE, body);
1437 break;
1438 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1439 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1440 FALSE, TRUE, FALSE, NULL);
1441 break;
1442 case COMPOSE_FORWARD:
1443 if (prefs_common.forward_as_attachment) {
1444 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1445 return compose;
1446 } else {
1447 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1448 return compose;
1450 break;
1451 case COMPOSE_FORWARD_INLINE:
1452 /* check if we reply to more than one Message */
1453 if (list_len == 1) {
1454 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1455 break;
1457 /* more messages FALL THROUGH */
1458 case COMPOSE_FORWARD_AS_ATTACH:
1459 compose = compose_forward_multiple(NULL, msginfo_list);
1460 break;
1461 case COMPOSE_REDIRECT:
1462 compose = compose_redirect(NULL, msginfo, FALSE);
1463 break;
1464 default:
1465 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1468 if (compose == NULL) {
1469 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1470 return NULL;
1473 compose->rmode = mode;
1474 switch (compose->rmode) {
1475 case COMPOSE_REPLY:
1476 case COMPOSE_REPLY_WITH_QUOTE:
1477 case COMPOSE_REPLY_WITHOUT_QUOTE:
1478 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1479 debug_print("reply mode Normal\n");
1480 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1481 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1482 break;
1483 case COMPOSE_REPLY_TO_SENDER:
1484 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1485 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1486 debug_print("reply mode Sender\n");
1487 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1488 break;
1489 case COMPOSE_REPLY_TO_ALL:
1490 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1491 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1492 debug_print("reply mode All\n");
1493 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1494 break;
1495 case COMPOSE_REPLY_TO_LIST:
1496 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1497 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1498 debug_print("reply mode List\n");
1499 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1500 break;
1501 case COMPOSE_REPLY_TO_ADDRESS:
1502 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1503 break;
1504 default:
1505 break;
1507 return compose;
1510 static Compose *compose_reply(MsgInfo *msginfo,
1511 ComposeQuoteMode quote_mode,
1512 gboolean to_all,
1513 gboolean to_ml,
1514 gboolean to_sender,
1515 const gchar *body)
1517 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1518 to_sender, FALSE, body);
1521 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1522 ComposeQuoteMode quote_mode,
1523 gboolean to_all,
1524 gboolean to_sender,
1525 const gchar *body)
1527 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1528 to_sender, TRUE, body);
1531 static void compose_extract_original_charset(Compose *compose)
1533 MsgInfo *info = NULL;
1534 if (compose->replyinfo) {
1535 info = compose->replyinfo;
1536 } else if (compose->fwdinfo) {
1537 info = compose->fwdinfo;
1538 } else if (compose->targetinfo) {
1539 info = compose->targetinfo;
1541 if (info) {
1542 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1543 MimeInfo *partinfo = mimeinfo;
1544 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1545 partinfo = procmime_mimeinfo_next(partinfo);
1546 if (partinfo) {
1547 compose->orig_charset =
1548 g_strdup(procmime_mimeinfo_get_parameter(
1549 partinfo, "charset"));
1551 procmime_mimeinfo_free_all(&mimeinfo);
1555 #define SIGNAL_BLOCK(buffer) { \
1556 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1557 G_CALLBACK(compose_changed_cb), \
1558 compose); \
1559 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1560 G_CALLBACK(text_inserted), \
1561 compose); \
1564 #define SIGNAL_UNBLOCK(buffer) { \
1565 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1566 G_CALLBACK(compose_changed_cb), \
1567 compose); \
1568 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1569 G_CALLBACK(text_inserted), \
1570 compose); \
1573 static Compose *compose_generic_reply(MsgInfo *msginfo,
1574 ComposeQuoteMode quote_mode,
1575 gboolean to_all, gboolean to_ml,
1576 gboolean to_sender,
1577 gboolean followup_and_reply_to,
1578 const gchar *body)
1580 Compose *compose;
1581 PrefsAccount *account = NULL;
1582 GtkTextView *textview;
1583 GtkTextBuffer *textbuf;
1584 gboolean quote = FALSE;
1585 const gchar *qmark = NULL;
1586 const gchar *body_fmt = NULL;
1587 gchar *s_system = NULL;
1588 START_TIMING("");
1589 cm_return_val_if_fail(msginfo != NULL, NULL);
1590 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1592 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1594 cm_return_val_if_fail(account != NULL, NULL);
1596 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1598 compose->updating = TRUE;
1600 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1601 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1603 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1604 if (!compose->replyinfo)
1605 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1607 compose_extract_original_charset(compose);
1609 if (msginfo->folder && msginfo->folder->ret_rcpt)
1610 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1612 /* Set save folder */
1613 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1614 gchar *folderidentifier;
1616 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1617 folderidentifier = folder_item_get_identifier(msginfo->folder);
1618 compose_set_save_to(compose, folderidentifier);
1619 g_free(folderidentifier);
1622 if (compose_parse_header(compose, msginfo) < 0) {
1623 compose->updating = FALSE;
1624 compose_destroy(compose);
1625 return NULL;
1628 /* override from name according to folder properties */
1629 if (msginfo->folder && msginfo->folder->prefs &&
1630 msginfo->folder->prefs->reply_with_format &&
1631 msginfo->folder->prefs->reply_override_from_format &&
1632 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1634 gchar *tmp = NULL;
1635 gchar *buf = NULL;
1637 /* decode \-escape sequences in the internal representation of the quote format */
1638 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1639 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1641 #ifdef USE_ENCHANT
1642 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1643 compose->gtkaspell);
1644 #else
1645 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1646 #endif
1647 quote_fmt_scan_string(tmp);
1648 quote_fmt_parse();
1650 buf = quote_fmt_get_buffer();
1651 if (buf == NULL)
1652 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1653 else
1654 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1655 quote_fmt_reset_vartable();
1657 g_free(tmp);
1660 textview = (GTK_TEXT_VIEW(compose->text));
1661 textbuf = gtk_text_view_get_buffer(textview);
1662 compose_create_tags(textview, compose);
1664 undo_block(compose->undostruct);
1665 #ifdef USE_ENCHANT
1666 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1667 gtkaspell_block_check(compose->gtkaspell);
1668 #endif
1670 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1671 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1672 /* use the reply format of folder (if enabled), or the account's one
1673 (if enabled) or fallback to the global reply format, which is always
1674 enabled (even if empty), and use the relevant quotemark */
1675 quote = TRUE;
1676 if (msginfo->folder && msginfo->folder->prefs &&
1677 msginfo->folder->prefs->reply_with_format) {
1678 qmark = msginfo->folder->prefs->reply_quotemark;
1679 body_fmt = msginfo->folder->prefs->reply_body_format;
1681 } else if (account->reply_with_format) {
1682 qmark = account->reply_quotemark;
1683 body_fmt = account->reply_body_format;
1685 } else {
1686 qmark = prefs_common.quotemark;
1687 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1688 body_fmt = gettext(prefs_common.quotefmt);
1689 else
1690 body_fmt = "";
1694 if (quote) {
1695 /* empty quotemark is not allowed */
1696 if (qmark == NULL || *qmark == '\0')
1697 qmark = "> ";
1698 compose_quote_fmt(compose, compose->replyinfo,
1699 body_fmt, qmark, body, FALSE, TRUE,
1700 _("The body of the \"Reply\" template has an error at line %d."));
1701 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1702 quote_fmt_reset_vartable();
1705 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1706 compose_force_encryption(compose, account, FALSE, s_system);
1709 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1710 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1711 compose_force_signing(compose, account, s_system);
1713 g_free(s_system);
1715 SIGNAL_BLOCK(textbuf);
1717 if (account->auto_sig)
1718 compose_insert_sig(compose, FALSE);
1720 compose_wrap_all(compose);
1722 #ifdef USE_ENCHANT
1723 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1724 gtkaspell_highlight_all(compose->gtkaspell);
1725 gtkaspell_unblock_check(compose->gtkaspell);
1726 #endif
1727 SIGNAL_UNBLOCK(textbuf);
1729 gtk_widget_grab_focus(compose->text);
1731 undo_unblock(compose->undostruct);
1733 if (prefs_common.auto_exteditor)
1734 compose_exec_ext_editor(compose);
1736 compose->modified = FALSE;
1737 compose_set_title(compose);
1739 compose->updating = FALSE;
1740 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1741 SCROLL_TO_CURSOR(compose);
1743 if (compose->deferred_destroy) {
1744 compose_destroy(compose);
1745 return NULL;
1747 END_TIMING();
1749 return compose;
1752 #define INSERT_FW_HEADER(var, hdr) \
1753 if (msginfo->var && *msginfo->var) { \
1754 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1755 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1756 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1759 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1760 gboolean as_attach, const gchar *body,
1761 gboolean no_extedit,
1762 gboolean batch)
1764 Compose *compose;
1765 GtkTextView *textview;
1766 GtkTextBuffer *textbuf;
1767 gint cursor_pos = -1;
1768 ComposeMode mode;
1770 cm_return_val_if_fail(msginfo != NULL, NULL);
1771 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1773 if (!account && !(account = compose_find_account(msginfo)))
1774 account = cur_account;
1776 if (!prefs_common.forward_as_attachment)
1777 mode = COMPOSE_FORWARD_INLINE;
1778 else
1779 mode = COMPOSE_FORWARD;
1780 compose = compose_create(account, msginfo->folder, mode, batch);
1782 compose->updating = TRUE;
1783 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1784 if (!compose->fwdinfo)
1785 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1787 compose_extract_original_charset(compose);
1789 if (msginfo->subject && *msginfo->subject) {
1790 gchar *buf, *buf2, *p;
1792 buf = p = g_strdup(msginfo->subject);
1793 p += subject_get_prefix_length(p);
1794 memmove(buf, p, strlen(p) + 1);
1796 buf2 = g_strdup_printf("Fw: %s", buf);
1797 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1799 g_free(buf);
1800 g_free(buf2);
1803 /* override from name according to folder properties */
1804 if (msginfo->folder && msginfo->folder->prefs &&
1805 msginfo->folder->prefs->forward_with_format &&
1806 msginfo->folder->prefs->forward_override_from_format &&
1807 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1809 gchar *tmp = NULL;
1810 gchar *buf = NULL;
1811 MsgInfo *full_msginfo = NULL;
1813 if (!as_attach)
1814 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1815 if (!full_msginfo)
1816 full_msginfo = procmsg_msginfo_copy(msginfo);
1818 /* decode \-escape sequences in the internal representation of the quote format */
1819 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1820 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1822 #ifdef USE_ENCHANT
1823 gtkaspell_block_check(compose->gtkaspell);
1824 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1825 compose->gtkaspell);
1826 #else
1827 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1828 #endif
1829 quote_fmt_scan_string(tmp);
1830 quote_fmt_parse();
1832 buf = quote_fmt_get_buffer();
1833 if (buf == NULL)
1834 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1835 else
1836 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1837 quote_fmt_reset_vartable();
1839 g_free(tmp);
1840 procmsg_msginfo_free(&full_msginfo);
1843 textview = GTK_TEXT_VIEW(compose->text);
1844 textbuf = gtk_text_view_get_buffer(textview);
1845 compose_create_tags(textview, compose);
1847 undo_block(compose->undostruct);
1848 if (as_attach) {
1849 gchar *msgfile;
1851 msgfile = procmsg_get_message_file(msginfo);
1852 if (!is_file_exist(msgfile))
1853 g_warning("%s: file does not exist", msgfile);
1854 else
1855 compose_attach_append(compose, msgfile, msgfile,
1856 "message/rfc822", NULL);
1858 g_free(msgfile);
1859 } else {
1860 const gchar *qmark = NULL;
1861 const gchar *body_fmt = NULL;
1862 MsgInfo *full_msginfo;
1864 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1865 if (!full_msginfo)
1866 full_msginfo = procmsg_msginfo_copy(msginfo);
1868 /* use the forward format of folder (if enabled), or the account's one
1869 (if enabled) or fallback to the global forward format, which is always
1870 enabled (even if empty), and use the relevant quotemark */
1871 if (msginfo->folder && msginfo->folder->prefs &&
1872 msginfo->folder->prefs->forward_with_format) {
1873 qmark = msginfo->folder->prefs->forward_quotemark;
1874 body_fmt = msginfo->folder->prefs->forward_body_format;
1876 } else if (account->forward_with_format) {
1877 qmark = account->forward_quotemark;
1878 body_fmt = account->forward_body_format;
1880 } else {
1881 qmark = prefs_common.fw_quotemark;
1882 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1883 body_fmt = gettext(prefs_common.fw_quotefmt);
1884 else
1885 body_fmt = "";
1888 /* empty quotemark is not allowed */
1889 if (qmark == NULL || *qmark == '\0')
1890 qmark = "> ";
1892 compose_quote_fmt(compose, full_msginfo,
1893 body_fmt, qmark, body, FALSE, TRUE,
1894 _("The body of the \"Forward\" template has an error at line %d."));
1895 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1896 quote_fmt_reset_vartable();
1897 compose_attach_parts(compose, msginfo);
1899 procmsg_msginfo_free(&full_msginfo);
1902 SIGNAL_BLOCK(textbuf);
1904 if (account->auto_sig)
1905 compose_insert_sig(compose, FALSE);
1907 compose_wrap_all(compose);
1909 #ifdef USE_ENCHANT
1910 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1911 gtkaspell_highlight_all(compose->gtkaspell);
1912 gtkaspell_unblock_check(compose->gtkaspell);
1913 #endif
1914 SIGNAL_UNBLOCK(textbuf);
1916 cursor_pos = quote_fmt_get_cursor_pos();
1917 if (cursor_pos == -1)
1918 gtk_widget_grab_focus(compose->header_last->entry);
1919 else
1920 gtk_widget_grab_focus(compose->text);
1922 if (!no_extedit && prefs_common.auto_exteditor)
1923 compose_exec_ext_editor(compose);
1925 /*save folder*/
1926 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1927 gchar *folderidentifier;
1929 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1930 folderidentifier = folder_item_get_identifier(msginfo->folder);
1931 compose_set_save_to(compose, folderidentifier);
1932 g_free(folderidentifier);
1935 undo_unblock(compose->undostruct);
1937 compose->modified = FALSE;
1938 compose_set_title(compose);
1940 compose->updating = FALSE;
1941 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1942 SCROLL_TO_CURSOR(compose);
1944 if (compose->deferred_destroy) {
1945 compose_destroy(compose);
1946 return NULL;
1949 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1951 return compose;
1954 #undef INSERT_FW_HEADER
1956 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1958 Compose *compose;
1959 GtkTextView *textview;
1960 GtkTextBuffer *textbuf;
1961 GtkTextIter iter;
1962 GSList *msginfo;
1963 gchar *msgfile;
1964 gboolean single_mail = TRUE;
1966 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1968 if (g_slist_length(msginfo_list) > 1)
1969 single_mail = FALSE;
1971 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1972 if (((MsgInfo *)msginfo->data)->folder == NULL)
1973 return NULL;
1975 /* guess account from first selected message */
1976 if (!account &&
1977 !(account = compose_find_account(msginfo_list->data)))
1978 account = cur_account;
1980 cm_return_val_if_fail(account != NULL, NULL);
1982 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1983 if (msginfo->data) {
1984 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1985 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1989 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1990 g_warning("no msginfo_list");
1991 return NULL;
1994 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1996 compose->updating = TRUE;
1998 /* override from name according to folder properties */
1999 if (msginfo_list->data) {
2000 MsgInfo *msginfo = msginfo_list->data;
2002 if (msginfo->folder && msginfo->folder->prefs &&
2003 msginfo->folder->prefs->forward_with_format &&
2004 msginfo->folder->prefs->forward_override_from_format &&
2005 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2007 gchar *tmp = NULL;
2008 gchar *buf = NULL;
2010 /* decode \-escape sequences in the internal representation of the quote format */
2011 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2012 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2014 #ifdef USE_ENCHANT
2015 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2016 compose->gtkaspell);
2017 #else
2018 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2019 #endif
2020 quote_fmt_scan_string(tmp);
2021 quote_fmt_parse();
2023 buf = quote_fmt_get_buffer();
2024 if (buf == NULL)
2025 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2026 else
2027 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2028 quote_fmt_reset_vartable();
2030 g_free(tmp);
2034 textview = GTK_TEXT_VIEW(compose->text);
2035 textbuf = gtk_text_view_get_buffer(textview);
2036 compose_create_tags(textview, compose);
2038 undo_block(compose->undostruct);
2039 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2040 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2042 if (!is_file_exist(msgfile))
2043 g_warning("%s: file does not exist", msgfile);
2044 else
2045 compose_attach_append(compose, msgfile, msgfile,
2046 "message/rfc822", NULL);
2047 g_free(msgfile);
2050 if (single_mail) {
2051 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2052 if (info->subject && *info->subject) {
2053 gchar *buf, *buf2, *p;
2055 buf = p = g_strdup(info->subject);
2056 p += subject_get_prefix_length(p);
2057 memmove(buf, p, strlen(p) + 1);
2059 buf2 = g_strdup_printf("Fw: %s", buf);
2060 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2062 g_free(buf);
2063 g_free(buf2);
2065 } else {
2066 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2067 _("Fw: multiple emails"));
2070 SIGNAL_BLOCK(textbuf);
2072 if (account->auto_sig)
2073 compose_insert_sig(compose, FALSE);
2075 compose_wrap_all(compose);
2077 SIGNAL_UNBLOCK(textbuf);
2079 gtk_text_buffer_get_start_iter(textbuf, &iter);
2080 gtk_text_buffer_place_cursor(textbuf, &iter);
2082 if (prefs_common.auto_exteditor)
2083 compose_exec_ext_editor(compose);
2085 gtk_widget_grab_focus(compose->header_last->entry);
2086 undo_unblock(compose->undostruct);
2087 compose->modified = FALSE;
2088 compose_set_title(compose);
2090 compose->updating = FALSE;
2091 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2092 SCROLL_TO_CURSOR(compose);
2094 if (compose->deferred_destroy) {
2095 compose_destroy(compose);
2096 return NULL;
2099 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2101 return compose;
2104 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2106 GtkTextIter start = *iter;
2107 GtkTextIter end_iter;
2108 int start_pos = gtk_text_iter_get_offset(&start);
2109 gchar *str = NULL;
2110 if (!compose->account->sig_sep)
2111 return FALSE;
2113 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2114 start_pos+strlen(compose->account->sig_sep));
2116 /* check sig separator */
2117 str = gtk_text_iter_get_text(&start, &end_iter);
2118 if (!strcmp(str, compose->account->sig_sep)) {
2119 gchar *tmp = NULL;
2120 /* check end of line (\n) */
2121 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2122 start_pos+strlen(compose->account->sig_sep));
2123 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2124 start_pos+strlen(compose->account->sig_sep)+1);
2125 tmp = gtk_text_iter_get_text(&start, &end_iter);
2126 if (!strcmp(tmp,"\n")) {
2127 g_free(str);
2128 g_free(tmp);
2129 return TRUE;
2131 g_free(tmp);
2133 g_free(str);
2135 return FALSE;
2138 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2140 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2141 Compose *compose = (Compose *)data;
2142 FolderItem *old_item = NULL;
2143 FolderItem *new_item = NULL;
2144 gchar *old_id, *new_id;
2146 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2147 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2148 return FALSE;
2150 old_item = hookdata->item;
2151 new_item = hookdata->item2;
2153 old_id = folder_item_get_identifier(old_item);
2154 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2156 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2157 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2158 compose->targetinfo->folder = new_item;
2161 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2162 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2163 compose->replyinfo->folder = new_item;
2166 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2167 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2168 compose->fwdinfo->folder = new_item;
2171 g_free(old_id);
2172 g_free(new_id);
2173 return FALSE;
2176 static void compose_colorize_signature(Compose *compose)
2178 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2179 GtkTextIter iter;
2180 GtkTextIter end_iter;
2181 gtk_text_buffer_get_start_iter(buffer, &iter);
2182 while (gtk_text_iter_forward_line(&iter))
2183 if (compose_is_sig_separator(compose, buffer, &iter)) {
2184 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2185 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2189 #define BLOCK_WRAP() { \
2190 prev_autowrap = compose->autowrap; \
2191 buffer = gtk_text_view_get_buffer( \
2192 GTK_TEXT_VIEW(compose->text)); \
2193 compose->autowrap = FALSE; \
2195 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2196 G_CALLBACK(compose_changed_cb), \
2197 compose); \
2198 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2199 G_CALLBACK(text_inserted), \
2200 compose); \
2202 #define UNBLOCK_WRAP() { \
2203 compose->autowrap = prev_autowrap; \
2204 if (compose->autowrap) { \
2205 gint old = compose->draft_timeout_tag; \
2206 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2207 compose_wrap_all(compose); \
2208 compose->draft_timeout_tag = old; \
2211 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2212 G_CALLBACK(compose_changed_cb), \
2213 compose); \
2214 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2215 G_CALLBACK(text_inserted), \
2216 compose); \
2219 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2221 Compose *compose = NULL;
2222 PrefsAccount *account = NULL;
2223 GtkTextView *textview;
2224 GtkTextBuffer *textbuf;
2225 GtkTextMark *mark;
2226 GtkTextIter iter;
2227 FILE *fp;
2228 gboolean use_signing = FALSE;
2229 gboolean use_encryption = FALSE;
2230 gchar *privacy_system = NULL;
2231 int priority = PRIORITY_NORMAL;
2232 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2233 gboolean autowrap = prefs_common.autowrap;
2234 gboolean autoindent = prefs_common.auto_indent;
2235 HeaderEntry *manual_headers = NULL;
2237 cm_return_val_if_fail(msginfo != NULL, NULL);
2238 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2240 if (compose_put_existing_to_front(msginfo)) {
2241 return NULL;
2244 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2245 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2246 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2247 gchar *queueheader_buf = NULL;
2248 gint id, param;
2250 /* Select Account from queue headers */
2251 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2252 "X-Claws-Account-Id:")) {
2253 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2254 account = account_find_from_id(id);
2255 g_free(queueheader_buf);
2257 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2258 "X-Sylpheed-Account-Id:")) {
2259 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2260 account = account_find_from_id(id);
2261 g_free(queueheader_buf);
2263 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2264 "NAID:")) {
2265 id = atoi(&queueheader_buf[strlen("NAID:")]);
2266 account = account_find_from_id(id);
2267 g_free(queueheader_buf);
2269 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2270 "MAID:")) {
2271 id = atoi(&queueheader_buf[strlen("MAID:")]);
2272 account = account_find_from_id(id);
2273 g_free(queueheader_buf);
2275 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2276 "S:")) {
2277 account = account_find_from_address(queueheader_buf, FALSE);
2278 g_free(queueheader_buf);
2280 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2281 "X-Claws-Sign:")) {
2282 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2283 use_signing = param;
2284 g_free(queueheader_buf);
2286 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2287 "X-Sylpheed-Sign:")) {
2288 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2289 use_signing = param;
2290 g_free(queueheader_buf);
2292 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2293 "X-Claws-Encrypt:")) {
2294 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2295 use_encryption = param;
2296 g_free(queueheader_buf);
2298 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2299 "X-Sylpheed-Encrypt:")) {
2300 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2301 use_encryption = param;
2302 g_free(queueheader_buf);
2304 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2305 "X-Claws-Auto-Wrapping:")) {
2306 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2307 autowrap = param;
2308 g_free(queueheader_buf);
2310 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2311 "X-Claws-Auto-Indent:")) {
2312 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2313 autoindent = param;
2314 g_free(queueheader_buf);
2316 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2317 "X-Claws-Privacy-System:")) {
2318 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2319 g_free(queueheader_buf);
2321 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2322 "X-Sylpheed-Privacy-System:")) {
2323 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2324 g_free(queueheader_buf);
2326 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2327 "X-Priority: ")) {
2328 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2329 priority = param;
2330 g_free(queueheader_buf);
2332 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2333 "RMID:")) {
2334 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2335 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2336 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2337 if (orig_item != NULL) {
2338 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2340 g_strfreev(tokens);
2342 g_free(queueheader_buf);
2344 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2345 "FMID:")) {
2346 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2347 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2348 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2349 if (orig_item != NULL) {
2350 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2352 g_strfreev(tokens);
2354 g_free(queueheader_buf);
2356 /* Get manual headers */
2357 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2358 "X-Claws-Manual-Headers:")) {
2359 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2360 if (listmh && *listmh != '\0') {
2361 debug_print("Got manual headers: %s\n", listmh);
2362 manual_headers = procheader_entries_from_str(listmh);
2363 g_free(listmh);
2365 g_free(queueheader_buf);
2367 } else {
2368 account = msginfo->folder->folder->account;
2371 if (!account && prefs_common.reedit_account_autosel) {
2372 gchar *from = NULL;
2373 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2374 extract_address(from);
2375 account = account_find_from_address(from, FALSE);
2376 g_free(from);
2379 if (!account) {
2380 account = cur_account;
2382 cm_return_val_if_fail(account != NULL, NULL);
2384 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2386 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2387 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2388 compose->autowrap = autowrap;
2389 compose->replyinfo = replyinfo;
2390 compose->fwdinfo = fwdinfo;
2392 compose->updating = TRUE;
2393 compose->priority = priority;
2395 if (privacy_system != NULL) {
2396 compose->privacy_system = privacy_system;
2397 compose_use_signing(compose, use_signing);
2398 compose_use_encryption(compose, use_encryption);
2399 compose_update_privacy_system_menu_item(compose, FALSE);
2400 } else {
2401 activate_privacy_system(compose, account, FALSE);
2404 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2406 compose_extract_original_charset(compose);
2408 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2409 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2410 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2411 gchar *queueheader_buf = NULL;
2413 /* Set message save folder */
2414 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2415 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2416 compose_set_save_to(compose, &queueheader_buf[4]);
2417 g_free(queueheader_buf);
2419 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2420 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2421 if (active) {
2422 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2424 g_free(queueheader_buf);
2428 if (compose_parse_header(compose, msginfo) < 0) {
2429 compose->updating = FALSE;
2430 compose_destroy(compose);
2431 return NULL;
2433 compose_reedit_set_entry(compose, msginfo);
2435 textview = GTK_TEXT_VIEW(compose->text);
2436 textbuf = gtk_text_view_get_buffer(textview);
2437 compose_create_tags(textview, compose);
2439 mark = gtk_text_buffer_get_insert(textbuf);
2440 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2442 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2443 G_CALLBACK(compose_changed_cb),
2444 compose);
2446 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2447 fp = procmime_get_first_encrypted_text_content(msginfo);
2448 if (fp) {
2449 compose_force_encryption(compose, account, TRUE, NULL);
2451 } else {
2452 fp = procmime_get_first_text_content(msginfo);
2454 if (fp == NULL) {
2455 g_warning("Can't get text part");
2458 if (fp != NULL) {
2459 gchar buf[BUFFSIZE];
2460 gboolean prev_autowrap;
2461 GtkTextBuffer *buffer;
2462 BLOCK_WRAP();
2463 while (fgets(buf, sizeof(buf), fp) != NULL) {
2464 strcrchomp(buf);
2465 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2467 UNBLOCK_WRAP();
2468 fclose(fp);
2471 compose_attach_parts(compose, msginfo);
2473 compose_colorize_signature(compose);
2475 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2476 G_CALLBACK(compose_changed_cb),
2477 compose);
2479 if (manual_headers != NULL) {
2480 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2481 procheader_entries_free(manual_headers);
2482 compose->updating = FALSE;
2483 compose_destroy(compose);
2484 return NULL;
2486 procheader_entries_free(manual_headers);
2489 gtk_widget_grab_focus(compose->text);
2491 if (prefs_common.auto_exteditor) {
2492 compose_exec_ext_editor(compose);
2494 compose->modified = FALSE;
2495 compose_set_title(compose);
2497 compose->updating = FALSE;
2498 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2499 SCROLL_TO_CURSOR(compose);
2501 if (compose->deferred_destroy) {
2502 compose_destroy(compose);
2503 return NULL;
2506 compose->sig_str = account_get_signature_str(compose->account);
2508 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2510 return compose;
2513 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2514 gboolean batch)
2516 Compose *compose;
2517 gchar *filename;
2518 FolderItem *item;
2520 cm_return_val_if_fail(msginfo != NULL, NULL);
2522 if (!account)
2523 account = account_get_reply_account(msginfo,
2524 prefs_common.reply_account_autosel);
2525 cm_return_val_if_fail(account != NULL, NULL);
2527 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2529 compose->updating = TRUE;
2531 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2532 compose->replyinfo = NULL;
2533 compose->fwdinfo = NULL;
2535 compose_show_first_last_header(compose, TRUE);
2537 gtk_widget_grab_focus(compose->header_last->entry);
2539 filename = procmsg_get_message_file(msginfo);
2541 if (filename == NULL) {
2542 compose->updating = FALSE;
2543 compose_destroy(compose);
2545 return NULL;
2548 compose->redirect_filename = filename;
2550 /* Set save folder */
2551 item = msginfo->folder;
2552 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2553 gchar *folderidentifier;
2555 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2556 folderidentifier = folder_item_get_identifier(item);
2557 compose_set_save_to(compose, folderidentifier);
2558 g_free(folderidentifier);
2561 compose_attach_parts(compose, msginfo);
2563 if (msginfo->subject)
2564 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2565 msginfo->subject);
2566 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2568 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2569 _("The body of the \"Redirect\" template has an error at line %d."));
2570 quote_fmt_reset_vartable();
2571 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2573 compose_colorize_signature(compose);
2576 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2577 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2578 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2580 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2581 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2582 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2583 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2584 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2585 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2586 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2587 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2588 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2590 if (compose->toolbar->draft_btn)
2591 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2592 if (compose->toolbar->insert_btn)
2593 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2594 if (compose->toolbar->attach_btn)
2595 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2596 if (compose->toolbar->sig_btn)
2597 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2598 if (compose->toolbar->exteditor_btn)
2599 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2600 if (compose->toolbar->linewrap_current_btn)
2601 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2602 if (compose->toolbar->linewrap_all_btn)
2603 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2605 compose->modified = FALSE;
2606 compose_set_title(compose);
2607 compose->updating = FALSE;
2608 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2609 SCROLL_TO_CURSOR(compose);
2611 if (compose->deferred_destroy) {
2612 compose_destroy(compose);
2613 return NULL;
2616 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2618 return compose;
2621 const GList *compose_get_compose_list(void)
2623 return compose_list;
2626 void compose_entry_append(Compose *compose, const gchar *address,
2627 ComposeEntryType type, ComposePrefType pref_type)
2629 const gchar *header;
2630 gchar *cur, *begin;
2631 gboolean in_quote = FALSE;
2632 if (!address || *address == '\0') return;
2634 switch (type) {
2635 case COMPOSE_CC:
2636 header = N_("Cc:");
2637 break;
2638 case COMPOSE_BCC:
2639 header = N_("Bcc:");
2640 break;
2641 case COMPOSE_REPLYTO:
2642 header = N_("Reply-To:");
2643 break;
2644 case COMPOSE_NEWSGROUPS:
2645 header = N_("Newsgroups:");
2646 break;
2647 case COMPOSE_FOLLOWUPTO:
2648 header = N_( "Followup-To:");
2649 break;
2650 case COMPOSE_INREPLYTO:
2651 header = N_( "In-Reply-To:");
2652 break;
2653 case COMPOSE_TO:
2654 default:
2655 header = N_("To:");
2656 break;
2658 header = prefs_common_translated_header_name(header);
2660 cur = begin = (gchar *)address;
2662 /* we separate the line by commas, but not if we're inside a quoted
2663 * string */
2664 while (*cur != '\0') {
2665 if (*cur == '"')
2666 in_quote = !in_quote;
2667 if (*cur == ',' && !in_quote) {
2668 gchar *tmp = g_strdup(begin);
2669 gchar *o_tmp = tmp;
2670 tmp[cur-begin]='\0';
2671 cur++;
2672 begin = cur;
2673 while (*tmp == ' ' || *tmp == '\t')
2674 tmp++;
2675 compose_add_header_entry(compose, header, tmp, pref_type);
2676 compose_entry_indicate(compose, tmp);
2677 g_free(o_tmp);
2678 continue;
2680 cur++;
2682 if (begin < cur) {
2683 gchar *tmp = g_strdup(begin);
2684 gchar *o_tmp = tmp;
2685 tmp[cur-begin]='\0';
2686 while (*tmp == ' ' || *tmp == '\t')
2687 tmp++;
2688 compose_add_header_entry(compose, header, tmp, pref_type);
2689 compose_entry_indicate(compose, tmp);
2690 g_free(o_tmp);
2694 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2696 GSList *h_list;
2697 GtkEntry *entry;
2699 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2700 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2701 if (gtk_entry_get_text(entry) &&
2702 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2703 gtk_widget_modify_base(
2704 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2705 GTK_STATE_NORMAL, &default_header_bgcolor);
2706 gtk_widget_modify_text(
2707 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2708 GTK_STATE_NORMAL, &default_header_color);
2713 void compose_toolbar_cb(gint action, gpointer data)
2715 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2716 Compose *compose = (Compose*)toolbar_item->parent;
2718 cm_return_if_fail(compose != NULL);
2720 switch(action) {
2721 case A_SEND:
2722 compose_send_cb(NULL, compose);
2723 break;
2724 case A_SEND_LATER:
2725 compose_send_later_cb(NULL, compose);
2726 break;
2727 case A_DRAFT:
2728 compose_draft(compose, COMPOSE_QUIT_EDITING);
2729 break;
2730 case A_INSERT:
2731 compose_insert_file_cb(NULL, compose);
2732 break;
2733 case A_ATTACH:
2734 compose_attach_cb(NULL, compose);
2735 break;
2736 case A_SIG:
2737 compose_insert_sig(compose, FALSE);
2738 break;
2739 case A_REP_SIG:
2740 compose_insert_sig(compose, TRUE);
2741 break;
2742 case A_EXTEDITOR:
2743 compose_ext_editor_cb(NULL, compose);
2744 break;
2745 case A_LINEWRAP_CURRENT:
2746 compose_beautify_paragraph(compose, NULL, TRUE);
2747 break;
2748 case A_LINEWRAP_ALL:
2749 compose_wrap_all_full(compose, TRUE);
2750 break;
2751 case A_ADDRBOOK:
2752 compose_address_cb(NULL, compose);
2753 break;
2754 #ifdef USE_ENCHANT
2755 case A_CHECK_SPELLING:
2756 compose_check_all(NULL, compose);
2757 break;
2758 #endif
2759 default:
2760 break;
2764 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2766 gchar *to = NULL;
2767 gchar *cc = NULL;
2768 gchar *bcc = NULL;
2769 gchar *subject = NULL;
2770 gchar *body = NULL;
2771 gchar *temp = NULL;
2772 gsize len = 0;
2773 gchar **attach = NULL;
2774 gchar *inreplyto = NULL;
2775 MailField mfield = NO_FIELD_PRESENT;
2777 /* get mailto parts but skip from */
2778 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2780 if (to) {
2781 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2782 mfield = TO_FIELD_PRESENT;
2784 if (cc)
2785 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2786 if (bcc)
2787 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2788 if (subject) {
2789 if (!g_utf8_validate (subject, -1, NULL)) {
2790 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2791 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2792 g_free(temp);
2793 } else {
2794 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2796 mfield = SUBJECT_FIELD_PRESENT;
2798 if (body) {
2799 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2800 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2801 GtkTextMark *mark;
2802 GtkTextIter iter;
2803 gboolean prev_autowrap = compose->autowrap;
2805 compose->autowrap = FALSE;
2807 mark = gtk_text_buffer_get_insert(buffer);
2808 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2810 if (!g_utf8_validate (body, -1, NULL)) {
2811 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2812 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2813 g_free(temp);
2814 } else {
2815 gtk_text_buffer_insert(buffer, &iter, body, -1);
2817 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2819 compose->autowrap = prev_autowrap;
2820 if (compose->autowrap)
2821 compose_wrap_all(compose);
2822 mfield = BODY_FIELD_PRESENT;
2825 if (attach) {
2826 gint i = 0, att = 0;
2827 gchar *warn_files = NULL;
2828 while (attach[i] != NULL) {
2829 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2830 if (utf8_filename) {
2831 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2832 gchar *tmp = g_strdup_printf("%s%s\n",
2833 warn_files?warn_files:"",
2834 utf8_filename);
2835 g_free(warn_files);
2836 warn_files = tmp;
2837 att++;
2839 g_free(utf8_filename);
2840 } else {
2841 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2843 i++;
2845 if (warn_files) {
2846 alertpanel_notice(ngettext(
2847 "The following file has been attached: \n%s",
2848 "The following files have been attached: \n%s", att), warn_files);
2849 g_free(warn_files);
2852 if (inreplyto)
2853 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2855 g_free(to);
2856 g_free(cc);
2857 g_free(bcc);
2858 g_free(subject);
2859 g_free(body);
2860 g_strfreev(attach);
2861 g_free(inreplyto);
2863 return mfield;
2866 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2868 static HeaderEntry hentry[] = {
2869 {"Reply-To:", NULL, TRUE },
2870 {"Cc:", NULL, TRUE },
2871 {"References:", NULL, FALSE },
2872 {"Bcc:", NULL, TRUE },
2873 {"Newsgroups:", NULL, TRUE },
2874 {"Followup-To:", NULL, TRUE },
2875 {"List-Post:", NULL, FALSE },
2876 {"X-Priority:", NULL, FALSE },
2877 {NULL, NULL, FALSE }
2880 enum
2882 H_REPLY_TO = 0,
2883 H_CC = 1,
2884 H_REFERENCES = 2,
2885 H_BCC = 3,
2886 H_NEWSGROUPS = 4,
2887 H_FOLLOWUP_TO = 5,
2888 H_LIST_POST = 6,
2889 H_X_PRIORITY = 7
2892 FILE *fp;
2894 cm_return_val_if_fail(msginfo != NULL, -1);
2896 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2897 procheader_get_header_fields(fp, hentry);
2898 fclose(fp);
2900 if (hentry[H_REPLY_TO].body != NULL) {
2901 if (hentry[H_REPLY_TO].body[0] != '\0') {
2902 compose->replyto =
2903 conv_unmime_header(hentry[H_REPLY_TO].body,
2904 NULL, TRUE);
2906 g_free(hentry[H_REPLY_TO].body);
2907 hentry[H_REPLY_TO].body = NULL;
2909 if (hentry[H_CC].body != NULL) {
2910 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2911 g_free(hentry[H_CC].body);
2912 hentry[H_CC].body = NULL;
2914 if (hentry[H_REFERENCES].body != NULL) {
2915 if (compose->mode == COMPOSE_REEDIT)
2916 compose->references = hentry[H_REFERENCES].body;
2917 else {
2918 compose->references = compose_parse_references
2919 (hentry[H_REFERENCES].body, msginfo->msgid);
2920 g_free(hentry[H_REFERENCES].body);
2922 hentry[H_REFERENCES].body = NULL;
2924 if (hentry[H_BCC].body != NULL) {
2925 if (compose->mode == COMPOSE_REEDIT)
2926 compose->bcc =
2927 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2928 g_free(hentry[H_BCC].body);
2929 hentry[H_BCC].body = NULL;
2931 if (hentry[H_NEWSGROUPS].body != NULL) {
2932 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2933 hentry[H_NEWSGROUPS].body = NULL;
2935 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2936 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2937 compose->followup_to =
2938 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2939 NULL, TRUE);
2941 g_free(hentry[H_FOLLOWUP_TO].body);
2942 hentry[H_FOLLOWUP_TO].body = NULL;
2944 if (hentry[H_LIST_POST].body != NULL) {
2945 gchar *to = NULL, *start = NULL;
2947 extract_address(hentry[H_LIST_POST].body);
2948 if (hentry[H_LIST_POST].body[0] != '\0') {
2949 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2951 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2952 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2954 if (to) {
2955 g_free(compose->ml_post);
2956 compose->ml_post = to;
2959 g_free(hentry[H_LIST_POST].body);
2960 hentry[H_LIST_POST].body = NULL;
2963 /* CLAWS - X-Priority */
2964 if (compose->mode == COMPOSE_REEDIT)
2965 if (hentry[H_X_PRIORITY].body != NULL) {
2966 gint priority;
2968 priority = atoi(hentry[H_X_PRIORITY].body);
2969 g_free(hentry[H_X_PRIORITY].body);
2971 hentry[H_X_PRIORITY].body = NULL;
2973 if (priority < PRIORITY_HIGHEST ||
2974 priority > PRIORITY_LOWEST)
2975 priority = PRIORITY_NORMAL;
2977 compose->priority = priority;
2980 if (compose->mode == COMPOSE_REEDIT) {
2981 if (msginfo->inreplyto && *msginfo->inreplyto)
2982 compose->inreplyto = g_strdup(msginfo->inreplyto);
2984 if (msginfo->msgid && *msginfo->msgid)
2985 compose->msgid = g_strdup(msginfo->msgid);
2986 } else {
2987 if (msginfo->msgid && *msginfo->msgid)
2988 compose->inreplyto = g_strdup(msginfo->msgid);
2990 if (!compose->references) {
2991 if (msginfo->msgid && *msginfo->msgid) {
2992 if (msginfo->inreplyto && *msginfo->inreplyto)
2993 compose->references =
2994 g_strdup_printf("<%s>\n\t<%s>",
2995 msginfo->inreplyto,
2996 msginfo->msgid);
2997 else
2998 compose->references =
2999 g_strconcat("<", msginfo->msgid, ">",
3000 NULL);
3001 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3002 compose->references =
3003 g_strconcat("<", msginfo->inreplyto, ">",
3004 NULL);
3009 return 0;
3012 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3014 FILE *fp;
3015 HeaderEntry *he;
3017 cm_return_val_if_fail(msginfo != NULL, -1);
3019 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3020 procheader_get_header_fields(fp, entries);
3021 fclose(fp);
3023 he = entries;
3024 while (he != NULL && he->name != NULL) {
3025 GtkTreeIter iter;
3026 GtkListStore *model = NULL;
3028 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3029 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3030 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3031 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3032 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3033 ++he;
3036 return 0;
3039 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3041 GSList *ref_id_list, *cur;
3042 GString *new_ref;
3043 gchar *new_ref_str;
3045 ref_id_list = references_list_append(NULL, ref);
3046 if (!ref_id_list) return NULL;
3047 if (msgid && *msgid)
3048 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3050 for (;;) {
3051 gint len = 0;
3053 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3054 /* "<" + Message-ID + ">" + CR+LF+TAB */
3055 len += strlen((gchar *)cur->data) + 5;
3057 if (len > MAX_REFERENCES_LEN) {
3058 /* remove second message-ID */
3059 if (ref_id_list && ref_id_list->next &&
3060 ref_id_list->next->next) {
3061 g_free(ref_id_list->next->data);
3062 ref_id_list = g_slist_remove
3063 (ref_id_list, ref_id_list->next->data);
3064 } else {
3065 slist_free_strings_full(ref_id_list);
3066 return NULL;
3068 } else
3069 break;
3072 new_ref = g_string_new("");
3073 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3074 if (new_ref->len > 0)
3075 g_string_append(new_ref, "\n\t");
3076 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3079 slist_free_strings_full(ref_id_list);
3081 new_ref_str = new_ref->str;
3082 g_string_free(new_ref, FALSE);
3084 return new_ref_str;
3087 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3088 const gchar *fmt, const gchar *qmark,
3089 const gchar *body, gboolean rewrap,
3090 gboolean need_unescape,
3091 const gchar *err_msg)
3093 MsgInfo* dummyinfo = NULL;
3094 gchar *quote_str = NULL;
3095 gchar *buf;
3096 gboolean prev_autowrap;
3097 const gchar *trimmed_body = body;
3098 gint cursor_pos = -1;
3099 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3100 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3101 GtkTextIter iter;
3102 GtkTextMark *mark;
3105 SIGNAL_BLOCK(buffer);
3107 if (!msginfo) {
3108 dummyinfo = compose_msginfo_new_from_compose(compose);
3109 msginfo = dummyinfo;
3112 if (qmark != NULL) {
3113 #ifdef USE_ENCHANT
3114 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3115 compose->gtkaspell);
3116 #else
3117 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3118 #endif
3119 quote_fmt_scan_string(qmark);
3120 quote_fmt_parse();
3122 buf = quote_fmt_get_buffer();
3123 if (buf == NULL)
3124 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3125 else
3126 Xstrdup_a(quote_str, buf, goto error)
3129 if (fmt && *fmt != '\0') {
3131 if (trimmed_body)
3132 while (*trimmed_body == '\n')
3133 trimmed_body++;
3135 #ifdef USE_ENCHANT
3136 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3137 compose->gtkaspell);
3138 #else
3139 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3140 #endif
3141 if (need_unescape) {
3142 gchar *tmp = NULL;
3144 /* decode \-escape sequences in the internal representation of the quote format */
3145 tmp = g_malloc(strlen(fmt)+1);
3146 pref_get_unescaped_pref(tmp, fmt);
3147 quote_fmt_scan_string(tmp);
3148 quote_fmt_parse();
3149 g_free(tmp);
3150 } else {
3151 quote_fmt_scan_string(fmt);
3152 quote_fmt_parse();
3155 buf = quote_fmt_get_buffer();
3156 if (buf == NULL) {
3157 gint line = quote_fmt_get_line();
3158 alertpanel_error(err_msg, line);
3159 goto error;
3161 } else
3162 buf = "";
3164 prev_autowrap = compose->autowrap;
3165 compose->autowrap = FALSE;
3167 mark = gtk_text_buffer_get_insert(buffer);
3168 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3169 if (g_utf8_validate(buf, -1, NULL)) {
3170 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3171 } else {
3172 gchar *tmpout = NULL;
3173 tmpout = conv_codeset_strdup
3174 (buf, conv_get_locale_charset_str_no_utf8(),
3175 CS_INTERNAL);
3176 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3177 g_free(tmpout);
3178 tmpout = g_malloc(strlen(buf)*2+1);
3179 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3181 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3182 g_free(tmpout);
3185 cursor_pos = quote_fmt_get_cursor_pos();
3186 if (cursor_pos == -1)
3187 cursor_pos = gtk_text_iter_get_offset(&iter);
3188 compose->set_cursor_pos = cursor_pos;
3190 gtk_text_buffer_get_start_iter(buffer, &iter);
3191 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3192 gtk_text_buffer_place_cursor(buffer, &iter);
3194 compose->autowrap = prev_autowrap;
3195 if (compose->autowrap && rewrap)
3196 compose_wrap_all(compose);
3198 goto ok;
3200 error:
3201 buf = NULL;
3203 SIGNAL_UNBLOCK(buffer);
3205 procmsg_msginfo_free( &dummyinfo );
3207 return buf;
3210 /* if ml_post is of type addr@host and from is of type
3211 * addr-anything@host, return TRUE
3213 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3215 gchar *left_ml = NULL;
3216 gchar *right_ml = NULL;
3217 gchar *left_from = NULL;
3218 gchar *right_from = NULL;
3219 gboolean result = FALSE;
3221 if (!ml_post || !from)
3222 return FALSE;
3224 left_ml = g_strdup(ml_post);
3225 if (strstr(left_ml, "@")) {
3226 right_ml = strstr(left_ml, "@")+1;
3227 *(strstr(left_ml, "@")) = '\0';
3230 left_from = g_strdup(from);
3231 if (strstr(left_from, "@")) {
3232 right_from = strstr(left_from, "@")+1;
3233 *(strstr(left_from, "@")) = '\0';
3236 if (right_ml && right_from
3237 && !strncmp(left_from, left_ml, strlen(left_ml))
3238 && !strcmp(right_from, right_ml)) {
3239 result = TRUE;
3241 g_free(left_ml);
3242 g_free(left_from);
3244 return result;
3247 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3248 gboolean respect_default_to)
3250 if (!compose)
3251 return;
3252 if (!folder || !folder->prefs)
3253 return;
3255 if (respect_default_to && folder->prefs->enable_default_to) {
3256 compose_entry_append(compose, folder->prefs->default_to,
3257 COMPOSE_TO, PREF_FOLDER);
3258 compose_entry_indicate(compose, folder->prefs->default_to);
3260 if (folder->prefs->enable_default_cc) {
3261 compose_entry_append(compose, folder->prefs->default_cc,
3262 COMPOSE_CC, PREF_FOLDER);
3263 compose_entry_indicate(compose, folder->prefs->default_cc);
3265 if (folder->prefs->enable_default_bcc) {
3266 compose_entry_append(compose, folder->prefs->default_bcc,
3267 COMPOSE_BCC, PREF_FOLDER);
3268 compose_entry_indicate(compose, folder->prefs->default_bcc);
3270 if (folder->prefs->enable_default_replyto) {
3271 compose_entry_append(compose, folder->prefs->default_replyto,
3272 COMPOSE_REPLYTO, PREF_FOLDER);
3273 compose_entry_indicate(compose, folder->prefs->default_replyto);
3277 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3279 gchar *buf, *buf2;
3280 gchar *p;
3282 if (!compose || !msginfo)
3283 return;
3285 if (msginfo->subject && *msginfo->subject) {
3286 buf = p = g_strdup(msginfo->subject);
3287 p += subject_get_prefix_length(p);
3288 memmove(buf, p, strlen(p) + 1);
3290 buf2 = g_strdup_printf("Re: %s", buf);
3291 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3293 g_free(buf2);
3294 g_free(buf);
3295 } else
3296 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3299 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3300 gboolean to_all, gboolean to_ml,
3301 gboolean to_sender,
3302 gboolean followup_and_reply_to)
3304 GSList *cc_list = NULL;
3305 GSList *cur;
3306 gchar *from = NULL;
3307 gchar *replyto = NULL;
3308 gchar *ac_email = NULL;
3310 gboolean reply_to_ml = FALSE;
3311 gboolean default_reply_to = FALSE;
3313 cm_return_if_fail(compose->account != NULL);
3314 cm_return_if_fail(msginfo != NULL);
3316 reply_to_ml = to_ml && compose->ml_post;
3318 default_reply_to = msginfo->folder &&
3319 msginfo->folder->prefs->enable_default_reply_to;
3321 if (compose->account->protocol != A_NNTP) {
3322 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3324 if (reply_to_ml && !default_reply_to) {
3326 gboolean is_subscr = is_subscription(compose->ml_post,
3327 msginfo->from);
3328 if (!is_subscr) {
3329 /* normal answer to ml post with a reply-to */
3330 compose_entry_append(compose,
3331 compose->ml_post,
3332 COMPOSE_TO, PREF_ML);
3333 if (compose->replyto)
3334 compose_entry_append(compose,
3335 compose->replyto,
3336 COMPOSE_CC, PREF_ML);
3337 } else {
3338 /* answer to subscription confirmation */
3339 if (compose->replyto)
3340 compose_entry_append(compose,
3341 compose->replyto,
3342 COMPOSE_TO, PREF_ML);
3343 else if (msginfo->from)
3344 compose_entry_append(compose,
3345 msginfo->from,
3346 COMPOSE_TO, PREF_ML);
3349 else if (!(to_all || to_sender) && default_reply_to) {
3350 compose_entry_append(compose,
3351 msginfo->folder->prefs->default_reply_to,
3352 COMPOSE_TO, PREF_FOLDER);
3353 compose_entry_indicate(compose,
3354 msginfo->folder->prefs->default_reply_to);
3355 } else {
3356 gchar *tmp1 = NULL;
3357 if (!msginfo->from)
3358 return;
3359 if (to_sender)
3360 compose_entry_append(compose, msginfo->from,
3361 COMPOSE_TO, PREF_NONE);
3362 else if (to_all) {
3363 Xstrdup_a(tmp1, msginfo->from, return);
3364 extract_address(tmp1);
3365 compose_entry_append(compose,
3366 (!account_find_from_address(tmp1, FALSE))
3367 ? msginfo->from :
3368 msginfo->to,
3369 COMPOSE_TO, PREF_NONE);
3370 if (compose->replyto)
3371 compose_entry_append(compose,
3372 compose->replyto,
3373 COMPOSE_CC, PREF_NONE);
3374 } else {
3375 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3376 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3377 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3378 if (compose->replyto) {
3379 compose_entry_append(compose,
3380 compose->replyto,
3381 COMPOSE_TO, PREF_NONE);
3382 } else {
3383 compose_entry_append(compose,
3384 msginfo->from ? msginfo->from : "",
3385 COMPOSE_TO, PREF_NONE);
3387 } else {
3388 /* replying to own mail, use original recp */
3389 compose_entry_append(compose,
3390 msginfo->to ? msginfo->to : "",
3391 COMPOSE_TO, PREF_NONE);
3392 compose_entry_append(compose,
3393 msginfo->cc ? msginfo->cc : "",
3394 COMPOSE_CC, PREF_NONE);
3398 } else {
3399 if (to_sender || (compose->followup_to &&
3400 !strncmp(compose->followup_to, "poster", 6)))
3401 compose_entry_append
3402 (compose,
3403 (compose->replyto ? compose->replyto :
3404 msginfo->from ? msginfo->from : ""),
3405 COMPOSE_TO, PREF_NONE);
3407 else if (followup_and_reply_to || to_all) {
3408 compose_entry_append
3409 (compose,
3410 (compose->replyto ? compose->replyto :
3411 msginfo->from ? msginfo->from : ""),
3412 COMPOSE_TO, PREF_NONE);
3414 compose_entry_append
3415 (compose,
3416 compose->followup_to ? compose->followup_to :
3417 compose->newsgroups ? compose->newsgroups : "",
3418 COMPOSE_NEWSGROUPS, PREF_NONE);
3420 compose_entry_append
3421 (compose,
3422 msginfo->cc ? msginfo->cc : "",
3423 COMPOSE_CC, PREF_NONE);
3425 else
3426 compose_entry_append
3427 (compose,
3428 compose->followup_to ? compose->followup_to :
3429 compose->newsgroups ? compose->newsgroups : "",
3430 COMPOSE_NEWSGROUPS, PREF_NONE);
3432 compose_reply_set_subject(compose, msginfo);
3434 if (to_ml && compose->ml_post) return;
3435 if (!to_all || compose->account->protocol == A_NNTP) return;
3437 if (compose->replyto) {
3438 Xstrdup_a(replyto, compose->replyto, return);
3439 extract_address(replyto);
3441 if (msginfo->from) {
3442 Xstrdup_a(from, msginfo->from, return);
3443 extract_address(from);
3446 if (replyto && from)
3447 cc_list = address_list_append_with_comments(cc_list, from);
3448 if (to_all && msginfo->folder &&
3449 msginfo->folder->prefs->enable_default_reply_to)
3450 cc_list = address_list_append_with_comments(cc_list,
3451 msginfo->folder->prefs->default_reply_to);
3452 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3453 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3455 ac_email = g_utf8_strdown(compose->account->address, -1);
3457 if (cc_list) {
3458 for (cur = cc_list; cur != NULL; cur = cur->next) {
3459 gchar *addr = g_utf8_strdown(cur->data, -1);
3460 extract_address(addr);
3462 if (strcmp(ac_email, addr))
3463 compose_entry_append(compose, (gchar *)cur->data,
3464 COMPOSE_CC, PREF_NONE);
3465 else
3466 debug_print("Cc address same as compose account's, ignoring\n");
3468 g_free(addr);
3471 slist_free_strings_full(cc_list);
3474 g_free(ac_email);
3477 #define SET_ENTRY(entry, str) \
3479 if (str && *str) \
3480 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3483 #define SET_ADDRESS(type, str) \
3485 if (str && *str) \
3486 compose_entry_append(compose, str, type, PREF_NONE); \
3489 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3491 cm_return_if_fail(msginfo != NULL);
3493 SET_ENTRY(subject_entry, msginfo->subject);
3494 SET_ENTRY(from_name, msginfo->from);
3495 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3496 SET_ADDRESS(COMPOSE_CC, compose->cc);
3497 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3498 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3499 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3500 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3502 compose_update_priority_menu_item(compose);
3503 compose_update_privacy_system_menu_item(compose, FALSE);
3504 compose_show_first_last_header(compose, TRUE);
3507 #undef SET_ENTRY
3508 #undef SET_ADDRESS
3510 static void compose_insert_sig(Compose *compose, gboolean replace)
3512 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3513 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3514 GtkTextMark *mark;
3515 GtkTextIter iter, iter_end;
3516 gint cur_pos, ins_pos;
3517 gboolean prev_autowrap;
3518 gboolean found = FALSE;
3519 gboolean exists = FALSE;
3521 cm_return_if_fail(compose->account != NULL);
3523 BLOCK_WRAP();
3525 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3526 G_CALLBACK(compose_changed_cb),
3527 compose);
3529 mark = gtk_text_buffer_get_insert(buffer);
3530 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3531 cur_pos = gtk_text_iter_get_offset (&iter);
3532 ins_pos = cur_pos;
3534 gtk_text_buffer_get_end_iter(buffer, &iter);
3536 exists = (compose->sig_str != NULL);
3538 if (replace) {
3539 GtkTextIter first_iter, start_iter, end_iter;
3541 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3543 if (!exists || compose->sig_str[0] == '\0')
3544 found = FALSE;
3545 else
3546 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3547 compose->signature_tag);
3549 if (found) {
3550 /* include previous \n\n */
3551 gtk_text_iter_backward_chars(&first_iter, 1);
3552 start_iter = first_iter;
3553 end_iter = first_iter;
3554 /* skip re-start */
3555 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3556 compose->signature_tag);
3557 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3558 compose->signature_tag);
3559 if (found) {
3560 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3561 iter = start_iter;
3566 g_free(compose->sig_str);
3567 compose->sig_str = account_get_signature_str(compose->account);
3569 cur_pos = gtk_text_iter_get_offset(&iter);
3571 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3572 g_free(compose->sig_str);
3573 compose->sig_str = NULL;
3574 } else {
3575 if (compose->sig_inserted == FALSE)
3576 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3577 compose->sig_inserted = TRUE;
3579 cur_pos = gtk_text_iter_get_offset(&iter);
3580 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3581 /* remove \n\n */
3582 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3583 gtk_text_iter_forward_chars(&iter, 1);
3584 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3585 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3587 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3588 cur_pos = gtk_text_buffer_get_char_count (buffer);
3591 /* put the cursor where it should be
3592 * either where the quote_fmt says, either where it was */
3593 if (compose->set_cursor_pos < 0)
3594 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3595 else
3596 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3597 compose->set_cursor_pos);
3599 compose->set_cursor_pos = -1;
3600 gtk_text_buffer_place_cursor(buffer, &iter);
3601 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3602 G_CALLBACK(compose_changed_cb),
3603 compose);
3605 UNBLOCK_WRAP();
3608 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3610 GtkTextView *text;
3611 GtkTextBuffer *buffer;
3612 GtkTextMark *mark;
3613 GtkTextIter iter;
3614 const gchar *cur_encoding;
3615 gchar buf[BUFFSIZE];
3616 gint len;
3617 FILE *fp;
3618 gboolean prev_autowrap;
3619 GStatBuf file_stat;
3620 int ret;
3621 GString *file_contents = NULL;
3622 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3624 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3626 /* get the size of the file we are about to insert */
3627 ret = g_stat(file, &file_stat);
3628 if (ret != 0) {
3629 gchar *shortfile = g_path_get_basename(file);
3630 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3631 g_free(shortfile);
3632 return COMPOSE_INSERT_NO_FILE;
3633 } else if (prefs_common.warn_large_insert == TRUE) {
3635 /* ask user for confirmation if the file is large */
3636 if (prefs_common.warn_large_insert_size < 0 ||
3637 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3638 AlertValue aval;
3639 gchar *msg;
3641 msg = g_strdup_printf(_("You are about to insert a file of %s "
3642 "in the message body. Are you sure you want to do that?"),
3643 to_human_readable(file_stat.st_size));
3644 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3645 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3646 g_free(msg);
3648 /* do we ask for confirmation next time? */
3649 if (aval & G_ALERTDISABLE) {
3650 /* no confirmation next time, disable feature in preferences */
3651 aval &= ~G_ALERTDISABLE;
3652 prefs_common.warn_large_insert = FALSE;
3655 /* abort file insertion if user canceled action */
3656 if (aval != G_ALERTALTERNATE) {
3657 return COMPOSE_INSERT_NO_FILE;
3663 if ((fp = g_fopen(file, "rb")) == NULL) {
3664 FILE_OP_ERROR(file, "fopen");
3665 return COMPOSE_INSERT_READ_ERROR;
3668 prev_autowrap = compose->autowrap;
3669 compose->autowrap = FALSE;
3671 text = GTK_TEXT_VIEW(compose->text);
3672 buffer = gtk_text_view_get_buffer(text);
3673 mark = gtk_text_buffer_get_insert(buffer);
3674 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3676 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3677 G_CALLBACK(text_inserted),
3678 compose);
3680 cur_encoding = conv_get_locale_charset_str_no_utf8();
3682 file_contents = g_string_new("");
3683 while (fgets(buf, sizeof(buf), fp) != NULL) {
3684 gchar *str;
3686 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3687 str = g_strdup(buf);
3688 else {
3689 codeconv_set_strict(TRUE);
3690 str = conv_codeset_strdup
3691 (buf, cur_encoding, CS_INTERNAL);
3692 codeconv_set_strict(FALSE);
3694 if (!str) {
3695 result = COMPOSE_INSERT_INVALID_CHARACTER;
3696 break;
3699 if (!str) continue;
3701 /* strip <CR> if DOS/Windows file,
3702 replace <CR> with <LF> if Macintosh file. */
3703 strcrchomp(str);
3704 len = strlen(str);
3705 if (len > 0 && str[len - 1] != '\n') {
3706 while (--len >= 0)
3707 if (str[len] == '\r') str[len] = '\n';
3710 file_contents = g_string_append(file_contents, str);
3711 g_free(str);
3714 if (result == COMPOSE_INSERT_SUCCESS) {
3715 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3717 compose_changed_cb(NULL, compose);
3718 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3719 G_CALLBACK(text_inserted),
3720 compose);
3721 compose->autowrap = prev_autowrap;
3722 if (compose->autowrap)
3723 compose_wrap_all(compose);
3726 g_string_free(file_contents, TRUE);
3727 fclose(fp);
3729 return result;
3732 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3733 const gchar *filename,
3734 const gchar *content_type,
3735 const gchar *charset)
3737 AttachInfo *ainfo;
3738 GtkTreeIter iter;
3739 FILE *fp;
3740 off_t size;
3741 GAuto *auto_ainfo;
3742 gchar *size_text;
3743 GtkListStore *store;
3744 gchar *name;
3745 gboolean has_binary = FALSE;
3747 if (!is_file_exist(file)) {
3748 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3749 gboolean result = FALSE;
3750 if (file_from_uri && is_file_exist(file_from_uri)) {
3751 result = compose_attach_append(
3752 compose, file_from_uri,
3753 filename, content_type,
3754 charset);
3756 g_free(file_from_uri);
3757 if (result)
3758 return TRUE;
3759 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3760 return FALSE;
3762 if ((size = get_file_size(file)) < 0) {
3763 alertpanel_error("Can't get file size of %s\n", filename);
3764 return FALSE;
3767 /* In batch mode, we allow 0-length files to be attached no questions asked */
3768 if (size == 0 && !compose->batch) {
3769 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3770 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3771 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3772 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3773 g_free(msg);
3775 if (aval != G_ALERTALTERNATE) {
3776 return FALSE;
3779 if ((fp = g_fopen(file, "rb")) == NULL) {
3780 alertpanel_error(_("Can't read %s."), filename);
3781 return FALSE;
3783 fclose(fp);
3785 ainfo = g_new0(AttachInfo, 1);
3786 auto_ainfo = g_auto_pointer_new_with_free
3787 (ainfo, (GFreeFunc) compose_attach_info_free);
3788 ainfo->file = g_strdup(file);
3790 if (content_type) {
3791 ainfo->content_type = g_strdup(content_type);
3792 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3793 MsgInfo *msginfo;
3794 MsgFlags flags = {0, 0};
3796 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3797 ainfo->encoding = ENC_7BIT;
3798 else
3799 ainfo->encoding = ENC_8BIT;
3801 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3802 if (msginfo && msginfo->subject)
3803 name = g_strdup(msginfo->subject);
3804 else
3805 name = g_path_get_basename(filename ? filename : file);
3807 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3809 procmsg_msginfo_free(&msginfo);
3810 } else {
3811 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3812 ainfo->charset = g_strdup(charset);
3813 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3814 } else {
3815 ainfo->encoding = ENC_BASE64;
3817 name = g_path_get_basename(filename ? filename : file);
3818 ainfo->name = g_strdup(name);
3820 g_free(name);
3821 } else {
3822 ainfo->content_type = procmime_get_mime_type(file);
3823 if (!ainfo->content_type) {
3824 ainfo->content_type =
3825 g_strdup("application/octet-stream");
3826 ainfo->encoding = ENC_BASE64;
3827 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3828 ainfo->encoding =
3829 procmime_get_encoding_for_text_file(file, &has_binary);
3830 else
3831 ainfo->encoding = ENC_BASE64;
3832 name = g_path_get_basename(filename ? filename : file);
3833 ainfo->name = g_strdup(name);
3834 g_free(name);
3837 if (ainfo->name != NULL
3838 && !strcmp(ainfo->name, ".")) {
3839 g_free(ainfo->name);
3840 ainfo->name = NULL;
3843 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3844 g_free(ainfo->content_type);
3845 ainfo->content_type = g_strdup("application/octet-stream");
3846 g_free(ainfo->charset);
3847 ainfo->charset = NULL;
3850 ainfo->size = (goffset)size;
3851 size_text = to_human_readable((goffset)size);
3853 store = GTK_LIST_STORE(gtk_tree_view_get_model
3854 (GTK_TREE_VIEW(compose->attach_clist)));
3856 gtk_list_store_append(store, &iter);
3857 gtk_list_store_set(store, &iter,
3858 COL_MIMETYPE, ainfo->content_type,
3859 COL_SIZE, size_text,
3860 COL_NAME, ainfo->name,
3861 COL_CHARSET, ainfo->charset,
3862 COL_DATA, ainfo,
3863 COL_AUTODATA, auto_ainfo,
3864 -1);
3866 g_auto_pointer_free(auto_ainfo);
3867 compose_attach_update_label(compose);
3868 return TRUE;
3871 static void compose_use_signing(Compose *compose, gboolean use_signing)
3873 compose->use_signing = use_signing;
3874 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3877 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3879 compose->use_encryption = use_encryption;
3880 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3883 #define NEXT_PART_NOT_CHILD(info) \
3885 node = info->node; \
3886 while (node->children) \
3887 node = g_node_last_child(node); \
3888 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3891 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3893 MimeInfo *mimeinfo;
3894 MimeInfo *child;
3895 MimeInfo *firsttext = NULL;
3896 MimeInfo *encrypted = NULL;
3897 GNode *node;
3898 gchar *outfile;
3899 const gchar *partname = NULL;
3901 mimeinfo = procmime_scan_message(msginfo);
3902 if (!mimeinfo) return;
3904 if (mimeinfo->node->children == NULL) {
3905 procmime_mimeinfo_free_all(&mimeinfo);
3906 return;
3909 /* find first content part */
3910 child = (MimeInfo *) mimeinfo->node->children->data;
3911 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3912 child = (MimeInfo *)child->node->children->data;
3914 if (child) {
3915 if (child->type == MIMETYPE_TEXT) {
3916 firsttext = child;
3917 debug_print("First text part found\n");
3918 } else if (compose->mode == COMPOSE_REEDIT &&
3919 child->type == MIMETYPE_APPLICATION &&
3920 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3921 encrypted = (MimeInfo *)child->node->parent->data;
3924 child = (MimeInfo *) mimeinfo->node->children->data;
3925 while (child != NULL) {
3926 gint err;
3928 if (child == encrypted) {
3929 /* skip this part of tree */
3930 NEXT_PART_NOT_CHILD(child);
3931 continue;
3934 if (child->type == MIMETYPE_MULTIPART) {
3935 /* get the actual content */
3936 child = procmime_mimeinfo_next(child);
3937 continue;
3940 if (child == firsttext) {
3941 child = procmime_mimeinfo_next(child);
3942 continue;
3945 outfile = procmime_get_tmp_file_name(child);
3946 if ((err = procmime_get_part(outfile, child)) < 0)
3947 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3948 else {
3949 gchar *content_type;
3951 content_type = procmime_get_content_type_str(child->type, child->subtype);
3953 /* if we meet a pgp signature, we don't attach it, but
3954 * we force signing. */
3955 if ((strcmp(content_type, "application/pgp-signature") &&
3956 strcmp(content_type, "application/pkcs7-signature") &&
3957 strcmp(content_type, "application/x-pkcs7-signature"))
3958 || compose->mode == COMPOSE_REDIRECT) {
3959 partname = procmime_mimeinfo_get_parameter(child, "filename");
3960 if (partname == NULL)
3961 partname = procmime_mimeinfo_get_parameter(child, "name");
3962 if (partname == NULL)
3963 partname = "";
3964 compose_attach_append(compose, outfile,
3965 partname, content_type,
3966 procmime_mimeinfo_get_parameter(child, "charset"));
3967 } else {
3968 compose_force_signing(compose, compose->account, NULL);
3970 g_free(content_type);
3972 g_free(outfile);
3973 NEXT_PART_NOT_CHILD(child);
3975 procmime_mimeinfo_free_all(&mimeinfo);
3978 #undef NEXT_PART_NOT_CHILD
3982 typedef enum {
3983 WAIT_FOR_INDENT_CHAR,
3984 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3985 } IndentState;
3987 /* return indent length, we allow:
3988 indent characters followed by indent characters or spaces/tabs,
3989 alphabets and numbers immediately followed by indent characters,
3990 and the repeating sequences of the above
3991 If quote ends with multiple spaces, only the first one is included. */
3992 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3993 const GtkTextIter *start, gint *len)
3995 GtkTextIter iter = *start;
3996 gunichar wc;
3997 gchar ch[6];
3998 gint clen;
3999 IndentState state = WAIT_FOR_INDENT_CHAR;
4000 gboolean is_space;
4001 gboolean is_indent;
4002 gint alnum_count = 0;
4003 gint space_count = 0;
4004 gint quote_len = 0;
4006 if (prefs_common.quote_chars == NULL) {
4007 return 0 ;
4010 while (!gtk_text_iter_ends_line(&iter)) {
4011 wc = gtk_text_iter_get_char(&iter);
4012 if (g_unichar_iswide(wc))
4013 break;
4014 clen = g_unichar_to_utf8(wc, ch);
4015 if (clen != 1)
4016 break;
4018 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4019 is_space = g_unichar_isspace(wc);
4021 if (state == WAIT_FOR_INDENT_CHAR) {
4022 if (!is_indent && !g_unichar_isalnum(wc))
4023 break;
4024 if (is_indent) {
4025 quote_len += alnum_count + space_count + 1;
4026 alnum_count = space_count = 0;
4027 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4028 } else
4029 alnum_count++;
4030 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4031 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4032 break;
4033 if (is_space)
4034 space_count++;
4035 else if (is_indent) {
4036 quote_len += alnum_count + space_count + 1;
4037 alnum_count = space_count = 0;
4038 } else {
4039 alnum_count++;
4040 state = WAIT_FOR_INDENT_CHAR;
4044 gtk_text_iter_forward_char(&iter);
4047 if (quote_len > 0 && space_count > 0)
4048 quote_len++;
4050 if (len)
4051 *len = quote_len;
4053 if (quote_len > 0) {
4054 iter = *start;
4055 gtk_text_iter_forward_chars(&iter, quote_len);
4056 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4059 return NULL;
4062 /* return >0 if the line is itemized */
4063 static int compose_itemized_length(GtkTextBuffer *buffer,
4064 const GtkTextIter *start)
4066 GtkTextIter iter = *start;
4067 gunichar wc;
4068 gchar ch[6];
4069 gint clen;
4070 gint len = 0;
4071 if (gtk_text_iter_ends_line(&iter))
4072 return 0;
4074 while (1) {
4075 len++;
4076 wc = gtk_text_iter_get_char(&iter);
4077 if (!g_unichar_isspace(wc))
4078 break;
4079 gtk_text_iter_forward_char(&iter);
4080 if (gtk_text_iter_ends_line(&iter))
4081 return 0;
4084 clen = g_unichar_to_utf8(wc, ch);
4085 if (clen != 1)
4086 return 0;
4088 if (!strchr("*-+", ch[0]))
4089 return 0;
4091 gtk_text_iter_forward_char(&iter);
4092 if (gtk_text_iter_ends_line(&iter))
4093 return 0;
4094 wc = gtk_text_iter_get_char(&iter);
4095 if (g_unichar_isspace(wc)) {
4096 return len+1;
4098 return 0;
4101 /* return the string at the start of the itemization */
4102 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4103 const GtkTextIter *start)
4105 GtkTextIter iter = *start;
4106 gunichar wc;
4107 gint len = 0;
4108 GString *item_chars = g_string_new("");
4109 gchar *str = NULL;
4111 if (gtk_text_iter_ends_line(&iter))
4112 return NULL;
4114 while (1) {
4115 len++;
4116 wc = gtk_text_iter_get_char(&iter);
4117 if (!g_unichar_isspace(wc))
4118 break;
4119 gtk_text_iter_forward_char(&iter);
4120 if (gtk_text_iter_ends_line(&iter))
4121 break;
4122 g_string_append_unichar(item_chars, wc);
4125 str = item_chars->str;
4126 g_string_free(item_chars, FALSE);
4127 return str;
4130 /* return the number of spaces at a line's start */
4131 static int compose_left_offset_length(GtkTextBuffer *buffer,
4132 const GtkTextIter *start)
4134 GtkTextIter iter = *start;
4135 gunichar wc;
4136 gint len = 0;
4137 if (gtk_text_iter_ends_line(&iter))
4138 return 0;
4140 while (1) {
4141 wc = gtk_text_iter_get_char(&iter);
4142 if (!g_unichar_isspace(wc))
4143 break;
4144 len++;
4145 gtk_text_iter_forward_char(&iter);
4146 if (gtk_text_iter_ends_line(&iter))
4147 return 0;
4150 gtk_text_iter_forward_char(&iter);
4151 if (gtk_text_iter_ends_line(&iter))
4152 return 0;
4153 return len;
4156 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4157 const GtkTextIter *start,
4158 GtkTextIter *break_pos,
4159 gint max_col,
4160 gint quote_len)
4162 GtkTextIter iter = *start, line_end = *start;
4163 PangoLogAttr *attrs;
4164 gchar *str;
4165 gchar *p;
4166 gint len;
4167 gint i;
4168 gint col = 0;
4169 gint pos = 0;
4170 gboolean can_break = FALSE;
4171 gboolean do_break = FALSE;
4172 gboolean was_white = FALSE;
4173 gboolean prev_dont_break = FALSE;
4175 gtk_text_iter_forward_to_line_end(&line_end);
4176 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4177 len = g_utf8_strlen(str, -1);
4179 if (len == 0) {
4180 g_free(str);
4181 g_warning("compose_get_line_break_pos: len = 0!");
4182 return FALSE;
4185 /* g_print("breaking line: %d: %s (len = %d)\n",
4186 gtk_text_iter_get_line(&iter), str, len); */
4188 attrs = g_new(PangoLogAttr, len + 1);
4190 pango_default_break(str, -1, NULL, attrs, len + 1);
4192 p = str;
4194 /* skip quote and leading spaces */
4195 for (i = 0; *p != '\0' && i < len; i++) {
4196 gunichar wc;
4198 wc = g_utf8_get_char(p);
4199 if (i >= quote_len && !g_unichar_isspace(wc))
4200 break;
4201 if (g_unichar_iswide(wc))
4202 col += 2;
4203 else if (*p == '\t')
4204 col += 8;
4205 else
4206 col++;
4207 p = g_utf8_next_char(p);
4210 for (; *p != '\0' && i < len; i++) {
4211 PangoLogAttr *attr = attrs + i;
4212 gunichar wc;
4213 gint uri_len;
4215 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4216 pos = i;
4218 was_white = attr->is_white;
4220 /* don't wrap URI */
4221 if ((uri_len = get_uri_len(p)) > 0) {
4222 col += uri_len;
4223 if (pos > 0 && col > max_col) {
4224 do_break = TRUE;
4225 break;
4227 i += uri_len - 1;
4228 p += uri_len;
4229 can_break = TRUE;
4230 continue;
4233 wc = g_utf8_get_char(p);
4234 if (g_unichar_iswide(wc)) {
4235 col += 2;
4236 if (prev_dont_break && can_break && attr->is_line_break)
4237 pos = i;
4238 } else if (*p == '\t')
4239 col += 8;
4240 else
4241 col++;
4242 if (pos > 0 && col > max_col) {
4243 do_break = TRUE;
4244 break;
4247 if (*p == '-' || *p == '/')
4248 prev_dont_break = TRUE;
4249 else
4250 prev_dont_break = FALSE;
4252 p = g_utf8_next_char(p);
4253 can_break = TRUE;
4256 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4258 g_free(attrs);
4259 g_free(str);
4261 *break_pos = *start;
4262 gtk_text_iter_set_line_offset(break_pos, pos);
4264 return do_break;
4267 static gboolean compose_join_next_line(Compose *compose,
4268 GtkTextBuffer *buffer,
4269 GtkTextIter *iter,
4270 const gchar *quote_str)
4272 GtkTextIter iter_ = *iter, cur, prev, next, end;
4273 PangoLogAttr attrs[3];
4274 gchar *str;
4275 gchar *next_quote_str;
4276 gunichar wc1, wc2;
4277 gint quote_len;
4278 gboolean keep_cursor = FALSE;
4280 if (!gtk_text_iter_forward_line(&iter_) ||
4281 gtk_text_iter_ends_line(&iter_)) {
4282 return FALSE;
4284 next_quote_str = compose_get_quote_str(buffer, &iter_, &quote_len);
4286 if ((quote_str || next_quote_str) &&
4287 strcmp2(quote_str, next_quote_str) != 0) {
4288 g_free(next_quote_str);
4289 return FALSE;
4291 g_free(next_quote_str);
4293 end = iter_;
4294 if (quote_len > 0) {
4295 gtk_text_iter_forward_chars(&end, quote_len);
4296 if (gtk_text_iter_ends_line(&end)) {
4297 return FALSE;
4301 /* don't join itemized lines */
4302 if (compose_itemized_length(buffer, &end) > 0) {
4303 return FALSE;
4306 /* don't join signature separator */
4307 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4308 return FALSE;
4310 /* delete quote str */
4311 if (quote_len > 0)
4312 gtk_text_buffer_delete(buffer, &iter_, &end);
4314 /* don't join line breaks put by the user */
4315 prev = cur = iter_;
4316 gtk_text_iter_backward_char(&cur);
4317 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4318 gtk_text_iter_forward_char(&cur);
4319 *iter = cur;
4320 return FALSE;
4322 gtk_text_iter_forward_char(&cur);
4323 /* delete linebreak and extra spaces */
4324 while (gtk_text_iter_backward_char(&cur)) {
4325 wc1 = gtk_text_iter_get_char(&cur);
4326 if (!g_unichar_isspace(wc1))
4327 break;
4328 prev = cur;
4330 next = cur = iter_;
4331 while (!gtk_text_iter_ends_line(&cur)) {
4332 wc1 = gtk_text_iter_get_char(&cur);
4333 if (!g_unichar_isspace(wc1))
4334 break;
4335 gtk_text_iter_forward_char(&cur);
4336 next = cur;
4338 if (!gtk_text_iter_equal(&prev, &next)) {
4339 GtkTextMark *mark;
4341 mark = gtk_text_buffer_get_insert(buffer);
4342 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4343 if (gtk_text_iter_equal(&prev, &cur))
4344 keep_cursor = TRUE;
4345 gtk_text_buffer_delete(buffer, &prev, &next);
4347 iter_ = prev;
4349 /* insert space if required */
4350 gtk_text_iter_backward_char(&prev);
4351 wc1 = gtk_text_iter_get_char(&prev);
4352 wc2 = gtk_text_iter_get_char(&next);
4353 gtk_text_iter_forward_char(&next);
4354 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4355 pango_default_break(str, -1, NULL, attrs, 3);
4356 if (!attrs[1].is_line_break ||
4357 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4358 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4359 if (keep_cursor) {
4360 gtk_text_iter_backward_char(&iter_);
4361 gtk_text_buffer_place_cursor(buffer, &iter_);
4364 g_free(str);
4366 *iter = iter_;
4367 return TRUE;
4370 #define ADD_TXT_POS(bp_, ep_, pti_) \
4371 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4372 last = last->next; \
4373 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4374 last->next = NULL; \
4375 } else { \
4376 g_warning("alloc error scanning URIs"); \
4379 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4381 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4382 GtkTextBuffer *buffer;
4383 GtkTextIter iter, break_pos, end_of_line;
4384 gchar *quote_str = NULL;
4385 gint quote_len;
4386 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4387 gboolean prev_autowrap = compose->autowrap;
4388 gint startq_offset = -1, noq_offset = -1;
4389 gint uri_start = -1, uri_stop = -1;
4390 gint nouri_start = -1, nouri_stop = -1;
4391 gint num_blocks = 0;
4392 gint quotelevel = -1;
4393 gboolean modified = force;
4394 gboolean removed = FALSE;
4395 gboolean modified_before_remove = FALSE;
4396 gint lines = 0;
4397 gboolean start = TRUE;
4398 gint itemized_len = 0, rem_item_len = 0;
4399 gchar *itemized_chars = NULL;
4400 gboolean item_continuation = FALSE;
4402 if (force) {
4403 modified = TRUE;
4405 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4406 modified = TRUE;
4409 compose->autowrap = FALSE;
4411 buffer = gtk_text_view_get_buffer(text);
4412 undo_wrapping(compose->undostruct, TRUE);
4413 if (par_iter) {
4414 iter = *par_iter;
4415 } else {
4416 GtkTextMark *mark;
4417 mark = gtk_text_buffer_get_insert(buffer);
4418 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4422 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4423 if (gtk_text_iter_ends_line(&iter)) {
4424 while (gtk_text_iter_ends_line(&iter) &&
4425 gtk_text_iter_forward_line(&iter))
4427 } else {
4428 while (gtk_text_iter_backward_line(&iter)) {
4429 if (gtk_text_iter_ends_line(&iter)) {
4430 gtk_text_iter_forward_line(&iter);
4431 break;
4435 } else {
4436 /* move to line start */
4437 gtk_text_iter_set_line_offset(&iter, 0);
4440 itemized_len = compose_itemized_length(buffer, &iter);
4442 if (!itemized_len) {
4443 itemized_len = compose_left_offset_length(buffer, &iter);
4444 item_continuation = TRUE;
4447 if (itemized_len)
4448 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4450 /* go until paragraph end (empty line) */
4451 while (start || !gtk_text_iter_ends_line(&iter)) {
4452 gchar *scanpos = NULL;
4453 /* parse table - in order of priority */
4454 struct table {
4455 const gchar *needle; /* token */
4457 /* token search function */
4458 gchar *(*search) (const gchar *haystack,
4459 const gchar *needle);
4460 /* part parsing function */
4461 gboolean (*parse) (const gchar *start,
4462 const gchar *scanpos,
4463 const gchar **bp_,
4464 const gchar **ep_,
4465 gboolean hdr);
4466 /* part to URI function */
4467 gchar *(*build_uri) (const gchar *bp,
4468 const gchar *ep);
4471 static struct table parser[] = {
4472 {"http://", strcasestr, get_uri_part, make_uri_string},
4473 {"https://", strcasestr, get_uri_part, make_uri_string},
4474 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4475 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4476 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4477 {"www.", strcasestr, get_uri_part, make_http_string},
4478 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4479 {"@", strcasestr, get_email_part, make_email_string}
4481 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4482 gint last_index = PARSE_ELEMS;
4483 gint n;
4484 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4485 gint walk_pos;
4487 start = FALSE;
4488 if (!prev_autowrap && num_blocks == 0) {
4489 num_blocks++;
4490 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4491 G_CALLBACK(text_inserted),
4492 compose);
4494 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4495 goto colorize;
4497 uri_start = uri_stop = -1;
4498 quote_len = 0;
4499 quote_str = compose_get_quote_str(buffer, &iter, &quote_len);
4501 if (quote_str) {
4502 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4503 if (startq_offset == -1)
4504 startq_offset = gtk_text_iter_get_offset(&iter);
4505 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4506 if (quotelevel > 2) {
4507 /* recycle colors */
4508 if (prefs_common.recycle_quote_colors)
4509 quotelevel %= 3;
4510 else
4511 quotelevel = 2;
4513 if (!wrap_quote) {
4514 goto colorize;
4516 } else {
4517 if (startq_offset == -1)
4518 noq_offset = gtk_text_iter_get_offset(&iter);
4519 quotelevel = -1;
4522 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4523 goto colorize;
4525 if (gtk_text_iter_ends_line(&iter)) {
4526 goto colorize;
4527 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4528 prefs_common.linewrap_len,
4529 quote_len)) {
4530 GtkTextIter prev, next, cur;
4531 if (prev_autowrap != FALSE || force) {
4532 compose->automatic_break = TRUE;
4533 modified = TRUE;
4534 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4535 compose->automatic_break = FALSE;
4536 if (itemized_len && compose->autoindent) {
4537 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4538 if (!item_continuation)
4539 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4541 } else if (quote_str && wrap_quote) {
4542 compose->automatic_break = TRUE;
4543 modified = TRUE;
4544 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4545 compose->automatic_break = FALSE;
4546 if (itemized_len && compose->autoindent) {
4547 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4548 if (!item_continuation)
4549 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4551 } else
4552 goto colorize;
4553 /* remove trailing spaces */
4554 cur = break_pos;
4555 rem_item_len = itemized_len;
4556 while (compose->autoindent && rem_item_len-- > 0)
4557 gtk_text_iter_backward_char(&cur);
4558 gtk_text_iter_backward_char(&cur);
4560 prev = next = cur;
4561 while (!gtk_text_iter_starts_line(&cur)) {
4562 gunichar wc;
4564 gtk_text_iter_backward_char(&cur);
4565 wc = gtk_text_iter_get_char(&cur);
4566 if (!g_unichar_isspace(wc))
4567 break;
4568 prev = cur;
4570 if (!gtk_text_iter_equal(&prev, &next)) {
4571 gtk_text_buffer_delete(buffer, &prev, &next);
4572 break_pos = next;
4573 gtk_text_iter_forward_char(&break_pos);
4576 if (quote_str)
4577 gtk_text_buffer_insert(buffer, &break_pos,
4578 quote_str, -1);
4580 iter = break_pos;
4581 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4583 /* move iter to current line start */
4584 gtk_text_iter_set_line_offset(&iter, 0);
4585 if (quote_str) {
4586 g_free(quote_str);
4587 quote_str = NULL;
4589 continue;
4590 } else {
4591 /* move iter to next line start */
4592 iter = break_pos;
4593 lines++;
4596 colorize:
4597 if (!prev_autowrap && num_blocks > 0) {
4598 num_blocks--;
4599 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4600 G_CALLBACK(text_inserted),
4601 compose);
4603 end_of_line = iter;
4604 while (!gtk_text_iter_ends_line(&end_of_line)) {
4605 gtk_text_iter_forward_char(&end_of_line);
4607 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4609 nouri_start = gtk_text_iter_get_offset(&iter);
4610 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4612 walk_pos = gtk_text_iter_get_offset(&iter);
4613 /* FIXME: this looks phony. scanning for anything in the parse table */
4614 for (n = 0; n < PARSE_ELEMS; n++) {
4615 gchar *tmp;
4617 tmp = parser[n].search(walk, parser[n].needle);
4618 if (tmp) {
4619 if (scanpos == NULL || tmp < scanpos) {
4620 scanpos = tmp;
4621 last_index = n;
4626 bp = ep = 0;
4627 if (scanpos) {
4628 /* check if URI can be parsed */
4629 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4630 (const gchar **)&ep, FALSE)
4631 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4632 walk = ep;
4633 } else
4634 walk = scanpos +
4635 strlen(parser[last_index].needle);
4637 if (bp && ep) {
4638 uri_start = walk_pos + (bp - o_walk);
4639 uri_stop = walk_pos + (ep - o_walk);
4641 g_free(o_walk);
4642 o_walk = NULL;
4643 gtk_text_iter_forward_line(&iter);
4644 g_free(quote_str);
4645 quote_str = NULL;
4646 if (startq_offset != -1) {
4647 GtkTextIter startquote, endquote;
4648 gtk_text_buffer_get_iter_at_offset(
4649 buffer, &startquote, startq_offset);
4650 endquote = iter;
4652 switch (quotelevel) {
4653 case 0:
4654 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4655 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4656 gtk_text_buffer_apply_tag_by_name(
4657 buffer, "quote0", &startquote, &endquote);
4658 gtk_text_buffer_remove_tag_by_name(
4659 buffer, "quote1", &startquote, &endquote);
4660 gtk_text_buffer_remove_tag_by_name(
4661 buffer, "quote2", &startquote, &endquote);
4662 modified = TRUE;
4664 break;
4665 case 1:
4666 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4667 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4668 gtk_text_buffer_apply_tag_by_name(
4669 buffer, "quote1", &startquote, &endquote);
4670 gtk_text_buffer_remove_tag_by_name(
4671 buffer, "quote0", &startquote, &endquote);
4672 gtk_text_buffer_remove_tag_by_name(
4673 buffer, "quote2", &startquote, &endquote);
4674 modified = TRUE;
4676 break;
4677 case 2:
4678 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4679 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4680 gtk_text_buffer_apply_tag_by_name(
4681 buffer, "quote2", &startquote, &endquote);
4682 gtk_text_buffer_remove_tag_by_name(
4683 buffer, "quote0", &startquote, &endquote);
4684 gtk_text_buffer_remove_tag_by_name(
4685 buffer, "quote1", &startquote, &endquote);
4686 modified = TRUE;
4688 break;
4690 startq_offset = -1;
4691 } else if (noq_offset != -1) {
4692 GtkTextIter startnoquote, endnoquote;
4693 gtk_text_buffer_get_iter_at_offset(
4694 buffer, &startnoquote, noq_offset);
4695 endnoquote = iter;
4697 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4698 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4699 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4700 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4701 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4702 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4703 gtk_text_buffer_remove_tag_by_name(
4704 buffer, "quote0", &startnoquote, &endnoquote);
4705 gtk_text_buffer_remove_tag_by_name(
4706 buffer, "quote1", &startnoquote, &endnoquote);
4707 gtk_text_buffer_remove_tag_by_name(
4708 buffer, "quote2", &startnoquote, &endnoquote);
4709 modified = TRUE;
4711 noq_offset = -1;
4714 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4715 GtkTextIter nouri_start_iter, nouri_end_iter;
4716 gtk_text_buffer_get_iter_at_offset(
4717 buffer, &nouri_start_iter, nouri_start);
4718 gtk_text_buffer_get_iter_at_offset(
4719 buffer, &nouri_end_iter, nouri_stop);
4720 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4721 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4722 gtk_text_buffer_remove_tag_by_name(
4723 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4724 modified_before_remove = modified;
4725 modified = TRUE;
4726 removed = TRUE;
4729 if (uri_start >= 0 && uri_stop > 0) {
4730 GtkTextIter uri_start_iter, uri_end_iter, back;
4731 gtk_text_buffer_get_iter_at_offset(
4732 buffer, &uri_start_iter, uri_start);
4733 gtk_text_buffer_get_iter_at_offset(
4734 buffer, &uri_end_iter, uri_stop);
4735 back = uri_end_iter;
4736 gtk_text_iter_backward_char(&back);
4737 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4738 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4739 gtk_text_buffer_apply_tag_by_name(
4740 buffer, "link", &uri_start_iter, &uri_end_iter);
4741 modified = TRUE;
4742 if (removed && !modified_before_remove) {
4743 modified = FALSE;
4747 if (!modified) {
4748 /* debug_print("not modified, out after %d lines\n", lines); */
4749 goto end;
4752 /* debug_print("modified, out after %d lines\n", lines); */
4753 end:
4754 g_free(itemized_chars);
4755 if (par_iter)
4756 *par_iter = iter;
4757 undo_wrapping(compose->undostruct, FALSE);
4758 compose->autowrap = prev_autowrap;
4760 return modified;
4763 void compose_action_cb(void *data)
4765 Compose *compose = (Compose *)data;
4766 compose_wrap_all(compose);
4769 static void compose_wrap_all(Compose *compose)
4771 compose_wrap_all_full(compose, FALSE);
4774 static void compose_wrap_all_full(Compose *compose, gboolean force)
4776 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4777 GtkTextBuffer *buffer;
4778 GtkTextIter iter;
4779 gboolean modified = TRUE;
4781 buffer = gtk_text_view_get_buffer(text);
4783 gtk_text_buffer_get_start_iter(buffer, &iter);
4785 undo_wrapping(compose->undostruct, TRUE);
4787 while (!gtk_text_iter_is_end(&iter) && modified)
4788 modified = compose_beautify_paragraph(compose, &iter, force);
4790 undo_wrapping(compose->undostruct, FALSE);
4794 static void compose_set_title(Compose *compose)
4796 gchar *str;
4797 gchar *edited;
4798 gchar *subject;
4800 edited = compose->modified ? _(" [Edited]") : "";
4802 subject = gtk_editable_get_chars(
4803 GTK_EDITABLE(compose->subject_entry), 0, -1);
4805 #ifndef GENERIC_UMPC
4806 if (subject && strlen(subject))
4807 str = g_strdup_printf(_("%s - Compose message%s"),
4808 subject, edited);
4809 else
4810 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4811 #else
4812 str = g_strdup(_("Compose message"));
4813 #endif
4815 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4816 g_free(str);
4817 g_free(subject);
4821 * compose_current_mail_account:
4823 * Find a current mail account (the currently selected account, or the
4824 * default account, if a news account is currently selected). If a
4825 * mail account cannot be found, display an error message.
4827 * Return value: Mail account, or NULL if not found.
4829 static PrefsAccount *
4830 compose_current_mail_account(void)
4832 PrefsAccount *ac;
4834 if (cur_account && cur_account->protocol != A_NNTP)
4835 ac = cur_account;
4836 else {
4837 ac = account_get_default();
4838 if (!ac || ac->protocol == A_NNTP) {
4839 alertpanel_error(_("Account for sending mail is not specified.\n"
4840 "Please select a mail account before sending."));
4841 return NULL;
4844 return ac;
4847 #define QUOTE_IF_REQUIRED(out, str) \
4849 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4850 gchar *__tmp; \
4851 gint len; \
4853 len = strlen(str) + 3; \
4854 if ((__tmp = alloca(len)) == NULL) { \
4855 g_warning("can't allocate memory"); \
4856 g_string_free(header, TRUE); \
4857 return NULL; \
4859 g_snprintf(__tmp, len, "\"%s\"", str); \
4860 out = __tmp; \
4861 } else { \
4862 gchar *__tmp; \
4864 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4865 g_warning("can't allocate memory"); \
4866 g_string_free(header, TRUE); \
4867 return NULL; \
4868 } else \
4869 strcpy(__tmp, str); \
4871 out = __tmp; \
4875 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4877 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4878 gchar *__tmp; \
4879 gint len; \
4881 len = strlen(str) + 3; \
4882 if ((__tmp = alloca(len)) == NULL) { \
4883 g_warning("can't allocate memory"); \
4884 errret; \
4886 g_snprintf(__tmp, len, "\"%s\"", str); \
4887 out = __tmp; \
4888 } else { \
4889 gchar *__tmp; \
4891 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4892 g_warning("can't allocate memory"); \
4893 errret; \
4894 } else \
4895 strcpy(__tmp, str); \
4897 out = __tmp; \
4901 static void compose_select_account(Compose *compose, PrefsAccount *account,
4902 gboolean init)
4904 gchar *from = NULL, *header = NULL;
4905 ComposeHeaderEntry *header_entry;
4906 #if GTK_CHECK_VERSION(2, 24, 0)
4907 GtkTreeIter iter;
4908 #endif
4910 cm_return_if_fail(account != NULL);
4912 compose->account = account;
4913 if (account->name && *account->name) {
4914 gchar *buf, *qbuf;
4915 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4916 qbuf = escape_internal_quotes(buf, '"');
4917 from = g_strdup_printf("%s <%s>",
4918 qbuf, account->address);
4919 if (qbuf != buf)
4920 g_free(qbuf);
4921 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4922 } else {
4923 from = g_strdup_printf("<%s>",
4924 account->address);
4925 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4928 g_free(from);
4930 compose_set_title(compose);
4932 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4933 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4934 else
4935 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4936 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4937 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4938 else
4939 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4941 activate_privacy_system(compose, account, FALSE);
4943 if (!init && compose->mode != COMPOSE_REDIRECT) {
4944 undo_block(compose->undostruct);
4945 compose_insert_sig(compose, TRUE);
4946 undo_unblock(compose->undostruct);
4949 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4950 #if !GTK_CHECK_VERSION(2, 24, 0)
4951 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4952 #else
4953 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4954 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4955 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4956 #endif
4958 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4959 if (account->protocol == A_NNTP) {
4960 if (!strcmp(header, _("To:")))
4961 combobox_select_by_text(
4962 GTK_COMBO_BOX(header_entry->combo),
4963 _("Newsgroups:"));
4964 } else {
4965 if (!strcmp(header, _("Newsgroups:")))
4966 combobox_select_by_text(
4967 GTK_COMBO_BOX(header_entry->combo),
4968 _("To:"));
4972 g_free(header);
4974 #ifdef USE_ENCHANT
4975 /* use account's dict info if set */
4976 if (compose->gtkaspell) {
4977 if (account->enable_default_dictionary)
4978 gtkaspell_change_dict(compose->gtkaspell,
4979 account->default_dictionary, FALSE);
4980 if (account->enable_default_alt_dictionary)
4981 gtkaspell_change_alt_dict(compose->gtkaspell,
4982 account->default_alt_dictionary);
4983 if (account->enable_default_dictionary
4984 || account->enable_default_alt_dictionary)
4985 compose_spell_menu_changed(compose);
4987 #endif
4990 gboolean compose_check_for_valid_recipient(Compose *compose) {
4991 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4992 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4993 gboolean recipient_found = FALSE;
4994 GSList *list;
4995 gchar **strptr;
4997 /* free to and newsgroup list */
4998 slist_free_strings_full(compose->to_list);
4999 compose->to_list = NULL;
5001 slist_free_strings_full(compose->newsgroup_list);
5002 compose->newsgroup_list = NULL;
5004 /* search header entries for to and newsgroup entries */
5005 for (list = compose->header_list; list; list = list->next) {
5006 gchar *header;
5007 gchar *entry;
5008 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5009 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5010 g_strstrip(entry);
5011 g_strstrip(header);
5012 if (entry[0] != '\0') {
5013 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5014 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5015 compose->to_list = address_list_append(compose->to_list, entry);
5016 recipient_found = TRUE;
5019 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5020 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5021 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5022 recipient_found = TRUE;
5026 g_free(header);
5027 g_free(entry);
5029 return recipient_found;
5032 static gboolean compose_check_for_set_recipients(Compose *compose)
5034 if (compose->account->set_autocc && compose->account->auto_cc) {
5035 gboolean found_other = FALSE;
5036 GSList *list;
5037 /* search header entries for to and newsgroup entries */
5038 for (list = compose->header_list; list; list = list->next) {
5039 gchar *entry;
5040 gchar *header;
5041 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5042 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5043 g_strstrip(entry);
5044 g_strstrip(header);
5045 if (strcmp(entry, compose->account->auto_cc)
5046 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5047 found_other = TRUE;
5048 g_free(entry);
5049 break;
5051 g_free(entry);
5052 g_free(header);
5054 if (!found_other) {
5055 AlertValue aval;
5056 gchar *text;
5057 if (compose->batch) {
5058 gtk_widget_show_all(compose->window);
5060 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5061 prefs_common_translated_header_name("Cc"));
5062 aval = alertpanel(_("Send"),
5063 text,
5064 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5065 g_free(text);
5066 if (aval != G_ALERTALTERNATE)
5067 return FALSE;
5070 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5071 gboolean found_other = FALSE;
5072 GSList *list;
5073 /* search header entries for to and newsgroup entries */
5074 for (list = compose->header_list; list; list = list->next) {
5075 gchar *entry;
5076 gchar *header;
5077 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5078 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5079 g_strstrip(entry);
5080 g_strstrip(header);
5081 if (strcmp(entry, compose->account->auto_bcc)
5082 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5083 found_other = TRUE;
5084 g_free(entry);
5085 g_free(header);
5086 break;
5088 g_free(entry);
5089 g_free(header);
5091 if (!found_other) {
5092 AlertValue aval;
5093 gchar *text;
5094 if (compose->batch) {
5095 gtk_widget_show_all(compose->window);
5097 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5098 prefs_common_translated_header_name("Bcc"));
5099 aval = alertpanel(_("Send"),
5100 text,
5101 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5102 g_free(text);
5103 if (aval != G_ALERTALTERNATE)
5104 return FALSE;
5107 return TRUE;
5110 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5112 const gchar *str;
5114 if (compose_check_for_valid_recipient(compose) == FALSE) {
5115 if (compose->batch) {
5116 gtk_widget_show_all(compose->window);
5118 alertpanel_error(_("Recipient is not specified."));
5119 return FALSE;
5122 if (compose_check_for_set_recipients(compose) == FALSE) {
5123 return FALSE;
5126 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5127 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5128 if (*str == '\0' && check_everything == TRUE &&
5129 compose->mode != COMPOSE_REDIRECT) {
5130 AlertValue aval;
5131 gchar *button_label;
5132 gchar *message;
5134 if (compose->sending)
5135 button_label = g_strconcat("+", _("_Send"), NULL);
5136 else
5137 button_label = g_strconcat("+", _("_Queue"), NULL);
5138 message = g_strdup_printf(_("Subject is empty. %s"),
5139 compose->sending?_("Send it anyway?"):
5140 _("Queue it anyway?"));
5142 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5143 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5144 ALERT_QUESTION, G_ALERTDEFAULT);
5145 g_free(message);
5146 if (aval & G_ALERTDISABLE) {
5147 aval &= ~G_ALERTDISABLE;
5148 prefs_common.warn_empty_subj = FALSE;
5150 if (aval != G_ALERTALTERNATE)
5151 return FALSE;
5155 if (!compose->batch && prefs_common.warn_sending_many_recipients == TRUE
5156 && check_everything == TRUE) {
5157 GSList *list;
5158 gint cnt = 0;
5160 /* count To and Cc recipients */
5161 for (list = compose->header_list; list; list = list->next) {
5162 gchar *header;
5163 gchar *entry;
5165 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5166 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5167 g_strstrip(header);
5168 g_strstrip(entry);
5169 if ((entry[0] != '\0')
5170 && (strcmp(header, prefs_common_translated_header_name("To:"))
5171 || strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5172 cnt++;
5174 g_free(header);
5175 g_free(entry);
5177 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5178 AlertValue aval;
5179 gchar *button_label;
5180 gchar *message;
5182 if (compose->sending)
5183 button_label = g_strconcat("+", _("_Send"), NULL);
5184 else
5185 button_label = g_strconcat("+", _("_Queue"), NULL);
5186 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5187 compose->sending?_("Send it anyway?"):
5188 _("Queue it anyway?"));
5190 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5191 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5192 ALERT_QUESTION, G_ALERTDEFAULT);
5193 g_free(message);
5194 if (aval & G_ALERTDISABLE) {
5195 aval &= ~G_ALERTDISABLE;
5196 prefs_common.warn_empty_subj = FALSE;
5198 if (aval != G_ALERTALTERNATE)
5199 return FALSE;
5203 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5204 return FALSE;
5206 return TRUE;
5209 gint compose_send(Compose *compose)
5211 gint msgnum;
5212 FolderItem *folder = NULL;
5213 gint val = -1;
5214 gchar *msgpath = NULL;
5215 gboolean discard_window = FALSE;
5216 gchar *errstr = NULL;
5217 gchar *tmsgid = NULL;
5218 MainWindow *mainwin = mainwindow_get_mainwindow();
5219 gboolean queued_removed = FALSE;
5221 if (prefs_common.send_dialog_invisible
5222 || compose->batch == TRUE)
5223 discard_window = TRUE;
5225 compose_allow_user_actions (compose, FALSE);
5226 compose->sending = TRUE;
5228 if (compose_check_entries(compose, TRUE) == FALSE) {
5229 if (compose->batch) {
5230 gtk_widget_show_all(compose->window);
5232 goto bail;
5235 inc_lock();
5236 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5238 if (val) {
5239 if (compose->batch) {
5240 gtk_widget_show_all(compose->window);
5242 if (val == -4) {
5243 alertpanel_error(_("Could not queue message for sending:\n\n"
5244 "Charset conversion failed."));
5245 } else if (val == -5) {
5246 alertpanel_error(_("Could not queue message for sending:\n\n"
5247 "Couldn't get recipient encryption key."));
5248 } else if (val == -6) {
5249 /* silent error */
5250 } else if (val == -3) {
5251 if (privacy_peek_error())
5252 alertpanel_error(_("Could not queue message for sending:\n\n"
5253 "Signature failed: %s"), privacy_get_error());
5254 } else if (val == -2 && errno != 0) {
5255 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5256 } else {
5257 alertpanel_error(_("Could not queue message for sending."));
5259 goto bail;
5262 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5263 if (discard_window) {
5264 compose->sending = FALSE;
5265 compose_close(compose);
5266 /* No more compose access in the normal codepath
5267 * after this point! */
5268 compose = NULL;
5271 if (msgnum == 0) {
5272 alertpanel_error(_("The message was queued but could not be "
5273 "sent.\nUse \"Send queued messages\" from "
5274 "the main window to retry."));
5275 if (!discard_window) {
5276 goto bail;
5278 inc_unlock();
5279 g_free(tmsgid);
5280 return -1;
5282 if (msgpath == NULL) {
5283 msgpath = folder_item_fetch_msg(folder, msgnum);
5284 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5285 g_free(msgpath);
5286 } else {
5287 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5288 claws_unlink(msgpath);
5289 g_free(msgpath);
5291 if (!discard_window) {
5292 if (val != 0) {
5293 if (!queued_removed)
5294 folder_item_remove_msg(folder, msgnum);
5295 folder_item_scan(folder);
5296 if (tmsgid) {
5297 /* make sure we delete that */
5298 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5299 if (tmp) {
5300 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5301 folder_item_remove_msg(folder, tmp->msgnum);
5302 procmsg_msginfo_free(&tmp);
5308 if (val == 0) {
5309 if (!queued_removed)
5310 folder_item_remove_msg(folder, msgnum);
5311 folder_item_scan(folder);
5312 if (tmsgid) {
5313 /* make sure we delete that */
5314 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5315 if (tmp) {
5316 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5317 folder_item_remove_msg(folder, tmp->msgnum);
5318 procmsg_msginfo_free(&tmp);
5321 if (!discard_window) {
5322 compose->sending = FALSE;
5323 compose_allow_user_actions (compose, TRUE);
5324 compose_close(compose);
5326 } else {
5327 if (errstr) {
5328 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5329 "the main window to retry."), errstr);
5330 g_free(errstr);
5331 } else {
5332 alertpanel_error_log(_("The message was queued but could not be "
5333 "sent.\nUse \"Send queued messages\" from "
5334 "the main window to retry."));
5336 if (!discard_window) {
5337 goto bail;
5339 inc_unlock();
5340 g_free(tmsgid);
5341 return -1;
5343 g_free(tmsgid);
5344 inc_unlock();
5345 toolbar_main_set_sensitive(mainwin);
5346 main_window_set_menu_sensitive(mainwin);
5347 return 0;
5349 bail:
5350 inc_unlock();
5351 g_free(tmsgid);
5352 compose_allow_user_actions (compose, TRUE);
5353 compose->sending = FALSE;
5354 compose->modified = TRUE;
5355 toolbar_main_set_sensitive(mainwin);
5356 main_window_set_menu_sensitive(mainwin);
5358 return -1;
5361 static gboolean compose_use_attach(Compose *compose)
5363 GtkTreeModel *model = gtk_tree_view_get_model
5364 (GTK_TREE_VIEW(compose->attach_clist));
5365 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5368 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5369 FILE *fp)
5371 gchar buf[BUFFSIZE];
5372 gchar *str;
5373 gboolean first_to_address;
5374 gboolean first_cc_address;
5375 GSList *list;
5376 ComposeHeaderEntry *headerentry;
5377 const gchar *headerentryname;
5378 const gchar *cc_hdr;
5379 const gchar *to_hdr;
5380 gboolean err = FALSE;
5382 debug_print("Writing redirect header\n");
5384 cc_hdr = prefs_common_translated_header_name("Cc:");
5385 to_hdr = prefs_common_translated_header_name("To:");
5387 first_to_address = TRUE;
5388 for (list = compose->header_list; list; list = list->next) {
5389 headerentry = ((ComposeHeaderEntry *)list->data);
5390 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5392 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5393 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5394 Xstrdup_a(str, entstr, return -1);
5395 g_strstrip(str);
5396 if (str[0] != '\0') {
5397 compose_convert_header
5398 (compose, buf, sizeof(buf), str,
5399 strlen("Resent-To") + 2, TRUE);
5401 if (first_to_address) {
5402 err |= (fprintf(fp, "Resent-To: ") < 0);
5403 first_to_address = FALSE;
5404 } else {
5405 err |= (fprintf(fp, ",") < 0);
5407 err |= (fprintf(fp, "%s", buf) < 0);
5411 if (!first_to_address) {
5412 err |= (fprintf(fp, "\n") < 0);
5415 first_cc_address = TRUE;
5416 for (list = compose->header_list; list; list = list->next) {
5417 headerentry = ((ComposeHeaderEntry *)list->data);
5418 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5420 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5421 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5422 Xstrdup_a(str, strg, return -1);
5423 g_strstrip(str);
5424 if (str[0] != '\0') {
5425 compose_convert_header
5426 (compose, buf, sizeof(buf), str,
5427 strlen("Resent-Cc") + 2, TRUE);
5429 if (first_cc_address) {
5430 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5431 first_cc_address = FALSE;
5432 } else {
5433 err |= (fprintf(fp, ",") < 0);
5435 err |= (fprintf(fp, "%s", buf) < 0);
5439 if (!first_cc_address) {
5440 err |= (fprintf(fp, "\n") < 0);
5443 return (err ? -1:0);
5446 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5448 gchar date[RFC822_DATE_BUFFSIZE];
5449 gchar buf[BUFFSIZE];
5450 gchar *str;
5451 const gchar *entstr;
5452 /* struct utsname utsbuf; */
5453 gboolean err = FALSE;
5455 cm_return_val_if_fail(fp != NULL, -1);
5456 cm_return_val_if_fail(compose->account != NULL, -1);
5457 cm_return_val_if_fail(compose->account->address != NULL, -1);
5459 /* Resent-Date */
5460 if (prefs_common.hide_timezone)
5461 get_rfc822_date_hide_tz(date, sizeof(date));
5462 else
5463 get_rfc822_date(date, sizeof(date));
5464 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5466 /* Resent-From */
5467 if (compose->account->name && *compose->account->name) {
5468 compose_convert_header
5469 (compose, buf, sizeof(buf), compose->account->name,
5470 strlen("From: "), TRUE);
5471 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5472 buf, compose->account->address) < 0);
5473 } else
5474 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5476 /* Subject */
5477 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5478 if (*entstr != '\0') {
5479 Xstrdup_a(str, entstr, return -1);
5480 g_strstrip(str);
5481 if (*str != '\0') {
5482 compose_convert_header(compose, buf, sizeof(buf), str,
5483 strlen("Subject: "), FALSE);
5484 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5488 /* Resent-Message-ID */
5489 if (compose->account->gen_msgid) {
5490 gchar *addr = prefs_account_generate_msgid(compose->account);
5491 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5492 if (compose->msgid)
5493 g_free(compose->msgid);
5494 compose->msgid = addr;
5495 } else {
5496 compose->msgid = NULL;
5499 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5500 return -1;
5502 /* separator between header and body */
5503 err |= (fputs("\n", fp) == EOF);
5505 return (err ? -1:0);
5508 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5510 FILE *fp;
5511 size_t len;
5512 gchar *buf = NULL;
5513 gchar rewrite_buf[BUFFSIZE];
5514 int i = 0;
5515 gboolean skip = FALSE;
5516 gboolean err = FALSE;
5517 gchar *not_included[]={
5518 "Return-Path:", "Delivered-To:", "Received:",
5519 "Subject:", "X-UIDL:", "AF:",
5520 "NF:", "PS:", "SRH:",
5521 "SFN:", "DSR:", "MID:",
5522 "CFG:", "PT:", "S:",
5523 "RQ:", "SSV:", "NSV:",
5524 "SSH:", "R:", "MAID:",
5525 "NAID:", "RMID:", "FMID:",
5526 "SCF:", "RRCPT:", "NG:",
5527 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5528 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5529 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5530 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5531 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5532 NULL
5534 gint ret = 0;
5536 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5537 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5538 return -1;
5541 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5542 skip = FALSE;
5543 for (i = 0; not_included[i] != NULL; i++) {
5544 if (g_ascii_strncasecmp(buf, not_included[i],
5545 strlen(not_included[i])) == 0) {
5546 skip = TRUE;
5547 break;
5550 if (skip) {
5551 g_free(buf);
5552 buf = NULL;
5553 continue;
5555 if (fputs(buf, fdest) == -1) {
5556 g_free(buf);
5557 buf = NULL;
5558 goto error;
5561 if (!prefs_common.redirect_keep_from) {
5562 if (g_ascii_strncasecmp(buf, "From:",
5563 strlen("From:")) == 0) {
5564 err |= (fputs(" (by way of ", fdest) == EOF);
5565 if (compose->account->name
5566 && *compose->account->name) {
5567 gchar buffer[BUFFSIZE];
5569 compose_convert_header
5570 (compose, buffer, sizeof(buffer),
5571 compose->account->name,
5572 strlen("From: "),
5573 FALSE);
5574 err |= (fprintf(fdest, "%s <%s>",
5575 buffer,
5576 compose->account->address) < 0);
5577 } else
5578 err |= (fprintf(fdest, "%s",
5579 compose->account->address) < 0);
5580 err |= (fputs(")", fdest) == EOF);
5584 g_free(buf);
5585 buf = NULL;
5586 if (fputs("\n", fdest) == -1)
5587 goto error;
5590 if (err)
5591 goto error;
5593 if (compose_redirect_write_headers(compose, fdest))
5594 goto error;
5596 while ((len = fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5597 if (fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5598 goto error;
5601 fclose(fp);
5603 return 0;
5605 error:
5606 fclose(fp);
5608 return -1;
5611 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5613 GtkTextBuffer *buffer;
5614 GtkTextIter start, end;
5615 gchar *chars, *tmp_enc_file, *content;
5616 gchar *buf, *msg;
5617 const gchar *out_codeset;
5618 EncodingType encoding = ENC_UNKNOWN;
5619 MimeInfo *mimemsg, *mimetext;
5620 gint line;
5621 const gchar *src_codeset = CS_INTERNAL;
5622 gchar *from_addr = NULL;
5623 gchar *from_name = NULL;
5624 FolderItem *outbox;
5626 if (action == COMPOSE_WRITE_FOR_SEND) {
5627 attach_parts = TRUE;
5629 /* We're sending the message, generate a Message-ID
5630 * if necessary. */
5631 if (compose->msgid == NULL &&
5632 compose->account->gen_msgid) {
5633 compose->msgid = prefs_account_generate_msgid(compose->account);
5637 /* create message MimeInfo */
5638 mimemsg = procmime_mimeinfo_new();
5639 mimemsg->type = MIMETYPE_MESSAGE;
5640 mimemsg->subtype = g_strdup("rfc822");
5641 mimemsg->content = MIMECONTENT_MEM;
5642 mimemsg->tmp = TRUE; /* must free content later */
5643 mimemsg->data.mem = compose_get_header(compose);
5645 /* Create text part MimeInfo */
5646 /* get all composed text */
5647 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5648 gtk_text_buffer_get_start_iter(buffer, &start);
5649 gtk_text_buffer_get_end_iter(buffer, &end);
5650 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5652 out_codeset = conv_get_charset_str(compose->out_encoding);
5654 if (!out_codeset && is_ascii_str(chars)) {
5655 out_codeset = CS_US_ASCII;
5656 } else if (prefs_common.outgoing_fallback_to_ascii &&
5657 is_ascii_str(chars)) {
5658 out_codeset = CS_US_ASCII;
5659 encoding = ENC_7BIT;
5662 if (!out_codeset) {
5663 gchar *test_conv_global_out = NULL;
5664 gchar *test_conv_reply = NULL;
5666 /* automatic mode. be automatic. */
5667 codeconv_set_strict(TRUE);
5669 out_codeset = conv_get_outgoing_charset_str();
5670 if (out_codeset) {
5671 debug_print("trying to convert to %s\n", out_codeset);
5672 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5675 if (!test_conv_global_out && compose->orig_charset
5676 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5677 out_codeset = compose->orig_charset;
5678 debug_print("failure; trying to convert to %s\n", out_codeset);
5679 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5682 if (!test_conv_global_out && !test_conv_reply) {
5683 /* we're lost */
5684 out_codeset = CS_INTERNAL;
5685 debug_print("failure; finally using %s\n", out_codeset);
5687 g_free(test_conv_global_out);
5688 g_free(test_conv_reply);
5689 codeconv_set_strict(FALSE);
5692 if (encoding == ENC_UNKNOWN) {
5693 if (prefs_common.encoding_method == CTE_BASE64)
5694 encoding = ENC_BASE64;
5695 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5696 encoding = ENC_QUOTED_PRINTABLE;
5697 else if (prefs_common.encoding_method == CTE_8BIT)
5698 encoding = ENC_8BIT;
5699 else
5700 encoding = procmime_get_encoding_for_charset(out_codeset);
5703 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5704 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5706 if (action == COMPOSE_WRITE_FOR_SEND) {
5707 codeconv_set_strict(TRUE);
5708 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5709 codeconv_set_strict(FALSE);
5711 if (!buf) {
5712 AlertValue aval;
5714 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5715 "to the specified %s charset.\n"
5716 "Send it as %s?"), out_codeset, src_codeset);
5717 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5718 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5719 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5720 g_free(msg);
5722 if (aval != G_ALERTALTERNATE) {
5723 g_free(chars);
5724 return -3;
5725 } else {
5726 buf = chars;
5727 out_codeset = src_codeset;
5728 chars = NULL;
5731 } else {
5732 buf = chars;
5733 out_codeset = src_codeset;
5734 chars = NULL;
5736 g_free(chars);
5738 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5739 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5740 strstr(buf, "\nFrom ") != NULL) {
5741 encoding = ENC_QUOTED_PRINTABLE;
5745 mimetext = procmime_mimeinfo_new();
5746 mimetext->content = MIMECONTENT_MEM;
5747 mimetext->tmp = TRUE; /* must free content later */
5748 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5749 * and free the data, which we need later. */
5750 mimetext->data.mem = g_strdup(buf);
5751 mimetext->type = MIMETYPE_TEXT;
5752 mimetext->subtype = g_strdup("plain");
5753 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5754 g_strdup(out_codeset));
5756 /* protect trailing spaces when signing message */
5757 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5758 privacy_system_can_sign(compose->privacy_system)) {
5759 encoding = ENC_QUOTED_PRINTABLE;
5762 #ifdef G_OS_WIN32
5763 debug_print("main text: %Id bytes encoded as %s in %d\n",
5764 #else
5765 debug_print("main text: %zd bytes encoded as %s in %d\n",
5766 #endif
5767 strlen(buf), out_codeset, encoding);
5769 /* check for line length limit */
5770 if (action == COMPOSE_WRITE_FOR_SEND &&
5771 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5772 check_line_length(buf, 1000, &line) < 0) {
5773 AlertValue aval;
5775 msg = g_strdup_printf
5776 (_("Line %d exceeds the line length limit (998 bytes).\n"
5777 "The contents of the message might be broken on the way to the delivery.\n"
5778 "\n"
5779 "Send it anyway?"), line + 1);
5780 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5781 g_free(msg);
5782 if (aval != G_ALERTALTERNATE) {
5783 g_free(buf);
5784 return -1;
5788 if (encoding != ENC_UNKNOWN)
5789 procmime_encode_content(mimetext, encoding);
5791 /* append attachment parts */
5792 if (compose_use_attach(compose) && attach_parts) {
5793 MimeInfo *mimempart;
5794 gchar *boundary = NULL;
5795 mimempart = procmime_mimeinfo_new();
5796 mimempart->content = MIMECONTENT_EMPTY;
5797 mimempart->type = MIMETYPE_MULTIPART;
5798 mimempart->subtype = g_strdup("mixed");
5800 do {
5801 g_free(boundary);
5802 boundary = generate_mime_boundary(NULL);
5803 } while (strstr(buf, boundary) != NULL);
5805 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5806 boundary);
5808 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5810 g_node_append(mimempart->node, mimetext->node);
5811 g_node_append(mimemsg->node, mimempart->node);
5813 if (compose_add_attachments(compose, mimempart) < 0)
5814 return -1;
5815 } else
5816 g_node_append(mimemsg->node, mimetext->node);
5818 g_free(buf);
5820 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5821 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5822 /* extract name and address */
5823 if (strstr(spec, " <") && strstr(spec, ">")) {
5824 from_addr = g_strdup(strrchr(spec, '<')+1);
5825 *(strrchr(from_addr, '>')) = '\0';
5826 from_name = g_strdup(spec);
5827 *(strrchr(from_name, '<')) = '\0';
5828 } else {
5829 from_name = NULL;
5830 from_addr = NULL;
5832 g_free(spec);
5834 /* sign message if sending */
5835 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5836 privacy_system_can_sign(compose->privacy_system))
5837 if (!privacy_sign(compose->privacy_system, mimemsg,
5838 compose->account, from_addr)) {
5839 g_free(from_name);
5840 g_free(from_addr);
5841 return -2;
5843 g_free(from_name);
5844 g_free(from_addr);
5846 if (compose->use_encryption) {
5847 if (compose->encdata != NULL &&
5848 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5850 /* First, write an unencrypted copy and save it to outbox, if
5851 * user wants that. */
5852 if (compose->account->save_encrypted_as_clear_text) {
5853 debug_print("saving sent message unencrypted...\n");
5854 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5855 if (tmpfp) {
5856 fclose(tmpfp);
5858 /* fp now points to a file with headers written,
5859 * let's make a copy. */
5860 rewind(fp);
5861 content = file_read_stream_to_str(fp);
5863 str_write_to_file(content, tmp_enc_file);
5864 g_free(content);
5866 /* Now write the unencrypted body. */
5867 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5868 procmime_write_mimeinfo(mimemsg, tmpfp);
5869 fclose(tmpfp);
5871 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5872 if (!outbox)
5873 outbox = folder_get_default_outbox();
5875 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5876 claws_unlink(tmp_enc_file);
5877 } else {
5878 g_warning("Can't open file '%s'", tmp_enc_file);
5880 } else {
5881 g_warning("couldn't get tempfile");
5884 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5885 debug_print("Couldn't encrypt mime structure: %s.\n",
5886 privacy_get_error());
5887 alertpanel_error(_("Couldn't encrypt the email: %s"),
5888 privacy_get_error());
5893 procmime_write_mimeinfo(mimemsg, fp);
5895 procmime_mimeinfo_free_all(&mimemsg);
5897 return 0;
5900 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5902 GtkTextBuffer *buffer;
5903 GtkTextIter start, end;
5904 FILE *fp;
5905 size_t len;
5906 gchar *chars, *tmp;
5908 if ((fp = g_fopen(file, "wb")) == NULL) {
5909 FILE_OP_ERROR(file, "fopen");
5910 return -1;
5913 /* chmod for security */
5914 if (change_file_mode_rw(fp, file) < 0) {
5915 FILE_OP_ERROR(file, "chmod");
5916 g_warning("can't change file mode");
5919 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5920 gtk_text_buffer_get_start_iter(buffer, &start);
5921 gtk_text_buffer_get_end_iter(buffer, &end);
5922 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5924 chars = conv_codeset_strdup
5925 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5927 g_free(tmp);
5928 if (!chars) {
5929 fclose(fp);
5930 claws_unlink(file);
5931 return -1;
5933 /* write body */
5934 len = strlen(chars);
5935 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5936 FILE_OP_ERROR(file, "fwrite");
5937 g_free(chars);
5938 fclose(fp);
5939 claws_unlink(file);
5940 return -1;
5943 g_free(chars);
5945 if (fclose(fp) == EOF) {
5946 FILE_OP_ERROR(file, "fclose");
5947 claws_unlink(file);
5948 return -1;
5950 return 0;
5953 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5955 FolderItem *item;
5956 MsgInfo *msginfo = compose->targetinfo;
5958 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5959 if (!msginfo) return -1;
5961 if (!force && MSG_IS_LOCKED(msginfo->flags))
5962 return 0;
5964 item = msginfo->folder;
5965 cm_return_val_if_fail(item != NULL, -1);
5967 if (procmsg_msg_exist(msginfo) &&
5968 (folder_has_parent_of_type(item, F_QUEUE) ||
5969 folder_has_parent_of_type(item, F_DRAFT)
5970 || msginfo == compose->autosaved_draft)) {
5971 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5972 g_warning("can't remove the old message");
5973 return -1;
5974 } else {
5975 debug_print("removed reedit target %d\n", msginfo->msgnum);
5979 return 0;
5982 static void compose_remove_draft(Compose *compose)
5984 FolderItem *drafts;
5985 MsgInfo *msginfo = compose->targetinfo;
5986 drafts = account_get_special_folder(compose->account, F_DRAFT);
5988 if (procmsg_msg_exist(msginfo)) {
5989 folder_item_remove_msg(drafts, msginfo->msgnum);
5994 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5995 gboolean remove_reedit_target)
5997 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6000 static gboolean compose_warn_encryption(Compose *compose)
6002 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6003 AlertValue val = G_ALERTALTERNATE;
6005 if (warning == NULL)
6006 return TRUE;
6008 val = alertpanel_full(_("Encryption warning"), warning,
6009 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
6010 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
6011 if (val & G_ALERTDISABLE) {
6012 val &= ~G_ALERTDISABLE;
6013 if (val == G_ALERTALTERNATE)
6014 privacy_inhibit_encrypt_warning(compose->privacy_system,
6015 TRUE);
6018 if (val == G_ALERTALTERNATE) {
6019 return TRUE;
6020 } else {
6021 return FALSE;
6025 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6026 gchar **msgpath, gboolean perform_checks,
6027 gboolean remove_reedit_target)
6029 FolderItem *queue;
6030 gchar *tmp;
6031 FILE *fp;
6032 GSList *cur;
6033 gint num;
6034 PrefsAccount *mailac = NULL, *newsac = NULL;
6035 gboolean err = FALSE;
6037 debug_print("queueing message...\n");
6038 cm_return_val_if_fail(compose->account != NULL, -1);
6040 if (compose_check_entries(compose, perform_checks) == FALSE) {
6041 if (compose->batch) {
6042 gtk_widget_show_all(compose->window);
6044 return -1;
6047 if (!compose->to_list && !compose->newsgroup_list) {
6048 g_warning("can't get recipient list.");
6049 return -1;
6052 if (compose->to_list) {
6053 if (compose->account->protocol != A_NNTP)
6054 mailac = compose->account;
6055 else if (cur_account && cur_account->protocol != A_NNTP)
6056 mailac = cur_account;
6057 else if (!(mailac = compose_current_mail_account())) {
6058 alertpanel_error(_("No account for sending mails available!"));
6059 return -1;
6063 if (compose->newsgroup_list) {
6064 if (compose->account->protocol == A_NNTP)
6065 newsac = compose->account;
6066 else {
6067 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6068 return -1;
6072 /* write queue header */
6073 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6074 G_DIR_SEPARATOR, compose, (guint) rand());
6075 debug_print("queuing to %s\n", tmp);
6076 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
6077 FILE_OP_ERROR(tmp, "fopen");
6078 g_free(tmp);
6079 return -2;
6082 if (change_file_mode_rw(fp, tmp) < 0) {
6083 FILE_OP_ERROR(tmp, "chmod");
6084 g_warning("can't change file mode");
6087 /* queueing variables */
6088 err |= (fprintf(fp, "AF:\n") < 0);
6089 err |= (fprintf(fp, "NF:0\n") < 0);
6090 err |= (fprintf(fp, "PS:10\n") < 0);
6091 err |= (fprintf(fp, "SRH:1\n") < 0);
6092 err |= (fprintf(fp, "SFN:\n") < 0);
6093 err |= (fprintf(fp, "DSR:\n") < 0);
6094 if (compose->msgid)
6095 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6096 else
6097 err |= (fprintf(fp, "MID:\n") < 0);
6098 err |= (fprintf(fp, "CFG:\n") < 0);
6099 err |= (fprintf(fp, "PT:0\n") < 0);
6100 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6101 err |= (fprintf(fp, "RQ:\n") < 0);
6102 if (mailac)
6103 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6104 else
6105 err |= (fprintf(fp, "SSV:\n") < 0);
6106 if (newsac)
6107 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6108 else
6109 err |= (fprintf(fp, "NSV:\n") < 0);
6110 err |= (fprintf(fp, "SSH:\n") < 0);
6111 /* write recepient list */
6112 if (compose->to_list) {
6113 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6114 for (cur = compose->to_list->next; cur != NULL;
6115 cur = cur->next)
6116 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6117 err |= (fprintf(fp, "\n") < 0);
6119 /* write newsgroup list */
6120 if (compose->newsgroup_list) {
6121 err |= (fprintf(fp, "NG:") < 0);
6122 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6123 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6124 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6125 err |= (fprintf(fp, "\n") < 0);
6127 /* Sylpheed account IDs */
6128 if (mailac)
6129 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6130 if (newsac)
6131 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6134 if (compose->privacy_system != NULL) {
6135 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6136 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6137 if (compose->use_encryption) {
6138 if (!compose_warn_encryption(compose)) {
6139 fclose(fp);
6140 claws_unlink(tmp);
6141 g_free(tmp);
6142 return -6;
6144 if (mailac && mailac->encrypt_to_self) {
6145 GSList *tmp_list = g_slist_copy(compose->to_list);
6146 tmp_list = g_slist_append(tmp_list, compose->account->address);
6147 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6148 g_slist_free(tmp_list);
6149 } else {
6150 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6152 if (compose->encdata != NULL) {
6153 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6154 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6155 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6156 compose->encdata) < 0);
6157 } /* else we finally dont want to encrypt */
6158 } else {
6159 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6160 /* and if encdata was null, it means there's been a problem in
6161 * key selection */
6162 if (err == TRUE)
6163 g_warning("failed to write queue message");
6164 fclose(fp);
6165 claws_unlink(tmp);
6166 g_free(tmp);
6167 return -5;
6172 /* Save copy folder */
6173 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6174 gchar *savefolderid;
6176 savefolderid = compose_get_save_to(compose);
6177 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6178 g_free(savefolderid);
6180 /* Save copy folder */
6181 if (compose->return_receipt) {
6182 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6184 /* Message-ID of message replying to */
6185 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6186 gchar *folderid = NULL;
6188 if (compose->replyinfo->folder)
6189 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6190 if (folderid == NULL)
6191 folderid = g_strdup("NULL");
6193 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6194 g_free(folderid);
6196 /* Message-ID of message forwarding to */
6197 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6198 gchar *folderid = NULL;
6200 if (compose->fwdinfo->folder)
6201 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6202 if (folderid == NULL)
6203 folderid = g_strdup("NULL");
6205 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6206 g_free(folderid);
6209 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6210 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6212 /* end of headers */
6213 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6215 if (compose->redirect_filename != NULL) {
6216 if (compose_redirect_write_to_file(compose, fp) < 0) {
6217 fclose(fp);
6218 claws_unlink(tmp);
6219 g_free(tmp);
6220 return -2;
6222 } else {
6223 gint result = 0;
6224 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6225 fclose(fp);
6226 claws_unlink(tmp);
6227 g_free(tmp);
6228 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6231 if (err == TRUE) {
6232 g_warning("failed to write queue message");
6233 fclose(fp);
6234 claws_unlink(tmp);
6235 g_free(tmp);
6236 return -2;
6238 if (fclose(fp) == EOF) {
6239 FILE_OP_ERROR(tmp, "fclose");
6240 claws_unlink(tmp);
6241 g_free(tmp);
6242 return -2;
6245 if (item && *item) {
6246 queue = *item;
6247 } else {
6248 queue = account_get_special_folder(compose->account, F_QUEUE);
6250 if (!queue) {
6251 g_warning("can't find queue folder");
6252 claws_unlink(tmp);
6253 g_free(tmp);
6254 return -1;
6256 folder_item_scan(queue);
6257 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6258 g_warning("can't queue the message");
6259 claws_unlink(tmp);
6260 g_free(tmp);
6261 return -1;
6264 if (msgpath == NULL) {
6265 claws_unlink(tmp);
6266 g_free(tmp);
6267 } else
6268 *msgpath = tmp;
6270 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6271 compose_remove_reedit_target(compose, FALSE);
6274 if ((msgnum != NULL) && (item != NULL)) {
6275 *msgnum = num;
6276 *item = queue;
6279 return 0;
6282 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6284 AttachInfo *ainfo;
6285 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6286 MimeInfo *mimepart;
6287 GStatBuf statbuf;
6288 gchar *type, *subtype;
6289 GtkTreeModel *model;
6290 GtkTreeIter iter;
6292 model = gtk_tree_view_get_model(tree_view);
6294 if (!gtk_tree_model_get_iter_first(model, &iter))
6295 return 0;
6296 do {
6297 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6299 if (!is_file_exist(ainfo->file)) {
6300 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6301 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6302 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6303 g_free(msg);
6304 if (val == G_ALERTDEFAULT) {
6305 return -1;
6307 continue;
6309 if (g_stat(ainfo->file, &statbuf) < 0)
6310 return -1;
6312 mimepart = procmime_mimeinfo_new();
6313 mimepart->content = MIMECONTENT_FILE;
6314 mimepart->data.filename = g_strdup(ainfo->file);
6315 mimepart->tmp = FALSE; /* or we destroy our attachment */
6316 mimepart->offset = 0;
6317 mimepart->length = statbuf.st_size;
6319 type = g_strdup(ainfo->content_type);
6321 if (!strchr(type, '/')) {
6322 g_free(type);
6323 type = g_strdup("application/octet-stream");
6326 subtype = strchr(type, '/') + 1;
6327 *(subtype - 1) = '\0';
6328 mimepart->type = procmime_get_media_type(type);
6329 mimepart->subtype = g_strdup(subtype);
6330 g_free(type);
6332 if (mimepart->type == MIMETYPE_MESSAGE &&
6333 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6334 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6335 } else if (mimepart->type == MIMETYPE_TEXT) {
6336 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6337 /* Text parts with no name come from multipart/alternative
6338 * forwards. Make sure the recipient won't look at the
6339 * original HTML part by mistake. */
6340 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6341 ainfo->name = g_strdup_printf(_("Original %s part"),
6342 mimepart->subtype);
6344 if (ainfo->charset)
6345 g_hash_table_insert(mimepart->typeparameters,
6346 g_strdup("charset"), g_strdup(ainfo->charset));
6348 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6349 if (mimepart->type == MIMETYPE_APPLICATION &&
6350 !strcmp2(mimepart->subtype, "octet-stream"))
6351 g_hash_table_insert(mimepart->typeparameters,
6352 g_strdup("name"), g_strdup(ainfo->name));
6353 g_hash_table_insert(mimepart->dispositionparameters,
6354 g_strdup("filename"), g_strdup(ainfo->name));
6355 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6358 if (mimepart->type == MIMETYPE_MESSAGE
6359 || mimepart->type == MIMETYPE_MULTIPART)
6360 ainfo->encoding = ENC_BINARY;
6361 else if (compose->use_signing) {
6362 if (ainfo->encoding == ENC_7BIT)
6363 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6364 else if (ainfo->encoding == ENC_8BIT)
6365 ainfo->encoding = ENC_BASE64;
6368 procmime_encode_content(mimepart, ainfo->encoding);
6370 g_node_append(parent->node, mimepart->node);
6371 } while (gtk_tree_model_iter_next(model, &iter));
6373 return 0;
6376 static gchar *compose_quote_list_of_addresses(gchar *str)
6378 GSList *list = NULL, *item = NULL;
6379 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6381 list = address_list_append_with_comments(list, str);
6382 for (item = list; item != NULL; item = item->next) {
6383 gchar *spec = item->data;
6384 gchar *endofname = strstr(spec, " <");
6385 if (endofname != NULL) {
6386 gchar * qqname;
6387 *endofname = '\0';
6388 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6389 qqname = escape_internal_quotes(qname, '"');
6390 *endofname = ' ';
6391 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6392 gchar *addr = g_strdup(endofname);
6393 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6394 faddr = g_strconcat(name, addr, NULL);
6395 g_free(name);
6396 g_free(addr);
6397 debug_print("new auto-quoted address: '%s'\n", faddr);
6400 if (result == NULL)
6401 result = g_strdup((faddr != NULL)? faddr: spec);
6402 else {
6403 result = g_strconcat(result,
6404 ", ",
6405 (faddr != NULL)? faddr: spec,
6406 NULL);
6408 if (faddr != NULL) {
6409 g_free(faddr);
6410 faddr = NULL;
6413 slist_free_strings_full(list);
6415 return result;
6418 #define IS_IN_CUSTOM_HEADER(header) \
6419 (compose->account->add_customhdr && \
6420 custom_header_find(compose->account->customhdr_list, header) != NULL)
6422 static const gchar *compose_untranslated_header_name(gchar *header_name)
6424 /* return the untranslated header name, if header_name is a known
6425 header name, in either its translated or untranslated form, with
6426 or without trailing colon. return NULL if no matching header name
6427 is found or if header_name is NULL. */
6428 gchar *translated_header_name;
6429 gchar *translated_header_name_wcolon;
6430 const gchar *untranslated_header_name;
6431 const gchar *untranslated_header_name_wcolon;
6432 gint i;
6434 cm_return_val_if_fail(header_name != NULL, NULL);
6436 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6437 untranslated_header_name = HEADERS[i].header_name;
6438 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6440 translated_header_name = gettext(untranslated_header_name);
6441 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6443 if (!strcmp(header_name, untranslated_header_name) ||
6444 !strcmp(header_name, translated_header_name)) {
6445 return untranslated_header_name;
6446 } else {
6447 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6448 !strcmp(header_name, translated_header_name_wcolon)) {
6449 return untranslated_header_name_wcolon;
6453 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6454 return NULL;
6457 static void compose_add_headerfield_from_headerlist(Compose *compose,
6458 GString *header,
6459 const gchar *fieldname,
6460 const gchar *seperator)
6462 gchar *str, *fieldname_w_colon;
6463 gboolean add_field = FALSE;
6464 GSList *list;
6465 ComposeHeaderEntry *headerentry;
6466 const gchar *headerentryname;
6467 const gchar *trans_fieldname;
6468 GString *fieldstr;
6470 if (IS_IN_CUSTOM_HEADER(fieldname))
6471 return;
6473 debug_print("Adding %s-fields\n", fieldname);
6475 fieldstr = g_string_sized_new(64);
6477 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6478 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6480 for (list = compose->header_list; list; list = list->next) {
6481 headerentry = ((ComposeHeaderEntry *)list->data);
6482 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6484 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6485 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6486 g_strstrip(ustr);
6487 str = compose_quote_list_of_addresses(ustr);
6488 g_free(ustr);
6489 if (str != NULL && str[0] != '\0') {
6490 if (add_field)
6491 g_string_append(fieldstr, seperator);
6492 g_string_append(fieldstr, str);
6493 add_field = TRUE;
6495 g_free(str);
6498 if (add_field) {
6499 gchar *buf;
6501 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6502 compose_convert_header
6503 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6504 strlen(fieldname) + 2, TRUE);
6505 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6506 g_free(buf);
6509 g_free(fieldname_w_colon);
6510 g_string_free(fieldstr, TRUE);
6512 return;
6515 static gchar *compose_get_manual_headers_info(Compose *compose)
6517 GString *sh_header = g_string_new(" ");
6518 GSList *list;
6519 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6521 for (list = compose->header_list; list; list = list->next) {
6522 ComposeHeaderEntry *headerentry;
6523 gchar *tmp;
6524 gchar *headername;
6525 gchar *headername_wcolon;
6526 const gchar *headername_trans;
6527 gchar **string;
6528 gboolean standard_header = FALSE;
6530 headerentry = ((ComposeHeaderEntry *)list->data);
6532 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6533 g_strstrip(tmp);
6534 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6535 g_free(tmp);
6536 continue;
6539 if (!strstr(tmp, ":")) {
6540 headername_wcolon = g_strconcat(tmp, ":", NULL);
6541 headername = g_strdup(tmp);
6542 } else {
6543 headername_wcolon = g_strdup(tmp);
6544 headername = g_strdup(strtok(tmp, ":"));
6546 g_free(tmp);
6548 string = std_headers;
6549 while (*string != NULL) {
6550 headername_trans = prefs_common_translated_header_name(*string);
6551 if (!strcmp(headername_trans, headername_wcolon))
6552 standard_header = TRUE;
6553 string++;
6555 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6556 g_string_append_printf(sh_header, "%s ", headername);
6557 g_free(headername);
6558 g_free(headername_wcolon);
6560 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6561 return g_string_free(sh_header, FALSE);
6564 static gchar *compose_get_header(Compose *compose)
6566 gchar date[RFC822_DATE_BUFFSIZE];
6567 gchar buf[BUFFSIZE];
6568 const gchar *entry_str;
6569 gchar *str;
6570 gchar *name;
6571 GSList *list;
6572 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6573 GString *header;
6574 gchar *from_name = NULL, *from_address = NULL;
6575 gchar *tmp;
6577 cm_return_val_if_fail(compose->account != NULL, NULL);
6578 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6580 header = g_string_sized_new(64);
6582 /* Date */
6583 if (prefs_common.hide_timezone)
6584 get_rfc822_date_hide_tz(date, sizeof(date));
6585 else
6586 get_rfc822_date(date, sizeof(date));
6587 g_string_append_printf(header, "Date: %s\n", date);
6589 /* From */
6591 if (compose->account->name && *compose->account->name) {
6592 gchar *buf;
6593 QUOTE_IF_REQUIRED(buf, compose->account->name);
6594 tmp = g_strdup_printf("%s <%s>",
6595 buf, compose->account->address);
6596 } else {
6597 tmp = g_strdup_printf("%s",
6598 compose->account->address);
6600 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6601 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6602 /* use default */
6603 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6604 from_address = g_strdup(compose->account->address);
6605 } else {
6606 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6607 /* extract name and address */
6608 if (strstr(spec, " <") && strstr(spec, ">")) {
6609 from_address = g_strdup(strrchr(spec, '<')+1);
6610 *(strrchr(from_address, '>')) = '\0';
6611 from_name = g_strdup(spec);
6612 *(strrchr(from_name, '<')) = '\0';
6613 } else {
6614 from_name = NULL;
6615 from_address = g_strdup(spec);
6617 g_free(spec);
6619 g_free(tmp);
6622 if (from_name && *from_name) {
6623 gchar *qname;
6624 compose_convert_header
6625 (compose, buf, sizeof(buf), from_name,
6626 strlen("From: "), TRUE);
6627 QUOTE_IF_REQUIRED(name, buf);
6628 qname = escape_internal_quotes(name, '"');
6630 g_string_append_printf(header, "From: %s <%s>\n",
6631 qname, from_address);
6632 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6633 compose->return_receipt) {
6634 compose_convert_header(compose, buf, sizeof(buf), from_name,
6635 strlen("Disposition-Notification-To: "),
6636 TRUE);
6637 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6639 if (qname != name)
6640 g_free(qname);
6641 } else {
6642 g_string_append_printf(header, "From: %s\n", from_address);
6643 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6644 compose->return_receipt)
6645 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6648 g_free(from_name);
6649 g_free(from_address);
6651 /* To */
6652 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6654 /* Newsgroups */
6655 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6657 /* Cc */
6658 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6660 /* Bcc */
6662 * If this account is a NNTP account remove Bcc header from
6663 * message body since it otherwise will be publicly shown
6665 if (compose->account->protocol != A_NNTP)
6666 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6668 /* Subject */
6669 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6671 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6672 g_strstrip(str);
6673 if (*str != '\0') {
6674 compose_convert_header(compose, buf, sizeof(buf), str,
6675 strlen("Subject: "), FALSE);
6676 g_string_append_printf(header, "Subject: %s\n", buf);
6679 g_free(str);
6681 /* Message-ID */
6682 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6683 g_string_append_printf(header, "Message-ID: <%s>\n",
6684 compose->msgid);
6687 if (compose->remove_references == FALSE) {
6688 /* In-Reply-To */
6689 if (compose->inreplyto && compose->to_list)
6690 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6692 /* References */
6693 if (compose->references)
6694 g_string_append_printf(header, "References: %s\n", compose->references);
6697 /* Followup-To */
6698 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6700 /* Reply-To */
6701 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6703 /* Organization */
6704 if (compose->account->organization &&
6705 strlen(compose->account->organization) &&
6706 !IS_IN_CUSTOM_HEADER("Organization")) {
6707 compose_convert_header(compose, buf, sizeof(buf),
6708 compose->account->organization,
6709 strlen("Organization: "), FALSE);
6710 g_string_append_printf(header, "Organization: %s\n", buf);
6713 /* Program version and system info */
6714 if (compose->account->gen_xmailer &&
6715 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6716 !compose->newsgroup_list) {
6717 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6718 prog_version,
6719 gtk_major_version, gtk_minor_version, gtk_micro_version,
6720 TARGET_ALIAS);
6722 if (compose->account->gen_xmailer &&
6723 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6724 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6725 prog_version,
6726 gtk_major_version, gtk_minor_version, gtk_micro_version,
6727 TARGET_ALIAS);
6730 /* custom headers */
6731 if (compose->account->add_customhdr) {
6732 GSList *cur;
6734 for (cur = compose->account->customhdr_list; cur != NULL;
6735 cur = cur->next) {
6736 CustomHeader *chdr = (CustomHeader *)cur->data;
6738 if (custom_header_is_allowed(chdr->name)
6739 && chdr->value != NULL
6740 && *(chdr->value) != '\0') {
6741 compose_convert_header
6742 (compose, buf, sizeof(buf),
6743 chdr->value,
6744 strlen(chdr->name) + 2, FALSE);
6745 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6750 /* Automatic Faces and X-Faces */
6751 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6752 g_string_append_printf(header, "X-Face: %s\n", buf);
6754 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6755 g_string_append_printf(header, "X-Face: %s\n", buf);
6757 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6758 g_string_append_printf(header, "Face: %s\n", buf);
6760 else if (get_default_face (buf, sizeof(buf)) == 0) {
6761 g_string_append_printf(header, "Face: %s\n", buf);
6764 /* PRIORITY */
6765 switch (compose->priority) {
6766 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6767 "X-Priority: 1 (Highest)\n");
6768 break;
6769 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6770 "X-Priority: 2 (High)\n");
6771 break;
6772 case PRIORITY_NORMAL: break;
6773 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6774 "X-Priority: 4 (Low)\n");
6775 break;
6776 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6777 "X-Priority: 5 (Lowest)\n");
6778 break;
6779 default: debug_print("compose: priority unknown : %d\n",
6780 compose->priority);
6783 /* get special headers */
6784 for (list = compose->header_list; list; list = list->next) {
6785 ComposeHeaderEntry *headerentry;
6786 gchar *tmp;
6787 gchar *headername;
6788 gchar *headername_wcolon;
6789 const gchar *headername_trans;
6790 gchar *headervalue;
6791 gchar **string;
6792 gboolean standard_header = FALSE;
6794 headerentry = ((ComposeHeaderEntry *)list->data);
6796 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6797 g_strstrip(tmp);
6798 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6799 g_free(tmp);
6800 continue;
6803 if (!strstr(tmp, ":")) {
6804 headername_wcolon = g_strconcat(tmp, ":", NULL);
6805 headername = g_strdup(tmp);
6806 } else {
6807 headername_wcolon = g_strdup(tmp);
6808 headername = g_strdup(strtok(tmp, ":"));
6810 g_free(tmp);
6812 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6813 Xstrdup_a(headervalue, entry_str, return NULL);
6814 subst_char(headervalue, '\r', ' ');
6815 subst_char(headervalue, '\n', ' ');
6816 g_strstrip(headervalue);
6817 if (*headervalue != '\0') {
6818 string = std_headers;
6819 while (*string != NULL && !standard_header) {
6820 headername_trans = prefs_common_translated_header_name(*string);
6821 /* support mixed translated and untranslated headers */
6822 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6823 standard_header = TRUE;
6824 string++;
6826 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6827 /* store untranslated header name */
6828 g_string_append_printf(header, "%s %s\n",
6829 compose_untranslated_header_name(headername_wcolon), headervalue);
6832 g_free(headername);
6833 g_free(headername_wcolon);
6836 str = header->str;
6837 g_string_free(header, FALSE);
6839 return str;
6842 #undef IS_IN_CUSTOM_HEADER
6844 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6845 gint header_len, gboolean addr_field)
6847 gchar *tmpstr = NULL;
6848 const gchar *out_codeset = NULL;
6850 cm_return_if_fail(src != NULL);
6851 cm_return_if_fail(dest != NULL);
6853 if (len < 1) return;
6855 tmpstr = g_strdup(src);
6857 subst_char(tmpstr, '\n', ' ');
6858 subst_char(tmpstr, '\r', ' ');
6859 g_strchomp(tmpstr);
6861 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6862 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6863 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6864 g_free(tmpstr);
6865 tmpstr = mybuf;
6868 codeconv_set_strict(TRUE);
6869 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6870 conv_get_charset_str(compose->out_encoding));
6871 codeconv_set_strict(FALSE);
6873 if (!dest || *dest == '\0') {
6874 gchar *test_conv_global_out = NULL;
6875 gchar *test_conv_reply = NULL;
6877 /* automatic mode. be automatic. */
6878 codeconv_set_strict(TRUE);
6880 out_codeset = conv_get_outgoing_charset_str();
6881 if (out_codeset) {
6882 debug_print("trying to convert to %s\n", out_codeset);
6883 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6886 if (!test_conv_global_out && compose->orig_charset
6887 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6888 out_codeset = compose->orig_charset;
6889 debug_print("failure; trying to convert to %s\n", out_codeset);
6890 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6893 if (!test_conv_global_out && !test_conv_reply) {
6894 /* we're lost */
6895 out_codeset = CS_INTERNAL;
6896 debug_print("finally using %s\n", out_codeset);
6898 g_free(test_conv_global_out);
6899 g_free(test_conv_reply);
6900 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6901 out_codeset);
6902 codeconv_set_strict(FALSE);
6904 g_free(tmpstr);
6907 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6909 gchar *address;
6911 cm_return_if_fail(user_data != NULL);
6913 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6914 g_strstrip(address);
6915 if (*address != '\0') {
6916 gchar *name = procheader_get_fromname(address);
6917 extract_address(address);
6918 #ifndef USE_ALT_ADDRBOOK
6919 addressbook_add_contact(name, address, NULL, NULL);
6920 #else
6921 debug_print("%s: %s\n", name, address);
6922 if (addressadd_selection(name, address, NULL, NULL)) {
6923 debug_print( "addressbook_add_contact - added\n" );
6925 #endif
6927 g_free(address);
6930 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6932 GtkWidget *menuitem;
6933 gchar *address;
6935 cm_return_if_fail(menu != NULL);
6936 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6938 menuitem = gtk_separator_menu_item_new();
6939 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6940 gtk_widget_show(menuitem);
6942 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6943 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6945 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6946 g_strstrip(address);
6947 if (*address == '\0') {
6948 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6951 g_signal_connect(G_OBJECT(menuitem), "activate",
6952 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6953 gtk_widget_show(menuitem);
6956 void compose_add_extra_header(gchar *header, GtkListStore *model)
6958 GtkTreeIter iter;
6959 if (strcmp(header, "")) {
6960 COMBOBOX_ADD(model, header, COMPOSE_TO);
6964 void compose_add_extra_header_entries(GtkListStore *model)
6966 FILE *exh;
6967 gchar *exhrc;
6968 gchar buf[BUFFSIZE];
6969 gint lastc;
6971 if (extra_headers == NULL) {
6972 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6973 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6974 debug_print("extra headers file not found\n");
6975 goto extra_headers_done;
6977 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6978 lastc = strlen(buf) - 1; /* remove trailing control chars */
6979 while (lastc >= 0 && buf[lastc] != ':')
6980 buf[lastc--] = '\0';
6981 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6982 buf[lastc] = '\0'; /* remove trailing : for comparison */
6983 if (custom_header_is_allowed(buf)) {
6984 buf[lastc] = ':';
6985 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6987 else
6988 g_message("disallowed extra header line: %s\n", buf);
6990 else {
6991 if (buf[0] != '#')
6992 g_message("invalid extra header line: %s\n", buf);
6995 fclose(exh);
6996 extra_headers_done:
6997 g_free(exhrc);
6998 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6999 extra_headers = g_slist_reverse(extra_headers);
7001 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7004 static void compose_create_header_entry(Compose *compose)
7006 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7008 GtkWidget *combo;
7009 GtkWidget *entry;
7010 GtkWidget *button;
7011 GtkWidget *hbox;
7012 gchar **string;
7013 const gchar *header = NULL;
7014 ComposeHeaderEntry *headerentry;
7015 gboolean standard_header = FALSE;
7016 GtkListStore *model;
7017 GtkTreeIter iter;
7019 headerentry = g_new0(ComposeHeaderEntry, 1);
7021 /* Combo box model */
7022 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7023 #if !GTK_CHECK_VERSION(2, 24, 0)
7024 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
7025 #endif
7026 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7027 COMPOSE_TO);
7028 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7029 COMPOSE_CC);
7030 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7031 COMPOSE_BCC);
7032 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7033 COMPOSE_NEWSGROUPS);
7034 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7035 COMPOSE_REPLYTO);
7036 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7037 COMPOSE_FOLLOWUPTO);
7038 compose_add_extra_header_entries(model);
7040 /* Combo box */
7041 #if GTK_CHECK_VERSION(2, 24, 0)
7042 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7043 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7044 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7045 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7046 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7047 #endif
7048 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7049 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7050 G_CALLBACK(compose_grab_focus_cb), compose);
7051 gtk_widget_show(combo);
7053 /* Putting only the combobox child into focus chain of its parent causes
7054 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7055 * This eliminates need to pres Tab twice in order to really get from the
7056 * combobox to next widget. */
7057 GList *l = NULL;
7058 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7059 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7060 g_list_free(l);
7062 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7063 compose->header_nextrow, compose->header_nextrow+1,
7064 GTK_SHRINK, GTK_FILL, 0, 0);
7065 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7066 const gchar *last_header_entry = gtk_entry_get_text(
7067 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7068 string = headers;
7069 while (*string != NULL) {
7070 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7071 standard_header = TRUE;
7072 string++;
7074 if (standard_header)
7075 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7077 if (!compose->header_last || !standard_header) {
7078 switch(compose->account->protocol) {
7079 case A_NNTP:
7080 header = prefs_common_translated_header_name("Newsgroups:");
7081 break;
7082 default:
7083 header = prefs_common_translated_header_name("To:");
7084 break;
7087 if (header)
7088 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7090 gtk_editable_set_editable(
7091 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7092 prefs_common.type_any_header);
7094 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7095 G_CALLBACK(compose_grab_focus_cb), compose);
7097 /* Entry field with cleanup button */
7098 button = gtk_button_new();
7099 gtk_button_set_image(GTK_BUTTON(button),
7100 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7101 gtk_widget_show(button);
7102 CLAWS_SET_TIP(button,
7103 _("Delete entry contents"));
7104 entry = gtk_entry_new();
7105 gtk_widget_show(entry);
7106 CLAWS_SET_TIP(entry,
7107 _("Use <tab> to autocomplete from addressbook"));
7108 hbox = gtk_hbox_new (FALSE, 0);
7109 gtk_widget_show(hbox);
7110 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7111 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7112 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7113 compose->header_nextrow, compose->header_nextrow+1,
7114 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7116 g_signal_connect(G_OBJECT(entry), "key-press-event",
7117 G_CALLBACK(compose_headerentry_key_press_event_cb),
7118 headerentry);
7119 g_signal_connect(G_OBJECT(entry), "changed",
7120 G_CALLBACK(compose_headerentry_changed_cb),
7121 headerentry);
7122 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7123 G_CALLBACK(compose_grab_focus_cb), compose);
7125 g_signal_connect(G_OBJECT(button), "clicked",
7126 G_CALLBACK(compose_headerentry_button_clicked_cb),
7127 headerentry);
7129 /* email dnd */
7130 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7131 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7132 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7133 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7134 G_CALLBACK(compose_header_drag_received_cb),
7135 entry);
7136 g_signal_connect(G_OBJECT(entry), "drag-drop",
7137 G_CALLBACK(compose_drag_drop),
7138 compose);
7139 g_signal_connect(G_OBJECT(entry), "populate-popup",
7140 G_CALLBACK(compose_entry_popup_extend),
7141 NULL);
7143 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7145 headerentry->compose = compose;
7146 headerentry->combo = combo;
7147 headerentry->entry = entry;
7148 headerentry->button = button;
7149 headerentry->hbox = hbox;
7150 headerentry->headernum = compose->header_nextrow;
7151 headerentry->type = PREF_NONE;
7153 compose->header_nextrow++;
7154 compose->header_last = headerentry;
7155 compose->header_list =
7156 g_slist_append(compose->header_list,
7157 headerentry);
7160 static void compose_add_header_entry(Compose *compose, const gchar *header,
7161 gchar *text, ComposePrefType pref_type)
7163 ComposeHeaderEntry *last_header = compose->header_last;
7164 gchar *tmp = g_strdup(text), *email;
7165 gboolean replyto_hdr;
7167 replyto_hdr = (!strcasecmp(header,
7168 prefs_common_translated_header_name("Reply-To:")) ||
7169 !strcasecmp(header,
7170 prefs_common_translated_header_name("Followup-To:")) ||
7171 !strcasecmp(header,
7172 prefs_common_translated_header_name("In-Reply-To:")));
7174 extract_address(tmp);
7175 email = g_utf8_strdown(tmp, -1);
7177 if (replyto_hdr == FALSE &&
7178 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7180 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7181 header, text, (gint) pref_type);
7182 g_free(email);
7183 g_free(tmp);
7184 return;
7187 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7188 gtk_entry_set_text(GTK_ENTRY(
7189 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7190 else
7191 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7192 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7193 last_header->type = pref_type;
7195 if (replyto_hdr == FALSE)
7196 g_hash_table_insert(compose->email_hashtable, email,
7197 GUINT_TO_POINTER(1));
7198 else
7199 g_free(email);
7201 g_free(tmp);
7204 static void compose_destroy_headerentry(Compose *compose,
7205 ComposeHeaderEntry *headerentry)
7207 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7208 gchar *email;
7210 extract_address(text);
7211 email = g_utf8_strdown(text, -1);
7212 g_hash_table_remove(compose->email_hashtable, email);
7213 g_free(text);
7214 g_free(email);
7216 gtk_widget_destroy(headerentry->combo);
7217 gtk_widget_destroy(headerentry->entry);
7218 gtk_widget_destroy(headerentry->button);
7219 gtk_widget_destroy(headerentry->hbox);
7220 g_free(headerentry);
7223 static void compose_remove_header_entries(Compose *compose)
7225 GSList *list;
7226 for (list = compose->header_list; list; list = list->next)
7227 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7229 compose->header_last = NULL;
7230 g_slist_free(compose->header_list);
7231 compose->header_list = NULL;
7232 compose->header_nextrow = 1;
7233 compose_create_header_entry(compose);
7236 static GtkWidget *compose_create_header(Compose *compose)
7238 GtkWidget *from_optmenu_hbox;
7239 GtkWidget *header_table_main;
7240 GtkWidget *header_scrolledwin;
7241 GtkWidget *header_table;
7243 /* parent with account selection and from header */
7244 header_table_main = gtk_table_new(2, 2, FALSE);
7245 gtk_widget_show(header_table_main);
7246 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7248 from_optmenu_hbox = compose_account_option_menu_create(compose);
7249 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7250 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7252 /* child with header labels and entries */
7253 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7254 gtk_widget_show(header_scrolledwin);
7255 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7257 header_table = gtk_table_new(2, 2, FALSE);
7258 gtk_widget_show(header_table);
7259 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7260 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7261 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7262 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7263 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7265 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7266 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7268 compose->header_table = header_table;
7269 compose->header_list = NULL;
7270 compose->header_nextrow = 0;
7272 compose_create_header_entry(compose);
7274 compose->table = NULL;
7276 return header_table_main;
7279 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7281 Compose *compose = (Compose *)data;
7282 GdkEventButton event;
7284 event.button = 3;
7285 event.time = gtk_get_current_event_time();
7287 return attach_button_pressed(compose->attach_clist, &event, compose);
7290 static GtkWidget *compose_create_attach(Compose *compose)
7292 GtkWidget *attach_scrwin;
7293 GtkWidget *attach_clist;
7295 GtkListStore *store;
7296 GtkCellRenderer *renderer;
7297 GtkTreeViewColumn *column;
7298 GtkTreeSelection *selection;
7300 /* attachment list */
7301 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7302 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7303 GTK_POLICY_AUTOMATIC,
7304 GTK_POLICY_AUTOMATIC);
7305 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7307 store = gtk_list_store_new(N_ATTACH_COLS,
7308 G_TYPE_STRING,
7309 G_TYPE_STRING,
7310 G_TYPE_STRING,
7311 G_TYPE_STRING,
7312 G_TYPE_POINTER,
7313 G_TYPE_AUTO_POINTER,
7314 -1);
7315 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7316 (GTK_TREE_MODEL(store)));
7317 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7318 g_object_unref(store);
7320 renderer = gtk_cell_renderer_text_new();
7321 column = gtk_tree_view_column_new_with_attributes
7322 (_("Mime type"), renderer, "text",
7323 COL_MIMETYPE, NULL);
7324 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7326 renderer = gtk_cell_renderer_text_new();
7327 column = gtk_tree_view_column_new_with_attributes
7328 (_("Size"), renderer, "text",
7329 COL_SIZE, NULL);
7330 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7332 renderer = gtk_cell_renderer_text_new();
7333 column = gtk_tree_view_column_new_with_attributes
7334 (_("Name"), renderer, "text",
7335 COL_NAME, NULL);
7336 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7338 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7339 prefs_common.use_stripes_everywhere);
7340 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7341 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7343 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7344 G_CALLBACK(attach_selected), compose);
7345 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7346 G_CALLBACK(attach_button_pressed), compose);
7347 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7348 G_CALLBACK(popup_attach_button_pressed), compose);
7349 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7350 G_CALLBACK(attach_key_pressed), compose);
7352 /* drag and drop */
7353 gtk_drag_dest_set(attach_clist,
7354 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7355 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7356 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7357 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7358 G_CALLBACK(compose_attach_drag_received_cb),
7359 compose);
7360 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7361 G_CALLBACK(compose_drag_drop),
7362 compose);
7364 compose->attach_scrwin = attach_scrwin;
7365 compose->attach_clist = attach_clist;
7367 return attach_scrwin;
7370 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7371 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7373 static GtkWidget *compose_create_others(Compose *compose)
7375 GtkWidget *table;
7376 GtkWidget *savemsg_checkbtn;
7377 GtkWidget *savemsg_combo;
7378 GtkWidget *savemsg_select;
7380 guint rowcount = 0;
7381 gchar *folderidentifier;
7383 /* Table for settings */
7384 table = gtk_table_new(3, 1, FALSE);
7385 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7386 gtk_widget_show(table);
7387 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7388 rowcount = 0;
7390 /* Save Message to folder */
7391 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7392 gtk_widget_show(savemsg_checkbtn);
7393 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7394 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7395 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7397 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7398 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7400 #if !GTK_CHECK_VERSION(2, 24, 0)
7401 savemsg_combo = gtk_combo_box_entry_new_text();
7402 #else
7403 savemsg_combo = gtk_combo_box_text_new_with_entry();
7404 #endif
7405 compose->savemsg_checkbtn = savemsg_checkbtn;
7406 compose->savemsg_combo = savemsg_combo;
7407 gtk_widget_show(savemsg_combo);
7409 if (prefs_common.compose_save_to_history)
7410 #if !GTK_CHECK_VERSION(2, 24, 0)
7411 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7412 prefs_common.compose_save_to_history);
7413 #else
7414 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7415 prefs_common.compose_save_to_history);
7416 #endif
7417 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7418 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7419 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7420 G_CALLBACK(compose_grab_focus_cb), compose);
7421 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7422 folderidentifier = folder_item_get_identifier(account_get_special_folder
7423 (compose->account, F_OUTBOX));
7424 compose_set_save_to(compose, folderidentifier);
7425 g_free(folderidentifier);
7428 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7429 gtk_widget_show(savemsg_select);
7430 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7431 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7432 G_CALLBACK(compose_savemsg_select_cb),
7433 compose);
7435 return table;
7438 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7440 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7441 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7444 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7446 FolderItem *dest;
7447 gchar * path;
7449 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7450 if (!dest) return;
7452 path = folder_item_get_identifier(dest);
7454 compose_set_save_to(compose, path);
7455 g_free(path);
7458 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7459 GdkAtom clip, GtkTextIter *insert_place);
7462 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7463 Compose *compose)
7465 gint prev_autowrap;
7466 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7467 #if USE_ENCHANT
7468 if (event->button == 3) {
7469 GtkTextIter iter;
7470 GtkTextIter sel_start, sel_end;
7471 gboolean stuff_selected;
7472 gint x, y;
7473 /* move the cursor to allow GtkAspell to check the word
7474 * under the mouse */
7475 if (event->x && event->y) {
7476 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7477 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7478 &x, &y);
7479 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7480 &iter, x, y);
7481 } else {
7482 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7483 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7485 /* get selection */
7486 stuff_selected = gtk_text_buffer_get_selection_bounds(
7487 buffer,
7488 &sel_start, &sel_end);
7490 gtk_text_buffer_place_cursor (buffer, &iter);
7491 /* reselect stuff */
7492 if (stuff_selected
7493 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7494 gtk_text_buffer_select_range(buffer,
7495 &sel_start, &sel_end);
7497 return FALSE; /* pass the event so that the right-click goes through */
7499 #endif
7500 if (event->button == 2) {
7501 GtkTextIter iter;
7502 gint x, y;
7503 BLOCK_WRAP();
7505 /* get the middle-click position to paste at the correct place */
7506 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7507 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7508 &x, &y);
7509 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7510 &iter, x, y);
7512 entry_paste_clipboard(compose, text,
7513 prefs_common.linewrap_pastes,
7514 GDK_SELECTION_PRIMARY, &iter);
7515 UNBLOCK_WRAP();
7516 return TRUE;
7518 return FALSE;
7521 #if USE_ENCHANT
7522 static void compose_spell_menu_changed(void *data)
7524 Compose *compose = (Compose *)data;
7525 GSList *items;
7526 GtkWidget *menuitem;
7527 GtkWidget *parent_item;
7528 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7529 GSList *spell_menu;
7531 if (compose->gtkaspell == NULL)
7532 return;
7534 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7535 "/Menu/Spelling/Options");
7537 /* setting the submenu removes /Spelling/Options from the factory
7538 * so we need to save it */
7540 if (parent_item == NULL) {
7541 parent_item = compose->aspell_options_menu;
7542 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7543 } else
7544 compose->aspell_options_menu = parent_item;
7546 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7548 spell_menu = g_slist_reverse(spell_menu);
7549 for (items = spell_menu;
7550 items; items = items->next) {
7551 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7552 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7553 gtk_widget_show(GTK_WIDGET(menuitem));
7555 g_slist_free(spell_menu);
7557 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7558 gtk_widget_show(parent_item);
7561 static void compose_dict_changed(void *data)
7563 Compose *compose = (Compose *) data;
7565 if(!compose->gtkaspell)
7566 return;
7567 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7568 return;
7570 gtkaspell_highlight_all(compose->gtkaspell);
7571 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7573 #endif
7575 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7577 Compose *compose = (Compose *)data;
7578 GdkEventButton event;
7580 event.button = 3;
7581 event.time = gtk_get_current_event_time();
7582 event.x = 0;
7583 event.y = 0;
7585 return text_clicked(compose->text, &event, compose);
7588 static gboolean compose_force_window_origin = TRUE;
7589 static Compose *compose_create(PrefsAccount *account,
7590 FolderItem *folder,
7591 ComposeMode mode,
7592 gboolean batch)
7594 Compose *compose;
7595 GtkWidget *window;
7596 GtkWidget *vbox;
7597 GtkWidget *menubar;
7598 GtkWidget *handlebox;
7600 GtkWidget *notebook;
7602 GtkWidget *attach_hbox;
7603 GtkWidget *attach_lab1;
7604 GtkWidget *attach_lab2;
7606 GtkWidget *vbox2;
7608 GtkWidget *label;
7609 GtkWidget *subject_hbox;
7610 GtkWidget *subject_frame;
7611 GtkWidget *subject_entry;
7612 GtkWidget *subject;
7613 GtkWidget *paned;
7615 GtkWidget *edit_vbox;
7616 GtkWidget *ruler_hbox;
7617 GtkWidget *ruler;
7618 GtkWidget *scrolledwin;
7619 GtkWidget *text;
7620 GtkTextBuffer *buffer;
7621 GtkClipboard *clipboard;
7623 UndoMain *undostruct;
7625 GtkWidget *popupmenu;
7626 GtkWidget *tmpl_menu;
7627 GtkActionGroup *action_group = NULL;
7629 #if USE_ENCHANT
7630 GtkAspell * gtkaspell = NULL;
7631 #endif
7633 static GdkGeometry geometry;
7635 cm_return_val_if_fail(account != NULL, NULL);
7637 gtkut_convert_int_to_gdk_color(prefs_common.default_header_bgcolor,
7638 &default_header_bgcolor);
7639 gtkut_convert_int_to_gdk_color(prefs_common.default_header_color,
7640 &default_header_color);
7642 debug_print("Creating compose window...\n");
7643 compose = g_new0(Compose, 1);
7645 compose->batch = batch;
7646 compose->account = account;
7647 compose->folder = folder;
7649 compose->mutex = cm_mutex_new();
7650 compose->set_cursor_pos = -1;
7652 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7654 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7655 gtk_widget_set_size_request(window, prefs_common.compose_width,
7656 prefs_common.compose_height);
7658 if (!geometry.max_width) {
7659 geometry.max_width = gdk_screen_width();
7660 geometry.max_height = gdk_screen_height();
7663 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7664 &geometry, GDK_HINT_MAX_SIZE);
7665 if (!geometry.min_width) {
7666 geometry.min_width = 600;
7667 geometry.min_height = 440;
7669 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7670 &geometry, GDK_HINT_MIN_SIZE);
7672 #ifndef GENERIC_UMPC
7673 if (compose_force_window_origin)
7674 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7675 prefs_common.compose_y);
7676 #endif
7677 g_signal_connect(G_OBJECT(window), "delete_event",
7678 G_CALLBACK(compose_delete_cb), compose);
7679 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7680 gtk_widget_realize(window);
7682 gtkut_widget_set_composer_icon(window);
7684 vbox = gtk_vbox_new(FALSE, 0);
7685 gtk_container_add(GTK_CONTAINER(window), vbox);
7687 compose->ui_manager = gtk_ui_manager_new();
7688 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7689 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7690 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7691 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7692 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7693 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7694 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7695 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7696 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7697 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7699 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7701 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7702 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7703 #ifdef USE_ENCHANT
7704 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7705 #endif
7706 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7707 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7708 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7710 /* Compose menu */
7711 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7712 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7713 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7714 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7715 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7716 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7717 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7718 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7719 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7720 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7722 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7725 /* Edit menu */
7726 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7727 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7728 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7730 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7731 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7732 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7734 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7735 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7736 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7737 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7739 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7741 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7742 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7743 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7744 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7745 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7746 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7747 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7748 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7750 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7751 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7752 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7753 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7754 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7755 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7757 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7759 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7760 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7761 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7762 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7763 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7765 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7767 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7769 #if USE_ENCHANT
7770 /* Spelling menu */
7771 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7772 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7773 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7774 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7775 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7776 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7777 #endif
7779 /* Options menu */
7780 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7781 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7782 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7783 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7784 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7786 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7787 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7788 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7789 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7790 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7793 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7794 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7795 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7796 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7797 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7798 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7799 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7801 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7802 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7803 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7804 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7805 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7807 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7809 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7810 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7811 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7812 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7813 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7815 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7816 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)
7817 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)
7818 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7820 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7823 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)
7824 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)
7826 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7828 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7829 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)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7833 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)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7839 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)
7840 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7841 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7846 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)
7847 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)
7848 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7854 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7860 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)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7865 /* phew. */
7867 /* Tools menu */
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7875 /* Help menu */
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7878 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7879 gtk_widget_show_all(menubar);
7881 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7882 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7884 if (prefs_common.toolbar_detachable) {
7885 handlebox = gtk_handle_box_new();
7886 } else {
7887 handlebox = gtk_hbox_new(FALSE, 0);
7889 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7891 gtk_widget_realize(handlebox);
7892 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7893 (gpointer)compose);
7895 vbox2 = gtk_vbox_new(FALSE, 2);
7896 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7897 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7899 /* Notebook */
7900 notebook = gtk_notebook_new();
7901 gtk_widget_show(notebook);
7903 /* header labels and entries */
7904 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7905 compose_create_header(compose),
7906 gtk_label_new_with_mnemonic(_("Hea_der")));
7907 /* attachment list */
7908 attach_hbox = gtk_hbox_new(FALSE, 0);
7909 gtk_widget_show(attach_hbox);
7911 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7912 gtk_widget_show(attach_lab1);
7913 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7915 attach_lab2 = gtk_label_new("");
7916 gtk_widget_show(attach_lab2);
7917 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7919 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7920 compose_create_attach(compose),
7921 attach_hbox);
7922 /* Others Tab */
7923 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7924 compose_create_others(compose),
7925 gtk_label_new_with_mnemonic(_("Othe_rs")));
7927 /* Subject */
7928 subject_hbox = gtk_hbox_new(FALSE, 0);
7929 gtk_widget_show(subject_hbox);
7931 subject_frame = gtk_frame_new(NULL);
7932 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7933 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7934 gtk_widget_show(subject_frame);
7936 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7937 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7938 gtk_widget_show(subject);
7940 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7941 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7942 gtk_widget_show(label);
7944 #ifdef USE_ENCHANT
7945 subject_entry = claws_spell_entry_new();
7946 #else
7947 subject_entry = gtk_entry_new();
7948 #endif
7949 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7950 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7951 G_CALLBACK(compose_grab_focus_cb), compose);
7952 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7953 gtk_widget_show(subject_entry);
7954 compose->subject_entry = subject_entry;
7955 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7957 edit_vbox = gtk_vbox_new(FALSE, 0);
7959 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7961 /* ruler */
7962 ruler_hbox = gtk_hbox_new(FALSE, 0);
7963 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7965 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7966 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7967 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7968 BORDER_WIDTH);
7970 /* text widget */
7971 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7972 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7973 GTK_POLICY_AUTOMATIC,
7974 GTK_POLICY_AUTOMATIC);
7975 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7976 GTK_SHADOW_IN);
7977 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7979 text = gtk_text_view_new();
7980 if (prefs_common.show_compose_margin) {
7981 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7982 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7984 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7985 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7986 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7987 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7988 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7990 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7991 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7992 G_CALLBACK(compose_edit_size_alloc),
7993 ruler);
7994 g_signal_connect(G_OBJECT(buffer), "changed",
7995 G_CALLBACK(compose_changed_cb), compose);
7996 g_signal_connect(G_OBJECT(text), "grab_focus",
7997 G_CALLBACK(compose_grab_focus_cb), compose);
7998 g_signal_connect(G_OBJECT(buffer), "insert_text",
7999 G_CALLBACK(text_inserted), compose);
8000 g_signal_connect(G_OBJECT(text), "button_press_event",
8001 G_CALLBACK(text_clicked), compose);
8002 g_signal_connect(G_OBJECT(text), "popup-menu",
8003 G_CALLBACK(compose_popup_menu), compose);
8004 g_signal_connect(G_OBJECT(subject_entry), "changed",
8005 G_CALLBACK(compose_changed_cb), compose);
8006 g_signal_connect(G_OBJECT(subject_entry), "activate",
8007 G_CALLBACK(compose_subject_entry_activated), compose);
8009 /* drag and drop */
8010 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8011 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8012 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8013 g_signal_connect(G_OBJECT(text), "drag_data_received",
8014 G_CALLBACK(compose_insert_drag_received_cb),
8015 compose);
8016 g_signal_connect(G_OBJECT(text), "drag-drop",
8017 G_CALLBACK(compose_drag_drop),
8018 compose);
8019 g_signal_connect(G_OBJECT(text), "key-press-event",
8020 G_CALLBACK(completion_set_focus_to_subject),
8021 compose);
8022 gtk_widget_show_all(vbox);
8024 /* pane between attach clist and text */
8025 paned = gtk_vpaned_new();
8026 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8027 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8028 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8029 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8030 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8031 G_CALLBACK(compose_notebook_size_alloc), paned);
8033 gtk_widget_show_all(paned);
8036 if (prefs_common.textfont) {
8037 PangoFontDescription *font_desc;
8039 font_desc = pango_font_description_from_string
8040 (prefs_common.textfont);
8041 if (font_desc) {
8042 gtk_widget_modify_font(text, font_desc);
8043 pango_font_description_free(font_desc);
8047 gtk_action_group_add_actions(action_group, compose_popup_entries,
8048 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8049 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8050 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8051 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8052 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8053 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8054 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8056 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8058 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8059 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8060 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8062 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8064 undostruct = undo_init(text);
8065 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8066 compose);
8068 address_completion_start(window);
8070 compose->window = window;
8071 compose->vbox = vbox;
8072 compose->menubar = menubar;
8073 compose->handlebox = handlebox;
8075 compose->vbox2 = vbox2;
8077 compose->paned = paned;
8079 compose->attach_label = attach_lab2;
8081 compose->notebook = notebook;
8082 compose->edit_vbox = edit_vbox;
8083 compose->ruler_hbox = ruler_hbox;
8084 compose->ruler = ruler;
8085 compose->scrolledwin = scrolledwin;
8086 compose->text = text;
8088 compose->focused_editable = NULL;
8090 compose->popupmenu = popupmenu;
8092 compose->tmpl_menu = tmpl_menu;
8094 compose->mode = mode;
8095 compose->rmode = mode;
8097 compose->targetinfo = NULL;
8098 compose->replyinfo = NULL;
8099 compose->fwdinfo = NULL;
8101 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8102 g_str_equal, (GDestroyNotify) g_free, NULL);
8104 compose->replyto = NULL;
8105 compose->cc = NULL;
8106 compose->bcc = NULL;
8107 compose->followup_to = NULL;
8109 compose->ml_post = NULL;
8111 compose->inreplyto = NULL;
8112 compose->references = NULL;
8113 compose->msgid = NULL;
8114 compose->boundary = NULL;
8116 compose->autowrap = prefs_common.autowrap;
8117 compose->autoindent = prefs_common.auto_indent;
8118 compose->use_signing = FALSE;
8119 compose->use_encryption = FALSE;
8120 compose->privacy_system = NULL;
8121 compose->encdata = NULL;
8123 compose->modified = FALSE;
8125 compose->return_receipt = FALSE;
8127 compose->to_list = NULL;
8128 compose->newsgroup_list = NULL;
8130 compose->undostruct = undostruct;
8132 compose->sig_str = NULL;
8134 compose->exteditor_file = NULL;
8135 compose->exteditor_pid = -1;
8136 compose->exteditor_tag = -1;
8137 compose->exteditor_socket = NULL;
8138 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8140 compose->folder_update_callback_id =
8141 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8142 compose_update_folder_hook,
8143 (gpointer) compose);
8145 #if USE_ENCHANT
8146 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8147 if (mode != COMPOSE_REDIRECT) {
8148 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8149 strcmp(prefs_common.dictionary, "")) {
8150 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8151 prefs_common.alt_dictionary,
8152 conv_get_locale_charset_str(),
8153 prefs_common.misspelled_col,
8154 prefs_common.check_while_typing,
8155 prefs_common.recheck_when_changing_dict,
8156 prefs_common.use_alternate,
8157 prefs_common.use_both_dicts,
8158 GTK_TEXT_VIEW(text),
8159 GTK_WINDOW(compose->window),
8160 compose_dict_changed,
8161 compose_spell_menu_changed,
8162 compose);
8163 if (!gtkaspell) {
8164 alertpanel_error(_("Spell checker could not "
8165 "be started.\n%s"),
8166 gtkaspell_checkers_strerror());
8167 gtkaspell_checkers_reset_error();
8168 } else {
8169 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8173 compose->gtkaspell = gtkaspell;
8174 compose_spell_menu_changed(compose);
8175 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8176 #endif
8178 compose_select_account(compose, account, TRUE);
8180 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8181 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8183 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8184 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8186 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8187 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8189 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8190 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8192 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8193 if (account->protocol != A_NNTP)
8194 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8195 prefs_common_translated_header_name("To:"));
8196 else
8197 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8198 prefs_common_translated_header_name("Newsgroups:"));
8200 #ifndef USE_ALT_ADDRBOOK
8201 addressbook_set_target_compose(compose);
8202 #endif
8203 if (mode != COMPOSE_REDIRECT)
8204 compose_set_template_menu(compose);
8205 else {
8206 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8209 compose_list = g_list_append(compose_list, compose);
8211 if (!prefs_common.show_ruler)
8212 gtk_widget_hide(ruler_hbox);
8214 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8216 /* Priority */
8217 compose->priority = PRIORITY_NORMAL;
8218 compose_update_priority_menu_item(compose);
8220 compose_set_out_encoding(compose);
8222 /* Actions menu */
8223 compose_update_actions_menu(compose);
8225 /* Privacy Systems menu */
8226 compose_update_privacy_systems_menu(compose);
8228 activate_privacy_system(compose, account, TRUE);
8229 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8230 if (batch) {
8231 gtk_widget_realize(window);
8232 } else {
8233 gtk_widget_show(window);
8236 return compose;
8239 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8241 GList *accounts;
8242 GtkWidget *hbox;
8243 GtkWidget *optmenu;
8244 GtkWidget *optmenubox;
8245 GtkWidget *fromlabel;
8246 GtkListStore *menu;
8247 GtkTreeIter iter;
8248 GtkWidget *from_name = NULL;
8250 gint num = 0, def_menu = 0;
8252 accounts = account_get_list();
8253 cm_return_val_if_fail(accounts != NULL, NULL);
8255 optmenubox = gtk_event_box_new();
8256 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8257 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8259 hbox = gtk_hbox_new(FALSE, 4);
8260 from_name = gtk_entry_new();
8262 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8263 G_CALLBACK(compose_grab_focus_cb), compose);
8264 g_signal_connect_after(G_OBJECT(from_name), "activate",
8265 G_CALLBACK(from_name_activate_cb), optmenu);
8267 for (; accounts != NULL; accounts = accounts->next, num++) {
8268 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8269 gchar *name, *from = NULL;
8271 if (ac == compose->account) def_menu = num;
8273 name = g_markup_printf_escaped("<i>%s</i>",
8274 ac->account_name);
8276 if (ac == compose->account) {
8277 if (ac->name && *ac->name) {
8278 gchar *buf;
8279 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8280 from = g_strdup_printf("%s <%s>",
8281 buf, ac->address);
8282 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8283 } else {
8284 from = g_strdup_printf("%s",
8285 ac->address);
8286 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8288 if (cur_account != compose->account) {
8289 gtk_widget_modify_base(
8290 GTK_WIDGET(from_name),
8291 GTK_STATE_NORMAL, &default_header_bgcolor);
8292 gtk_widget_modify_text(
8293 GTK_WIDGET(from_name),
8294 GTK_STATE_NORMAL, &default_header_color);
8297 COMBOBOX_ADD(menu, name, ac->account_id);
8298 g_free(name);
8299 g_free(from);
8302 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8304 g_signal_connect(G_OBJECT(optmenu), "changed",
8305 G_CALLBACK(account_activated),
8306 compose);
8307 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8308 G_CALLBACK(compose_entry_popup_extend),
8309 NULL);
8311 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8312 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8314 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8315 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8316 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8318 /* Putting only the GtkEntry into focus chain of parent hbox causes
8319 * the account selector combobox next to it to be unreachable when
8320 * navigating widgets in GtkTable with up/down arrow keys.
8321 * Note: gtk_widget_set_can_focus() was not enough. */
8322 GList *l = NULL;
8323 l = g_list_prepend(l, from_name);
8324 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8325 g_list_free(l);
8327 CLAWS_SET_TIP(optmenubox,
8328 _("Account to use for this email"));
8329 CLAWS_SET_TIP(from_name,
8330 _("Sender address to be used"));
8332 compose->account_combo = optmenu;
8333 compose->from_name = from_name;
8335 return hbox;
8338 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8340 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8341 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8342 Compose *compose = (Compose *) data;
8343 if (active) {
8344 compose->priority = value;
8348 static void compose_reply_change_mode(Compose *compose,
8349 ComposeMode action)
8351 gboolean was_modified = compose->modified;
8353 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8355 cm_return_if_fail(compose->replyinfo != NULL);
8357 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8358 ml = TRUE;
8359 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8360 followup = TRUE;
8361 if (action == COMPOSE_REPLY_TO_ALL)
8362 all = TRUE;
8363 if (action == COMPOSE_REPLY_TO_SENDER)
8364 sender = TRUE;
8365 if (action == COMPOSE_REPLY_TO_LIST)
8366 ml = TRUE;
8368 compose_remove_header_entries(compose);
8369 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8370 if (compose->account->set_autocc && compose->account->auto_cc)
8371 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8373 if (compose->account->set_autobcc && compose->account->auto_bcc)
8374 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8376 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8377 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8378 compose_show_first_last_header(compose, TRUE);
8379 compose->modified = was_modified;
8380 compose_set_title(compose);
8383 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8385 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8386 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8387 Compose *compose = (Compose *) data;
8389 if (active)
8390 compose_reply_change_mode(compose, value);
8393 static void compose_update_priority_menu_item(Compose * compose)
8395 GtkWidget *menuitem = NULL;
8396 switch (compose->priority) {
8397 case PRIORITY_HIGHEST:
8398 menuitem = gtk_ui_manager_get_widget
8399 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8400 break;
8401 case PRIORITY_HIGH:
8402 menuitem = gtk_ui_manager_get_widget
8403 (compose->ui_manager, "/Menu/Options/Priority/High");
8404 break;
8405 case PRIORITY_NORMAL:
8406 menuitem = gtk_ui_manager_get_widget
8407 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8408 break;
8409 case PRIORITY_LOW:
8410 menuitem = gtk_ui_manager_get_widget
8411 (compose->ui_manager, "/Menu/Options/Priority/Low");
8412 break;
8413 case PRIORITY_LOWEST:
8414 menuitem = gtk_ui_manager_get_widget
8415 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8416 break;
8418 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8421 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8423 Compose *compose = (Compose *) data;
8424 gchar *systemid;
8425 gboolean can_sign = FALSE, can_encrypt = FALSE;
8427 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8429 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8430 return;
8432 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8433 g_free(compose->privacy_system);
8434 compose->privacy_system = NULL;
8435 g_free(compose->encdata);
8436 compose->encdata = NULL;
8437 if (systemid != NULL) {
8438 compose->privacy_system = g_strdup(systemid);
8440 can_sign = privacy_system_can_sign(systemid);
8441 can_encrypt = privacy_system_can_encrypt(systemid);
8444 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8446 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8447 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8450 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8452 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8453 GtkWidget *menuitem = NULL;
8454 GList *children, *amenu;
8455 gboolean can_sign = FALSE, can_encrypt = FALSE;
8456 gboolean found = FALSE;
8458 if (compose->privacy_system != NULL) {
8459 gchar *systemid;
8460 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8461 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8462 cm_return_if_fail(menuitem != NULL);
8464 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8465 amenu = children;
8466 menuitem = NULL;
8467 while (amenu != NULL) {
8468 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8469 if (systemid != NULL) {
8470 if (strcmp(systemid, compose->privacy_system) == 0 &&
8471 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8472 menuitem = GTK_WIDGET(amenu->data);
8474 can_sign = privacy_system_can_sign(systemid);
8475 can_encrypt = privacy_system_can_encrypt(systemid);
8476 found = TRUE;
8477 break;
8479 } else if (strlen(compose->privacy_system) == 0 &&
8480 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8481 menuitem = GTK_WIDGET(amenu->data);
8483 can_sign = FALSE;
8484 can_encrypt = FALSE;
8485 found = TRUE;
8486 break;
8489 amenu = amenu->next;
8491 g_list_free(children);
8492 if (menuitem != NULL)
8493 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8495 if (warn && !found && strlen(compose->privacy_system)) {
8496 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8497 "will not be able to sign or encrypt this message."),
8498 compose->privacy_system);
8502 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8503 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8506 static void compose_set_out_encoding(Compose *compose)
8508 CharSet out_encoding;
8509 const gchar *branch = NULL;
8510 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8512 switch(out_encoding) {
8513 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8514 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8515 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8516 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8517 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8518 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8519 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8520 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8521 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8522 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8523 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8524 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8525 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8526 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8527 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8528 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8529 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8530 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8531 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8532 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8533 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8534 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8535 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8536 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8537 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8538 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8539 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8540 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8541 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8542 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8543 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8544 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8545 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8546 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8548 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8551 static void compose_set_template_menu(Compose *compose)
8553 GSList *tmpl_list, *cur;
8554 GtkWidget *menu;
8555 GtkWidget *item;
8557 tmpl_list = template_get_config();
8559 menu = gtk_menu_new();
8561 gtk_menu_set_accel_group (GTK_MENU (menu),
8562 gtk_ui_manager_get_accel_group(compose->ui_manager));
8563 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8564 Template *tmpl = (Template *)cur->data;
8565 gchar *accel_path = NULL;
8566 item = gtk_menu_item_new_with_label(tmpl->name);
8567 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8568 g_signal_connect(G_OBJECT(item), "activate",
8569 G_CALLBACK(compose_template_activate_cb),
8570 compose);
8571 g_object_set_data(G_OBJECT(item), "template", tmpl);
8572 gtk_widget_show(item);
8573 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8574 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8575 g_free(accel_path);
8578 gtk_widget_show(menu);
8579 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8582 void compose_update_actions_menu(Compose *compose)
8584 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8587 static void compose_update_privacy_systems_menu(Compose *compose)
8589 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8590 GSList *systems, *cur;
8591 GtkWidget *widget;
8592 GtkWidget *system_none;
8593 GSList *group;
8594 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8595 GtkWidget *privacy_menu = gtk_menu_new();
8597 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8598 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8600 g_signal_connect(G_OBJECT(system_none), "activate",
8601 G_CALLBACK(compose_set_privacy_system_cb), compose);
8603 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8604 gtk_widget_show(system_none);
8606 systems = privacy_get_system_ids();
8607 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8608 gchar *systemid = cur->data;
8610 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8611 widget = gtk_radio_menu_item_new_with_label(group,
8612 privacy_system_get_name(systemid));
8613 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8614 g_strdup(systemid), g_free);
8615 g_signal_connect(G_OBJECT(widget), "activate",
8616 G_CALLBACK(compose_set_privacy_system_cb), compose);
8618 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8619 gtk_widget_show(widget);
8620 g_free(systemid);
8622 g_slist_free(systems);
8623 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8624 gtk_widget_show_all(privacy_menu);
8625 gtk_widget_show_all(privacy_menuitem);
8628 void compose_reflect_prefs_all(void)
8630 GList *cur;
8631 Compose *compose;
8633 for (cur = compose_list; cur != NULL; cur = cur->next) {
8634 compose = (Compose *)cur->data;
8635 compose_set_template_menu(compose);
8639 void compose_reflect_prefs_pixmap_theme(void)
8641 GList *cur;
8642 Compose *compose;
8644 for (cur = compose_list; cur != NULL; cur = cur->next) {
8645 compose = (Compose *)cur->data;
8646 toolbar_update(TOOLBAR_COMPOSE, compose);
8650 static const gchar *compose_quote_char_from_context(Compose *compose)
8652 const gchar *qmark = NULL;
8654 cm_return_val_if_fail(compose != NULL, NULL);
8656 switch (compose->mode) {
8657 /* use forward-specific quote char */
8658 case COMPOSE_FORWARD:
8659 case COMPOSE_FORWARD_AS_ATTACH:
8660 case COMPOSE_FORWARD_INLINE:
8661 if (compose->folder && compose->folder->prefs &&
8662 compose->folder->prefs->forward_with_format)
8663 qmark = compose->folder->prefs->forward_quotemark;
8664 else if (compose->account->forward_with_format)
8665 qmark = compose->account->forward_quotemark;
8666 else
8667 qmark = prefs_common.fw_quotemark;
8668 break;
8670 /* use reply-specific quote char in all other modes */
8671 default:
8672 if (compose->folder && compose->folder->prefs &&
8673 compose->folder->prefs->reply_with_format)
8674 qmark = compose->folder->prefs->reply_quotemark;
8675 else if (compose->account->reply_with_format)
8676 qmark = compose->account->reply_quotemark;
8677 else
8678 qmark = prefs_common.quotemark;
8679 break;
8682 if (qmark == NULL || *qmark == '\0')
8683 qmark = "> ";
8685 return qmark;
8688 static void compose_template_apply(Compose *compose, Template *tmpl,
8689 gboolean replace)
8691 GtkTextView *text;
8692 GtkTextBuffer *buffer;
8693 GtkTextMark *mark;
8694 GtkTextIter iter;
8695 const gchar *qmark;
8696 gchar *parsed_str = NULL;
8697 gint cursor_pos = 0;
8698 const gchar *err_msg = _("The body of the template has an error at line %d.");
8699 if (!tmpl) return;
8701 /* process the body */
8703 text = GTK_TEXT_VIEW(compose->text);
8704 buffer = gtk_text_view_get_buffer(text);
8706 if (tmpl->value) {
8707 qmark = compose_quote_char_from_context(compose);
8709 if (compose->replyinfo != NULL) {
8711 if (replace)
8712 gtk_text_buffer_set_text(buffer, "", -1);
8713 mark = gtk_text_buffer_get_insert(buffer);
8714 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8716 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8717 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8719 } else if (compose->fwdinfo != NULL) {
8721 if (replace)
8722 gtk_text_buffer_set_text(buffer, "", -1);
8723 mark = gtk_text_buffer_get_insert(buffer);
8724 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8726 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8727 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8729 } else {
8730 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8732 GtkTextIter start, end;
8733 gchar *tmp = NULL;
8735 gtk_text_buffer_get_start_iter(buffer, &start);
8736 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8737 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8739 /* clear the buffer now */
8740 if (replace)
8741 gtk_text_buffer_set_text(buffer, "", -1);
8743 parsed_str = compose_quote_fmt(compose, dummyinfo,
8744 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8745 procmsg_msginfo_free( &dummyinfo );
8747 g_free( tmp );
8749 } else {
8750 if (replace)
8751 gtk_text_buffer_set_text(buffer, "", -1);
8752 mark = gtk_text_buffer_get_insert(buffer);
8753 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8756 if (replace && parsed_str && compose->account->auto_sig)
8757 compose_insert_sig(compose, FALSE);
8759 if (replace && parsed_str) {
8760 gtk_text_buffer_get_start_iter(buffer, &iter);
8761 gtk_text_buffer_place_cursor(buffer, &iter);
8764 if (parsed_str) {
8765 cursor_pos = quote_fmt_get_cursor_pos();
8766 compose->set_cursor_pos = cursor_pos;
8767 if (cursor_pos == -1)
8768 cursor_pos = 0;
8769 gtk_text_buffer_get_start_iter(buffer, &iter);
8770 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8771 gtk_text_buffer_place_cursor(buffer, &iter);
8774 /* process the other fields */
8776 compose_template_apply_fields(compose, tmpl);
8777 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8778 quote_fmt_reset_vartable();
8779 compose_changed_cb(NULL, compose);
8781 #ifdef USE_ENCHANT
8782 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8783 gtkaspell_highlight_all(compose->gtkaspell);
8784 #endif
8787 static void compose_template_apply_fields_error(const gchar *header)
8789 gchar *tr;
8790 gchar *text;
8792 tr = g_strdup(C_("'%s' stands for a header name",
8793 "Template '%s' format error."));
8794 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8795 alertpanel_error(text);
8797 g_free(text);
8798 g_free(tr);
8801 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8803 MsgInfo* dummyinfo = NULL;
8804 MsgInfo *msginfo = NULL;
8805 gchar *buf = NULL;
8807 if (compose->replyinfo != NULL)
8808 msginfo = compose->replyinfo;
8809 else if (compose->fwdinfo != NULL)
8810 msginfo = compose->fwdinfo;
8811 else {
8812 dummyinfo = compose_msginfo_new_from_compose(compose);
8813 msginfo = dummyinfo;
8816 if (tmpl->from && *tmpl->from != '\0') {
8817 #ifdef USE_ENCHANT
8818 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8819 compose->gtkaspell);
8820 #else
8821 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8822 #endif
8823 quote_fmt_scan_string(tmpl->from);
8824 quote_fmt_parse();
8826 buf = quote_fmt_get_buffer();
8827 if (buf == NULL) {
8828 compose_template_apply_fields_error("From");
8829 } else {
8830 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8834 if (tmpl->to && *tmpl->to != '\0') {
8835 #ifdef USE_ENCHANT
8836 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8837 compose->gtkaspell);
8838 #else
8839 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8840 #endif
8841 quote_fmt_scan_string(tmpl->to);
8842 quote_fmt_parse();
8844 buf = quote_fmt_get_buffer();
8845 if (buf == NULL) {
8846 compose_template_apply_fields_error("To");
8847 } else {
8848 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8852 if (tmpl->cc && *tmpl->cc != '\0') {
8853 #ifdef USE_ENCHANT
8854 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8855 compose->gtkaspell);
8856 #else
8857 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8858 #endif
8859 quote_fmt_scan_string(tmpl->cc);
8860 quote_fmt_parse();
8862 buf = quote_fmt_get_buffer();
8863 if (buf == NULL) {
8864 compose_template_apply_fields_error("Cc");
8865 } else {
8866 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8870 if (tmpl->bcc && *tmpl->bcc != '\0') {
8871 #ifdef USE_ENCHANT
8872 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8873 compose->gtkaspell);
8874 #else
8875 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8876 #endif
8877 quote_fmt_scan_string(tmpl->bcc);
8878 quote_fmt_parse();
8880 buf = quote_fmt_get_buffer();
8881 if (buf == NULL) {
8882 compose_template_apply_fields_error("Bcc");
8883 } else {
8884 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8888 if (tmpl->replyto && *tmpl->replyto != '\0') {
8889 #ifdef USE_ENCHANT
8890 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8891 compose->gtkaspell);
8892 #else
8893 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8894 #endif
8895 quote_fmt_scan_string(tmpl->replyto);
8896 quote_fmt_parse();
8898 buf = quote_fmt_get_buffer();
8899 if (buf == NULL) {
8900 compose_template_apply_fields_error("Reply-To");
8901 } else {
8902 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8906 /* process the subject */
8907 if (tmpl->subject && *tmpl->subject != '\0') {
8908 #ifdef USE_ENCHANT
8909 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8910 compose->gtkaspell);
8911 #else
8912 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8913 #endif
8914 quote_fmt_scan_string(tmpl->subject);
8915 quote_fmt_parse();
8917 buf = quote_fmt_get_buffer();
8918 if (buf == NULL) {
8919 compose_template_apply_fields_error("Subject");
8920 } else {
8921 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8925 procmsg_msginfo_free( &dummyinfo );
8928 static void compose_destroy(Compose *compose)
8930 GtkAllocation allocation;
8931 GtkTextBuffer *buffer;
8932 GtkClipboard *clipboard;
8934 compose_list = g_list_remove(compose_list, compose);
8936 if (compose->updating) {
8937 debug_print("danger, not destroying anything now\n");
8938 compose->deferred_destroy = TRUE;
8939 return;
8942 /* NOTE: address_completion_end() does nothing with the window
8943 * however this may change. */
8944 address_completion_end(compose->window);
8946 slist_free_strings_full(compose->to_list);
8947 slist_free_strings_full(compose->newsgroup_list);
8948 slist_free_strings_full(compose->header_list);
8950 slist_free_strings_full(extra_headers);
8951 extra_headers = NULL;
8953 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8955 g_hash_table_destroy(compose->email_hashtable);
8957 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8958 compose->folder_update_callback_id);
8960 procmsg_msginfo_free(&(compose->targetinfo));
8961 procmsg_msginfo_free(&(compose->replyinfo));
8962 procmsg_msginfo_free(&(compose->fwdinfo));
8964 g_free(compose->replyto);
8965 g_free(compose->cc);
8966 g_free(compose->bcc);
8967 g_free(compose->newsgroups);
8968 g_free(compose->followup_to);
8970 g_free(compose->ml_post);
8972 g_free(compose->inreplyto);
8973 g_free(compose->references);
8974 g_free(compose->msgid);
8975 g_free(compose->boundary);
8977 g_free(compose->redirect_filename);
8978 if (compose->undostruct)
8979 undo_destroy(compose->undostruct);
8981 g_free(compose->sig_str);
8983 g_free(compose->exteditor_file);
8985 g_free(compose->orig_charset);
8987 g_free(compose->privacy_system);
8988 g_free(compose->encdata);
8990 #ifndef USE_ALT_ADDRBOOK
8991 if (addressbook_get_target_compose() == compose)
8992 addressbook_set_target_compose(NULL);
8993 #endif
8994 #if USE_ENCHANT
8995 if (compose->gtkaspell) {
8996 gtkaspell_delete(compose->gtkaspell);
8997 compose->gtkaspell = NULL;
8999 #endif
9001 if (!compose->batch) {
9002 gtk_widget_get_allocation(compose->window, &allocation);
9003 prefs_common.compose_width = allocation.width;
9004 prefs_common.compose_height = allocation.height;
9007 if (!gtk_widget_get_parent(compose->paned))
9008 gtk_widget_destroy(compose->paned);
9009 gtk_widget_destroy(compose->popupmenu);
9011 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9012 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9013 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9015 gtk_widget_destroy(compose->window);
9016 toolbar_destroy(compose->toolbar);
9017 g_free(compose->toolbar);
9018 cm_mutex_free(compose->mutex);
9019 g_free(compose);
9022 static void compose_attach_info_free(AttachInfo *ainfo)
9024 g_free(ainfo->file);
9025 g_free(ainfo->content_type);
9026 g_free(ainfo->name);
9027 g_free(ainfo->charset);
9028 g_free(ainfo);
9031 static void compose_attach_update_label(Compose *compose)
9033 GtkTreeIter iter;
9034 gint i = 1;
9035 gchar *text;
9036 GtkTreeModel *model;
9037 goffset total_size;
9038 AttachInfo *ainfo;
9040 if (compose == NULL)
9041 return;
9043 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9044 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9045 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9046 return;
9049 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9050 total_size = ainfo->size;
9051 while(gtk_tree_model_iter_next(model, &iter)) {
9052 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9053 total_size += ainfo->size;
9054 i++;
9056 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9057 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9058 g_free(text);
9061 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9063 Compose *compose = (Compose *)data;
9064 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9065 GtkTreeSelection *selection;
9066 GList *sel, *cur;
9067 GtkTreeModel *model;
9069 selection = gtk_tree_view_get_selection(tree_view);
9070 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9072 if (!sel)
9073 return;
9075 for (cur = sel; cur != NULL; cur = cur->next) {
9076 GtkTreePath *path = cur->data;
9077 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9078 (model, cur->data);
9079 cur->data = ref;
9080 gtk_tree_path_free(path);
9083 for (cur = sel; cur != NULL; cur = cur->next) {
9084 GtkTreeRowReference *ref = cur->data;
9085 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9086 GtkTreeIter iter;
9088 if (gtk_tree_model_get_iter(model, &iter, path))
9089 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9091 gtk_tree_path_free(path);
9092 gtk_tree_row_reference_free(ref);
9095 g_list_free(sel);
9096 compose_attach_update_label(compose);
9099 static struct _AttachProperty
9101 GtkWidget *window;
9102 GtkWidget *mimetype_entry;
9103 GtkWidget *encoding_optmenu;
9104 GtkWidget *path_entry;
9105 GtkWidget *filename_entry;
9106 GtkWidget *ok_btn;
9107 GtkWidget *cancel_btn;
9108 } attach_prop;
9110 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9112 gtk_tree_path_free((GtkTreePath *)ptr);
9115 static void compose_attach_property(GtkAction *action, gpointer data)
9117 Compose *compose = (Compose *)data;
9118 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9119 AttachInfo *ainfo;
9120 GtkComboBox *optmenu;
9121 GtkTreeSelection *selection;
9122 GList *sel;
9123 GtkTreeModel *model;
9124 GtkTreeIter iter;
9125 GtkTreePath *path;
9126 static gboolean cancelled;
9128 /* only if one selected */
9129 selection = gtk_tree_view_get_selection(tree_view);
9130 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9131 return;
9133 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9134 if (!sel)
9135 return;
9137 path = (GtkTreePath *) sel->data;
9138 gtk_tree_model_get_iter(model, &iter, path);
9139 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9141 if (!ainfo) {
9142 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9143 g_list_free(sel);
9144 return;
9146 g_list_free(sel);
9148 if (!attach_prop.window)
9149 compose_attach_property_create(&cancelled);
9150 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9151 gtk_widget_grab_focus(attach_prop.ok_btn);
9152 gtk_widget_show(attach_prop.window);
9153 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9154 GTK_WINDOW(compose->window));
9156 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9157 if (ainfo->encoding == ENC_UNKNOWN)
9158 combobox_select_by_data(optmenu, ENC_BASE64);
9159 else
9160 combobox_select_by_data(optmenu, ainfo->encoding);
9162 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9163 ainfo->content_type ? ainfo->content_type : "");
9164 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9165 ainfo->file ? ainfo->file : "");
9166 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9167 ainfo->name ? ainfo->name : "");
9169 for (;;) {
9170 const gchar *entry_text;
9171 gchar *text;
9172 gchar *cnttype = NULL;
9173 gchar *file = NULL;
9174 off_t size = 0;
9176 cancelled = FALSE;
9177 gtk_main();
9179 gtk_widget_hide(attach_prop.window);
9180 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9182 if (cancelled)
9183 break;
9185 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9186 if (*entry_text != '\0') {
9187 gchar *p;
9189 text = g_strstrip(g_strdup(entry_text));
9190 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9191 cnttype = g_strdup(text);
9192 g_free(text);
9193 } else {
9194 alertpanel_error(_("Invalid MIME type."));
9195 g_free(text);
9196 continue;
9200 ainfo->encoding = combobox_get_active_data(optmenu);
9202 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9203 if (*entry_text != '\0') {
9204 if (is_file_exist(entry_text) &&
9205 (size = get_file_size(entry_text)) > 0)
9206 file = g_strdup(entry_text);
9207 else {
9208 alertpanel_error
9209 (_("File doesn't exist or is empty."));
9210 g_free(cnttype);
9211 continue;
9215 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9216 if (*entry_text != '\0') {
9217 g_free(ainfo->name);
9218 ainfo->name = g_strdup(entry_text);
9221 if (cnttype) {
9222 g_free(ainfo->content_type);
9223 ainfo->content_type = cnttype;
9225 if (file) {
9226 g_free(ainfo->file);
9227 ainfo->file = file;
9229 if (size)
9230 ainfo->size = (goffset)size;
9232 /* update tree store */
9233 text = to_human_readable(ainfo->size);
9234 gtk_tree_model_get_iter(model, &iter, path);
9235 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9236 COL_MIMETYPE, ainfo->content_type,
9237 COL_SIZE, text,
9238 COL_NAME, ainfo->name,
9239 COL_CHARSET, ainfo->charset,
9240 -1);
9242 break;
9245 gtk_tree_path_free(path);
9248 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9250 label = gtk_label_new(str); \
9251 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9252 GTK_FILL, 0, 0, 0); \
9253 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9255 entry = gtk_entry_new(); \
9256 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9257 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9260 static void compose_attach_property_create(gboolean *cancelled)
9262 GtkWidget *window;
9263 GtkWidget *vbox;
9264 GtkWidget *table;
9265 GtkWidget *label;
9266 GtkWidget *mimetype_entry;
9267 GtkWidget *hbox;
9268 GtkWidget *optmenu;
9269 GtkListStore *optmenu_menu;
9270 GtkWidget *path_entry;
9271 GtkWidget *filename_entry;
9272 GtkWidget *hbbox;
9273 GtkWidget *ok_btn;
9274 GtkWidget *cancel_btn;
9275 GList *mime_type_list, *strlist;
9276 GtkTreeIter iter;
9278 debug_print("Creating attach_property window...\n");
9280 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9281 gtk_widget_set_size_request(window, 480, -1);
9282 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9283 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9284 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9285 g_signal_connect(G_OBJECT(window), "delete_event",
9286 G_CALLBACK(attach_property_delete_event),
9287 cancelled);
9288 g_signal_connect(G_OBJECT(window), "key_press_event",
9289 G_CALLBACK(attach_property_key_pressed),
9290 cancelled);
9292 vbox = gtk_vbox_new(FALSE, 8);
9293 gtk_container_add(GTK_CONTAINER(window), vbox);
9295 table = gtk_table_new(4, 2, FALSE);
9296 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9297 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9298 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9300 label = gtk_label_new(_("MIME type"));
9301 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9302 GTK_FILL, 0, 0, 0);
9303 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9304 #if !GTK_CHECK_VERSION(2, 24, 0)
9305 mimetype_entry = gtk_combo_box_entry_new_text();
9306 #else
9307 mimetype_entry = gtk_combo_box_text_new_with_entry();
9308 #endif
9309 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9310 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9312 /* stuff with list */
9313 mime_type_list = procmime_get_mime_type_list();
9314 strlist = NULL;
9315 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9316 MimeType *type = (MimeType *) mime_type_list->data;
9317 gchar *tmp;
9319 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9321 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9322 g_free(tmp);
9323 else
9324 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9325 (GCompareFunc)strcmp2);
9328 for (mime_type_list = strlist; mime_type_list != NULL;
9329 mime_type_list = mime_type_list->next) {
9330 #if !GTK_CHECK_VERSION(2, 24, 0)
9331 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9332 #else
9333 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9334 #endif
9335 g_free(mime_type_list->data);
9337 g_list_free(strlist);
9338 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9339 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9341 label = gtk_label_new(_("Encoding"));
9342 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9343 GTK_FILL, 0, 0, 0);
9344 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9346 hbox = gtk_hbox_new(FALSE, 0);
9347 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9348 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9350 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9351 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9353 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9354 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9355 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9356 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9357 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9359 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9361 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9362 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9364 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9365 &ok_btn, GTK_STOCK_OK,
9366 NULL, NULL);
9367 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9368 gtk_widget_grab_default(ok_btn);
9370 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9371 G_CALLBACK(attach_property_ok),
9372 cancelled);
9373 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9374 G_CALLBACK(attach_property_cancel),
9375 cancelled);
9377 gtk_widget_show_all(vbox);
9379 attach_prop.window = window;
9380 attach_prop.mimetype_entry = mimetype_entry;
9381 attach_prop.encoding_optmenu = optmenu;
9382 attach_prop.path_entry = path_entry;
9383 attach_prop.filename_entry = filename_entry;
9384 attach_prop.ok_btn = ok_btn;
9385 attach_prop.cancel_btn = cancel_btn;
9388 #undef SET_LABEL_AND_ENTRY
9390 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9392 *cancelled = FALSE;
9393 gtk_main_quit();
9396 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9398 *cancelled = TRUE;
9399 gtk_main_quit();
9402 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9403 gboolean *cancelled)
9405 *cancelled = TRUE;
9406 gtk_main_quit();
9408 return TRUE;
9411 static gboolean attach_property_key_pressed(GtkWidget *widget,
9412 GdkEventKey *event,
9413 gboolean *cancelled)
9415 if (event && event->keyval == GDK_KEY_Escape) {
9416 *cancelled = TRUE;
9417 gtk_main_quit();
9419 if (event && event->keyval == GDK_KEY_Return) {
9420 *cancelled = FALSE;
9421 gtk_main_quit();
9422 return TRUE;
9424 return FALSE;
9427 static void compose_exec_ext_editor(Compose *compose)
9429 #ifdef G_OS_UNIX
9430 gchar *tmp;
9431 GtkWidget *socket;
9432 GdkNativeWindow socket_wid = 0;
9433 pid_t pid;
9434 gint pipe_fds[2];
9436 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9437 G_DIR_SEPARATOR, compose);
9439 if (compose_get_ext_editor_uses_socket()) {
9440 /* Only allow one socket */
9441 if (compose->exteditor_socket != NULL) {
9442 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9443 /* Move the focus off of the socket */
9444 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9446 g_free(tmp);
9447 return;
9449 /* Create the receiving GtkSocket */
9450 socket = gtk_socket_new ();
9451 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9452 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9453 compose);
9454 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9455 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9456 /* Realize the socket so that we can use its ID */
9457 gtk_widget_realize(socket);
9458 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9459 compose->exteditor_socket = socket;
9462 if (pipe(pipe_fds) < 0) {
9463 perror("pipe");
9464 g_free(tmp);
9465 return;
9468 if ((pid = fork()) < 0) {
9469 perror("fork");
9470 g_free(tmp);
9471 return;
9474 if (pid != 0) {
9475 /* close the write side of the pipe */
9476 close(pipe_fds[1]);
9478 compose->exteditor_file = g_strdup(tmp);
9479 compose->exteditor_pid = pid;
9481 compose_set_ext_editor_sensitive(compose, FALSE);
9483 #ifndef G_OS_WIN32
9484 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9485 #else
9486 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9487 #endif
9488 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9489 G_IO_IN,
9490 compose_input_cb,
9491 compose);
9492 } else { /* process-monitoring process */
9493 pid_t pid_ed;
9495 if (setpgid(0, 0))
9496 perror("setpgid");
9498 /* close the read side of the pipe */
9499 close(pipe_fds[0]);
9501 if (compose_write_body_to_file(compose, tmp) < 0) {
9502 fd_write_all(pipe_fds[1], "2\n", 2);
9503 _exit(1);
9506 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9507 if (pid_ed < 0) {
9508 fd_write_all(pipe_fds[1], "1\n", 2);
9509 _exit(1);
9512 /* wait until editor is terminated */
9513 waitpid(pid_ed, NULL, 0);
9515 fd_write_all(pipe_fds[1], "0\n", 2);
9517 close(pipe_fds[1]);
9518 _exit(0);
9521 g_free(tmp);
9522 #endif /* G_OS_UNIX */
9525 static gboolean compose_can_autosave(Compose *compose)
9527 if (compose->privacy_system && compose->use_encryption)
9528 return prefs_common.autosave && prefs_common.autosave_encrypted;
9529 else
9530 return prefs_common.autosave;
9533 #ifdef G_OS_UNIX
9534 static gboolean compose_get_ext_editor_cmd_valid()
9536 gboolean has_s = FALSE;
9537 gboolean has_w = FALSE;
9538 const gchar *p = prefs_common_get_ext_editor_cmd();
9539 if (!p)
9540 return FALSE;
9541 while ((p = strchr(p, '%'))) {
9542 p++;
9543 if (*p == 's') {
9544 if (has_s)
9545 return FALSE;
9546 has_s = TRUE;
9547 } else if (*p == 'w') {
9548 if (has_w)
9549 return FALSE;
9550 has_w = TRUE;
9551 } else {
9552 return FALSE;
9555 return TRUE;
9558 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9560 gchar *buf;
9561 gchar *p, *s;
9562 gchar **cmdline;
9563 pid_t pid;
9565 cm_return_val_if_fail(file != NULL, -1);
9567 if ((pid = fork()) < 0) {
9568 perror("fork");
9569 return -1;
9572 if (pid != 0) return pid;
9574 /* grandchild process */
9576 if (setpgid(0, getppid()))
9577 perror("setpgid");
9579 if (compose_get_ext_editor_cmd_valid()) {
9580 if (compose_get_ext_editor_uses_socket()) {
9581 p = g_strdup(prefs_common_get_ext_editor_cmd());
9582 s = strstr(p, "%w");
9583 s[1] = 'u';
9584 if (strstr(p, "%s") < s)
9585 buf = g_strdup_printf(p, file, socket_wid);
9586 else
9587 buf = g_strdup_printf(p, socket_wid, file);
9588 g_free(p);
9589 } else {
9590 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9592 } else {
9593 if (prefs_common_get_ext_editor_cmd())
9594 g_warning("External editor command-line is invalid: '%s'",
9595 prefs_common_get_ext_editor_cmd());
9596 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9599 cmdline = strsplit_with_quote(buf, " ", 0);
9600 g_free(buf);
9601 execvp(cmdline[0], cmdline);
9603 perror("execvp");
9604 g_strfreev(cmdline);
9606 _exit(1);
9609 static gboolean compose_ext_editor_kill(Compose *compose)
9611 pid_t pgid = compose->exteditor_pid * -1;
9612 gint ret;
9614 ret = kill(pgid, 0);
9616 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9617 AlertValue val;
9618 gchar *msg;
9620 msg = g_strdup_printf
9621 (_("The external editor is still working.\n"
9622 "Force terminating the process?\n"
9623 "process group id: %d"), -pgid);
9624 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9625 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9627 g_free(msg);
9629 if (val == G_ALERTALTERNATE) {
9630 g_source_remove(compose->exteditor_tag);
9631 g_io_channel_shutdown(compose->exteditor_ch,
9632 FALSE, NULL);
9633 g_io_channel_unref(compose->exteditor_ch);
9635 if (kill(pgid, SIGTERM) < 0) perror("kill");
9636 waitpid(compose->exteditor_pid, NULL, 0);
9638 g_warning("Terminated process group id: %d. "
9639 "Temporary file: %s", -pgid, compose->exteditor_file);
9641 compose_set_ext_editor_sensitive(compose, TRUE);
9643 g_free(compose->exteditor_file);
9644 compose->exteditor_file = NULL;
9645 compose->exteditor_pid = -1;
9646 compose->exteditor_ch = NULL;
9647 compose->exteditor_tag = -1;
9648 } else
9649 return FALSE;
9652 return TRUE;
9655 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9656 gpointer data)
9658 gchar buf[3] = "3";
9659 Compose *compose = (Compose *)data;
9660 gsize bytes_read;
9662 debug_print("Compose: input from monitoring process\n");
9664 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9665 bytes_read = 0;
9666 buf[0] = '\0';
9669 g_io_channel_shutdown(source, FALSE, NULL);
9670 g_io_channel_unref(source);
9672 waitpid(compose->exteditor_pid, NULL, 0);
9674 if (buf[0] == '0') { /* success */
9675 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9676 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9677 GtkTextIter start, end;
9678 gchar *chars;
9680 gtk_text_buffer_set_text(buffer, "", -1);
9681 compose_insert_file(compose, compose->exteditor_file);
9682 compose_changed_cb(NULL, compose);
9684 /* Check if we should save the draft or not */
9685 if (compose_can_autosave(compose))
9686 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9688 if (claws_unlink(compose->exteditor_file) < 0)
9689 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9691 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9692 gtk_text_buffer_get_start_iter(buffer, &start);
9693 gtk_text_buffer_get_end_iter(buffer, &end);
9694 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9695 if (chars && strlen(chars) > 0)
9696 compose->modified = TRUE;
9697 g_free(chars);
9698 } else if (buf[0] == '1') { /* failed */
9699 g_warning("Couldn't exec external editor");
9700 if (claws_unlink(compose->exteditor_file) < 0)
9701 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9702 } else if (buf[0] == '2') {
9703 g_warning("Couldn't write to file");
9704 } else if (buf[0] == '3') {
9705 g_warning("Pipe read failed");
9708 compose_set_ext_editor_sensitive(compose, TRUE);
9710 g_free(compose->exteditor_file);
9711 compose->exteditor_file = NULL;
9712 compose->exteditor_pid = -1;
9713 compose->exteditor_ch = NULL;
9714 compose->exteditor_tag = -1;
9715 if (compose->exteditor_socket) {
9716 gtk_widget_destroy(compose->exteditor_socket);
9717 compose->exteditor_socket = NULL;
9721 return FALSE;
9724 static char *ext_editor_menu_entries[] = {
9725 "Menu/Message/Send",
9726 "Menu/Message/SendLater",
9727 "Menu/Message/InsertFile",
9728 "Menu/Message/InsertSig",
9729 "Menu/Message/ReplaceSig",
9730 "Menu/Message/Save",
9731 "Menu/Message/Print",
9732 "Menu/Edit",
9733 #if USE_ENCHANT
9734 "Menu/Spelling",
9735 #endif
9736 "Menu/Tools/ShowRuler",
9737 "Menu/Tools/Actions",
9738 "Menu/Help",
9739 NULL
9742 static void compose_set_ext_editor_sensitive(Compose *compose,
9743 gboolean sensitive)
9745 int i;
9747 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9748 cm_menu_set_sensitive_full(compose->ui_manager,
9749 ext_editor_menu_entries[i], sensitive);
9752 if (compose_get_ext_editor_uses_socket()) {
9753 if (sensitive) {
9754 if (compose->exteditor_socket)
9755 gtk_widget_hide(compose->exteditor_socket);
9756 gtk_widget_show(compose->scrolledwin);
9757 if (prefs_common.show_ruler)
9758 gtk_widget_show(compose->ruler_hbox);
9759 /* Fix the focus, as it doesn't go anywhere when the
9760 * socket is hidden or destroyed */
9761 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9762 } else {
9763 g_assert (compose->exteditor_socket != NULL);
9764 /* Fix the focus, as it doesn't go anywhere when the
9765 * edit box is hidden */
9766 if (gtk_widget_is_focus(compose->text))
9767 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9768 gtk_widget_hide(compose->scrolledwin);
9769 gtk_widget_hide(compose->ruler_hbox);
9770 gtk_widget_show(compose->exteditor_socket);
9772 } else {
9773 gtk_widget_set_sensitive(compose->text, sensitive);
9775 if (compose->toolbar->send_btn)
9776 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9777 if (compose->toolbar->sendl_btn)
9778 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9779 if (compose->toolbar->draft_btn)
9780 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9781 if (compose->toolbar->insert_btn)
9782 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9783 if (compose->toolbar->sig_btn)
9784 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9785 if (compose->toolbar->exteditor_btn)
9786 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9787 if (compose->toolbar->linewrap_current_btn)
9788 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9789 if (compose->toolbar->linewrap_all_btn)
9790 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9793 static gboolean compose_get_ext_editor_uses_socket()
9795 return (prefs_common_get_ext_editor_cmd() &&
9796 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9799 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9801 compose->exteditor_socket = NULL;
9802 /* returning FALSE allows destruction of the socket */
9803 return FALSE;
9805 #endif /* G_OS_UNIX */
9808 * compose_undo_state_changed:
9810 * Change the sensivity of the menuentries undo and redo
9812 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9813 gint redo_state, gpointer data)
9815 Compose *compose = (Compose *)data;
9817 switch (undo_state) {
9818 case UNDO_STATE_TRUE:
9819 if (!undostruct->undo_state) {
9820 undostruct->undo_state = TRUE;
9821 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9823 break;
9824 case UNDO_STATE_FALSE:
9825 if (undostruct->undo_state) {
9826 undostruct->undo_state = FALSE;
9827 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9829 break;
9830 case UNDO_STATE_UNCHANGED:
9831 break;
9832 case UNDO_STATE_REFRESH:
9833 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9834 break;
9835 default:
9836 g_warning("Undo state not recognized");
9837 break;
9840 switch (redo_state) {
9841 case UNDO_STATE_TRUE:
9842 if (!undostruct->redo_state) {
9843 undostruct->redo_state = TRUE;
9844 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9846 break;
9847 case UNDO_STATE_FALSE:
9848 if (undostruct->redo_state) {
9849 undostruct->redo_state = FALSE;
9850 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9852 break;
9853 case UNDO_STATE_UNCHANGED:
9854 break;
9855 case UNDO_STATE_REFRESH:
9856 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9857 break;
9858 default:
9859 g_warning("Redo state not recognized");
9860 break;
9864 /* callback functions */
9866 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9867 GtkAllocation *allocation,
9868 GtkPaned *paned)
9870 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9873 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9874 * includes "non-client" (windows-izm) in calculation, so this calculation
9875 * may not be accurate.
9877 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9878 GtkAllocation *allocation,
9879 GtkSHRuler *shruler)
9881 if (prefs_common.show_ruler) {
9882 gint char_width = 0, char_height = 0;
9883 gint line_width_in_chars;
9885 gtkut_get_font_size(GTK_WIDGET(widget),
9886 &char_width, &char_height);
9887 line_width_in_chars =
9888 (allocation->width - allocation->x) / char_width;
9890 /* got the maximum */
9891 gtk_shruler_set_range(GTK_SHRULER(shruler),
9892 0.0, line_width_in_chars, 0);
9895 return TRUE;
9898 typedef struct {
9899 gchar *header;
9900 gchar *entry;
9901 ComposePrefType type;
9902 gboolean entry_marked;
9903 } HeaderEntryState;
9905 static void account_activated(GtkComboBox *optmenu, gpointer data)
9907 Compose *compose = (Compose *)data;
9909 PrefsAccount *ac;
9910 gchar *folderidentifier;
9911 gint account_id = 0;
9912 GtkTreeModel *menu;
9913 GtkTreeIter iter;
9914 GSList *list, *saved_list = NULL;
9915 HeaderEntryState *state;
9917 /* Get ID of active account in the combo box */
9918 menu = gtk_combo_box_get_model(optmenu);
9919 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9920 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9922 ac = account_find_from_id(account_id);
9923 cm_return_if_fail(ac != NULL);
9925 if (ac != compose->account) {
9926 compose_select_account(compose, ac, FALSE);
9928 for (list = compose->header_list; list; list = list->next) {
9929 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9931 if (hentry->type == PREF_ACCOUNT || !list->next) {
9932 compose_destroy_headerentry(compose, hentry);
9933 continue;
9935 state = g_malloc0(sizeof(HeaderEntryState));
9936 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9937 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9938 state->entry = gtk_editable_get_chars(
9939 GTK_EDITABLE(hentry->entry), 0, -1);
9940 state->type = hentry->type;
9942 saved_list = g_slist_append(saved_list, state);
9943 compose_destroy_headerentry(compose, hentry);
9946 compose->header_last = NULL;
9947 g_slist_free(compose->header_list);
9948 compose->header_list = NULL;
9949 compose->header_nextrow = 1;
9950 compose_create_header_entry(compose);
9952 if (ac->set_autocc && ac->auto_cc)
9953 compose_entry_append(compose, ac->auto_cc,
9954 COMPOSE_CC, PREF_ACCOUNT);
9955 if (ac->set_autobcc && ac->auto_bcc)
9956 compose_entry_append(compose, ac->auto_bcc,
9957 COMPOSE_BCC, PREF_ACCOUNT);
9958 if (ac->set_autoreplyto && ac->auto_replyto)
9959 compose_entry_append(compose, ac->auto_replyto,
9960 COMPOSE_REPLYTO, PREF_ACCOUNT);
9962 for (list = saved_list; list; list = list->next) {
9963 state = (HeaderEntryState *) list->data;
9965 compose_add_header_entry(compose, state->header,
9966 state->entry, state->type);
9968 g_free(state->header);
9969 g_free(state->entry);
9970 g_free(state);
9972 g_slist_free(saved_list);
9974 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9975 (ac->protocol == A_NNTP) ?
9976 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9979 /* Set message save folder */
9980 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9981 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9983 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9984 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9986 compose_set_save_to(compose, NULL);
9987 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9988 folderidentifier = folder_item_get_identifier(account_get_special_folder
9989 (compose->account, F_OUTBOX));
9990 compose_set_save_to(compose, folderidentifier);
9991 g_free(folderidentifier);
9995 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9996 GtkTreeViewColumn *column, Compose *compose)
9998 compose_attach_property(NULL, compose);
10001 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10002 gpointer data)
10004 Compose *compose = (Compose *)data;
10005 GtkTreeSelection *attach_selection;
10006 gint attach_nr_selected;
10007 GtkTreePath *path;
10009 if (!event) return FALSE;
10011 if (event->button == 3) {
10012 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10013 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10015 /* If no rows, or just one row is selected, right-click should
10016 * open menu relevant to the row being right-clicked on. We
10017 * achieve that by selecting the clicked row first. If more
10018 * than one row is selected, we shouldn't modify the selection,
10019 * as user may want to remove selected rows (attachments). */
10020 if (attach_nr_selected < 2) {
10021 gtk_tree_selection_unselect_all(attach_selection);
10022 attach_nr_selected = 0;
10023 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10024 event->x, event->y, &path, NULL, NULL, NULL);
10025 if (path != NULL) {
10026 gtk_tree_selection_select_path(attach_selection, path);
10027 gtk_tree_path_free(path);
10028 attach_nr_selected++;
10032 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10033 /* Properties menu item makes no sense with more than one row
10034 * selected, the properties dialog can only edit one attachment. */
10035 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10037 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10038 NULL, NULL, event->button, event->time);
10039 return TRUE;
10042 return FALSE;
10045 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10046 gpointer data)
10048 Compose *compose = (Compose *)data;
10050 if (!event) return FALSE;
10052 switch (event->keyval) {
10053 case GDK_KEY_Delete:
10054 compose_attach_remove_selected(NULL, compose);
10055 break;
10057 return FALSE;
10060 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10062 toolbar_comp_set_sensitive(compose, allow);
10063 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10064 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10065 #if USE_ENCHANT
10066 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10067 #endif
10068 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10069 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10070 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10072 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10076 static void compose_send_cb(GtkAction *action, gpointer data)
10078 Compose *compose = (Compose *)data;
10080 #ifdef G_OS_UNIX
10081 if (compose->exteditor_tag != -1) {
10082 debug_print("ignoring send: external editor still open\n");
10083 return;
10085 #endif
10086 if (prefs_common.work_offline &&
10087 !inc_offline_should_override(TRUE,
10088 _("Claws Mail needs network access in order "
10089 "to send this email.")))
10090 return;
10092 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10093 g_source_remove(compose->draft_timeout_tag);
10094 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10097 compose_send(compose);
10100 static void compose_send_later_cb(GtkAction *action, gpointer data)
10102 Compose *compose = (Compose *)data;
10103 gint val;
10105 inc_lock();
10106 compose_allow_user_actions(compose, FALSE);
10107 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10108 compose_allow_user_actions(compose, TRUE);
10109 inc_unlock();
10111 if (!val) {
10112 compose_close(compose);
10113 } else if (val == -1) {
10114 alertpanel_error(_("Could not queue message."));
10115 } else if (val == -2) {
10116 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
10117 } else if (val == -3) {
10118 if (privacy_peek_error())
10119 alertpanel_error(_("Could not queue message for sending:\n\n"
10120 "Signature failed: %s"), privacy_get_error());
10121 } else if (val == -4) {
10122 alertpanel_error(_("Could not queue message for sending:\n\n"
10123 "Charset conversion failed."));
10124 } else if (val == -5) {
10125 alertpanel_error(_("Could not queue message for sending:\n\n"
10126 "Couldn't get recipient encryption key."));
10127 } else if (val == -6) {
10128 /* silent error */
10130 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10133 #define DRAFTED_AT_EXIT "drafted_at_exit"
10134 static void compose_register_draft(MsgInfo *info)
10136 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10137 DRAFTED_AT_EXIT, NULL);
10138 FILE *fp = g_fopen(filepath, "ab");
10140 if (fp) {
10141 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10142 info->msgnum);
10143 fclose(fp);
10146 g_free(filepath);
10149 gboolean compose_draft (gpointer data, guint action)
10151 Compose *compose = (Compose *)data;
10152 FolderItem *draft;
10153 gchar *tmp;
10154 gchar *sheaders;
10155 gint msgnum;
10156 MsgFlags flag = {0, 0};
10157 static gboolean lock = FALSE;
10158 MsgInfo *newmsginfo;
10159 FILE *fp;
10160 gboolean target_locked = FALSE;
10161 gboolean err = FALSE;
10163 if (lock) return FALSE;
10165 if (compose->sending)
10166 return TRUE;
10168 draft = account_get_special_folder(compose->account, F_DRAFT);
10169 cm_return_val_if_fail(draft != NULL, FALSE);
10171 if (!g_mutex_trylock(compose->mutex)) {
10172 /* we don't want to lock the mutex once it's available,
10173 * because as the only other part of compose.c locking
10174 * it is compose_close - which means once unlocked,
10175 * the compose struct will be freed */
10176 debug_print("couldn't lock mutex, probably sending\n");
10177 return FALSE;
10180 lock = TRUE;
10182 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10183 G_DIR_SEPARATOR, compose);
10184 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10185 FILE_OP_ERROR(tmp, "fopen");
10186 goto warn_err;
10189 /* chmod for security */
10190 if (change_file_mode_rw(fp, tmp) < 0) {
10191 FILE_OP_ERROR(tmp, "chmod");
10192 g_warning("can't change file mode");
10195 /* Save draft infos */
10196 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10197 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10199 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10200 gchar *savefolderid;
10202 savefolderid = compose_get_save_to(compose);
10203 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10204 g_free(savefolderid);
10206 if (compose->return_receipt) {
10207 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10209 if (compose->privacy_system) {
10210 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10211 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10212 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10215 /* Message-ID of message replying to */
10216 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10217 gchar *folderid = NULL;
10219 if (compose->replyinfo->folder)
10220 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10221 if (folderid == NULL)
10222 folderid = g_strdup("NULL");
10224 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10225 g_free(folderid);
10227 /* Message-ID of message forwarding to */
10228 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10229 gchar *folderid = NULL;
10231 if (compose->fwdinfo->folder)
10232 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10233 if (folderid == NULL)
10234 folderid = g_strdup("NULL");
10236 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10237 g_free(folderid);
10240 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10241 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10243 sheaders = compose_get_manual_headers_info(compose);
10244 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10245 g_free(sheaders);
10247 /* end of headers */
10248 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10250 if (err) {
10251 fclose(fp);
10252 goto warn_err;
10255 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10256 fclose(fp);
10257 goto warn_err;
10259 if (fclose(fp) == EOF) {
10260 goto warn_err;
10263 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10264 if (compose->targetinfo) {
10265 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10266 if (target_locked)
10267 flag.perm_flags |= MSG_LOCKED;
10269 flag.tmp_flags = MSG_DRAFT;
10271 folder_item_scan(draft);
10272 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10273 MsgInfo *tmpinfo = NULL;
10274 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10275 if (compose->msgid) {
10276 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10278 if (tmpinfo) {
10279 msgnum = tmpinfo->msgnum;
10280 procmsg_msginfo_free(&tmpinfo);
10281 debug_print("got draft msgnum %d from scanning\n", msgnum);
10282 } else {
10283 debug_print("didn't get draft msgnum after scanning\n");
10285 } else {
10286 debug_print("got draft msgnum %d from adding\n", msgnum);
10288 if (msgnum < 0) {
10289 warn_err:
10290 claws_unlink(tmp);
10291 g_free(tmp);
10292 if (action != COMPOSE_AUTO_SAVE) {
10293 if (action != COMPOSE_DRAFT_FOR_EXIT)
10294 alertpanel_error(_("Could not save draft."));
10295 else {
10296 AlertValue val;
10297 gtkut_window_popup(compose->window);
10298 val = alertpanel_full(_("Could not save draft"),
10299 _("Could not save draft.\n"
10300 "Do you want to cancel exit or discard this email?"),
10301 _("_Cancel exit"), _("_Discard email"), NULL,
10302 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10303 if (val == G_ALERTALTERNATE) {
10304 lock = FALSE;
10305 g_mutex_unlock(compose->mutex); /* must be done before closing */
10306 compose_close(compose);
10307 return TRUE;
10308 } else {
10309 lock = FALSE;
10310 g_mutex_unlock(compose->mutex); /* must be done before closing */
10311 return FALSE;
10315 goto unlock;
10317 g_free(tmp);
10319 if (compose->mode == COMPOSE_REEDIT) {
10320 compose_remove_reedit_target(compose, TRUE);
10323 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10325 if (newmsginfo) {
10326 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10327 if (target_locked)
10328 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10329 else
10330 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10331 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10332 procmsg_msginfo_set_flags(newmsginfo, 0,
10333 MSG_HAS_ATTACHMENT);
10335 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10336 compose_register_draft(newmsginfo);
10338 procmsg_msginfo_free(&newmsginfo);
10341 folder_item_scan(draft);
10343 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10344 lock = FALSE;
10345 g_mutex_unlock(compose->mutex); /* must be done before closing */
10346 compose_close(compose);
10347 return TRUE;
10348 } else {
10349 GStatBuf s;
10350 gchar *path;
10352 path = folder_item_fetch_msg(draft, msgnum);
10353 if (path == NULL) {
10354 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10355 goto unlock;
10357 if (g_stat(path, &s) < 0) {
10358 FILE_OP_ERROR(path, "stat");
10359 g_free(path);
10360 goto unlock;
10362 g_free(path);
10364 procmsg_msginfo_free(&(compose->targetinfo));
10365 compose->targetinfo = procmsg_msginfo_new();
10366 compose->targetinfo->msgnum = msgnum;
10367 compose->targetinfo->size = (goffset)s.st_size;
10368 compose->targetinfo->mtime = s.st_mtime;
10369 compose->targetinfo->folder = draft;
10370 if (target_locked)
10371 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10372 compose->mode = COMPOSE_REEDIT;
10374 if (action == COMPOSE_AUTO_SAVE) {
10375 compose->autosaved_draft = compose->targetinfo;
10377 compose->modified = FALSE;
10378 compose_set_title(compose);
10380 unlock:
10381 lock = FALSE;
10382 g_mutex_unlock(compose->mutex);
10383 return TRUE;
10386 void compose_clear_exit_drafts(void)
10388 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10389 DRAFTED_AT_EXIT, NULL);
10390 if (is_file_exist(filepath))
10391 claws_unlink(filepath);
10393 g_free(filepath);
10396 void compose_reopen_exit_drafts(void)
10398 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10399 DRAFTED_AT_EXIT, NULL);
10400 FILE *fp = g_fopen(filepath, "rb");
10401 gchar buf[1024];
10403 if (fp) {
10404 while (fgets(buf, sizeof(buf), fp)) {
10405 gchar **parts = g_strsplit(buf, "\t", 2);
10406 const gchar *folder = parts[0];
10407 int msgnum = parts[1] ? atoi(parts[1]):-1;
10409 if (folder && *folder && msgnum > -1) {
10410 FolderItem *item = folder_find_item_from_identifier(folder);
10411 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10412 if (info)
10413 compose_reedit(info, FALSE);
10415 g_strfreev(parts);
10417 fclose(fp);
10419 g_free(filepath);
10420 compose_clear_exit_drafts();
10423 static void compose_save_cb(GtkAction *action, gpointer data)
10425 Compose *compose = (Compose *)data;
10426 compose_draft(compose, COMPOSE_KEEP_EDITING);
10427 compose->rmode = COMPOSE_REEDIT;
10430 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10432 if (compose && file_list) {
10433 GList *tmp;
10435 for ( tmp = file_list; tmp; tmp = tmp->next) {
10436 gchar *file = (gchar *) tmp->data;
10437 gchar *utf8_filename = conv_filename_to_utf8(file);
10438 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10439 compose_changed_cb(NULL, compose);
10440 if (free_data) {
10441 g_free(file);
10442 tmp->data = NULL;
10444 g_free(utf8_filename);
10449 static void compose_attach_cb(GtkAction *action, gpointer data)
10451 Compose *compose = (Compose *)data;
10452 GList *file_list;
10454 if (compose->redirect_filename != NULL)
10455 return;
10457 /* Set focus_window properly, in case we were called via popup menu,
10458 * which unsets it (via focus_out_event callback on compose window). */
10459 manage_window_focus_in(compose->window, NULL, NULL);
10461 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10463 if (file_list) {
10464 compose_attach_from_list(compose, file_list, TRUE);
10465 g_list_free(file_list);
10469 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10471 Compose *compose = (Compose *)data;
10472 GList *file_list;
10473 gint files_inserted = 0;
10475 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10477 if (file_list) {
10478 GList *tmp;
10480 for ( tmp = file_list; tmp; tmp = tmp->next) {
10481 gchar *file = (gchar *) tmp->data;
10482 gchar *filedup = g_strdup(file);
10483 gchar *shortfile = g_path_get_basename(filedup);
10484 ComposeInsertResult res;
10485 /* insert the file if the file is short or if the user confirmed that
10486 he/she wants to insert the large file */
10487 res = compose_insert_file(compose, file);
10488 if (res == COMPOSE_INSERT_READ_ERROR) {
10489 alertpanel_error(_("File '%s' could not be read."), shortfile);
10490 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10491 alertpanel_error(_("File '%s' contained invalid characters\n"
10492 "for the current encoding, insertion may be incorrect."),
10493 shortfile);
10494 } else if (res == COMPOSE_INSERT_SUCCESS)
10495 files_inserted++;
10497 g_free(shortfile);
10498 g_free(filedup);
10499 g_free(file);
10501 g_list_free(file_list);
10504 #ifdef USE_ENCHANT
10505 if (files_inserted > 0 && compose->gtkaspell &&
10506 compose->gtkaspell->check_while_typing)
10507 gtkaspell_highlight_all(compose->gtkaspell);
10508 #endif
10511 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10513 Compose *compose = (Compose *)data;
10515 compose_insert_sig(compose, FALSE);
10518 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10520 Compose *compose = (Compose *)data;
10522 compose_insert_sig(compose, TRUE);
10525 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10526 gpointer data)
10528 gint x, y;
10529 Compose *compose = (Compose *)data;
10531 gtkut_widget_get_uposition(widget, &x, &y);
10532 if (!compose->batch) {
10533 prefs_common.compose_x = x;
10534 prefs_common.compose_y = y;
10536 if (compose->sending || compose->updating)
10537 return TRUE;
10538 compose_close_cb(NULL, compose);
10539 return TRUE;
10542 void compose_close_toolbar(Compose *compose)
10544 compose_close_cb(NULL, compose);
10547 static void compose_close_cb(GtkAction *action, gpointer data)
10549 Compose *compose = (Compose *)data;
10550 AlertValue val;
10552 #ifdef G_OS_UNIX
10553 if (compose->exteditor_tag != -1) {
10554 if (!compose_ext_editor_kill(compose))
10555 return;
10557 #endif
10559 if (compose->modified) {
10560 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10561 if (!g_mutex_trylock(compose->mutex)) {
10562 /* we don't want to lock the mutex once it's available,
10563 * because as the only other part of compose.c locking
10564 * it is compose_close - which means once unlocked,
10565 * the compose struct will be freed */
10566 debug_print("couldn't lock mutex, probably sending\n");
10567 return;
10569 if (!reedit) {
10570 val = alertpanel(_("Discard message"),
10571 _("This message has been modified. Discard it?"),
10572 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10573 } else {
10574 val = alertpanel(_("Save changes"),
10575 _("This message has been modified. Save the latest changes?"),
10576 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10577 GTK_STOCK_CANCEL);
10579 g_mutex_unlock(compose->mutex);
10580 switch (val) {
10581 case G_ALERTDEFAULT:
10582 if (compose_can_autosave(compose) && !reedit)
10583 compose_remove_draft(compose);
10584 break;
10585 case G_ALERTALTERNATE:
10586 compose_draft(data, COMPOSE_QUIT_EDITING);
10587 return;
10588 default:
10589 return;
10593 compose_close(compose);
10596 static void compose_print_cb(GtkAction *action, gpointer data)
10598 Compose *compose = (Compose *) data;
10600 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10601 if (compose->targetinfo)
10602 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10605 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10607 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10608 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10609 Compose *compose = (Compose *) data;
10611 if (active)
10612 compose->out_encoding = (CharSet)value;
10615 static void compose_address_cb(GtkAction *action, gpointer data)
10617 Compose *compose = (Compose *)data;
10619 #ifndef USE_ALT_ADDRBOOK
10620 addressbook_open(compose);
10621 #else
10622 GError* error = NULL;
10623 addressbook_connect_signals(compose);
10624 addressbook_dbus_open(TRUE, &error);
10625 if (error) {
10626 g_warning("%s", error->message);
10627 g_error_free(error);
10629 #endif
10632 static void about_show_cb(GtkAction *action, gpointer data)
10634 about_show();
10637 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10639 Compose *compose = (Compose *)data;
10640 Template *tmpl;
10641 gchar *msg;
10642 AlertValue val;
10644 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10645 cm_return_if_fail(tmpl != NULL);
10647 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10648 tmpl->name);
10649 val = alertpanel(_("Apply template"), msg,
10650 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10651 g_free(msg);
10653 if (val == G_ALERTDEFAULT)
10654 compose_template_apply(compose, tmpl, TRUE);
10655 else if (val == G_ALERTALTERNATE)
10656 compose_template_apply(compose, tmpl, FALSE);
10659 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10661 Compose *compose = (Compose *)data;
10663 #ifdef G_OS_UNIX
10664 if (compose->exteditor_tag != -1) {
10665 debug_print("ignoring open external editor: external editor still open\n");
10666 return;
10668 #endif
10669 compose_exec_ext_editor(compose);
10672 static void compose_undo_cb(GtkAction *action, gpointer data)
10674 Compose *compose = (Compose *)data;
10675 gboolean prev_autowrap = compose->autowrap;
10677 compose->autowrap = FALSE;
10678 undo_undo(compose->undostruct);
10679 compose->autowrap = prev_autowrap;
10682 static void compose_redo_cb(GtkAction *action, gpointer data)
10684 Compose *compose = (Compose *)data;
10685 gboolean prev_autowrap = compose->autowrap;
10687 compose->autowrap = FALSE;
10688 undo_redo(compose->undostruct);
10689 compose->autowrap = prev_autowrap;
10692 static void entry_cut_clipboard(GtkWidget *entry)
10694 if (GTK_IS_EDITABLE(entry))
10695 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10696 else if (GTK_IS_TEXT_VIEW(entry))
10697 gtk_text_buffer_cut_clipboard(
10698 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10699 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10700 TRUE);
10703 static void entry_copy_clipboard(GtkWidget *entry)
10705 if (GTK_IS_EDITABLE(entry))
10706 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10707 else if (GTK_IS_TEXT_VIEW(entry))
10708 gtk_text_buffer_copy_clipboard(
10709 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10710 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10713 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10714 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10716 if (GTK_IS_TEXT_VIEW(entry)) {
10717 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10718 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10719 GtkTextIter start_iter, end_iter;
10720 gint start, end;
10721 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10723 if (contents == NULL)
10724 return;
10726 /* we shouldn't delete the selection when middle-click-pasting, or we
10727 * can't mid-click-paste our own selection */
10728 if (clip != GDK_SELECTION_PRIMARY) {
10729 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10730 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10733 if (insert_place == NULL) {
10734 /* if insert_place isn't specified, insert at the cursor.
10735 * used for Ctrl-V pasting */
10736 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10737 start = gtk_text_iter_get_offset(&start_iter);
10738 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10739 } else {
10740 /* if insert_place is specified, paste here.
10741 * used for mid-click-pasting */
10742 start = gtk_text_iter_get_offset(insert_place);
10743 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10744 if (prefs_common.primary_paste_unselects)
10745 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10748 if (!wrap) {
10749 /* paste unwrapped: mark the paste so it's not wrapped later */
10750 end = start + strlen(contents);
10751 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10752 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10753 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10754 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10755 /* rewrap paragraph now (after a mid-click-paste) */
10756 mark_start = gtk_text_buffer_get_insert(buffer);
10757 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10758 gtk_text_iter_backward_char(&start_iter);
10759 compose_beautify_paragraph(compose, &start_iter, TRUE);
10761 } else if (GTK_IS_EDITABLE(entry))
10762 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10764 compose->modified = TRUE;
10767 static void entry_allsel(GtkWidget *entry)
10769 if (GTK_IS_EDITABLE(entry))
10770 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10771 else if (GTK_IS_TEXT_VIEW(entry)) {
10772 GtkTextIter startiter, enditer;
10773 GtkTextBuffer *textbuf;
10775 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10776 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10777 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10779 gtk_text_buffer_move_mark_by_name(textbuf,
10780 "selection_bound", &startiter);
10781 gtk_text_buffer_move_mark_by_name(textbuf,
10782 "insert", &enditer);
10786 static void compose_cut_cb(GtkAction *action, gpointer data)
10788 Compose *compose = (Compose *)data;
10789 if (compose->focused_editable
10790 #ifndef GENERIC_UMPC
10791 && gtk_widget_has_focus(compose->focused_editable)
10792 #endif
10794 entry_cut_clipboard(compose->focused_editable);
10797 static void compose_copy_cb(GtkAction *action, gpointer data)
10799 Compose *compose = (Compose *)data;
10800 if (compose->focused_editable
10801 #ifndef GENERIC_UMPC
10802 && gtk_widget_has_focus(compose->focused_editable)
10803 #endif
10805 entry_copy_clipboard(compose->focused_editable);
10808 static void compose_paste_cb(GtkAction *action, gpointer data)
10810 Compose *compose = (Compose *)data;
10811 gint prev_autowrap;
10812 GtkTextBuffer *buffer;
10813 BLOCK_WRAP();
10814 if (compose->focused_editable &&
10815 #ifndef GENERIC_UMPC
10816 gtk_widget_has_focus(compose->focused_editable)
10817 #endif
10819 entry_paste_clipboard(compose, compose->focused_editable,
10820 prefs_common.linewrap_pastes,
10821 GDK_SELECTION_CLIPBOARD, NULL);
10822 UNBLOCK_WRAP();
10824 #ifdef USE_ENCHANT
10825 if (
10826 #ifndef GENERIC_UMPC
10827 gtk_widget_has_focus(compose->text) &&
10828 #endif
10829 compose->gtkaspell &&
10830 compose->gtkaspell->check_while_typing)
10831 gtkaspell_highlight_all(compose->gtkaspell);
10832 #endif
10835 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10837 Compose *compose = (Compose *)data;
10838 gint wrap_quote = prefs_common.linewrap_quote;
10839 if (compose->focused_editable
10840 #ifndef GENERIC_UMPC
10841 && gtk_widget_has_focus(compose->focused_editable)
10842 #endif
10844 /* let text_insert() (called directly or at a later time
10845 * after the gtk_editable_paste_clipboard) know that
10846 * text is to be inserted as a quotation. implemented
10847 * by using a simple refcount... */
10848 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10849 G_OBJECT(compose->focused_editable),
10850 "paste_as_quotation"));
10851 g_object_set_data(G_OBJECT(compose->focused_editable),
10852 "paste_as_quotation",
10853 GINT_TO_POINTER(paste_as_quotation + 1));
10854 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10855 entry_paste_clipboard(compose, compose->focused_editable,
10856 prefs_common.linewrap_pastes,
10857 GDK_SELECTION_CLIPBOARD, NULL);
10858 prefs_common.linewrap_quote = wrap_quote;
10862 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10864 Compose *compose = (Compose *)data;
10865 gint prev_autowrap;
10866 GtkTextBuffer *buffer;
10867 BLOCK_WRAP();
10868 if (compose->focused_editable
10869 #ifndef GENERIC_UMPC
10870 && gtk_widget_has_focus(compose->focused_editable)
10871 #endif
10873 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10874 GDK_SELECTION_CLIPBOARD, NULL);
10875 UNBLOCK_WRAP();
10877 #ifdef USE_ENCHANT
10878 if (
10879 #ifndef GENERIC_UMPC
10880 gtk_widget_has_focus(compose->text) &&
10881 #endif
10882 compose->gtkaspell &&
10883 compose->gtkaspell->check_while_typing)
10884 gtkaspell_highlight_all(compose->gtkaspell);
10885 #endif
10888 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10890 Compose *compose = (Compose *)data;
10891 gint prev_autowrap;
10892 GtkTextBuffer *buffer;
10893 BLOCK_WRAP();
10894 if (compose->focused_editable
10895 #ifndef GENERIC_UMPC
10896 && gtk_widget_has_focus(compose->focused_editable)
10897 #endif
10899 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10900 GDK_SELECTION_CLIPBOARD, NULL);
10901 UNBLOCK_WRAP();
10903 #ifdef USE_ENCHANT
10904 if (
10905 #ifndef GENERIC_UMPC
10906 gtk_widget_has_focus(compose->text) &&
10907 #endif
10908 compose->gtkaspell &&
10909 compose->gtkaspell->check_while_typing)
10910 gtkaspell_highlight_all(compose->gtkaspell);
10911 #endif
10914 static void compose_allsel_cb(GtkAction *action, gpointer data)
10916 Compose *compose = (Compose *)data;
10917 if (compose->focused_editable
10918 #ifndef GENERIC_UMPC
10919 && gtk_widget_has_focus(compose->focused_editable)
10920 #endif
10922 entry_allsel(compose->focused_editable);
10925 static void textview_move_beginning_of_line (GtkTextView *text)
10927 GtkTextBuffer *buffer;
10928 GtkTextMark *mark;
10929 GtkTextIter ins;
10931 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10933 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10934 mark = gtk_text_buffer_get_insert(buffer);
10935 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10936 gtk_text_iter_set_line_offset(&ins, 0);
10937 gtk_text_buffer_place_cursor(buffer, &ins);
10940 static void textview_move_forward_character (GtkTextView *text)
10942 GtkTextBuffer *buffer;
10943 GtkTextMark *mark;
10944 GtkTextIter ins;
10946 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10948 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10949 mark = gtk_text_buffer_get_insert(buffer);
10950 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10951 if (gtk_text_iter_forward_cursor_position(&ins))
10952 gtk_text_buffer_place_cursor(buffer, &ins);
10955 static void textview_move_backward_character (GtkTextView *text)
10957 GtkTextBuffer *buffer;
10958 GtkTextMark *mark;
10959 GtkTextIter ins;
10961 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10963 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10964 mark = gtk_text_buffer_get_insert(buffer);
10965 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10966 if (gtk_text_iter_backward_cursor_position(&ins))
10967 gtk_text_buffer_place_cursor(buffer, &ins);
10970 static void textview_move_forward_word (GtkTextView *text)
10972 GtkTextBuffer *buffer;
10973 GtkTextMark *mark;
10974 GtkTextIter ins;
10975 gint count;
10977 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10979 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10980 mark = gtk_text_buffer_get_insert(buffer);
10981 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10982 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10983 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10984 gtk_text_iter_backward_word_start(&ins);
10985 gtk_text_buffer_place_cursor(buffer, &ins);
10989 static void textview_move_backward_word (GtkTextView *text)
10991 GtkTextBuffer *buffer;
10992 GtkTextMark *mark;
10993 GtkTextIter ins;
10995 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10997 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10998 mark = gtk_text_buffer_get_insert(buffer);
10999 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11000 if (gtk_text_iter_backward_word_starts(&ins, 1))
11001 gtk_text_buffer_place_cursor(buffer, &ins);
11004 static void textview_move_end_of_line (GtkTextView *text)
11006 GtkTextBuffer *buffer;
11007 GtkTextMark *mark;
11008 GtkTextIter ins;
11010 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11012 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11013 mark = gtk_text_buffer_get_insert(buffer);
11014 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11015 if (gtk_text_iter_forward_to_line_end(&ins))
11016 gtk_text_buffer_place_cursor(buffer, &ins);
11019 static void textview_move_next_line (GtkTextView *text)
11021 GtkTextBuffer *buffer;
11022 GtkTextMark *mark;
11023 GtkTextIter ins;
11024 gint offset;
11026 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11028 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11029 mark = gtk_text_buffer_get_insert(buffer);
11030 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11031 offset = gtk_text_iter_get_line_offset(&ins);
11032 if (gtk_text_iter_forward_line(&ins)) {
11033 gtk_text_iter_set_line_offset(&ins, offset);
11034 gtk_text_buffer_place_cursor(buffer, &ins);
11038 static void textview_move_previous_line (GtkTextView *text)
11040 GtkTextBuffer *buffer;
11041 GtkTextMark *mark;
11042 GtkTextIter ins;
11043 gint offset;
11045 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11047 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11048 mark = gtk_text_buffer_get_insert(buffer);
11049 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11050 offset = gtk_text_iter_get_line_offset(&ins);
11051 if (gtk_text_iter_backward_line(&ins)) {
11052 gtk_text_iter_set_line_offset(&ins, offset);
11053 gtk_text_buffer_place_cursor(buffer, &ins);
11057 static void textview_delete_forward_character (GtkTextView *text)
11059 GtkTextBuffer *buffer;
11060 GtkTextMark *mark;
11061 GtkTextIter ins, end_iter;
11063 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11065 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11066 mark = gtk_text_buffer_get_insert(buffer);
11067 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11068 end_iter = ins;
11069 if (gtk_text_iter_forward_char(&end_iter)) {
11070 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11074 static void textview_delete_backward_character (GtkTextView *text)
11076 GtkTextBuffer *buffer;
11077 GtkTextMark *mark;
11078 GtkTextIter ins, end_iter;
11080 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11082 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11083 mark = gtk_text_buffer_get_insert(buffer);
11084 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11085 end_iter = ins;
11086 if (gtk_text_iter_backward_char(&end_iter)) {
11087 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11091 static void textview_delete_forward_word (GtkTextView *text)
11093 GtkTextBuffer *buffer;
11094 GtkTextMark *mark;
11095 GtkTextIter ins, end_iter;
11097 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11099 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11100 mark = gtk_text_buffer_get_insert(buffer);
11101 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11102 end_iter = ins;
11103 if (gtk_text_iter_forward_word_end(&end_iter)) {
11104 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11108 static void textview_delete_backward_word (GtkTextView *text)
11110 GtkTextBuffer *buffer;
11111 GtkTextMark *mark;
11112 GtkTextIter ins, end_iter;
11114 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11116 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11117 mark = gtk_text_buffer_get_insert(buffer);
11118 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11119 end_iter = ins;
11120 if (gtk_text_iter_backward_word_start(&end_iter)) {
11121 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11125 static void textview_delete_line (GtkTextView *text)
11127 GtkTextBuffer *buffer;
11128 GtkTextMark *mark;
11129 GtkTextIter ins, start_iter, end_iter;
11131 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11133 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11134 mark = gtk_text_buffer_get_insert(buffer);
11135 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11137 start_iter = ins;
11138 gtk_text_iter_set_line_offset(&start_iter, 0);
11140 end_iter = ins;
11141 if (gtk_text_iter_ends_line(&end_iter)){
11142 if (!gtk_text_iter_forward_char(&end_iter))
11143 gtk_text_iter_backward_char(&start_iter);
11145 else
11146 gtk_text_iter_forward_to_line_end(&end_iter);
11147 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11150 static void textview_delete_to_line_end (GtkTextView *text)
11152 GtkTextBuffer *buffer;
11153 GtkTextMark *mark;
11154 GtkTextIter ins, end_iter;
11156 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11158 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11159 mark = gtk_text_buffer_get_insert(buffer);
11160 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11161 end_iter = ins;
11162 if (gtk_text_iter_ends_line(&end_iter))
11163 gtk_text_iter_forward_char(&end_iter);
11164 else
11165 gtk_text_iter_forward_to_line_end(&end_iter);
11166 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11169 #define DO_ACTION(name, act) { \
11170 if(!strcmp(name, a_name)) { \
11171 return act; \
11174 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11176 const gchar *a_name = gtk_action_get_name(action);
11177 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11178 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11179 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11180 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11181 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11182 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11183 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11184 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11185 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11186 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11187 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11188 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11189 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11190 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11191 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11194 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11196 Compose *compose = (Compose *)data;
11197 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11198 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11200 action = compose_call_advanced_action_from_path(gaction);
11202 static struct {
11203 void (*do_action) (GtkTextView *text);
11204 } action_table[] = {
11205 {textview_move_beginning_of_line},
11206 {textview_move_forward_character},
11207 {textview_move_backward_character},
11208 {textview_move_forward_word},
11209 {textview_move_backward_word},
11210 {textview_move_end_of_line},
11211 {textview_move_next_line},
11212 {textview_move_previous_line},
11213 {textview_delete_forward_character},
11214 {textview_delete_backward_character},
11215 {textview_delete_forward_word},
11216 {textview_delete_backward_word},
11217 {textview_delete_line},
11218 {textview_delete_to_line_end}
11221 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11223 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11224 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11225 if (action_table[action].do_action)
11226 action_table[action].do_action(text);
11227 else
11228 g_warning("Not implemented yet.");
11232 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11234 GtkAllocation allocation;
11235 GtkWidget *parent;
11236 gchar *str = NULL;
11238 if (GTK_IS_EDITABLE(widget)) {
11239 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11240 gtk_editable_set_position(GTK_EDITABLE(widget),
11241 strlen(str));
11242 g_free(str);
11243 if ((parent = gtk_widget_get_parent(widget))
11244 && (parent = gtk_widget_get_parent(parent))
11245 && (parent = gtk_widget_get_parent(parent))) {
11246 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11247 gtk_widget_get_allocation(widget, &allocation);
11248 gint y = allocation.y;
11249 gint height = allocation.height;
11250 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11251 (GTK_SCROLLED_WINDOW(parent));
11253 gfloat value = gtk_adjustment_get_value(shown);
11254 gfloat upper = gtk_adjustment_get_upper(shown);
11255 gfloat page_size = gtk_adjustment_get_page_size(shown);
11256 if (y < (int)value) {
11257 gtk_adjustment_set_value(shown, y - 1);
11259 if ((y + height) > ((int)value + (int)page_size)) {
11260 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11261 gtk_adjustment_set_value(shown,
11262 y + height - (int)page_size - 1);
11263 } else {
11264 gtk_adjustment_set_value(shown,
11265 (int)upper - (int)page_size - 1);
11272 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11273 compose->focused_editable = widget;
11275 #ifdef GENERIC_UMPC
11276 if (GTK_IS_TEXT_VIEW(widget)
11277 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11278 g_object_ref(compose->notebook);
11279 g_object_ref(compose->edit_vbox);
11280 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11281 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11282 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11283 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11284 g_object_unref(compose->notebook);
11285 g_object_unref(compose->edit_vbox);
11286 g_signal_handlers_block_by_func(G_OBJECT(widget),
11287 G_CALLBACK(compose_grab_focus_cb),
11288 compose);
11289 gtk_widget_grab_focus(widget);
11290 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11291 G_CALLBACK(compose_grab_focus_cb),
11292 compose);
11293 } else if (!GTK_IS_TEXT_VIEW(widget)
11294 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11295 g_object_ref(compose->notebook);
11296 g_object_ref(compose->edit_vbox);
11297 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11298 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11299 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11300 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11301 g_object_unref(compose->notebook);
11302 g_object_unref(compose->edit_vbox);
11303 g_signal_handlers_block_by_func(G_OBJECT(widget),
11304 G_CALLBACK(compose_grab_focus_cb),
11305 compose);
11306 gtk_widget_grab_focus(widget);
11307 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11308 G_CALLBACK(compose_grab_focus_cb),
11309 compose);
11311 #endif
11314 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11316 compose->modified = TRUE;
11317 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11318 #ifndef GENERIC_UMPC
11319 compose_set_title(compose);
11320 #endif
11323 static void compose_wrap_cb(GtkAction *action, gpointer data)
11325 Compose *compose = (Compose *)data;
11326 compose_beautify_paragraph(compose, NULL, TRUE);
11329 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11331 Compose *compose = (Compose *)data;
11332 compose_wrap_all_full(compose, TRUE);
11335 static void compose_find_cb(GtkAction *action, gpointer data)
11337 Compose *compose = (Compose *)data;
11339 message_search_compose(compose);
11342 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11343 gpointer data)
11345 Compose *compose = (Compose *)data;
11346 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11347 if (compose->autowrap)
11348 compose_wrap_all_full(compose, TRUE);
11349 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11352 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11353 gpointer data)
11355 Compose *compose = (Compose *)data;
11356 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11359 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11361 Compose *compose = (Compose *)data;
11363 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11366 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11368 Compose *compose = (Compose *)data;
11370 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11373 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11375 g_free(compose->privacy_system);
11376 g_free(compose->encdata);
11378 compose->privacy_system = g_strdup(account->default_privacy_system);
11379 compose_update_privacy_system_menu_item(compose, warn);
11382 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11384 Compose *compose = (Compose *)data;
11386 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11387 gtk_widget_show(compose->ruler_hbox);
11388 prefs_common.show_ruler = TRUE;
11389 } else {
11390 gtk_widget_hide(compose->ruler_hbox);
11391 gtk_widget_queue_resize(compose->edit_vbox);
11392 prefs_common.show_ruler = FALSE;
11396 static void compose_attach_drag_received_cb (GtkWidget *widget,
11397 GdkDragContext *context,
11398 gint x,
11399 gint y,
11400 GtkSelectionData *data,
11401 guint info,
11402 guint time,
11403 gpointer user_data)
11405 Compose *compose = (Compose *)user_data;
11406 GList *list, *tmp;
11407 GdkAtom type;
11409 type = gtk_selection_data_get_data_type(data);
11410 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11411 && gtk_drag_get_source_widget(context) !=
11412 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11413 list = uri_list_extract_filenames(
11414 (const gchar *)gtk_selection_data_get_data(data));
11415 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11416 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11417 compose_attach_append
11418 (compose, (const gchar *)tmp->data,
11419 utf8_filename, NULL, NULL);
11420 g_free(utf8_filename);
11422 if (list) compose_changed_cb(NULL, compose);
11423 list_free_strings(list);
11424 g_list_free(list);
11425 } else if (gtk_drag_get_source_widget(context)
11426 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11427 /* comes from our summaryview */
11428 SummaryView * summaryview = NULL;
11429 GSList * list = NULL, *cur = NULL;
11431 if (mainwindow_get_mainwindow())
11432 summaryview = mainwindow_get_mainwindow()->summaryview;
11434 if (summaryview)
11435 list = summary_get_selected_msg_list(summaryview);
11437 for (cur = list; cur; cur = cur->next) {
11438 MsgInfo *msginfo = (MsgInfo *)cur->data;
11439 gchar *file = NULL;
11440 if (msginfo)
11441 file = procmsg_get_message_file_full(msginfo,
11442 TRUE, TRUE);
11443 if (file) {
11444 compose_attach_append(compose, (const gchar *)file,
11445 (const gchar *)file, "message/rfc822", NULL);
11446 g_free(file);
11449 g_slist_free(list);
11453 static gboolean compose_drag_drop(GtkWidget *widget,
11454 GdkDragContext *drag_context,
11455 gint x, gint y,
11456 guint time, gpointer user_data)
11458 /* not handling this signal makes compose_insert_drag_received_cb
11459 * called twice */
11460 return TRUE;
11463 static gboolean completion_set_focus_to_subject
11464 (GtkWidget *widget,
11465 GdkEventKey *event,
11466 Compose *compose)
11468 cm_return_val_if_fail(compose != NULL, FALSE);
11470 /* make backtab move to subject field */
11471 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11472 gtk_widget_grab_focus(compose->subject_entry);
11473 return TRUE;
11475 return FALSE;
11478 static void compose_insert_drag_received_cb (GtkWidget *widget,
11479 GdkDragContext *drag_context,
11480 gint x,
11481 gint y,
11482 GtkSelectionData *data,
11483 guint info,
11484 guint time,
11485 gpointer user_data)
11487 Compose *compose = (Compose *)user_data;
11488 GList *list, *tmp;
11489 GdkAtom type;
11490 guint num_files;
11491 gchar *msg;
11493 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11494 * does not work */
11495 type = gtk_selection_data_get_data_type(data);
11496 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11497 AlertValue val = G_ALERTDEFAULT;
11498 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11500 list = uri_list_extract_filenames(ddata);
11501 num_files = g_list_length(list);
11502 if (list == NULL && strstr(ddata, "://")) {
11503 /* Assume a list of no files, and data has ://, is a remote link */
11504 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11505 gchar *tmpfile = get_tmp_file();
11506 str_write_to_file(tmpdata, tmpfile);
11507 g_free(tmpdata);
11508 compose_insert_file(compose, tmpfile);
11509 claws_unlink(tmpfile);
11510 g_free(tmpfile);
11511 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11512 compose_beautify_paragraph(compose, NULL, TRUE);
11513 return;
11515 switch (prefs_common.compose_dnd_mode) {
11516 case COMPOSE_DND_ASK:
11517 msg = g_strdup_printf(
11518 ngettext(
11519 "Do you want to insert the contents of the file "
11520 "into the message body, or attach it to the email?",
11521 "Do you want to insert the contents of the %d files "
11522 "into the message body, or attach them to the email?",
11523 num_files),
11524 num_files);
11525 val = alertpanel_full(_("Insert or attach?"), msg,
11526 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11527 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11528 g_free(msg);
11529 break;
11530 case COMPOSE_DND_INSERT:
11531 val = G_ALERTALTERNATE;
11532 break;
11533 case COMPOSE_DND_ATTACH:
11534 val = G_ALERTOTHER;
11535 break;
11536 default:
11537 /* unexpected case */
11538 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11541 if (val & G_ALERTDISABLE) {
11542 val &= ~G_ALERTDISABLE;
11543 /* remember what action to perform by default, only if we don't click Cancel */
11544 if (val == G_ALERTALTERNATE)
11545 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11546 else if (val == G_ALERTOTHER)
11547 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11550 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11551 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11552 list_free_strings(list);
11553 g_list_free(list);
11554 return;
11555 } else if (val == G_ALERTOTHER) {
11556 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11557 list_free_strings(list);
11558 g_list_free(list);
11559 return;
11562 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11563 compose_insert_file(compose, (const gchar *)tmp->data);
11565 list_free_strings(list);
11566 g_list_free(list);
11567 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11568 return;
11572 static void compose_header_drag_received_cb (GtkWidget *widget,
11573 GdkDragContext *drag_context,
11574 gint x,
11575 gint y,
11576 GtkSelectionData *data,
11577 guint info,
11578 guint time,
11579 gpointer user_data)
11581 GtkEditable *entry = (GtkEditable *)user_data;
11582 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11584 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11585 * does not work */
11587 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11588 gchar *decoded=g_new(gchar, strlen(email));
11589 int start = 0;
11591 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11592 gtk_editable_delete_text(entry, 0, -1);
11593 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11594 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11595 g_free(decoded);
11596 return;
11598 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11601 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11603 Compose *compose = (Compose *)data;
11605 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11606 compose->return_receipt = TRUE;
11607 else
11608 compose->return_receipt = FALSE;
11611 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11613 Compose *compose = (Compose *)data;
11615 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11616 compose->remove_references = TRUE;
11617 else
11618 compose->remove_references = FALSE;
11621 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11622 ComposeHeaderEntry *headerentry)
11624 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11625 return FALSE;
11628 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11629 GdkEventKey *event,
11630 ComposeHeaderEntry *headerentry)
11632 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11633 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11634 !(event->state & GDK_MODIFIER_MASK) &&
11635 (event->keyval == GDK_KEY_BackSpace) &&
11636 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11637 gtk_container_remove
11638 (GTK_CONTAINER(headerentry->compose->header_table),
11639 headerentry->combo);
11640 gtk_container_remove
11641 (GTK_CONTAINER(headerentry->compose->header_table),
11642 headerentry->entry);
11643 headerentry->compose->header_list =
11644 g_slist_remove(headerentry->compose->header_list,
11645 headerentry);
11646 g_free(headerentry);
11647 } else if (event->keyval == GDK_KEY_Tab) {
11648 if (headerentry->compose->header_last == headerentry) {
11649 /* Override default next focus, and give it to subject_entry
11650 * instead of notebook tabs
11652 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11653 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11654 return TRUE;
11657 return FALSE;
11660 static gboolean scroll_postpone(gpointer data)
11662 Compose *compose = (Compose *)data;
11664 if (compose->batch)
11665 return FALSE;
11667 GTK_EVENTS_FLUSH();
11668 compose_show_first_last_header(compose, FALSE);
11669 return FALSE;
11672 static void compose_headerentry_changed_cb(GtkWidget *entry,
11673 ComposeHeaderEntry *headerentry)
11675 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11676 compose_create_header_entry(headerentry->compose);
11677 g_signal_handlers_disconnect_matched
11678 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11679 0, 0, NULL, NULL, headerentry);
11681 if (!headerentry->compose->batch)
11682 g_timeout_add(0, scroll_postpone, headerentry->compose);
11686 static gboolean compose_defer_auto_save_draft(Compose *compose)
11688 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11689 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11690 return FALSE;
11693 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11695 GtkAdjustment *vadj;
11697 cm_return_if_fail(compose);
11699 if(compose->batch)
11700 return;
11702 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11703 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11704 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11705 gtk_widget_get_parent(compose->header_table)));
11706 gtk_adjustment_set_value(vadj, (show_first ?
11707 gtk_adjustment_get_lower(vadj) :
11708 (gtk_adjustment_get_upper(vadj) -
11709 gtk_adjustment_get_page_size(vadj))));
11710 gtk_adjustment_changed(vadj);
11713 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11714 const gchar *text, gint len, Compose *compose)
11716 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11717 (G_OBJECT(compose->text), "paste_as_quotation"));
11718 GtkTextMark *mark;
11720 cm_return_if_fail(text != NULL);
11722 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11723 G_CALLBACK(text_inserted),
11724 compose);
11725 if (paste_as_quotation) {
11726 gchar *new_text;
11727 const gchar *qmark;
11728 guint pos = 0;
11729 GtkTextIter start_iter;
11731 if (len < 0)
11732 len = strlen(text);
11734 new_text = g_strndup(text, len);
11736 qmark = compose_quote_char_from_context(compose);
11738 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11739 gtk_text_buffer_place_cursor(buffer, iter);
11741 pos = gtk_text_iter_get_offset(iter);
11743 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11744 _("Quote format error at line %d."));
11745 quote_fmt_reset_vartable();
11746 g_free(new_text);
11747 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11748 GINT_TO_POINTER(paste_as_quotation - 1));
11750 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11751 gtk_text_buffer_place_cursor(buffer, iter);
11752 gtk_text_buffer_delete_mark(buffer, mark);
11754 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11755 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11756 compose_beautify_paragraph(compose, &start_iter, FALSE);
11757 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11758 gtk_text_buffer_delete_mark(buffer, mark);
11759 } else {
11760 if (strcmp(text, "\n") || compose->automatic_break
11761 || gtk_text_iter_starts_line(iter)) {
11762 GtkTextIter before_ins;
11763 gtk_text_buffer_insert(buffer, iter, text, len);
11764 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11765 before_ins = *iter;
11766 gtk_text_iter_backward_chars(&before_ins, len);
11767 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11769 } else {
11770 /* check if the preceding is just whitespace or quote */
11771 GtkTextIter start_line;
11772 gchar *tmp = NULL, *quote = NULL;
11773 gint quote_len = 0, is_normal = 0;
11774 start_line = *iter;
11775 gtk_text_iter_set_line_offset(&start_line, 0);
11776 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11777 g_strstrip(tmp);
11779 if (*tmp == '\0') {
11780 is_normal = 1;
11781 } else {
11782 quote = compose_get_quote_str(buffer, &start_line, &quote_len);
11783 if (quote)
11784 is_normal = 1;
11785 g_free(quote);
11787 g_free(tmp);
11789 if (is_normal) {
11790 gtk_text_buffer_insert(buffer, iter, text, len);
11791 } else {
11792 gtk_text_buffer_insert_with_tags_by_name(buffer,
11793 iter, text, len, "no_join", NULL);
11798 if (!paste_as_quotation) {
11799 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11800 compose_beautify_paragraph(compose, iter, FALSE);
11801 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11802 gtk_text_buffer_delete_mark(buffer, mark);
11805 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11806 G_CALLBACK(text_inserted),
11807 compose);
11808 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11810 if (compose_can_autosave(compose) &&
11811 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11812 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11813 compose->draft_timeout_tag = g_timeout_add
11814 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11817 #if USE_ENCHANT
11818 static void compose_check_all(GtkAction *action, gpointer data)
11820 Compose *compose = (Compose *)data;
11821 if (!compose->gtkaspell)
11822 return;
11824 if (gtk_widget_has_focus(compose->subject_entry))
11825 claws_spell_entry_check_all(
11826 CLAWS_SPELL_ENTRY(compose->subject_entry));
11827 else
11828 gtkaspell_check_all(compose->gtkaspell);
11831 static void compose_highlight_all(GtkAction *action, gpointer data)
11833 Compose *compose = (Compose *)data;
11834 if (compose->gtkaspell) {
11835 claws_spell_entry_recheck_all(
11836 CLAWS_SPELL_ENTRY(compose->subject_entry));
11837 gtkaspell_highlight_all(compose->gtkaspell);
11841 static void compose_check_backwards(GtkAction *action, gpointer data)
11843 Compose *compose = (Compose *)data;
11844 if (!compose->gtkaspell) {
11845 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11846 return;
11849 if (gtk_widget_has_focus(compose->subject_entry))
11850 claws_spell_entry_check_backwards(
11851 CLAWS_SPELL_ENTRY(compose->subject_entry));
11852 else
11853 gtkaspell_check_backwards(compose->gtkaspell);
11856 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11858 Compose *compose = (Compose *)data;
11859 if (!compose->gtkaspell) {
11860 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11861 return;
11864 if (gtk_widget_has_focus(compose->subject_entry))
11865 claws_spell_entry_check_forwards_go(
11866 CLAWS_SPELL_ENTRY(compose->subject_entry));
11867 else
11868 gtkaspell_check_forwards_go(compose->gtkaspell);
11870 #endif
11873 *\brief Guess originating forward account from MsgInfo and several
11874 * "common preference" settings. Return NULL if no guess.
11876 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
11878 PrefsAccount *account = NULL;
11880 cm_return_val_if_fail(msginfo, NULL);
11881 cm_return_val_if_fail(msginfo->folder, NULL);
11882 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11884 if (msginfo->folder->prefs->enable_default_account)
11885 account = account_find_from_id(msginfo->folder->prefs->default_account);
11887 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11888 gchar *to;
11889 Xstrdup_a(to, msginfo->to, return NULL);
11890 extract_address(to);
11891 account = account_find_from_address(to, FALSE);
11894 if (!account && prefs_common.forward_account_autosel) {
11895 gchar *cc = NULL;
11896 if (!procheader_get_header_from_msginfo
11897 (msginfo, &cc, "Cc:")) {
11898 gchar *buf = cc + strlen("Cc:");
11899 extract_address(buf);
11900 account = account_find_from_address(buf, FALSE);
11901 g_free(cc);
11905 if (!account && prefs_common.forward_account_autosel) {
11906 gchar *deliveredto = NULL;
11907 if (!procheader_get_header_from_msginfo
11908 (msginfo, &deliveredto, "Delivered-To:")) {
11909 gchar *buf = deliveredto + strlen("Delivered-To:");
11910 extract_address(buf);
11911 account = account_find_from_address(buf, FALSE);
11912 g_free(deliveredto);
11916 if (!account)
11917 account = msginfo->folder->folder->account;
11919 return account;
11922 gboolean compose_close(Compose *compose)
11924 gint x, y;
11926 cm_return_val_if_fail(compose, FALSE);
11928 if (!g_mutex_trylock(compose->mutex)) {
11929 /* we have to wait for the (possibly deferred by auto-save)
11930 * drafting to be done, before destroying the compose under
11931 * it. */
11932 debug_print("waiting for drafting to finish...\n");
11933 compose_allow_user_actions(compose, FALSE);
11934 if (compose->close_timeout_tag == 0) {
11935 compose->close_timeout_tag =
11936 g_timeout_add (500, (GSourceFunc) compose_close,
11937 compose);
11939 return TRUE;
11942 if (compose->draft_timeout_tag >= 0) {
11943 g_source_remove(compose->draft_timeout_tag);
11944 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11947 gtkut_widget_get_uposition(compose->window, &x, &y);
11948 if (!compose->batch) {
11949 prefs_common.compose_x = x;
11950 prefs_common.compose_y = y;
11952 g_mutex_unlock(compose->mutex);
11953 compose_destroy(compose);
11954 return FALSE;
11958 * Add entry field for each address in list.
11959 * \param compose E-Mail composition object.
11960 * \param listAddress List of (formatted) E-Mail addresses.
11962 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11963 GList *node;
11964 gchar *addr;
11965 node = listAddress;
11966 while( node ) {
11967 addr = ( gchar * ) node->data;
11968 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11969 node = g_list_next( node );
11973 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11974 guint action, gboolean opening_multiple)
11976 gchar *body = NULL;
11977 GSList *new_msglist = NULL;
11978 MsgInfo *tmp_msginfo = NULL;
11979 gboolean originally_enc = FALSE;
11980 gboolean originally_sig = FALSE;
11981 Compose *compose = NULL;
11982 gchar *s_system = NULL;
11984 cm_return_if_fail(msgview != NULL);
11986 cm_return_if_fail(msginfo_list != NULL);
11988 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11989 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11990 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11992 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11993 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11994 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11995 orig_msginfo, mimeinfo);
11996 if (tmp_msginfo != NULL) {
11997 new_msglist = g_slist_append(NULL, tmp_msginfo);
11999 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12000 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12001 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12003 tmp_msginfo->folder = orig_msginfo->folder;
12004 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12005 if (orig_msginfo->tags) {
12006 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12007 tmp_msginfo->folder->tags_dirty = TRUE;
12013 if (!opening_multiple)
12014 body = messageview_get_selection(msgview);
12016 if (new_msglist) {
12017 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12018 procmsg_msginfo_free(&tmp_msginfo);
12019 g_slist_free(new_msglist);
12020 } else
12021 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12023 if (compose && originally_enc) {
12024 compose_force_encryption(compose, compose->account, FALSE, s_system);
12027 if (compose && originally_sig && compose->account->default_sign_reply) {
12028 compose_force_signing(compose, compose->account, s_system);
12030 g_free(s_system);
12031 g_free(body);
12032 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12035 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12036 guint action)
12038 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12039 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12040 GSList *cur = msginfo_list;
12041 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12042 "messages. Opening the windows "
12043 "could take some time. Do you "
12044 "want to continue?"),
12045 g_slist_length(msginfo_list));
12046 if (g_slist_length(msginfo_list) > 9
12047 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
12048 != G_ALERTALTERNATE) {
12049 g_free(msg);
12050 return;
12052 g_free(msg);
12053 /* We'll open multiple compose windows */
12054 /* let the WM place the next windows */
12055 compose_force_window_origin = FALSE;
12056 for (; cur; cur = cur->next) {
12057 GSList tmplist;
12058 tmplist.data = cur->data;
12059 tmplist.next = NULL;
12060 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12062 compose_force_window_origin = TRUE;
12063 } else {
12064 /* forwarding multiple mails as attachments is done via a
12065 * single compose window */
12066 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12070 void compose_check_for_email_account(Compose *compose)
12072 PrefsAccount *ac = NULL, *curr = NULL;
12073 GList *list;
12075 if (!compose)
12076 return;
12078 if (compose->account && compose->account->protocol == A_NNTP) {
12079 ac = account_get_cur_account();
12080 if (ac->protocol == A_NNTP) {
12081 list = account_get_list();
12083 for( ; list != NULL ; list = g_list_next(list)) {
12084 curr = (PrefsAccount *) list->data;
12085 if (curr->protocol != A_NNTP) {
12086 ac = curr;
12087 break;
12091 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12092 ac->account_id);
12096 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12097 const gchar *address)
12099 GSList *msginfo_list = NULL;
12100 gchar *body = messageview_get_selection(msgview);
12101 Compose *compose;
12103 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12105 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12106 compose_check_for_email_account(compose);
12107 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12108 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12109 compose_reply_set_subject(compose, msginfo);
12111 g_free(body);
12112 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12115 void compose_set_position(Compose *compose, gint pos)
12117 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12119 gtkut_text_view_set_position(text, pos);
12122 gboolean compose_search_string(Compose *compose,
12123 const gchar *str, gboolean case_sens)
12125 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12127 return gtkut_text_view_search_string(text, str, case_sens);
12130 gboolean compose_search_string_backward(Compose *compose,
12131 const gchar *str, gboolean case_sens)
12133 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12135 return gtkut_text_view_search_string_backward(text, str, case_sens);
12138 /* allocate a msginfo structure and populate its data from a compose data structure */
12139 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12141 MsgInfo *newmsginfo;
12142 GSList *list;
12143 gchar date[RFC822_DATE_BUFFSIZE];
12145 cm_return_val_if_fail( compose != NULL, NULL );
12147 newmsginfo = procmsg_msginfo_new();
12149 /* date is now */
12150 get_rfc822_date(date, sizeof(date));
12151 newmsginfo->date = g_strdup(date);
12153 /* from */
12154 if (compose->from_name) {
12155 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12156 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12159 /* subject */
12160 if (compose->subject_entry)
12161 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12163 /* to, cc, reply-to, newsgroups */
12164 for (list = compose->header_list; list; list = list->next) {
12165 gchar *header = gtk_editable_get_chars(
12166 GTK_EDITABLE(
12167 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12168 gchar *entry = gtk_editable_get_chars(
12169 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12171 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12172 if ( newmsginfo->to == NULL ) {
12173 newmsginfo->to = g_strdup(entry);
12174 } else if (entry && *entry) {
12175 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12176 g_free(newmsginfo->to);
12177 newmsginfo->to = tmp;
12179 } else
12180 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12181 if ( newmsginfo->cc == NULL ) {
12182 newmsginfo->cc = g_strdup(entry);
12183 } else if (entry && *entry) {
12184 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12185 g_free(newmsginfo->cc);
12186 newmsginfo->cc = tmp;
12188 } else
12189 if ( strcasecmp(header,
12190 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12191 if ( newmsginfo->newsgroups == NULL ) {
12192 newmsginfo->newsgroups = g_strdup(entry);
12193 } else if (entry && *entry) {
12194 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12195 g_free(newmsginfo->newsgroups);
12196 newmsginfo->newsgroups = tmp;
12200 g_free(header);
12201 g_free(entry);
12204 /* other data is unset */
12206 return newmsginfo;
12209 #ifdef USE_ENCHANT
12210 /* update compose's dictionaries from folder dict settings */
12211 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12212 FolderItem *folder_item)
12214 cm_return_if_fail(compose != NULL);
12216 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12217 FolderItemPrefs *prefs = folder_item->prefs;
12219 if (prefs->enable_default_dictionary)
12220 gtkaspell_change_dict(compose->gtkaspell,
12221 prefs->default_dictionary, FALSE);
12222 if (folder_item->prefs->enable_default_alt_dictionary)
12223 gtkaspell_change_alt_dict(compose->gtkaspell,
12224 prefs->default_alt_dictionary);
12225 if (prefs->enable_default_dictionary
12226 || prefs->enable_default_alt_dictionary)
12227 compose_spell_menu_changed(compose);
12230 #endif
12232 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12234 Compose *compose = (Compose *)data;
12236 cm_return_if_fail(compose != NULL);
12238 gtk_widget_grab_focus(compose->text);
12241 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12243 gtk_combo_box_popup(GTK_COMBO_BOX(data));
12248 * End of Source.