Use custom get_default_font_size() and _name()
[claws.git] / src / compose.c
blob1966e5ac0bcdde4ddf49cf437cab173d3f5c09c6
1 /*
2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2023 the Claws Mail team and Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #include "claws-features.h"
22 #endif
24 #include "defs.h"
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
28 #endif
30 #include <glib.h>
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtk.h>
34 #ifdef GDK_WINDOWING_X11
35 #include <gtk/gtkx.h>
36 #endif
38 #include <pango/pango-break.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <ctype.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <unistd.h>
46 #include <time.h>
47 #if HAVE_SYS_WAIT_H
48 # include <sys/wait.h>
49 #endif
50 #include <signal.h>
51 #include <errno.h>
52 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
53 #include <libgen.h>
54 #endif
55 #ifdef G_OS_WIN32
56 #include <windows.h>
57 #endif
59 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
60 # include <wchar.h>
61 # include <wctype.h>
62 #endif
64 #include "claws.h"
65 #include "main.h"
66 #include "mainwindow.h"
67 #include "compose.h"
68 #ifndef USE_ALT_ADDRBOOK
69 #include "addressbook.h"
70 #else
71 #include "addressbook-dbus.h"
72 #include "addressadd.h"
73 #endif
74 #include "folderview.h"
75 #include "procmsg.h"
76 #include "menu.h"
77 #include "stock_pixmap.h"
78 #include "send_message.h"
79 #include "imap.h"
80 #include "news.h"
81 #include "customheader.h"
82 #include "prefs_common.h"
83 #include "prefs_account.h"
84 #include "action.h"
85 #include "account.h"
86 #include "filesel.h"
87 #include "procheader.h"
88 #include "procmime.h"
89 #include "statusbar.h"
90 #include "about.h"
91 #include "quoted-printable.h"
92 #include "codeconv.h"
93 #include "utils.h"
94 #include "gtkutils.h"
95 #include "gtkshruler.h"
96 #include "socket.h"
97 #include "alertpanel.h"
98 #include "manage_window.h"
99 #include "folder.h"
100 #include "folder_item_prefs.h"
101 #include "addr_compl.h"
102 #include "quote_fmt.h"
103 #include "undo.h"
104 #include "foldersel.h"
105 #include "toolbar.h"
106 #include "inc.h"
107 #include "message_search.h"
108 #include "combobox.h"
109 #include "hooks.h"
110 #include "privacy.h"
111 #include "timing.h"
112 #include "autofaces.h"
113 #include "spell_entry.h"
114 #include "headers.h"
115 #include "file-utils.h"
117 #ifdef USE_LDAP
118 #include "password.h"
119 #include "ldapserver.h"
120 #endif
122 enum
124 COL_MIMETYPE = 0,
125 COL_SIZE = 1,
126 COL_NAME = 2,
127 COL_CHARSET = 3,
128 COL_DATA = 4,
129 COL_AUTODATA = 5,
130 N_COL_COLUMNS
133 #define N_ATTACH_COLS (N_COL_COLUMNS)
135 typedef enum
137 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
138 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
139 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
140 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
141 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
142 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
143 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
144 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
145 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
146 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
147 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
148 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
149 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
150 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
151 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
152 } ComposeCallAdvancedAction;
154 typedef enum
156 PRIORITY_HIGHEST = 1,
157 PRIORITY_HIGH,
158 PRIORITY_NORMAL,
159 PRIORITY_LOW,
160 PRIORITY_LOWEST
161 } PriorityLevel;
163 typedef enum
165 COMPOSE_INSERT_SUCCESS,
166 COMPOSE_INSERT_READ_ERROR,
167 COMPOSE_INSERT_INVALID_CHARACTER,
168 COMPOSE_INSERT_NO_FILE
169 } ComposeInsertResult;
171 typedef enum
173 COMPOSE_WRITE_FOR_SEND,
174 COMPOSE_WRITE_FOR_STORE
175 } ComposeWriteType;
177 typedef enum
179 COMPOSE_QUOTE_FORCED,
180 COMPOSE_QUOTE_CHECK,
181 COMPOSE_QUOTE_SKIP
182 } ComposeQuoteMode;
184 typedef enum {
185 TO_FIELD_PRESENT,
186 SUBJECT_FIELD_PRESENT,
187 BODY_FIELD_PRESENT,
188 NO_FIELD_PRESENT
189 } MailField;
191 #define B64_LINE_SIZE 57
192 #define B64_BUFFSIZE 77
194 #define MAX_REFERENCES_LEN 999
196 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
197 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
199 #define COMPOSE_PRIVACY_WARNING() { \
200 alertpanel_error(_("You have opted to sign and/or encrypt this " \
201 "message but have not selected a privacy system.\n\n" \
202 "Signing and encrypting have been disabled for this " \
203 "message.")); \
206 #ifdef G_OS_WIN32
207 #define INVALID_PID INVALID_HANDLE_VALUE
208 #else
209 #define INVALID_PID -1
210 #endif
212 static GdkRGBA default_header_bgcolor =
213 {0, 0, 0, 1};
215 static GdkRGBA default_header_color =
216 {0, 0, 0, 1};
218 static GList *compose_list = NULL;
219 static GSList *extra_headers = NULL;
221 static Compose *compose_generic_new (PrefsAccount *account,
222 const gchar *to,
223 FolderItem *item,
224 GList *attach_files,
225 GList *listAddress );
227 static Compose *compose_create (PrefsAccount *account,
228 FolderItem *item,
229 ComposeMode mode,
230 gboolean batch);
232 static void compose_entry_indicate (Compose *compose,
233 const gchar *address);
234 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
235 ComposeQuoteMode quote_mode,
236 gboolean to_all,
237 gboolean to_sender,
238 const gchar *body);
239 static Compose *compose_forward_multiple (PrefsAccount *account,
240 GSList *msginfo_list);
241 static Compose *compose_reply (MsgInfo *msginfo,
242 ComposeQuoteMode quote_mode,
243 gboolean to_all,
244 gboolean to_ml,
245 gboolean to_sender,
246 const gchar *body);
247 static Compose *compose_reply_mode (ComposeMode mode,
248 GSList *msginfo_list,
249 gchar *body);
250 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
251 static void compose_update_privacy_systems_menu(Compose *compose);
253 static GtkWidget *compose_account_option_menu_create
254 (Compose *compose);
255 static void compose_set_out_encoding (Compose *compose);
256 static void compose_set_template_menu (Compose *compose);
257 static void compose_destroy (Compose *compose);
259 static MailField compose_entries_set (Compose *compose,
260 const gchar *mailto,
261 ComposeEntryType to_type);
262 static gint compose_parse_header (Compose *compose,
263 MsgInfo *msginfo);
264 static gint compose_parse_manual_headers (Compose *compose,
265 MsgInfo *msginfo,
266 HeaderEntry *entries);
267 static gchar *compose_parse_references (const gchar *ref,
268 const gchar *msgid);
270 static gchar *compose_quote_fmt (Compose *compose,
271 MsgInfo *msginfo,
272 const gchar *fmt,
273 const gchar *qmark,
274 const gchar *body,
275 gboolean rewrap,
276 gboolean need_unescape,
277 const gchar *err_msg);
279 static void compose_reply_set_entry (Compose *compose,
280 MsgInfo *msginfo,
281 gboolean to_all,
282 gboolean to_ml,
283 gboolean to_sender,
284 gboolean
285 followup_and_reply_to);
286 static void compose_reedit_set_entry (Compose *compose,
287 MsgInfo *msginfo);
289 static void compose_insert_sig (Compose *compose,
290 gboolean replace);
291 static ComposeInsertResult compose_insert_file (Compose *compose,
292 const gchar *file);
294 static gboolean compose_attach_append (Compose *compose,
295 const gchar *file,
296 const gchar *type,
297 const gchar *content_type,
298 const gchar *charset);
299 static void compose_attach_parts (Compose *compose,
300 MsgInfo *msginfo);
302 static gboolean compose_beautify_paragraph (Compose *compose,
303 GtkTextIter *par_iter,
304 gboolean force);
305 static void compose_wrap_all (Compose *compose);
306 static void compose_wrap_all_full (Compose *compose,
307 gboolean autowrap);
309 static void compose_set_title (Compose *compose);
310 static void compose_select_account (Compose *compose,
311 PrefsAccount *account,
312 gboolean init);
314 static PrefsAccount *compose_current_mail_account(void);
315 /* static gint compose_send (Compose *compose); */
316 static gboolean compose_check_for_valid_recipient
317 (Compose *compose);
318 static gboolean compose_check_entries (Compose *compose,
319 gboolean check_everything);
320 static gint compose_write_to_file (Compose *compose,
321 FILE *fp,
322 gint action,
323 gboolean attach_parts);
324 static gint compose_write_body_to_file (Compose *compose,
325 const gchar *file);
326 static gint compose_remove_reedit_target (Compose *compose,
327 gboolean force);
328 static void compose_remove_draft (Compose *compose);
329 static ComposeQueueResult compose_queue_sub (Compose *compose,
330 gint *msgnum,
331 FolderItem **item,
332 gchar **msgpath,
333 gboolean perform_checks,
334 gboolean remove_reedit_target);
335 static int compose_add_attachments (Compose *compose,
336 MimeInfo *parent,
337 gint action);
338 static gchar *compose_get_header (Compose *compose);
339 static gchar *compose_get_manual_headers_info (Compose *compose);
341 static void compose_convert_header (Compose *compose,
342 gchar *dest,
343 gint len,
344 gchar *src,
345 gint header_len,
346 gboolean addr_field);
348 static void compose_attach_info_free (AttachInfo *ainfo);
349 static void compose_attach_remove_selected (GtkAction *action,
350 gpointer data);
352 static void compose_template_apply (Compose *compose,
353 Template *tmpl,
354 gboolean replace);
355 static void compose_attach_property (GtkAction *action,
356 gpointer data);
357 static void compose_attach_property_create (gboolean *cancelled);
358 static void attach_property_ok (GtkWidget *widget,
359 gboolean *cancelled);
360 static void attach_property_cancel (GtkWidget *widget,
361 gboolean *cancelled);
362 static gint attach_property_delete_event (GtkWidget *widget,
363 GdkEventAny *event,
364 gboolean *cancelled);
365 static gboolean attach_property_key_pressed (GtkWidget *widget,
366 GdkEventKey *event,
367 gboolean *cancelled);
369 static void compose_exec_ext_editor (Compose *compose);
370 static gboolean compose_ext_editor_kill (Compose *compose);
371 static void compose_ext_editor_closed_cb (GPid pid,
372 gint exit_status,
373 gpointer data);
374 static void compose_set_ext_editor_sensitive (Compose *compose,
375 gboolean sensitive);
376 static gboolean compose_get_ext_editor_cmd_valid();
377 static gboolean compose_get_ext_editor_uses_socket();
378 #ifndef G_OS_WIN32
379 static gboolean compose_ext_editor_plug_removed_cb
380 (GtkSocket *socket,
381 Compose *compose);
382 #endif /* G_OS_WIN32 */
384 static void compose_undo_state_changed (UndoMain *undostruct,
385 gint undo_state,
386 gint redo_state,
387 gpointer data);
389 static void compose_create_header_entry (Compose *compose);
390 static void compose_add_header_entry (Compose *compose, const gchar *header,
391 gchar *text, ComposePrefType pref_type);
392 static void compose_remove_header_entries(Compose *compose);
394 static void compose_update_priority_menu_item(Compose * compose);
395 #if USE_ENCHANT
396 static void compose_spell_menu_changed (void *data);
397 static void compose_dict_changed (void *data);
398 #endif
399 static void compose_add_field_list ( Compose *compose,
400 GList *listAddress );
402 /* callback functions */
404 static void compose_notebook_size_alloc (GtkNotebook *notebook,
405 GtkAllocation *allocation,
406 GtkPaned *paned);
407 static gboolean compose_edit_size_alloc (GtkEditable *widget,
408 GtkAllocation *allocation,
409 GtkSHRuler *shruler);
410 static void account_activated (GtkComboBox *optmenu,
411 gpointer data);
412 static void attach_selected (GtkTreeView *tree_view,
413 GtkTreePath *tree_path,
414 GtkTreeViewColumn *column,
415 Compose *compose);
416 static gboolean attach_button_pressed (GtkWidget *widget,
417 GdkEventButton *event,
418 gpointer data);
419 static gboolean attach_key_pressed (GtkWidget *widget,
420 GdkEventKey *event,
421 gpointer data);
422 static void compose_send_cb (GtkAction *action, gpointer data);
423 static void compose_send_later_cb (GtkAction *action, gpointer data);
425 static void compose_save_cb (GtkAction *action,
426 gpointer data);
428 static void compose_attach_cb (GtkAction *action,
429 gpointer data);
430 static void compose_insert_file_cb (GtkAction *action,
431 gpointer data);
432 static void compose_insert_sig_cb (GtkAction *action,
433 gpointer data);
434 static void compose_replace_sig_cb (GtkAction *action,
435 gpointer data);
437 static void compose_close_cb (GtkAction *action,
438 gpointer data);
439 static void compose_print_cb (GtkAction *action,
440 gpointer data);
442 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
444 static void compose_address_cb (GtkAction *action,
445 gpointer data);
446 static void about_show_cb (GtkAction *action,
447 gpointer data);
448 static void compose_template_activate_cb(GtkWidget *widget,
449 gpointer data);
451 static void compose_ext_editor_cb (GtkAction *action,
452 gpointer data);
454 static gint compose_delete_cb (GtkWidget *widget,
455 GdkEventAny *event,
456 gpointer data);
458 static void compose_undo_cb (GtkAction *action,
459 gpointer data);
460 static void compose_redo_cb (GtkAction *action,
461 gpointer data);
462 static void compose_cut_cb (GtkAction *action,
463 gpointer data);
464 static void compose_copy_cb (GtkAction *action,
465 gpointer data);
466 static void compose_paste_cb (GtkAction *action,
467 gpointer data);
468 static void compose_paste_as_quote_cb (GtkAction *action,
469 gpointer data);
470 static void compose_paste_no_wrap_cb (GtkAction *action,
471 gpointer data);
472 static void compose_paste_wrap_cb (GtkAction *action,
473 gpointer data);
474 static void compose_allsel_cb (GtkAction *action,
475 gpointer data);
477 static void compose_advanced_action_cb (GtkAction *action,
478 gpointer data);
480 static void compose_grab_focus_cb (GtkWidget *widget,
481 Compose *compose);
483 static void compose_changed_cb (GtkTextBuffer *textbuf,
484 Compose *compose);
486 static void compose_wrap_cb (GtkAction *action,
487 gpointer data);
488 static void compose_wrap_all_cb (GtkAction *action,
489 gpointer data);
490 static void compose_find_cb (GtkAction *action,
491 gpointer data);
492 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
493 gpointer data);
494 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
495 gpointer data);
497 static void compose_toggle_ruler_cb (GtkToggleAction *action,
498 gpointer data);
499 static void compose_toggle_sign_cb (GtkToggleAction *action,
500 gpointer data);
501 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
502 gpointer data);
503 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
504 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
505 static void compose_activate_privacy_system (Compose *compose,
506 PrefsAccount *account,
507 gboolean warn);
508 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item);
509 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
510 gpointer data);
511 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
512 gpointer data);
513 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
514 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
515 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
517 static void compose_attach_drag_received_cb (GtkWidget *widget,
518 GdkDragContext *drag_context,
519 gint x,
520 gint y,
521 GtkSelectionData *data,
522 guint info,
523 guint time,
524 gpointer user_data);
525 static void compose_insert_drag_received_cb (GtkWidget *widget,
526 GdkDragContext *drag_context,
527 gint x,
528 gint y,
529 GtkSelectionData *data,
530 guint info,
531 guint time,
532 gpointer user_data);
533 static void compose_header_drag_received_cb (GtkWidget *widget,
534 GdkDragContext *drag_context,
535 gint x,
536 gint y,
537 GtkSelectionData *data,
538 guint info,
539 guint time,
540 gpointer user_data);
542 static gboolean compose_drag_drop (GtkWidget *widget,
543 GdkDragContext *drag_context,
544 gint x, gint y,
545 guint time, gpointer user_data);
546 static gboolean completion_set_focus_to_subject
547 (GtkWidget *widget,
548 GdkEventKey *event,
549 Compose *user_data);
551 static void text_inserted (GtkTextBuffer *buffer,
552 GtkTextIter *iter,
553 const gchar *text,
554 gint len,
555 Compose *compose);
556 static Compose *compose_generic_reply(MsgInfo *msginfo,
557 ComposeQuoteMode quote_mode,
558 gboolean to_all,
559 gboolean to_ml,
560 gboolean to_sender,
561 gboolean followup_and_reply_to,
562 const gchar *body);
564 static void compose_headerentry_changed_cb (GtkWidget *entry,
565 ComposeHeaderEntry *headerentry);
566 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
567 GdkEventKey *event,
568 ComposeHeaderEntry *headerentry);
569 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
570 ComposeHeaderEntry *headerentry);
572 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
574 static void compose_allow_user_actions (Compose *compose, gboolean allow);
576 static void compose_nothing_cb (GtkAction *action, gpointer data)
581 #if USE_ENCHANT
582 static void compose_check_all (GtkAction *action, gpointer data);
583 static void compose_highlight_all (GtkAction *action, gpointer data);
584 static void compose_check_backwards (GtkAction *action, gpointer data);
585 static void compose_check_forwards_go (GtkAction *action, gpointer data);
586 #endif
588 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
590 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
592 #ifdef USE_ENCHANT
593 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
594 FolderItem *folder_item);
595 #endif
596 static void compose_attach_update_label(Compose *compose);
597 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
598 gboolean respect_default_to);
599 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
600 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
602 static GtkActionEntry compose_popup_entries[] =
604 {"Compose", NULL, "Compose", NULL, NULL, NULL },
605 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
606 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
607 {"Compose/---", NULL, "---", NULL, NULL, NULL },
608 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
611 static GtkActionEntry compose_entries[] =
613 {"Menu", NULL, "Menu", NULL, NULL, NULL },
614 /* menus */
615 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
616 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
617 #if USE_ENCHANT
618 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
619 #endif
620 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
621 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
622 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
623 /* Message menu */
624 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
625 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
626 {"Message/---", NULL, "---", NULL, NULL, NULL },
628 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
629 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
630 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
631 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
632 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
633 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
634 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
635 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
636 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
637 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
639 /* Edit menu */
640 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
641 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
642 {"Edit/---", NULL, "---", NULL, NULL, NULL },
644 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
645 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
646 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
648 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
649 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
650 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
651 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
653 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
655 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
656 {"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*/
657 {"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*/
658 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
659 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
660 {"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*/
661 {"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*/
662 {"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*/
663 {"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*/
664 {"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*/
665 {"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*/
666 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
667 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
668 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
669 {"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*/
671 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
672 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
674 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
675 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
676 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
677 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
678 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
679 #if USE_ENCHANT
680 /* Spelling menu */
681 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
682 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
683 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
684 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
686 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
687 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
688 #endif
690 /* Options menu */
691 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
692 {"Options/---", NULL, "---", NULL, NULL, NULL },
693 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
694 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
696 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
697 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
699 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
700 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
701 #define ENC_ACTION(cs_char,c_char,string) \
702 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
704 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
705 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
706 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
707 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
708 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
709 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
710 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
711 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
712 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
714 /* Tools menu */
715 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
717 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
718 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
719 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
720 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
722 /* Help menu */
723 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
726 static GtkToggleActionEntry compose_toggle_entries[] =
728 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
729 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
730 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
731 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
732 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
733 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
734 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
737 static GtkRadioActionEntry compose_radio_rm_entries[] =
739 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
740 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
741 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
742 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
745 static GtkRadioActionEntry compose_radio_prio_entries[] =
747 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
748 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
749 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
750 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
751 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
754 static GtkRadioActionEntry compose_radio_enc_entries[] =
756 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
777 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
778 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
779 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
780 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
781 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
782 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
783 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
784 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
785 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
786 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
787 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
788 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
791 static GtkTargetEntry compose_mime_types[] =
793 {"text/uri-list", 0, 0},
794 {"UTF8_STRING", 0, 0},
795 {"text/plain", 0, 0}
798 static gboolean compose_put_existing_to_front(MsgInfo *info)
800 const GList *compose_list = compose_get_compose_list();
801 const GList *elem = NULL;
803 if (compose_list) {
804 for (elem = compose_list; elem != NULL && elem->data != NULL;
805 elem = elem->next) {
806 Compose *c = (Compose*)elem->data;
808 if (!c->targetinfo || !c->targetinfo->msgid ||
809 !info->msgid)
810 continue;
812 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
813 gtkut_window_popup(c->window);
814 return TRUE;
818 return FALSE;
821 static GdkRGBA quote_color1 =
822 {0, 0, 0, 1};
823 static GdkRGBA quote_color2 =
824 {0, 0, 0, 1};
825 static GdkRGBA quote_color3 =
826 {0, 0, 0, 1};
828 static GdkRGBA quote_bgcolor1 =
829 {0, 0, 0, 1};
830 static GdkRGBA quote_bgcolor2 =
831 {0, 0, 0, 1};
832 static GdkRGBA quote_bgcolor3 =
833 {0, 0, 0, 1};
835 static GdkRGBA signature_color =
836 {0.5, 0.5, 0.5, 1};
838 static GdkRGBA uri_color =
839 {0, 0, 0, 1};
841 static void compose_create_tags(GtkTextView *text, Compose *compose)
843 GtkTextBuffer *buffer;
844 GdkRGBA black = { 0, 0, 0, 1 };
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 quote_color1 = prefs_common.color[COL_QUOTE_LEVEL1];
851 quote_color2 = prefs_common.color[COL_QUOTE_LEVEL2];
852 quote_color3 = prefs_common.color[COL_QUOTE_LEVEL3];
853 quote_bgcolor1 = prefs_common.color[COL_QUOTE_LEVEL1_BG];
854 quote_bgcolor2 = prefs_common.color[COL_QUOTE_LEVEL2_BG];
855 quote_bgcolor3 = prefs_common.color[COL_QUOTE_LEVEL3_BG];
856 signature_color = prefs_common.color[COL_SIGNATURE];
857 uri_color = prefs_common.color[COL_URI];
858 } else {
859 signature_color = quote_color1 = quote_color2 = quote_color3 =
860 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
863 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
864 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
865 "foreground-rgba", &quote_color1,
866 "paragraph-background-rgba", &quote_bgcolor1,
867 NULL);
868 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
869 "foreground-rgba", &quote_color2,
870 "paragraph-background-rgba", &quote_bgcolor2,
871 NULL);
872 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
873 "foreground-rgba", &quote_color3,
874 "paragraph-background-rgba", &quote_bgcolor3,
875 NULL);
876 } else {
877 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
878 "foreground-rgba", &quote_color1,
879 NULL);
880 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
881 "foreground-rgba", &quote_color2,
882 NULL);
883 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
884 "foreground-rgba", &quote_color3,
885 NULL);
888 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
889 "foreground-rgba", &signature_color,
890 NULL);
892 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
893 "foreground-rgba", &uri_color,
894 NULL);
895 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
896 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
899 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
900 GList *attach_files)
902 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
905 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
907 return compose_generic_new(account, mailto, item, NULL, NULL);
910 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
912 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
915 #define SCROLL_TO_CURSOR(compose) { \
916 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
917 gtk_text_view_get_buffer( \
918 GTK_TEXT_VIEW(compose->text))); \
919 gtk_text_view_scroll_mark_onscreen( \
920 GTK_TEXT_VIEW(compose->text), \
921 cmark); \
924 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
926 GtkEditable *entry;
927 if (folderidentifier) {
928 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
929 prefs_common.compose_save_to_history = add_history(
930 prefs_common.compose_save_to_history, folderidentifier);
931 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
932 prefs_common.compose_save_to_history);
935 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
936 if (folderidentifier)
937 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
938 else
939 gtk_entry_set_text(GTK_ENTRY(entry), "");
942 static gchar *compose_get_save_to(Compose *compose)
944 GtkEditable *entry;
945 gchar *result = NULL;
946 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
947 result = gtk_editable_get_chars(entry, 0, -1);
949 if (result) {
950 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
951 prefs_common.compose_save_to_history = add_history(
952 prefs_common.compose_save_to_history, result);
953 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
954 prefs_common.compose_save_to_history);
956 return result;
959 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
960 GList *attach_files, GList *listAddress )
962 Compose *compose;
963 GtkTextView *textview;
964 GtkTextBuffer *textbuf;
965 GtkTextIter iter;
966 const gchar *subject_format = NULL;
967 const gchar *body_format = NULL;
968 gchar *mailto_from = NULL;
969 PrefsAccount *mailto_account = NULL;
970 MsgInfo* dummyinfo = NULL;
971 gint cursor_pos = -1;
972 MailField mfield = NO_FIELD_PRESENT;
973 gchar* buf;
974 GtkTextMark *mark;
976 /* check if mailto defines a from */
977 if (mailto && *mailto != '\0') {
978 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
979 /* mailto defines a from, check if we can get account prefs from it,
980 if not, the account prefs will be guessed using other ways, but we'll keep
981 the from anyway */
982 if (mailto_from) {
983 mailto_account = account_find_from_address(mailto_from, TRUE);
984 if (mailto_account == NULL) {
985 gchar *tmp_from;
986 Xstrdup_a(tmp_from, mailto_from, return NULL);
987 extract_address(tmp_from);
988 mailto_account = account_find_from_address(tmp_from, TRUE);
991 if (mailto_account)
992 account = mailto_account;
995 /* if no account prefs set from mailto, set if from folder prefs (if any) */
996 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
997 account = account_find_from_id(item->prefs->default_account);
999 /* if no account prefs set, fallback to the current one */
1000 if (!account) account = cur_account;
1001 cm_return_val_if_fail(account != NULL, NULL);
1003 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1004 compose_apply_folder_privacy_settings(compose, item);
1006 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1007 (account->default_encrypt || account->default_sign))
1008 COMPOSE_PRIVACY_WARNING();
1010 /* override from name if mailto asked for it */
1011 if (mailto_from) {
1012 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1013 g_free(mailto_from);
1014 } else
1015 /* override from name according to folder properties */
1016 if (item && item->prefs &&
1017 item->prefs->compose_with_format &&
1018 item->prefs->compose_override_from_format &&
1019 *item->prefs->compose_override_from_format != '\0') {
1021 gchar *tmp = NULL;
1022 gchar *buf = NULL;
1024 dummyinfo = compose_msginfo_new_from_compose(compose);
1026 /* decode \-escape sequences in the internal representation of the quote format */
1027 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1028 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1030 #ifdef USE_ENCHANT
1031 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1032 compose->gtkaspell);
1033 #else
1034 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1035 #endif
1036 quote_fmt_scan_string(tmp);
1037 quote_fmt_parse();
1039 buf = quote_fmt_get_buffer();
1040 if (buf == NULL)
1041 alertpanel_error(_("New message From format error."));
1042 else
1043 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1044 quote_fmt_reset_vartable();
1045 quote_fmtlex_destroy();
1047 g_free(tmp);
1050 compose->replyinfo = NULL;
1051 compose->fwdinfo = NULL;
1053 textview = GTK_TEXT_VIEW(compose->text);
1054 textbuf = gtk_text_view_get_buffer(textview);
1055 compose_create_tags(textview, compose);
1057 undo_block(compose->undostruct);
1058 #ifdef USE_ENCHANT
1059 compose_set_dictionaries_from_folder_prefs(compose, item);
1060 #endif
1062 if (account->auto_sig)
1063 compose_insert_sig(compose, FALSE);
1064 gtk_text_buffer_get_start_iter(textbuf, &iter);
1065 gtk_text_buffer_place_cursor(textbuf, &iter);
1067 if (account->protocol != A_NNTP) {
1068 if (mailto && *mailto != '\0') {
1069 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1071 } else {
1072 compose_set_folder_prefs(compose, item, TRUE);
1074 if (item && item->ret_rcpt) {
1075 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1077 } else {
1078 if (mailto && *mailto != '\0') {
1079 if (!strchr(mailto, '@'))
1080 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1081 else
1082 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1083 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1084 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1085 mfield = TO_FIELD_PRESENT;
1088 * CLAWS: just don't allow return receipt request, even if the user
1089 * may want to send an email. simple but foolproof.
1091 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1093 compose_add_field_list( compose, listAddress );
1095 if (item && item->prefs && item->prefs->compose_with_format) {
1096 subject_format = item->prefs->compose_subject_format;
1097 body_format = item->prefs->compose_body_format;
1098 } else if (account->compose_with_format) {
1099 subject_format = account->compose_subject_format;
1100 body_format = account->compose_body_format;
1101 } else if (prefs_common.compose_with_format) {
1102 subject_format = prefs_common.compose_subject_format;
1103 body_format = prefs_common.compose_body_format;
1106 if (subject_format || body_format) {
1108 if ( subject_format
1109 && *subject_format != '\0' )
1111 gchar *subject = NULL;
1112 gchar *tmp = NULL;
1113 gchar *buf = NULL;
1115 if (!dummyinfo)
1116 dummyinfo = compose_msginfo_new_from_compose(compose);
1118 /* decode \-escape sequences in the internal representation of the quote format */
1119 tmp = g_malloc(strlen(subject_format)+1);
1120 pref_get_unescaped_pref(tmp, subject_format);
1122 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1123 #ifdef USE_ENCHANT
1124 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1125 compose->gtkaspell);
1126 #else
1127 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1128 #endif
1129 quote_fmt_scan_string(tmp);
1130 quote_fmt_parse();
1132 buf = quote_fmt_get_buffer();
1133 if (buf == NULL)
1134 alertpanel_error(_("New message subject format error."));
1135 else
1136 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1137 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1138 quote_fmt_reset_vartable();
1139 quote_fmtlex_destroy();
1141 g_free(subject);
1142 g_free(tmp);
1143 mfield = SUBJECT_FIELD_PRESENT;
1146 if ( body_format
1147 && *body_format != '\0' )
1149 GtkTextView *text;
1150 GtkTextBuffer *buffer;
1151 GtkTextIter start, end;
1152 gchar *tmp = NULL;
1154 if (!dummyinfo)
1155 dummyinfo = compose_msginfo_new_from_compose(compose);
1157 text = GTK_TEXT_VIEW(compose->text);
1158 buffer = gtk_text_view_get_buffer(text);
1159 gtk_text_buffer_get_start_iter(buffer, &start);
1160 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1161 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1163 compose_quote_fmt(compose, dummyinfo,
1164 body_format,
1165 NULL, tmp, FALSE, TRUE,
1166 _("The body of the \"New message\" template has an error at line %d."));
1167 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1168 quote_fmt_reset_vartable();
1170 g_free(tmp);
1171 #ifdef USE_ENCHANT
1172 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1173 gtkaspell_highlight_all(compose->gtkaspell);
1174 #endif
1175 mfield = BODY_FIELD_PRESENT;
1179 procmsg_msginfo_free( &dummyinfo );
1181 if (attach_files) {
1182 GList *curr;
1183 AttachInfo *ainfo;
1185 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1186 ainfo = (AttachInfo *) curr->data;
1187 if (ainfo->insert)
1188 compose_insert_file(compose, ainfo->file);
1189 else
1190 compose_attach_append(compose, ainfo->file, ainfo->file,
1191 ainfo->content_type, ainfo->charset);
1195 compose_show_first_last_header(compose, TRUE);
1197 /* Set save folder */
1198 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1199 gchar *folderidentifier;
1201 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1202 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1203 folderidentifier = folder_item_get_identifier(item);
1204 compose_set_save_to(compose, folderidentifier);
1205 g_free(folderidentifier);
1208 /* Place cursor according to provided input (mfield) */
1209 switch (mfield) {
1210 case NO_FIELD_PRESENT:
1211 if (compose->header_last)
1212 gtk_widget_grab_focus(compose->header_last->entry);
1213 break;
1214 case TO_FIELD_PRESENT:
1215 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1216 if (buf) {
1217 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1218 g_free(buf);
1220 gtk_widget_grab_focus(compose->subject_entry);
1221 break;
1222 case SUBJECT_FIELD_PRESENT:
1223 textview = GTK_TEXT_VIEW(compose->text);
1224 if (!textview)
1225 break;
1226 textbuf = gtk_text_view_get_buffer(textview);
1227 if (!textbuf)
1228 break;
1229 mark = gtk_text_buffer_get_insert(textbuf);
1230 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1231 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1233 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1234 * only defers where it comes to the variable body
1235 * is not null. If no body is present compose->text
1236 * will be null in which case you cannot place the
1237 * cursor inside the component so. An empty component
1238 * is therefore created before placing the cursor
1240 case BODY_FIELD_PRESENT:
1241 cursor_pos = quote_fmt_get_cursor_pos();
1242 if (cursor_pos == -1)
1243 gtk_widget_grab_focus(compose->header_last->entry);
1244 else
1245 gtk_widget_grab_focus(compose->text);
1246 break;
1249 undo_unblock(compose->undostruct);
1251 if (prefs_common.auto_exteditor)
1252 compose_exec_ext_editor(compose);
1254 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1256 SCROLL_TO_CURSOR(compose);
1258 compose->modified = FALSE;
1259 compose_set_title(compose);
1261 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1263 return compose;
1266 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1267 gboolean override_pref, const gchar *system)
1269 const gchar *privacy = NULL;
1271 cm_return_if_fail(compose != NULL);
1272 cm_return_if_fail(account != NULL);
1274 if (privacy_system_can_encrypt(compose->privacy_system) == FALSE ||
1275 (override_pref == FALSE && account->default_encrypt_reply == FALSE))
1276 return;
1278 if (account->default_privacy_system && strlen(account->default_privacy_system))
1279 privacy = account->default_privacy_system;
1280 else if (system)
1281 privacy = system;
1282 else {
1283 GSList *privacy_avail = privacy_get_system_ids();
1284 if (privacy_avail && g_slist_length(privacy_avail)) {
1285 privacy = (gchar *)(privacy_avail->data);
1287 g_slist_free_full(privacy_avail, g_free);
1289 if (privacy != NULL) {
1290 if (system) {
1291 g_free(compose->privacy_system);
1292 compose->privacy_system = NULL;
1293 g_free(compose->encdata);
1294 compose->encdata = NULL;
1296 if (compose->privacy_system == NULL)
1297 compose->privacy_system = g_strdup(privacy);
1298 else if (*(compose->privacy_system) == '\0') {
1299 g_free(compose->privacy_system);
1300 g_free(compose->encdata);
1301 compose->encdata = NULL;
1302 compose->privacy_system = g_strdup(privacy);
1304 compose_update_privacy_system_menu_item(compose, FALSE);
1305 compose_use_encryption(compose, TRUE);
1309 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1311 const gchar *privacy = NULL;
1312 if (privacy_system_can_sign(compose->privacy_system) == 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);
1326 if (privacy != NULL) {
1327 if (system) {
1328 g_free(compose->privacy_system);
1329 compose->privacy_system = NULL;
1330 g_free(compose->encdata);
1331 compose->encdata = NULL;
1333 if (compose->privacy_system == NULL)
1334 compose->privacy_system = g_strdup(privacy);
1335 compose_update_privacy_system_menu_item(compose, FALSE);
1336 compose_use_signing(compose, TRUE);
1340 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1342 MsgInfo *msginfo;
1343 guint list_len;
1344 Compose *compose = NULL;
1346 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1348 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1349 cm_return_val_if_fail(msginfo != NULL, NULL);
1351 list_len = g_slist_length(msginfo_list);
1353 switch (mode) {
1354 case COMPOSE_REPLY:
1355 case COMPOSE_REPLY_TO_ADDRESS:
1356 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1357 FALSE, prefs_common.default_reply_list, FALSE, body);
1358 break;
1359 case COMPOSE_REPLY_WITH_QUOTE:
1360 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1361 FALSE, prefs_common.default_reply_list, FALSE, body);
1362 break;
1363 case COMPOSE_REPLY_WITHOUT_QUOTE:
1364 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1365 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1366 break;
1367 case COMPOSE_REPLY_TO_SENDER:
1368 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1369 FALSE, FALSE, TRUE, body);
1370 break;
1371 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1372 compose = compose_followup_and_reply_to(msginfo,
1373 COMPOSE_QUOTE_CHECK,
1374 FALSE, FALSE, body);
1375 break;
1376 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1377 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1378 FALSE, FALSE, TRUE, body);
1379 break;
1380 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1381 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1382 FALSE, FALSE, TRUE, NULL);
1383 break;
1384 case COMPOSE_REPLY_TO_ALL:
1385 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1386 TRUE, FALSE, FALSE, body);
1387 break;
1388 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1389 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1390 TRUE, FALSE, FALSE, body);
1391 break;
1392 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1393 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1394 TRUE, FALSE, FALSE, NULL);
1395 break;
1396 case COMPOSE_REPLY_TO_LIST:
1397 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1398 FALSE, TRUE, FALSE, body);
1399 break;
1400 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1401 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1402 FALSE, TRUE, FALSE, body);
1403 break;
1404 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1405 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1406 FALSE, TRUE, FALSE, NULL);
1407 break;
1408 case COMPOSE_FORWARD:
1409 if (prefs_common.forward_as_attachment) {
1410 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1411 return compose;
1412 } else {
1413 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1414 return compose;
1416 break;
1417 case COMPOSE_FORWARD_INLINE:
1418 /* check if we reply to more than one Message */
1419 if (list_len == 1) {
1420 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1421 break;
1423 /* more messages FALL THROUGH */
1424 case COMPOSE_FORWARD_AS_ATTACH:
1425 compose = compose_forward_multiple(NULL, msginfo_list);
1426 break;
1427 case COMPOSE_REDIRECT:
1428 compose = compose_redirect(NULL, msginfo, FALSE);
1429 break;
1430 default:
1431 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1434 if (compose == NULL) {
1435 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1436 return NULL;
1439 compose->rmode = mode;
1440 switch (compose->rmode) {
1441 case COMPOSE_REPLY:
1442 case COMPOSE_REPLY_WITH_QUOTE:
1443 case COMPOSE_REPLY_WITHOUT_QUOTE:
1444 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1445 debug_print("reply mode Normal\n");
1446 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1447 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1448 break;
1449 case COMPOSE_REPLY_TO_SENDER:
1450 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1451 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1452 debug_print("reply mode Sender\n");
1453 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1454 break;
1455 case COMPOSE_REPLY_TO_ALL:
1456 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1457 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1458 debug_print("reply mode All\n");
1459 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1460 break;
1461 case COMPOSE_REPLY_TO_LIST:
1462 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1463 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1464 debug_print("reply mode List\n");
1465 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1466 break;
1467 case COMPOSE_REPLY_TO_ADDRESS:
1468 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1469 break;
1470 default:
1471 break;
1473 return compose;
1476 static Compose *compose_reply(MsgInfo *msginfo,
1477 ComposeQuoteMode quote_mode,
1478 gboolean to_all,
1479 gboolean to_ml,
1480 gboolean to_sender,
1481 const gchar *body)
1483 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1484 to_sender, FALSE, body);
1487 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1488 ComposeQuoteMode quote_mode,
1489 gboolean to_all,
1490 gboolean to_sender,
1491 const gchar *body)
1493 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1494 to_sender, TRUE, body);
1497 static void compose_extract_original_charset(Compose *compose)
1499 MsgInfo *info = NULL;
1500 if (compose->replyinfo) {
1501 info = compose->replyinfo;
1502 } else if (compose->fwdinfo) {
1503 info = compose->fwdinfo;
1504 } else if (compose->targetinfo) {
1505 info = compose->targetinfo;
1507 if (info) {
1508 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1509 MimeInfo *partinfo = mimeinfo;
1510 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1511 partinfo = procmime_mimeinfo_next(partinfo);
1512 if (partinfo) {
1513 compose->orig_charset =
1514 g_strdup(procmime_mimeinfo_get_parameter(
1515 partinfo, "charset"));
1517 procmime_mimeinfo_free_all(&mimeinfo);
1521 #define SIGNAL_BLOCK(buffer) { \
1522 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1523 G_CALLBACK(compose_changed_cb), \
1524 compose); \
1525 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1526 G_CALLBACK(text_inserted), \
1527 compose); \
1530 #define SIGNAL_UNBLOCK(buffer) { \
1531 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1532 G_CALLBACK(compose_changed_cb), \
1533 compose); \
1534 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1535 G_CALLBACK(text_inserted), \
1536 compose); \
1539 static Compose *compose_generic_reply(MsgInfo *msginfo,
1540 ComposeQuoteMode quote_mode,
1541 gboolean to_all, gboolean to_ml,
1542 gboolean to_sender,
1543 gboolean followup_and_reply_to,
1544 const gchar *body)
1546 Compose *compose;
1547 PrefsAccount *account = NULL;
1548 GtkTextView *textview;
1549 GtkTextBuffer *textbuf;
1550 gboolean quote = FALSE;
1551 const gchar *qmark = NULL;
1552 const gchar *body_fmt = NULL;
1553 gchar *s_system = NULL;
1554 START_TIMING("");
1555 cm_return_val_if_fail(msginfo != NULL, NULL);
1556 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1558 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1560 cm_return_val_if_fail(account != NULL, NULL);
1562 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1563 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1565 compose->updating = TRUE;
1567 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1568 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1570 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1571 if (!compose->replyinfo)
1572 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1574 compose_extract_original_charset(compose);
1576 if (msginfo->folder && msginfo->folder->ret_rcpt)
1577 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1579 /* Set save folder */
1580 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1581 gchar *folderidentifier;
1583 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1584 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1585 folderidentifier = folder_item_get_identifier(msginfo->folder);
1586 compose_set_save_to(compose, folderidentifier);
1587 g_free(folderidentifier);
1590 if (compose_parse_header(compose, msginfo) < 0) {
1591 compose->updating = FALSE;
1592 compose_destroy(compose);
1593 return NULL;
1596 /* override from name according to folder properties */
1597 if (msginfo->folder && msginfo->folder->prefs &&
1598 msginfo->folder->prefs->reply_with_format &&
1599 msginfo->folder->prefs->reply_override_from_format &&
1600 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1602 gchar *tmp = NULL;
1603 gchar *buf = NULL;
1605 /* decode \-escape sequences in the internal representation of the quote format */
1606 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1607 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1609 #ifdef USE_ENCHANT
1610 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1611 compose->gtkaspell);
1612 #else
1613 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1614 #endif
1615 quote_fmt_scan_string(tmp);
1616 quote_fmt_parse();
1618 buf = quote_fmt_get_buffer();
1619 if (buf == NULL)
1620 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1621 else
1622 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1623 quote_fmt_reset_vartable();
1624 quote_fmtlex_destroy();
1626 g_free(tmp);
1629 textview = (GTK_TEXT_VIEW(compose->text));
1630 textbuf = gtk_text_view_get_buffer(textview);
1631 compose_create_tags(textview, compose);
1633 undo_block(compose->undostruct);
1634 #ifdef USE_ENCHANT
1635 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1636 gtkaspell_block_check(compose->gtkaspell);
1637 #endif
1639 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1640 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1641 /* use the reply format of folder (if enabled), or the account's one
1642 (if enabled) or fallback to the global reply format, which is always
1643 enabled (even if empty), and use the relevant quotemark */
1644 quote = TRUE;
1645 if (msginfo->folder && msginfo->folder->prefs &&
1646 msginfo->folder->prefs->reply_with_format) {
1647 qmark = msginfo->folder->prefs->reply_quotemark;
1648 body_fmt = msginfo->folder->prefs->reply_body_format;
1650 } else if (account->reply_with_format) {
1651 qmark = account->reply_quotemark;
1652 body_fmt = account->reply_body_format;
1654 } else {
1655 qmark = prefs_common.quotemark;
1656 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1657 body_fmt = gettext(prefs_common.quotefmt);
1658 else
1659 body_fmt = "";
1663 if (quote) {
1664 /* empty quotemark is not allowed */
1665 if (qmark == NULL || *qmark == '\0')
1666 qmark = "> ";
1667 compose_quote_fmt(compose, compose->replyinfo,
1668 body_fmt, qmark, body, FALSE, TRUE,
1669 _("The body of the \"Reply\" template has an error at line %d."));
1670 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1671 quote_fmt_reset_vartable();
1674 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1675 compose_force_encryption(compose, account, FALSE, s_system);
1678 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1679 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1680 compose_force_signing(compose, account, s_system);
1682 g_free(s_system);
1684 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1685 ((account->default_encrypt || account->default_sign) ||
1686 (account->default_encrypt_reply && MSG_IS_ENCRYPTED(compose->replyinfo->flags)) ||
1687 (account->default_sign_reply && MSG_IS_SIGNED(compose->replyinfo->flags))))
1688 COMPOSE_PRIVACY_WARNING();
1690 SIGNAL_BLOCK(textbuf);
1692 if (account->auto_sig)
1693 compose_insert_sig(compose, FALSE);
1695 compose_wrap_all(compose);
1697 #ifdef USE_ENCHANT
1698 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1699 gtkaspell_highlight_all(compose->gtkaspell);
1700 gtkaspell_unblock_check(compose->gtkaspell);
1701 #endif
1702 SIGNAL_UNBLOCK(textbuf);
1704 gtk_widget_grab_focus(compose->text);
1706 undo_unblock(compose->undostruct);
1708 if (prefs_common.auto_exteditor)
1709 compose_exec_ext_editor(compose);
1711 compose->modified = FALSE;
1712 compose_set_title(compose);
1714 compose->updating = FALSE;
1715 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1716 SCROLL_TO_CURSOR(compose);
1718 if (compose->deferred_destroy) {
1719 compose_destroy(compose);
1720 return NULL;
1722 END_TIMING();
1724 return compose;
1727 #define INSERT_FW_HEADER(var, hdr) \
1728 if (msginfo->var && *msginfo->var) { \
1729 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1730 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1731 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1734 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1735 gboolean as_attach, const gchar *body,
1736 gboolean no_extedit,
1737 gboolean batch)
1739 Compose *compose;
1740 GtkTextView *textview;
1741 GtkTextBuffer *textbuf;
1742 gint cursor_pos = -1;
1743 ComposeMode mode;
1745 cm_return_val_if_fail(msginfo != NULL, NULL);
1746 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1748 if (!account && !(account = compose_find_account(msginfo)))
1749 account = cur_account;
1751 if (!prefs_common.forward_as_attachment)
1752 mode = COMPOSE_FORWARD_INLINE;
1753 else
1754 mode = COMPOSE_FORWARD;
1755 compose = compose_create(account, msginfo->folder, mode, batch);
1756 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1758 compose->updating = TRUE;
1759 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1760 if (!compose->fwdinfo)
1761 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1763 compose_extract_original_charset(compose);
1765 if (msginfo->subject && *msginfo->subject) {
1766 gchar *buf, *buf2, *p;
1768 buf = p = g_strdup(msginfo->subject);
1769 p += subject_get_prefix_length(p);
1770 memmove(buf, p, strlen(p) + 1);
1772 buf2 = g_strdup_printf("Fw: %s", buf);
1773 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1775 g_free(buf);
1776 g_free(buf2);
1779 /* override from name according to folder properties */
1780 if (msginfo->folder && msginfo->folder->prefs &&
1781 msginfo->folder->prefs->forward_with_format &&
1782 msginfo->folder->prefs->forward_override_from_format &&
1783 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1785 gchar *tmp = NULL;
1786 gchar *buf = NULL;
1787 MsgInfo *full_msginfo = NULL;
1789 if (!as_attach)
1790 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1791 if (!full_msginfo)
1792 full_msginfo = procmsg_msginfo_copy(msginfo);
1794 /* decode \-escape sequences in the internal representation of the quote format */
1795 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1796 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1798 #ifdef USE_ENCHANT
1799 gtkaspell_block_check(compose->gtkaspell);
1800 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1801 compose->gtkaspell);
1802 #else
1803 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1804 #endif
1805 quote_fmt_scan_string(tmp);
1806 quote_fmt_parse();
1808 buf = quote_fmt_get_buffer();
1809 if (buf == NULL)
1810 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1811 else
1812 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1813 quote_fmt_reset_vartable();
1814 quote_fmtlex_destroy();
1816 g_free(tmp);
1817 procmsg_msginfo_free(&full_msginfo);
1820 textview = GTK_TEXT_VIEW(compose->text);
1821 textbuf = gtk_text_view_get_buffer(textview);
1822 compose_create_tags(textview, compose);
1824 undo_block(compose->undostruct);
1825 if (as_attach) {
1826 gchar *msgfile;
1828 msgfile = procmsg_get_message_file(msginfo);
1829 if (!is_file_exist(msgfile))
1830 g_warning("%s: file does not exist", msgfile);
1831 else
1832 compose_attach_append(compose, msgfile, msgfile,
1833 "message/rfc822", NULL);
1835 g_free(msgfile);
1836 } else {
1837 const gchar *qmark = NULL;
1838 const gchar *body_fmt = NULL;
1839 MsgInfo *full_msginfo;
1841 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1842 if (!full_msginfo)
1843 full_msginfo = procmsg_msginfo_copy(msginfo);
1845 /* use the forward format of folder (if enabled), or the account's one
1846 (if enabled) or fallback to the global forward format, which is always
1847 enabled (even if empty), and use the relevant quotemark */
1848 if (msginfo->folder && msginfo->folder->prefs &&
1849 msginfo->folder->prefs->forward_with_format) {
1850 qmark = msginfo->folder->prefs->forward_quotemark;
1851 body_fmt = msginfo->folder->prefs->forward_body_format;
1853 } else if (account->forward_with_format) {
1854 qmark = account->forward_quotemark;
1855 body_fmt = account->forward_body_format;
1857 } else {
1858 qmark = prefs_common.fw_quotemark;
1859 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1860 body_fmt = gettext(prefs_common.fw_quotefmt);
1861 else
1862 body_fmt = "";
1865 /* empty quotemark is not allowed */
1866 if (qmark == NULL || *qmark == '\0')
1867 qmark = "> ";
1869 compose_quote_fmt(compose, full_msginfo,
1870 body_fmt, qmark, body, FALSE, TRUE,
1871 _("The body of the \"Forward\" template has an error at line %d."));
1872 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1873 quote_fmt_reset_vartable();
1874 compose_attach_parts(compose, msginfo);
1876 procmsg_msginfo_free(&full_msginfo);
1879 SIGNAL_BLOCK(textbuf);
1881 if (account->auto_sig)
1882 compose_insert_sig(compose, FALSE);
1884 compose_wrap_all(compose);
1886 #ifdef USE_ENCHANT
1887 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1888 gtkaspell_highlight_all(compose->gtkaspell);
1889 gtkaspell_unblock_check(compose->gtkaspell);
1890 #endif
1891 SIGNAL_UNBLOCK(textbuf);
1893 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1894 (account->default_encrypt || account->default_sign))
1895 COMPOSE_PRIVACY_WARNING();
1897 cursor_pos = quote_fmt_get_cursor_pos();
1898 if (cursor_pos == -1)
1899 gtk_widget_grab_focus(compose->header_last->entry);
1900 else
1901 gtk_widget_grab_focus(compose->text);
1903 if (!no_extedit && prefs_common.auto_exteditor)
1904 compose_exec_ext_editor(compose);
1906 /*save folder*/
1907 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1908 gchar *folderidentifier;
1910 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1911 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1912 folderidentifier = folder_item_get_identifier(msginfo->folder);
1913 compose_set_save_to(compose, folderidentifier);
1914 g_free(folderidentifier);
1917 undo_unblock(compose->undostruct);
1919 compose->modified = FALSE;
1920 compose_set_title(compose);
1922 compose->updating = FALSE;
1923 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1924 SCROLL_TO_CURSOR(compose);
1926 if (compose->deferred_destroy) {
1927 compose_destroy(compose);
1928 return NULL;
1931 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1933 return compose;
1936 #undef INSERT_FW_HEADER
1938 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1940 Compose *compose;
1941 GtkTextView *textview;
1942 GtkTextBuffer *textbuf;
1943 GtkTextIter iter;
1944 GSList *msginfo;
1945 gchar *msgfile;
1946 gboolean single_mail = TRUE;
1948 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1950 if (g_slist_length(msginfo_list) > 1)
1951 single_mail = FALSE;
1953 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1954 if (((MsgInfo *)msginfo->data)->folder == NULL)
1955 return NULL;
1957 /* guess account from first selected message */
1958 if (!account &&
1959 !(account = compose_find_account(msginfo_list->data)))
1960 account = cur_account;
1962 cm_return_val_if_fail(account != NULL, NULL);
1964 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1965 if (msginfo->data) {
1966 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1967 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1971 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1972 g_warning("no msginfo_list");
1973 return NULL;
1976 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1977 compose_apply_folder_privacy_settings(compose, ((MsgInfo *)msginfo_list->data)->folder);
1978 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1979 (account->default_encrypt || account->default_sign))
1980 COMPOSE_PRIVACY_WARNING();
1982 compose->updating = TRUE;
1984 /* override from name according to folder properties */
1985 if (msginfo_list->data) {
1986 MsgInfo *msginfo = msginfo_list->data;
1988 if (msginfo->folder && msginfo->folder->prefs &&
1989 msginfo->folder->prefs->forward_with_format &&
1990 msginfo->folder->prefs->forward_override_from_format &&
1991 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1993 gchar *tmp = NULL;
1994 gchar *buf = NULL;
1996 /* decode \-escape sequences in the internal representation of the quote format */
1997 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1998 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2000 #ifdef USE_ENCHANT
2001 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2002 compose->gtkaspell);
2003 #else
2004 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2005 #endif
2006 quote_fmt_scan_string(tmp);
2007 quote_fmt_parse();
2009 buf = quote_fmt_get_buffer();
2010 if (buf == NULL)
2011 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2012 else
2013 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2014 quote_fmt_reset_vartable();
2015 quote_fmtlex_destroy();
2017 g_free(tmp);
2021 textview = GTK_TEXT_VIEW(compose->text);
2022 textbuf = gtk_text_view_get_buffer(textview);
2023 compose_create_tags(textview, compose);
2025 undo_block(compose->undostruct);
2026 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2027 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2029 if (!is_file_exist(msgfile))
2030 g_warning("%s: file does not exist", msgfile);
2031 else
2032 compose_attach_append(compose, msgfile, msgfile,
2033 "message/rfc822", NULL);
2034 g_free(msgfile);
2037 if (single_mail) {
2038 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2039 if (info->subject && *info->subject) {
2040 gchar *buf, *buf2, *p;
2042 buf = p = g_strdup(info->subject);
2043 p += subject_get_prefix_length(p);
2044 memmove(buf, p, strlen(p) + 1);
2046 buf2 = g_strdup_printf("Fw: %s", buf);
2047 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2049 g_free(buf);
2050 g_free(buf2);
2052 } else {
2053 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2054 _("Fw: multiple emails"));
2057 SIGNAL_BLOCK(textbuf);
2059 if (account->auto_sig)
2060 compose_insert_sig(compose, FALSE);
2062 compose_wrap_all(compose);
2064 SIGNAL_UNBLOCK(textbuf);
2066 gtk_text_buffer_get_start_iter(textbuf, &iter);
2067 gtk_text_buffer_place_cursor(textbuf, &iter);
2069 if (prefs_common.auto_exteditor)
2070 compose_exec_ext_editor(compose);
2072 gtk_widget_grab_focus(compose->header_last->entry);
2073 undo_unblock(compose->undostruct);
2074 compose->modified = FALSE;
2075 compose_set_title(compose);
2077 compose->updating = FALSE;
2078 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2079 SCROLL_TO_CURSOR(compose);
2081 if (compose->deferred_destroy) {
2082 compose_destroy(compose);
2083 return NULL;
2086 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2088 return compose;
2091 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2093 GtkTextIter start = *iter;
2094 GtkTextIter end_iter;
2095 int start_pos = gtk_text_iter_get_offset(&start);
2096 gchar *str = NULL;
2097 if (!compose->account->sig_sep)
2098 return FALSE;
2100 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2101 start_pos+strlen(compose->account->sig_sep));
2103 /* check sig separator */
2104 str = gtk_text_iter_get_text(&start, &end_iter);
2105 if (!strcmp(str, compose->account->sig_sep)) {
2106 gchar *tmp = NULL;
2107 /* check end of line (\n) */
2108 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2109 start_pos+strlen(compose->account->sig_sep));
2110 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2111 start_pos+strlen(compose->account->sig_sep)+1);
2112 tmp = gtk_text_iter_get_text(&start, &end_iter);
2113 if (!strcmp(tmp,"\n")) {
2114 g_free(str);
2115 g_free(tmp);
2116 return TRUE;
2118 g_free(tmp);
2120 g_free(str);
2122 return FALSE;
2125 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2127 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2128 Compose *compose = (Compose *)data;
2129 FolderItem *old_item = NULL;
2130 FolderItem *new_item = NULL;
2131 gchar *old_id, *new_id;
2133 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2134 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2135 return FALSE;
2137 old_item = hookdata->item;
2138 new_item = hookdata->item2;
2140 old_id = folder_item_get_identifier(old_item);
2141 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2143 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2144 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2145 compose->targetinfo->folder = new_item;
2148 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2149 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2150 compose->replyinfo->folder = new_item;
2153 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2154 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2155 compose->fwdinfo->folder = new_item;
2158 g_free(old_id);
2159 g_free(new_id);
2160 return FALSE;
2163 static void compose_colorize_signature(Compose *compose)
2165 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2166 GtkTextIter iter;
2167 GtkTextIter end_iter;
2168 gtk_text_buffer_get_start_iter(buffer, &iter);
2169 while (gtk_text_iter_forward_line(&iter))
2170 if (compose_is_sig_separator(compose, buffer, &iter)) {
2171 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2172 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2176 #define BLOCK_WRAP() { \
2177 prev_autowrap = compose->autowrap; \
2178 buffer = gtk_text_view_get_buffer( \
2179 GTK_TEXT_VIEW(compose->text)); \
2180 compose->autowrap = FALSE; \
2182 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2183 G_CALLBACK(compose_changed_cb), \
2184 compose); \
2185 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2186 G_CALLBACK(text_inserted), \
2187 compose); \
2189 #define UNBLOCK_WRAP() { \
2190 compose->autowrap = prev_autowrap; \
2191 if (compose->autowrap) { \
2192 gint old = compose->draft_timeout_tag; \
2193 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2194 compose_wrap_all(compose); \
2195 compose->draft_timeout_tag = old; \
2198 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2199 G_CALLBACK(compose_changed_cb), \
2200 compose); \
2201 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2202 G_CALLBACK(text_inserted), \
2203 compose); \
2206 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2208 Compose *compose = NULL;
2209 PrefsAccount *account = NULL;
2210 GtkTextView *textview;
2211 GtkTextBuffer *textbuf;
2212 GtkTextMark *mark;
2213 GtkTextIter iter;
2214 FILE *fp;
2215 gboolean use_signing = FALSE;
2216 gboolean use_encryption = FALSE;
2217 gchar *privacy_system = NULL;
2218 int priority = PRIORITY_NORMAL;
2219 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2220 gboolean autowrap = prefs_common.autowrap;
2221 gboolean autoindent = prefs_common.auto_indent;
2222 HeaderEntry *manual_headers = NULL;
2224 cm_return_val_if_fail(msginfo != NULL, NULL);
2225 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2227 if (compose_put_existing_to_front(msginfo)) {
2228 return NULL;
2231 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2232 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2233 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2234 gchar *queueheader_buf = NULL;
2235 gint id, param;
2237 /* Select Account from queue headers */
2238 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2239 "X-Claws-Account-Id:")) {
2240 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2241 account = account_find_from_id(id);
2242 g_free(queueheader_buf);
2244 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2245 "X-Sylpheed-Account-Id:")) {
2246 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2247 account = account_find_from_id(id);
2248 g_free(queueheader_buf);
2250 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2251 "NAID:")) {
2252 id = atoi(&queueheader_buf[strlen("NAID:")]);
2253 account = account_find_from_id(id);
2254 g_free(queueheader_buf);
2256 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2257 "MAID:")) {
2258 id = atoi(&queueheader_buf[strlen("MAID:")]);
2259 account = account_find_from_id(id);
2260 g_free(queueheader_buf);
2262 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2263 "S:")) {
2264 account = account_find_from_address(queueheader_buf, FALSE);
2265 g_free(queueheader_buf);
2267 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2268 "X-Claws-Sign:")) {
2269 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2270 use_signing = param;
2271 g_free(queueheader_buf);
2273 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2274 "X-Sylpheed-Sign:")) {
2275 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2276 use_signing = param;
2277 g_free(queueheader_buf);
2279 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2280 "X-Claws-Encrypt:")) {
2281 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2282 use_encryption = param;
2283 g_free(queueheader_buf);
2285 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2286 "X-Sylpheed-Encrypt:")) {
2287 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2288 use_encryption = param;
2289 g_free(queueheader_buf);
2291 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2292 "X-Claws-Auto-Wrapping:")) {
2293 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2294 autowrap = param;
2295 g_free(queueheader_buf);
2297 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2298 "X-Claws-Auto-Indent:")) {
2299 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2300 autoindent = param;
2301 g_free(queueheader_buf);
2303 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2304 "X-Claws-Privacy-System:")) {
2305 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2306 g_free(queueheader_buf);
2308 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2309 "X-Sylpheed-Privacy-System:")) {
2310 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2311 g_free(queueheader_buf);
2313 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2314 "X-Priority: ")) {
2315 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2316 priority = param;
2317 g_free(queueheader_buf);
2319 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2320 "RMID:")) {
2321 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2322 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2323 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2324 if (orig_item != NULL) {
2325 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2328 if (tokens)
2329 g_strfreev(tokens);
2330 g_free(queueheader_buf);
2332 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2333 "FMID:")) {
2334 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\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 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2341 if (tokens)
2342 g_strfreev(tokens);
2343 g_free(queueheader_buf);
2345 /* Get manual headers */
2346 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2347 "X-Claws-Manual-Headers:")) {
2348 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2349 if (listmh && *listmh != '\0') {
2350 debug_print("Got manual headers: %s\n", listmh);
2351 manual_headers = procheader_entries_from_str(listmh);
2353 if (listmh)
2354 g_free(listmh);
2355 g_free(queueheader_buf);
2357 } else {
2358 account = msginfo->folder->folder->account;
2361 if (!account && prefs_common.reedit_account_autosel) {
2362 gchar *from = NULL;
2363 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2364 extract_address(from);
2365 account = account_find_from_address(from, FALSE);
2367 if (from)
2368 g_free(from);
2370 if (!account) {
2371 account = cur_account;
2373 if (!account) {
2374 g_warning("can't select account");
2375 if (manual_headers)
2376 procheader_entries_free(manual_headers);
2377 return NULL;
2380 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2382 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
2383 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
2384 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2385 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2386 compose->autowrap = autowrap;
2387 compose->replyinfo = replyinfo;
2388 compose->fwdinfo = fwdinfo;
2390 compose->updating = TRUE;
2391 compose->priority = priority;
2393 if (privacy_system != NULL) {
2394 compose->privacy_system = privacy_system;
2395 compose_use_signing(compose, use_signing);
2396 compose_use_encryption(compose, use_encryption);
2397 compose_update_privacy_system_menu_item(compose, FALSE);
2398 } else {
2399 compose_activate_privacy_system(compose, account, FALSE);
2401 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2402 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
2403 (account->default_encrypt || account->default_sign))
2404 COMPOSE_PRIVACY_WARNING();
2406 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2407 compose->targetinfo->tags = g_slist_copy(msginfo->tags);
2409 compose_extract_original_charset(compose);
2411 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2412 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2413 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2414 gchar *queueheader_buf = NULL;
2416 /* Set message save folder */
2417 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2418 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2419 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2420 compose_set_save_to(compose, &queueheader_buf[4]);
2421 g_free(queueheader_buf);
2423 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2424 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2425 if (active) {
2426 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2428 g_free(queueheader_buf);
2432 if (compose_parse_header(compose, msginfo) < 0) {
2433 compose->updating = FALSE;
2434 compose_destroy(compose);
2435 if (manual_headers)
2436 procheader_entries_free(manual_headers);
2437 return NULL;
2439 compose_reedit_set_entry(compose, msginfo);
2441 textview = GTK_TEXT_VIEW(compose->text);
2442 textbuf = gtk_text_view_get_buffer(textview);
2443 compose_create_tags(textview, compose);
2445 mark = gtk_text_buffer_get_insert(textbuf);
2446 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2448 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2449 G_CALLBACK(compose_changed_cb),
2450 compose);
2452 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2453 fp = procmime_get_first_encrypted_text_content(msginfo);
2454 if (fp) {
2455 compose_force_encryption(compose, account, TRUE, NULL);
2457 } else {
2458 fp = procmime_get_first_text_content(msginfo);
2460 if (fp == NULL) {
2461 g_warning("can't get text part");
2464 if (fp != NULL) {
2465 gchar buf[BUFFSIZE];
2466 gboolean prev_autowrap;
2467 GtkTextBuffer *buffer;
2468 BLOCK_WRAP();
2469 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2470 strcrchomp(buf);
2471 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2473 UNBLOCK_WRAP();
2474 claws_fclose(fp);
2477 compose_attach_parts(compose, msginfo);
2479 compose_colorize_signature(compose);
2481 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2482 G_CALLBACK(compose_changed_cb),
2483 compose);
2485 if (manual_headers != NULL) {
2486 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2487 procheader_entries_free(manual_headers);
2488 compose->updating = FALSE;
2489 compose_destroy(compose);
2490 return NULL;
2492 procheader_entries_free(manual_headers);
2495 gtk_widget_grab_focus(compose->text);
2497 if (prefs_common.auto_exteditor) {
2498 compose_exec_ext_editor(compose);
2500 compose->modified = FALSE;
2501 compose_set_title(compose);
2503 compose->updating = FALSE;
2504 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2505 SCROLL_TO_CURSOR(compose);
2507 if (compose->deferred_destroy) {
2508 compose_destroy(compose);
2509 return NULL;
2512 compose->sig_str = account_get_signature_str(compose->account);
2514 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2516 return compose;
2519 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2520 gboolean batch)
2522 Compose *compose;
2523 gchar *filename;
2524 FolderItem *item;
2526 cm_return_val_if_fail(msginfo != NULL, NULL);
2528 if (!account)
2529 account = account_get_reply_account(msginfo,
2530 prefs_common.reply_account_autosel);
2531 cm_return_val_if_fail(account != NULL, NULL);
2533 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2535 compose->updating = TRUE;
2537 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2538 compose->replyinfo = NULL;
2539 compose->fwdinfo = NULL;
2541 compose_show_first_last_header(compose, TRUE);
2543 gtk_widget_grab_focus(compose->header_last->entry);
2545 filename = procmsg_get_message_file(msginfo);
2547 if (filename == NULL) {
2548 compose->updating = FALSE;
2549 compose_destroy(compose);
2551 return NULL;
2554 compose->redirect_filename = filename;
2556 /* Set save folder */
2557 item = msginfo->folder;
2558 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2559 gchar *folderidentifier;
2561 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2562 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2563 folderidentifier = folder_item_get_identifier(item);
2564 compose_set_save_to(compose, folderidentifier);
2565 g_free(folderidentifier);
2568 compose_attach_parts(compose, msginfo);
2570 if (msginfo->subject)
2571 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2572 msginfo->subject);
2573 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2575 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2576 _("The body of the \"Redirect\" template has an error at line %d."));
2577 quote_fmt_reset_vartable();
2578 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2580 compose_colorize_signature(compose);
2582 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2583 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2584 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2586 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", FALSE);
2587 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2588 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2589 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2590 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2591 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2592 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2593 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2594 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2595 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2597 if (compose->toolbar->sendl_btn)
2598 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, FALSE);
2599 if (compose->toolbar->draft_btn)
2600 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2601 if (compose->toolbar->insert_btn)
2602 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2603 if (compose->toolbar->attach_btn)
2604 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2605 if (compose->toolbar->sig_btn)
2606 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2607 if (compose->toolbar->exteditor_btn)
2608 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2609 if (compose->toolbar->linewrap_current_btn)
2610 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2611 if (compose->toolbar->linewrap_all_btn)
2612 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2613 if (compose->toolbar->privacy_sign_btn)
2614 gtk_widget_set_sensitive(compose->toolbar->privacy_sign_btn, FALSE);
2615 if (compose->toolbar->privacy_encrypt_btn)
2616 gtk_widget_set_sensitive(compose->toolbar->privacy_encrypt_btn, FALSE);
2618 compose->modified = FALSE;
2619 compose_set_title(compose);
2620 compose->updating = FALSE;
2621 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2622 SCROLL_TO_CURSOR(compose);
2624 if (compose->deferred_destroy) {
2625 compose_destroy(compose);
2626 return NULL;
2629 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2631 return compose;
2634 const GList *compose_get_compose_list(void)
2636 return compose_list;
2639 void compose_entry_append(Compose *compose, const gchar *address,
2640 ComposeEntryType type, ComposePrefType pref_type)
2642 const gchar *header;
2643 gchar *cur, *begin;
2644 gboolean in_quote = FALSE;
2645 if (!address || *address == '\0') return;
2647 switch (type) {
2648 case COMPOSE_CC:
2649 header = N_("Cc:");
2650 break;
2651 case COMPOSE_BCC:
2652 header = N_("Bcc:");
2653 break;
2654 case COMPOSE_REPLYTO:
2655 header = N_("Reply-To:");
2656 break;
2657 case COMPOSE_NEWSGROUPS:
2658 header = N_("Newsgroups:");
2659 break;
2660 case COMPOSE_FOLLOWUPTO:
2661 header = N_( "Followup-To:");
2662 break;
2663 case COMPOSE_INREPLYTO:
2664 header = N_( "In-Reply-To:");
2665 break;
2666 case COMPOSE_TO:
2667 default:
2668 header = N_("To:");
2669 break;
2671 header = prefs_common_translated_header_name(header);
2673 cur = begin = (gchar *)address;
2675 /* we separate the line by commas, but not if we're inside a quoted
2676 * string */
2677 while (*cur != '\0') {
2678 if (*cur == '"')
2679 in_quote = !in_quote;
2680 if (*cur == ',' && !in_quote) {
2681 gchar *tmp = g_strdup(begin);
2682 gchar *o_tmp = tmp;
2683 tmp[cur-begin]='\0';
2684 cur++;
2685 begin = cur;
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);
2691 continue;
2693 cur++;
2695 if (begin < cur) {
2696 gchar *tmp = g_strdup(begin);
2697 gchar *o_tmp = tmp;
2698 tmp[cur-begin]='\0';
2699 while (*tmp == ' ' || *tmp == '\t')
2700 tmp++;
2701 compose_add_header_entry(compose, header, tmp, pref_type);
2702 compose_entry_indicate(compose, tmp);
2703 g_free(o_tmp);
2707 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2709 GSList *h_list;
2710 GtkEntry *entry;
2711 GdkColor color;
2713 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2714 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2715 if (gtk_entry_get_text(entry) &&
2716 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2717 /* Modify background color */
2718 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_bgcolor, color);
2719 gtk_widget_modify_base(
2720 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2721 GTK_STATE_NORMAL, &color);
2723 /* Modify foreground color */
2724 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_color, color);
2725 gtk_widget_modify_text(
2726 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2727 GTK_STATE_NORMAL, &color);
2732 void compose_toolbar_cb(gint action, gpointer data)
2734 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2735 Compose *compose = (Compose*)toolbar_item->parent;
2737 cm_return_if_fail(compose != NULL);
2739 switch(action) {
2740 case A_SEND:
2741 compose_send_cb(NULL, compose);
2742 break;
2743 case A_SEND_LATER:
2744 compose_send_later_cb(NULL, compose);
2745 break;
2746 case A_DRAFT:
2747 compose_draft(compose, COMPOSE_QUIT_EDITING);
2748 break;
2749 case A_INSERT:
2750 compose_insert_file_cb(NULL, compose);
2751 break;
2752 case A_ATTACH:
2753 compose_attach_cb(NULL, compose);
2754 break;
2755 case A_SIG:
2756 compose_insert_sig(compose, FALSE);
2757 break;
2758 case A_REP_SIG:
2759 compose_insert_sig(compose, TRUE);
2760 break;
2761 case A_EXTEDITOR:
2762 compose_ext_editor_cb(NULL, compose);
2763 break;
2764 case A_LINEWRAP_CURRENT:
2765 compose_beautify_paragraph(compose, NULL, TRUE);
2766 break;
2767 case A_LINEWRAP_ALL:
2768 compose_wrap_all_full(compose, TRUE);
2769 break;
2770 case A_ADDRBOOK:
2771 compose_address_cb(NULL, compose);
2772 break;
2773 #ifdef USE_ENCHANT
2774 case A_CHECK_SPELLING:
2775 compose_check_all(NULL, compose);
2776 break;
2777 #endif
2778 case A_PRIVACY_SIGN:
2779 break;
2780 case A_PRIVACY_ENCRYPT:
2781 break;
2782 default:
2783 break;
2787 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2789 gchar *to = NULL;
2790 gchar *cc = NULL;
2791 gchar *bcc = NULL;
2792 gchar *subject = NULL;
2793 gchar *body = NULL;
2794 gchar *temp = NULL;
2795 gsize len = 0;
2796 gchar **attach = NULL;
2797 gchar *inreplyto = NULL;
2798 MailField mfield = NO_FIELD_PRESENT;
2800 /* get mailto parts but skip from */
2801 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2803 if (to) {
2804 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2805 mfield = TO_FIELD_PRESENT;
2807 if (cc)
2808 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2809 if (bcc)
2810 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2811 if (subject) {
2812 if (!g_utf8_validate (subject, -1, NULL)) {
2813 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2814 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2815 g_free(temp);
2816 } else {
2817 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2819 mfield = SUBJECT_FIELD_PRESENT;
2821 if (body) {
2822 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2823 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2824 GtkTextMark *mark;
2825 GtkTextIter iter;
2826 gboolean prev_autowrap = compose->autowrap;
2828 compose->autowrap = FALSE;
2830 mark = gtk_text_buffer_get_insert(buffer);
2831 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2833 if (!g_utf8_validate (body, -1, NULL)) {
2834 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2835 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2836 g_free(temp);
2837 } else {
2838 gtk_text_buffer_insert(buffer, &iter, body, -1);
2840 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2842 compose->autowrap = prev_autowrap;
2843 if (compose->autowrap)
2844 compose_wrap_all(compose);
2845 mfield = BODY_FIELD_PRESENT;
2848 if (attach) {
2849 gint i = 0, att = 0;
2850 gchar *warn_files = NULL;
2851 while (attach[i] != NULL) {
2852 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2853 if (utf8_filename) {
2854 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2855 gchar *tmp = g_strdup_printf("%s%s\n",
2856 warn_files?warn_files:"",
2857 utf8_filename);
2858 g_free(warn_files);
2859 warn_files = tmp;
2860 att++;
2862 g_free(utf8_filename);
2863 } else {
2864 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2866 i++;
2868 if (warn_files) {
2869 alertpanel_notice(ngettext(
2870 "The following file has been attached: \n%s",
2871 "The following files have been attached: \n%s", att), warn_files);
2872 g_free(warn_files);
2875 if (inreplyto)
2876 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2878 g_free(to);
2879 g_free(cc);
2880 g_free(bcc);
2881 g_free(subject);
2882 g_free(body);
2883 g_strfreev(attach);
2884 g_free(inreplyto);
2886 return mfield;
2889 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2891 static HeaderEntry hentry[] = {
2892 {"Reply-To:", NULL, TRUE },
2893 {"Cc:", NULL, TRUE },
2894 {"References:", NULL, FALSE },
2895 {"Bcc:", NULL, TRUE },
2896 {"Newsgroups:", NULL, TRUE },
2897 {"Followup-To:", NULL, TRUE },
2898 {"List-Post:", NULL, FALSE },
2899 {"X-Priority:", NULL, FALSE },
2900 {NULL, NULL, FALSE }
2903 enum
2905 H_REPLY_TO = 0,
2906 H_CC = 1,
2907 H_REFERENCES = 2,
2908 H_BCC = 3,
2909 H_NEWSGROUPS = 4,
2910 H_FOLLOWUP_TO = 5,
2911 H_LIST_POST = 6,
2912 H_X_PRIORITY = 7
2915 FILE *fp;
2917 cm_return_val_if_fail(msginfo != NULL, -1);
2919 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
2920 procheader_get_header_fields(fp, hentry);
2921 claws_fclose(fp);
2923 if (hentry[H_REPLY_TO].body != NULL) {
2924 if (hentry[H_REPLY_TO].body[0] != '\0') {
2925 compose->replyto =
2926 conv_unmime_header(hentry[H_REPLY_TO].body,
2927 NULL, TRUE);
2929 g_free(hentry[H_REPLY_TO].body);
2930 hentry[H_REPLY_TO].body = NULL;
2932 if (hentry[H_CC].body != NULL) {
2933 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2934 g_free(hentry[H_CC].body);
2935 hentry[H_CC].body = NULL;
2937 if (hentry[H_REFERENCES].body != NULL) {
2938 if (compose->mode == COMPOSE_REEDIT)
2939 compose->references = hentry[H_REFERENCES].body;
2940 else {
2941 compose->references = compose_parse_references
2942 (hentry[H_REFERENCES].body, msginfo->msgid);
2943 g_free(hentry[H_REFERENCES].body);
2945 hentry[H_REFERENCES].body = NULL;
2947 if (hentry[H_BCC].body != NULL) {
2948 if (compose->mode == COMPOSE_REEDIT)
2949 compose->bcc =
2950 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2951 g_free(hentry[H_BCC].body);
2952 hentry[H_BCC].body = NULL;
2954 if (hentry[H_NEWSGROUPS].body != NULL) {
2955 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2956 hentry[H_NEWSGROUPS].body = NULL;
2958 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2959 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2960 compose->followup_to =
2961 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2962 NULL, TRUE);
2964 g_free(hentry[H_FOLLOWUP_TO].body);
2965 hentry[H_FOLLOWUP_TO].body = NULL;
2967 if (hentry[H_LIST_POST].body != NULL) {
2968 gchar *to = NULL, *start = NULL;
2970 extract_address(hentry[H_LIST_POST].body);
2971 if (hentry[H_LIST_POST].body[0] != '\0') {
2972 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2974 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2975 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2977 if (to) {
2978 g_free(compose->ml_post);
2979 compose->ml_post = to;
2982 g_free(hentry[H_LIST_POST].body);
2983 hentry[H_LIST_POST].body = NULL;
2986 /* CLAWS - X-Priority */
2987 if (compose->mode == COMPOSE_REEDIT)
2988 if (hentry[H_X_PRIORITY].body != NULL) {
2989 gint priority;
2991 priority = atoi(hentry[H_X_PRIORITY].body);
2992 g_free(hentry[H_X_PRIORITY].body);
2994 hentry[H_X_PRIORITY].body = NULL;
2996 if (priority < PRIORITY_HIGHEST ||
2997 priority > PRIORITY_LOWEST)
2998 priority = PRIORITY_NORMAL;
3000 compose->priority = priority;
3003 if (compose->mode == COMPOSE_REEDIT) {
3004 if (msginfo->inreplyto && *msginfo->inreplyto)
3005 compose->inreplyto = g_strdup(msginfo->inreplyto);
3007 if (msginfo->msgid && *msginfo->msgid &&
3008 compose->folder != NULL &&
3009 compose->folder->stype == F_DRAFT)
3010 compose->msgid = g_strdup(msginfo->msgid);
3011 } else {
3012 if (msginfo->msgid && *msginfo->msgid)
3013 compose->inreplyto = g_strdup(msginfo->msgid);
3015 if (!compose->references) {
3016 if (msginfo->msgid && *msginfo->msgid) {
3017 if (msginfo->inreplyto && *msginfo->inreplyto)
3018 compose->references =
3019 g_strdup_printf("<%s>\n\t<%s>",
3020 msginfo->inreplyto,
3021 msginfo->msgid);
3022 else
3023 compose->references =
3024 g_strconcat("<", msginfo->msgid, ">",
3025 NULL);
3026 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3027 compose->references =
3028 g_strconcat("<", msginfo->inreplyto, ">",
3029 NULL);
3034 return 0;
3037 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3039 FILE *fp;
3040 HeaderEntry *he;
3042 cm_return_val_if_fail(msginfo != NULL, -1);
3044 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
3045 procheader_get_header_fields(fp, entries);
3046 claws_fclose(fp);
3048 he = entries;
3049 while (he != NULL && he->name != NULL) {
3050 GtkTreeIter iter;
3051 GtkListStore *model = NULL;
3053 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3054 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3055 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3056 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3057 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3058 ++he;
3061 return 0;
3064 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3066 GSList *ref_id_list, *cur;
3067 GString *new_ref;
3068 gchar *new_ref_str;
3070 ref_id_list = references_list_append(NULL, ref);
3071 if (!ref_id_list) return NULL;
3072 if (msgid && *msgid)
3073 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3075 for (;;) {
3076 gint len = 0;
3078 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3079 /* "<" + Message-ID + ">" + CR+LF+TAB */
3080 len += strlen((gchar *)cur->data) + 5;
3082 if (len > MAX_REFERENCES_LEN) {
3083 /* remove second message-ID */
3084 if (ref_id_list && ref_id_list->next &&
3085 ref_id_list->next->next) {
3086 g_free(ref_id_list->next->data);
3087 ref_id_list = g_slist_remove
3088 (ref_id_list, ref_id_list->next->data);
3089 } else {
3090 slist_free_strings_full(ref_id_list);
3091 return NULL;
3093 } else
3094 break;
3097 new_ref = g_string_new("");
3098 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3099 if (new_ref->len > 0)
3100 g_string_append(new_ref, "\n\t");
3101 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3104 slist_free_strings_full(ref_id_list);
3106 new_ref_str = new_ref->str;
3107 g_string_free(new_ref, FALSE);
3109 return new_ref_str;
3112 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3113 const gchar *fmt, const gchar *qmark,
3114 const gchar *body, gboolean rewrap,
3115 gboolean need_unescape,
3116 const gchar *err_msg)
3118 MsgInfo* dummyinfo = NULL;
3119 gchar *quote_str = NULL;
3120 gchar *buf;
3121 gboolean prev_autowrap;
3122 const gchar *trimmed_body = body;
3123 gint cursor_pos = -1;
3124 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3125 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3126 GtkTextIter iter;
3127 GtkTextMark *mark;
3130 SIGNAL_BLOCK(buffer);
3132 if (!msginfo) {
3133 dummyinfo = compose_msginfo_new_from_compose(compose);
3134 msginfo = dummyinfo;
3137 if (qmark != NULL) {
3138 #ifdef USE_ENCHANT
3139 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3140 compose->gtkaspell);
3141 #else
3142 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3143 #endif
3144 quote_fmt_scan_string(qmark);
3145 quote_fmt_parse();
3147 buf = quote_fmt_get_buffer();
3149 if (buf == NULL)
3150 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3151 else
3152 Xstrdup_a(quote_str, buf, goto error)
3155 if (fmt && *fmt != '\0') {
3157 if (trimmed_body)
3158 while (*trimmed_body == '\n')
3159 trimmed_body++;
3161 #ifdef USE_ENCHANT
3162 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3163 compose->gtkaspell);
3164 #else
3165 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3166 #endif
3167 if (need_unescape) {
3168 gchar *tmp = NULL;
3170 /* decode \-escape sequences in the internal representation of the quote format */
3171 tmp = g_malloc(strlen(fmt)+1);
3172 pref_get_unescaped_pref(tmp, fmt);
3173 quote_fmt_scan_string(tmp);
3174 quote_fmt_parse();
3175 g_free(tmp);
3176 } else {
3177 quote_fmt_scan_string(fmt);
3178 quote_fmt_parse();
3181 buf = quote_fmt_get_buffer();
3183 if (buf == NULL) {
3184 gint line = quote_fmt_get_line();
3185 alertpanel_error(err_msg, line);
3187 goto error;
3190 } else
3191 buf = "";
3193 prev_autowrap = compose->autowrap;
3194 compose->autowrap = FALSE;
3196 mark = gtk_text_buffer_get_insert(buffer);
3197 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3198 if (g_utf8_validate(buf, -1, NULL)) {
3199 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3200 } else {
3201 gchar *tmpout = NULL;
3202 tmpout = conv_codeset_strdup
3203 (buf, conv_get_locale_charset_str_no_utf8(),
3204 CS_INTERNAL);
3205 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3206 g_free(tmpout);
3207 tmpout = g_malloc(strlen(buf)*2+1);
3208 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3210 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3211 g_free(tmpout);
3214 cursor_pos = quote_fmt_get_cursor_pos();
3215 if (cursor_pos == -1)
3216 cursor_pos = gtk_text_iter_get_offset(&iter);
3217 compose->set_cursor_pos = cursor_pos;
3219 gtk_text_buffer_get_start_iter(buffer, &iter);
3220 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3221 gtk_text_buffer_place_cursor(buffer, &iter);
3223 compose->autowrap = prev_autowrap;
3224 if (compose->autowrap && rewrap)
3225 compose_wrap_all(compose);
3227 goto ok;
3229 error:
3230 buf = NULL;
3232 SIGNAL_UNBLOCK(buffer);
3234 procmsg_msginfo_free( &dummyinfo );
3236 return buf;
3239 /* if ml_post is of type addr@host and from is of type
3240 * addr-anything@host, return TRUE
3242 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3244 gchar *left_ml = NULL;
3245 gchar *right_ml = NULL;
3246 gchar *left_from = NULL;
3247 gchar *right_from = NULL;
3248 gboolean result = FALSE;
3250 if (!ml_post || !from)
3251 return FALSE;
3253 left_ml = g_strdup(ml_post);
3254 if (strstr(left_ml, "@")) {
3255 right_ml = strstr(left_ml, "@")+1;
3256 *(strstr(left_ml, "@")) = '\0';
3259 left_from = g_strdup(from);
3260 if (strstr(left_from, "@")) {
3261 right_from = strstr(left_from, "@")+1;
3262 *(strstr(left_from, "@")) = '\0';
3265 if (right_ml && right_from
3266 && !strncmp(left_from, left_ml, strlen(left_ml))
3267 && !strcmp(right_from, right_ml)) {
3268 result = TRUE;
3270 g_free(left_ml);
3271 g_free(left_from);
3273 return result;
3276 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3277 gboolean respect_default_to)
3279 if (!compose)
3280 return;
3281 if (!folder || !folder->prefs)
3282 return;
3284 if (folder->prefs->enable_default_from) {
3285 gtk_entry_set_text(GTK_ENTRY(compose->from_name), folder->prefs->default_from);
3286 compose_entry_indicate(compose, folder->prefs->default_from);
3288 if (respect_default_to && folder->prefs->enable_default_to) {
3289 compose_entry_append(compose, folder->prefs->default_to,
3290 COMPOSE_TO, PREF_FOLDER);
3291 compose_entry_indicate(compose, folder->prefs->default_to);
3293 if (folder->prefs->enable_default_cc) {
3294 compose_entry_append(compose, folder->prefs->default_cc,
3295 COMPOSE_CC, PREF_FOLDER);
3296 compose_entry_indicate(compose, folder->prefs->default_cc);
3298 if (folder->prefs->enable_default_bcc) {
3299 compose_entry_append(compose, folder->prefs->default_bcc,
3300 COMPOSE_BCC, PREF_FOLDER);
3301 compose_entry_indicate(compose, folder->prefs->default_bcc);
3303 if (folder->prefs->enable_default_replyto) {
3304 compose_entry_append(compose, folder->prefs->default_replyto,
3305 COMPOSE_REPLYTO, PREF_FOLDER);
3306 compose_entry_indicate(compose, folder->prefs->default_replyto);
3310 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3312 gchar *buf, *buf2;
3313 gchar *p;
3315 if (!compose || !msginfo)
3316 return;
3318 if (msginfo->subject && *msginfo->subject) {
3319 buf = p = g_strdup(msginfo->subject);
3320 p += subject_get_prefix_length(p);
3321 memmove(buf, p, strlen(p) + 1);
3323 buf2 = g_strdup_printf("Re: %s", buf);
3324 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3326 g_free(buf2);
3327 g_free(buf);
3328 } else
3329 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3332 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3333 gboolean to_all, gboolean to_ml,
3334 gboolean to_sender,
3335 gboolean followup_and_reply_to)
3337 GSList *cc_list = NULL;
3338 GSList *cur;
3339 gchar *from = NULL;
3340 gchar *replyto = NULL;
3341 gchar *ac_email = NULL;
3343 gboolean reply_to_ml = FALSE;
3344 gboolean default_reply_to = FALSE;
3346 cm_return_if_fail(compose->account != NULL);
3347 cm_return_if_fail(msginfo != NULL);
3349 reply_to_ml = to_ml && compose->ml_post;
3351 default_reply_to = msginfo->folder &&
3352 msginfo->folder->prefs->enable_default_reply_to;
3354 if (compose->account->protocol != A_NNTP) {
3355 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3357 if (reply_to_ml && !default_reply_to) {
3359 gboolean is_subscr = is_subscription(compose->ml_post,
3360 msginfo->from);
3361 if (!is_subscr) {
3362 /* normal answer to ml post with a reply-to */
3363 compose_entry_append(compose,
3364 compose->ml_post,
3365 COMPOSE_TO, PREF_ML);
3366 if (compose->replyto)
3367 compose_entry_append(compose,
3368 compose->replyto,
3369 COMPOSE_CC, PREF_ML);
3370 } else {
3371 /* answer to subscription confirmation */
3372 if (compose->replyto)
3373 compose_entry_append(compose,
3374 compose->replyto,
3375 COMPOSE_TO, PREF_ML);
3376 else if (msginfo->from)
3377 compose_entry_append(compose,
3378 msginfo->from,
3379 COMPOSE_TO, PREF_ML);
3382 else if (!(to_all || to_sender) && default_reply_to) {
3383 compose_entry_append(compose,
3384 msginfo->folder->prefs->default_reply_to,
3385 COMPOSE_TO, PREF_FOLDER);
3386 compose_entry_indicate(compose,
3387 msginfo->folder->prefs->default_reply_to);
3388 } else {
3389 gchar *tmp1 = NULL;
3390 if (!msginfo->from)
3391 return;
3392 if (to_sender)
3393 compose_entry_append(compose, msginfo->from,
3394 COMPOSE_TO, PREF_NONE);
3395 else if (to_all) {
3396 Xstrdup_a(tmp1, msginfo->from, return);
3397 extract_address(tmp1);
3398 compose_entry_append(compose,
3399 (!account_find_from_address(tmp1, FALSE))
3400 ? msginfo->from :
3401 msginfo->to,
3402 COMPOSE_TO, PREF_NONE);
3403 if (compose->replyto)
3404 compose_entry_append(compose,
3405 compose->replyto,
3406 COMPOSE_CC, PREF_NONE);
3407 } else {
3408 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3409 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3410 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3411 if (compose->replyto) {
3412 compose_entry_append(compose,
3413 compose->replyto,
3414 COMPOSE_TO, PREF_NONE);
3415 } else {
3416 compose_entry_append(compose,
3417 msginfo->from ? msginfo->from : "",
3418 COMPOSE_TO, PREF_NONE);
3420 } else {
3421 /* replying to own mail, use original recp */
3422 compose_entry_append(compose,
3423 msginfo->to ? msginfo->to : "",
3424 COMPOSE_TO, PREF_NONE);
3425 compose_entry_append(compose,
3426 msginfo->cc ? msginfo->cc : "",
3427 COMPOSE_CC, PREF_NONE);
3431 } else {
3432 if (to_sender || (compose->followup_to &&
3433 !strncmp(compose->followup_to, "poster", 6)))
3434 compose_entry_append
3435 (compose,
3436 (compose->replyto ? compose->replyto :
3437 msginfo->from ? msginfo->from : ""),
3438 COMPOSE_TO, PREF_NONE);
3440 else if (followup_and_reply_to || to_all) {
3441 compose_entry_append
3442 (compose,
3443 (compose->replyto ? compose->replyto :
3444 msginfo->from ? msginfo->from : ""),
3445 COMPOSE_TO, PREF_NONE);
3447 compose_entry_append
3448 (compose,
3449 compose->followup_to ? compose->followup_to :
3450 compose->newsgroups ? compose->newsgroups : "",
3451 COMPOSE_NEWSGROUPS, PREF_NONE);
3453 compose_entry_append
3454 (compose,
3455 msginfo->cc ? msginfo->cc : "",
3456 COMPOSE_CC, PREF_NONE);
3458 else
3459 compose_entry_append
3460 (compose,
3461 compose->followup_to ? compose->followup_to :
3462 compose->newsgroups ? compose->newsgroups : "",
3463 COMPOSE_NEWSGROUPS, PREF_NONE);
3465 compose_reply_set_subject(compose, msginfo);
3467 if (to_ml && compose->ml_post) return;
3468 if (!to_all || compose->account->protocol == A_NNTP) return;
3470 if (compose->replyto) {
3471 Xstrdup_a(replyto, compose->replyto, return);
3472 extract_address(replyto);
3474 if (msginfo->from) {
3475 Xstrdup_a(from, msginfo->from, return);
3476 extract_address(from);
3479 if (replyto && from)
3480 cc_list = address_list_append_with_comments(cc_list, from);
3481 if (to_all && msginfo->folder &&
3482 msginfo->folder->prefs->enable_default_reply_to)
3483 cc_list = address_list_append_with_comments(cc_list,
3484 msginfo->folder->prefs->default_reply_to);
3485 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3486 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3488 ac_email = g_utf8_strdown(compose->account->address, -1);
3490 if (cc_list) {
3491 for (cur = cc_list; cur != NULL; cur = cur->next) {
3492 gchar *addr = g_utf8_strdown(cur->data, -1);
3493 extract_address(addr);
3495 if (strcmp(ac_email, addr))
3496 compose_entry_append(compose, (gchar *)cur->data,
3497 COMPOSE_CC, PREF_NONE);
3498 else
3499 debug_print("Cc address same as compose account's, ignoring\n");
3501 g_free(addr);
3504 slist_free_strings_full(cc_list);
3507 g_free(ac_email);
3510 #define SET_ENTRY(entry, str) \
3512 if (str && *str) \
3513 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3516 #define SET_ADDRESS(type, str) \
3518 if (str && *str) \
3519 compose_entry_append(compose, str, type, PREF_NONE); \
3522 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3524 cm_return_if_fail(msginfo != NULL);
3526 SET_ENTRY(subject_entry, msginfo->subject);
3527 SET_ENTRY(from_name, msginfo->from);
3528 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3529 SET_ADDRESS(COMPOSE_CC, compose->cc);
3530 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3531 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3532 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3533 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3535 compose_update_priority_menu_item(compose);
3536 compose_update_privacy_system_menu_item(compose, FALSE);
3537 compose_show_first_last_header(compose, TRUE);
3540 #undef SET_ENTRY
3541 #undef SET_ADDRESS
3543 static void compose_insert_sig(Compose *compose, gboolean replace)
3545 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3546 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3547 GtkTextMark *mark;
3548 GtkTextIter iter, iter_end;
3549 gint cur_pos, ins_pos;
3550 gboolean prev_autowrap;
3551 gboolean found = FALSE;
3552 gboolean exists = FALSE;
3554 cm_return_if_fail(compose->account != NULL);
3556 BLOCK_WRAP();
3558 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3559 G_CALLBACK(compose_changed_cb),
3560 compose);
3562 mark = gtk_text_buffer_get_insert(buffer);
3563 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3564 cur_pos = gtk_text_iter_get_offset (&iter);
3565 ins_pos = cur_pos;
3567 gtk_text_buffer_get_end_iter(buffer, &iter);
3569 exists = (compose->sig_str != NULL);
3571 if (replace) {
3572 GtkTextIter first_iter, start_iter, end_iter;
3574 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3576 if (!exists || compose->sig_str[0] == '\0')
3577 found = FALSE;
3578 else
3579 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3580 compose->signature_tag);
3582 if (found) {
3583 /* include previous \n\n */
3584 gtk_text_iter_backward_chars(&first_iter, 1);
3585 start_iter = first_iter;
3586 end_iter = first_iter;
3587 /* skip re-start */
3588 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3589 compose->signature_tag);
3590 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3591 compose->signature_tag);
3592 if (found) {
3593 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3594 iter = start_iter;
3599 g_free(compose->sig_str);
3600 compose->sig_str = account_get_signature_str(compose->account);
3602 cur_pos = gtk_text_iter_get_offset(&iter);
3604 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3605 g_free(compose->sig_str);
3606 compose->sig_str = NULL;
3607 } else {
3608 if (compose->sig_inserted == FALSE)
3609 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3610 compose->sig_inserted = TRUE;
3612 cur_pos = gtk_text_iter_get_offset(&iter);
3613 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3614 /* remove \n\n */
3615 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3616 gtk_text_iter_forward_chars(&iter, 1);
3617 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3618 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3620 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3621 cur_pos = gtk_text_buffer_get_char_count (buffer);
3624 /* put the cursor where it should be
3625 * either where the quote_fmt says, either where it was */
3626 if (compose->set_cursor_pos < 0)
3627 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3628 else
3629 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3630 compose->set_cursor_pos);
3632 compose->set_cursor_pos = -1;
3633 gtk_text_buffer_place_cursor(buffer, &iter);
3634 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3635 G_CALLBACK(compose_changed_cb),
3636 compose);
3638 UNBLOCK_WRAP();
3641 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3643 GtkTextView *text;
3644 GtkTextBuffer *buffer;
3645 GtkTextMark *mark;
3646 GtkTextIter iter;
3647 const gchar *cur_encoding;
3648 gchar buf[BUFFSIZE];
3649 gint len;
3650 FILE *fp;
3651 gboolean prev_autowrap;
3652 #ifdef G_OS_WIN32
3653 GFile *f;
3654 GFileInfo *fi;
3655 GError *error = NULL;
3656 #else
3657 GStatBuf file_stat;
3658 #endif
3659 int ret;
3660 goffset size;
3661 GString *file_contents = NULL;
3662 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3664 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3666 /* get the size of the file we are about to insert */
3667 #ifdef G_OS_WIN32
3668 f = g_file_new_for_path(file);
3669 fi = g_file_query_info(f, "standard::size",
3670 G_FILE_QUERY_INFO_NONE, NULL, &error);
3671 ret = 0;
3672 if (error != NULL) {
3673 g_warning(error->message);
3674 ret = 1;
3675 g_error_free(error);
3676 g_object_unref(f);
3678 #else
3679 ret = g_stat(file, &file_stat);
3680 #endif
3681 if (ret != 0) {
3682 gchar *shortfile = g_path_get_basename(file);
3683 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3684 g_free(shortfile);
3685 return COMPOSE_INSERT_NO_FILE;
3686 } else if (prefs_common.warn_large_insert == TRUE) {
3687 #ifdef G_OS_WIN32
3688 size = g_file_info_get_size(fi);
3689 g_object_unref(fi);
3690 g_object_unref(f);
3691 #else
3692 size = file_stat.st_size;
3693 #endif
3695 /* ask user for confirmation if the file is large */
3696 if (prefs_common.warn_large_insert_size < 0 ||
3697 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3698 AlertValue aval;
3699 gchar *msg;
3701 msg = g_strdup_printf(_("You are about to insert a file of %s "
3702 "in the message body. Are you sure you want to do that?"),
3703 to_human_readable(size));
3704 aval = alertpanel_full(_("Are you sure?"), msg, NULL, _("_Cancel"),
3705 NULL, _("_Insert"), NULL, NULL, ALERTFOCUS_SECOND, TRUE,
3706 NULL, ALERT_QUESTION);
3707 g_free(msg);
3709 /* do we ask for confirmation next time? */
3710 if (aval & G_ALERTDISABLE) {
3711 /* no confirmation next time, disable feature in preferences */
3712 aval &= ~G_ALERTDISABLE;
3713 prefs_common.warn_large_insert = FALSE;
3716 /* abort file insertion if user canceled action */
3717 if (aval != G_ALERTALTERNATE) {
3718 return COMPOSE_INSERT_NO_FILE;
3724 if ((fp = claws_fopen(file, "rb")) == NULL) {
3725 FILE_OP_ERROR(file, "claws_fopen");
3726 return COMPOSE_INSERT_READ_ERROR;
3729 prev_autowrap = compose->autowrap;
3730 compose->autowrap = FALSE;
3732 text = GTK_TEXT_VIEW(compose->text);
3733 buffer = gtk_text_view_get_buffer(text);
3734 mark = gtk_text_buffer_get_insert(buffer);
3735 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3737 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3738 G_CALLBACK(text_inserted),
3739 compose);
3741 cur_encoding = conv_get_locale_charset_str_no_utf8();
3743 file_contents = g_string_new("");
3744 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3745 gchar *str;
3747 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3748 str = g_strdup(buf);
3749 else {
3750 codeconv_set_strict(TRUE);
3751 str = conv_codeset_strdup
3752 (buf, cur_encoding, CS_INTERNAL);
3753 codeconv_set_strict(FALSE);
3755 if (!str) {
3756 result = COMPOSE_INSERT_INVALID_CHARACTER;
3757 break;
3760 if (!str) continue;
3762 /* strip <CR> if DOS/Windows file,
3763 replace <CR> with <LF> if Macintosh file. */
3764 strcrchomp(str);
3765 len = strlen(str);
3766 if (len > 0 && str[len - 1] != '\n') {
3767 while (--len >= 0)
3768 if (str[len] == '\r') str[len] = '\n';
3771 file_contents = g_string_append(file_contents, str);
3772 g_free(str);
3775 if (result == COMPOSE_INSERT_SUCCESS) {
3776 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3778 compose_changed_cb(NULL, compose);
3779 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3780 G_CALLBACK(text_inserted),
3781 compose);
3782 compose->autowrap = prev_autowrap;
3783 if (compose->autowrap)
3784 compose_wrap_all(compose);
3787 g_string_free(file_contents, TRUE);
3788 claws_fclose(fp);
3790 return result;
3793 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3794 const gchar *filename,
3795 const gchar *content_type,
3796 const gchar *charset)
3798 AttachInfo *ainfo;
3799 GtkTreeIter iter;
3800 FILE *fp;
3801 off_t size;
3802 GAuto *auto_ainfo;
3803 gchar *size_text;
3804 GtkListStore *store;
3805 gchar *name;
3806 gboolean has_binary = FALSE;
3808 if (!is_file_exist(file)) {
3809 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3810 gboolean result = FALSE;
3811 if (file_from_uri && is_file_exist(file_from_uri)) {
3812 result = compose_attach_append(
3813 compose, file_from_uri,
3814 filename, content_type,
3815 charset);
3817 g_free(file_from_uri);
3818 if (result)
3819 return TRUE;
3820 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3821 return FALSE;
3823 if ((size = get_file_size(file)) < 0) {
3824 alertpanel_error("Can't get file size of %s\n", filename);
3825 return FALSE;
3828 /* In batch mode, we allow 0-length files to be attached no questions asked */
3829 if (size == 0 && !compose->batch) {
3830 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3831 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3832 NULL, _("_Cancel"), NULL, _("_Attach anyway"),
3833 NULL, NULL, ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3834 g_free(msg);
3836 if (aval != G_ALERTALTERNATE) {
3837 return FALSE;
3840 if ((fp = claws_fopen(file, "rb")) == NULL) {
3841 alertpanel_error(_("Can't read %s."), filename);
3842 return FALSE;
3844 claws_fclose(fp);
3846 ainfo = g_new0(AttachInfo, 1);
3847 auto_ainfo = g_auto_pointer_new_with_free
3848 (ainfo, (GFreeFunc) compose_attach_info_free);
3849 ainfo->file = g_strdup(file);
3851 if (content_type) {
3852 ainfo->content_type = g_strdup(content_type);
3853 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3854 MsgInfo *msginfo;
3855 MsgFlags flags = {0, 0};
3857 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3858 ainfo->encoding = ENC_7BIT;
3859 else
3860 ainfo->encoding = ENC_8BIT;
3862 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3863 if (msginfo && msginfo->subject)
3864 name = g_strdup(msginfo->subject);
3865 else
3866 name = g_path_get_basename(filename ? filename : file);
3868 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3870 procmsg_msginfo_free(&msginfo);
3871 } else {
3872 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3873 ainfo->charset = g_strdup(charset);
3874 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3875 } else {
3876 ainfo->encoding = ENC_BASE64;
3878 name = g_path_get_basename(filename ? filename : file);
3879 ainfo->name = g_strdup(name);
3881 g_free(name);
3882 } else {
3883 ainfo->content_type = procmime_get_mime_type(file);
3884 if (!ainfo->content_type) {
3885 ainfo->content_type =
3886 g_strdup("application/octet-stream");
3887 ainfo->encoding = ENC_BASE64;
3888 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3889 ainfo->encoding =
3890 procmime_get_encoding_for_text_file(file, &has_binary);
3891 else
3892 ainfo->encoding = ENC_BASE64;
3893 name = g_path_get_basename(filename ? filename : file);
3894 ainfo->name = g_strdup(name);
3895 g_free(name);
3898 if (ainfo->name != NULL
3899 && !strcmp(ainfo->name, ".")) {
3900 g_free(ainfo->name);
3901 ainfo->name = NULL;
3904 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3905 g_free(ainfo->content_type);
3906 ainfo->content_type = g_strdup("application/octet-stream");
3907 g_free(ainfo->charset);
3908 ainfo->charset = NULL;
3911 ainfo->size = (goffset)size;
3912 size_text = to_human_readable((goffset)size);
3914 store = GTK_LIST_STORE(gtk_tree_view_get_model
3915 (GTK_TREE_VIEW(compose->attach_clist)));
3917 gtk_list_store_append(store, &iter);
3918 gtk_list_store_set(store, &iter,
3919 COL_MIMETYPE, ainfo->content_type,
3920 COL_SIZE, size_text,
3921 COL_NAME, ainfo->name,
3922 COL_CHARSET, ainfo->charset,
3923 COL_DATA, ainfo,
3924 COL_AUTODATA, auto_ainfo,
3925 -1);
3927 g_auto_pointer_free(auto_ainfo);
3928 compose_attach_update_label(compose);
3929 return TRUE;
3932 void compose_use_signing(Compose *compose, gboolean use_signing)
3934 compose->use_signing = use_signing;
3935 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3938 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3940 compose->use_encryption = use_encryption;
3941 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3944 #define NEXT_PART_NOT_CHILD(info) \
3946 node = info->node; \
3947 while (node->children) \
3948 node = g_node_last_child(node); \
3949 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3952 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3954 MimeInfo *mimeinfo;
3955 MimeInfo *child;
3956 MimeInfo *firsttext = NULL;
3957 MimeInfo *encrypted = NULL;
3958 GNode *node;
3959 gchar *outfile;
3960 const gchar *partname = NULL;
3962 mimeinfo = procmime_scan_message(msginfo);
3963 if (!mimeinfo) return;
3965 if (mimeinfo->node->children == NULL) {
3966 procmime_mimeinfo_free_all(&mimeinfo);
3967 return;
3970 /* find first content part */
3971 child = (MimeInfo *) mimeinfo->node->children->data;
3972 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3973 child = (MimeInfo *)child->node->children->data;
3975 if (child) {
3976 if (child->type == MIMETYPE_TEXT) {
3977 firsttext = child;
3978 debug_print("First text part found\n");
3979 } else if (compose->mode == COMPOSE_REEDIT &&
3980 child->type == MIMETYPE_APPLICATION &&
3981 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3982 encrypted = (MimeInfo *)child->node->parent->data;
3985 child = (MimeInfo *) mimeinfo->node->children->data;
3986 while (child != NULL) {
3987 gint err;
3989 if (child == encrypted) {
3990 /* skip this part of tree */
3991 NEXT_PART_NOT_CHILD(child);
3992 continue;
3995 if (child->type == MIMETYPE_MULTIPART) {
3996 /* get the actual content */
3997 child = procmime_mimeinfo_next(child);
3998 continue;
4001 if (child == firsttext) {
4002 child = procmime_mimeinfo_next(child);
4003 continue;
4006 outfile = procmime_get_tmp_file_name(child);
4007 if ((err = procmime_get_part(outfile, child)) < 0)
4008 g_warning("can't get the part of multipart message. (%s)", g_strerror(-err));
4009 else {
4010 gchar *content_type;
4012 content_type = procmime_get_content_type_str(child->type, child->subtype);
4014 /* if we meet a pgp signature, we don't attach it, but
4015 * we force signing. */
4016 if ((strcmp(content_type, "application/pgp-signature") &&
4017 strcmp(content_type, "application/pkcs7-signature") &&
4018 strcmp(content_type, "application/x-pkcs7-signature"))
4019 || compose->mode == COMPOSE_REDIRECT) {
4020 partname = procmime_mimeinfo_get_parameter(child, "filename");
4021 if (partname == NULL)
4022 partname = procmime_mimeinfo_get_parameter(child, "name");
4023 if (partname == NULL)
4024 partname = "";
4025 compose_attach_append(compose, outfile,
4026 partname, content_type,
4027 procmime_mimeinfo_get_parameter(child, "charset"));
4028 } else {
4029 compose_force_signing(compose, compose->account, NULL);
4031 g_free(content_type);
4033 g_free(outfile);
4034 NEXT_PART_NOT_CHILD(child);
4036 procmime_mimeinfo_free_all(&mimeinfo);
4039 #undef NEXT_PART_NOT_CHILD
4043 typedef enum {
4044 WAIT_FOR_INDENT_CHAR,
4045 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4046 } IndentState;
4048 /* return indent length, we allow:
4049 indent characters followed by indent characters or spaces/tabs,
4050 alphabets and numbers immediately followed by indent characters,
4051 and the repeating sequences of the above
4052 If quote ends with multiple spaces, only the first one is included. */
4053 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4054 const GtkTextIter *start, gint *len)
4056 GtkTextIter iter = *start;
4057 gunichar wc;
4058 gchar ch[6];
4059 gint clen;
4060 IndentState state = WAIT_FOR_INDENT_CHAR;
4061 gboolean is_space;
4062 gboolean is_indent;
4063 gint alnum_count = 0;
4064 gint space_count = 0;
4065 gint quote_len = 0;
4067 if (prefs_common.quote_chars == NULL) {
4068 return 0 ;
4071 while (!gtk_text_iter_ends_line(&iter)) {
4072 wc = gtk_text_iter_get_char(&iter);
4073 if (g_unichar_iswide(wc))
4074 break;
4075 clen = g_unichar_to_utf8(wc, ch);
4076 if (clen != 1)
4077 break;
4079 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4080 is_space = g_unichar_isspace(wc);
4082 if (state == WAIT_FOR_INDENT_CHAR) {
4083 if (!is_indent && !g_unichar_isalnum(wc))
4084 break;
4085 if (is_indent) {
4086 quote_len += alnum_count + space_count + 1;
4087 alnum_count = space_count = 0;
4088 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4089 } else
4090 alnum_count++;
4091 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4092 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4093 break;
4094 if (is_space)
4095 space_count++;
4096 else if (is_indent) {
4097 quote_len += alnum_count + space_count + 1;
4098 alnum_count = space_count = 0;
4099 } else {
4100 alnum_count++;
4101 state = WAIT_FOR_INDENT_CHAR;
4105 gtk_text_iter_forward_char(&iter);
4108 if (quote_len > 0 && space_count > 0)
4109 quote_len++;
4111 if (len)
4112 *len = quote_len;
4114 if (quote_len > 0) {
4115 iter = *start;
4116 gtk_text_iter_forward_chars(&iter, quote_len);
4117 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4120 return NULL;
4123 /* return >0 if the line is itemized */
4124 static int compose_itemized_length(GtkTextBuffer *buffer,
4125 const GtkTextIter *start)
4127 GtkTextIter iter = *start;
4128 gunichar wc;
4129 gchar ch[6];
4130 gint clen;
4131 gint len = 0;
4132 if (gtk_text_iter_ends_line(&iter))
4133 return 0;
4135 while (1) {
4136 len++;
4137 wc = gtk_text_iter_get_char(&iter);
4138 if (!g_unichar_isspace(wc))
4139 break;
4140 gtk_text_iter_forward_char(&iter);
4141 if (gtk_text_iter_ends_line(&iter))
4142 return 0;
4145 clen = g_unichar_to_utf8(wc, ch);
4146 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4147 (clen == 3 && (
4148 wc == 0x2022 || /* BULLET */
4149 wc == 0x2023 || /* TRIANGULAR BULLET */
4150 wc == 0x2043 || /* HYPHEN BULLET */
4151 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4152 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4153 wc == 0x2219 || /* BULLET OPERATOR */
4154 wc == 0x25d8 || /* INVERSE BULLET */
4155 wc == 0x25e6 || /* WHITE BULLET */
4156 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4157 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4158 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4159 wc == 0x29be || /* CIRCLED WHITE BULLET */
4160 wc == 0x29bf /* CIRCLED BULLET */
4161 ))))
4162 return 0;
4164 gtk_text_iter_forward_char(&iter);
4165 if (gtk_text_iter_ends_line(&iter))
4166 return 0;
4167 wc = gtk_text_iter_get_char(&iter);
4168 if (g_unichar_isspace(wc)) {
4169 return len+1;
4171 return 0;
4174 /* return the string at the start of the itemization */
4175 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4176 const GtkTextIter *start)
4178 GtkTextIter iter = *start;
4179 gunichar wc;
4180 gint len = 0;
4181 GString *item_chars = g_string_new("");
4182 gchar *str = NULL;
4184 if (gtk_text_iter_ends_line(&iter)) {
4185 g_string_free(item_chars, FALSE);
4186 return NULL;
4189 while (1) {
4190 len++;
4191 wc = gtk_text_iter_get_char(&iter);
4192 if (!g_unichar_isspace(wc))
4193 break;
4194 gtk_text_iter_forward_char(&iter);
4195 if (gtk_text_iter_ends_line(&iter))
4196 break;
4197 g_string_append_unichar(item_chars, wc);
4200 str = item_chars->str;
4201 g_string_free(item_chars, FALSE);
4202 return str;
4205 /* return the number of spaces at a line's start */
4206 static int compose_left_offset_length(GtkTextBuffer *buffer,
4207 const GtkTextIter *start)
4209 GtkTextIter iter = *start;
4210 gunichar wc;
4211 gint len = 0;
4212 if (gtk_text_iter_ends_line(&iter))
4213 return 0;
4215 while (1) {
4216 wc = gtk_text_iter_get_char(&iter);
4217 if (!g_unichar_isspace(wc))
4218 break;
4219 len++;
4220 gtk_text_iter_forward_char(&iter);
4221 if (gtk_text_iter_ends_line(&iter))
4222 return 0;
4225 gtk_text_iter_forward_char(&iter);
4226 if (gtk_text_iter_ends_line(&iter))
4227 return 0;
4228 return len;
4231 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4232 const GtkTextIter *start,
4233 GtkTextIter *break_pos,
4234 gint max_col,
4235 gint quote_len)
4237 GtkTextIter iter = *start, line_end = *start;
4238 PangoLogAttr *attrs;
4239 gchar *str;
4240 gchar *p;
4241 gint len;
4242 gint i;
4243 gint col = 0;
4244 gint pos = 0;
4245 gboolean can_break = FALSE;
4246 gboolean do_break = FALSE;
4247 gboolean was_white = FALSE;
4248 gboolean prev_dont_break = FALSE;
4250 gtk_text_iter_forward_to_line_end(&line_end);
4251 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4252 len = g_utf8_strlen(str, -1);
4254 if (len == 0) {
4255 g_free(str);
4256 g_warning("compose_get_line_break_pos: len = 0!");
4257 return FALSE;
4260 /* g_print("breaking line: %d: %s (len = %d)\n",
4261 gtk_text_iter_get_line(&iter), str, len); */
4263 attrs = g_new(PangoLogAttr, len + 1);
4265 pango_default_break(str, -1, NULL, attrs, len + 1);
4267 p = str;
4269 /* skip quote and leading spaces */
4270 for (i = 0; *p != '\0' && i < len; i++) {
4271 gunichar wc;
4273 wc = g_utf8_get_char(p);
4274 if (i >= quote_len && !g_unichar_isspace(wc))
4275 break;
4276 if (g_unichar_iswide(wc))
4277 col += 2;
4278 else if (*p == '\t')
4279 col += 8;
4280 else
4281 col++;
4282 p = g_utf8_next_char(p);
4285 for (; *p != '\0' && i < len; i++) {
4286 PangoLogAttr *attr = attrs + i;
4287 gunichar wc = g_utf8_get_char(p);
4288 gint uri_len;
4290 /* attr->is_line_break will be false for some characters that
4291 * we want to break a line before, like '/' or ':', so we
4292 * also allow breaking on any non-wide character. The
4293 * mentioned pango attribute is still useful to decide on
4294 * line breaks when wide characters are involved. */
4295 if ((!g_unichar_iswide(wc) || attr->is_line_break)
4296 && can_break && was_white && !prev_dont_break)
4297 pos = i;
4299 was_white = attr->is_white;
4301 /* don't wrap URI */
4302 if ((uri_len = get_uri_len(p)) > 0) {
4303 col += uri_len;
4304 if (pos > 0 && col > max_col) {
4305 do_break = TRUE;
4306 break;
4308 i += uri_len - 1;
4309 p += uri_len;
4310 can_break = TRUE;
4311 continue;
4314 if (g_unichar_iswide(wc)) {
4315 col += 2;
4316 if (prev_dont_break && can_break && attr->is_line_break)
4317 pos = i;
4318 } else if (*p == '\t')
4319 col += 8;
4320 else
4321 col++;
4322 if (pos > 0 && col > max_col) {
4323 do_break = TRUE;
4324 break;
4327 if (*p == '-' || *p == '/')
4328 prev_dont_break = TRUE;
4329 else
4330 prev_dont_break = FALSE;
4332 p = g_utf8_next_char(p);
4333 can_break = TRUE;
4336 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4338 g_free(attrs);
4339 g_free(str);
4341 *break_pos = *start;
4342 gtk_text_iter_set_line_offset(break_pos, pos);
4344 return do_break;
4347 static gboolean compose_join_next_line(Compose *compose,
4348 GtkTextBuffer *buffer,
4349 GtkTextIter *iter,
4350 const gchar *quote_str)
4352 GtkTextIter iter_ = *iter, cur, prev, next, end;
4353 PangoLogAttr attrs[3];
4354 gchar *str;
4355 gchar *next_quote_str;
4356 gunichar wc1, wc2;
4357 gint quote_len;
4358 gboolean keep_cursor = FALSE;
4360 if (!gtk_text_iter_forward_line(&iter_) ||
4361 gtk_text_iter_ends_line(&iter_)) {
4362 return FALSE;
4364 next_quote_str = compose_get_quote_str(buffer, &iter_, &quote_len);
4366 if ((quote_str || next_quote_str) &&
4367 g_strcmp0(quote_str, next_quote_str) != 0) {
4368 g_free(next_quote_str);
4369 return FALSE;
4371 g_free(next_quote_str);
4373 end = iter_;
4374 if (quote_len > 0) {
4375 gtk_text_iter_forward_chars(&end, quote_len);
4376 if (gtk_text_iter_ends_line(&end)) {
4377 return FALSE;
4381 /* don't join itemized lines */
4382 if (compose_itemized_length(buffer, &end) > 0) {
4383 return FALSE;
4386 /* don't join signature separator */
4387 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4388 return FALSE;
4390 /* delete quote str */
4391 if (quote_len > 0)
4392 gtk_text_buffer_delete(buffer, &iter_, &end);
4394 /* don't join line breaks put by the user */
4395 prev = cur = iter_;
4396 gtk_text_iter_backward_char(&cur);
4397 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4398 gtk_text_iter_forward_char(&cur);
4399 *iter = cur;
4400 return FALSE;
4402 gtk_text_iter_forward_char(&cur);
4403 /* delete linebreak and extra spaces */
4404 while (gtk_text_iter_backward_char(&cur)) {
4405 wc1 = gtk_text_iter_get_char(&cur);
4406 if (!g_unichar_isspace(wc1))
4407 break;
4408 prev = cur;
4410 next = cur = iter_;
4411 while (!gtk_text_iter_ends_line(&cur)) {
4412 wc1 = gtk_text_iter_get_char(&cur);
4413 if (!g_unichar_isspace(wc1))
4414 break;
4415 gtk_text_iter_forward_char(&cur);
4416 next = cur;
4418 if (!gtk_text_iter_equal(&prev, &next)) {
4419 GtkTextMark *mark;
4421 mark = gtk_text_buffer_get_insert(buffer);
4422 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4423 if (gtk_text_iter_equal(&prev, &cur))
4424 keep_cursor = TRUE;
4425 gtk_text_buffer_delete(buffer, &prev, &next);
4427 iter_ = prev;
4429 /* insert space if required */
4430 gtk_text_iter_backward_char(&prev);
4431 wc1 = gtk_text_iter_get_char(&prev);
4432 wc2 = gtk_text_iter_get_char(&next);
4433 gtk_text_iter_forward_char(&next);
4434 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4435 pango_default_break(str, -1, NULL, attrs, 3);
4436 if (!attrs[1].is_line_break ||
4437 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4438 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4439 if (keep_cursor) {
4440 gtk_text_iter_backward_char(&iter_);
4441 gtk_text_buffer_place_cursor(buffer, &iter_);
4444 g_free(str);
4446 *iter = iter_;
4447 return TRUE;
4450 #define ADD_TXT_POS(bp_, ep_, pti_) \
4451 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4452 last = last->next; \
4453 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4454 last->next = NULL; \
4455 } else { \
4456 g_warning("alloc error scanning URIs"); \
4459 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4461 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4462 GtkTextBuffer *buffer;
4463 GtkTextIter iter, break_pos, end_of_line;
4464 gchar *quote_str = NULL;
4465 gint quote_len;
4466 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4467 gboolean prev_autowrap = compose->autowrap;
4468 gint startq_offset = -1, noq_offset = -1;
4469 gint uri_start = -1, uri_stop = -1;
4470 gint nouri_start = -1, nouri_stop = -1;
4471 gint num_blocks = 0;
4472 gint quotelevel = -1;
4473 gboolean modified = force;
4474 gboolean removed = FALSE;
4475 gboolean modified_before_remove = FALSE;
4476 gint lines = 0;
4477 gboolean start = TRUE;
4478 gint itemized_len = 0, rem_item_len = 0;
4479 gchar *itemized_chars = NULL;
4480 gboolean item_continuation = FALSE;
4482 if (force) {
4483 modified = TRUE;
4485 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4486 modified = TRUE;
4489 compose->autowrap = FALSE;
4491 buffer = gtk_text_view_get_buffer(text);
4492 undo_wrapping(compose->undostruct, TRUE);
4493 if (par_iter) {
4494 iter = *par_iter;
4495 } else {
4496 GtkTextMark *mark;
4497 mark = gtk_text_buffer_get_insert(buffer);
4498 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4502 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4503 if (gtk_text_iter_ends_line(&iter)) {
4504 while (gtk_text_iter_ends_line(&iter) &&
4505 gtk_text_iter_forward_line(&iter))
4507 } else {
4508 while (gtk_text_iter_backward_line(&iter)) {
4509 if (gtk_text_iter_ends_line(&iter)) {
4510 gtk_text_iter_forward_line(&iter);
4511 break;
4515 } else {
4516 /* move to line start */
4517 gtk_text_iter_set_line_offset(&iter, 0);
4520 itemized_len = compose_itemized_length(buffer, &iter);
4522 if (!itemized_len) {
4523 itemized_len = compose_left_offset_length(buffer, &iter);
4524 item_continuation = TRUE;
4527 if (itemized_len)
4528 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4530 /* go until paragraph end (empty line) */
4531 while (start || !gtk_text_iter_ends_line(&iter)) {
4532 gchar *scanpos = NULL;
4533 /* parse table - in order of priority */
4534 struct table {
4535 const gchar *needle; /* token */
4537 /* token search function */
4538 gchar *(*search) (const gchar *haystack,
4539 const gchar *needle);
4540 /* part parsing function */
4541 gboolean (*parse) (const gchar *start,
4542 const gchar *scanpos,
4543 const gchar **bp_,
4544 const gchar **ep_,
4545 gboolean hdr);
4546 /* part to URI function */
4547 gchar *(*build_uri) (const gchar *bp,
4548 const gchar *ep);
4551 static struct table parser[] = {
4552 {"http://", strcasestr, get_uri_part, make_uri_string},
4553 {"https://", strcasestr, get_uri_part, make_uri_string},
4554 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4555 {"ftps://", strcasestr, get_uri_part, make_uri_string},
4556 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4557 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4558 {"www.", strcasestr, get_uri_part, make_http_string},
4559 {"webcal://",strcasestr, get_uri_part, make_uri_string},
4560 {"webcals://",strcasestr, get_uri_part, make_uri_string},
4561 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4562 {"@", strcasestr, get_email_part, make_email_string}
4564 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4565 gint last_index = PARSE_ELEMS;
4566 gint n;
4567 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4568 gint walk_pos;
4570 start = FALSE;
4571 if (!prev_autowrap && num_blocks == 0) {
4572 num_blocks++;
4573 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4574 G_CALLBACK(text_inserted),
4575 compose);
4577 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4578 goto colorize;
4580 uri_start = uri_stop = -1;
4581 quote_len = 0;
4582 quote_str = compose_get_quote_str(buffer, &iter, &quote_len);
4584 if (quote_str) {
4585 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4586 if (startq_offset == -1)
4587 startq_offset = gtk_text_iter_get_offset(&iter);
4588 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4589 if (quotelevel > 2) {
4590 /* recycle colors */
4591 if (prefs_common.recycle_quote_colors)
4592 quotelevel %= 3;
4593 else
4594 quotelevel = 2;
4596 if (!wrap_quote) {
4597 goto colorize;
4599 } else {
4600 if (startq_offset == -1)
4601 noq_offset = gtk_text_iter_get_offset(&iter);
4602 quotelevel = -1;
4605 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4606 goto colorize;
4608 if (gtk_text_iter_ends_line(&iter)) {
4609 goto colorize;
4610 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4611 prefs_common.linewrap_len,
4612 quote_len)) {
4613 GtkTextIter prev, next, cur;
4614 if (prev_autowrap != FALSE || force) {
4615 compose->automatic_break = TRUE;
4616 modified = TRUE;
4617 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4618 compose->automatic_break = FALSE;
4619 if (itemized_len && compose->autoindent) {
4620 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4621 if (!item_continuation)
4622 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4624 } else if (quote_str && wrap_quote) {
4625 compose->automatic_break = TRUE;
4626 modified = TRUE;
4627 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4628 compose->automatic_break = FALSE;
4629 if (itemized_len && compose->autoindent) {
4630 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4631 if (!item_continuation)
4632 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4634 } else
4635 goto colorize;
4636 /* remove trailing spaces */
4637 cur = break_pos;
4638 rem_item_len = itemized_len;
4639 while (compose->autoindent && rem_item_len-- > 0)
4640 gtk_text_iter_backward_char(&cur);
4641 gtk_text_iter_backward_char(&cur);
4643 prev = next = cur;
4644 while (!gtk_text_iter_starts_line(&cur)) {
4645 gunichar wc;
4647 gtk_text_iter_backward_char(&cur);
4648 wc = gtk_text_iter_get_char(&cur);
4649 if (!g_unichar_isspace(wc))
4650 break;
4651 prev = cur;
4653 if (!gtk_text_iter_equal(&prev, &next)) {
4654 gtk_text_buffer_delete(buffer, &prev, &next);
4655 break_pos = next;
4656 gtk_text_iter_forward_char(&break_pos);
4659 if (quote_str)
4660 gtk_text_buffer_insert(buffer, &break_pos,
4661 quote_str, -1);
4663 iter = break_pos;
4664 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4666 /* move iter to current line start */
4667 gtk_text_iter_set_line_offset(&iter, 0);
4668 if (quote_str) {
4669 g_free(quote_str);
4670 quote_str = NULL;
4672 continue;
4673 } else {
4674 /* move iter to next line start */
4675 iter = break_pos;
4676 lines++;
4679 colorize:
4680 if (!prev_autowrap && num_blocks > 0) {
4681 num_blocks--;
4682 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4683 G_CALLBACK(text_inserted),
4684 compose);
4686 end_of_line = iter;
4687 while (!gtk_text_iter_ends_line(&end_of_line)) {
4688 gtk_text_iter_forward_char(&end_of_line);
4690 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4692 nouri_start = gtk_text_iter_get_offset(&iter);
4693 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4695 walk_pos = gtk_text_iter_get_offset(&iter);
4696 /* FIXME: this looks phony. scanning for anything in the parse table */
4697 for (n = 0; n < PARSE_ELEMS; n++) {
4698 gchar *tmp;
4700 tmp = parser[n].search(walk, parser[n].needle);
4701 if (tmp) {
4702 if (scanpos == NULL || tmp < scanpos) {
4703 scanpos = tmp;
4704 last_index = n;
4709 bp = ep = 0;
4710 if (scanpos) {
4711 /* check if URI can be parsed */
4712 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4713 (const gchar **)&ep, FALSE)
4714 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4715 walk = ep;
4716 } else
4717 walk = scanpos +
4718 strlen(parser[last_index].needle);
4720 if (bp && ep) {
4721 uri_start = walk_pos + (bp - o_walk);
4722 uri_stop = walk_pos + (ep - o_walk);
4724 g_free(o_walk);
4725 o_walk = NULL;
4726 gtk_text_iter_forward_line(&iter);
4727 g_free(quote_str);
4728 quote_str = NULL;
4729 if (startq_offset != -1) {
4730 GtkTextIter startquote, endquote;
4731 gtk_text_buffer_get_iter_at_offset(
4732 buffer, &startquote, startq_offset);
4733 endquote = iter;
4735 switch (quotelevel) {
4736 case 0:
4737 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4738 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4739 gtk_text_buffer_apply_tag_by_name(
4740 buffer, "quote0", &startquote, &endquote);
4741 gtk_text_buffer_remove_tag_by_name(
4742 buffer, "quote1", &startquote, &endquote);
4743 gtk_text_buffer_remove_tag_by_name(
4744 buffer, "quote2", &startquote, &endquote);
4745 modified = TRUE;
4747 break;
4748 case 1:
4749 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4750 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4751 gtk_text_buffer_apply_tag_by_name(
4752 buffer, "quote1", &startquote, &endquote);
4753 gtk_text_buffer_remove_tag_by_name(
4754 buffer, "quote0", &startquote, &endquote);
4755 gtk_text_buffer_remove_tag_by_name(
4756 buffer, "quote2", &startquote, &endquote);
4757 modified = TRUE;
4759 break;
4760 case 2:
4761 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4762 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4763 gtk_text_buffer_apply_tag_by_name(
4764 buffer, "quote2", &startquote, &endquote);
4765 gtk_text_buffer_remove_tag_by_name(
4766 buffer, "quote0", &startquote, &endquote);
4767 gtk_text_buffer_remove_tag_by_name(
4768 buffer, "quote1", &startquote, &endquote);
4769 modified = TRUE;
4771 break;
4773 startq_offset = -1;
4774 } else if (noq_offset != -1) {
4775 GtkTextIter startnoquote, endnoquote;
4776 gtk_text_buffer_get_iter_at_offset(
4777 buffer, &startnoquote, noq_offset);
4778 endnoquote = iter;
4780 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4781 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4782 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4783 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4784 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4785 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4786 gtk_text_buffer_remove_tag_by_name(
4787 buffer, "quote0", &startnoquote, &endnoquote);
4788 gtk_text_buffer_remove_tag_by_name(
4789 buffer, "quote1", &startnoquote, &endnoquote);
4790 gtk_text_buffer_remove_tag_by_name(
4791 buffer, "quote2", &startnoquote, &endnoquote);
4792 modified = TRUE;
4794 noq_offset = -1;
4797 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4798 GtkTextIter nouri_start_iter, nouri_end_iter;
4799 gtk_text_buffer_get_iter_at_offset(
4800 buffer, &nouri_start_iter, nouri_start);
4801 gtk_text_buffer_get_iter_at_offset(
4802 buffer, &nouri_end_iter, nouri_stop);
4803 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4804 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4805 gtk_text_buffer_remove_tag_by_name(
4806 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4807 modified_before_remove = modified;
4808 modified = TRUE;
4809 removed = TRUE;
4812 if (uri_start >= 0 && uri_stop > 0) {
4813 GtkTextIter uri_start_iter, uri_end_iter, back;
4814 gtk_text_buffer_get_iter_at_offset(
4815 buffer, &uri_start_iter, uri_start);
4816 gtk_text_buffer_get_iter_at_offset(
4817 buffer, &uri_end_iter, uri_stop);
4818 back = uri_end_iter;
4819 gtk_text_iter_backward_char(&back);
4820 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4821 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4822 gtk_text_buffer_apply_tag_by_name(
4823 buffer, "link", &uri_start_iter, &uri_end_iter);
4824 modified = TRUE;
4825 if (removed && !modified_before_remove) {
4826 modified = FALSE;
4830 if (!modified) {
4831 /* debug_print("not modified, out after %d lines\n", lines); */
4832 goto end;
4835 /* debug_print("modified, out after %d lines\n", lines); */
4836 end:
4837 g_free(itemized_chars);
4838 if (par_iter)
4839 *par_iter = iter;
4840 undo_wrapping(compose->undostruct, FALSE);
4841 compose->autowrap = prev_autowrap;
4843 return modified;
4846 void compose_action_cb(void *data)
4848 Compose *compose = (Compose *)data;
4849 compose_wrap_all(compose);
4852 static void compose_wrap_all(Compose *compose)
4854 compose_wrap_all_full(compose, FALSE);
4857 static void compose_wrap_all_full(Compose *compose, gboolean force)
4859 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4860 GtkTextBuffer *buffer;
4861 GtkTextIter iter;
4862 gboolean modified = TRUE;
4864 buffer = gtk_text_view_get_buffer(text);
4866 gtk_text_buffer_get_start_iter(buffer, &iter);
4868 undo_wrapping(compose->undostruct, TRUE);
4870 while (!gtk_text_iter_is_end(&iter) && modified)
4871 modified = compose_beautify_paragraph(compose, &iter, force);
4873 undo_wrapping(compose->undostruct, FALSE);
4877 static void compose_set_title(Compose *compose)
4879 gchar *str;
4880 gchar *edited;
4881 gchar *subject;
4883 edited = compose->modified ? _(" [Edited]") : "";
4885 subject = gtk_editable_get_chars(
4886 GTK_EDITABLE(compose->subject_entry), 0, -1);
4888 #ifndef GENERIC_UMPC
4889 if (subject && strlen(subject))
4890 str = g_strdup_printf(_("%s - Compose message%s"),
4891 subject, edited);
4892 else
4893 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4894 #else
4895 str = g_strdup(_("Compose message"));
4896 #endif
4898 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4899 g_free(str);
4900 g_free(subject);
4904 * compose_current_mail_account:
4906 * Find a current mail account (the currently selected account, or the
4907 * default account, if a news account is currently selected). If a
4908 * mail account cannot be found, display an error message.
4910 * Return value: Mail account, or NULL if not found.
4912 static PrefsAccount *
4913 compose_current_mail_account(void)
4915 PrefsAccount *ac;
4917 if (cur_account && cur_account->protocol != A_NNTP)
4918 ac = cur_account;
4919 else {
4920 ac = account_get_default();
4921 if (!ac || ac->protocol == A_NNTP) {
4922 alertpanel_error(_("Account for sending mail is not specified.\n"
4923 "Please select a mail account before sending."));
4924 return NULL;
4927 return ac;
4930 #define QUOTE_IF_REQUIRED(out, str) \
4932 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4933 gchar *__tmp; \
4934 gint len; \
4936 len = strlen(str) + 3; \
4937 if ((__tmp = alloca(len)) == NULL) { \
4938 g_warning("can't allocate memory"); \
4939 g_string_free(header, TRUE); \
4940 return NULL; \
4942 g_snprintf(__tmp, len, "\"%s\"", str); \
4943 out = __tmp; \
4944 } else { \
4945 gchar *__tmp; \
4947 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4948 g_warning("can't allocate memory"); \
4949 g_string_free(header, TRUE); \
4950 return NULL; \
4951 } else \
4952 strcpy(__tmp, str); \
4954 out = __tmp; \
4958 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4960 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4961 gchar *__tmp; \
4962 gint len; \
4964 len = strlen(str) + 3; \
4965 if ((__tmp = alloca(len)) == NULL) { \
4966 g_warning("can't allocate memory"); \
4967 errret; \
4969 g_snprintf(__tmp, len, "\"%s\"", str); \
4970 out = __tmp; \
4971 } else { \
4972 gchar *__tmp; \
4974 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4975 g_warning("can't allocate memory"); \
4976 errret; \
4977 } else \
4978 strcpy(__tmp, str); \
4980 out = __tmp; \
4984 static void compose_select_account(Compose *compose, PrefsAccount *account,
4985 gboolean init)
4987 gchar *from = NULL, *header = NULL;
4988 ComposeHeaderEntry *header_entry;
4989 GtkTreeIter iter;
4991 cm_return_if_fail(account != NULL);
4993 compose->account = account;
4994 if (account->name && *account->name) {
4995 gchar *buf, *qbuf;
4996 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4997 qbuf = escape_internal_quotes(buf, '"');
4998 from = g_strdup_printf("%s <%s>",
4999 qbuf, account->address);
5000 if (qbuf != buf)
5001 g_free(qbuf);
5002 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
5003 } else {
5004 from = g_strdup_printf("<%s>",
5005 account->address);
5006 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
5009 g_free(from);
5011 compose_set_title(compose);
5013 compose_activate_privacy_system(compose, account, FALSE);
5015 if (account->default_sign && privacy_system_can_sign(compose->privacy_system) &&
5016 compose->mode != COMPOSE_REDIRECT)
5017 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
5018 else
5019 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
5020 if (account->default_encrypt && privacy_system_can_encrypt(compose->privacy_system) &&
5021 compose->mode != COMPOSE_REDIRECT)
5022 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
5023 else
5024 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
5026 if (!init && compose->mode != COMPOSE_REDIRECT) {
5027 undo_block(compose->undostruct);
5028 compose_insert_sig(compose, TRUE);
5029 undo_unblock(compose->undostruct);
5032 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
5033 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
5034 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
5035 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5037 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5038 if (account->protocol == A_NNTP) {
5039 if (!strcmp(header, _("To:")))
5040 combobox_select_by_text(
5041 GTK_COMBO_BOX(header_entry->combo),
5042 _("Newsgroups:"));
5043 } else {
5044 if (!strcmp(header, _("Newsgroups:")))
5045 combobox_select_by_text(
5046 GTK_COMBO_BOX(header_entry->combo),
5047 _("To:"));
5051 g_free(header);
5053 #ifdef USE_ENCHANT
5054 /* use account's dict info if set */
5055 if (compose->gtkaspell) {
5056 if (account->enable_default_dictionary)
5057 gtkaspell_change_dict(compose->gtkaspell,
5058 account->default_dictionary, FALSE);
5059 if (account->enable_default_alt_dictionary)
5060 gtkaspell_change_alt_dict(compose->gtkaspell,
5061 account->default_alt_dictionary);
5062 if (account->enable_default_dictionary
5063 || account->enable_default_alt_dictionary)
5064 compose_spell_menu_changed(compose);
5066 #endif
5069 gboolean compose_check_for_valid_recipient(Compose *compose) {
5070 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5071 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5072 gboolean recipient_found = FALSE;
5073 GSList *list;
5074 gchar **strptr;
5076 /* free to and newsgroup list */
5077 slist_free_strings_full(compose->to_list);
5078 compose->to_list = NULL;
5080 slist_free_strings_full(compose->newsgroup_list);
5081 compose->newsgroup_list = NULL;
5083 /* search header entries for to and newsgroup entries */
5084 for (list = compose->header_list; list; list = list->next) {
5085 gchar *header;
5086 gchar *entry;
5087 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5088 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5089 g_strstrip(entry);
5090 g_strstrip(header);
5091 if (entry[0] != '\0') {
5092 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5093 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5094 compose->to_list = address_list_append(compose->to_list, entry);
5095 recipient_found = TRUE;
5098 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5099 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5100 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5101 recipient_found = TRUE;
5105 g_free(header);
5106 g_free(entry);
5108 return recipient_found;
5111 static gboolean compose_check_for_set_recipients(Compose *compose)
5113 if (compose->account->set_autocc && compose->account->auto_cc) {
5114 gboolean found_other = FALSE;
5115 GSList *list;
5116 /* search header entries for to and newsgroup entries */
5117 for (list = compose->header_list; list; list = list->next) {
5118 gchar *entry;
5119 gchar *header;
5120 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5121 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5122 g_strstrip(entry);
5123 g_strstrip(header);
5124 if (strcmp(entry, compose->account->auto_cc)
5125 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5126 found_other = TRUE;
5127 g_free(entry);
5128 break;
5130 g_free(entry);
5131 g_free(header);
5133 if (!found_other) {
5134 AlertValue aval;
5135 gchar *text;
5136 if (compose->batch) {
5137 gtk_widget_show_all(compose->window);
5139 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5140 prefs_common_translated_header_name("Cc"));
5141 aval = alertpanel(_("Send"),
5142 text,
5143 NULL, _("_Cancel"), NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND);
5144 g_free(text);
5145 if (aval != G_ALERTALTERNATE)
5146 return FALSE;
5149 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5150 gboolean found_other = FALSE;
5151 GSList *list;
5152 /* search header entries for to and newsgroup entries */
5153 for (list = compose->header_list; list; list = list->next) {
5154 gchar *entry;
5155 gchar *header;
5156 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5157 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5158 g_strstrip(entry);
5159 g_strstrip(header);
5160 if (strcmp(entry, compose->account->auto_bcc)
5161 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5162 found_other = TRUE;
5163 g_free(entry);
5164 g_free(header);
5165 break;
5167 g_free(entry);
5168 g_free(header);
5170 if (!found_other) {
5171 AlertValue aval;
5172 gchar *text;
5173 if (compose->batch) {
5174 gtk_widget_show_all(compose->window);
5176 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5177 prefs_common_translated_header_name("Bcc"));
5178 aval = alertpanel(_("Send"),
5179 text,
5180 NULL, _("_Cancel"), NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND);
5181 g_free(text);
5182 if (aval != G_ALERTALTERNATE)
5183 return FALSE;
5186 return TRUE;
5189 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5191 const gchar *str;
5193 if (compose_check_for_valid_recipient(compose) == FALSE) {
5194 if (compose->batch) {
5195 gtk_widget_show_all(compose->window);
5197 alertpanel_error(_("Recipient is not specified."));
5198 return FALSE;
5201 if (compose_check_for_set_recipients(compose) == FALSE) {
5202 return FALSE;
5205 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5206 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5207 if (*str == '\0' && check_everything == TRUE &&
5208 compose->mode != COMPOSE_REDIRECT) {
5209 AlertValue aval;
5210 gchar *message;
5212 message = g_strdup_printf(_("Subject is empty. %s"),
5213 compose->sending?_("Send it anyway?"):
5214 _("Queue it anyway?"));
5216 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5217 NULL, _("_Cancel"), NULL, compose->sending?_("_Send"):_("_Queue"),
5218 NULL, NULL, ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5219 g_free(message);
5220 if (aval & G_ALERTDISABLE) {
5221 aval &= ~G_ALERTDISABLE;
5222 prefs_common.warn_empty_subj = FALSE;
5224 if (aval != G_ALERTALTERNATE)
5225 return FALSE;
5229 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5230 && check_everything == TRUE) {
5231 GSList *list;
5232 gint cnt = 0;
5234 /* count To and Cc recipients */
5235 for (list = compose->header_list; list; list = list->next) {
5236 gchar *header;
5237 gchar *entry;
5239 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5240 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5241 g_strstrip(header);
5242 g_strstrip(entry);
5243 if ((entry[0] != '\0') &&
5244 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5245 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5246 cnt++;
5248 g_free(header);
5249 g_free(entry);
5251 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5252 AlertValue aval;
5253 gchar *message;
5255 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5256 compose->sending?_("Send it anyway?"):
5257 _("Queue it anyway?"));
5259 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5260 NULL, _("_Cancel"), NULL, compose->sending?_("_Send"):_("_Queue"),
5261 NULL, NULL, ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5262 g_free(message);
5263 if (aval & G_ALERTDISABLE) {
5264 aval &= ~G_ALERTDISABLE;
5265 prefs_common.warn_sending_many_recipients_num = 0;
5267 if (aval != G_ALERTALTERNATE)
5268 return FALSE;
5272 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5273 return FALSE;
5275 return TRUE;
5278 static void _display_queue_error(ComposeQueueResult val)
5280 switch (val) {
5281 case COMPOSE_QUEUE_SUCCESS:
5282 break;
5283 case COMPOSE_QUEUE_ERROR_NO_MSG:
5284 alertpanel_error(_("Could not queue message."));
5285 break;
5286 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5287 alertpanel_error(_("Could not queue message:\n\n%s."),
5288 g_strerror(errno));
5289 break;
5290 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5291 alertpanel_error(_("Could not queue message for sending:\n\n"
5292 "Signature failed: %s"),
5293 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5294 break;
5295 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5296 alertpanel_error(_("Could not queue message for sending:\n\n"
5297 "Encryption failed: %s"),
5298 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5299 break;
5300 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5301 alertpanel_error(_("Could not queue message for sending:\n\n"
5302 "Charset conversion failed."));
5303 break;
5304 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5305 alertpanel_error(_("Could not queue message for sending:\n\n"
5306 "Couldn't get recipient encryption key."));
5307 break;
5308 case COMPOSE_QUEUE_SIGNING_CANCELLED:
5309 debug_print("signing cancelled\n");
5310 break;
5311 default:
5312 /* unhandled error */
5313 debug_print("oops, unhandled compose_queue() return value %d\n",
5314 val);
5315 break;
5319 gint compose_send(Compose *compose)
5321 gint msgnum;
5322 FolderItem *folder = NULL;
5323 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5324 gchar *msgpath = NULL;
5325 gboolean discard_window = FALSE;
5326 gchar *errstr = NULL;
5327 gchar *tmsgid = NULL;
5328 MainWindow *mainwin = mainwindow_get_mainwindow();
5329 gboolean queued_removed = FALSE;
5331 if (prefs_common.send_dialog_invisible
5332 || compose->batch == TRUE)
5333 discard_window = TRUE;
5335 compose_allow_user_actions (compose, FALSE);
5336 compose->sending = TRUE;
5338 if (compose_check_entries(compose, TRUE) == FALSE) {
5339 if (compose->batch) {
5340 gtk_widget_show_all(compose->window);
5342 goto bail;
5345 inc_lock();
5346 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5348 if (val != COMPOSE_QUEUE_SUCCESS) {
5349 if (compose->batch) {
5350 gtk_widget_show_all(compose->window);
5353 _display_queue_error(val);
5355 goto bail;
5358 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5359 if (discard_window) {
5360 compose->sending = FALSE;
5361 compose_close(compose);
5362 /* No more compose access in the normal codepath
5363 * after this point! */
5364 compose = NULL;
5367 if (msgnum == 0) {
5368 alertpanel_error(_("The message was queued but could not be "
5369 "sent.\nUse \"Send queued messages\" from "
5370 "the main window to retry."));
5371 if (!discard_window) {
5372 goto bail;
5374 inc_unlock();
5375 g_free(tmsgid);
5376 return -1;
5378 if (msgpath == NULL) {
5379 msgpath = folder_item_fetch_msg(folder, msgnum);
5380 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5381 g_free(msgpath);
5382 } else {
5383 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5384 claws_unlink(msgpath);
5385 g_free(msgpath);
5387 if (!discard_window) {
5388 if (val != 0) {
5389 if (!queued_removed)
5390 folder_item_remove_msg(folder, msgnum);
5391 folder_item_scan(folder);
5392 if (tmsgid) {
5393 /* make sure we delete that */
5394 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5395 if (tmp) {
5396 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5397 folder_item_remove_msg(folder, tmp->msgnum);
5398 procmsg_msginfo_free(&tmp);
5404 if (val == 0) {
5405 if (!queued_removed)
5406 folder_item_remove_msg(folder, msgnum);
5407 folder_item_scan(folder);
5408 if (tmsgid) {
5409 /* make sure we delete that */
5410 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5411 if (tmp) {
5412 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5413 folder_item_remove_msg(folder, tmp->msgnum);
5414 procmsg_msginfo_free(&tmp);
5417 if (!discard_window) {
5418 compose->sending = FALSE;
5419 compose_allow_user_actions (compose, TRUE);
5420 compose_close(compose);
5422 } else {
5423 if (errstr) {
5424 alertpanel_error_log(_("%s\nYou can try to \"Send\" again "
5425 "or queue the message with \"Send later\""), errstr);
5426 g_free(errstr);
5427 } else {
5428 alertpanel_error_log(_("The message was queued but could not be "
5429 "sent.\nUse \"Send queued messages\" from "
5430 "the main window to retry."));
5432 if (!discard_window) {
5433 goto bail;
5435 inc_unlock();
5436 g_free(tmsgid);
5437 return -1;
5439 g_free(tmsgid);
5440 inc_unlock();
5441 toolbar_main_set_sensitive(mainwin);
5442 main_window_set_menu_sensitive(mainwin);
5443 return 0;
5445 bail:
5446 inc_unlock();
5447 g_free(tmsgid);
5448 compose_allow_user_actions (compose, TRUE);
5449 compose->sending = FALSE;
5450 compose->modified = TRUE;
5451 toolbar_main_set_sensitive(mainwin);
5452 main_window_set_menu_sensitive(mainwin);
5454 return -1;
5457 static gboolean compose_use_attach(Compose *compose)
5459 GtkTreeModel *model = gtk_tree_view_get_model
5460 (GTK_TREE_VIEW(compose->attach_clist));
5461 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5464 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5465 FILE *fp)
5467 gchar buf[BUFFSIZE];
5468 gchar *str;
5469 gboolean first_to_address;
5470 gboolean first_cc_address;
5471 GSList *list;
5472 ComposeHeaderEntry *headerentry;
5473 const gchar *headerentryname;
5474 const gchar *cc_hdr;
5475 const gchar *to_hdr;
5476 gboolean err = FALSE;
5478 debug_print("Writing redirect header\n");
5480 cc_hdr = prefs_common_translated_header_name("Cc:");
5481 to_hdr = prefs_common_translated_header_name("To:");
5483 first_to_address = TRUE;
5484 for (list = compose->header_list; list; list = list->next) {
5485 headerentry = ((ComposeHeaderEntry *)list->data);
5486 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5488 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5489 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5490 Xstrdup_a(str, entstr, return -1);
5491 g_strstrip(str);
5492 if (str[0] != '\0') {
5493 compose_convert_header
5494 (compose, buf, sizeof(buf), str,
5495 strlen("Resent-To") + 2, TRUE);
5497 if (first_to_address) {
5498 err |= (fprintf(fp, "Resent-To: ") < 0);
5499 first_to_address = FALSE;
5500 } else {
5501 err |= (fprintf(fp, ",") < 0);
5503 err |= (fprintf(fp, "%s", buf) < 0);
5507 if (!first_to_address) {
5508 err |= (fprintf(fp, "\n") < 0);
5511 first_cc_address = TRUE;
5512 for (list = compose->header_list; list; list = list->next) {
5513 headerentry = ((ComposeHeaderEntry *)list->data);
5514 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5516 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5517 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5518 Xstrdup_a(str, strg, return -1);
5519 g_strstrip(str);
5520 if (str[0] != '\0') {
5521 compose_convert_header
5522 (compose, buf, sizeof(buf), str,
5523 strlen("Resent-Cc") + 2, TRUE);
5525 if (first_cc_address) {
5526 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5527 first_cc_address = FALSE;
5528 } else {
5529 err |= (fprintf(fp, ",") < 0);
5531 err |= (fprintf(fp, "%s", buf) < 0);
5535 if (!first_cc_address) {
5536 err |= (fprintf(fp, "\n") < 0);
5539 return (err ? -1:0);
5542 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5544 gchar date[RFC822_DATE_BUFFSIZE];
5545 gchar buf[BUFFSIZE];
5546 gchar *str;
5547 const gchar *entstr;
5548 /* struct utsname utsbuf; */
5549 gboolean err = FALSE;
5551 cm_return_val_if_fail(fp != NULL, -1);
5552 cm_return_val_if_fail(compose->account != NULL, -1);
5553 cm_return_val_if_fail(compose->account->address != NULL, -1);
5555 /* Resent-Date */
5556 if (prefs_common.hide_timezone)
5557 get_rfc822_date_hide_tz(date, sizeof(date));
5558 else
5559 get_rfc822_date(date, sizeof(date));
5560 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5562 /* Resent-From */
5563 if (compose->account->name && *compose->account->name) {
5564 compose_convert_header
5565 (compose, buf, sizeof(buf), compose->account->name,
5566 strlen("From: "), TRUE);
5567 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5568 buf, compose->account->address) < 0);
5569 } else
5570 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5572 /* Subject */
5573 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5574 if (*entstr != '\0') {
5575 Xstrdup_a(str, entstr, return -1);
5576 g_strstrip(str);
5577 if (*str != '\0') {
5578 compose_convert_header(compose, buf, sizeof(buf), str,
5579 strlen("Subject: "), FALSE);
5580 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5584 /* Resent-Message-ID */
5585 if (compose->account->gen_msgid) {
5586 gchar *addr = prefs_account_generate_msgid(compose->account);
5587 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5588 if (compose->msgid)
5589 g_free(compose->msgid);
5590 compose->msgid = addr;
5591 } else {
5592 compose->msgid = NULL;
5595 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5596 return -1;
5598 /* separator between header and body */
5599 err |= (claws_fputs("\n", fp) == EOF);
5601 return (err ? -1:0);
5604 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5606 FILE *fp;
5607 size_t len;
5608 gchar *buf = NULL;
5609 gchar rewrite_buf[BUFFSIZE];
5610 int i = 0;
5611 gboolean skip = FALSE;
5612 gboolean err = FALSE;
5613 gchar *not_included[]={
5614 "Return-Path:", "Delivered-To:", "Received:",
5615 "Subject:", "X-UIDL:", "AF:",
5616 "NF:", "PS:", "SRH:",
5617 "SFN:", "DSR:", "MID:",
5618 "CFG:", "PT:", "S:",
5619 "RQ:", "SSV:", "NSV:",
5620 "SSH:", "R:", "MAID:",
5621 "NAID:", "RMID:", "FMID:",
5622 "SCF:", "RRCPT:", "NG:",
5623 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5624 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5625 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5626 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5627 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5628 NULL
5630 gint ret = 0;
5632 if ((fp = claws_fopen(compose->redirect_filename, "rb")) == NULL) {
5633 FILE_OP_ERROR(compose->redirect_filename, "claws_fopen");
5634 return -1;
5637 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5638 skip = FALSE;
5639 for (i = 0; not_included[i] != NULL; i++) {
5640 if (g_ascii_strncasecmp(buf, not_included[i],
5641 strlen(not_included[i])) == 0) {
5642 skip = TRUE;
5643 break;
5646 if (skip) {
5647 g_free(buf);
5648 buf = NULL;
5649 continue;
5651 if (claws_fputs(buf, fdest) == -1) {
5652 g_free(buf);
5653 buf = NULL;
5654 goto error;
5657 if (!prefs_common.redirect_keep_from) {
5658 if (g_ascii_strncasecmp(buf, "From:",
5659 strlen("From:")) == 0) {
5660 err |= (claws_fputs(" (by way of ", fdest) == EOF);
5661 if (compose->account->name
5662 && *compose->account->name) {
5663 gchar buffer[BUFFSIZE];
5665 compose_convert_header
5666 (compose, buffer, sizeof(buffer),
5667 compose->account->name,
5668 strlen("From: "),
5669 FALSE);
5670 err |= (fprintf(fdest, "%s <%s>",
5671 buffer,
5672 compose->account->address) < 0);
5673 } else
5674 err |= (fprintf(fdest, "%s",
5675 compose->account->address) < 0);
5676 err |= (claws_fputs(")", fdest) == EOF);
5680 g_free(buf);
5681 buf = NULL;
5682 if (claws_fputs("\n", fdest) == -1)
5683 goto error;
5686 if (err)
5687 goto error;
5689 if (compose_redirect_write_headers(compose, fdest))
5690 goto error;
5692 while ((len = claws_fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5693 if (claws_fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5694 goto error;
5697 claws_fclose(fp);
5699 return 0;
5701 error:
5702 claws_fclose(fp);
5704 return -1;
5707 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5709 GtkTextBuffer *buffer;
5710 GtkTextIter start, end, tmp;
5711 gchar *chars, *tmp_enc_file = NULL, *content;
5712 gchar *buf, *msg;
5713 const gchar *out_codeset;
5714 EncodingType encoding = ENC_UNKNOWN;
5715 MimeInfo *mimemsg, *mimetext;
5716 gint line;
5717 const gchar *src_codeset = CS_INTERNAL;
5718 gchar *from_addr = NULL;
5719 gchar *from_name = NULL;
5720 FolderItem *outbox;
5722 if (action == COMPOSE_WRITE_FOR_SEND) {
5723 attach_parts = TRUE;
5725 /* We're sending the message, generate a Message-ID
5726 * if necessary. */
5727 if (compose->msgid == NULL &&
5728 compose->account->gen_msgid) {
5729 compose->msgid = prefs_account_generate_msgid(compose->account);
5733 /* create message MimeInfo */
5734 mimemsg = procmime_mimeinfo_new();
5735 mimemsg->type = MIMETYPE_MESSAGE;
5736 mimemsg->subtype = g_strdup("rfc822");
5737 mimemsg->content = MIMECONTENT_MEM;
5738 mimemsg->tmp = TRUE; /* must free content later */
5739 mimemsg->data.mem = compose_get_header(compose);
5741 /* Create text part MimeInfo */
5742 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5743 gtk_text_buffer_get_end_iter(buffer, &end);
5744 tmp = end;
5746 /* We make sure that there is a newline at the end. */
5747 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5748 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5749 if (*chars != '\n') {
5750 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5752 g_free(chars);
5755 /* get all composed text */
5756 gtk_text_buffer_get_start_iter(buffer, &start);
5757 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5759 out_codeset = conv_get_charset_str(compose->out_encoding);
5761 if (!out_codeset && is_ascii_str(chars)) {
5762 out_codeset = CS_US_ASCII;
5763 } else if (prefs_common.outgoing_fallback_to_ascii &&
5764 is_ascii_str(chars)) {
5765 out_codeset = CS_US_ASCII;
5766 encoding = ENC_7BIT;
5769 if (!out_codeset) {
5770 gchar *test_conv_global_out = NULL;
5771 gchar *test_conv_reply = NULL;
5773 /* automatic mode. be automatic. */
5774 codeconv_set_strict(TRUE);
5776 out_codeset = conv_get_outgoing_charset_str();
5777 if (out_codeset) {
5778 debug_print("trying to convert to %s\n", out_codeset);
5779 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5782 if (!test_conv_global_out && compose->orig_charset
5783 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5784 out_codeset = compose->orig_charset;
5785 debug_print("failure; trying to convert to %s\n", out_codeset);
5786 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5789 if (!test_conv_global_out && !test_conv_reply) {
5790 /* we're lost */
5791 out_codeset = CS_INTERNAL;
5792 debug_print("failure; finally using %s\n", out_codeset);
5794 g_free(test_conv_global_out);
5795 g_free(test_conv_reply);
5796 codeconv_set_strict(FALSE);
5799 if (encoding == ENC_UNKNOWN) {
5800 if (prefs_common.encoding_method == CTE_BASE64)
5801 encoding = ENC_BASE64;
5802 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5803 encoding = ENC_QUOTED_PRINTABLE;
5804 else if (prefs_common.encoding_method == CTE_8BIT)
5805 encoding = ENC_8BIT;
5806 else
5807 encoding = procmime_get_encoding_for_charset(out_codeset);
5810 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5811 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5813 if (action == COMPOSE_WRITE_FOR_SEND) {
5814 codeconv_set_strict(TRUE);
5815 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5816 codeconv_set_strict(FALSE);
5818 if (!buf) {
5819 AlertValue aval;
5821 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5822 "to the specified %s charset.\n"
5823 "Send it as %s?"), out_codeset, src_codeset);
5824 aval = alertpanel_full(_("Error"), msg, NULL, _("_Cancel"),
5825 NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND, FALSE,
5826 NULL, ALERT_ERROR);
5827 g_free(msg);
5829 if (aval != G_ALERTALTERNATE) {
5830 g_free(chars);
5831 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5832 } else {
5833 buf = chars;
5834 out_codeset = src_codeset;
5835 chars = NULL;
5838 } else {
5839 buf = chars;
5840 out_codeset = src_codeset;
5841 chars = NULL;
5843 g_free(chars);
5845 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5846 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5847 strstr(buf, "\nFrom ") != NULL) {
5848 encoding = ENC_QUOTED_PRINTABLE;
5852 mimetext = procmime_mimeinfo_new();
5853 mimetext->content = MIMECONTENT_MEM;
5854 mimetext->tmp = TRUE; /* must free content later */
5855 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5856 * and free the data, which we need later. */
5857 mimetext->data.mem = g_strdup(buf);
5858 mimetext->type = MIMETYPE_TEXT;
5859 mimetext->subtype = g_strdup("plain");
5860 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5861 g_strdup(out_codeset));
5863 /* protect trailing spaces when signing message */
5864 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5865 privacy_system_can_sign(compose->privacy_system)) {
5866 encoding = ENC_QUOTED_PRINTABLE;
5869 debug_print("main text: %" G_GSIZE_FORMAT " bytes encoded as %s in %d\n",
5870 strlen(buf), out_codeset, encoding);
5872 /* check for line length limit */
5873 if (action == COMPOSE_WRITE_FOR_SEND &&
5874 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5875 check_line_length(buf, 1000, &line) < 0) {
5876 AlertValue aval;
5878 msg = g_strdup_printf
5879 (_("Line %d exceeds the line length limit (998 bytes).\n"
5880 "The contents of the message might be broken on the way to the delivery.\n"
5881 "\n"
5882 "Send it anyway?"), line + 1);
5883 aval = alertpanel(_("Warning"), msg, NULL, _("_Cancel"), NULL, _("_OK"),
5884 NULL, NULL, ALERTFOCUS_FIRST);
5885 g_free(msg);
5886 if (aval != G_ALERTALTERNATE) {
5887 g_free(buf);
5888 return COMPOSE_QUEUE_ERROR_NO_MSG;
5892 if (encoding != ENC_UNKNOWN)
5893 procmime_encode_content(mimetext, encoding);
5895 /* append attachment parts */
5896 if (compose_use_attach(compose) && attach_parts) {
5897 MimeInfo *mimempart;
5898 gchar *boundary = NULL;
5899 mimempart = procmime_mimeinfo_new();
5900 mimempart->content = MIMECONTENT_EMPTY;
5901 mimempart->type = MIMETYPE_MULTIPART;
5902 mimempart->subtype = g_strdup("mixed");
5904 do {
5905 g_free(boundary);
5906 boundary = generate_mime_boundary(NULL);
5907 } while (strstr(buf, boundary) != NULL);
5909 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5910 boundary);
5912 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5914 g_node_append(mimempart->node, mimetext->node);
5915 g_node_append(mimemsg->node, mimempart->node);
5917 if (compose_add_attachments(compose, mimempart, action) < 0)
5918 return COMPOSE_QUEUE_ERROR_NO_MSG;
5919 } else
5920 g_node_append(mimemsg->node, mimetext->node);
5922 g_free(buf);
5924 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5925 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5926 /* extract name and address */
5927 if (strstr(spec, " <") && strstr(spec, ">")) {
5928 from_addr = g_strdup(strrchr(spec, '<')+1);
5929 *(strrchr(from_addr, '>')) = '\0';
5930 from_name = g_strdup(spec);
5931 *(strrchr(from_name, '<')) = '\0';
5932 } else {
5933 from_name = NULL;
5934 from_addr = NULL;
5936 g_free(spec);
5938 /* sign message if sending */
5939 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5940 privacy_system_can_sign(compose->privacy_system))
5941 if (!privacy_sign(compose->privacy_system, mimemsg,
5942 compose->account, from_addr)) {
5943 g_free(from_name);
5944 g_free(from_addr);
5945 if (!privacy_peek_error())
5946 return COMPOSE_QUEUE_SIGNING_CANCELLED;
5947 else
5948 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5950 g_free(from_name);
5951 g_free(from_addr);
5953 if (compose->use_encryption) {
5954 if (compose->encdata != NULL &&
5955 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5957 /* First, write an unencrypted copy and save it to outbox, if
5958 * user wants that. */
5959 if (compose->account->save_encrypted_as_clear_text) {
5960 debug_print("saving sent message unencrypted...\n");
5961 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5962 if (tmpfp) {
5963 claws_fclose(tmpfp);
5965 /* fp now points to a file with headers written,
5966 * let's make a copy. */
5967 rewind(fp);
5968 content = file_read_stream_to_str(fp);
5970 str_write_to_file(content, tmp_enc_file, TRUE);
5971 g_free(content);
5973 /* Now write the unencrypted body. */
5974 if ((tmpfp = claws_fopen(tmp_enc_file, "a")) != NULL) {
5975 procmime_write_mimeinfo(mimemsg, tmpfp);
5976 claws_fclose(tmpfp);
5978 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5979 if (!outbox)
5980 outbox = folder_get_default_outbox();
5982 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5983 claws_unlink(tmp_enc_file);
5984 } else {
5985 g_warning("can't open file '%s'", tmp_enc_file);
5987 } else {
5988 g_warning("couldn't get tempfile");
5991 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5992 debug_print("Couldn't encrypt mime structure: %s.\n",
5993 privacy_get_error());
5994 if (tmp_enc_file)
5995 g_free(tmp_enc_file);
5996 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
6000 if (tmp_enc_file)
6001 g_free(tmp_enc_file);
6003 procmime_write_mimeinfo(mimemsg, fp);
6005 procmime_mimeinfo_free_all(&mimemsg);
6007 return 0;
6010 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
6012 GtkTextBuffer *buffer;
6013 GtkTextIter start, end;
6014 FILE *fp;
6015 size_t len;
6016 gchar *chars, *tmp;
6018 if ((fp = claws_fopen(file, "wb")) == NULL) {
6019 FILE_OP_ERROR(file, "claws_fopen");
6020 return -1;
6023 /* chmod for security */
6024 if (change_file_mode_rw(fp, file) < 0) {
6025 FILE_OP_ERROR(file, "chmod");
6026 g_warning("can't change file mode");
6029 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
6030 gtk_text_buffer_get_start_iter(buffer, &start);
6031 gtk_text_buffer_get_end_iter(buffer, &end);
6032 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
6034 chars = conv_codeset_strdup
6035 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6037 g_free(tmp);
6038 if (!chars) {
6039 claws_fclose(fp);
6040 claws_unlink(file);
6041 return -1;
6043 /* write body */
6044 len = strlen(chars);
6045 if (claws_fwrite(chars, sizeof(gchar), len, fp) != len) {
6046 FILE_OP_ERROR(file, "claws_fwrite");
6047 g_free(chars);
6048 claws_fclose(fp);
6049 claws_unlink(file);
6050 return -1;
6053 g_free(chars);
6055 if (claws_safe_fclose(fp) == EOF) {
6056 FILE_OP_ERROR(file, "claws_fclose");
6057 claws_unlink(file);
6058 return -1;
6060 return 0;
6063 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6065 FolderItem *item;
6066 MsgInfo *msginfo = compose->targetinfo;
6068 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6069 if (!msginfo) return -1;
6071 if (!force && MSG_IS_LOCKED(msginfo->flags))
6072 return 0;
6074 item = msginfo->folder;
6075 cm_return_val_if_fail(item != NULL, -1);
6077 if (procmsg_msg_exist(msginfo) &&
6078 (folder_has_parent_of_type(item, F_QUEUE) ||
6079 folder_has_parent_of_type(item, F_DRAFT)
6080 || msginfo == compose->autosaved_draft)) {
6081 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6082 g_warning("can't remove the old message");
6083 return -1;
6084 } else {
6085 debug_print("removed reedit target %d\n", msginfo->msgnum);
6089 return 0;
6092 static void compose_remove_draft(Compose *compose)
6094 FolderItem *drafts;
6095 MsgInfo *msginfo = compose->targetinfo;
6096 drafts = account_get_special_folder(compose->account, F_DRAFT);
6098 if (procmsg_msg_exist(msginfo)) {
6099 folder_item_remove_msg(drafts, msginfo->msgnum);
6104 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6105 gboolean remove_reedit_target)
6107 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6110 static gboolean compose_warn_encryption(Compose *compose)
6112 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6113 AlertValue val = G_ALERTALTERNATE;
6115 if (warning == NULL)
6116 return TRUE;
6118 val = alertpanel_full(_("Encryption warning"), warning,
6119 NULL, _("_Cancel"), NULL, _("C_ontinue"), NULL, NULL,
6120 ALERTFOCUS_SECOND, TRUE, NULL, ALERT_WARNING);
6121 if (val & G_ALERTDISABLE) {
6122 val &= ~G_ALERTDISABLE;
6123 if (val == G_ALERTALTERNATE)
6124 privacy_inhibit_encrypt_warning(compose->privacy_system,
6125 TRUE);
6128 if (val == G_ALERTALTERNATE) {
6129 return TRUE;
6130 } else {
6131 return FALSE;
6135 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6136 gchar **msgpath, gboolean perform_checks,
6137 gboolean remove_reedit_target)
6139 FolderItem *queue;
6140 gchar *tmp;
6141 FILE *fp;
6142 GSList *cur;
6143 gint num;
6144 PrefsAccount *mailac = NULL, *newsac = NULL;
6145 gboolean err = FALSE;
6147 debug_print("queueing message...\n");
6148 cm_return_val_if_fail(compose->account != NULL, -1);
6150 if (compose_check_entries(compose, perform_checks) == FALSE) {
6151 if (compose->batch) {
6152 gtk_widget_show_all(compose->window);
6154 return COMPOSE_QUEUE_ERROR_NO_MSG;
6157 if (!compose->to_list && !compose->newsgroup_list) {
6158 g_warning("can't get recipient list");
6159 return COMPOSE_QUEUE_ERROR_NO_MSG;
6162 if (compose->to_list) {
6163 mailac = compose->account;
6164 if (!mailac && cur_account && cur_account->protocol != A_NNTP)
6165 mailac = cur_account;
6166 else if (!mailac && !(mailac = compose_current_mail_account())) {
6167 alertpanel_error(_("No account for sending mails available!"));
6168 return COMPOSE_QUEUE_ERROR_NO_MSG;
6172 if (compose->newsgroup_list) {
6173 if (compose->account->protocol == A_NNTP)
6174 newsac = compose->account;
6175 else {
6176 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6177 return COMPOSE_QUEUE_ERROR_NO_MSG;
6181 /* write queue header */
6182 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6183 G_DIR_SEPARATOR, compose, (guint) rand());
6184 debug_print("queuing to %s\n", tmp);
6185 if ((fp = claws_fopen(tmp, "w+b")) == NULL) {
6186 FILE_OP_ERROR(tmp, "claws_fopen");
6187 g_free(tmp);
6188 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6191 if (change_file_mode_rw(fp, tmp) < 0) {
6192 FILE_OP_ERROR(tmp, "chmod");
6193 g_warning("can't change file mode");
6196 /* queueing variables */
6197 err |= (fprintf(fp, "AF:\n") < 0);
6198 err |= (fprintf(fp, "NF:0\n") < 0);
6199 err |= (fprintf(fp, "PS:10\n") < 0);
6200 err |= (fprintf(fp, "SRH:1\n") < 0);
6201 err |= (fprintf(fp, "SFN:\n") < 0);
6202 err |= (fprintf(fp, "DSR:\n") < 0);
6203 if (compose->msgid)
6204 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6205 else
6206 err |= (fprintf(fp, "MID:\n") < 0);
6207 err |= (fprintf(fp, "CFG:\n") < 0);
6208 err |= (fprintf(fp, "PT:0\n") < 0);
6209 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6210 err |= (fprintf(fp, "RQ:\n") < 0);
6211 if (mailac)
6212 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6213 else
6214 err |= (fprintf(fp, "SSV:\n") < 0);
6215 if (newsac)
6216 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6217 else
6218 err |= (fprintf(fp, "NSV:\n") < 0);
6219 err |= (fprintf(fp, "SSH:\n") < 0);
6220 /* write recipient list */
6221 if (compose->to_list) {
6222 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6223 for (cur = compose->to_list->next; cur != NULL;
6224 cur = cur->next)
6225 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6226 err |= (fprintf(fp, "\n") < 0);
6228 /* write newsgroup list */
6229 if (compose->newsgroup_list) {
6230 err |= (fprintf(fp, "NG:") < 0);
6231 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6232 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6233 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6234 err |= (fprintf(fp, "\n") < 0);
6236 /* account IDs */
6237 if (mailac)
6238 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6239 if (newsac)
6240 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6243 if (compose->privacy_system != NULL) {
6244 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6245 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6246 if (compose->use_encryption) {
6247 if (!compose_warn_encryption(compose)) {
6248 claws_fclose(fp);
6249 claws_unlink(tmp);
6250 g_free(tmp);
6251 return COMPOSE_QUEUE_ERROR_NO_MSG;
6253 if (mailac && mailac->encrypt_to_self) {
6254 GSList *tmp_list = g_slist_copy(compose->to_list);
6255 tmp_list = g_slist_append(tmp_list, compose->account->address);
6256 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6257 g_slist_free(tmp_list);
6258 } else {
6259 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6261 if (compose->encdata != NULL) {
6262 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6263 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6264 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6265 compose->encdata) < 0);
6266 } /* else we finally dont want to encrypt */
6267 } else {
6268 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6269 /* and if encdata was null, it means there's been a problem in
6270 * key selection */
6271 if (err == TRUE)
6272 g_warning("failed to write queue message");
6273 claws_fclose(fp);
6274 claws_unlink(tmp);
6275 g_free(tmp);
6276 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6281 /* Save copy folder */
6282 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6283 gchar *savefolderid;
6285 savefolderid = compose_get_save_to(compose);
6286 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6287 g_free(savefolderid);
6289 /* Save copy folder */
6290 if (compose->return_receipt) {
6291 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6293 /* Message-ID of message replying to */
6294 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6295 gchar *folderid = NULL;
6297 if (compose->replyinfo->folder)
6298 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6299 if (folderid == NULL)
6300 folderid = g_strdup("NULL");
6302 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6303 g_free(folderid);
6305 /* Message-ID of message forwarding to */
6306 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6307 gchar *folderid = NULL;
6309 if (compose->fwdinfo->folder)
6310 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6311 if (folderid == NULL)
6312 folderid = g_strdup("NULL");
6314 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6315 g_free(folderid);
6318 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6319 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6321 /* end of headers */
6322 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6324 if (compose->redirect_filename != NULL) {
6325 if (compose_redirect_write_to_file(compose, fp) < 0) {
6326 claws_fclose(fp);
6327 claws_unlink(tmp);
6328 g_free(tmp);
6329 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6331 } else {
6332 gint result = 0;
6333 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6334 claws_fclose(fp);
6335 claws_unlink(tmp);
6336 g_free(tmp);
6337 return result;
6340 if (err == TRUE) {
6341 g_warning("failed to write queue message");
6342 claws_fclose(fp);
6343 claws_unlink(tmp);
6344 g_free(tmp);
6345 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6347 if (claws_safe_fclose(fp) == EOF) {
6348 FILE_OP_ERROR(tmp, "claws_fclose");
6349 claws_unlink(tmp);
6350 g_free(tmp);
6351 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6354 if (item && *item) {
6355 queue = *item;
6356 } else {
6357 queue = account_get_special_folder(compose->account, F_QUEUE);
6359 if (!queue) {
6360 g_warning("can't find queue folder");
6361 claws_unlink(tmp);
6362 g_free(tmp);
6363 return COMPOSE_QUEUE_ERROR_NO_MSG;
6365 folder_item_scan(queue);
6366 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6367 g_warning("can't queue the message");
6368 claws_unlink(tmp);
6369 g_free(tmp);
6370 return COMPOSE_QUEUE_ERROR_NO_MSG;
6373 if (msgpath == NULL) {
6374 claws_unlink(tmp);
6375 g_free(tmp);
6376 } else
6377 *msgpath = tmp;
6379 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6380 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6381 if (mi) {
6382 procmsg_msginfo_change_flags(mi,
6383 compose->targetinfo->flags.perm_flags,
6384 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6385 0, 0);
6387 g_slist_free(mi->tags);
6388 mi->tags = g_slist_copy(compose->targetinfo->tags);
6389 procmsg_msginfo_free(&mi);
6393 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6394 compose_remove_reedit_target(compose, FALSE);
6397 if ((msgnum != NULL) && (item != NULL)) {
6398 *msgnum = num;
6399 *item = queue;
6402 return COMPOSE_QUEUE_SUCCESS;
6405 static int compose_add_attachments(Compose *compose, MimeInfo *parent, gint action)
6407 AttachInfo *ainfo;
6408 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6409 MimeInfo *mimepart;
6410 #ifdef G_OS_WIN32
6411 GFile *f;
6412 GFileInfo *fi;
6413 GError *error = NULL;
6414 #else
6415 GStatBuf statbuf;
6416 #endif
6417 goffset size;
6418 gchar *type, *subtype;
6419 GtkTreeModel *model;
6420 GtkTreeIter iter;
6422 model = gtk_tree_view_get_model(tree_view);
6424 if (!gtk_tree_model_get_iter_first(model, &iter))
6425 return 0;
6426 do {
6427 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6429 if (!is_file_exist(ainfo->file)) {
6430 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6431 AlertValue val = alertpanel_full(_("Warning"), msg, NULL,
6432 action == COMPOSE_WRITE_FOR_STORE? _("Cancel drafting"): _("Cancel sending"),
6433 NULL, _("Ignore attachment"), NULL, NULL,
6434 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6435 g_free(msg);
6436 if (val == G_ALERTDEFAULT) {
6437 return -1;
6439 continue;
6441 #ifdef G_OS_WIN32
6442 f = g_file_new_for_path(ainfo->file);
6443 fi = g_file_query_info(f, "standard::size",
6444 G_FILE_QUERY_INFO_NONE, NULL, &error);
6445 if (error != NULL) {
6446 g_warning(error->message);
6447 g_error_free(error);
6448 g_object_unref(f);
6449 return -1;
6451 size = g_file_info_get_size(fi);
6452 g_object_unref(fi);
6453 g_object_unref(f);
6454 #else
6455 if (g_stat(ainfo->file, &statbuf) < 0)
6456 return -1;
6457 size = statbuf.st_size;
6458 #endif
6460 mimepart = procmime_mimeinfo_new();
6461 mimepart->content = MIMECONTENT_FILE;
6462 mimepart->data.filename = g_strdup(ainfo->file);
6463 mimepart->tmp = FALSE; /* or we destroy our attachment */
6464 mimepart->offset = 0;
6465 mimepart->length = size;
6467 type = g_strdup(ainfo->content_type);
6469 if (!strchr(type, '/')) {
6470 g_free(type);
6471 type = g_strdup("application/octet-stream");
6474 subtype = strchr(type, '/') + 1;
6475 *(subtype - 1) = '\0';
6476 mimepart->type = procmime_get_media_type(type);
6477 mimepart->subtype = g_strdup(subtype);
6478 g_free(type);
6480 if (mimepart->type == MIMETYPE_MESSAGE &&
6481 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6482 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6483 } else if (mimepart->type == MIMETYPE_TEXT) {
6484 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6485 /* Text parts with no name come from multipart/alternative
6486 * forwards. Make sure the recipient won't look at the
6487 * original HTML part by mistake. */
6488 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6489 ainfo->name = g_strdup_printf(_("Original %s part"),
6490 mimepart->subtype);
6492 if (ainfo->charset)
6493 g_hash_table_insert(mimepart->typeparameters,
6494 g_strdup("charset"), g_strdup(ainfo->charset));
6496 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6497 if (mimepart->type == MIMETYPE_APPLICATION &&
6498 !g_strcmp0(mimepart->subtype, "octet-stream"))
6499 g_hash_table_insert(mimepart->typeparameters,
6500 g_strdup("name"), g_strdup(ainfo->name));
6501 g_hash_table_insert(mimepart->dispositionparameters,
6502 g_strdup("filename"), g_strdup(ainfo->name));
6503 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6506 if (mimepart->type == MIMETYPE_MESSAGE
6507 || mimepart->type == MIMETYPE_MULTIPART)
6508 ainfo->encoding = ENC_BINARY;
6509 else if (compose->use_signing || compose->fwdinfo != NULL) {
6510 if (ainfo->encoding == ENC_7BIT)
6511 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6512 else if (ainfo->encoding == ENC_8BIT)
6513 ainfo->encoding = ENC_BASE64;
6516 procmime_encode_content(mimepart, ainfo->encoding);
6518 g_node_append(parent->node, mimepart->node);
6519 } while (gtk_tree_model_iter_next(model, &iter));
6521 return 0;
6524 static gchar *compose_quote_list_of_addresses(gchar *str)
6526 GSList *list = NULL, *item = NULL;
6527 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6529 list = address_list_append_with_comments(list, str);
6530 for (item = list; item != NULL; item = item->next) {
6531 gchar *spec = item->data;
6532 gchar *endofname = strstr(spec, " <");
6533 if (endofname != NULL) {
6534 gchar * qqname;
6535 *endofname = '\0';
6536 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6537 qqname = escape_internal_quotes(qname, '"');
6538 *endofname = ' ';
6539 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6540 gchar *addr = g_strdup(endofname);
6541 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6542 faddr = g_strconcat(name, addr, NULL);
6543 g_free(name);
6544 g_free(addr);
6545 debug_print("new auto-quoted address: '%s'\n", faddr);
6548 if (result == NULL)
6549 result = g_strdup((faddr != NULL)? faddr: spec);
6550 else {
6551 gchar *tmp = g_strconcat(result,
6552 ", ",
6553 (faddr != NULL)? faddr: spec,
6554 NULL);
6555 g_free(result);
6556 result = tmp;
6558 if (faddr != NULL) {
6559 g_free(faddr);
6560 faddr = NULL;
6563 slist_free_strings_full(list);
6565 return result;
6568 #define IS_IN_CUSTOM_HEADER(header) \
6569 (compose->account->add_customhdr && \
6570 custom_header_find(compose->account->customhdr_list, header) != NULL)
6572 static const gchar *compose_untranslated_header_name(gchar *header_name)
6574 /* return the untranslated header name, if header_name is a known
6575 header name, in either its translated or untranslated form, with
6576 or without trailing colon. otherwise, returns header_name. */
6577 gchar *translated_header_name;
6578 gchar *translated_header_name_wcolon;
6579 const gchar *untranslated_header_name;
6580 const gchar *untranslated_header_name_wcolon;
6581 gint i;
6583 cm_return_val_if_fail(header_name != NULL, NULL);
6585 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6586 untranslated_header_name = HEADERS[i].header_name;
6587 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6589 translated_header_name = gettext(untranslated_header_name);
6590 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6592 if (!strcmp(header_name, untranslated_header_name) ||
6593 !strcmp(header_name, translated_header_name)) {
6594 return untranslated_header_name;
6595 } else {
6596 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6597 !strcmp(header_name, translated_header_name_wcolon)) {
6598 return untranslated_header_name_wcolon;
6602 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6603 return header_name;
6606 static void compose_add_headerfield_from_headerlist(Compose *compose,
6607 GString *header,
6608 const gchar *fieldname,
6609 const gchar *seperator)
6611 gchar *str, *fieldname_w_colon;
6612 gboolean add_field = FALSE;
6613 GSList *list;
6614 ComposeHeaderEntry *headerentry;
6615 const gchar *headerentryname;
6616 const gchar *trans_fieldname;
6617 GString *fieldstr;
6619 if (IS_IN_CUSTOM_HEADER(fieldname))
6620 return;
6622 debug_print("Adding %s-fields\n", fieldname);
6624 fieldstr = g_string_sized_new(64);
6626 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6627 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6629 for (list = compose->header_list; list; list = list->next) {
6630 headerentry = ((ComposeHeaderEntry *)list->data);
6631 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6633 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6634 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6635 g_strstrip(ustr);
6636 str = compose_quote_list_of_addresses(ustr);
6637 g_free(ustr);
6638 if (str != NULL && str[0] != '\0') {
6639 if (add_field)
6640 g_string_append(fieldstr, seperator);
6641 g_string_append(fieldstr, str);
6642 add_field = TRUE;
6644 g_free(str);
6647 if (add_field) {
6648 gchar *buf;
6650 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6651 compose_convert_header
6652 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6653 strlen(fieldname) + 2, TRUE);
6654 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6655 g_free(buf);
6658 g_free(fieldname_w_colon);
6659 g_string_free(fieldstr, TRUE);
6661 return;
6664 static gchar *compose_get_manual_headers_info(Compose *compose)
6666 GString *sh_header = g_string_new(" ");
6667 GSList *list;
6668 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6670 for (list = compose->header_list; list; list = list->next) {
6671 ComposeHeaderEntry *headerentry;
6672 gchar *tmp;
6673 gchar *headername;
6674 gchar *headername_wcolon;
6675 const gchar *headername_trans;
6676 gchar **string;
6677 gboolean standard_header = FALSE;
6679 headerentry = ((ComposeHeaderEntry *)list->data);
6681 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6682 g_strstrip(tmp);
6683 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6684 g_free(tmp);
6685 continue;
6688 if (!strstr(tmp, ":")) {
6689 headername_wcolon = g_strconcat(tmp, ":", NULL);
6690 headername = g_strdup(tmp);
6691 } else {
6692 headername_wcolon = g_strdup(tmp);
6693 headername = g_strdup(strtok(tmp, ":"));
6695 g_free(tmp);
6697 string = std_headers;
6698 while (*string != NULL) {
6699 headername_trans = prefs_common_translated_header_name(*string);
6700 if (!strcmp(headername_trans, headername_wcolon))
6701 standard_header = TRUE;
6702 string++;
6704 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6705 g_string_append_printf(sh_header, "%s ", headername);
6706 g_free(headername);
6707 g_free(headername_wcolon);
6709 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6710 return g_string_free(sh_header, FALSE);
6713 static gchar *compose_get_header(Compose *compose)
6715 gchar date[RFC822_DATE_BUFFSIZE];
6716 gchar buf[BUFFSIZE];
6717 const gchar *entry_str;
6718 gchar *str;
6719 gchar *name;
6720 GSList *list;
6721 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6722 GString *header;
6723 gchar *from_name = NULL, *from_address = NULL;
6724 gchar *tmp;
6726 cm_return_val_if_fail(compose->account != NULL, NULL);
6727 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6729 header = g_string_sized_new(64);
6731 /* Date */
6732 if (prefs_common.hide_timezone)
6733 get_rfc822_date_hide_tz(date, sizeof(date));
6734 else
6735 get_rfc822_date(date, sizeof(date));
6736 g_string_append_printf(header, "Date: %s\n", date);
6738 /* From */
6740 if (compose->account->name && *compose->account->name) {
6741 gchar *buf;
6742 QUOTE_IF_REQUIRED(buf, compose->account->name);
6743 tmp = g_strdup_printf("%s <%s>",
6744 buf, compose->account->address);
6745 } else {
6746 tmp = g_strdup_printf("%s",
6747 compose->account->address);
6749 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6750 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6751 /* use default */
6752 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6753 from_address = g_strdup(compose->account->address);
6754 } else {
6755 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6756 /* extract name and address */
6757 if (strstr(spec, " <") && strstr(spec, ">")) {
6758 from_address = g_strdup(strrchr(spec, '<')+1);
6759 *(strrchr(from_address, '>')) = '\0';
6760 from_name = g_strdup(spec);
6761 *(strrchr(from_name, '<')) = '\0';
6762 } else {
6763 from_name = NULL;
6764 from_address = g_strdup(spec);
6766 g_free(spec);
6768 g_free(tmp);
6771 if (from_name && *from_name) {
6772 gchar *qname;
6773 compose_convert_header
6774 (compose, buf, sizeof(buf), from_name,
6775 strlen("From: "), TRUE);
6776 QUOTE_IF_REQUIRED(name, buf);
6777 qname = escape_internal_quotes(name, '"');
6779 g_string_append_printf(header, "From: %s <%s>\n",
6780 qname, from_address);
6781 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6782 compose->return_receipt) {
6783 compose_convert_header(compose, buf, sizeof(buf), from_name,
6784 strlen("Disposition-Notification-To: "),
6785 TRUE);
6786 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6788 if (qname != name)
6789 g_free(qname);
6790 } else {
6791 g_string_append_printf(header, "From: %s\n", from_address);
6792 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6793 compose->return_receipt)
6794 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6797 g_free(from_name);
6798 g_free(from_address);
6800 /* To */
6801 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6803 /* Newsgroups */
6804 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6806 /* Cc */
6807 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6809 /* Bcc */
6811 * If this account is a NNTP account remove Bcc header from
6812 * message body since it otherwise will be publicly shown
6814 if (compose->account->protocol != A_NNTP)
6815 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6817 /* Subject */
6818 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6820 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6821 g_strstrip(str);
6822 if (*str != '\0') {
6823 compose_convert_header(compose, buf, sizeof(buf), str,
6824 strlen("Subject: "), FALSE);
6825 g_string_append_printf(header, "Subject: %s\n", buf);
6828 g_free(str);
6830 /* Message-ID */
6831 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6832 g_string_append_printf(header, "Message-ID: <%s>\n",
6833 compose->msgid);
6836 if (compose->remove_references == FALSE) {
6837 /* In-Reply-To */
6838 if (compose->inreplyto && compose->to_list)
6839 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6841 /* References */
6842 if (compose->references)
6843 g_string_append_printf(header, "References: %s\n", compose->references);
6846 /* Followup-To */
6847 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6849 /* Reply-To */
6850 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6852 /* Organization */
6853 if (compose->account->organization &&
6854 strlen(compose->account->organization) &&
6855 !IS_IN_CUSTOM_HEADER("Organization")) {
6856 compose_convert_header(compose, buf, sizeof(buf),
6857 compose->account->organization,
6858 strlen("Organization: "), FALSE);
6859 g_string_append_printf(header, "Organization: %s\n", buf);
6862 /* Program version and system info */
6863 if (compose->account->gen_xmailer &&
6864 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6865 !compose->newsgroup_list) {
6866 g_string_append_printf(header, "X-Mailer: %s (GTK %d.%d.%d; %s)\n",
6867 prog_version,
6868 gtk_major_version, gtk_minor_version, gtk_micro_version,
6869 TARGET_ALIAS);
6871 if (compose->account->gen_xmailer &&
6872 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6873 g_string_append_printf(header, "X-Newsreader: %s (GTK %d.%d.%d; %s)\n",
6874 prog_version,
6875 gtk_major_version, gtk_minor_version, gtk_micro_version,
6876 TARGET_ALIAS);
6879 /* custom headers */
6880 if (compose->account->add_customhdr) {
6881 GSList *cur;
6883 for (cur = compose->account->customhdr_list; cur != NULL;
6884 cur = cur->next) {
6885 CustomHeader *chdr = (CustomHeader *)cur->data;
6887 if (custom_header_is_allowed(chdr->name)
6888 && chdr->value != NULL
6889 && *(chdr->value) != '\0') {
6890 compose_convert_header
6891 (compose, buf, sizeof(buf),
6892 chdr->value,
6893 strlen(chdr->name) + 2, FALSE);
6894 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6899 /* Automatic Faces and X-Faces */
6900 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6901 g_string_append_printf(header, "X-Face: %s\n", buf);
6903 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6904 g_string_append_printf(header, "X-Face: %s\n", buf);
6906 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6907 g_string_append_printf(header, "Face: %s\n", buf);
6909 else if (get_default_face (buf, sizeof(buf)) == 0) {
6910 g_string_append_printf(header, "Face: %s\n", buf);
6913 /* PRIORITY */
6914 switch (compose->priority) {
6915 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6916 "X-Priority: 1 (Highest)\n");
6917 break;
6918 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6919 "X-Priority: 2 (High)\n");
6920 break;
6921 case PRIORITY_NORMAL: break;
6922 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6923 "X-Priority: 4 (Low)\n");
6924 break;
6925 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6926 "X-Priority: 5 (Lowest)\n");
6927 break;
6928 default: debug_print("compose: priority unknown : %d\n",
6929 compose->priority);
6932 /* get special headers */
6933 for (list = compose->header_list; list; list = list->next) {
6934 ComposeHeaderEntry *headerentry;
6935 gchar *tmp;
6936 gchar *headername;
6937 gchar *headername_wcolon;
6938 const gchar *headername_trans;
6939 gchar *headervalue;
6940 gchar **string;
6941 gboolean standard_header = FALSE;
6943 headerentry = ((ComposeHeaderEntry *)list->data);
6945 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6946 g_strstrip(tmp);
6947 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6948 g_free(tmp);
6949 continue;
6952 if (!strstr(tmp, ":")) {
6953 headername_wcolon = g_strconcat(tmp, ":", NULL);
6954 headername = g_strdup(tmp);
6955 } else {
6956 headername_wcolon = g_strdup(tmp);
6957 headername = g_strdup(strtok(tmp, ":"));
6959 g_free(tmp);
6961 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6962 Xstrdup_a(headervalue, entry_str, {
6963 g_free(headername);
6964 g_free(headername_wcolon);
6965 g_string_free(header, TRUE);
6966 return NULL;
6968 subst_char(headervalue, '\r', ' ');
6969 subst_char(headervalue, '\n', ' ');
6970 g_strstrip(headervalue);
6971 if (*headervalue != '\0') {
6972 string = std_headers;
6973 while (*string != NULL && !standard_header) {
6974 headername_trans = prefs_common_translated_header_name(*string);
6975 /* support mixed translated and untranslated headers */
6976 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6977 standard_header = TRUE;
6978 string++;
6980 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6981 /* store untranslated header name */
6982 g_string_append_printf(header, "%s %s\n",
6983 compose_untranslated_header_name(headername_wcolon), headervalue);
6986 g_free(headername);
6987 g_free(headername_wcolon);
6990 str = header->str;
6991 g_string_free(header, FALSE);
6993 return str;
6996 #undef IS_IN_CUSTOM_HEADER
6998 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6999 gint header_len, gboolean addr_field)
7001 gchar *tmpstr = NULL;
7002 const gchar *out_codeset = NULL;
7004 cm_return_if_fail(src != NULL);
7005 cm_return_if_fail(dest != NULL);
7007 if (len < 1) return;
7009 tmpstr = g_strdup(src);
7011 subst_char(tmpstr, '\n', ' ');
7012 subst_char(tmpstr, '\r', ' ');
7013 g_strchomp(tmpstr);
7015 if (!g_utf8_validate(tmpstr, -1, NULL)) {
7016 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
7017 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
7018 g_free(tmpstr);
7019 tmpstr = mybuf;
7022 codeconv_set_strict(TRUE);
7023 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7024 conv_get_charset_str(compose->out_encoding));
7025 codeconv_set_strict(FALSE);
7027 if (!dest || *dest == '\0') {
7028 gchar *test_conv_global_out = NULL;
7029 gchar *test_conv_reply = NULL;
7031 /* automatic mode. be automatic. */
7032 codeconv_set_strict(TRUE);
7034 out_codeset = conv_get_outgoing_charset_str();
7035 if (out_codeset) {
7036 debug_print("trying to convert to %s\n", out_codeset);
7037 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7040 if (!test_conv_global_out && compose->orig_charset
7041 && strcmp(compose->orig_charset, CS_US_ASCII)) {
7042 out_codeset = compose->orig_charset;
7043 debug_print("failure; trying to convert to %s\n", out_codeset);
7044 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7047 if (!test_conv_global_out && !test_conv_reply) {
7048 /* we're lost */
7049 out_codeset = CS_INTERNAL;
7050 debug_print("finally using %s\n", out_codeset);
7052 g_free(test_conv_global_out);
7053 g_free(test_conv_reply);
7054 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7055 out_codeset);
7056 codeconv_set_strict(FALSE);
7058 g_free(tmpstr);
7061 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7063 gchar *address;
7065 cm_return_if_fail(user_data != NULL);
7067 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7068 g_strstrip(address);
7069 if (*address != '\0') {
7070 gchar *name = procheader_get_fromname(address);
7071 extract_address(address);
7072 #ifndef USE_ALT_ADDRBOOK
7073 addressbook_add_contact(name, address, NULL, NULL);
7074 #else
7075 debug_print("%s: %s\n", name, address);
7076 if (addressadd_selection(name, address, NULL, NULL)) {
7077 debug_print( "addressbook_add_contact - added\n" );
7079 #endif
7081 g_free(address);
7084 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7086 GtkWidget *menuitem;
7087 gchar *address;
7089 cm_return_if_fail(menu != NULL);
7090 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7092 menuitem = gtk_separator_menu_item_new();
7093 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7094 gtk_widget_show(menuitem);
7096 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7097 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7099 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7100 g_strstrip(address);
7101 if (*address == '\0') {
7102 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7105 g_signal_connect(G_OBJECT(menuitem), "activate",
7106 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7107 gtk_widget_show(menuitem);
7110 void compose_add_extra_header(gchar *header, GtkListStore *model)
7112 GtkTreeIter iter;
7113 if (strcmp(header, "")) {
7114 COMBOBOX_ADD(model, header, COMPOSE_TO);
7118 void compose_add_extra_header_entries(GtkListStore *model)
7120 FILE *exh;
7121 gchar *exhrc;
7122 gchar buf[BUFFSIZE];
7123 gint lastc;
7125 if (extra_headers == NULL) {
7126 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7127 if ((exh = claws_fopen(exhrc, "rb")) == NULL) {
7128 debug_print("extra headers file not found\n");
7129 goto extra_headers_done;
7131 while (claws_fgets(buf, BUFFSIZE, exh) != NULL) {
7132 lastc = strlen(buf) - 1; /* remove trailing control chars */
7133 while (lastc >= 0 && buf[lastc] != ':')
7134 buf[lastc--] = '\0';
7135 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7136 buf[lastc] = '\0'; /* remove trailing : for comparison */
7137 if (custom_header_is_allowed(buf)) {
7138 buf[lastc] = ':';
7139 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7141 else
7142 g_message("disallowed extra header line: %s\n", buf);
7144 else {
7145 if (buf[0] != '#')
7146 g_message("invalid extra header line: %s\n", buf);
7149 claws_fclose(exh);
7150 extra_headers_done:
7151 g_free(exhrc);
7152 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7153 extra_headers = g_slist_reverse(extra_headers);
7155 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7158 #ifdef USE_LDAP
7159 static void _ldap_srv_func(gpointer data, gpointer user_data)
7161 LdapServer *server = (LdapServer *)data;
7162 gboolean *enable = (gboolean *)user_data;
7164 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7165 server->searchFlag = *enable;
7167 #endif
7169 static void compose_create_header_entry(Compose *compose)
7171 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7173 GtkWidget *combo;
7174 GtkWidget *entry;
7175 GtkWidget *button;
7176 GtkWidget *hbox;
7177 gchar **string;
7178 const gchar *header = NULL;
7179 ComposeHeaderEntry *headerentry;
7180 gboolean standard_header = FALSE;
7181 GtkListStore *model;
7182 GtkTreeIter iter;
7184 headerentry = g_new0(ComposeHeaderEntry, 1);
7186 /* Combo box model */
7187 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7188 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7189 COMPOSE_TO);
7190 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7191 COMPOSE_CC);
7192 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7193 COMPOSE_BCC);
7194 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7195 COMPOSE_NEWSGROUPS);
7196 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7197 COMPOSE_REPLYTO);
7198 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7199 COMPOSE_FOLLOWUPTO);
7200 compose_add_extra_header_entries(model);
7202 /* Combo box */
7203 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7204 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7205 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7206 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7207 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7208 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7209 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7210 G_CALLBACK(compose_grab_focus_cb), compose);
7211 gtk_widget_show(combo);
7213 gtk_grid_attach(GTK_GRID(compose->header_table), combo, 0, compose->header_nextrow,
7214 1, 1);
7215 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7216 const gchar *last_header_entry = gtk_entry_get_text(
7217 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7218 string = headers;
7219 while (*string != NULL) {
7220 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7221 standard_header = TRUE;
7222 string++;
7224 if (standard_header)
7225 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7227 if (!compose->header_last || !standard_header) {
7228 switch(compose->account->protocol) {
7229 case A_NNTP:
7230 header = prefs_common_translated_header_name("Newsgroups:");
7231 break;
7232 default:
7233 header = prefs_common_translated_header_name("To:");
7234 break;
7237 if (header)
7238 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7240 gtk_editable_set_editable(
7241 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7242 prefs_common.type_any_header);
7244 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7245 G_CALLBACK(compose_grab_focus_cb), compose);
7247 /* Entry field with cleanup button */
7248 button = gtk_button_new_from_icon_name("edit-clear", GTK_ICON_SIZE_MENU);
7249 gtk_widget_show(button);
7250 CLAWS_SET_TIP(button,
7251 _("Delete entry contents"));
7252 entry = gtk_entry_new();
7253 gtk_widget_show(entry);
7254 CLAWS_SET_TIP(entry,
7255 _("Use <tab> to autocomplete from addressbook"));
7256 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
7257 gtk_widget_show(hbox);
7258 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7259 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7260 gtk_grid_attach(GTK_GRID(compose->header_table), hbox, 1, compose->header_nextrow,
7261 1, 1);
7262 gtk_widget_set_hexpand(hbox, TRUE);
7263 gtk_widget_set_halign(hbox, GTK_ALIGN_FILL);
7265 g_signal_connect(G_OBJECT(entry), "key-press-event",
7266 G_CALLBACK(compose_headerentry_key_press_event_cb),
7267 headerentry);
7268 g_signal_connect(G_OBJECT(entry), "changed",
7269 G_CALLBACK(compose_headerentry_changed_cb),
7270 headerentry);
7271 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7272 G_CALLBACK(compose_grab_focus_cb), compose);
7274 g_signal_connect(G_OBJECT(button), "clicked",
7275 G_CALLBACK(compose_headerentry_button_clicked_cb),
7276 headerentry);
7278 /* email dnd */
7279 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7280 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7281 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7282 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7283 G_CALLBACK(compose_header_drag_received_cb),
7284 entry);
7285 g_signal_connect(G_OBJECT(entry), "drag-drop",
7286 G_CALLBACK(compose_drag_drop),
7287 compose);
7288 g_signal_connect(G_OBJECT(entry), "populate-popup",
7289 G_CALLBACK(compose_entry_popup_extend),
7290 NULL);
7292 #ifdef USE_LDAP
7293 #ifndef PASSWORD_CRYPTO_OLD
7294 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7295 if (pwd_servers != NULL && primary_passphrase() == NULL) {
7296 gboolean enable = FALSE;
7297 debug_print("Primary passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7298 /* Temporarily disable password-protected LDAP servers,
7299 * because user did not provide a primary passphrase.
7300 * We can safely enable searchFlag on all servers in this list
7301 * later, since addrindex_get_password_protected_ldap_servers()
7302 * includes servers which have it enabled initially. */
7303 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7304 compose->passworded_ldap_servers = pwd_servers;
7306 #endif /* PASSWORD_CRYPTO_OLD */
7307 #endif /* USE_LDAP */
7309 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7311 headerentry->compose = compose;
7312 headerentry->combo = combo;
7313 headerentry->entry = entry;
7314 headerentry->button = button;
7315 headerentry->hbox = hbox;
7316 headerentry->headernum = compose->header_nextrow;
7317 headerentry->type = PREF_NONE;
7319 compose->header_nextrow++;
7320 compose->header_last = headerentry;
7321 compose->header_list =
7322 g_slist_append(compose->header_list,
7323 headerentry);
7326 static void compose_add_header_entry(Compose *compose, const gchar *header,
7327 gchar *text, ComposePrefType pref_type)
7329 ComposeHeaderEntry *last_header = compose->header_last;
7330 gchar *tmp = g_strdup(text), *email;
7331 gboolean replyto_hdr;
7333 replyto_hdr = (!strcasecmp(header,
7334 prefs_common_translated_header_name("Reply-To:")) ||
7335 !strcasecmp(header,
7336 prefs_common_translated_header_name("Followup-To:")) ||
7337 !strcasecmp(header,
7338 prefs_common_translated_header_name("In-Reply-To:")));
7340 extract_address(tmp);
7341 email = g_utf8_strdown(tmp, -1);
7343 if (replyto_hdr == FALSE &&
7344 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7346 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7347 header, text, (gint) pref_type);
7348 g_free(email);
7349 g_free(tmp);
7350 return;
7353 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7354 gtk_entry_set_text(GTK_ENTRY(
7355 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7356 else
7357 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7358 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7359 last_header->type = pref_type;
7361 if (replyto_hdr == FALSE)
7362 g_hash_table_insert(compose->email_hashtable, email,
7363 GUINT_TO_POINTER(1));
7364 else
7365 g_free(email);
7367 g_free(tmp);
7370 static void compose_destroy_headerentry(Compose *compose,
7371 ComposeHeaderEntry *headerentry)
7373 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7374 gchar *email;
7376 extract_address(text);
7377 email = g_utf8_strdown(text, -1);
7378 g_hash_table_remove(compose->email_hashtable, email);
7379 g_free(text);
7380 g_free(email);
7382 gtk_widget_destroy(headerentry->combo);
7383 gtk_widget_destroy(headerentry->entry);
7384 gtk_widget_destroy(headerentry->button);
7385 gtk_widget_destroy(headerentry->hbox);
7386 g_free(headerentry);
7389 static void compose_remove_header_entries(Compose *compose)
7391 GSList *list;
7392 for (list = compose->header_list; list; list = list->next)
7393 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7395 compose->header_last = NULL;
7396 g_slist_free(compose->header_list);
7397 compose->header_list = NULL;
7398 compose->header_nextrow = 1;
7399 compose_create_header_entry(compose);
7402 static GtkWidget *compose_create_header(Compose *compose)
7404 GtkWidget *from_optmenu_hbox;
7405 GtkWidget *header_table_main;
7406 GtkWidget *header_scrolledwin;
7407 GtkWidget *header_table;
7409 /* parent with account selection and from header */
7410 header_table_main = gtk_grid_new();
7411 gtk_widget_show(header_table_main);
7412 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7414 from_optmenu_hbox = compose_account_option_menu_create(compose);
7415 gtk_grid_attach(GTK_GRID(header_table_main),from_optmenu_hbox, 0, 0, 1, 1);
7416 gtk_widget_set_hexpand(from_optmenu_hbox, TRUE);
7417 gtk_widget_set_halign(from_optmenu_hbox, GTK_ALIGN_FILL);
7419 /* child with header labels and entries */
7420 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7421 gtk_widget_show(header_scrolledwin);
7422 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7424 header_table = gtk_grid_new();
7425 gtk_widget_show(header_table);
7426 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7427 gtk_container_add(GTK_CONTAINER(header_scrolledwin), header_table);
7428 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7429 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7430 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7432 gtk_grid_attach(GTK_GRID(header_table_main), header_scrolledwin, 0, 1, 1, 1);
7433 gtk_widget_set_vexpand(header_scrolledwin, TRUE);
7434 gtk_widget_set_valign(header_scrolledwin, GTK_ALIGN_FILL);
7436 compose->header_table = header_table;
7437 compose->header_list = NULL;
7438 compose->header_nextrow = 0;
7440 compose_create_header_entry(compose);
7442 compose->table = NULL;
7444 return header_table_main;
7447 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7449 Compose *compose = (Compose *)data;
7450 GdkEventButton event;
7452 event.button = 3;
7453 event.time = gtk_get_current_event_time();
7455 return attach_button_pressed(compose->attach_clist, &event, compose);
7458 static GtkWidget *compose_create_attach(Compose *compose)
7460 GtkWidget *attach_scrwin;
7461 GtkWidget *attach_clist;
7463 GtkListStore *store;
7464 GtkCellRenderer *renderer;
7465 GtkTreeViewColumn *column;
7466 GtkTreeSelection *selection;
7468 /* attachment list */
7469 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7470 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7471 GTK_POLICY_AUTOMATIC,
7472 GTK_POLICY_AUTOMATIC);
7473 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7475 store = gtk_list_store_new(N_ATTACH_COLS,
7476 G_TYPE_STRING,
7477 G_TYPE_STRING,
7478 G_TYPE_STRING,
7479 G_TYPE_STRING,
7480 G_TYPE_POINTER,
7481 G_TYPE_AUTO_POINTER,
7482 -1);
7483 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7484 (GTK_TREE_MODEL(store)));
7485 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7486 g_object_unref(store);
7488 renderer = gtk_cell_renderer_text_new();
7489 column = gtk_tree_view_column_new_with_attributes
7490 (_("Mime type"), renderer, "text",
7491 COL_MIMETYPE, NULL);
7492 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7494 renderer = gtk_cell_renderer_text_new();
7495 column = gtk_tree_view_column_new_with_attributes
7496 (_("Size"), renderer, "text",
7497 COL_SIZE, NULL);
7498 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7500 renderer = gtk_cell_renderer_text_new();
7501 column = gtk_tree_view_column_new_with_attributes
7502 (_("Name"), renderer, "text",
7503 COL_NAME, NULL);
7504 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7506 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7507 prefs_common.use_stripes_everywhere);
7508 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7509 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7511 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7512 G_CALLBACK(attach_selected), compose);
7513 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7514 G_CALLBACK(attach_button_pressed), compose);
7515 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7516 G_CALLBACK(popup_attach_button_pressed), compose);
7517 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7518 G_CALLBACK(attach_key_pressed), compose);
7520 /* drag and drop */
7521 gtk_drag_dest_set(attach_clist,
7522 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7523 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7524 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7525 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7526 G_CALLBACK(compose_attach_drag_received_cb),
7527 compose);
7528 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7529 G_CALLBACK(compose_drag_drop),
7530 compose);
7532 compose->attach_scrwin = attach_scrwin;
7533 compose->attach_clist = attach_clist;
7535 return attach_scrwin;
7538 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7540 static GtkWidget *compose_create_others(Compose *compose)
7542 GtkWidget *table;
7543 GtkWidget *savemsg_checkbtn;
7544 GtkWidget *savemsg_combo;
7545 GtkWidget *savemsg_select;
7547 guint rowcount = 0;
7548 gchar *folderidentifier;
7550 /* Table for settings */
7551 table = gtk_grid_new();
7552 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7553 gtk_widget_show(table);
7554 gtk_grid_set_row_spacing(GTK_GRID(table), VSPACING_NARROW);
7555 rowcount = 0;
7557 /* Save Message to folder */
7558 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7559 gtk_widget_show(savemsg_checkbtn);
7560 gtk_grid_attach(GTK_GRID(table), savemsg_checkbtn, 0, rowcount, 1, 1);
7561 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7562 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7565 savemsg_combo = gtk_combo_box_text_new_with_entry();
7566 compose->savemsg_checkbtn = savemsg_checkbtn;
7567 compose->savemsg_combo = savemsg_combo;
7568 gtk_widget_show(savemsg_combo);
7570 if (prefs_common.compose_save_to_history)
7571 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7572 prefs_common.compose_save_to_history);
7573 gtk_grid_attach(GTK_GRID(table), savemsg_combo, 1, rowcount, 1, 1);
7574 gtk_widget_set_hexpand(savemsg_combo, TRUE);
7575 gtk_widget_set_halign(savemsg_combo, GTK_ALIGN_FILL);
7576 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7577 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7578 G_CALLBACK(compose_grab_focus_cb), compose);
7579 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7580 if (compose->account->set_sent_folder || prefs_common.savemsg)
7581 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), TRUE);
7582 else
7583 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), FALSE);
7584 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), TRUE);
7585 folderidentifier = folder_item_get_identifier(account_get_special_folder
7586 (compose->account, F_OUTBOX));
7587 compose_set_save_to(compose, folderidentifier);
7588 g_free(folderidentifier);
7591 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7592 gtk_widget_show(savemsg_select);
7593 gtk_grid_attach(GTK_GRID(table), savemsg_select, 2, rowcount, 1, 1);
7594 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7595 G_CALLBACK(compose_savemsg_select_cb),
7596 compose);
7598 return table;
7601 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7603 FolderItem *dest;
7604 gchar * path;
7606 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7607 _("Select folder to save message to"));
7608 if (!dest) return;
7610 path = folder_item_get_identifier(dest);
7612 compose_set_save_to(compose, path);
7613 g_free(path);
7616 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7617 GdkAtom clip, GtkTextIter *insert_place);
7620 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7621 Compose *compose)
7623 gint prev_autowrap;
7624 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7625 #if USE_ENCHANT
7626 if (event->button == 3) {
7627 GtkTextIter iter;
7628 GtkTextIter sel_start, sel_end;
7629 gboolean stuff_selected;
7630 gint x, y;
7631 /* move the cursor to allow GtkAspell to check the word
7632 * under the mouse */
7633 if (event->x && event->y) {
7634 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7635 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7636 &x, &y);
7637 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7638 &iter, x, y);
7639 } else {
7640 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7641 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7643 /* get selection */
7644 stuff_selected = gtk_text_buffer_get_selection_bounds(
7645 buffer,
7646 &sel_start, &sel_end);
7648 gtk_text_buffer_place_cursor (buffer, &iter);
7649 /* reselect stuff */
7650 if (stuff_selected
7651 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7652 gtk_text_buffer_select_range(buffer,
7653 &sel_start, &sel_end);
7655 return FALSE; /* pass the event so that the right-click goes through */
7657 #endif
7658 if (event->button == 2) {
7659 GtkTextIter iter;
7660 gint x, y;
7661 BLOCK_WRAP();
7663 /* get the middle-click position to paste at the correct place */
7664 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7665 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7666 &x, &y);
7667 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7668 &iter, x, y);
7670 entry_paste_clipboard(compose, text,
7671 prefs_common.linewrap_pastes,
7672 GDK_SELECTION_PRIMARY, &iter);
7673 UNBLOCK_WRAP();
7674 return TRUE;
7676 return FALSE;
7679 #if USE_ENCHANT
7680 static void compose_spell_menu_changed(void *data)
7682 Compose *compose = (Compose *)data;
7683 GSList *items;
7684 GtkWidget *menuitem;
7685 GtkWidget *parent_item;
7686 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7687 GSList *spell_menu;
7689 if (compose->gtkaspell == NULL)
7690 return;
7692 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7693 "/Menu/Spelling/Options");
7695 /* setting the submenu removes /Spelling/Options from the factory
7696 * so we need to save it */
7698 if (parent_item == NULL) {
7699 parent_item = compose->aspell_options_menu;
7700 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7701 } else
7702 compose->aspell_options_menu = parent_item;
7704 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7706 spell_menu = g_slist_reverse(spell_menu);
7707 for (items = spell_menu;
7708 items; items = items->next) {
7709 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7710 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7711 gtk_widget_show(GTK_WIDGET(menuitem));
7713 g_slist_free(spell_menu);
7715 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7716 gtk_widget_show(parent_item);
7719 static void compose_dict_changed(void *data)
7721 Compose *compose = (Compose *) data;
7723 if(!compose->gtkaspell)
7724 return;
7725 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7726 return;
7728 gtkaspell_highlight_all(compose->gtkaspell);
7729 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7731 #endif
7733 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7735 Compose *compose = (Compose *)data;
7736 GdkEventButton event;
7738 event.button = 3;
7739 event.time = gtk_get_current_event_time();
7740 event.x = 0;
7741 event.y = 0;
7743 return text_clicked(compose->text, &event, compose);
7746 static gboolean compose_force_window_origin = TRUE;
7747 static Compose *compose_create(PrefsAccount *account,
7748 FolderItem *folder,
7749 ComposeMode mode,
7750 gboolean batch)
7752 Compose *compose;
7753 GtkWidget *window;
7754 GtkWidget *vbox;
7755 GtkWidget *menubar;
7756 GtkWidget *handlebox;
7758 GtkWidget *notebook;
7760 GtkWidget *attach_hbox;
7761 GtkWidget *attach_lab1;
7762 GtkWidget *attach_lab2;
7764 GtkWidget *vbox2;
7766 GtkWidget *label;
7767 GtkWidget *subject_hbox;
7768 GtkWidget *subject_frame;
7769 GtkWidget *subject_entry;
7770 GtkWidget *subject;
7771 GtkWidget *paned;
7773 GtkWidget *edit_vbox;
7774 GtkWidget *ruler_hbox;
7775 GtkWidget *ruler;
7776 GtkWidget *scrolledwin;
7777 GtkWidget *text;
7778 GtkTextBuffer *buffer;
7779 GtkClipboard *clipboard;
7781 UndoMain *undostruct;
7783 GtkWidget *popupmenu;
7784 GtkWidget *tmpl_menu;
7785 GtkActionGroup *action_group = NULL;
7787 #if USE_ENCHANT
7788 GtkAspell * gtkaspell = NULL;
7789 #endif
7791 static GdkGeometry geometry;
7792 GdkRectangle workarea = {0};
7794 cm_return_val_if_fail(account != NULL, NULL);
7796 default_header_bgcolor = prefs_common.color[COL_DEFAULT_HEADER_BG],
7797 default_header_color = prefs_common.color[COL_DEFAULT_HEADER],
7799 debug_print("Creating compose window...\n");
7800 compose = g_new0(Compose, 1);
7802 compose->batch = batch;
7803 compose->account = account;
7804 compose->folder = folder;
7806 g_mutex_init(&compose->mutex);
7807 compose->set_cursor_pos = -1;
7809 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7811 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7812 gtk_window_set_default_size(GTK_WINDOW(window), prefs_common.compose_width,
7813 prefs_common.compose_height);
7815 gdk_monitor_get_workarea(gdk_display_get_primary_monitor(gdk_display_get_default()),
7816 &workarea);
7818 if (!geometry.max_width) {
7819 geometry.max_width = workarea.width;
7820 geometry.max_height = workarea.height;
7823 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7824 &geometry, GDK_HINT_MAX_SIZE);
7825 if (!geometry.min_width) {
7826 geometry.min_width = 600;
7827 geometry.min_height = 440;
7829 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7830 &geometry, GDK_HINT_MIN_SIZE);
7832 #ifndef GENERIC_UMPC
7833 if (compose_force_window_origin)
7834 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7835 prefs_common.compose_y);
7836 #endif
7837 g_signal_connect(G_OBJECT(window), "delete_event",
7838 G_CALLBACK(compose_delete_cb), compose);
7839 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7840 gtk_widget_realize(window);
7842 gtkut_widget_set_composer_icon(window);
7844 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
7845 gtk_container_add(GTK_CONTAINER(window), vbox);
7847 compose->ui_manager = gtk_ui_manager_new();
7848 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7849 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7850 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7851 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7852 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7853 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7854 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7855 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7856 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7857 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7863 #ifdef USE_ENCHANT
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7865 #endif
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7870 /* Compose menu */
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7885 /* Edit menu */
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7908 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7915 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7922 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7928 #if USE_ENCHANT
7929 /* Spelling menu */
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7935 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7936 #endif
7938 /* Options menu */
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7940 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7941 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7942 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7943 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7946 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7947 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7948 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7949 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7952 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7953 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7954 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7955 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7956 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7957 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7958 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7960 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7961 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7962 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7963 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7964 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7966 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7968 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7969 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7970 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7971 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7972 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7974 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7975 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)
7976 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)
7977 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7979 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7981 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7982 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)
7983 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)
7985 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7987 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7988 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)
7989 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7991 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7992 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)
7993 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7995 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7997 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7998 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)
7999 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
8000 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
8001 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
8002 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
8004 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
8005 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)
8006 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)
8007 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
8008 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
8010 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
8011 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
8012 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
8013 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
8014 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
8015 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
8017 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
8018 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
8019 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)
8021 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
8022 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
8023 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
8024 /* phew. */
8026 /* Tools menu */
8027 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
8028 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
8029 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
8030 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8031 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
8032 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8034 /* Help menu */
8035 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
8037 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
8038 gtk_widget_show_all(menubar);
8040 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
8041 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
8043 handlebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8044 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8046 gtk_widget_realize(handlebox);
8047 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8048 (gpointer)compose);
8050 vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
8051 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8052 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8054 /* Notebook */
8055 notebook = gtk_notebook_new();
8056 gtk_widget_show(notebook);
8058 /* header labels and entries */
8059 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8060 compose_create_header(compose),
8061 gtk_label_new_with_mnemonic(_("Hea_der")));
8062 /* attachment list */
8063 attach_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8064 gtk_widget_show(attach_hbox);
8066 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8067 gtk_widget_show(attach_lab1);
8068 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8070 attach_lab2 = gtk_label_new("");
8071 gtk_widget_show(attach_lab2);
8072 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8074 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8075 compose_create_attach(compose),
8076 attach_hbox);
8077 /* Others Tab */
8078 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8079 compose_create_others(compose),
8080 gtk_label_new_with_mnemonic(_("Othe_rs")));
8082 /* Subject */
8083 subject_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8084 gtk_widget_show(subject_hbox);
8086 subject_frame = gtk_frame_new(NULL);
8087 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8088 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8089 gtk_widget_show(subject_frame);
8091 subject = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, HSPACING_NARROW);
8092 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8093 gtk_widget_show(subject);
8095 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8096 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8097 gtk_widget_show(label);
8099 #ifdef USE_ENCHANT
8100 subject_entry = claws_spell_entry_new();
8101 #else
8102 subject_entry = gtk_entry_new();
8103 #endif
8104 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8105 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8106 G_CALLBACK(compose_grab_focus_cb), compose);
8107 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8108 gtk_widget_show(subject_entry);
8109 compose->subject_entry = subject_entry;
8110 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8112 edit_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
8114 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8116 /* ruler */
8117 ruler_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8118 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8120 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8121 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8122 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8123 BORDER_WIDTH);
8125 /* text widget */
8126 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8127 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8128 GTK_POLICY_AUTOMATIC,
8129 GTK_POLICY_AUTOMATIC);
8130 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8131 GTK_SHADOW_IN);
8132 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8134 text = gtk_text_view_new();
8135 if (prefs_common.show_compose_margin) {
8136 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8137 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8139 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8140 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8141 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8142 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8143 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8145 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8146 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8147 G_CALLBACK(compose_edit_size_alloc),
8148 ruler);
8149 g_signal_connect(G_OBJECT(buffer), "changed",
8150 G_CALLBACK(compose_changed_cb), compose);
8151 g_signal_connect(G_OBJECT(text), "grab_focus",
8152 G_CALLBACK(compose_grab_focus_cb), compose);
8153 g_signal_connect(G_OBJECT(buffer), "insert_text",
8154 G_CALLBACK(text_inserted), compose);
8155 g_signal_connect(G_OBJECT(text), "button_press_event",
8156 G_CALLBACK(text_clicked), compose);
8157 g_signal_connect(G_OBJECT(text), "popup-menu",
8158 G_CALLBACK(compose_popup_menu), compose);
8159 g_signal_connect(G_OBJECT(subject_entry), "changed",
8160 G_CALLBACK(compose_changed_cb), compose);
8161 g_signal_connect(G_OBJECT(subject_entry), "activate",
8162 G_CALLBACK(compose_subject_entry_activated), compose);
8164 /* drag and drop */
8165 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8166 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8167 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8168 g_signal_connect(G_OBJECT(text), "drag_data_received",
8169 G_CALLBACK(compose_insert_drag_received_cb),
8170 compose);
8171 g_signal_connect(G_OBJECT(text), "drag-drop",
8172 G_CALLBACK(compose_drag_drop),
8173 compose);
8174 g_signal_connect(G_OBJECT(text), "key-press-event",
8175 G_CALLBACK(completion_set_focus_to_subject),
8176 compose);
8177 gtk_widget_show_all(vbox);
8179 /* pane between attach clist and text */
8180 paned = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
8181 gtk_box_pack_start(GTK_BOX(vbox2), paned, TRUE, TRUE, 0);
8182 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8183 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8184 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8185 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8186 G_CALLBACK(compose_notebook_size_alloc), paned);
8188 gtk_widget_show_all(paned);
8191 if (prefs_common.textfont) {
8192 PangoFontDescription *font_desc;
8194 font_desc = pango_font_description_from_string
8195 (prefs_common.textfont);
8196 if (font_desc) {
8197 gtk_widget_override_font(text, font_desc);
8198 pango_font_description_free(font_desc);
8202 gtk_action_group_add_actions(action_group, compose_popup_entries,
8203 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8204 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8205 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8206 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8207 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8208 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8209 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8211 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8213 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8214 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8215 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8217 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8219 undostruct = undo_init(text);
8220 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8221 compose);
8223 address_completion_start(window);
8225 compose->window = window;
8226 compose->vbox = vbox;
8227 compose->menubar = menubar;
8228 compose->handlebox = handlebox;
8230 compose->vbox2 = vbox2;
8232 compose->paned = paned;
8234 compose->attach_label = attach_lab2;
8236 compose->notebook = notebook;
8237 compose->edit_vbox = edit_vbox;
8238 compose->ruler_hbox = ruler_hbox;
8239 compose->ruler = ruler;
8240 compose->scrolledwin = scrolledwin;
8241 compose->text = text;
8243 compose->focused_editable = NULL;
8245 compose->popupmenu = popupmenu;
8247 compose->tmpl_menu = tmpl_menu;
8249 compose->mode = mode;
8250 compose->rmode = mode;
8252 compose->targetinfo = NULL;
8253 compose->replyinfo = NULL;
8254 compose->fwdinfo = NULL;
8256 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8257 g_str_equal, (GDestroyNotify) g_free, NULL);
8259 compose->replyto = NULL;
8260 compose->cc = NULL;
8261 compose->bcc = NULL;
8262 compose->followup_to = NULL;
8264 compose->ml_post = NULL;
8266 compose->inreplyto = NULL;
8267 compose->references = NULL;
8268 compose->msgid = NULL;
8269 compose->boundary = NULL;
8271 compose->autowrap = prefs_common.autowrap;
8272 compose->autoindent = prefs_common.auto_indent;
8273 compose->use_signing = FALSE;
8274 compose->use_encryption = FALSE;
8275 compose->privacy_system = NULL;
8276 compose->encdata = NULL;
8278 compose->modified = FALSE;
8280 compose->return_receipt = FALSE;
8282 compose->to_list = NULL;
8283 compose->newsgroup_list = NULL;
8285 compose->undostruct = undostruct;
8287 compose->sig_str = NULL;
8289 compose->exteditor_file = NULL;
8290 compose->exteditor_pid = INVALID_PID;
8291 compose->exteditor_tag = -1;
8292 compose->exteditor_socket = NULL;
8293 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8295 compose->folder_update_callback_id =
8296 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8297 compose_update_folder_hook,
8298 (gpointer) compose);
8300 #if USE_ENCHANT
8301 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8302 if (mode != COMPOSE_REDIRECT) {
8303 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8304 strcmp(prefs_common.dictionary, "")) {
8305 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8306 prefs_common.alt_dictionary,
8307 conv_get_locale_charset_str(),
8308 prefs_common.color[COL_MISSPELLED],
8309 prefs_common.check_while_typing,
8310 prefs_common.recheck_when_changing_dict,
8311 prefs_common.use_alternate,
8312 prefs_common.use_both_dicts,
8313 GTK_TEXT_VIEW(text),
8314 GTK_WINDOW(compose->window),
8315 compose_dict_changed,
8316 compose_spell_menu_changed,
8317 compose);
8318 if (!gtkaspell) {
8319 alertpanel_error(_("Spell checker could not "
8320 "be started.\n%s"),
8321 gtkaspell_checkers_strerror());
8322 gtkaspell_checkers_reset_error();
8323 } else {
8324 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8328 compose->gtkaspell = gtkaspell;
8329 compose_spell_menu_changed(compose);
8330 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8331 #endif
8333 compose_select_account(compose, account, TRUE);
8335 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8336 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8338 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8339 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8341 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8342 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8344 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8345 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8347 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8348 if (account->protocol != A_NNTP)
8349 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8350 prefs_common_translated_header_name("To:"));
8351 else
8352 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8353 prefs_common_translated_header_name("Newsgroups:"));
8355 #ifndef USE_ALT_ADDRBOOK
8356 addressbook_set_target_compose(compose);
8357 #endif
8358 if (mode != COMPOSE_REDIRECT)
8359 compose_set_template_menu(compose);
8360 else {
8361 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8364 compose_list = g_list_append(compose_list, compose);
8366 if (!prefs_common.show_ruler)
8367 gtk_widget_hide(ruler_hbox);
8369 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8371 /* Priority */
8372 compose->priority = PRIORITY_NORMAL;
8373 compose_update_priority_menu_item(compose);
8375 compose_set_out_encoding(compose);
8377 /* Actions menu */
8378 compose_update_actions_menu(compose);
8380 /* Privacy Systems menu */
8381 compose_update_privacy_systems_menu(compose);
8382 compose_activate_privacy_system(compose, account, TRUE);
8384 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8385 if (batch) {
8386 gtk_widget_realize(window);
8387 } else {
8388 gtk_widget_show(window);
8391 return compose;
8394 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8396 GList *accounts;
8397 GtkWidget *hbox;
8398 GtkWidget *optmenu;
8399 GtkWidget *optmenubox;
8400 GtkWidget *fromlabel;
8401 GtkListStore *menu;
8402 GtkTreeIter iter;
8403 GtkWidget *from_name = NULL;
8405 gint num = 0, def_menu = 0;
8407 accounts = account_get_list();
8408 cm_return_val_if_fail(accounts != NULL, NULL);
8410 optmenubox = gtk_event_box_new();
8411 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8412 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8414 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
8415 from_name = gtk_entry_new();
8417 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8418 G_CALLBACK(compose_grab_focus_cb), compose);
8419 g_signal_connect_after(G_OBJECT(from_name), "activate",
8420 G_CALLBACK(from_name_activate_cb), optmenu);
8422 for (; accounts != NULL; accounts = accounts->next, num++) {
8423 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8424 gchar *name, *from = NULL;
8426 if (ac == compose->account) def_menu = num;
8428 name = g_markup_printf_escaped("<i>%s</i>",
8429 ac->account_name);
8431 if (ac == compose->account) {
8432 if (ac->name && *ac->name) {
8433 gchar *buf;
8434 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8435 from = g_strdup_printf("%s <%s>",
8436 buf, ac->address);
8437 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8438 } else {
8439 from = g_strdup_printf("%s",
8440 ac->address);
8441 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8443 if (cur_account != compose->account) {
8444 GdkColor color;
8446 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_bgcolor, color);
8447 gtk_widget_modify_base(
8448 GTK_WIDGET(from_name),
8449 GTK_STATE_NORMAL, &color);
8450 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_color, color);
8451 gtk_widget_modify_text(
8452 GTK_WIDGET(from_name),
8453 GTK_STATE_NORMAL, &color);
8456 COMBOBOX_ADD(menu, name, ac->account_id);
8457 g_free(name);
8458 g_free(from);
8461 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8463 g_signal_connect(G_OBJECT(optmenu), "changed",
8464 G_CALLBACK(account_activated),
8465 compose);
8466 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8467 G_CALLBACK(compose_entry_popup_extend),
8468 NULL);
8470 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8471 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8473 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8474 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8475 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8477 CLAWS_SET_TIP(optmenubox,
8478 _("Account to use for this email"));
8479 CLAWS_SET_TIP(from_name,
8480 _("Sender address to be used"));
8482 compose->account_combo = optmenu;
8483 compose->from_name = from_name;
8485 return hbox;
8488 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8490 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8491 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8492 Compose *compose = (Compose *) data;
8493 if (active) {
8494 compose->priority = value;
8498 static void compose_reply_change_mode(Compose *compose,
8499 ComposeMode action)
8501 gboolean was_modified = compose->modified;
8503 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8505 cm_return_if_fail(compose->replyinfo != NULL);
8507 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8508 ml = TRUE;
8509 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8510 followup = TRUE;
8511 if (action == COMPOSE_REPLY_TO_ALL)
8512 all = TRUE;
8513 if (action == COMPOSE_REPLY_TO_SENDER)
8514 sender = TRUE;
8515 if (action == COMPOSE_REPLY_TO_LIST)
8516 ml = TRUE;
8518 compose_remove_header_entries(compose);
8519 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8520 if (compose->account->set_autocc && compose->account->auto_cc)
8521 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8523 if (compose->account->set_autobcc && compose->account->auto_bcc)
8524 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8526 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8527 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8528 compose_show_first_last_header(compose, TRUE);
8529 compose->modified = was_modified;
8530 compose_set_title(compose);
8533 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8535 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8536 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8537 Compose *compose = (Compose *) data;
8539 if (active)
8540 compose_reply_change_mode(compose, value);
8543 static void compose_update_priority_menu_item(Compose * compose)
8545 GtkWidget *menuitem = NULL;
8546 switch (compose->priority) {
8547 case PRIORITY_HIGHEST:
8548 menuitem = gtk_ui_manager_get_widget
8549 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8550 break;
8551 case PRIORITY_HIGH:
8552 menuitem = gtk_ui_manager_get_widget
8553 (compose->ui_manager, "/Menu/Options/Priority/High");
8554 break;
8555 case PRIORITY_NORMAL:
8556 menuitem = gtk_ui_manager_get_widget
8557 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8558 break;
8559 case PRIORITY_LOW:
8560 menuitem = gtk_ui_manager_get_widget
8561 (compose->ui_manager, "/Menu/Options/Priority/Low");
8562 break;
8563 case PRIORITY_LOWEST:
8564 menuitem = gtk_ui_manager_get_widget
8565 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8566 break;
8568 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8571 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8573 Compose *compose = (Compose *) data;
8574 gchar *systemid;
8575 gboolean can_sign = FALSE, can_encrypt = FALSE;
8577 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8579 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8580 return;
8582 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8583 g_free(compose->privacy_system);
8584 compose->privacy_system = NULL;
8585 g_free(compose->encdata);
8586 compose->encdata = NULL;
8587 if (systemid != NULL) {
8588 compose->privacy_system = g_strdup(systemid);
8590 can_sign = privacy_system_can_sign(systemid);
8591 can_encrypt = privacy_system_can_encrypt(systemid);
8594 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8596 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8597 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8598 if (compose->toolbar->privacy_sign_btn != NULL) {
8599 gtk_widget_set_sensitive(
8600 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8601 can_sign);
8602 gtk_toggle_tool_button_set_active(
8603 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8604 can_sign ? compose->use_signing : FALSE);
8606 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8607 gtk_widget_set_sensitive(
8608 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8609 can_encrypt);
8610 gtk_toggle_tool_button_set_active(
8611 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8612 can_encrypt ? compose->use_encryption : FALSE);
8616 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8618 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8619 GtkWidget *menuitem = NULL;
8620 GList *children, *amenu;
8621 gboolean can_sign = FALSE, can_encrypt = FALSE;
8622 gboolean found = FALSE;
8624 if (compose->privacy_system != NULL) {
8625 gchar *systemid;
8626 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8627 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8628 cm_return_if_fail(menuitem != NULL);
8630 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8631 amenu = children;
8632 menuitem = NULL;
8633 while (amenu != NULL) {
8634 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8635 if (systemid != NULL) {
8636 if (strcmp(systemid, compose->privacy_system) == 0 &&
8637 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8638 menuitem = GTK_WIDGET(amenu->data);
8640 can_sign = privacy_system_can_sign(systemid);
8641 can_encrypt = privacy_system_can_encrypt(systemid);
8642 found = TRUE;
8643 break;
8645 } else if (strlen(compose->privacy_system) == 0 &&
8646 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8647 menuitem = GTK_WIDGET(amenu->data);
8649 can_sign = FALSE;
8650 can_encrypt = FALSE;
8651 found = TRUE;
8652 break;
8655 amenu = amenu->next;
8657 g_list_free(children);
8658 if (menuitem != NULL)
8659 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8661 if (warn && !found && strlen(compose->privacy_system)) {
8662 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8663 "will not be able to sign or encrypt this message."),
8664 compose->privacy_system);
8668 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8669 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8670 if (compose->toolbar->privacy_sign_btn != NULL) {
8671 gtk_widget_set_sensitive(
8672 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8673 can_sign);
8675 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8676 gtk_widget_set_sensitive(
8677 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8678 can_encrypt);
8682 static void compose_set_out_encoding(Compose *compose)
8684 CharSet out_encoding;
8685 const gchar *branch = NULL;
8686 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8688 switch(out_encoding) {
8689 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8690 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8691 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8692 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8693 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8694 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8695 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8696 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8697 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8698 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8699 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8700 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8701 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8702 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8703 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8704 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8705 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8706 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8707 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8708 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8709 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8710 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8711 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8712 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8713 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8714 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8715 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8716 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8717 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8718 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8719 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8720 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8721 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8722 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8724 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8727 static void compose_set_template_menu(Compose *compose)
8729 GSList *tmpl_list, *cur;
8730 GtkWidget *menu;
8731 GtkWidget *item;
8733 tmpl_list = template_get_config();
8735 menu = gtk_menu_new();
8737 gtk_menu_set_accel_group (GTK_MENU (menu),
8738 gtk_ui_manager_get_accel_group(compose->ui_manager));
8739 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8740 Template *tmpl = (Template *)cur->data;
8741 gchar *accel_path = NULL;
8742 item = gtk_menu_item_new_with_label(tmpl->name);
8743 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8744 g_signal_connect(G_OBJECT(item), "activate",
8745 G_CALLBACK(compose_template_activate_cb),
8746 compose);
8747 g_object_set_data(G_OBJECT(item), "template", tmpl);
8748 gtk_widget_show(item);
8749 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8750 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8751 g_free(accel_path);
8754 gtk_widget_show(menu);
8755 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8758 void compose_update_actions_menu(Compose *compose)
8760 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8763 static void compose_update_privacy_systems_menu(Compose *compose)
8765 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8766 GSList *systems, *cur;
8767 GtkWidget *widget;
8768 GtkWidget *system_none;
8769 GSList *group;
8770 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8771 GtkWidget *privacy_menu = gtk_menu_new();
8773 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8774 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8776 g_signal_connect(G_OBJECT(system_none), "activate",
8777 G_CALLBACK(compose_set_privacy_system_cb), compose);
8779 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8780 gtk_widget_show(system_none);
8782 systems = privacy_get_system_ids();
8783 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8784 gchar *systemid = cur->data;
8786 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8787 widget = gtk_radio_menu_item_new_with_label(group,
8788 privacy_system_get_name(systemid));
8789 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8790 g_strdup(systemid), g_free);
8791 g_signal_connect(G_OBJECT(widget), "activate",
8792 G_CALLBACK(compose_set_privacy_system_cb), compose);
8794 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8795 gtk_widget_show(widget);
8796 g_free(systemid);
8798 g_slist_free(systems);
8799 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8800 gtk_widget_show_all(privacy_menu);
8801 gtk_widget_show_all(privacy_menuitem);
8804 void compose_reflect_prefs_all(void)
8806 GList *cur;
8807 Compose *compose;
8809 for (cur = compose_list; cur != NULL; cur = cur->next) {
8810 compose = (Compose *)cur->data;
8811 compose_set_template_menu(compose);
8815 void compose_reflect_prefs_pixmap_theme(void)
8817 GList *cur;
8818 Compose *compose;
8820 for (cur = compose_list; cur != NULL; cur = cur->next) {
8821 compose = (Compose *)cur->data;
8822 toolbar_update(TOOLBAR_COMPOSE, compose);
8826 static const gchar *compose_quote_char_from_context(Compose *compose)
8828 const gchar *qmark = NULL;
8830 cm_return_val_if_fail(compose != NULL, NULL);
8832 switch (compose->mode) {
8833 /* use forward-specific quote char */
8834 case COMPOSE_FORWARD:
8835 case COMPOSE_FORWARD_AS_ATTACH:
8836 case COMPOSE_FORWARD_INLINE:
8837 if (compose->folder && compose->folder->prefs &&
8838 compose->folder->prefs->forward_with_format)
8839 qmark = compose->folder->prefs->forward_quotemark;
8840 else if (compose->account->forward_with_format)
8841 qmark = compose->account->forward_quotemark;
8842 else
8843 qmark = prefs_common.fw_quotemark;
8844 break;
8846 /* use reply-specific quote char in all other modes */
8847 default:
8848 if (compose->folder && compose->folder->prefs &&
8849 compose->folder->prefs->reply_with_format)
8850 qmark = compose->folder->prefs->reply_quotemark;
8851 else if (compose->account->reply_with_format)
8852 qmark = compose->account->reply_quotemark;
8853 else
8854 qmark = prefs_common.quotemark;
8855 break;
8858 if (qmark == NULL || *qmark == '\0')
8859 qmark = "> ";
8861 return qmark;
8864 static void compose_template_apply(Compose *compose, Template *tmpl,
8865 gboolean replace)
8867 GtkTextView *text;
8868 GtkTextBuffer *buffer;
8869 GtkTextMark *mark;
8870 GtkTextIter iter;
8871 const gchar *qmark;
8872 gchar *parsed_str = NULL;
8873 gint cursor_pos = 0;
8874 const gchar *err_msg = _("The body of the template has an error at line %d.");
8875 if (!tmpl) return;
8877 /* process the body */
8879 text = GTK_TEXT_VIEW(compose->text);
8880 buffer = gtk_text_view_get_buffer(text);
8882 if (tmpl->value) {
8883 qmark = compose_quote_char_from_context(compose);
8885 if (compose->replyinfo != NULL) {
8887 if (replace)
8888 gtk_text_buffer_set_text(buffer, "", -1);
8889 mark = gtk_text_buffer_get_insert(buffer);
8890 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8892 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8893 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8895 } else if (compose->fwdinfo != NULL) {
8897 if (replace)
8898 gtk_text_buffer_set_text(buffer, "", -1);
8899 mark = gtk_text_buffer_get_insert(buffer);
8900 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8902 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8903 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8905 } else {
8906 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8908 GtkTextIter start, end;
8909 gchar *tmp = NULL;
8911 gtk_text_buffer_get_start_iter(buffer, &start);
8912 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8913 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8915 /* clear the buffer now */
8916 if (replace)
8917 gtk_text_buffer_set_text(buffer, "", -1);
8919 parsed_str = compose_quote_fmt(compose, dummyinfo,
8920 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8921 procmsg_msginfo_free( &dummyinfo );
8923 g_free( tmp );
8925 } else {
8926 if (replace)
8927 gtk_text_buffer_set_text(buffer, "", -1);
8928 mark = gtk_text_buffer_get_insert(buffer);
8929 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8932 if (replace && parsed_str && compose->account->auto_sig)
8933 compose_insert_sig(compose, FALSE);
8935 if (replace && parsed_str) {
8936 gtk_text_buffer_get_start_iter(buffer, &iter);
8937 gtk_text_buffer_place_cursor(buffer, &iter);
8940 if (parsed_str) {
8941 cursor_pos = quote_fmt_get_cursor_pos();
8942 compose->set_cursor_pos = cursor_pos;
8943 if (cursor_pos == -1)
8944 cursor_pos = 0;
8945 gtk_text_buffer_get_start_iter(buffer, &iter);
8946 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8947 gtk_text_buffer_place_cursor(buffer, &iter);
8950 /* process the other fields */
8952 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8953 compose_template_apply_fields(compose, tmpl);
8954 quote_fmt_reset_vartable();
8955 quote_fmtlex_destroy();
8957 compose_changed_cb(NULL, compose);
8959 #ifdef USE_ENCHANT
8960 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8961 gtkaspell_highlight_all(compose->gtkaspell);
8962 #endif
8965 static void compose_template_apply_fields_error(const gchar *header)
8967 gchar *tr;
8968 gchar *text;
8970 tr = g_strdup(C_("'%s' stands for a header name",
8971 "Template '%s' format error."));
8972 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8973 alertpanel_error("%s", text);
8975 g_free(text);
8976 g_free(tr);
8979 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8981 MsgInfo* dummyinfo = NULL;
8982 MsgInfo *msginfo = NULL;
8983 gchar *buf = NULL;
8985 if (compose->replyinfo != NULL)
8986 msginfo = compose->replyinfo;
8987 else if (compose->fwdinfo != NULL)
8988 msginfo = compose->fwdinfo;
8989 else {
8990 dummyinfo = compose_msginfo_new_from_compose(compose);
8991 msginfo = dummyinfo;
8994 if (tmpl->from && *tmpl->from != '\0') {
8995 #ifdef USE_ENCHANT
8996 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8997 compose->gtkaspell);
8998 #else
8999 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9000 #endif
9001 quote_fmt_scan_string(tmpl->from);
9002 quote_fmt_parse();
9004 buf = quote_fmt_get_buffer();
9005 if (buf == NULL) {
9006 compose_template_apply_fields_error("From");
9007 } else {
9008 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
9011 quote_fmt_reset_vartable();
9012 quote_fmtlex_destroy();
9015 if (tmpl->to && *tmpl->to != '\0') {
9016 #ifdef USE_ENCHANT
9017 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9018 compose->gtkaspell);
9019 #else
9020 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9021 #endif
9022 quote_fmt_scan_string(tmpl->to);
9023 quote_fmt_parse();
9025 buf = quote_fmt_get_buffer();
9026 if (buf == NULL) {
9027 compose_template_apply_fields_error("To");
9028 } else {
9029 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
9032 quote_fmt_reset_vartable();
9033 quote_fmtlex_destroy();
9036 if (tmpl->cc && *tmpl->cc != '\0') {
9037 #ifdef USE_ENCHANT
9038 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9039 compose->gtkaspell);
9040 #else
9041 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9042 #endif
9043 quote_fmt_scan_string(tmpl->cc);
9044 quote_fmt_parse();
9046 buf = quote_fmt_get_buffer();
9047 if (buf == NULL) {
9048 compose_template_apply_fields_error("Cc");
9049 } else {
9050 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9053 quote_fmt_reset_vartable();
9054 quote_fmtlex_destroy();
9057 if (tmpl->bcc && *tmpl->bcc != '\0') {
9058 #ifdef USE_ENCHANT
9059 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9060 compose->gtkaspell);
9061 #else
9062 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9063 #endif
9064 quote_fmt_scan_string(tmpl->bcc);
9065 quote_fmt_parse();
9067 buf = quote_fmt_get_buffer();
9068 if (buf == NULL) {
9069 compose_template_apply_fields_error("Bcc");
9070 } else {
9071 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9074 quote_fmt_reset_vartable();
9075 quote_fmtlex_destroy();
9078 if (tmpl->replyto && *tmpl->replyto != '\0') {
9079 #ifdef USE_ENCHANT
9080 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9081 compose->gtkaspell);
9082 #else
9083 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9084 #endif
9085 quote_fmt_scan_string(tmpl->replyto);
9086 quote_fmt_parse();
9088 buf = quote_fmt_get_buffer();
9089 if (buf == NULL) {
9090 compose_template_apply_fields_error("Reply-To");
9091 } else {
9092 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9095 quote_fmt_reset_vartable();
9096 quote_fmtlex_destroy();
9099 /* process the subject */
9100 if (tmpl->subject && *tmpl->subject != '\0') {
9101 #ifdef USE_ENCHANT
9102 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9103 compose->gtkaspell);
9104 #else
9105 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9106 #endif
9107 quote_fmt_scan_string(tmpl->subject);
9108 quote_fmt_parse();
9110 buf = quote_fmt_get_buffer();
9111 if (buf == NULL) {
9112 compose_template_apply_fields_error("Subject");
9113 } else {
9114 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9117 quote_fmt_reset_vartable();
9118 quote_fmtlex_destroy();
9121 procmsg_msginfo_free( &dummyinfo );
9124 static void compose_destroy(Compose *compose)
9126 GtkAllocation allocation;
9127 GtkTextBuffer *buffer;
9128 GtkClipboard *clipboard;
9130 compose_list = g_list_remove(compose_list, compose);
9132 #ifdef USE_LDAP
9133 gboolean enable = TRUE;
9134 g_slist_foreach(compose->passworded_ldap_servers,
9135 _ldap_srv_func, &enable);
9136 g_slist_free(compose->passworded_ldap_servers);
9137 #endif
9139 if (compose->updating) {
9140 debug_print("danger, not destroying anything now\n");
9141 compose->deferred_destroy = TRUE;
9142 return;
9145 /* NOTE: address_completion_end() does nothing with the window
9146 * however this may change. */
9147 address_completion_end(compose->window);
9149 slist_free_strings_full(compose->to_list);
9150 slist_free_strings_full(compose->newsgroup_list);
9151 slist_free_strings_full(compose->header_list);
9153 slist_free_strings_full(extra_headers);
9154 extra_headers = NULL;
9156 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9158 g_hash_table_destroy(compose->email_hashtable);
9160 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9161 compose->folder_update_callback_id);
9163 procmsg_msginfo_free(&(compose->targetinfo));
9164 procmsg_msginfo_free(&(compose->replyinfo));
9165 procmsg_msginfo_free(&(compose->fwdinfo));
9167 g_free(compose->replyto);
9168 g_free(compose->cc);
9169 g_free(compose->bcc);
9170 g_free(compose->newsgroups);
9171 g_free(compose->followup_to);
9173 g_free(compose->ml_post);
9175 g_free(compose->inreplyto);
9176 g_free(compose->references);
9177 g_free(compose->msgid);
9178 g_free(compose->boundary);
9180 g_free(compose->redirect_filename);
9181 if (compose->undostruct)
9182 undo_destroy(compose->undostruct);
9184 g_free(compose->sig_str);
9186 g_free(compose->exteditor_file);
9188 g_free(compose->orig_charset);
9190 g_free(compose->privacy_system);
9191 g_free(compose->encdata);
9193 #ifndef USE_ALT_ADDRBOOK
9194 if (addressbook_get_target_compose() == compose)
9195 addressbook_set_target_compose(NULL);
9196 #endif
9197 #if USE_ENCHANT
9198 if (compose->gtkaspell) {
9199 gtkaspell_delete(compose->gtkaspell);
9200 compose->gtkaspell = NULL;
9202 #endif
9204 if (!compose->batch) {
9205 gtk_window_get_size(GTK_WINDOW(compose->window),
9206 &allocation.width, &allocation.height);
9207 prefs_common.compose_width = allocation.width;
9208 prefs_common.compose_height = allocation.height;
9211 if (!gtk_widget_get_parent(compose->paned))
9212 gtk_widget_destroy(compose->paned);
9213 gtk_widget_destroy(compose->popupmenu);
9215 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9216 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9217 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9219 message_search_close(compose);
9220 gtk_widget_destroy(compose->window);
9221 toolbar_destroy(compose->toolbar);
9222 g_free(compose->toolbar);
9223 g_mutex_clear(&compose->mutex);
9224 g_free(compose);
9227 static void compose_attach_info_free(AttachInfo *ainfo)
9229 g_free(ainfo->file);
9230 g_free(ainfo->content_type);
9231 g_free(ainfo->name);
9232 g_free(ainfo->charset);
9233 g_free(ainfo);
9236 static void compose_attach_update_label(Compose *compose)
9238 GtkTreeIter iter;
9239 gint i = 1;
9240 gchar *text;
9241 GtkTreeModel *model;
9242 goffset total_size;
9243 AttachInfo *ainfo;
9245 if (compose == NULL)
9246 return;
9248 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9249 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9250 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9251 return;
9254 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9255 total_size = ainfo->size;
9256 while(gtk_tree_model_iter_next(model, &iter)) {
9257 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9258 total_size += ainfo->size;
9259 i++;
9261 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9262 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9263 g_free(text);
9266 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9268 Compose *compose = (Compose *)data;
9269 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9270 GtkTreeSelection *selection;
9271 GList *sel, *cur;
9272 GtkTreeModel *model;
9274 selection = gtk_tree_view_get_selection(tree_view);
9275 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9276 cm_return_if_fail(sel);
9278 for (cur = sel; cur != NULL; cur = cur->next) {
9279 GtkTreePath *path = cur->data;
9280 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9281 (model, cur->data);
9282 cur->data = ref;
9283 gtk_tree_path_free(path);
9286 for (cur = sel; cur != NULL; cur = cur->next) {
9287 GtkTreeRowReference *ref = cur->data;
9288 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9289 GtkTreeIter iter;
9291 if (gtk_tree_model_get_iter(model, &iter, path))
9292 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9294 gtk_tree_path_free(path);
9295 gtk_tree_row_reference_free(ref);
9298 g_list_free(sel);
9299 compose_attach_update_label(compose);
9302 static struct _AttachProperty
9304 GtkWidget *window;
9305 GtkWidget *mimetype_entry;
9306 GtkWidget *encoding_optmenu;
9307 GtkWidget *path_entry;
9308 GtkWidget *filename_entry;
9309 GtkWidget *ok_btn;
9310 GtkWidget *cancel_btn;
9311 } attach_prop;
9313 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9315 gtk_tree_path_free((GtkTreePath *)ptr);
9318 static void compose_attach_property(GtkAction *action, gpointer data)
9320 Compose *compose = (Compose *)data;
9321 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9322 AttachInfo *ainfo;
9323 GtkComboBox *optmenu;
9324 GtkTreeSelection *selection;
9325 GList *sel;
9326 GtkTreeModel *model;
9327 GtkTreeIter iter;
9328 GtkTreePath *path;
9329 static gboolean cancelled;
9331 /* only if one selected */
9332 selection = gtk_tree_view_get_selection(tree_view);
9333 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9334 return;
9336 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9337 cm_return_if_fail(sel);
9339 path = (GtkTreePath *) sel->data;
9340 gtk_tree_model_get_iter(model, &iter, path);
9341 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9343 if (!ainfo) {
9344 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9345 g_list_free(sel);
9346 return;
9348 g_list_free(sel);
9350 if (!attach_prop.window)
9351 compose_attach_property_create(&cancelled);
9352 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9353 gtk_widget_grab_focus(attach_prop.ok_btn);
9354 gtk_widget_show(attach_prop.window);
9355 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9356 GTK_WINDOW(compose->window));
9358 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9359 if (ainfo->encoding == ENC_UNKNOWN)
9360 combobox_select_by_data(optmenu, ENC_BASE64);
9361 else
9362 combobox_select_by_data(optmenu, ainfo->encoding);
9364 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9365 ainfo->content_type ? ainfo->content_type : "");
9366 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9367 ainfo->file ? ainfo->file : "");
9368 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9369 ainfo->name ? ainfo->name : "");
9371 for (;;) {
9372 const gchar *entry_text;
9373 gchar *text;
9374 gchar *cnttype = NULL;
9375 gchar *file = NULL;
9376 off_t size = 0;
9378 cancelled = FALSE;
9379 gtk_main();
9381 gtk_widget_hide(attach_prop.window);
9382 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9384 if (cancelled)
9385 break;
9387 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9388 if (*entry_text != '\0') {
9389 gchar *p;
9391 text = g_strstrip(g_strdup(entry_text));
9392 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9393 cnttype = g_strdup(text);
9394 g_free(text);
9395 } else {
9396 alertpanel_error(_("Invalid MIME type."));
9397 g_free(text);
9398 continue;
9402 ainfo->encoding = combobox_get_active_data(optmenu);
9404 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9405 if (*entry_text != '\0') {
9406 if (is_file_exist(entry_text) &&
9407 (size = get_file_size(entry_text)) > 0)
9408 file = g_strdup(entry_text);
9409 else {
9410 alertpanel_error
9411 (_("File doesn't exist or is empty."));
9412 g_free(cnttype);
9413 continue;
9417 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9418 if (*entry_text != '\0') {
9419 g_free(ainfo->name);
9420 ainfo->name = g_strdup(entry_text);
9423 if (cnttype) {
9424 g_free(ainfo->content_type);
9425 ainfo->content_type = cnttype;
9427 if (file) {
9428 g_free(ainfo->file);
9429 ainfo->file = file;
9431 if (size)
9432 ainfo->size = (goffset)size;
9434 /* update tree store */
9435 text = to_human_readable(ainfo->size);
9436 gtk_tree_model_get_iter(model, &iter, path);
9437 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9438 COL_MIMETYPE, ainfo->content_type,
9439 COL_SIZE, text,
9440 COL_NAME, ainfo->name,
9441 COL_CHARSET, ainfo->charset,
9442 -1);
9444 break;
9447 gtk_tree_path_free(path);
9450 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9452 label = gtk_label_new(str); \
9453 gtk_grid_attach(GTK_GRID(table), label, 0, top, 1, 1); \
9454 gtk_label_set_xalign(GTK_LABEL(label), 0.0); \
9455 entry = gtk_entry_new(); \
9456 gtk_grid_attach(GTK_GRID(table), entry, 1, top, 1, 1); \
9459 static void compose_attach_property_create(gboolean *cancelled)
9461 GtkWidget *window;
9462 GtkWidget *vbox;
9463 GtkWidget *table;
9464 GtkWidget *label;
9465 GtkWidget *mimetype_entry;
9466 GtkWidget *hbox;
9467 GtkWidget *optmenu;
9468 GtkListStore *optmenu_menu;
9469 GtkWidget *path_entry;
9470 GtkWidget *filename_entry;
9471 GtkWidget *hbbox;
9472 GtkWidget *ok_btn;
9473 GtkWidget *cancel_btn;
9474 GList *mime_type_list, *strlist;
9475 GtkTreeIter iter;
9477 debug_print("Creating attach_property window...\n");
9479 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9480 gtk_widget_set_size_request(window, 480, -1);
9481 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9482 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9483 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9484 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
9485 g_signal_connect(G_OBJECT(window), "delete_event",
9486 G_CALLBACK(attach_property_delete_event),
9487 cancelled);
9488 g_signal_connect(G_OBJECT(window), "key_press_event",
9489 G_CALLBACK(attach_property_key_pressed),
9490 cancelled);
9492 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
9493 gtk_container_add(GTK_CONTAINER(window), vbox);
9495 table = gtk_grid_new();
9496 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9497 gtk_grid_set_row_spacing(GTK_GRID(table), 8);
9498 gtk_grid_set_column_spacing(GTK_GRID(table), 8);
9500 label = gtk_label_new(_("MIME type"));
9501 gtk_grid_attach(GTK_GRID(table), label, 0, 0, 1, 1);
9502 gtk_label_set_xalign(GTK_LABEL(label), 0.0);
9503 mimetype_entry = gtk_combo_box_text_new_with_entry();
9504 gtk_grid_attach(GTK_GRID(table), mimetype_entry, 1, 0, 1, 1);
9505 gtk_widget_set_hexpand(mimetype_entry, TRUE);
9506 gtk_widget_set_halign(mimetype_entry, GTK_ALIGN_FILL);
9508 /* stuff with list */
9509 mime_type_list = procmime_get_mime_type_list();
9510 strlist = NULL;
9511 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9512 MimeType *type = (MimeType *) mime_type_list->data;
9513 gchar *tmp;
9515 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9517 if (g_list_find_custom(strlist, tmp, (GCompareFunc)g_strcmp0))
9518 g_free(tmp);
9519 else
9520 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9521 (GCompareFunc)g_strcmp0);
9524 for (mime_type_list = strlist; mime_type_list != NULL;
9525 mime_type_list = mime_type_list->next) {
9526 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9527 g_free(mime_type_list->data);
9529 g_list_free(strlist);
9530 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9531 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9533 label = gtk_label_new(_("Encoding"));
9534 gtk_grid_attach(GTK_GRID(table), label, 0, 1, 1, 1);
9535 gtk_label_set_xalign(GTK_LABEL(label), 0.0);
9537 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
9538 gtk_grid_attach(GTK_GRID(table), hbox, 1, 1, 1, 1);
9539 gtk_widget_set_hexpand(hbox, TRUE);
9540 gtk_widget_set_halign(hbox, GTK_ALIGN_FILL);
9542 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9543 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9545 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9546 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9547 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9548 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9549 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9551 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9553 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9554 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9556 gtkut_stock_button_set_create(&hbbox, &cancel_btn, NULL, _("_Cancel"),
9557 &ok_btn, NULL, _("_OK"),
9558 NULL, NULL, NULL);
9559 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9560 gtk_widget_grab_default(ok_btn);
9562 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9563 G_CALLBACK(attach_property_ok),
9564 cancelled);
9565 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9566 G_CALLBACK(attach_property_cancel),
9567 cancelled);
9569 gtk_widget_show_all(vbox);
9571 attach_prop.window = window;
9572 attach_prop.mimetype_entry = mimetype_entry;
9573 attach_prop.encoding_optmenu = optmenu;
9574 attach_prop.path_entry = path_entry;
9575 attach_prop.filename_entry = filename_entry;
9576 attach_prop.ok_btn = ok_btn;
9577 attach_prop.cancel_btn = cancel_btn;
9580 #undef SET_LABEL_AND_ENTRY
9582 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9584 *cancelled = FALSE;
9585 gtk_main_quit();
9588 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9590 *cancelled = TRUE;
9591 gtk_main_quit();
9594 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9595 gboolean *cancelled)
9597 *cancelled = TRUE;
9598 gtk_main_quit();
9600 return TRUE;
9603 static gboolean attach_property_key_pressed(GtkWidget *widget,
9604 GdkEventKey *event,
9605 gboolean *cancelled)
9607 if (event && event->keyval == GDK_KEY_Escape) {
9608 *cancelled = TRUE;
9609 gtk_main_quit();
9611 if (event && (event->keyval == GDK_KEY_KP_Enter ||
9612 event->keyval == GDK_KEY_Return)) {
9613 *cancelled = FALSE;
9614 gtk_main_quit();
9615 return TRUE;
9617 return FALSE;
9620 static gboolean compose_can_autosave(Compose *compose)
9622 if (compose->privacy_system && compose->use_encryption)
9623 return prefs_common.autosave && prefs_common.autosave_encrypted;
9624 else
9625 return prefs_common.autosave;
9629 * compose_exec_ext_editor:
9631 * Open (and optionally embed) external editor
9633 static void compose_exec_ext_editor(Compose *compose)
9635 gchar *tmp;
9636 #ifndef G_OS_WIN32
9637 GtkWidget *socket;
9638 Window socket_wid = 0;
9639 gchar *p, *s;
9640 #endif /* G_OS_WIN32 */
9641 GPid pid;
9642 GError *error = NULL;
9643 gchar *cmd = NULL;
9644 gchar **argv;
9646 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9647 G_DIR_SEPARATOR, compose);
9649 if (compose_write_body_to_file(compose, tmp) < 0) {
9650 alertpanel_error(_("Could not write the body to file:\n%s"),
9651 tmp);
9652 g_free(tmp);
9653 return;
9656 if (compose_get_ext_editor_uses_socket()) {
9657 #ifndef G_OS_WIN32
9658 /* Only allow one socket */
9659 if (compose->exteditor_socket != NULL) {
9660 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9661 /* Move the focus off of the socket */
9662 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9664 g_free(tmp);
9665 return;
9667 /* Create the receiving GtkSocket */
9668 socket = gtk_socket_new ();
9669 g_signal_connect (G_OBJECT(socket), "plug-removed",
9670 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9671 compose);
9672 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9673 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9674 /* Realize the socket so that we can use its ID */
9675 gtk_widget_realize(socket);
9676 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9677 compose->exteditor_socket = socket;
9678 #else
9679 alertpanel_error(_("Socket communication with an external editor is not available on Windows."));
9680 g_free(tmp);
9681 return;
9682 #endif /* G_OS_WIN32 */
9685 if (compose_get_ext_editor_cmd_valid()) {
9686 if (compose_get_ext_editor_uses_socket()) {
9687 #ifndef G_OS_WIN32
9688 p = g_strdup(prefs_common_get_ext_editor_cmd());
9689 s = strstr(p, "%w");
9690 s[1] = 'u';
9691 if (strstr(p, "%s") < s)
9692 cmd = g_strdup_printf(p, tmp, socket_wid);
9693 else
9694 cmd = g_strdup_printf(p, socket_wid, tmp);
9695 g_free(p);
9696 #endif /* G_OS_WIN32 */
9697 } else {
9698 cmd = g_strdup_printf(prefs_common_get_ext_editor_cmd(), tmp);
9700 } else {
9701 if (prefs_common_get_ext_editor_cmd())
9702 g_warning("external editor command-line is invalid: '%s'",
9703 prefs_common_get_ext_editor_cmd());
9704 cmd = g_strdup_printf(DEFAULT_EDITOR_CMD, tmp);
9707 argv = strsplit_with_quote(cmd, " ", 0);
9709 if (!g_spawn_async(NULL, argv, NULL,
9710 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
9711 NULL, NULL, &pid, &error)) {
9712 alertpanel_error(_("Could not spawn the following "
9713 "external editor command:\n%s\n%s"),
9714 cmd, error ? error->message : _("Unknown error"));
9715 if (error)
9716 g_error_free(error);
9717 g_free(tmp);
9718 g_free(cmd);
9719 g_strfreev(argv);
9720 return;
9722 g_free(cmd);
9723 g_strfreev(argv);
9725 compose->exteditor_file = g_strdup(tmp);
9726 compose->exteditor_pid = pid;
9727 compose->exteditor_tag = g_child_watch_add(pid,
9728 compose_ext_editor_closed_cb,
9729 compose);
9731 compose_set_ext_editor_sensitive(compose, FALSE);
9733 g_free(tmp);
9737 * compose_ext_editor_cb:
9739 * External editor has closed (called by g_child_watch)
9741 static void compose_ext_editor_closed_cb(GPid pid, gint exit_status, gpointer data)
9743 Compose *compose = (Compose *)data;
9744 GError *error = NULL;
9745 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9746 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9747 GtkTextIter start, end;
9748 gchar *chars;
9750 #if GLIB_CHECK_VERSION(2,70,0)
9751 if (!g_spawn_check_wait_status(exit_status, &error)) {
9752 #else
9753 if (!g_spawn_check_exit_status(exit_status, &error)) {
9754 #endif
9755 alertpanel_error(
9756 _("External editor stopped with an error: %s"),
9757 error ? error->message : _("Unknown error"));
9758 if (error)
9759 g_error_free(error);
9761 g_spawn_close_pid(compose->exteditor_pid);
9763 gtk_text_buffer_set_text(buffer, "", -1);
9764 compose_insert_file(compose, compose->exteditor_file);
9765 compose_changed_cb(NULL, compose);
9767 /* Check if we should save the draft or not */
9768 if (compose_can_autosave(compose))
9769 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9771 if (claws_unlink(compose->exteditor_file) < 0)
9772 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9774 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9775 gtk_text_buffer_get_start_iter(buffer, &start);
9776 gtk_text_buffer_get_end_iter(buffer, &end);
9777 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9778 if (chars && strlen(chars) > 0)
9779 compose->modified = TRUE;
9780 g_free(chars);
9782 compose_set_ext_editor_sensitive(compose, TRUE);
9784 g_free(compose->exteditor_file);
9785 compose->exteditor_file = NULL;
9786 compose->exteditor_pid = INVALID_PID;
9787 compose->exteditor_tag = -1;
9788 if (compose->exteditor_socket) {
9789 gtk_widget_destroy(compose->exteditor_socket);
9790 compose->exteditor_socket = NULL;
9795 static gboolean compose_get_ext_editor_cmd_valid()
9797 gboolean has_s = FALSE;
9798 gboolean has_w = FALSE;
9799 const gchar *p = prefs_common_get_ext_editor_cmd();
9800 if (!p)
9801 return FALSE;
9802 while ((p = strchr(p, '%'))) {
9803 p++;
9804 if (*p == 's') {
9805 if (has_s)
9806 return FALSE;
9807 has_s = TRUE;
9808 } else if (*p == 'w') {
9809 if (has_w)
9810 return FALSE;
9811 has_w = TRUE;
9812 } else {
9813 return FALSE;
9816 return TRUE;
9819 static gboolean compose_ext_editor_kill(Compose *compose)
9821 GPid pid = compose->exteditor_pid;
9822 gchar *pidmsg = NULL;
9824 if (pid > 0) {
9825 AlertValue val;
9826 gchar *msg;
9828 pidmsg = g_strdup_printf(_("process id: %" G_PID_FORMAT), pid);
9830 msg = g_strdup_printf
9831 (_("The external editor is still working.\n"
9832 "Force terminating the process?\n"
9833 "%s"), pidmsg);
9834 val = alertpanel_full(_("Notice"), msg, NULL, _("_No"), NULL, _("_Yes"),
9835 NULL, NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9836 ALERT_WARNING);
9837 g_free(msg);
9839 if (val == G_ALERTALTERNATE) {
9840 g_source_remove(compose->exteditor_tag);
9842 #ifdef G_OS_WIN32
9843 if (!TerminateProcess(compose->exteditor_pid, 0))
9844 perror("TerminateProcess");
9845 #else
9846 if (kill(pid, SIGTERM) < 0) perror("kill");
9847 waitpid(compose->exteditor_pid, NULL, 0);
9848 #endif /* G_OS_WIN32 */
9850 g_warning("terminated %s, temporary file: %s",
9851 pidmsg, compose->exteditor_file);
9852 g_spawn_close_pid(compose->exteditor_pid);
9854 compose_set_ext_editor_sensitive(compose, TRUE);
9856 g_free(compose->exteditor_file);
9857 compose->exteditor_file = NULL;
9858 compose->exteditor_pid = INVALID_PID;
9859 compose->exteditor_tag = -1;
9860 } else {
9861 g_free(pidmsg);
9862 return FALSE;
9866 if (pidmsg)
9867 g_free(pidmsg);
9868 return TRUE;
9871 static char *ext_editor_menu_entries[] = {
9872 "Menu/Message/Send",
9873 "Menu/Message/SendLater",
9874 "Menu/Message/InsertFile",
9875 "Menu/Message/InsertSig",
9876 "Menu/Message/ReplaceSig",
9877 "Menu/Message/Save",
9878 "Menu/Message/Print",
9879 "Menu/Edit",
9880 #if USE_ENCHANT
9881 "Menu/Spelling",
9882 #endif
9883 "Menu/Tools/ShowRuler",
9884 "Menu/Tools/Actions",
9885 "Menu/Help",
9886 NULL
9889 static void compose_set_ext_editor_sensitive(Compose *compose,
9890 gboolean sensitive)
9892 int i;
9894 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9895 cm_menu_set_sensitive_full(compose->ui_manager,
9896 ext_editor_menu_entries[i], sensitive);
9899 if (compose_get_ext_editor_uses_socket()) {
9900 if (sensitive) {
9901 if (compose->exteditor_socket)
9902 gtk_widget_hide(compose->exteditor_socket);
9903 gtk_widget_show(compose->scrolledwin);
9904 if (prefs_common.show_ruler)
9905 gtk_widget_show(compose->ruler_hbox);
9906 /* Fix the focus, as it doesn't go anywhere when the
9907 * socket is hidden or destroyed */
9908 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9909 } else {
9910 g_assert (compose->exteditor_socket != NULL);
9911 /* Fix the focus, as it doesn't go anywhere when the
9912 * edit box is hidden */
9913 if (gtk_widget_is_focus(compose->text))
9914 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9915 gtk_widget_hide(compose->scrolledwin);
9916 gtk_widget_hide(compose->ruler_hbox);
9917 gtk_widget_show(compose->exteditor_socket);
9919 } else {
9920 gtk_widget_set_sensitive(compose->text, sensitive);
9922 if (compose->toolbar->send_btn)
9923 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9924 if (compose->toolbar->sendl_btn)
9925 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9926 if (compose->toolbar->draft_btn)
9927 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9928 if (compose->toolbar->insert_btn)
9929 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9930 if (compose->toolbar->sig_btn)
9931 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9932 if (compose->toolbar->exteditor_btn)
9933 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9934 if (compose->toolbar->linewrap_current_btn)
9935 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9936 if (compose->toolbar->linewrap_all_btn)
9937 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9940 static gboolean compose_get_ext_editor_uses_socket()
9942 return (prefs_common_get_ext_editor_cmd() &&
9943 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9946 #ifndef G_OS_WIN32
9947 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9949 compose->exteditor_socket = NULL;
9950 /* returning FALSE allows destruction of the socket */
9951 return FALSE;
9953 #endif /* G_OS_WIN32 */
9956 * compose_undo_state_changed:
9958 * Change the sensivity of the menuentries undo and redo
9960 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9961 gint redo_state, gpointer data)
9963 Compose *compose = (Compose *)data;
9965 switch (undo_state) {
9966 case UNDO_STATE_TRUE:
9967 if (!undostruct->undo_state) {
9968 undostruct->undo_state = TRUE;
9969 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9971 break;
9972 case UNDO_STATE_FALSE:
9973 if (undostruct->undo_state) {
9974 undostruct->undo_state = FALSE;
9975 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9977 break;
9978 case UNDO_STATE_UNCHANGED:
9979 break;
9980 case UNDO_STATE_REFRESH:
9981 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9982 break;
9983 default:
9984 g_warning("undo state not recognized");
9985 break;
9988 switch (redo_state) {
9989 case UNDO_STATE_TRUE:
9990 if (!undostruct->redo_state) {
9991 undostruct->redo_state = TRUE;
9992 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9994 break;
9995 case UNDO_STATE_FALSE:
9996 if (undostruct->redo_state) {
9997 undostruct->redo_state = FALSE;
9998 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
10000 break;
10001 case UNDO_STATE_UNCHANGED:
10002 break;
10003 case UNDO_STATE_REFRESH:
10004 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
10005 break;
10006 default:
10007 g_warning("redo state not recognized");
10008 break;
10012 /* callback functions */
10014 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10015 GtkAllocation *allocation,
10016 GtkPaned *paned)
10018 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10021 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10022 * includes "non-client" (windows-izm) in calculation, so this calculation
10023 * may not be accurate.
10025 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10026 GtkAllocation *allocation,
10027 GtkSHRuler *shruler)
10029 if (prefs_common.show_ruler) {
10030 gint char_width = 0, char_height = 0;
10031 gint line_width_in_chars;
10033 gtkut_get_font_size(GTK_WIDGET(widget),
10034 &char_width, &char_height);
10035 line_width_in_chars =
10036 (allocation->width - allocation->x) / char_width;
10038 /* got the maximum */
10039 gtk_shruler_set_range(GTK_SHRULER(shruler),
10040 0.0, line_width_in_chars, 0);
10043 return TRUE;
10046 typedef struct {
10047 gchar *header;
10048 gchar *entry;
10049 ComposePrefType type;
10050 gboolean entry_marked;
10051 } HeaderEntryState;
10053 static void account_activated(GtkComboBox *optmenu, gpointer data)
10055 Compose *compose = (Compose *)data;
10057 PrefsAccount *ac;
10058 gchar *folderidentifier;
10059 gint account_id = 0;
10060 GtkTreeModel *menu;
10061 GtkTreeIter iter;
10062 GSList *list, *saved_list = NULL;
10063 HeaderEntryState *state;
10065 /* Get ID of active account in the combo box */
10066 menu = gtk_combo_box_get_model(optmenu);
10067 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10068 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10070 ac = account_find_from_id(account_id);
10071 cm_return_if_fail(ac != NULL);
10073 if (ac != compose->account) {
10074 compose_select_account(compose, ac, FALSE);
10076 for (list = compose->header_list; list; list = list->next) {
10077 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10079 if (hentry->type == PREF_ACCOUNT || !list->next) {
10080 compose_destroy_headerentry(compose, hentry);
10081 continue;
10083 state = g_malloc0(sizeof(HeaderEntryState));
10084 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10085 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10086 state->entry = gtk_editable_get_chars(
10087 GTK_EDITABLE(hentry->entry), 0, -1);
10088 state->type = hentry->type;
10090 saved_list = g_slist_append(saved_list, state);
10091 compose_destroy_headerentry(compose, hentry);
10094 compose->header_last = NULL;
10095 g_slist_free(compose->header_list);
10096 compose->header_list = NULL;
10097 compose->header_nextrow = 1;
10098 compose_create_header_entry(compose);
10100 if (ac->set_autocc && ac->auto_cc)
10101 compose_entry_append(compose, ac->auto_cc,
10102 COMPOSE_CC, PREF_ACCOUNT);
10103 if (ac->set_autobcc && ac->auto_bcc)
10104 compose_entry_append(compose, ac->auto_bcc,
10105 COMPOSE_BCC, PREF_ACCOUNT);
10106 if (ac->set_autoreplyto && ac->auto_replyto)
10107 compose_entry_append(compose, ac->auto_replyto,
10108 COMPOSE_REPLYTO, PREF_ACCOUNT);
10110 for (list = saved_list; list; list = list->next) {
10111 state = (HeaderEntryState *) list->data;
10113 compose_add_header_entry(compose, state->header,
10114 state->entry, state->type);
10116 g_free(state->header);
10117 g_free(state->entry);
10118 g_free(state);
10120 g_slist_free(saved_list);
10122 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10123 (ac->protocol == A_NNTP) ?
10124 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10127 /* Set message save folder */
10128 compose_set_save_to(compose, NULL);
10129 if (compose->folder && compose->folder->prefs && compose->folder->prefs->save_copy_to_folder) {
10130 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10131 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10132 folderidentifier = folder_item_get_identifier(compose->folder);
10133 compose_set_save_to(compose, folderidentifier);
10134 g_free(folderidentifier);
10135 } else if (account_get_special_folder(compose->account, F_OUTBOX)) {
10136 if (compose->account->set_sent_folder || prefs_common.savemsg)
10137 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10138 else
10139 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE);
10140 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10141 folderidentifier = folder_item_get_identifier(account_get_special_folder
10142 (compose->account, F_OUTBOX));
10143 compose_set_save_to(compose, folderidentifier);
10144 g_free(folderidentifier);
10148 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10149 GtkTreeViewColumn *column, Compose *compose)
10151 compose_attach_property(NULL, compose);
10154 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10155 gpointer data)
10157 Compose *compose = (Compose *)data;
10158 GtkTreeSelection *attach_selection;
10159 gint attach_nr_selected;
10160 GtkTreePath *path;
10162 if (!event || compose->redirect_filename != NULL)
10163 return FALSE;
10165 if (event->button == 3) {
10166 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10167 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10169 /* If no rows, or just one row is selected, right-click should
10170 * open menu relevant to the row being right-clicked on. We
10171 * achieve that by selecting the clicked row first. If more
10172 * than one row is selected, we shouldn't modify the selection,
10173 * as user may want to remove selected rows (attachments). */
10174 if (attach_nr_selected < 2) {
10175 gtk_tree_selection_unselect_all(attach_selection);
10176 attach_nr_selected = 0;
10177 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10178 event->x, event->y, &path, NULL, NULL, NULL);
10179 if (path != NULL) {
10180 gtk_tree_selection_select_path(attach_selection, path);
10181 gtk_tree_path_free(path);
10182 attach_nr_selected++;
10186 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10187 /* Properties menu item makes no sense with more than one row
10188 * selected, the properties dialog can only edit one attachment. */
10189 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10191 gtk_menu_popup_at_pointer(GTK_MENU(compose->popupmenu), NULL);
10193 return TRUE;
10196 return FALSE;
10199 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10200 gpointer data)
10202 Compose *compose = (Compose *)data;
10204 if (!event) return FALSE;
10206 switch (event->keyval) {
10207 case GDK_KEY_Delete:
10208 compose_attach_remove_selected(NULL, compose);
10209 break;
10211 return FALSE;
10214 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10216 toolbar_comp_set_sensitive(compose, allow);
10217 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10218 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10219 #if USE_ENCHANT
10220 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10221 #endif
10222 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10223 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10224 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10226 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10230 static void compose_send_cb(GtkAction *action, gpointer data)
10232 Compose *compose = (Compose *)data;
10234 #ifdef G_OS_UNIX
10235 if (compose->exteditor_tag != -1) {
10236 debug_print("ignoring send: external editor still open\n");
10237 return;
10239 #endif
10240 if (prefs_common.work_offline &&
10241 !inc_offline_should_override(TRUE,
10242 _("Claws Mail needs network access in order "
10243 "to send this email.")))
10244 return;
10246 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10247 g_source_remove(compose->draft_timeout_tag);
10248 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10251 compose_send(compose);
10254 static void compose_send_later_cb(GtkAction *action, gpointer data)
10256 Compose *compose = (Compose *)data;
10257 ComposeQueueResult val;
10259 inc_lock();
10260 compose_allow_user_actions(compose, FALSE);
10261 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10262 compose_allow_user_actions(compose, TRUE);
10263 inc_unlock();
10265 if (val == COMPOSE_QUEUE_SUCCESS) {
10266 compose_close(compose);
10267 } else {
10268 _display_queue_error(val);
10271 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10274 #define DRAFTED_AT_EXIT "drafted_at_exit"
10275 static void compose_register_draft(MsgInfo *info)
10277 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10278 DRAFTED_AT_EXIT, NULL);
10279 FILE *fp = claws_fopen(filepath, "ab");
10281 if (fp) {
10282 gchar *name = folder_item_get_identifier(info->folder);
10283 fprintf(fp, "%s\t%d\n", name, info->msgnum);
10284 g_free(name);
10285 claws_fclose(fp);
10288 g_free(filepath);
10291 gboolean compose_draft (gpointer data, guint action)
10293 Compose *compose = (Compose *)data;
10294 FolderItem *draft;
10295 FolderItemPrefs *prefs;
10296 gchar *tmp;
10297 gchar *sheaders;
10298 gint msgnum;
10299 MsgFlags flag = {0, 0};
10300 static gboolean lock = FALSE;
10301 MsgInfo *newmsginfo;
10302 FILE *fp;
10303 gboolean target_locked = FALSE;
10304 gboolean err = FALSE;
10305 gint filemode = 0;
10307 if (lock) return FALSE;
10309 if (compose->sending)
10310 return TRUE;
10312 draft = account_get_special_folder(compose->account, F_DRAFT);
10313 cm_return_val_if_fail(draft != NULL, FALSE);
10315 if (!g_mutex_trylock(&compose->mutex)) {
10316 /* we don't want to lock the mutex once it's available,
10317 * because as the only other part of compose.c locking
10318 * it is compose_close - which means once unlocked,
10319 * the compose struct will be freed */
10320 debug_print("couldn't lock mutex, probably sending\n");
10321 return FALSE;
10324 lock = TRUE;
10326 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10327 G_DIR_SEPARATOR, compose);
10328 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10329 FILE_OP_ERROR(tmp, "claws_fopen");
10330 goto warn_err;
10333 /* chmod for security unless folder chmod is set */
10334 prefs = draft->prefs;
10335 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) {
10336 filemode = prefs->folder_chmod;
10337 if (filemode & S_IRGRP) filemode |= S_IWGRP;
10338 if (filemode & S_IROTH) filemode |= S_IWOTH;
10339 if (chmod(tmp, filemode) < 0)
10340 FILE_OP_ERROR(tmp, "chmod");
10341 } else if (change_file_mode_rw(fp, tmp) < 0) {
10342 FILE_OP_ERROR(tmp, "chmod");
10343 g_warning("can't change file mode");
10346 /* Save draft infos */
10347 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10348 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10350 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10351 gchar *savefolderid;
10353 savefolderid = compose_get_save_to(compose);
10354 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10355 g_free(savefolderid);
10357 if (compose->return_receipt) {
10358 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10360 if (compose->privacy_system) {
10361 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10362 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10363 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10366 /* Message-ID of message replying to */
10367 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10368 gchar *folderid = NULL;
10370 if (compose->replyinfo->folder)
10371 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10372 if (folderid == NULL)
10373 folderid = g_strdup("NULL");
10375 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10376 g_free(folderid);
10378 /* Message-ID of message forwarding to */
10379 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10380 gchar *folderid = NULL;
10382 if (compose->fwdinfo->folder)
10383 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10384 if (folderid == NULL)
10385 folderid = g_strdup("NULL");
10387 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10388 g_free(folderid);
10391 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10392 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10394 sheaders = compose_get_manual_headers_info(compose);
10395 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10396 g_free(sheaders);
10398 /* end of headers */
10399 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10401 if (err) {
10402 claws_fclose(fp);
10403 goto warn_err;
10406 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10407 claws_fclose(fp);
10408 goto warn_err;
10410 if (claws_safe_fclose(fp) == EOF) {
10411 goto warn_err;
10414 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10415 if (compose->targetinfo) {
10416 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10417 if (target_locked)
10418 flag.perm_flags |= MSG_LOCKED;
10420 flag.tmp_flags = MSG_DRAFT;
10422 folder_item_scan(draft);
10423 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10424 MsgInfo *tmpinfo = NULL;
10425 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10426 if (compose->msgid) {
10427 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10429 if (tmpinfo) {
10430 msgnum = tmpinfo->msgnum;
10431 procmsg_msginfo_free(&tmpinfo);
10432 debug_print("got draft msgnum %d from scanning\n", msgnum);
10433 } else {
10434 debug_print("didn't get draft msgnum after scanning\n");
10436 } else {
10437 debug_print("got draft msgnum %d from adding\n", msgnum);
10439 if (msgnum < 0) {
10440 warn_err:
10441 claws_unlink(tmp);
10442 g_free(tmp);
10443 if (action != COMPOSE_AUTO_SAVE) {
10444 if (action != COMPOSE_DRAFT_FOR_EXIT)
10445 alertpanel_error(_("Could not save draft."));
10446 else {
10447 AlertValue val;
10448 gtkut_window_popup(compose->window);
10449 val = alertpanel_full(_("Could not save draft"),
10450 _("Could not save draft.\n"
10451 "Do you want to cancel exit or discard this email?"),
10452 NULL, _("_Cancel exit"), NULL, _("_Discard email"),
10453 NULL, NULL, ALERTFOCUS_FIRST, FALSE, NULL, ALERT_QUESTION);
10454 if (val == G_ALERTALTERNATE) {
10455 lock = FALSE;
10456 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10457 compose_close(compose);
10458 return TRUE;
10459 } else {
10460 lock = FALSE;
10461 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10462 return FALSE;
10466 goto unlock;
10468 g_free(tmp);
10470 if (compose->mode == COMPOSE_REEDIT) {
10471 compose_remove_reedit_target(compose, TRUE);
10474 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10476 if (newmsginfo) {
10477 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10478 if (target_locked)
10479 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10480 else
10481 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10482 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10483 procmsg_msginfo_set_flags(newmsginfo, 0,
10484 MSG_HAS_ATTACHMENT);
10486 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10487 compose_register_draft(newmsginfo);
10489 procmsg_msginfo_free(&newmsginfo);
10492 folder_item_scan(draft);
10494 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10495 lock = FALSE;
10496 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10497 compose_close(compose);
10498 return TRUE;
10499 } else {
10500 #ifdef G_OS_WIN32
10501 GFile *f;
10502 GFileInfo *fi;
10503 GTimeVal tv;
10504 GError *error = NULL;
10505 #else
10506 GStatBuf s;
10507 #endif
10508 gchar *path;
10509 goffset size, mtime;
10511 path = folder_item_fetch_msg(draft, msgnum);
10512 if (path == NULL) {
10513 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10514 goto unlock;
10516 #ifdef G_OS_WIN32
10517 f = g_file_new_for_path(path);
10518 fi = g_file_query_info(f, "standard::size,time::modified",
10519 G_FILE_QUERY_INFO_NONE, NULL, &error);
10520 if (error != NULL) {
10521 debug_print("couldn't query file info for '%s': %s\n",
10522 path, error->message);
10523 g_error_free(error);
10524 g_free(path);
10525 g_object_unref(f);
10526 goto unlock;
10528 size = g_file_info_get_size(fi);
10529 g_file_info_get_modification_time(fi, &tv);
10530 mtime = tv.tv_sec;
10531 g_object_unref(fi);
10532 g_object_unref(f);
10533 #else
10534 if (g_stat(path, &s) < 0) {
10535 FILE_OP_ERROR(path, "stat");
10536 g_free(path);
10537 goto unlock;
10539 size = s.st_size;
10540 mtime = s.st_mtime;
10541 #endif
10542 g_free(path);
10544 procmsg_msginfo_free(&(compose->targetinfo));
10545 compose->targetinfo = procmsg_msginfo_new();
10546 compose->targetinfo->msgnum = msgnum;
10547 compose->targetinfo->size = size;
10548 compose->targetinfo->mtime = mtime;
10549 compose->targetinfo->folder = draft;
10550 if (target_locked)
10551 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10552 compose->mode = COMPOSE_REEDIT;
10554 if (action == COMPOSE_AUTO_SAVE) {
10555 compose->modified = FALSE;
10556 compose->autosaved_draft = compose->targetinfo;
10558 compose_set_title(compose);
10560 unlock:
10561 lock = FALSE;
10562 g_mutex_unlock(&compose->mutex);
10563 return TRUE;
10566 void compose_clear_exit_drafts(void)
10568 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10569 DRAFTED_AT_EXIT, NULL);
10570 if (is_file_exist(filepath))
10571 claws_unlink(filepath);
10573 g_free(filepath);
10576 void compose_reopen_exit_drafts(void)
10578 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10579 DRAFTED_AT_EXIT, NULL);
10580 FILE *fp = claws_fopen(filepath, "rb");
10581 gchar buf[1024];
10583 if (fp) {
10584 while (claws_fgets(buf, sizeof(buf), fp)) {
10585 gchar **parts = g_strsplit(buf, "\t", 2);
10586 const gchar *folder = parts[0];
10587 int msgnum = parts[1] ? atoi(parts[1]):-1;
10589 if (folder && *folder && msgnum > -1) {
10590 FolderItem *item = folder_find_item_from_identifier(folder);
10591 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10592 if (info)
10593 compose_reedit(info, FALSE);
10595 g_strfreev(parts);
10597 claws_fclose(fp);
10599 g_free(filepath);
10600 compose_clear_exit_drafts();
10603 static void compose_save_cb(GtkAction *action, gpointer data)
10605 Compose *compose = (Compose *)data;
10606 compose_draft(compose, COMPOSE_KEEP_EDITING);
10607 compose->rmode = COMPOSE_REEDIT;
10608 compose->modified = FALSE;
10609 compose_set_title(compose);
10612 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10614 if (compose && file_list) {
10615 GList *tmp;
10617 for ( tmp = file_list; tmp; tmp = tmp->next) {
10618 gchar *file = (gchar *) tmp->data;
10619 gchar *utf8_filename = conv_filename_to_utf8(file);
10620 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10621 compose_changed_cb(NULL, compose);
10622 if (free_data) {
10623 g_free(file);
10624 tmp->data = NULL;
10626 g_free(utf8_filename);
10631 static void compose_attach_cb(GtkAction *action, gpointer data)
10633 Compose *compose = (Compose *)data;
10634 GList *file_list;
10636 if (compose->redirect_filename != NULL)
10637 return;
10639 /* Set focus_window properly, in case we were called via popup menu,
10640 * which unsets it (via focus_out_event callback on compose window). */
10641 manage_window_focus_in(compose->window, NULL, NULL);
10643 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10645 if (file_list) {
10646 compose_attach_from_list(compose, file_list, TRUE);
10647 g_list_free(file_list);
10651 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10653 Compose *compose = (Compose *)data;
10654 GList *file_list;
10655 gint files_inserted = 0;
10657 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10659 if (file_list) {
10660 GList *tmp;
10662 for ( tmp = file_list; tmp; tmp = tmp->next) {
10663 gchar *file = (gchar *) tmp->data;
10664 gchar *filedup = g_strdup(file);
10665 gchar *shortfile = g_path_get_basename(filedup);
10666 ComposeInsertResult res;
10667 /* insert the file if the file is short or if the user confirmed that
10668 he/she wants to insert the large file */
10669 res = compose_insert_file(compose, file);
10670 if (res == COMPOSE_INSERT_READ_ERROR) {
10671 alertpanel_error(_("File '%s' could not be read."), shortfile);
10672 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10673 alertpanel_error(_("File '%s' contained invalid characters\n"
10674 "for the current encoding, insertion may be incorrect."),
10675 shortfile);
10676 } else if (res == COMPOSE_INSERT_SUCCESS)
10677 files_inserted++;
10679 g_free(shortfile);
10680 g_free(filedup);
10681 g_free(file);
10683 g_list_free(file_list);
10686 #ifdef USE_ENCHANT
10687 if (files_inserted > 0 && compose->gtkaspell &&
10688 compose->gtkaspell->check_while_typing)
10689 gtkaspell_highlight_all(compose->gtkaspell);
10690 #endif
10693 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10695 Compose *compose = (Compose *)data;
10697 compose_insert_sig(compose, FALSE);
10700 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10702 Compose *compose = (Compose *)data;
10704 compose_insert_sig(compose, TRUE);
10707 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10708 gpointer data)
10710 gint x, y;
10711 Compose *compose = (Compose *)data;
10713 gtk_window_get_position(GTK_WINDOW(widget), &x, &y);
10714 if (!compose->batch) {
10715 prefs_common.compose_x = x;
10716 prefs_common.compose_y = y;
10718 if (compose->sending || compose->updating)
10719 return TRUE;
10720 compose_close_cb(NULL, compose);
10721 return TRUE;
10724 void compose_close_toolbar(Compose *compose)
10726 compose_close_cb(NULL, compose);
10729 static void compose_close_cb(GtkAction *action, gpointer data)
10731 Compose *compose = (Compose *)data;
10732 AlertValue val;
10734 if (compose->exteditor_tag != -1) {
10735 if (!compose_ext_editor_kill(compose))
10736 return;
10739 if (compose->modified) {
10740 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10741 if (!g_mutex_trylock(&compose->mutex)) {
10742 /* we don't want to lock the mutex once it's available,
10743 * because as the only other part of compose.c locking
10744 * it is compose_close - which means once unlocked,
10745 * the compose struct will be freed */
10746 debug_print("couldn't lock mutex, probably sending\n");
10747 return;
10749 if (!reedit || (compose->folder != NULL && compose->folder->stype == F_DRAFT)) {
10750 val = alertpanel(_("Discard message"),
10751 _("This message has been modified. Discard it?"),
10752 NULL, _("_Discard"), NULL, _("_Save to Drafts"), NULL, _("_Cancel"),
10753 ALERTFOCUS_FIRST);
10754 } else {
10755 val = alertpanel(_("Save changes"),
10756 _("This message has been modified. Save the latest changes?"),
10757 NULL, _("_Don't save"), NULL, _("_Save to Drafts"), NULL, _("_Cancel"),
10758 ALERTFOCUS_SECOND);
10760 g_mutex_unlock(&compose->mutex);
10761 switch (val) {
10762 case G_ALERTDEFAULT:
10763 if (compose_can_autosave(compose) && !reedit)
10764 compose_remove_draft(compose);
10765 break;
10766 case G_ALERTALTERNATE:
10767 compose_draft(data, COMPOSE_QUIT_EDITING);
10768 return;
10769 default:
10770 return;
10774 compose_close(compose);
10777 static void compose_print_cb(GtkAction *action, gpointer data)
10779 Compose *compose = (Compose *) data;
10781 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10782 if (compose->targetinfo)
10783 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10786 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10788 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10789 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10790 Compose *compose = (Compose *) data;
10792 if (active)
10793 compose->out_encoding = (CharSet)value;
10796 static void compose_address_cb(GtkAction *action, gpointer data)
10798 Compose *compose = (Compose *)data;
10800 #ifndef USE_ALT_ADDRBOOK
10801 addressbook_open(compose);
10802 #else
10803 GError* error = NULL;
10804 addressbook_connect_signals(compose);
10805 addressbook_dbus_open(TRUE, &error);
10806 if (error) {
10807 g_warning("%s", error->message);
10808 g_error_free(error);
10810 #endif
10813 static void about_show_cb(GtkAction *action, gpointer data)
10815 about_show();
10818 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10820 Compose *compose = (Compose *)data;
10821 Template *tmpl;
10822 gchar *msg;
10823 AlertValue val;
10825 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10826 cm_return_if_fail(tmpl != NULL);
10828 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10829 tmpl->name);
10830 val = alertpanel(_("Apply template"), msg,
10831 NULL, _("_Replace"), NULL, _("_Insert"), NULL, _("_Cancel"),
10832 ALERTFOCUS_FIRST);
10833 g_free(msg);
10835 if (val == G_ALERTDEFAULT)
10836 compose_template_apply(compose, tmpl, TRUE);
10837 else if (val == G_ALERTALTERNATE)
10838 compose_template_apply(compose, tmpl, FALSE);
10841 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10843 Compose *compose = (Compose *)data;
10845 if (compose->exteditor_tag != -1) {
10846 debug_print("ignoring open external editor: external editor still open\n");
10847 return;
10849 compose_exec_ext_editor(compose);
10852 static void compose_undo_cb(GtkAction *action, gpointer data)
10854 Compose *compose = (Compose *)data;
10855 gboolean prev_autowrap = compose->autowrap;
10857 compose->autowrap = FALSE;
10858 undo_undo(compose->undostruct);
10859 compose->autowrap = prev_autowrap;
10862 static void compose_redo_cb(GtkAction *action, gpointer data)
10864 Compose *compose = (Compose *)data;
10865 gboolean prev_autowrap = compose->autowrap;
10867 compose->autowrap = FALSE;
10868 undo_redo(compose->undostruct);
10869 compose->autowrap = prev_autowrap;
10872 static void entry_cut_clipboard(GtkWidget *entry)
10874 if (GTK_IS_EDITABLE(entry))
10875 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10876 else if (GTK_IS_TEXT_VIEW(entry))
10877 gtk_text_buffer_cut_clipboard(
10878 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10879 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10880 TRUE);
10883 static void entry_copy_clipboard(GtkWidget *entry)
10885 if (GTK_IS_EDITABLE(entry))
10886 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10887 else if (GTK_IS_TEXT_VIEW(entry))
10888 gtk_text_buffer_copy_clipboard(
10889 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10890 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10893 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10894 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10896 if (GTK_IS_TEXT_VIEW(entry)) {
10897 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10898 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10899 GtkTextIter start_iter, end_iter;
10900 gint start, end;
10901 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10903 if (contents == NULL)
10904 return;
10906 /* we shouldn't delete the selection when middle-click-pasting, or we
10907 * can't mid-click-paste our own selection */
10908 if (clip != GDK_SELECTION_PRIMARY) {
10909 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10910 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10913 if (insert_place == NULL) {
10914 /* if insert_place isn't specified, insert at the cursor.
10915 * used for Ctrl-V pasting */
10916 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10917 start = gtk_text_iter_get_offset(&start_iter);
10918 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10919 } else {
10920 /* if insert_place is specified, paste here.
10921 * used for mid-click-pasting */
10922 start = gtk_text_iter_get_offset(insert_place);
10923 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10924 if (prefs_common.primary_paste_unselects)
10925 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10928 if (!wrap) {
10929 /* paste unwrapped: mark the paste so it's not wrapped later */
10930 end = start + strlen(contents);
10931 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10932 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10933 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10934 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10935 /* rewrap paragraph now (after a mid-click-paste) */
10936 mark_start = gtk_text_buffer_get_insert(buffer);
10937 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10938 gtk_text_iter_backward_char(&start_iter);
10939 compose_beautify_paragraph(compose, &start_iter, TRUE);
10941 } else if (GTK_IS_EDITABLE(entry))
10942 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10944 compose->modified = TRUE;
10947 static void entry_allsel(GtkWidget *entry)
10949 if (GTK_IS_EDITABLE(entry))
10950 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10951 else if (GTK_IS_TEXT_VIEW(entry)) {
10952 GtkTextIter startiter, enditer;
10953 GtkTextBuffer *textbuf;
10955 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10956 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10957 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10959 gtk_text_buffer_move_mark_by_name(textbuf,
10960 "selection_bound", &startiter);
10961 gtk_text_buffer_move_mark_by_name(textbuf,
10962 "insert", &enditer);
10966 static void compose_cut_cb(GtkAction *action, gpointer data)
10968 Compose *compose = (Compose *)data;
10969 if (compose->focused_editable
10970 #ifndef GENERIC_UMPC
10971 && gtk_widget_has_focus(compose->focused_editable)
10972 #endif
10974 entry_cut_clipboard(compose->focused_editable);
10977 static void compose_copy_cb(GtkAction *action, gpointer data)
10979 Compose *compose = (Compose *)data;
10980 if (compose->focused_editable
10981 #ifndef GENERIC_UMPC
10982 && gtk_widget_has_focus(compose->focused_editable)
10983 #endif
10985 entry_copy_clipboard(compose->focused_editable);
10988 static void compose_paste_cb(GtkAction *action, gpointer data)
10990 Compose *compose = (Compose *)data;
10991 gint prev_autowrap;
10992 GtkTextBuffer *buffer;
10993 BLOCK_WRAP();
10994 if (compose->focused_editable
10995 #ifndef GENERIC_UMPC
10996 && gtk_widget_has_focus(compose->focused_editable)
10997 #endif
10999 entry_paste_clipboard(compose, compose->focused_editable,
11000 prefs_common.linewrap_pastes,
11001 GDK_SELECTION_CLIPBOARD, NULL);
11002 UNBLOCK_WRAP();
11004 #ifdef USE_ENCHANT
11005 if (
11006 #ifndef GENERIC_UMPC
11007 gtk_widget_has_focus(compose->text) &&
11008 #endif
11009 compose->gtkaspell &&
11010 compose->gtkaspell->check_while_typing)
11011 gtkaspell_highlight_all(compose->gtkaspell);
11012 #endif
11015 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11017 Compose *compose = (Compose *)data;
11018 gint wrap_quote = prefs_common.linewrap_quote;
11019 if (compose->focused_editable
11020 #ifndef GENERIC_UMPC
11021 && gtk_widget_has_focus(compose->focused_editable)
11022 #endif
11024 /* let text_insert() (called directly or at a later time
11025 * after the gtk_editable_paste_clipboard) know that
11026 * text is to be inserted as a quotation. implemented
11027 * by using a simple refcount... */
11028 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11029 G_OBJECT(compose->focused_editable),
11030 "paste_as_quotation"));
11031 g_object_set_data(G_OBJECT(compose->focused_editable),
11032 "paste_as_quotation",
11033 GINT_TO_POINTER(paste_as_quotation + 1));
11034 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11035 entry_paste_clipboard(compose, compose->focused_editable,
11036 prefs_common.linewrap_pastes,
11037 GDK_SELECTION_CLIPBOARD, NULL);
11038 prefs_common.linewrap_quote = wrap_quote;
11042 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11044 Compose *compose = (Compose *)data;
11045 gint prev_autowrap;
11046 GtkTextBuffer *buffer;
11047 BLOCK_WRAP();
11048 if (compose->focused_editable
11049 #ifndef GENERIC_UMPC
11050 && gtk_widget_has_focus(compose->focused_editable)
11051 #endif
11053 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11054 GDK_SELECTION_CLIPBOARD, NULL);
11055 UNBLOCK_WRAP();
11057 #ifdef USE_ENCHANT
11058 if (
11059 #ifndef GENERIC_UMPC
11060 gtk_widget_has_focus(compose->text) &&
11061 #endif
11062 compose->gtkaspell &&
11063 compose->gtkaspell->check_while_typing)
11064 gtkaspell_highlight_all(compose->gtkaspell);
11065 #endif
11068 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11070 Compose *compose = (Compose *)data;
11071 gint prev_autowrap;
11072 GtkTextBuffer *buffer;
11073 BLOCK_WRAP();
11074 if (compose->focused_editable
11075 #ifndef GENERIC_UMPC
11076 && gtk_widget_has_focus(compose->focused_editable)
11077 #endif
11079 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11080 GDK_SELECTION_CLIPBOARD, NULL);
11081 UNBLOCK_WRAP();
11083 #ifdef USE_ENCHANT
11084 if (
11085 #ifndef GENERIC_UMPC
11086 gtk_widget_has_focus(compose->text) &&
11087 #endif
11088 compose->gtkaspell &&
11089 compose->gtkaspell->check_while_typing)
11090 gtkaspell_highlight_all(compose->gtkaspell);
11091 #endif
11094 static void compose_allsel_cb(GtkAction *action, gpointer data)
11096 Compose *compose = (Compose *)data;
11097 if (compose->focused_editable
11098 #ifndef GENERIC_UMPC
11099 && gtk_widget_has_focus(compose->focused_editable)
11100 #endif
11102 entry_allsel(compose->focused_editable);
11105 static void textview_move_beginning_of_line (GtkTextView *text)
11107 GtkTextBuffer *buffer;
11108 GtkTextMark *mark;
11109 GtkTextIter ins;
11111 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11113 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11114 mark = gtk_text_buffer_get_insert(buffer);
11115 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11116 gtk_text_iter_set_line_offset(&ins, 0);
11117 gtk_text_buffer_place_cursor(buffer, &ins);
11120 static void textview_move_forward_character (GtkTextView *text)
11122 GtkTextBuffer *buffer;
11123 GtkTextMark *mark;
11124 GtkTextIter ins;
11126 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11128 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11129 mark = gtk_text_buffer_get_insert(buffer);
11130 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11131 if (gtk_text_iter_forward_cursor_position(&ins))
11132 gtk_text_buffer_place_cursor(buffer, &ins);
11135 static void textview_move_backward_character (GtkTextView *text)
11137 GtkTextBuffer *buffer;
11138 GtkTextMark *mark;
11139 GtkTextIter ins;
11141 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11143 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11144 mark = gtk_text_buffer_get_insert(buffer);
11145 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11146 if (gtk_text_iter_backward_cursor_position(&ins))
11147 gtk_text_buffer_place_cursor(buffer, &ins);
11150 static void textview_move_forward_word (GtkTextView *text)
11152 GtkTextBuffer *buffer;
11153 GtkTextMark *mark;
11154 GtkTextIter ins;
11155 gint count;
11157 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11159 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11160 mark = gtk_text_buffer_get_insert(buffer);
11161 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11162 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11163 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11164 gtk_text_iter_backward_word_start(&ins);
11165 gtk_text_buffer_place_cursor(buffer, &ins);
11169 static void textview_move_backward_word (GtkTextView *text)
11171 GtkTextBuffer *buffer;
11172 GtkTextMark *mark;
11173 GtkTextIter ins;
11175 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11177 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11178 mark = gtk_text_buffer_get_insert(buffer);
11179 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11180 if (gtk_text_iter_backward_word_starts(&ins, 1))
11181 gtk_text_buffer_place_cursor(buffer, &ins);
11184 static void textview_move_end_of_line (GtkTextView *text)
11186 GtkTextBuffer *buffer;
11187 GtkTextMark *mark;
11188 GtkTextIter ins;
11190 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11192 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11193 mark = gtk_text_buffer_get_insert(buffer);
11194 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11195 if (gtk_text_iter_forward_to_line_end(&ins))
11196 gtk_text_buffer_place_cursor(buffer, &ins);
11199 static void textview_move_next_line (GtkTextView *text)
11201 GtkTextBuffer *buffer;
11202 GtkTextMark *mark;
11203 GtkTextIter ins;
11204 gint offset;
11206 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11208 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11209 mark = gtk_text_buffer_get_insert(buffer);
11210 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11211 offset = gtk_text_iter_get_line_offset(&ins);
11212 if (gtk_text_iter_forward_line(&ins)) {
11213 gtk_text_iter_set_line_offset(&ins, offset);
11214 gtk_text_buffer_place_cursor(buffer, &ins);
11218 static void textview_move_previous_line (GtkTextView *text)
11220 GtkTextBuffer *buffer;
11221 GtkTextMark *mark;
11222 GtkTextIter ins;
11223 gint offset;
11225 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11227 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11228 mark = gtk_text_buffer_get_insert(buffer);
11229 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11230 offset = gtk_text_iter_get_line_offset(&ins);
11231 if (gtk_text_iter_backward_line(&ins)) {
11232 gtk_text_iter_set_line_offset(&ins, offset);
11233 gtk_text_buffer_place_cursor(buffer, &ins);
11237 static void textview_delete_forward_character (GtkTextView *text)
11239 GtkTextBuffer *buffer;
11240 GtkTextMark *mark;
11241 GtkTextIter ins, end_iter;
11243 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11245 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11246 mark = gtk_text_buffer_get_insert(buffer);
11247 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11248 end_iter = ins;
11249 if (gtk_text_iter_forward_char(&end_iter)) {
11250 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11254 static void textview_delete_backward_character (GtkTextView *text)
11256 GtkTextBuffer *buffer;
11257 GtkTextMark *mark;
11258 GtkTextIter ins, end_iter;
11260 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11262 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11263 mark = gtk_text_buffer_get_insert(buffer);
11264 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11265 end_iter = ins;
11266 if (gtk_text_iter_backward_char(&end_iter)) {
11267 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11271 static void textview_delete_forward_word (GtkTextView *text)
11273 GtkTextBuffer *buffer;
11274 GtkTextMark *mark;
11275 GtkTextIter ins, end_iter;
11277 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11279 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11280 mark = gtk_text_buffer_get_insert(buffer);
11281 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11282 end_iter = ins;
11283 if (gtk_text_iter_forward_word_end(&end_iter)) {
11284 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11288 static void textview_delete_backward_word (GtkTextView *text)
11290 GtkTextBuffer *buffer;
11291 GtkTextMark *mark;
11292 GtkTextIter ins, end_iter;
11294 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11296 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11297 mark = gtk_text_buffer_get_insert(buffer);
11298 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11299 end_iter = ins;
11300 if (gtk_text_iter_backward_word_start(&end_iter)) {
11301 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11305 static void textview_delete_line (GtkTextView *text)
11307 GtkTextBuffer *buffer;
11308 GtkTextMark *mark;
11309 GtkTextIter ins, start_iter, end_iter;
11311 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11313 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11314 mark = gtk_text_buffer_get_insert(buffer);
11315 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11317 start_iter = ins;
11318 gtk_text_iter_set_line_offset(&start_iter, 0);
11320 end_iter = ins;
11321 if (gtk_text_iter_ends_line(&end_iter)){
11322 if (!gtk_text_iter_forward_char(&end_iter))
11323 gtk_text_iter_backward_char(&start_iter);
11325 else
11326 gtk_text_iter_forward_to_line_end(&end_iter);
11327 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11330 static void textview_delete_to_line_end (GtkTextView *text)
11332 GtkTextBuffer *buffer;
11333 GtkTextMark *mark;
11334 GtkTextIter ins, end_iter;
11336 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11338 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11339 mark = gtk_text_buffer_get_insert(buffer);
11340 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11341 end_iter = ins;
11342 if (gtk_text_iter_ends_line(&end_iter))
11343 gtk_text_iter_forward_char(&end_iter);
11344 else
11345 gtk_text_iter_forward_to_line_end(&end_iter);
11346 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11349 #define DO_ACTION(name, act) { \
11350 if(!strcmp(name, a_name)) { \
11351 return act; \
11354 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11356 const gchar *a_name = gtk_action_get_name(action);
11357 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11358 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11359 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11360 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11361 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11362 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11363 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11364 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11365 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11366 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11367 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11368 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11369 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11370 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11371 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11374 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11376 Compose *compose = (Compose *)data;
11377 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11378 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11380 action = compose_call_advanced_action_from_path(gaction);
11382 static struct {
11383 void (*do_action) (GtkTextView *text);
11384 } action_table[] = {
11385 {textview_move_beginning_of_line},
11386 {textview_move_forward_character},
11387 {textview_move_backward_character},
11388 {textview_move_forward_word},
11389 {textview_move_backward_word},
11390 {textview_move_end_of_line},
11391 {textview_move_next_line},
11392 {textview_move_previous_line},
11393 {textview_delete_forward_character},
11394 {textview_delete_backward_character},
11395 {textview_delete_forward_word},
11396 {textview_delete_backward_word},
11397 {textview_delete_line},
11398 {textview_delete_to_line_end}
11401 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11403 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11404 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11405 if (action_table[action].do_action)
11406 action_table[action].do_action(text);
11407 else
11408 g_warning("not implemented yet");
11412 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11414 GtkAllocation allocation;
11415 GtkWidget *parent;
11416 gchar *str = NULL;
11418 if (GTK_IS_EDITABLE(widget)) {
11419 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11420 gtk_editable_set_position(GTK_EDITABLE(widget),
11421 strlen(str));
11422 g_free(str);
11423 if ((parent = gtk_widget_get_parent(widget))
11424 && (parent = gtk_widget_get_parent(parent))
11425 && (parent = gtk_widget_get_parent(parent))) {
11426 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11427 gtk_widget_get_allocation(widget, &allocation);
11428 gint y = allocation.y;
11429 gint height = allocation.height;
11430 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11431 (GTK_SCROLLED_WINDOW(parent));
11433 gfloat value = gtk_adjustment_get_value(shown);
11434 gfloat upper = gtk_adjustment_get_upper(shown);
11435 gfloat page_size = gtk_adjustment_get_page_size(shown);
11436 if (y < (int)value) {
11437 gtk_adjustment_set_value(shown, y - 1);
11439 if ((y + height) > ((int)value + (int)page_size)) {
11440 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11441 gtk_adjustment_set_value(shown,
11442 y + height - (int)page_size - 1);
11443 } else {
11444 gtk_adjustment_set_value(shown,
11445 (int)upper - (int)page_size - 1);
11452 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11453 compose->focused_editable = widget;
11455 #ifdef GENERIC_UMPC
11456 if (GTK_IS_TEXT_VIEW(widget)
11457 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11458 g_object_ref(compose->notebook);
11459 g_object_ref(compose->edit_vbox);
11460 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11461 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11462 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11463 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11464 g_object_unref(compose->notebook);
11465 g_object_unref(compose->edit_vbox);
11466 g_signal_handlers_block_by_func(G_OBJECT(widget),
11467 G_CALLBACK(compose_grab_focus_cb),
11468 compose);
11469 gtk_widget_grab_focus(widget);
11470 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11471 G_CALLBACK(compose_grab_focus_cb),
11472 compose);
11473 } else if (!GTK_IS_TEXT_VIEW(widget)
11474 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11475 g_object_ref(compose->notebook);
11476 g_object_ref(compose->edit_vbox);
11477 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11478 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11479 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11480 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11481 g_object_unref(compose->notebook);
11482 g_object_unref(compose->edit_vbox);
11483 g_signal_handlers_block_by_func(G_OBJECT(widget),
11484 G_CALLBACK(compose_grab_focus_cb),
11485 compose);
11486 gtk_widget_grab_focus(widget);
11487 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11488 G_CALLBACK(compose_grab_focus_cb),
11489 compose);
11491 #endif
11494 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11496 compose->modified = TRUE;
11497 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11498 #ifndef GENERIC_UMPC
11499 compose_set_title(compose);
11500 #endif
11503 static void compose_wrap_cb(GtkAction *action, gpointer data)
11505 Compose *compose = (Compose *)data;
11506 compose_beautify_paragraph(compose, NULL, TRUE);
11509 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11511 Compose *compose = (Compose *)data;
11512 compose_wrap_all_full(compose, TRUE);
11515 static void compose_find_cb(GtkAction *action, gpointer data)
11517 Compose *compose = (Compose *)data;
11519 message_search_compose(compose);
11522 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11523 gpointer data)
11525 Compose *compose = (Compose *)data;
11526 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11527 if (compose->autowrap)
11528 compose_wrap_all_full(compose, TRUE);
11529 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11532 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11533 gpointer data)
11535 Compose *compose = (Compose *)data;
11536 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11539 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11541 Compose *compose = (Compose *)data;
11543 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11544 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11547 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11549 Compose *compose = (Compose *)data;
11551 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11552 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11555 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11557 g_free(compose->privacy_system);
11558 g_free(compose->encdata);
11560 compose->privacy_system = g_strdup(account->default_privacy_system);
11561 compose_update_privacy_system_menu_item(compose, warn);
11564 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11566 if (folder_item != NULL) {
11567 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT &&
11568 privacy_system_can_sign(compose->privacy_system)) {
11569 compose_use_signing(compose,
11570 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11572 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT &&
11573 privacy_system_can_encrypt(compose->privacy_system)) {
11574 compose_use_encryption(compose,
11575 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11580 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11582 Compose *compose = (Compose *)data;
11584 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11585 gtk_widget_show(compose->ruler_hbox);
11586 prefs_common.show_ruler = TRUE;
11587 } else {
11588 gtk_widget_hide(compose->ruler_hbox);
11589 gtk_widget_queue_resize(compose->edit_vbox);
11590 prefs_common.show_ruler = FALSE;
11594 static void compose_attach_drag_received_cb (GtkWidget *widget,
11595 GdkDragContext *context,
11596 gint x,
11597 gint y,
11598 GtkSelectionData *data,
11599 guint info,
11600 guint time,
11601 gpointer user_data)
11603 Compose *compose = (Compose *)user_data;
11604 GList *list, *tmp;
11605 GdkAtom type;
11607 type = gtk_selection_data_get_data_type(data);
11608 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11609 && gtk_drag_get_source_widget(context) !=
11610 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11611 list = uri_list_extract_filenames(
11612 (const gchar *)gtk_selection_data_get_data(data));
11613 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11614 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11615 compose_attach_append
11616 (compose, (const gchar *)tmp->data,
11617 utf8_filename, NULL, NULL);
11618 g_free(utf8_filename);
11620 if (list)
11621 compose_changed_cb(NULL, compose);
11622 list_free_strings_full(list);
11623 } else if (gtk_drag_get_source_widget(context)
11624 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11625 /* comes from our summaryview */
11626 SummaryView * summaryview = NULL;
11627 GSList * list = NULL, *cur = NULL;
11629 if (mainwindow_get_mainwindow())
11630 summaryview = mainwindow_get_mainwindow()->summaryview;
11632 if (summaryview)
11633 list = summary_get_selected_msg_list(summaryview);
11635 for (cur = list; cur; cur = cur->next) {
11636 MsgInfo *msginfo = (MsgInfo *)cur->data;
11637 gchar *file = NULL;
11638 if (msginfo)
11639 file = procmsg_get_message_file_full(msginfo,
11640 TRUE, TRUE);
11641 if (file) {
11642 compose_attach_append(compose, (const gchar *)file,
11643 (const gchar *)file, "message/rfc822", NULL);
11644 g_free(file);
11647 g_slist_free(list);
11651 static gboolean compose_drag_drop(GtkWidget *widget,
11652 GdkDragContext *drag_context,
11653 gint x, gint y,
11654 guint time, gpointer user_data)
11656 /* not handling this signal makes compose_insert_drag_received_cb
11657 * called twice */
11658 return TRUE;
11661 static gboolean completion_set_focus_to_subject
11662 (GtkWidget *widget,
11663 GdkEventKey *event,
11664 Compose *compose)
11666 cm_return_val_if_fail(compose != NULL, FALSE);
11668 /* make backtab move to subject field */
11669 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11670 gtk_widget_grab_focus(compose->subject_entry);
11671 return TRUE;
11673 return FALSE;
11676 static void compose_insert_drag_received_cb (GtkWidget *widget,
11677 GdkDragContext *drag_context,
11678 gint x,
11679 gint y,
11680 GtkSelectionData *data,
11681 guint info,
11682 guint time,
11683 gpointer user_data)
11685 Compose *compose = (Compose *)user_data;
11686 GList *list, *tmp;
11687 GdkAtom type;
11688 guint num_files;
11689 gchar *msg;
11691 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11692 * does not work */
11693 type = gtk_selection_data_get_data_type(data);
11694 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11695 AlertValue val = G_ALERTDEFAULT;
11696 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11698 list = uri_list_extract_filenames(ddata);
11699 num_files = g_list_length(list);
11700 if (list == NULL && strstr(ddata, "://")) {
11701 /* Assume a list of no files, and data has ://, is a remote link */
11702 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11703 gchar *tmpfile = get_tmp_file();
11704 str_write_to_file(tmpdata, tmpfile, TRUE);
11705 g_free(tmpdata);
11706 compose_insert_file(compose, tmpfile);
11707 claws_unlink(tmpfile);
11708 g_free(tmpfile);
11709 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11710 compose_beautify_paragraph(compose, NULL, TRUE);
11711 return;
11713 switch (prefs_common.compose_dnd_mode) {
11714 case COMPOSE_DND_ASK:
11715 msg = g_strdup_printf(
11716 ngettext(
11717 "Do you want to insert the contents of the file "
11718 "into the message body, or attach it to the email?",
11719 "Do you want to insert the contents of the %d files "
11720 "into the message body, or attach them to the email?",
11721 num_files),
11722 num_files);
11723 val = alertpanel_full(_("Insert or attach?"), msg,
11724 NULL, _("_Cancel"), NULL, _("_Insert"), NULL, _("_Attach"),
11725 ALERTFOCUS_SECOND, TRUE, NULL, ALERT_QUESTION);
11726 g_free(msg);
11727 break;
11728 case COMPOSE_DND_INSERT:
11729 val = G_ALERTALTERNATE;
11730 break;
11731 case COMPOSE_DND_ATTACH:
11732 val = G_ALERTOTHER;
11733 break;
11734 default:
11735 /* unexpected case */
11736 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11739 if (val & G_ALERTDISABLE) {
11740 val &= ~G_ALERTDISABLE;
11741 /* remember what action to perform by default, only if we don't click Cancel */
11742 if (val == G_ALERTALTERNATE)
11743 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11744 else if (val == G_ALERTOTHER)
11745 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11748 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11749 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11750 list_free_strings_full(list);
11751 return;
11752 } else if (val == G_ALERTOTHER) {
11753 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11754 list_free_strings_full(list);
11755 return;
11758 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11759 compose_insert_file(compose, (const gchar *)tmp->data);
11761 list_free_strings_full(list);
11762 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11763 return;
11767 static void compose_header_drag_received_cb (GtkWidget *widget,
11768 GdkDragContext *drag_context,
11769 gint x,
11770 gint y,
11771 GtkSelectionData *data,
11772 guint info,
11773 guint time,
11774 gpointer user_data)
11776 GtkEditable *entry = (GtkEditable *)user_data;
11777 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11779 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11780 * does not work */
11782 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11783 gchar *decoded=g_new(gchar, strlen(email));
11784 int start = 0;
11786 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11787 gtk_editable_delete_text(entry, 0, -1);
11788 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11789 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11790 g_free(decoded);
11791 return;
11793 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11796 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11798 Compose *compose = (Compose *)data;
11800 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11801 compose->return_receipt = TRUE;
11802 else
11803 compose->return_receipt = FALSE;
11806 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11808 Compose *compose = (Compose *)data;
11810 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11811 compose->remove_references = TRUE;
11812 else
11813 compose->remove_references = FALSE;
11816 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11817 ComposeHeaderEntry *headerentry)
11819 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11820 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11821 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11822 return FALSE;
11825 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11826 GdkEventKey *event,
11827 ComposeHeaderEntry *headerentry)
11829 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11830 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11831 !(event->state & GDK_MODIFIER_MASK) &&
11832 (event->keyval == GDK_KEY_BackSpace) &&
11833 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11834 gtk_container_remove
11835 (GTK_CONTAINER(headerentry->compose->header_table),
11836 headerentry->combo);
11837 gtk_container_remove
11838 (GTK_CONTAINER(headerentry->compose->header_table),
11839 headerentry->entry);
11840 headerentry->compose->header_list =
11841 g_slist_remove(headerentry->compose->header_list,
11842 headerentry);
11843 g_free(headerentry);
11844 } else if (event->keyval == GDK_KEY_Tab) {
11845 if (headerentry->compose->header_last == headerentry) {
11846 /* Override default next focus, and give it to subject_entry
11847 * instead of notebook tabs
11849 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11850 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11851 return TRUE;
11854 return FALSE;
11857 static gboolean scroll_postpone(gpointer data)
11859 Compose *compose = (Compose *)data;
11861 if (compose->batch)
11862 return FALSE;
11864 GTK_EVENTS_FLUSH();
11865 compose_show_first_last_header(compose, FALSE);
11866 return FALSE;
11869 static void compose_headerentry_changed_cb(GtkWidget *entry,
11870 ComposeHeaderEntry *headerentry)
11872 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11873 compose_create_header_entry(headerentry->compose);
11874 g_signal_handlers_disconnect_matched
11875 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11876 0, 0, NULL, NULL, headerentry);
11878 if (!headerentry->compose->batch)
11879 g_timeout_add(0, scroll_postpone, headerentry->compose);
11883 static gboolean compose_defer_auto_save_draft(Compose *compose)
11885 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11886 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11887 return FALSE;
11890 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11892 GtkAdjustment *vadj;
11894 cm_return_if_fail(compose);
11896 if(compose->batch)
11897 return;
11899 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11900 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11901 vadj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(gtk_widget_get_parent(compose->header_table)));
11902 gtk_adjustment_set_value(vadj, (show_first ?
11903 gtk_adjustment_get_lower(vadj) :
11904 (gtk_adjustment_get_upper(vadj) -
11905 gtk_adjustment_get_page_size(vadj))));
11908 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11909 const gchar *text, gint len, Compose *compose)
11911 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11912 (G_OBJECT(compose->text), "paste_as_quotation"));
11913 GtkTextMark *mark;
11915 cm_return_if_fail(text != NULL);
11917 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11918 G_CALLBACK(text_inserted),
11919 compose);
11920 if (paste_as_quotation) {
11921 gchar *new_text;
11922 const gchar *qmark;
11923 guint pos = 0;
11924 GtkTextIter start_iter;
11926 if (len < 0)
11927 len = strlen(text);
11929 new_text = g_strndup(text, len);
11931 qmark = compose_quote_char_from_context(compose);
11933 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11934 gtk_text_buffer_place_cursor(buffer, iter);
11936 pos = gtk_text_iter_get_offset(iter);
11938 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11939 _("Quote format error at line %d."));
11940 quote_fmt_reset_vartable();
11941 g_free(new_text);
11942 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11943 GINT_TO_POINTER(paste_as_quotation - 1));
11945 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11946 gtk_text_buffer_place_cursor(buffer, iter);
11947 gtk_text_buffer_delete_mark(buffer, mark);
11949 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11950 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11951 compose_beautify_paragraph(compose, &start_iter, FALSE);
11952 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11953 gtk_text_buffer_delete_mark(buffer, mark);
11954 } else {
11955 if (strcmp(text, "\n") || compose->automatic_break
11956 || gtk_text_iter_starts_line(iter)) {
11957 GtkTextIter before_ins;
11958 gtk_text_buffer_insert(buffer, iter, text, len);
11959 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11960 before_ins = *iter;
11961 gtk_text_iter_backward_chars(&before_ins, len);
11962 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11964 } else {
11965 /* check if the preceding is just whitespace or quote */
11966 GtkTextIter start_line;
11967 gchar *tmp = NULL, *quote = NULL;
11968 gint quote_len = 0, is_normal = 0;
11969 start_line = *iter;
11970 gtk_text_iter_set_line_offset(&start_line, 0);
11971 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11972 g_strstrip(tmp);
11974 if (*tmp == '\0') {
11975 is_normal = 1;
11976 } else {
11977 quote = compose_get_quote_str(buffer, &start_line, &quote_len);
11978 if (quote)
11979 is_normal = 1;
11980 g_free(quote);
11982 g_free(tmp);
11984 if (is_normal) {
11985 gtk_text_buffer_insert(buffer, iter, text, len);
11986 } else {
11987 gtk_text_buffer_insert_with_tags_by_name(buffer,
11988 iter, text, len, "no_join", NULL);
11993 if (!paste_as_quotation) {
11994 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11995 compose_beautify_paragraph(compose, iter, FALSE);
11996 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11997 gtk_text_buffer_delete_mark(buffer, mark);
12000 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
12001 G_CALLBACK(text_inserted),
12002 compose);
12003 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
12005 if (compose_can_autosave(compose) &&
12006 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
12007 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
12008 compose->draft_timeout_tag = g_timeout_add
12009 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
12012 #if USE_ENCHANT
12013 static void compose_check_all(GtkAction *action, gpointer data)
12015 Compose *compose = (Compose *)data;
12016 if (!compose->gtkaspell)
12017 return;
12019 if (gtk_widget_has_focus(compose->subject_entry))
12020 claws_spell_entry_check_all(
12021 CLAWS_SPELL_ENTRY(compose->subject_entry));
12022 else
12023 gtkaspell_check_all(compose->gtkaspell);
12026 static void compose_highlight_all(GtkAction *action, gpointer data)
12028 Compose *compose = (Compose *)data;
12029 if (compose->gtkaspell) {
12030 claws_spell_entry_recheck_all(
12031 CLAWS_SPELL_ENTRY(compose->subject_entry));
12032 gtkaspell_highlight_all(compose->gtkaspell);
12036 static void compose_check_backwards(GtkAction *action, gpointer data)
12038 Compose *compose = (Compose *)data;
12039 if (!compose->gtkaspell) {
12040 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12041 return;
12044 if (gtk_widget_has_focus(compose->subject_entry))
12045 claws_spell_entry_check_backwards(
12046 CLAWS_SPELL_ENTRY(compose->subject_entry));
12047 else
12048 gtkaspell_check_backwards(compose->gtkaspell);
12051 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12053 Compose *compose = (Compose *)data;
12054 if (!compose->gtkaspell) {
12055 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12056 return;
12059 if (gtk_widget_has_focus(compose->subject_entry))
12060 claws_spell_entry_check_forwards_go(
12061 CLAWS_SPELL_ENTRY(compose->subject_entry));
12062 else
12063 gtkaspell_check_forwards_go(compose->gtkaspell);
12065 #endif
12068 *\brief Guess originating forward account from MsgInfo and several
12069 * "common preference" settings. Return NULL if no guess.
12071 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12073 PrefsAccount *account = NULL;
12075 cm_return_val_if_fail(msginfo, NULL);
12076 cm_return_val_if_fail(msginfo->folder, NULL);
12077 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12079 if (msginfo->folder->prefs->enable_default_account)
12080 account = account_find_from_id(msginfo->folder->prefs->default_account);
12082 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12083 gchar *to;
12084 Xstrdup_a(to, msginfo->to, return NULL);
12085 extract_address(to);
12086 account = account_find_from_address(to, FALSE);
12089 if (!account && prefs_common.forward_account_autosel) {
12090 gchar *cc = NULL;
12091 if (!procheader_get_header_from_msginfo
12092 (msginfo, &cc, "Cc:")) {
12093 gchar *buf = cc + strlen("Cc:");
12094 extract_address(buf);
12095 account = account_find_from_address(buf, FALSE);
12096 g_free(cc);
12100 if (!account && prefs_common.forward_account_autosel) {
12101 gchar *deliveredto = NULL;
12102 if (!procheader_get_header_from_msginfo
12103 (msginfo, &deliveredto, "Delivered-To:")) {
12104 gchar *buf = deliveredto + strlen("Delivered-To:");
12105 extract_address(buf);
12106 account = account_find_from_address(buf, FALSE);
12107 g_free(deliveredto);
12111 if (!account)
12112 account = msginfo->folder->folder->account;
12114 return account;
12117 gboolean compose_close(Compose *compose)
12119 gint x, y;
12121 cm_return_val_if_fail(compose, FALSE);
12123 if (!g_mutex_trylock(&compose->mutex)) {
12124 /* we have to wait for the (possibly deferred by auto-save)
12125 * drafting to be done, before destroying the compose under
12126 * it. */
12127 debug_print("waiting for drafting to finish...\n");
12128 compose_allow_user_actions(compose, FALSE);
12129 if (compose->close_timeout_tag == 0) {
12130 compose->close_timeout_tag =
12131 g_timeout_add (500, (GSourceFunc) compose_close,
12132 compose);
12134 return TRUE;
12137 if (compose->draft_timeout_tag >= 0) {
12138 g_source_remove(compose->draft_timeout_tag);
12139 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12142 gtk_window_get_position(GTK_WINDOW(compose->window), &x, &y);
12143 if (!compose->batch) {
12144 prefs_common.compose_x = x;
12145 prefs_common.compose_y = y;
12147 g_mutex_unlock(&compose->mutex);
12148 compose_destroy(compose);
12149 return FALSE;
12153 * Add entry field for each address in list.
12154 * \param compose E-Mail composition object.
12155 * \param listAddress List of (formatted) E-Mail addresses.
12157 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12158 GList *node;
12159 gchar *addr;
12160 node = listAddress;
12161 while( node ) {
12162 addr = ( gchar * ) node->data;
12163 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12164 node = g_list_next( node );
12168 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12169 guint action, gboolean opening_multiple)
12171 gchar *body = NULL;
12172 GSList *new_msglist = NULL;
12173 MsgInfo *tmp_msginfo = NULL;
12174 gboolean originally_enc = FALSE;
12175 gboolean originally_sig = FALSE;
12176 Compose *compose = NULL;
12177 gchar *s_system = NULL;
12179 cm_return_if_fail(msginfo_list != NULL);
12181 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12182 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12183 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12185 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12186 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12187 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12188 orig_msginfo, mimeinfo);
12189 if (tmp_msginfo != NULL) {
12190 new_msglist = g_slist_append(NULL, tmp_msginfo);
12192 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12193 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12194 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12196 tmp_msginfo->folder = orig_msginfo->folder;
12197 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12198 if (orig_msginfo->tags) {
12199 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12200 tmp_msginfo->folder->tags_dirty = TRUE;
12206 if (!opening_multiple && msgview != NULL)
12207 body = messageview_get_selection(msgview);
12209 if (new_msglist) {
12210 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12211 procmsg_msginfo_free(&tmp_msginfo);
12212 g_slist_free(new_msglist);
12213 } else
12214 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12216 if (compose && originally_enc) {
12217 compose_force_encryption(compose, compose->account, FALSE, s_system);
12220 if (compose && originally_sig && compose->account->default_sign_reply) {
12221 compose_force_signing(compose, compose->account, s_system);
12223 g_free(s_system);
12224 g_free(body);
12225 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12228 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12229 guint action)
12231 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12232 && msginfo_list != NULL
12233 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12234 GSList *cur = msginfo_list;
12235 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12236 "messages. Opening the windows "
12237 "could take some time. Do you "
12238 "want to continue?"),
12239 g_slist_length(msginfo_list));
12240 if (g_slist_length(msginfo_list) > 9
12241 && alertpanel(_("Warning"), msg, NULL, _("_Cancel"), NULL, _("_Yes"),
12242 NULL, NULL, ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12243 g_free(msg);
12244 return;
12246 g_free(msg);
12247 /* We'll open multiple compose windows */
12248 /* let the WM place the next windows */
12249 compose_force_window_origin = FALSE;
12250 for (; cur; cur = cur->next) {
12251 GSList tmplist;
12252 tmplist.data = cur->data;
12253 tmplist.next = NULL;
12254 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12256 compose_force_window_origin = TRUE;
12257 } else {
12258 /* forwarding multiple mails as attachments is done via a
12259 * single compose window */
12260 if (msginfo_list != NULL) {
12261 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12262 } else if (msgview != NULL) {
12263 GSList tmplist;
12264 tmplist.data = msgview->msginfo;
12265 tmplist.next = NULL;
12266 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12267 } else {
12268 debug_print("Nothing to reply to\n");
12273 void compose_check_for_email_account(Compose *compose)
12275 PrefsAccount *ac = NULL, *curr = NULL;
12276 GList *list;
12278 if (!compose)
12279 return;
12281 if (compose->account && compose->account->protocol == A_NNTP) {
12282 ac = account_get_cur_account();
12283 if (ac->protocol == A_NNTP) {
12284 list = account_get_list();
12286 for( ; list != NULL ; list = g_list_next(list)) {
12287 curr = (PrefsAccount *) list->data;
12288 if (curr->protocol != A_NNTP) {
12289 ac = curr;
12290 break;
12294 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12295 ac->account_id);
12299 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12300 const gchar *address)
12302 GSList *msginfo_list = NULL;
12303 gchar *body = messageview_get_selection(msgview);
12304 Compose *compose;
12306 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12308 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12309 compose_check_for_email_account(compose);
12310 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12311 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12312 compose_reply_set_subject(compose, msginfo);
12314 g_free(body);
12315 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12318 void compose_set_position(Compose *compose, gint pos)
12320 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12322 gtkut_text_view_set_position(text, pos);
12325 gboolean compose_search_string(Compose *compose,
12326 const gchar *str, gboolean case_sens)
12328 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12330 return gtkut_text_view_search_string(text, str, case_sens);
12333 gboolean compose_search_string_backward(Compose *compose,
12334 const gchar *str, gboolean case_sens)
12336 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12338 return gtkut_text_view_search_string_backward(text, str, case_sens);
12341 /* allocate a msginfo structure and populate its data from a compose data structure */
12342 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12344 MsgInfo *newmsginfo;
12345 GSList *list;
12346 gchar date[RFC822_DATE_BUFFSIZE];
12348 cm_return_val_if_fail( compose != NULL, NULL );
12350 newmsginfo = procmsg_msginfo_new();
12352 /* date is now */
12353 get_rfc822_date(date, sizeof(date));
12354 newmsginfo->date = g_strdup(date);
12356 /* from */
12357 if (compose->from_name) {
12358 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12359 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12362 /* subject */
12363 if (compose->subject_entry)
12364 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12366 /* to, cc, reply-to, newsgroups */
12367 for (list = compose->header_list; list; list = list->next) {
12368 gchar *header = gtk_editable_get_chars(
12369 GTK_EDITABLE(
12370 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12371 gchar *entry = gtk_editable_get_chars(
12372 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12374 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12375 if ( newmsginfo->to == NULL ) {
12376 newmsginfo->to = g_strdup(entry);
12377 } else if (entry && *entry) {
12378 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12379 g_free(newmsginfo->to);
12380 newmsginfo->to = tmp;
12382 } else
12383 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12384 if ( newmsginfo->cc == NULL ) {
12385 newmsginfo->cc = g_strdup(entry);
12386 } else if (entry && *entry) {
12387 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12388 g_free(newmsginfo->cc);
12389 newmsginfo->cc = tmp;
12391 } else
12392 if ( strcasecmp(header,
12393 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12394 if ( newmsginfo->newsgroups == NULL ) {
12395 newmsginfo->newsgroups = g_strdup(entry);
12396 } else if (entry && *entry) {
12397 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12398 g_free(newmsginfo->newsgroups);
12399 newmsginfo->newsgroups = tmp;
12403 g_free(header);
12404 g_free(entry);
12407 /* other data is unset */
12409 return newmsginfo;
12412 #ifdef USE_ENCHANT
12413 /* update compose's dictionaries from folder dict settings */
12414 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12415 FolderItem *folder_item)
12417 cm_return_if_fail(compose != NULL);
12419 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12420 FolderItemPrefs *prefs = folder_item->prefs;
12422 if (prefs->enable_default_dictionary)
12423 gtkaspell_change_dict(compose->gtkaspell,
12424 prefs->default_dictionary, FALSE);
12425 if (folder_item->prefs->enable_default_alt_dictionary)
12426 gtkaspell_change_alt_dict(compose->gtkaspell,
12427 prefs->default_alt_dictionary);
12428 if (prefs->enable_default_dictionary
12429 || prefs->enable_default_alt_dictionary)
12430 compose_spell_menu_changed(compose);
12433 #endif
12435 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12437 Compose *compose = (Compose *)data;
12439 cm_return_if_fail(compose != NULL);
12441 gtk_widget_grab_focus(compose->text);
12444 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12446 gtk_combo_box_popup(GTK_COMBO_BOX(data));
12451 * End of Source.