Sync Spanish manual
[claws.git] / src / compose.c
blob5c3817989edd66b098c7aa968c2ccf98722b2b91
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 #ifdef GDK_WINDOWING_X11
379 static gboolean compose_ext_editor_plug_removed_cb
380 (GtkSocket *socket,
381 Compose *compose);
382 #endif /* GDK_WINDOWING_X11 */
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;
3069 ref_id_list = references_list_append(NULL, ref);
3070 if (!ref_id_list) return NULL;
3071 if (msgid && *msgid)
3072 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3074 for (;;) {
3075 gint len = 0;
3077 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3078 /* "<" + Message-ID + ">" + CR+LF+TAB */
3079 len += strlen((gchar *)cur->data) + 5;
3081 if (len > MAX_REFERENCES_LEN) {
3082 /* remove second message-ID */
3083 if (ref_id_list && ref_id_list->next &&
3084 ref_id_list->next->next) {
3085 g_free(ref_id_list->next->data);
3086 ref_id_list = g_slist_remove
3087 (ref_id_list, ref_id_list->next->data);
3088 } else {
3089 slist_free_strings_full(ref_id_list);
3090 return NULL;
3092 } else
3093 break;
3096 new_ref = g_string_new("");
3097 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3098 if (new_ref->len > 0)
3099 g_string_append(new_ref, "\n\t");
3100 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3103 slist_free_strings_full(ref_id_list);
3105 return g_string_free(new_ref, FALSE);
3108 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3109 const gchar *fmt, const gchar *qmark,
3110 const gchar *body, gboolean rewrap,
3111 gboolean need_unescape,
3112 const gchar *err_msg)
3114 MsgInfo* dummyinfo = NULL;
3115 gchar *quote_str = NULL;
3116 gchar *buf;
3117 gboolean prev_autowrap;
3118 const gchar *trimmed_body = body;
3119 gint cursor_pos = -1;
3120 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3121 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3122 GtkTextIter iter;
3123 GtkTextMark *mark;
3126 SIGNAL_BLOCK(buffer);
3128 if (!msginfo) {
3129 dummyinfo = compose_msginfo_new_from_compose(compose);
3130 msginfo = dummyinfo;
3133 if (qmark != NULL) {
3134 #ifdef USE_ENCHANT
3135 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3136 compose->gtkaspell);
3137 #else
3138 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3139 #endif
3140 quote_fmt_scan_string(qmark);
3141 quote_fmt_parse();
3143 buf = quote_fmt_get_buffer();
3145 if (buf == NULL)
3146 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3147 else
3148 Xstrdup_a(quote_str, buf, goto error)
3151 if (fmt && *fmt != '\0') {
3153 if (trimmed_body)
3154 while (*trimmed_body == '\n')
3155 trimmed_body++;
3157 #ifdef USE_ENCHANT
3158 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3159 compose->gtkaspell);
3160 #else
3161 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3162 #endif
3163 if (need_unescape) {
3164 gchar *tmp = NULL;
3166 /* decode \-escape sequences in the internal representation of the quote format */
3167 tmp = g_malloc(strlen(fmt)+1);
3168 pref_get_unescaped_pref(tmp, fmt);
3169 quote_fmt_scan_string(tmp);
3170 quote_fmt_parse();
3171 g_free(tmp);
3172 } else {
3173 quote_fmt_scan_string(fmt);
3174 quote_fmt_parse();
3177 buf = quote_fmt_get_buffer();
3179 if (buf == NULL) {
3180 gint line = quote_fmt_get_line();
3181 alertpanel_error(err_msg, line);
3183 goto error;
3186 } else
3187 buf = "";
3189 prev_autowrap = compose->autowrap;
3190 compose->autowrap = FALSE;
3192 mark = gtk_text_buffer_get_insert(buffer);
3193 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3194 if (g_utf8_validate(buf, -1, NULL)) {
3195 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3196 } else {
3197 gchar *tmpout = NULL;
3198 tmpout = conv_codeset_strdup
3199 (buf, conv_get_locale_charset_str_no_utf8(),
3200 CS_INTERNAL);
3201 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3202 g_free(tmpout);
3203 tmpout = g_malloc(strlen(buf)*2+1);
3204 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3206 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3207 g_free(tmpout);
3210 cursor_pos = quote_fmt_get_cursor_pos();
3211 if (cursor_pos == -1)
3212 cursor_pos = gtk_text_iter_get_offset(&iter);
3213 compose->set_cursor_pos = cursor_pos;
3215 gtk_text_buffer_get_start_iter(buffer, &iter);
3216 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3217 gtk_text_buffer_place_cursor(buffer, &iter);
3219 compose->autowrap = prev_autowrap;
3220 if (compose->autowrap && rewrap)
3221 compose_wrap_all(compose);
3223 goto ok;
3225 error:
3226 buf = NULL;
3228 SIGNAL_UNBLOCK(buffer);
3230 procmsg_msginfo_free( &dummyinfo );
3232 return buf;
3235 /* if ml_post is of type addr@host and from is of type
3236 * addr-anything@host, return TRUE
3238 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3240 gchar *left_ml = NULL;
3241 gchar *right_ml = NULL;
3242 gchar *left_from = NULL;
3243 gchar *right_from = NULL;
3244 gboolean result = FALSE;
3246 if (!ml_post || !from)
3247 return FALSE;
3249 left_ml = g_strdup(ml_post);
3250 if (strstr(left_ml, "@")) {
3251 right_ml = strstr(left_ml, "@")+1;
3252 *(strstr(left_ml, "@")) = '\0';
3255 left_from = g_strdup(from);
3256 if (strstr(left_from, "@")) {
3257 right_from = strstr(left_from, "@")+1;
3258 *(strstr(left_from, "@")) = '\0';
3261 if (right_ml && right_from
3262 && !strncmp(left_from, left_ml, strlen(left_ml))
3263 && !strcmp(right_from, right_ml)) {
3264 result = TRUE;
3266 g_free(left_ml);
3267 g_free(left_from);
3269 return result;
3272 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3273 gboolean respect_default_to)
3275 if (!compose)
3276 return;
3277 if (!folder || !folder->prefs)
3278 return;
3280 if (folder->prefs->enable_default_from) {
3281 gtk_entry_set_text(GTK_ENTRY(compose->from_name), folder->prefs->default_from);
3282 compose_entry_indicate(compose, folder->prefs->default_from);
3284 if (respect_default_to && folder->prefs->enable_default_to) {
3285 compose_entry_append(compose, folder->prefs->default_to,
3286 COMPOSE_TO, PREF_FOLDER);
3287 compose_entry_indicate(compose, folder->prefs->default_to);
3289 if (folder->prefs->enable_default_cc) {
3290 compose_entry_append(compose, folder->prefs->default_cc,
3291 COMPOSE_CC, PREF_FOLDER);
3292 compose_entry_indicate(compose, folder->prefs->default_cc);
3294 if (folder->prefs->enable_default_bcc) {
3295 compose_entry_append(compose, folder->prefs->default_bcc,
3296 COMPOSE_BCC, PREF_FOLDER);
3297 compose_entry_indicate(compose, folder->prefs->default_bcc);
3299 if (folder->prefs->enable_default_replyto) {
3300 compose_entry_append(compose, folder->prefs->default_replyto,
3301 COMPOSE_REPLYTO, PREF_FOLDER);
3302 compose_entry_indicate(compose, folder->prefs->default_replyto);
3306 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3308 gchar *buf, *buf2;
3309 gchar *p;
3311 if (!compose || !msginfo)
3312 return;
3314 if (msginfo->subject && *msginfo->subject) {
3315 buf = p = g_strdup(msginfo->subject);
3316 p += subject_get_prefix_length(p);
3317 memmove(buf, p, strlen(p) + 1);
3319 buf2 = g_strdup_printf("Re: %s", buf);
3320 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3322 g_free(buf2);
3323 g_free(buf);
3324 } else
3325 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3328 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3329 gboolean to_all, gboolean to_ml,
3330 gboolean to_sender,
3331 gboolean followup_and_reply_to)
3333 GSList *cc_list = NULL;
3334 GSList *cur;
3335 gchar *from = NULL;
3336 gchar *replyto = NULL;
3337 gchar *ac_email = NULL;
3339 gboolean reply_to_ml = FALSE;
3340 gboolean default_reply_to = FALSE;
3342 cm_return_if_fail(compose->account != NULL);
3343 cm_return_if_fail(msginfo != NULL);
3345 reply_to_ml = to_ml && compose->ml_post;
3347 default_reply_to = msginfo->folder &&
3348 msginfo->folder->prefs->enable_default_reply_to;
3350 if (compose->account->protocol != A_NNTP) {
3351 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3353 if (reply_to_ml && !default_reply_to) {
3355 gboolean is_subscr = is_subscription(compose->ml_post,
3356 msginfo->from);
3357 if (!is_subscr) {
3358 /* normal answer to ml post with a reply-to */
3359 compose_entry_append(compose,
3360 compose->ml_post,
3361 COMPOSE_TO, PREF_ML);
3362 if (compose->replyto)
3363 compose_entry_append(compose,
3364 compose->replyto,
3365 COMPOSE_CC, PREF_ML);
3366 } else {
3367 /* answer to subscription confirmation */
3368 if (compose->replyto)
3369 compose_entry_append(compose,
3370 compose->replyto,
3371 COMPOSE_TO, PREF_ML);
3372 else if (msginfo->from)
3373 compose_entry_append(compose,
3374 msginfo->from,
3375 COMPOSE_TO, PREF_ML);
3378 else if (!(to_all || to_sender) && default_reply_to) {
3379 compose_entry_append(compose,
3380 msginfo->folder->prefs->default_reply_to,
3381 COMPOSE_TO, PREF_FOLDER);
3382 compose_entry_indicate(compose,
3383 msginfo->folder->prefs->default_reply_to);
3384 } else {
3385 gchar *tmp1 = NULL;
3386 if (!msginfo->from)
3387 return;
3388 if (to_sender)
3389 compose_entry_append(compose, msginfo->from,
3390 COMPOSE_TO, PREF_NONE);
3391 else if (to_all) {
3392 Xstrdup_a(tmp1, msginfo->from, return);
3393 extract_address(tmp1);
3394 compose_entry_append(compose,
3395 (!account_find_from_address(tmp1, FALSE))
3396 ? msginfo->from :
3397 msginfo->to,
3398 COMPOSE_TO, PREF_NONE);
3399 if (compose->replyto)
3400 compose_entry_append(compose,
3401 compose->replyto,
3402 COMPOSE_CC, PREF_NONE);
3403 } else {
3404 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3405 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3406 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3407 if (compose->replyto) {
3408 compose_entry_append(compose,
3409 compose->replyto,
3410 COMPOSE_TO, PREF_NONE);
3411 } else {
3412 compose_entry_append(compose,
3413 msginfo->from ? msginfo->from : "",
3414 COMPOSE_TO, PREF_NONE);
3416 } else {
3417 /* replying to own mail, use original recp */
3418 compose_entry_append(compose,
3419 msginfo->to ? msginfo->to : "",
3420 COMPOSE_TO, PREF_NONE);
3421 compose_entry_append(compose,
3422 msginfo->cc ? msginfo->cc : "",
3423 COMPOSE_CC, PREF_NONE);
3427 } else {
3428 if (to_sender || (compose->followup_to &&
3429 !strncmp(compose->followup_to, "poster", 6)))
3430 compose_entry_append
3431 (compose,
3432 (compose->replyto ? compose->replyto :
3433 msginfo->from ? msginfo->from : ""),
3434 COMPOSE_TO, PREF_NONE);
3436 else if (followup_and_reply_to || to_all) {
3437 compose_entry_append
3438 (compose,
3439 (compose->replyto ? compose->replyto :
3440 msginfo->from ? msginfo->from : ""),
3441 COMPOSE_TO, PREF_NONE);
3443 compose_entry_append
3444 (compose,
3445 compose->followup_to ? compose->followup_to :
3446 compose->newsgroups ? compose->newsgroups : "",
3447 COMPOSE_NEWSGROUPS, PREF_NONE);
3449 compose_entry_append
3450 (compose,
3451 msginfo->cc ? msginfo->cc : "",
3452 COMPOSE_CC, PREF_NONE);
3454 else
3455 compose_entry_append
3456 (compose,
3457 compose->followup_to ? compose->followup_to :
3458 compose->newsgroups ? compose->newsgroups : "",
3459 COMPOSE_NEWSGROUPS, PREF_NONE);
3461 compose_reply_set_subject(compose, msginfo);
3463 if (to_ml && compose->ml_post) return;
3464 if (!to_all || compose->account->protocol == A_NNTP) return;
3466 if (compose->replyto) {
3467 Xstrdup_a(replyto, compose->replyto, return);
3468 extract_address(replyto);
3470 if (msginfo->from) {
3471 Xstrdup_a(from, msginfo->from, return);
3472 extract_address(from);
3475 if (replyto && from)
3476 cc_list = address_list_append_with_comments(cc_list, from);
3477 if (to_all && msginfo->folder &&
3478 msginfo->folder->prefs->enable_default_reply_to)
3479 cc_list = address_list_append_with_comments(cc_list,
3480 msginfo->folder->prefs->default_reply_to);
3481 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3482 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3484 ac_email = g_utf8_strdown(compose->account->address, -1);
3486 if (cc_list) {
3487 for (cur = cc_list; cur != NULL; cur = cur->next) {
3488 gchar *addr = g_utf8_strdown(cur->data, -1);
3489 extract_address(addr);
3491 if (strcmp(ac_email, addr))
3492 compose_entry_append(compose, (gchar *)cur->data,
3493 COMPOSE_CC, PREF_NONE);
3494 else
3495 debug_print("Cc address same as compose account's, ignoring\n");
3497 g_free(addr);
3500 slist_free_strings_full(cc_list);
3503 g_free(ac_email);
3506 #define SET_ENTRY(entry, str) \
3508 if (str && *str) \
3509 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3512 #define SET_ADDRESS(type, str) \
3514 if (str && *str) \
3515 compose_entry_append(compose, str, type, PREF_NONE); \
3518 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3520 cm_return_if_fail(msginfo != NULL);
3522 SET_ENTRY(subject_entry, msginfo->subject);
3523 SET_ENTRY(from_name, msginfo->from);
3524 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3525 SET_ADDRESS(COMPOSE_CC, compose->cc);
3526 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3527 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3528 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3529 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3531 compose_update_priority_menu_item(compose);
3532 compose_update_privacy_system_menu_item(compose, FALSE);
3533 compose_show_first_last_header(compose, TRUE);
3536 #undef SET_ENTRY
3537 #undef SET_ADDRESS
3539 static void compose_insert_sig(Compose *compose, gboolean replace)
3541 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3542 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3543 GtkTextMark *mark;
3544 GtkTextIter iter, iter_end;
3545 gint cur_pos, ins_pos;
3546 gboolean prev_autowrap;
3547 gboolean found = FALSE;
3548 gboolean exists = FALSE;
3550 cm_return_if_fail(compose->account != NULL);
3552 BLOCK_WRAP();
3554 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3555 G_CALLBACK(compose_changed_cb),
3556 compose);
3558 mark = gtk_text_buffer_get_insert(buffer);
3559 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3560 cur_pos = gtk_text_iter_get_offset (&iter);
3561 ins_pos = cur_pos;
3563 gtk_text_buffer_get_end_iter(buffer, &iter);
3565 exists = (compose->sig_str != NULL);
3567 if (replace) {
3568 GtkTextIter first_iter, start_iter, end_iter;
3570 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3572 if (!exists || compose->sig_str[0] == '\0')
3573 found = FALSE;
3574 else
3575 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3576 compose->signature_tag);
3578 if (found) {
3579 /* include previous \n\n */
3580 gtk_text_iter_backward_chars(&first_iter, 1);
3581 start_iter = first_iter;
3582 end_iter = first_iter;
3583 /* skip re-start */
3584 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3585 compose->signature_tag);
3586 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3587 compose->signature_tag);
3588 if (found) {
3589 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3590 iter = start_iter;
3595 g_free(compose->sig_str);
3596 compose->sig_str = account_get_signature_str(compose->account);
3598 cur_pos = gtk_text_iter_get_offset(&iter);
3600 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3601 g_free(compose->sig_str);
3602 compose->sig_str = NULL;
3603 } else {
3604 if (compose->sig_inserted == FALSE)
3605 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3606 compose->sig_inserted = TRUE;
3608 cur_pos = gtk_text_iter_get_offset(&iter);
3609 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3610 /* remove \n\n */
3611 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3612 gtk_text_iter_forward_chars(&iter, 1);
3613 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3614 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3616 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3617 cur_pos = gtk_text_buffer_get_char_count (buffer);
3620 /* put the cursor where it should be
3621 * either where the quote_fmt says, either where it was */
3622 if (compose->set_cursor_pos < 0)
3623 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3624 else
3625 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3626 compose->set_cursor_pos);
3628 compose->set_cursor_pos = -1;
3629 gtk_text_buffer_place_cursor(buffer, &iter);
3630 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3631 G_CALLBACK(compose_changed_cb),
3632 compose);
3634 UNBLOCK_WRAP();
3637 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3639 GtkTextView *text;
3640 GtkTextBuffer *buffer;
3641 GtkTextMark *mark;
3642 GtkTextIter iter;
3643 const gchar *cur_encoding;
3644 gchar buf[BUFFSIZE];
3645 gint len;
3646 FILE *fp;
3647 gboolean prev_autowrap;
3648 #ifdef G_OS_WIN32
3649 GFile *f;
3650 GFileInfo *fi;
3651 GError *error = NULL;
3652 #else
3653 GStatBuf file_stat;
3654 #endif
3655 int ret;
3656 goffset size;
3657 GString *file_contents = NULL;
3658 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3660 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3662 /* get the size of the file we are about to insert */
3663 #ifdef G_OS_WIN32
3664 f = g_file_new_for_path(file);
3665 fi = g_file_query_info(f, "standard::size",
3666 G_FILE_QUERY_INFO_NONE, NULL, &error);
3667 ret = 0;
3668 if (error != NULL) {
3669 g_warning(error->message);
3670 ret = 1;
3671 g_error_free(error);
3672 g_object_unref(f);
3674 #else
3675 ret = g_stat(file, &file_stat);
3676 #endif
3677 if (ret != 0) {
3678 gchar *shortfile = g_path_get_basename(file);
3679 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3680 g_free(shortfile);
3681 return COMPOSE_INSERT_NO_FILE;
3682 } else if (prefs_common.warn_large_insert == TRUE) {
3683 #ifdef G_OS_WIN32
3684 size = g_file_info_get_size(fi);
3685 g_object_unref(fi);
3686 g_object_unref(f);
3687 #else
3688 size = file_stat.st_size;
3689 #endif
3691 /* ask user for confirmation if the file is large */
3692 if (prefs_common.warn_large_insert_size < 0 ||
3693 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3694 AlertValue aval;
3695 gchar *msg;
3697 msg = g_strdup_printf(_("You are about to insert a file of %s "
3698 "in the message body. Are you sure you want to do that?"),
3699 to_human_readable(size));
3700 aval = alertpanel_full(_("Are you sure?"), msg, NULL, _("_Cancel"),
3701 NULL, _("_Insert"), NULL, NULL, ALERTFOCUS_SECOND, TRUE,
3702 NULL, ALERT_QUESTION);
3703 g_free(msg);
3705 /* do we ask for confirmation next time? */
3706 if (aval & G_ALERTDISABLE) {
3707 /* no confirmation next time, disable feature in preferences */
3708 aval &= ~G_ALERTDISABLE;
3709 prefs_common.warn_large_insert = FALSE;
3712 /* abort file insertion if user canceled action */
3713 if (aval != G_ALERTALTERNATE) {
3714 return COMPOSE_INSERT_NO_FILE;
3720 if ((fp = claws_fopen(file, "rb")) == NULL) {
3721 FILE_OP_ERROR(file, "claws_fopen");
3722 return COMPOSE_INSERT_READ_ERROR;
3725 prev_autowrap = compose->autowrap;
3726 compose->autowrap = FALSE;
3728 text = GTK_TEXT_VIEW(compose->text);
3729 buffer = gtk_text_view_get_buffer(text);
3730 mark = gtk_text_buffer_get_insert(buffer);
3731 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3733 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3734 G_CALLBACK(text_inserted),
3735 compose);
3737 cur_encoding = conv_get_locale_charset_str_no_utf8();
3739 file_contents = g_string_new("");
3740 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3741 gchar *str;
3743 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3744 str = g_strdup(buf);
3745 else {
3746 codeconv_set_strict(TRUE);
3747 str = conv_codeset_strdup
3748 (buf, cur_encoding, CS_INTERNAL);
3749 codeconv_set_strict(FALSE);
3751 if (!str) {
3752 result = COMPOSE_INSERT_INVALID_CHARACTER;
3753 break;
3756 if (!str) continue;
3758 /* strip <CR> if DOS/Windows file,
3759 replace <CR> with <LF> if Macintosh file. */
3760 strcrchomp(str);
3761 len = strlen(str);
3762 if (len > 0 && str[len - 1] != '\n') {
3763 while (--len >= 0)
3764 if (str[len] == '\r') str[len] = '\n';
3767 file_contents = g_string_append(file_contents, str);
3768 g_free(str);
3771 if (result == COMPOSE_INSERT_SUCCESS) {
3772 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3774 compose_changed_cb(NULL, compose);
3775 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3776 G_CALLBACK(text_inserted),
3777 compose);
3778 compose->autowrap = prev_autowrap;
3779 if (compose->autowrap)
3780 compose_wrap_all(compose);
3783 g_string_free(file_contents, TRUE);
3784 claws_fclose(fp);
3786 return result;
3789 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3790 const gchar *filename,
3791 const gchar *content_type,
3792 const gchar *charset)
3794 AttachInfo *ainfo;
3795 GtkTreeIter iter;
3796 FILE *fp;
3797 off_t size;
3798 GAuto *auto_ainfo;
3799 gchar *size_text;
3800 GtkListStore *store;
3801 gchar *name;
3802 gboolean has_binary = FALSE;
3804 if (!is_file_exist(file)) {
3805 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3806 gboolean result = FALSE;
3807 if (file_from_uri && is_file_exist(file_from_uri)) {
3808 result = compose_attach_append(
3809 compose, file_from_uri,
3810 filename, content_type,
3811 charset);
3813 g_free(file_from_uri);
3814 if (result)
3815 return TRUE;
3816 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3817 return FALSE;
3819 if ((size = get_file_size(file)) < 0) {
3820 alertpanel_error("Can't get file size of %s\n", filename);
3821 return FALSE;
3824 /* In batch mode, we allow 0-length files to be attached no questions asked */
3825 if (size == 0 && !compose->batch) {
3826 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3827 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3828 NULL, _("_Cancel"), NULL, _("_Attach anyway"),
3829 NULL, NULL, ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3830 g_free(msg);
3832 if (aval != G_ALERTALTERNATE) {
3833 return FALSE;
3836 if ((fp = claws_fopen(file, "rb")) == NULL) {
3837 alertpanel_error(_("Can't read %s."), filename);
3838 return FALSE;
3840 claws_fclose(fp);
3842 ainfo = g_new0(AttachInfo, 1);
3843 auto_ainfo = g_auto_pointer_new_with_free
3844 (ainfo, (GFreeFunc) compose_attach_info_free);
3845 ainfo->file = g_strdup(file);
3847 if (content_type) {
3848 ainfo->content_type = g_strdup(content_type);
3849 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3850 MsgInfo *msginfo;
3851 MsgFlags flags = {0, 0};
3853 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3854 ainfo->encoding = ENC_7BIT;
3855 else
3856 ainfo->encoding = ENC_8BIT;
3858 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3859 if (msginfo && msginfo->subject)
3860 name = g_strdup(msginfo->subject);
3861 else
3862 name = g_path_get_basename(filename ? filename : file);
3864 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3866 procmsg_msginfo_free(&msginfo);
3867 } else {
3868 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3869 ainfo->charset = g_strdup(charset);
3870 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3871 } else {
3872 ainfo->encoding = ENC_BASE64;
3874 name = g_path_get_basename(filename ? filename : file);
3875 ainfo->name = g_strdup(name);
3877 g_free(name);
3878 } else {
3879 ainfo->content_type = procmime_get_mime_type(file);
3880 if (!ainfo->content_type) {
3881 ainfo->content_type =
3882 g_strdup("application/octet-stream");
3883 ainfo->encoding = ENC_BASE64;
3884 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3885 ainfo->encoding =
3886 procmime_get_encoding_for_text_file(file, &has_binary);
3887 else
3888 ainfo->encoding = ENC_BASE64;
3889 name = g_path_get_basename(filename ? filename : file);
3890 ainfo->name = g_strdup(name);
3891 g_free(name);
3894 if (ainfo->name != NULL
3895 && !strcmp(ainfo->name, ".")) {
3896 g_free(ainfo->name);
3897 ainfo->name = NULL;
3900 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3901 g_free(ainfo->content_type);
3902 ainfo->content_type = g_strdup("application/octet-stream");
3903 g_free(ainfo->charset);
3904 ainfo->charset = NULL;
3907 ainfo->size = (goffset)size;
3908 size_text = to_human_readable((goffset)size);
3910 store = GTK_LIST_STORE(gtk_tree_view_get_model
3911 (GTK_TREE_VIEW(compose->attach_clist)));
3913 gtk_list_store_append(store, &iter);
3914 gtk_list_store_set(store, &iter,
3915 COL_MIMETYPE, ainfo->content_type,
3916 COL_SIZE, size_text,
3917 COL_NAME, ainfo->name,
3918 COL_CHARSET, ainfo->charset,
3919 COL_DATA, ainfo,
3920 COL_AUTODATA, auto_ainfo,
3921 -1);
3923 g_auto_pointer_free(auto_ainfo);
3924 compose_attach_update_label(compose);
3925 return TRUE;
3928 void compose_use_signing(Compose *compose, gboolean use_signing)
3930 compose->use_signing = use_signing;
3931 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3934 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3936 compose->use_encryption = use_encryption;
3937 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3940 #define NEXT_PART_NOT_CHILD(info) \
3942 node = info->node; \
3943 while (node->children) \
3944 node = g_node_last_child(node); \
3945 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3948 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3950 MimeInfo *mimeinfo;
3951 MimeInfo *child;
3952 MimeInfo *firsttext = NULL;
3953 MimeInfo *encrypted = NULL;
3954 GNode *node;
3955 gchar *outfile;
3956 const gchar *partname = NULL;
3958 mimeinfo = procmime_scan_message(msginfo);
3959 if (!mimeinfo) return;
3961 if (mimeinfo->node->children == NULL) {
3962 procmime_mimeinfo_free_all(&mimeinfo);
3963 return;
3966 /* find first content part */
3967 child = (MimeInfo *) mimeinfo->node->children->data;
3968 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3969 child = (MimeInfo *)child->node->children->data;
3971 if (child) {
3972 if (child->type == MIMETYPE_TEXT) {
3973 firsttext = child;
3974 debug_print("First text part found\n");
3975 } else if (compose->mode == COMPOSE_REEDIT &&
3976 child->type == MIMETYPE_APPLICATION &&
3977 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3978 encrypted = (MimeInfo *)child->node->parent->data;
3981 child = (MimeInfo *) mimeinfo->node->children->data;
3982 while (child != NULL) {
3983 gint err;
3985 if (child == encrypted) {
3986 /* skip this part of tree */
3987 NEXT_PART_NOT_CHILD(child);
3988 continue;
3991 if (child->type == MIMETYPE_MULTIPART) {
3992 /* get the actual content */
3993 child = procmime_mimeinfo_next(child);
3994 continue;
3997 if (child == firsttext) {
3998 child = procmime_mimeinfo_next(child);
3999 continue;
4002 outfile = procmime_get_tmp_file_name(child);
4003 if ((err = procmime_get_part(outfile, child)) < 0)
4004 g_warning("can't get the part of multipart message. (%s)", g_strerror(-err));
4005 else {
4006 gchar *content_type;
4008 content_type = procmime_get_content_type_str(child->type, child->subtype);
4010 /* if we meet a pgp signature, we don't attach it, but
4011 * we force signing. */
4012 if ((strcmp(content_type, "application/pgp-signature") &&
4013 strcmp(content_type, "application/pkcs7-signature") &&
4014 strcmp(content_type, "application/x-pkcs7-signature"))
4015 || compose->mode == COMPOSE_REDIRECT) {
4016 partname = procmime_mimeinfo_get_parameter(child, "filename");
4017 if (partname == NULL)
4018 partname = procmime_mimeinfo_get_parameter(child, "name");
4019 if (partname == NULL)
4020 partname = "";
4021 compose_attach_append(compose, outfile,
4022 partname, content_type,
4023 procmime_mimeinfo_get_parameter(child, "charset"));
4024 } else {
4025 compose_force_signing(compose, compose->account, NULL);
4027 g_free(content_type);
4029 g_free(outfile);
4030 NEXT_PART_NOT_CHILD(child);
4032 procmime_mimeinfo_free_all(&mimeinfo);
4035 #undef NEXT_PART_NOT_CHILD
4039 typedef enum {
4040 WAIT_FOR_INDENT_CHAR,
4041 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4042 } IndentState;
4044 /* return indent length, we allow:
4045 indent characters followed by indent characters or spaces/tabs,
4046 alphabets and numbers immediately followed by indent characters,
4047 and the repeating sequences of the above
4048 If quote ends with multiple spaces, only the first one is included. */
4049 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4050 const GtkTextIter *start, gint *len)
4052 GtkTextIter iter = *start;
4053 gunichar wc;
4054 gchar ch[6];
4055 gint clen;
4056 IndentState state = WAIT_FOR_INDENT_CHAR;
4057 gboolean is_space;
4058 gboolean is_indent;
4059 gint alnum_count = 0;
4060 gint space_count = 0;
4061 gint quote_len = 0;
4063 if (prefs_common.quote_chars == NULL) {
4064 return 0 ;
4067 while (!gtk_text_iter_ends_line(&iter)) {
4068 wc = gtk_text_iter_get_char(&iter);
4069 if (g_unichar_iswide(wc))
4070 break;
4071 clen = g_unichar_to_utf8(wc, ch);
4072 if (clen != 1)
4073 break;
4075 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4076 is_space = g_unichar_isspace(wc);
4078 if (state == WAIT_FOR_INDENT_CHAR) {
4079 if (!is_indent && !g_unichar_isalnum(wc))
4080 break;
4081 if (is_indent) {
4082 quote_len += alnum_count + space_count + 1;
4083 alnum_count = space_count = 0;
4084 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4085 } else
4086 alnum_count++;
4087 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4088 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4089 break;
4090 if (is_space)
4091 space_count++;
4092 else if (is_indent) {
4093 quote_len += alnum_count + space_count + 1;
4094 alnum_count = space_count = 0;
4095 } else {
4096 alnum_count++;
4097 state = WAIT_FOR_INDENT_CHAR;
4101 gtk_text_iter_forward_char(&iter);
4104 if (quote_len > 0 && space_count > 0)
4105 quote_len++;
4107 if (len)
4108 *len = quote_len;
4110 if (quote_len > 0) {
4111 iter = *start;
4112 gtk_text_iter_forward_chars(&iter, quote_len);
4113 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4116 return NULL;
4119 /* return >0 if the line is itemized */
4120 static int compose_itemized_length(GtkTextBuffer *buffer,
4121 const GtkTextIter *start)
4123 GtkTextIter iter = *start;
4124 gunichar wc;
4125 gchar ch[6];
4126 gint clen;
4127 gint len = 0;
4128 if (gtk_text_iter_ends_line(&iter))
4129 return 0;
4131 while (1) {
4132 len++;
4133 wc = gtk_text_iter_get_char(&iter);
4134 if (!g_unichar_isspace(wc))
4135 break;
4136 gtk_text_iter_forward_char(&iter);
4137 if (gtk_text_iter_ends_line(&iter))
4138 return 0;
4141 clen = g_unichar_to_utf8(wc, ch);
4142 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4143 (clen == 3 && (
4144 wc == 0x2022 || /* BULLET */
4145 wc == 0x2023 || /* TRIANGULAR BULLET */
4146 wc == 0x2043 || /* HYPHEN BULLET */
4147 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4148 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4149 wc == 0x2219 || /* BULLET OPERATOR */
4150 wc == 0x25d8 || /* INVERSE BULLET */
4151 wc == 0x25e6 || /* WHITE BULLET */
4152 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4153 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4154 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4155 wc == 0x29be || /* CIRCLED WHITE BULLET */
4156 wc == 0x29bf /* CIRCLED BULLET */
4157 ))))
4158 return 0;
4160 gtk_text_iter_forward_char(&iter);
4161 if (gtk_text_iter_ends_line(&iter))
4162 return 0;
4163 wc = gtk_text_iter_get_char(&iter);
4164 if (g_unichar_isspace(wc)) {
4165 return len+1;
4167 return 0;
4170 /* return the string at the start of the itemization */
4171 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4172 const GtkTextIter *start)
4174 GtkTextIter iter = *start;
4175 gunichar wc;
4176 gint len = 0;
4177 GString *item_chars = g_string_new("");
4179 if (gtk_text_iter_ends_line(&iter)) {
4180 g_string_free(item_chars, TRUE);
4181 return NULL;
4184 while (1) {
4185 len++;
4186 wc = gtk_text_iter_get_char(&iter);
4187 if (!g_unichar_isspace(wc))
4188 break;
4189 gtk_text_iter_forward_char(&iter);
4190 if (gtk_text_iter_ends_line(&iter))
4191 break;
4192 g_string_append_unichar(item_chars, wc);
4195 return g_string_free(item_chars, FALSE);
4198 /* return the number of spaces at a line's start */
4199 static int compose_left_offset_length(GtkTextBuffer *buffer,
4200 const GtkTextIter *start)
4202 GtkTextIter iter = *start;
4203 gunichar wc;
4204 gint len = 0;
4205 if (gtk_text_iter_ends_line(&iter))
4206 return 0;
4208 while (1) {
4209 wc = gtk_text_iter_get_char(&iter);
4210 if (!g_unichar_isspace(wc))
4211 break;
4212 len++;
4213 gtk_text_iter_forward_char(&iter);
4214 if (gtk_text_iter_ends_line(&iter))
4215 return 0;
4218 gtk_text_iter_forward_char(&iter);
4219 if (gtk_text_iter_ends_line(&iter))
4220 return 0;
4221 return len;
4224 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4225 const GtkTextIter *start,
4226 GtkTextIter *break_pos,
4227 gint max_col,
4228 gint quote_len)
4230 GtkTextIter iter = *start, line_end = *start;
4231 PangoLogAttr *attrs;
4232 gchar *str;
4233 gchar *p;
4234 gint len;
4235 gint i;
4236 gint col = 0;
4237 gint pos = 0;
4238 gboolean can_break = FALSE;
4239 gboolean do_break = FALSE;
4240 gboolean was_white = FALSE;
4241 gboolean prev_dont_break = FALSE;
4243 gtk_text_iter_forward_to_line_end(&line_end);
4244 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4245 len = g_utf8_strlen(str, -1);
4247 if (len == 0) {
4248 g_free(str);
4249 g_warning("compose_get_line_break_pos: len = 0!");
4250 return FALSE;
4253 /* g_print("breaking line: %d: %s (len = %d)\n",
4254 gtk_text_iter_get_line(&iter), str, len); */
4256 attrs = g_new(PangoLogAttr, len + 1);
4258 pango_default_break(str, -1, NULL, attrs, len + 1);
4260 p = str;
4262 /* skip quote and leading spaces */
4263 for (i = 0; *p != '\0' && i < len; i++) {
4264 gunichar wc;
4266 wc = g_utf8_get_char(p);
4267 if (i >= quote_len && !g_unichar_isspace(wc))
4268 break;
4269 if (g_unichar_iswide(wc))
4270 col += 2;
4271 else if (*p == '\t')
4272 col += 8;
4273 else
4274 col++;
4275 p = g_utf8_next_char(p);
4278 for (; *p != '\0' && i < len; i++) {
4279 PangoLogAttr *attr = attrs + i;
4280 gunichar wc = g_utf8_get_char(p);
4281 gint uri_len;
4283 /* attr->is_line_break will be false for some characters that
4284 * we want to break a line before, like '/' or ':', so we
4285 * also allow breaking on any non-wide character. The
4286 * mentioned pango attribute is still useful to decide on
4287 * line breaks when wide characters are involved. */
4288 if ((!g_unichar_iswide(wc) || attr->is_line_break)
4289 && can_break && was_white && !prev_dont_break)
4290 pos = i;
4292 was_white = attr->is_white;
4294 /* don't wrap URI */
4295 if ((uri_len = get_uri_len(p)) > 0) {
4296 col += uri_len;
4297 if (pos > 0 && col > max_col) {
4298 do_break = TRUE;
4299 break;
4301 i += uri_len - 1;
4302 p += uri_len;
4303 can_break = TRUE;
4304 continue;
4307 if (g_unichar_iswide(wc)) {
4308 col += 2;
4309 if (prev_dont_break && can_break && attr->is_line_break)
4310 pos = i;
4311 } else if (*p == '\t')
4312 col += 8;
4313 else
4314 col++;
4315 if (pos > 0 && col > max_col) {
4316 do_break = TRUE;
4317 break;
4320 if (*p == '-' || *p == '/')
4321 prev_dont_break = TRUE;
4322 else
4323 prev_dont_break = FALSE;
4325 p = g_utf8_next_char(p);
4326 can_break = TRUE;
4329 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4331 g_free(attrs);
4332 g_free(str);
4334 *break_pos = *start;
4335 gtk_text_iter_set_line_offset(break_pos, pos);
4337 return do_break;
4340 static gboolean compose_join_next_line(Compose *compose,
4341 GtkTextBuffer *buffer,
4342 GtkTextIter *iter,
4343 const gchar *quote_str)
4345 GtkTextIter iter_ = *iter, cur, prev, next, end;
4346 PangoLogAttr attrs[3];
4347 gchar *str;
4348 gchar *next_quote_str;
4349 gunichar wc1, wc2;
4350 gint quote_len;
4351 gboolean keep_cursor = FALSE;
4353 if (!gtk_text_iter_forward_line(&iter_) ||
4354 gtk_text_iter_ends_line(&iter_)) {
4355 return FALSE;
4357 next_quote_str = compose_get_quote_str(buffer, &iter_, &quote_len);
4359 if ((quote_str || next_quote_str) &&
4360 g_strcmp0(quote_str, next_quote_str) != 0) {
4361 g_free(next_quote_str);
4362 return FALSE;
4364 g_free(next_quote_str);
4366 end = iter_;
4367 if (quote_len > 0) {
4368 gtk_text_iter_forward_chars(&end, quote_len);
4369 if (gtk_text_iter_ends_line(&end)) {
4370 return FALSE;
4374 /* don't join itemized lines */
4375 if (compose_itemized_length(buffer, &end) > 0) {
4376 return FALSE;
4379 /* don't join signature separator */
4380 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4381 return FALSE;
4383 /* delete quote str */
4384 if (quote_len > 0)
4385 gtk_text_buffer_delete(buffer, &iter_, &end);
4387 /* don't join line breaks put by the user */
4388 prev = cur = iter_;
4389 gtk_text_iter_backward_char(&cur);
4390 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4391 gtk_text_iter_forward_char(&cur);
4392 *iter = cur;
4393 return FALSE;
4395 gtk_text_iter_forward_char(&cur);
4396 /* delete linebreak and extra spaces */
4397 while (gtk_text_iter_backward_char(&cur)) {
4398 wc1 = gtk_text_iter_get_char(&cur);
4399 if (!g_unichar_isspace(wc1))
4400 break;
4401 prev = cur;
4403 next = cur = iter_;
4404 while (!gtk_text_iter_ends_line(&cur)) {
4405 wc1 = gtk_text_iter_get_char(&cur);
4406 if (!g_unichar_isspace(wc1))
4407 break;
4408 gtk_text_iter_forward_char(&cur);
4409 next = cur;
4411 if (!gtk_text_iter_equal(&prev, &next)) {
4412 GtkTextMark *mark;
4414 mark = gtk_text_buffer_get_insert(buffer);
4415 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4416 if (gtk_text_iter_equal(&prev, &cur))
4417 keep_cursor = TRUE;
4418 gtk_text_buffer_delete(buffer, &prev, &next);
4420 iter_ = prev;
4422 /* insert space if required */
4423 gtk_text_iter_backward_char(&prev);
4424 wc1 = gtk_text_iter_get_char(&prev);
4425 wc2 = gtk_text_iter_get_char(&next);
4426 gtk_text_iter_forward_char(&next);
4427 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4428 pango_default_break(str, -1, NULL, attrs, 3);
4429 if (!attrs[1].is_line_break ||
4430 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4431 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4432 if (keep_cursor) {
4433 gtk_text_iter_backward_char(&iter_);
4434 gtk_text_buffer_place_cursor(buffer, &iter_);
4437 g_free(str);
4439 *iter = iter_;
4440 return TRUE;
4443 #define ADD_TXT_POS(bp_, ep_, pti_) \
4444 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4445 last = last->next; \
4446 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4447 last->next = NULL; \
4448 } else { \
4449 g_warning("alloc error scanning URIs"); \
4452 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4454 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4455 GtkTextBuffer *buffer;
4456 GtkTextIter iter, break_pos, end_of_line;
4457 gchar *quote_str = NULL;
4458 gint quote_len;
4459 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4460 gboolean prev_autowrap = compose->autowrap;
4461 gint startq_offset = -1, noq_offset = -1;
4462 gint uri_start = -1, uri_stop = -1;
4463 gint nouri_start = -1, nouri_stop = -1;
4464 gint num_blocks = 0;
4465 gint quotelevel = -1;
4466 gboolean modified = force;
4467 gboolean removed = FALSE;
4468 gboolean modified_before_remove = FALSE;
4469 gint lines = 0;
4470 gboolean start = TRUE;
4471 gint itemized_len = 0, rem_item_len = 0;
4472 gchar *itemized_chars = NULL;
4473 gboolean item_continuation = FALSE;
4475 if (force) {
4476 modified = TRUE;
4478 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4479 modified = TRUE;
4482 compose->autowrap = FALSE;
4484 buffer = gtk_text_view_get_buffer(text);
4485 undo_wrapping(compose->undostruct, TRUE);
4486 if (par_iter) {
4487 iter = *par_iter;
4488 } else {
4489 GtkTextMark *mark;
4490 mark = gtk_text_buffer_get_insert(buffer);
4491 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4495 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4496 if (gtk_text_iter_ends_line(&iter)) {
4497 while (gtk_text_iter_ends_line(&iter) &&
4498 gtk_text_iter_forward_line(&iter))
4500 } else {
4501 while (gtk_text_iter_backward_line(&iter)) {
4502 if (gtk_text_iter_ends_line(&iter)) {
4503 gtk_text_iter_forward_line(&iter);
4504 break;
4508 } else {
4509 /* move to line start */
4510 gtk_text_iter_set_line_offset(&iter, 0);
4513 itemized_len = compose_itemized_length(buffer, &iter);
4515 if (!itemized_len) {
4516 itemized_len = compose_left_offset_length(buffer, &iter);
4517 item_continuation = TRUE;
4520 if (itemized_len)
4521 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4523 /* go until paragraph end (empty line) */
4524 while (start || !gtk_text_iter_ends_line(&iter)) {
4525 gchar *scanpos = NULL;
4526 /* parse table - in order of priority */
4527 struct table {
4528 const gchar *needle; /* token */
4530 /* token search function */
4531 gchar *(*search) (const gchar *haystack,
4532 const gchar *needle);
4533 /* part parsing function */
4534 gboolean (*parse) (const gchar *start,
4535 const gchar *scanpos,
4536 const gchar **bp_,
4537 const gchar **ep_,
4538 gboolean hdr);
4539 /* part to URI function */
4540 gchar *(*build_uri) (const gchar *bp,
4541 const gchar *ep);
4544 static struct table parser[] = {
4545 {"http://", strcasestr, get_uri_part, make_uri_string},
4546 {"https://", strcasestr, get_uri_part, make_uri_string},
4547 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4548 {"ftps://", strcasestr, get_uri_part, make_uri_string},
4549 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4550 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4551 {"www.", strcasestr, get_uri_part, make_http_string},
4552 {"webcal://",strcasestr, get_uri_part, make_uri_string},
4553 {"webcals://",strcasestr, get_uri_part, make_uri_string},
4554 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4555 {"@", strcasestr, get_email_part, make_email_string}
4557 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4558 gint last_index = PARSE_ELEMS;
4559 gint n;
4560 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4561 gint walk_pos;
4563 start = FALSE;
4564 if (!prev_autowrap && num_blocks == 0) {
4565 num_blocks++;
4566 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4567 G_CALLBACK(text_inserted),
4568 compose);
4570 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4571 goto colorize;
4573 uri_start = uri_stop = -1;
4574 quote_len = 0;
4575 quote_str = compose_get_quote_str(buffer, &iter, &quote_len);
4577 if (quote_str) {
4578 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4579 if (startq_offset == -1)
4580 startq_offset = gtk_text_iter_get_offset(&iter);
4581 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4582 if (quotelevel > 2) {
4583 /* recycle colors */
4584 if (prefs_common.recycle_quote_colors)
4585 quotelevel %= 3;
4586 else
4587 quotelevel = 2;
4589 if (!wrap_quote) {
4590 goto colorize;
4592 } else {
4593 if (startq_offset == -1)
4594 noq_offset = gtk_text_iter_get_offset(&iter);
4595 quotelevel = -1;
4598 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4599 goto colorize;
4601 if (gtk_text_iter_ends_line(&iter)) {
4602 goto colorize;
4603 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4604 prefs_common.linewrap_len,
4605 quote_len)) {
4606 GtkTextIter prev, next, cur;
4607 if (prev_autowrap != FALSE || force) {
4608 compose->automatic_break = TRUE;
4609 modified = TRUE;
4610 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4611 compose->automatic_break = FALSE;
4612 if (itemized_len && compose->autoindent) {
4613 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4614 if (!item_continuation)
4615 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4617 } else if (quote_str && wrap_quote) {
4618 compose->automatic_break = TRUE;
4619 modified = TRUE;
4620 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4621 compose->automatic_break = FALSE;
4622 if (itemized_len && compose->autoindent) {
4623 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4624 if (!item_continuation)
4625 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4627 } else
4628 goto colorize;
4629 /* remove trailing spaces */
4630 cur = break_pos;
4631 rem_item_len = itemized_len;
4632 while (compose->autoindent && rem_item_len-- > 0)
4633 gtk_text_iter_backward_char(&cur);
4634 gtk_text_iter_backward_char(&cur);
4636 prev = next = cur;
4637 while (!gtk_text_iter_starts_line(&cur)) {
4638 gunichar wc;
4640 gtk_text_iter_backward_char(&cur);
4641 wc = gtk_text_iter_get_char(&cur);
4642 if (!g_unichar_isspace(wc))
4643 break;
4644 prev = cur;
4646 if (!gtk_text_iter_equal(&prev, &next)) {
4647 gtk_text_buffer_delete(buffer, &prev, &next);
4648 break_pos = next;
4649 gtk_text_iter_forward_char(&break_pos);
4652 if (quote_str)
4653 gtk_text_buffer_insert(buffer, &break_pos,
4654 quote_str, -1);
4656 iter = break_pos;
4657 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4659 /* move iter to current line start */
4660 gtk_text_iter_set_line_offset(&iter, 0);
4661 if (quote_str) {
4662 g_free(quote_str);
4663 quote_str = NULL;
4665 continue;
4666 } else {
4667 /* move iter to next line start */
4668 iter = break_pos;
4669 lines++;
4672 colorize:
4673 if (!prev_autowrap && num_blocks > 0) {
4674 num_blocks--;
4675 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4676 G_CALLBACK(text_inserted),
4677 compose);
4679 end_of_line = iter;
4680 while (!gtk_text_iter_ends_line(&end_of_line)) {
4681 gtk_text_iter_forward_char(&end_of_line);
4683 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4685 nouri_start = gtk_text_iter_get_offset(&iter);
4686 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4688 walk_pos = gtk_text_iter_get_offset(&iter);
4689 /* FIXME: this looks phony. scanning for anything in the parse table */
4690 for (n = 0; n < PARSE_ELEMS; n++) {
4691 gchar *tmp;
4693 tmp = parser[n].search(walk, parser[n].needle);
4694 if (tmp) {
4695 if (scanpos == NULL || tmp < scanpos) {
4696 scanpos = tmp;
4697 last_index = n;
4702 bp = ep = 0;
4703 if (scanpos) {
4704 /* check if URI can be parsed */
4705 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4706 (const gchar **)&ep, FALSE)
4707 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4708 walk = ep;
4709 } else
4710 walk = scanpos +
4711 strlen(parser[last_index].needle);
4713 if (bp && ep) {
4714 uri_start = walk_pos + (bp - o_walk);
4715 uri_stop = walk_pos + (ep - o_walk);
4717 g_free(o_walk);
4718 o_walk = NULL;
4719 gtk_text_iter_forward_line(&iter);
4720 g_free(quote_str);
4721 quote_str = NULL;
4722 if (startq_offset != -1) {
4723 GtkTextIter startquote, endquote;
4724 gtk_text_buffer_get_iter_at_offset(
4725 buffer, &startquote, startq_offset);
4726 endquote = iter;
4728 switch (quotelevel) {
4729 case 0:
4730 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4731 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4732 gtk_text_buffer_apply_tag_by_name(
4733 buffer, "quote0", &startquote, &endquote);
4734 gtk_text_buffer_remove_tag_by_name(
4735 buffer, "quote1", &startquote, &endquote);
4736 gtk_text_buffer_remove_tag_by_name(
4737 buffer, "quote2", &startquote, &endquote);
4738 modified = TRUE;
4740 break;
4741 case 1:
4742 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4743 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4744 gtk_text_buffer_apply_tag_by_name(
4745 buffer, "quote1", &startquote, &endquote);
4746 gtk_text_buffer_remove_tag_by_name(
4747 buffer, "quote0", &startquote, &endquote);
4748 gtk_text_buffer_remove_tag_by_name(
4749 buffer, "quote2", &startquote, &endquote);
4750 modified = TRUE;
4752 break;
4753 case 2:
4754 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4755 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4756 gtk_text_buffer_apply_tag_by_name(
4757 buffer, "quote2", &startquote, &endquote);
4758 gtk_text_buffer_remove_tag_by_name(
4759 buffer, "quote0", &startquote, &endquote);
4760 gtk_text_buffer_remove_tag_by_name(
4761 buffer, "quote1", &startquote, &endquote);
4762 modified = TRUE;
4764 break;
4766 startq_offset = -1;
4767 } else if (noq_offset != -1) {
4768 GtkTextIter startnoquote, endnoquote;
4769 gtk_text_buffer_get_iter_at_offset(
4770 buffer, &startnoquote, noq_offset);
4771 endnoquote = iter;
4773 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4774 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4775 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4776 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4777 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4778 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4779 gtk_text_buffer_remove_tag_by_name(
4780 buffer, "quote0", &startnoquote, &endnoquote);
4781 gtk_text_buffer_remove_tag_by_name(
4782 buffer, "quote1", &startnoquote, &endnoquote);
4783 gtk_text_buffer_remove_tag_by_name(
4784 buffer, "quote2", &startnoquote, &endnoquote);
4785 modified = TRUE;
4787 noq_offset = -1;
4790 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4791 GtkTextIter nouri_start_iter, nouri_end_iter;
4792 gtk_text_buffer_get_iter_at_offset(
4793 buffer, &nouri_start_iter, nouri_start);
4794 gtk_text_buffer_get_iter_at_offset(
4795 buffer, &nouri_end_iter, nouri_stop);
4796 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4797 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4798 gtk_text_buffer_remove_tag_by_name(
4799 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4800 modified_before_remove = modified;
4801 modified = TRUE;
4802 removed = TRUE;
4805 if (uri_start >= 0 && uri_stop > 0) {
4806 GtkTextIter uri_start_iter, uri_end_iter, back;
4807 gtk_text_buffer_get_iter_at_offset(
4808 buffer, &uri_start_iter, uri_start);
4809 gtk_text_buffer_get_iter_at_offset(
4810 buffer, &uri_end_iter, uri_stop);
4811 back = uri_end_iter;
4812 gtk_text_iter_backward_char(&back);
4813 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4814 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4815 gtk_text_buffer_apply_tag_by_name(
4816 buffer, "link", &uri_start_iter, &uri_end_iter);
4817 modified = TRUE;
4818 if (removed && !modified_before_remove) {
4819 modified = FALSE;
4823 if (!modified) {
4824 /* debug_print("not modified, out after %d lines\n", lines); */
4825 goto end;
4828 /* debug_print("modified, out after %d lines\n", lines); */
4829 end:
4830 g_free(itemized_chars);
4831 if (par_iter)
4832 *par_iter = iter;
4833 undo_wrapping(compose->undostruct, FALSE);
4834 compose->autowrap = prev_autowrap;
4836 return modified;
4839 void compose_action_cb(void *data)
4841 Compose *compose = (Compose *)data;
4842 compose_wrap_all(compose);
4845 static void compose_wrap_all(Compose *compose)
4847 compose_wrap_all_full(compose, FALSE);
4850 static void compose_wrap_all_full(Compose *compose, gboolean force)
4852 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4853 GtkTextBuffer *buffer;
4854 GtkTextIter iter;
4855 gboolean modified = TRUE;
4857 buffer = gtk_text_view_get_buffer(text);
4859 gtk_text_buffer_get_start_iter(buffer, &iter);
4861 undo_wrapping(compose->undostruct, TRUE);
4863 while (!gtk_text_iter_is_end(&iter) && modified)
4864 modified = compose_beautify_paragraph(compose, &iter, force);
4866 undo_wrapping(compose->undostruct, FALSE);
4870 static void compose_set_title(Compose *compose)
4872 gchar *str;
4873 gchar *edited;
4874 gchar *subject;
4876 edited = compose->modified ? _(" [Edited]") : "";
4878 subject = gtk_editable_get_chars(
4879 GTK_EDITABLE(compose->subject_entry), 0, -1);
4881 #ifndef GENERIC_UMPC
4882 if (subject && strlen(subject))
4883 str = g_strdup_printf(_("%s - Compose message%s"),
4884 subject, edited);
4885 else
4886 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4887 #else
4888 str = g_strdup(_("Compose message"));
4889 #endif
4891 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4892 g_free(str);
4893 g_free(subject);
4897 * compose_current_mail_account:
4899 * Find a current mail account (the currently selected account, or the
4900 * default account, if a news account is currently selected). If a
4901 * mail account cannot be found, display an error message.
4903 * Return value: Mail account, or NULL if not found.
4905 static PrefsAccount *
4906 compose_current_mail_account(void)
4908 PrefsAccount *ac;
4910 if (cur_account && cur_account->protocol != A_NNTP)
4911 ac = cur_account;
4912 else {
4913 ac = account_get_default();
4914 if (!ac || ac->protocol == A_NNTP) {
4915 alertpanel_error(_("Account for sending mail is not specified.\n"
4916 "Please select a mail account before sending."));
4917 return NULL;
4920 return ac;
4923 #define QUOTE_IF_REQUIRED(out, str) \
4925 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4926 gchar *__tmp; \
4927 gint len; \
4929 len = strlen(str) + 3; \
4930 if ((__tmp = alloca(len)) == NULL) { \
4931 g_warning("can't allocate memory"); \
4932 g_string_free(header, TRUE); \
4933 return NULL; \
4935 g_snprintf(__tmp, len, "\"%s\"", str); \
4936 out = __tmp; \
4937 } else { \
4938 gchar *__tmp; \
4940 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4941 g_warning("can't allocate memory"); \
4942 g_string_free(header, TRUE); \
4943 return NULL; \
4944 } else \
4945 strcpy(__tmp, str); \
4947 out = __tmp; \
4951 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4953 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4954 gchar *__tmp; \
4955 gint len; \
4957 len = strlen(str) + 3; \
4958 if ((__tmp = alloca(len)) == NULL) { \
4959 g_warning("can't allocate memory"); \
4960 errret; \
4962 g_snprintf(__tmp, len, "\"%s\"", str); \
4963 out = __tmp; \
4964 } else { \
4965 gchar *__tmp; \
4967 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4968 g_warning("can't allocate memory"); \
4969 errret; \
4970 } else \
4971 strcpy(__tmp, str); \
4973 out = __tmp; \
4977 static void compose_select_account(Compose *compose, PrefsAccount *account,
4978 gboolean init)
4980 gchar *from = NULL, *header = NULL;
4981 ComposeHeaderEntry *header_entry;
4982 GtkTreeIter iter;
4984 cm_return_if_fail(account != NULL);
4986 compose->account = account;
4987 if (account->name && *account->name) {
4988 gchar *buf, *qbuf;
4989 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4990 qbuf = escape_internal_quotes(buf, '"');
4991 from = g_strdup_printf("%s <%s>",
4992 qbuf, account->address);
4993 if (qbuf != buf)
4994 g_free(qbuf);
4995 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4996 } else {
4997 from = g_strdup_printf("<%s>",
4998 account->address);
4999 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
5002 g_free(from);
5004 compose_set_title(compose);
5006 compose_activate_privacy_system(compose, account, FALSE);
5008 if (account->default_sign && privacy_system_can_sign(compose->privacy_system) &&
5009 compose->mode != COMPOSE_REDIRECT)
5010 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
5011 else
5012 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
5013 if (account->default_encrypt && privacy_system_can_encrypt(compose->privacy_system) &&
5014 compose->mode != COMPOSE_REDIRECT)
5015 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
5016 else
5017 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
5019 if (!init && compose->mode != COMPOSE_REDIRECT) {
5020 undo_block(compose->undostruct);
5021 compose_insert_sig(compose, TRUE);
5022 undo_unblock(compose->undostruct);
5025 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
5026 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
5027 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
5028 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5030 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5031 if (account->protocol == A_NNTP) {
5032 if (!strcmp(header, _("To:")))
5033 combobox_select_by_text(
5034 GTK_COMBO_BOX(header_entry->combo),
5035 _("Newsgroups:"));
5036 } else {
5037 if (!strcmp(header, _("Newsgroups:")))
5038 combobox_select_by_text(
5039 GTK_COMBO_BOX(header_entry->combo),
5040 _("To:"));
5044 g_free(header);
5046 #ifdef USE_ENCHANT
5047 /* use account's dict info if set */
5048 if (compose->gtkaspell) {
5049 if (account->enable_default_dictionary)
5050 gtkaspell_change_dict(compose->gtkaspell,
5051 account->default_dictionary, FALSE);
5052 if (account->enable_default_alt_dictionary)
5053 gtkaspell_change_alt_dict(compose->gtkaspell,
5054 account->default_alt_dictionary);
5055 if (account->enable_default_dictionary
5056 || account->enable_default_alt_dictionary)
5057 compose_spell_menu_changed(compose);
5059 #endif
5062 gboolean compose_check_for_valid_recipient(Compose *compose) {
5063 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5064 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5065 gboolean recipient_found = FALSE;
5066 GSList *list;
5067 gchar **strptr;
5069 /* free to and newsgroup list */
5070 slist_free_strings_full(compose->to_list);
5071 compose->to_list = NULL;
5073 slist_free_strings_full(compose->newsgroup_list);
5074 compose->newsgroup_list = NULL;
5076 /* search header entries for to and newsgroup entries */
5077 for (list = compose->header_list; list; list = list->next) {
5078 gchar *header;
5079 gchar *entry;
5080 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5081 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5082 g_strstrip(entry);
5083 g_strstrip(header);
5084 if (entry[0] != '\0') {
5085 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5086 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5087 compose->to_list = address_list_append(compose->to_list, entry);
5088 recipient_found = TRUE;
5091 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5092 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5093 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5094 recipient_found = TRUE;
5098 g_free(header);
5099 g_free(entry);
5101 return recipient_found;
5104 static gboolean compose_check_for_set_recipients(Compose *compose)
5106 if (compose->account->set_autocc && compose->account->auto_cc) {
5107 gboolean found_other = FALSE;
5108 GSList *list;
5109 /* search header entries for to and newsgroup entries */
5110 for (list = compose->header_list; list; list = list->next) {
5111 gchar *entry;
5112 gchar *header;
5113 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5114 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5115 g_strstrip(entry);
5116 g_strstrip(header);
5117 if (strcmp(entry, compose->account->auto_cc)
5118 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5119 found_other = TRUE;
5120 g_free(entry);
5121 break;
5123 g_free(entry);
5124 g_free(header);
5126 if (!found_other) {
5127 AlertValue aval;
5128 gchar *text;
5129 if (compose->batch) {
5130 gtk_widget_show_all(compose->window);
5132 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5133 prefs_common_translated_header_name("Cc"));
5134 aval = alertpanel(_("Send"),
5135 text,
5136 NULL, _("_Cancel"), NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND);
5137 g_free(text);
5138 if (aval != G_ALERTALTERNATE)
5139 return FALSE;
5142 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5143 gboolean found_other = FALSE;
5144 GSList *list;
5145 /* search header entries for to and newsgroup entries */
5146 for (list = compose->header_list; list; list = list->next) {
5147 gchar *entry;
5148 gchar *header;
5149 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5150 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5151 g_strstrip(entry);
5152 g_strstrip(header);
5153 if (strcmp(entry, compose->account->auto_bcc)
5154 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5155 found_other = TRUE;
5156 g_free(entry);
5157 g_free(header);
5158 break;
5160 g_free(entry);
5161 g_free(header);
5163 if (!found_other) {
5164 AlertValue aval;
5165 gchar *text;
5166 if (compose->batch) {
5167 gtk_widget_show_all(compose->window);
5169 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5170 prefs_common_translated_header_name("Bcc"));
5171 aval = alertpanel(_("Send"),
5172 text,
5173 NULL, _("_Cancel"), NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND);
5174 g_free(text);
5175 if (aval != G_ALERTALTERNATE)
5176 return FALSE;
5179 return TRUE;
5182 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5184 const gchar *str;
5186 if (compose_check_for_valid_recipient(compose) == FALSE) {
5187 if (compose->batch) {
5188 gtk_widget_show_all(compose->window);
5190 alertpanel_error(_("Recipient is not specified."));
5191 return FALSE;
5194 if (compose_check_for_set_recipients(compose) == FALSE) {
5195 return FALSE;
5198 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5199 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5200 if (*str == '\0' && check_everything == TRUE &&
5201 compose->mode != COMPOSE_REDIRECT) {
5202 AlertValue aval;
5203 gchar *message;
5205 message = g_strdup_printf(_("Subject is empty. %s"),
5206 compose->sending?_("Send it anyway?"):
5207 _("Queue it anyway?"));
5209 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5210 NULL, _("_Cancel"), NULL, compose->sending?_("_Send"):_("_Queue"),
5211 NULL, NULL, ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5212 g_free(message);
5213 if (aval & G_ALERTDISABLE) {
5214 aval &= ~G_ALERTDISABLE;
5215 prefs_common.warn_empty_subj = FALSE;
5217 if (aval != G_ALERTALTERNATE)
5218 return FALSE;
5222 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5223 && check_everything == TRUE) {
5224 GSList *list;
5225 gint cnt = 0;
5227 /* count To and Cc recipients */
5228 for (list = compose->header_list; list; list = list->next) {
5229 gchar *header;
5230 gchar *entry;
5232 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5233 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5234 g_strstrip(header);
5235 g_strstrip(entry);
5236 if ((entry[0] != '\0') &&
5237 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5238 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5239 cnt++;
5241 g_free(header);
5242 g_free(entry);
5244 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5245 AlertValue aval;
5246 gchar *message;
5248 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5249 compose->sending?_("Send it anyway?"):
5250 _("Queue it anyway?"));
5252 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5253 NULL, _("_Cancel"), NULL, compose->sending?_("_Send"):_("_Queue"),
5254 NULL, NULL, ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5255 g_free(message);
5256 if (aval & G_ALERTDISABLE) {
5257 aval &= ~G_ALERTDISABLE;
5258 prefs_common.warn_sending_many_recipients_num = 0;
5260 if (aval != G_ALERTALTERNATE)
5261 return FALSE;
5265 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5266 return FALSE;
5268 return TRUE;
5271 static void _display_queue_error(ComposeQueueResult val)
5273 switch (val) {
5274 case COMPOSE_QUEUE_SUCCESS:
5275 break;
5276 case COMPOSE_QUEUE_ERROR_NO_MSG:
5277 alertpanel_error(_("Could not queue message."));
5278 break;
5279 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5280 alertpanel_error(_("Could not queue message:\n\n%s."),
5281 g_strerror(errno));
5282 break;
5283 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5284 alertpanel_error(_("Could not queue message for sending:\n\n"
5285 "Signature failed: %s"),
5286 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5287 break;
5288 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5289 alertpanel_error(_("Could not queue message for sending:\n\n"
5290 "Encryption failed: %s"),
5291 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5292 break;
5293 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5294 alertpanel_error(_("Could not queue message for sending:\n\n"
5295 "Charset conversion failed."));
5296 break;
5297 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5298 alertpanel_error(_("Could not queue message for sending:\n\n"
5299 "Couldn't get recipient encryption key."));
5300 break;
5301 case COMPOSE_QUEUE_SIGNING_CANCELLED:
5302 debug_print("signing cancelled\n");
5303 break;
5304 default:
5305 /* unhandled error */
5306 debug_print("oops, unhandled compose_queue() return value %d\n",
5307 val);
5308 break;
5312 gint compose_send(Compose *compose)
5314 gint msgnum;
5315 FolderItem *folder = NULL;
5316 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5317 gchar *msgpath = NULL;
5318 gboolean discard_window = FALSE;
5319 gchar *errstr = NULL;
5320 gchar *tmsgid = NULL;
5321 MainWindow *mainwin = mainwindow_get_mainwindow();
5322 gboolean queued_removed = FALSE;
5324 if (prefs_common.send_dialog_invisible
5325 || compose->batch == TRUE)
5326 discard_window = TRUE;
5328 compose_allow_user_actions (compose, FALSE);
5329 compose->sending = TRUE;
5331 if (compose_check_entries(compose, TRUE) == FALSE) {
5332 if (compose->batch) {
5333 gtk_widget_show_all(compose->window);
5335 goto bail;
5338 inc_lock();
5339 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5341 if (val != COMPOSE_QUEUE_SUCCESS) {
5342 if (compose->batch) {
5343 gtk_widget_show_all(compose->window);
5346 _display_queue_error(val);
5348 goto bail;
5351 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5352 if (discard_window) {
5353 compose->sending = FALSE;
5354 compose_close(compose);
5355 /* No more compose access in the normal codepath
5356 * after this point! */
5357 compose = NULL;
5360 if (msgnum == 0) {
5361 alertpanel_error(_("The message was queued but could not be "
5362 "sent.\nUse \"Send queued messages\" from "
5363 "the main window to retry."));
5364 if (!discard_window) {
5365 goto bail;
5367 inc_unlock();
5368 g_free(tmsgid);
5369 return -1;
5371 if (msgpath == NULL) {
5372 msgpath = folder_item_fetch_msg(folder, msgnum);
5373 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5374 g_free(msgpath);
5375 } else {
5376 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5377 claws_unlink(msgpath);
5378 g_free(msgpath);
5380 if (!discard_window) {
5381 if (val != 0) {
5382 if (!queued_removed)
5383 folder_item_remove_msg(folder, msgnum);
5384 folder_item_scan(folder);
5385 if (tmsgid) {
5386 /* make sure we delete that */
5387 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5388 if (tmp) {
5389 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5390 folder_item_remove_msg(folder, tmp->msgnum);
5391 procmsg_msginfo_free(&tmp);
5397 if (val == 0) {
5398 if (!queued_removed)
5399 folder_item_remove_msg(folder, msgnum);
5400 folder_item_scan(folder);
5401 if (tmsgid) {
5402 /* make sure we delete that */
5403 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5404 if (tmp) {
5405 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5406 folder_item_remove_msg(folder, tmp->msgnum);
5407 procmsg_msginfo_free(&tmp);
5410 if (!discard_window) {
5411 compose->sending = FALSE;
5412 compose_allow_user_actions (compose, TRUE);
5413 compose_close(compose);
5415 } else {
5416 if (errstr) {
5417 alertpanel_error_log(_("%s\nYou can try to \"Send\" again "
5418 "or queue the message with \"Send later\""), errstr);
5419 g_free(errstr);
5420 } else {
5421 alertpanel_error_log(_("The message was queued but could not be "
5422 "sent.\nUse \"Send queued messages\" from "
5423 "the main window to retry."));
5425 if (!discard_window) {
5426 goto bail;
5428 inc_unlock();
5429 g_free(tmsgid);
5430 return -1;
5432 g_free(tmsgid);
5433 inc_unlock();
5434 toolbar_main_set_sensitive(mainwin);
5435 main_window_set_menu_sensitive(mainwin);
5436 return 0;
5438 bail:
5439 inc_unlock();
5440 g_free(tmsgid);
5441 compose_allow_user_actions (compose, TRUE);
5442 compose->sending = FALSE;
5443 compose->modified = TRUE;
5444 toolbar_main_set_sensitive(mainwin);
5445 main_window_set_menu_sensitive(mainwin);
5447 return -1;
5450 static gboolean compose_use_attach(Compose *compose)
5452 GtkTreeModel *model = gtk_tree_view_get_model
5453 (GTK_TREE_VIEW(compose->attach_clist));
5454 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5457 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5458 FILE *fp)
5460 gchar buf[BUFFSIZE];
5461 gchar *str;
5462 gboolean first_to_address;
5463 gboolean first_cc_address;
5464 GSList *list;
5465 ComposeHeaderEntry *headerentry;
5466 const gchar *headerentryname;
5467 const gchar *cc_hdr;
5468 const gchar *to_hdr;
5469 gboolean err = FALSE;
5471 debug_print("Writing redirect header\n");
5473 cc_hdr = prefs_common_translated_header_name("Cc:");
5474 to_hdr = prefs_common_translated_header_name("To:");
5476 first_to_address = TRUE;
5477 for (list = compose->header_list; list; list = list->next) {
5478 headerentry = ((ComposeHeaderEntry *)list->data);
5479 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5481 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5482 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5483 Xstrdup_a(str, entstr, return -1);
5484 g_strstrip(str);
5485 if (str[0] != '\0') {
5486 compose_convert_header
5487 (compose, buf, sizeof(buf), str,
5488 strlen("Resent-To") + 2, TRUE);
5490 if (first_to_address) {
5491 err |= (fprintf(fp, "Resent-To: ") < 0);
5492 first_to_address = FALSE;
5493 } else {
5494 err |= (fprintf(fp, ",") < 0);
5496 err |= (fprintf(fp, "%s", buf) < 0);
5500 if (!first_to_address) {
5501 err |= (fprintf(fp, "\n") < 0);
5504 first_cc_address = TRUE;
5505 for (list = compose->header_list; list; list = list->next) {
5506 headerentry = ((ComposeHeaderEntry *)list->data);
5507 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5509 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5510 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5511 Xstrdup_a(str, strg, return -1);
5512 g_strstrip(str);
5513 if (str[0] != '\0') {
5514 compose_convert_header
5515 (compose, buf, sizeof(buf), str,
5516 strlen("Resent-Cc") + 2, TRUE);
5518 if (first_cc_address) {
5519 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5520 first_cc_address = FALSE;
5521 } else {
5522 err |= (fprintf(fp, ",") < 0);
5524 err |= (fprintf(fp, "%s", buf) < 0);
5528 if (!first_cc_address) {
5529 err |= (fprintf(fp, "\n") < 0);
5532 return (err ? -1:0);
5535 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5537 gchar date[RFC822_DATE_BUFFSIZE];
5538 gchar buf[BUFFSIZE];
5539 gchar *str;
5540 const gchar *entstr;
5541 /* struct utsname utsbuf; */
5542 gboolean err = FALSE;
5544 cm_return_val_if_fail(fp != NULL, -1);
5545 cm_return_val_if_fail(compose->account != NULL, -1);
5546 cm_return_val_if_fail(compose->account->address != NULL, -1);
5548 /* Resent-Date */
5549 if (prefs_common.hide_timezone)
5550 get_rfc822_date_hide_tz(date, sizeof(date));
5551 else
5552 get_rfc822_date(date, sizeof(date));
5553 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5555 /* Resent-From */
5556 if (compose->account->name && *compose->account->name) {
5557 compose_convert_header
5558 (compose, buf, sizeof(buf), compose->account->name,
5559 strlen("From: "), TRUE);
5560 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5561 buf, compose->account->address) < 0);
5562 } else
5563 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5565 /* Subject */
5566 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5567 if (*entstr != '\0') {
5568 Xstrdup_a(str, entstr, return -1);
5569 g_strstrip(str);
5570 if (*str != '\0') {
5571 compose_convert_header(compose, buf, sizeof(buf), str,
5572 strlen("Subject: "), FALSE);
5573 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5577 /* Resent-Message-ID */
5578 if (compose->account->gen_msgid) {
5579 gchar *addr = prefs_account_generate_msgid(compose->account);
5580 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5581 if (compose->msgid)
5582 g_free(compose->msgid);
5583 compose->msgid = addr;
5584 } else {
5585 compose->msgid = NULL;
5588 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5589 return -1;
5591 /* separator between header and body */
5592 err |= (claws_fputs("\n", fp) == EOF);
5594 return (err ? -1:0);
5597 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5599 FILE *fp;
5600 size_t len;
5601 gchar *buf = NULL;
5602 gchar rewrite_buf[BUFFSIZE];
5603 int i = 0;
5604 gboolean skip = FALSE;
5605 gboolean err = FALSE;
5606 gchar *not_included[]={
5607 "Return-Path:", "Delivered-To:", "Received:",
5608 "Subject:", "X-UIDL:", "AF:",
5609 "NF:", "PS:", "SRH:",
5610 "SFN:", "DSR:", "MID:",
5611 "CFG:", "PT:", "S:",
5612 "RQ:", "SSV:", "NSV:",
5613 "SSH:", "R:", "MAID:",
5614 "NAID:", "RMID:", "FMID:",
5615 "SCF:", "RRCPT:", "NG:",
5616 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5617 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5618 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5619 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5620 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5621 NULL
5623 gint ret = 0;
5625 if ((fp = claws_fopen(compose->redirect_filename, "rb")) == NULL) {
5626 FILE_OP_ERROR(compose->redirect_filename, "claws_fopen");
5627 return -1;
5630 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5631 skip = FALSE;
5632 for (i = 0; not_included[i] != NULL; i++) {
5633 if (g_ascii_strncasecmp(buf, not_included[i],
5634 strlen(not_included[i])) == 0) {
5635 skip = TRUE;
5636 break;
5639 if (skip) {
5640 g_free(buf);
5641 buf = NULL;
5642 continue;
5644 if (claws_fputs(buf, fdest) == -1) {
5645 g_free(buf);
5646 buf = NULL;
5647 goto error;
5650 if (!prefs_common.redirect_keep_from) {
5651 if (g_ascii_strncasecmp(buf, "From:",
5652 strlen("From:")) == 0) {
5653 err |= (claws_fputs(" (by way of ", fdest) == EOF);
5654 if (compose->account->name
5655 && *compose->account->name) {
5656 gchar buffer[BUFFSIZE];
5658 compose_convert_header
5659 (compose, buffer, sizeof(buffer),
5660 compose->account->name,
5661 strlen("From: "),
5662 FALSE);
5663 err |= (fprintf(fdest, "%s <%s>",
5664 buffer,
5665 compose->account->address) < 0);
5666 } else
5667 err |= (fprintf(fdest, "%s",
5668 compose->account->address) < 0);
5669 err |= (claws_fputs(")", fdest) == EOF);
5673 g_free(buf);
5674 buf = NULL;
5675 if (claws_fputs("\n", fdest) == -1)
5676 goto error;
5679 if (err)
5680 goto error;
5682 if (compose_redirect_write_headers(compose, fdest))
5683 goto error;
5685 while ((len = claws_fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5686 if (claws_fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5687 goto error;
5690 claws_fclose(fp);
5692 return 0;
5694 error:
5695 claws_fclose(fp);
5697 return -1;
5700 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5702 GtkTextBuffer *buffer;
5703 GtkTextIter start, end, tmp;
5704 gchar *chars, *tmp_enc_file = NULL, *content;
5705 gchar *buf, *msg;
5706 const gchar *out_codeset;
5707 EncodingType encoding = ENC_UNKNOWN;
5708 MimeInfo *mimemsg, *mimetext;
5709 gint line;
5710 const gchar *src_codeset = CS_INTERNAL;
5711 gchar *from_addr = NULL;
5712 gchar *from_name = NULL;
5713 FolderItem *outbox;
5715 if (action == COMPOSE_WRITE_FOR_SEND) {
5716 attach_parts = TRUE;
5718 /* We're sending the message, generate a Message-ID
5719 * if necessary. */
5720 if (compose->msgid == NULL &&
5721 compose->account->gen_msgid) {
5722 compose->msgid = prefs_account_generate_msgid(compose->account);
5726 /* create message MimeInfo */
5727 mimemsg = procmime_mimeinfo_new();
5728 mimemsg->type = MIMETYPE_MESSAGE;
5729 mimemsg->subtype = g_strdup("rfc822");
5730 mimemsg->content = MIMECONTENT_MEM;
5731 mimemsg->tmp = TRUE; /* must free content later */
5732 mimemsg->data.mem = compose_get_header(compose);
5734 /* Create text part MimeInfo */
5735 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5736 gtk_text_buffer_get_end_iter(buffer, &end);
5737 tmp = end;
5739 /* We make sure that there is a newline at the end. */
5740 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5741 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5742 if (*chars != '\n') {
5743 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5745 g_free(chars);
5748 /* get all composed text */
5749 gtk_text_buffer_get_start_iter(buffer, &start);
5750 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5752 out_codeset = conv_get_charset_str(compose->out_encoding);
5754 if (!out_codeset && is_ascii_str(chars)) {
5755 out_codeset = CS_US_ASCII;
5756 } else if (prefs_common.outgoing_fallback_to_ascii &&
5757 is_ascii_str(chars)) {
5758 out_codeset = CS_US_ASCII;
5759 encoding = ENC_7BIT;
5762 if (!out_codeset) {
5763 gchar *test_conv_global_out = NULL;
5764 gchar *test_conv_reply = NULL;
5766 /* automatic mode. be automatic. */
5767 codeconv_set_strict(TRUE);
5769 out_codeset = conv_get_outgoing_charset_str();
5770 if (out_codeset) {
5771 debug_print("trying to convert to %s\n", out_codeset);
5772 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5775 if (!test_conv_global_out && compose->orig_charset
5776 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5777 out_codeset = compose->orig_charset;
5778 debug_print("failure; trying to convert to %s\n", out_codeset);
5779 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5782 if (!test_conv_global_out && !test_conv_reply) {
5783 /* we're lost */
5784 out_codeset = CS_INTERNAL;
5785 debug_print("failure; finally using %s\n", out_codeset);
5787 g_free(test_conv_global_out);
5788 g_free(test_conv_reply);
5789 codeconv_set_strict(FALSE);
5792 if (encoding == ENC_UNKNOWN) {
5793 if (prefs_common.encoding_method == CTE_BASE64)
5794 encoding = ENC_BASE64;
5795 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5796 encoding = ENC_QUOTED_PRINTABLE;
5797 else if (prefs_common.encoding_method == CTE_8BIT)
5798 encoding = ENC_8BIT;
5799 else
5800 encoding = procmime_get_encoding_for_charset(out_codeset);
5803 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5804 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5806 if (action == COMPOSE_WRITE_FOR_SEND) {
5807 codeconv_set_strict(TRUE);
5808 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5809 codeconv_set_strict(FALSE);
5811 if (!buf) {
5812 AlertValue aval;
5814 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5815 "to the specified %s charset.\n"
5816 "Send it as %s?"), out_codeset, src_codeset);
5817 aval = alertpanel_full(_("Error"), msg, NULL, _("_Cancel"),
5818 NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND, FALSE,
5819 NULL, ALERT_ERROR);
5820 g_free(msg);
5822 if (aval != G_ALERTALTERNATE) {
5823 g_free(chars);
5824 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5825 } else {
5826 buf = chars;
5827 out_codeset = src_codeset;
5828 chars = NULL;
5831 } else {
5832 buf = chars;
5833 out_codeset = src_codeset;
5834 chars = NULL;
5836 g_free(chars);
5838 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5839 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5840 strstr(buf, "\nFrom ") != NULL) {
5841 encoding = ENC_QUOTED_PRINTABLE;
5845 mimetext = procmime_mimeinfo_new();
5846 mimetext->content = MIMECONTENT_MEM;
5847 mimetext->tmp = TRUE; /* must free content later */
5848 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5849 * and free the data, which we need later. */
5850 mimetext->data.mem = g_strdup(buf);
5851 mimetext->type = MIMETYPE_TEXT;
5852 mimetext->subtype = g_strdup("plain");
5853 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5854 g_strdup(out_codeset));
5856 /* protect trailing spaces when signing message */
5857 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5858 privacy_system_can_sign(compose->privacy_system)) {
5859 encoding = ENC_QUOTED_PRINTABLE;
5862 debug_print("main text: %" G_GSIZE_FORMAT " bytes encoded as %s in %d\n",
5863 strlen(buf), out_codeset, encoding);
5865 /* check for line length limit */
5866 if (action == COMPOSE_WRITE_FOR_SEND &&
5867 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5868 check_line_length(buf, 1000, &line) < 0) {
5869 AlertValue aval;
5871 msg = g_strdup_printf
5872 (_("Line %d exceeds the line length limit (998 bytes).\n"
5873 "The contents of the message might be broken on the way to the delivery.\n"
5874 "\n"
5875 "Send it anyway?"), line + 1);
5876 aval = alertpanel(_("Warning"), msg, NULL, _("_Cancel"), NULL, _("_OK"),
5877 NULL, NULL, ALERTFOCUS_FIRST);
5878 g_free(msg);
5879 if (aval != G_ALERTALTERNATE) {
5880 g_free(buf);
5881 return COMPOSE_QUEUE_ERROR_NO_MSG;
5885 if (encoding != ENC_UNKNOWN)
5886 procmime_encode_content(mimetext, encoding);
5888 /* append attachment parts */
5889 if (compose_use_attach(compose) && attach_parts) {
5890 MimeInfo *mimempart;
5891 gchar *boundary = NULL;
5892 mimempart = procmime_mimeinfo_new();
5893 mimempart->content = MIMECONTENT_EMPTY;
5894 mimempart->type = MIMETYPE_MULTIPART;
5895 mimempart->subtype = g_strdup("mixed");
5897 do {
5898 g_free(boundary);
5899 boundary = generate_mime_boundary(NULL);
5900 } while (strstr(buf, boundary) != NULL);
5902 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5903 boundary);
5905 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5907 g_node_append(mimempart->node, mimetext->node);
5908 g_node_append(mimemsg->node, mimempart->node);
5910 if (compose_add_attachments(compose, mimempart, action) < 0)
5911 return COMPOSE_QUEUE_ERROR_NO_MSG;
5912 } else
5913 g_node_append(mimemsg->node, mimetext->node);
5915 g_free(buf);
5917 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5918 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5919 /* extract name and address */
5920 if (strstr(spec, " <") && strstr(spec, ">")) {
5921 from_addr = g_strdup(strrchr(spec, '<')+1);
5922 *(strrchr(from_addr, '>')) = '\0';
5923 from_name = g_strdup(spec);
5924 *(strrchr(from_name, '<')) = '\0';
5925 } else {
5926 from_name = NULL;
5927 from_addr = NULL;
5929 g_free(spec);
5931 /* sign message if sending */
5932 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5933 privacy_system_can_sign(compose->privacy_system))
5934 if (!privacy_sign(compose->privacy_system, mimemsg,
5935 compose->account, from_addr)) {
5936 g_free(from_name);
5937 g_free(from_addr);
5938 if (!privacy_peek_error())
5939 return COMPOSE_QUEUE_SIGNING_CANCELLED;
5940 else
5941 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5943 g_free(from_name);
5944 g_free(from_addr);
5946 if (compose->use_encryption) {
5947 if (compose->encdata != NULL &&
5948 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5950 /* First, write an unencrypted copy and save it to outbox, if
5951 * user wants that. */
5952 if (compose->account->save_encrypted_as_clear_text) {
5953 debug_print("saving sent message unencrypted...\n");
5954 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5955 if (tmpfp) {
5956 claws_fclose(tmpfp);
5958 /* fp now points to a file with headers written,
5959 * let's make a copy. */
5960 rewind(fp);
5961 content = file_read_stream_to_str(fp);
5963 str_write_to_file(content, tmp_enc_file, TRUE);
5964 g_free(content);
5966 /* Now write the unencrypted body. */
5967 if ((tmpfp = claws_fopen(tmp_enc_file, "a")) != NULL) {
5968 procmime_write_mimeinfo(mimemsg, tmpfp);
5969 claws_fclose(tmpfp);
5971 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5972 if (!outbox)
5973 outbox = folder_get_default_outbox();
5975 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5976 claws_unlink(tmp_enc_file);
5977 } else {
5978 g_warning("can't open file '%s'", tmp_enc_file);
5980 } else {
5981 g_warning("couldn't get tempfile");
5984 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5985 debug_print("Couldn't encrypt mime structure: %s.\n",
5986 privacy_get_error());
5987 if (tmp_enc_file)
5988 g_free(tmp_enc_file);
5989 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5993 if (tmp_enc_file)
5994 g_free(tmp_enc_file);
5996 procmime_write_mimeinfo(mimemsg, fp);
5998 procmime_mimeinfo_free_all(&mimemsg);
6000 return 0;
6003 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
6005 GtkTextBuffer *buffer;
6006 GtkTextIter start, end;
6007 FILE *fp;
6008 size_t len;
6009 gchar *chars, *tmp;
6011 if ((fp = claws_fopen(file, "wb")) == NULL) {
6012 FILE_OP_ERROR(file, "claws_fopen");
6013 return -1;
6016 /* chmod for security */
6017 if (change_file_mode_rw(fp, file) < 0) {
6018 FILE_OP_ERROR(file, "chmod");
6019 g_warning("can't change file mode");
6022 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
6023 gtk_text_buffer_get_start_iter(buffer, &start);
6024 gtk_text_buffer_get_end_iter(buffer, &end);
6025 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
6027 chars = conv_codeset_strdup
6028 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6030 g_free(tmp);
6031 if (!chars) {
6032 claws_fclose(fp);
6033 claws_unlink(file);
6034 return -1;
6036 /* write body */
6037 len = strlen(chars);
6038 if (claws_fwrite(chars, sizeof(gchar), len, fp) != len) {
6039 FILE_OP_ERROR(file, "claws_fwrite");
6040 g_free(chars);
6041 claws_fclose(fp);
6042 claws_unlink(file);
6043 return -1;
6046 g_free(chars);
6048 if (claws_safe_fclose(fp) == EOF) {
6049 FILE_OP_ERROR(file, "claws_fclose");
6050 claws_unlink(file);
6051 return -1;
6053 return 0;
6056 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6058 FolderItem *item;
6059 MsgInfo *msginfo = compose->targetinfo;
6061 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6062 if (!msginfo) return -1;
6064 if (!force && MSG_IS_LOCKED(msginfo->flags))
6065 return 0;
6067 item = msginfo->folder;
6068 cm_return_val_if_fail(item != NULL, -1);
6070 if (procmsg_msg_exist(msginfo) &&
6071 (folder_has_parent_of_type(item, F_QUEUE) ||
6072 folder_has_parent_of_type(item, F_DRAFT)
6073 || msginfo == compose->autosaved_draft)) {
6074 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6075 g_warning("can't remove the old message");
6076 return -1;
6077 } else {
6078 debug_print("removed reedit target %d\n", msginfo->msgnum);
6082 return 0;
6085 static void compose_remove_draft(Compose *compose)
6087 FolderItem *drafts;
6088 MsgInfo *msginfo = compose->targetinfo;
6089 drafts = account_get_special_folder(compose->account, F_DRAFT);
6091 if (procmsg_msg_exist(msginfo)) {
6092 folder_item_remove_msg(drafts, msginfo->msgnum);
6097 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6098 gboolean remove_reedit_target)
6100 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6103 static gboolean compose_warn_encryption(Compose *compose)
6105 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6106 AlertValue val = G_ALERTALTERNATE;
6108 if (warning == NULL)
6109 return TRUE;
6111 val = alertpanel_full(_("Encryption warning"), warning,
6112 NULL, _("_Cancel"), NULL, _("C_ontinue"), NULL, NULL,
6113 ALERTFOCUS_SECOND, TRUE, NULL, ALERT_WARNING);
6114 if (val & G_ALERTDISABLE) {
6115 val &= ~G_ALERTDISABLE;
6116 if (val == G_ALERTALTERNATE)
6117 privacy_inhibit_encrypt_warning(compose->privacy_system,
6118 TRUE);
6121 if (val == G_ALERTALTERNATE) {
6122 return TRUE;
6123 } else {
6124 return FALSE;
6128 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6129 gchar **msgpath, gboolean perform_checks,
6130 gboolean remove_reedit_target)
6132 FolderItem *queue;
6133 gchar *tmp;
6134 FILE *fp;
6135 GSList *cur;
6136 gint num;
6137 PrefsAccount *mailac = NULL, *newsac = NULL;
6138 gboolean err = FALSE;
6140 debug_print("queueing message...\n");
6141 cm_return_val_if_fail(compose->account != NULL, -1);
6143 if (compose_check_entries(compose, perform_checks) == FALSE) {
6144 if (compose->batch) {
6145 gtk_widget_show_all(compose->window);
6147 return COMPOSE_QUEUE_ERROR_NO_MSG;
6150 if (!compose->to_list && !compose->newsgroup_list) {
6151 g_warning("can't get recipient list");
6152 return COMPOSE_QUEUE_ERROR_NO_MSG;
6155 if (compose->to_list) {
6156 mailac = compose->account;
6157 if (!mailac && cur_account && cur_account->protocol != A_NNTP)
6158 mailac = cur_account;
6159 else if (!mailac && !(mailac = compose_current_mail_account())) {
6160 alertpanel_error(_("No account for sending mails available!"));
6161 return COMPOSE_QUEUE_ERROR_NO_MSG;
6165 if (compose->newsgroup_list) {
6166 if (compose->account->protocol == A_NNTP)
6167 newsac = compose->account;
6168 else {
6169 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6170 return COMPOSE_QUEUE_ERROR_NO_MSG;
6174 /* write queue header */
6175 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6176 G_DIR_SEPARATOR, compose, (guint) rand());
6177 debug_print("queuing to %s\n", tmp);
6178 if ((fp = claws_fopen(tmp, "w+b")) == NULL) {
6179 FILE_OP_ERROR(tmp, "claws_fopen");
6180 g_free(tmp);
6181 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6184 if (change_file_mode_rw(fp, tmp) < 0) {
6185 FILE_OP_ERROR(tmp, "chmod");
6186 g_warning("can't change file mode");
6189 /* queueing variables */
6190 err |= (fprintf(fp, "AF:\n") < 0);
6191 err |= (fprintf(fp, "NF:0\n") < 0);
6192 err |= (fprintf(fp, "PS:10\n") < 0);
6193 err |= (fprintf(fp, "SRH:1\n") < 0);
6194 err |= (fprintf(fp, "SFN:\n") < 0);
6195 err |= (fprintf(fp, "DSR:\n") < 0);
6196 if (compose->msgid)
6197 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6198 else
6199 err |= (fprintf(fp, "MID:\n") < 0);
6200 err |= (fprintf(fp, "CFG:\n") < 0);
6201 err |= (fprintf(fp, "PT:0\n") < 0);
6202 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6203 err |= (fprintf(fp, "RQ:\n") < 0);
6204 if (mailac)
6205 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6206 else
6207 err |= (fprintf(fp, "SSV:\n") < 0);
6208 if (newsac)
6209 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6210 else
6211 err |= (fprintf(fp, "NSV:\n") < 0);
6212 err |= (fprintf(fp, "SSH:\n") < 0);
6213 /* write recipient list */
6214 if (compose->to_list) {
6215 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6216 for (cur = compose->to_list->next; cur != NULL;
6217 cur = cur->next)
6218 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6219 err |= (fprintf(fp, "\n") < 0);
6221 /* write newsgroup list */
6222 if (compose->newsgroup_list) {
6223 err |= (fprintf(fp, "NG:") < 0);
6224 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6225 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6226 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6227 err |= (fprintf(fp, "\n") < 0);
6229 /* account IDs */
6230 if (mailac)
6231 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6232 if (newsac)
6233 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6236 if (compose->privacy_system != NULL) {
6237 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6238 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6239 if (compose->use_encryption) {
6240 if (!compose_warn_encryption(compose)) {
6241 claws_fclose(fp);
6242 claws_unlink(tmp);
6243 g_free(tmp);
6244 return COMPOSE_QUEUE_ERROR_NO_MSG;
6246 if (mailac && mailac->encrypt_to_self) {
6247 GSList *tmp_list = g_slist_copy(compose->to_list);
6248 tmp_list = g_slist_append(tmp_list, compose->account->address);
6249 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6250 g_slist_free(tmp_list);
6251 } else {
6252 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6254 if (compose->encdata != NULL) {
6255 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6256 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6257 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6258 compose->encdata) < 0);
6259 } /* else we finally dont want to encrypt */
6260 } else {
6261 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6262 /* and if encdata was null, it means there's been a problem in
6263 * key selection */
6264 if (err == TRUE)
6265 g_warning("failed to write queue message");
6266 claws_fclose(fp);
6267 claws_unlink(tmp);
6268 g_free(tmp);
6269 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6274 /* Save copy folder */
6275 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6276 gchar *savefolderid;
6278 savefolderid = compose_get_save_to(compose);
6279 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6280 g_free(savefolderid);
6282 /* Save copy folder */
6283 if (compose->return_receipt) {
6284 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6286 /* Message-ID of message replying to */
6287 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6288 gchar *folderid = NULL;
6290 if (compose->replyinfo->folder)
6291 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6292 if (folderid == NULL)
6293 folderid = g_strdup("NULL");
6295 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6296 g_free(folderid);
6298 /* Message-ID of message forwarding to */
6299 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6300 gchar *folderid = NULL;
6302 if (compose->fwdinfo->folder)
6303 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6304 if (folderid == NULL)
6305 folderid = g_strdup("NULL");
6307 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6308 g_free(folderid);
6311 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6312 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6314 /* end of headers */
6315 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6317 if (compose->redirect_filename != NULL) {
6318 if (compose_redirect_write_to_file(compose, fp) < 0) {
6319 claws_fclose(fp);
6320 claws_unlink(tmp);
6321 g_free(tmp);
6322 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6324 } else {
6325 gint result = 0;
6326 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6327 claws_fclose(fp);
6328 claws_unlink(tmp);
6329 g_free(tmp);
6330 return result;
6333 if (err == TRUE) {
6334 g_warning("failed to write queue message");
6335 claws_fclose(fp);
6336 claws_unlink(tmp);
6337 g_free(tmp);
6338 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6340 if (claws_safe_fclose(fp) == EOF) {
6341 FILE_OP_ERROR(tmp, "claws_fclose");
6342 claws_unlink(tmp);
6343 g_free(tmp);
6344 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6347 if (item && *item) {
6348 queue = *item;
6349 } else {
6350 queue = account_get_special_folder(compose->account, F_QUEUE);
6352 if (!queue) {
6353 g_warning("can't find queue folder");
6354 claws_unlink(tmp);
6355 g_free(tmp);
6356 return COMPOSE_QUEUE_ERROR_NO_MSG;
6358 folder_item_scan(queue);
6359 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6360 g_warning("can't queue the message");
6361 claws_unlink(tmp);
6362 g_free(tmp);
6363 return COMPOSE_QUEUE_ERROR_NO_MSG;
6366 if (msgpath == NULL) {
6367 claws_unlink(tmp);
6368 g_free(tmp);
6369 } else
6370 *msgpath = tmp;
6372 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6373 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6374 if (mi) {
6375 procmsg_msginfo_change_flags(mi,
6376 compose->targetinfo->flags.perm_flags,
6377 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6378 0, 0);
6380 g_slist_free(mi->tags);
6381 mi->tags = g_slist_copy(compose->targetinfo->tags);
6382 procmsg_msginfo_free(&mi);
6386 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6387 compose_remove_reedit_target(compose, FALSE);
6390 if ((msgnum != NULL) && (item != NULL)) {
6391 *msgnum = num;
6392 *item = queue;
6395 return COMPOSE_QUEUE_SUCCESS;
6398 static int compose_add_attachments(Compose *compose, MimeInfo *parent, gint action)
6400 AttachInfo *ainfo;
6401 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6402 MimeInfo *mimepart;
6403 #ifdef G_OS_WIN32
6404 GFile *f;
6405 GFileInfo *fi;
6406 GError *error = NULL;
6407 #else
6408 GStatBuf statbuf;
6409 #endif
6410 goffset size;
6411 gchar *type, *subtype;
6412 GtkTreeModel *model;
6413 GtkTreeIter iter;
6415 model = gtk_tree_view_get_model(tree_view);
6417 if (!gtk_tree_model_get_iter_first(model, &iter))
6418 return 0;
6419 do {
6420 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6422 if (!is_file_exist(ainfo->file)) {
6423 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6424 AlertValue val = alertpanel_full(_("Warning"), msg, NULL,
6425 action == COMPOSE_WRITE_FOR_STORE? _("Cancel drafting"): _("Cancel sending"),
6426 NULL, _("Ignore attachment"), NULL, NULL,
6427 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6428 g_free(msg);
6429 if (val == G_ALERTDEFAULT) {
6430 return -1;
6432 continue;
6434 #ifdef G_OS_WIN32
6435 f = g_file_new_for_path(ainfo->file);
6436 fi = g_file_query_info(f, "standard::size",
6437 G_FILE_QUERY_INFO_NONE, NULL, &error);
6438 if (error != NULL) {
6439 g_warning(error->message);
6440 g_error_free(error);
6441 g_object_unref(f);
6442 return -1;
6444 size = g_file_info_get_size(fi);
6445 g_object_unref(fi);
6446 g_object_unref(f);
6447 #else
6448 if (g_stat(ainfo->file, &statbuf) < 0)
6449 return -1;
6450 size = statbuf.st_size;
6451 #endif
6453 mimepart = procmime_mimeinfo_new();
6454 mimepart->content = MIMECONTENT_FILE;
6455 mimepart->data.filename = g_strdup(ainfo->file);
6456 mimepart->tmp = FALSE; /* or we destroy our attachment */
6457 mimepart->offset = 0;
6458 mimepart->length = size;
6460 type = g_strdup(ainfo->content_type);
6462 if (!strchr(type, '/')) {
6463 g_free(type);
6464 type = g_strdup("application/octet-stream");
6467 subtype = strchr(type, '/') + 1;
6468 *(subtype - 1) = '\0';
6469 mimepart->type = procmime_get_media_type(type);
6470 mimepart->subtype = g_strdup(subtype);
6471 g_free(type);
6473 if (mimepart->type == MIMETYPE_MESSAGE &&
6474 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6475 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6476 } else if (mimepart->type == MIMETYPE_TEXT) {
6477 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6478 /* Text parts with no name come from multipart/alternative
6479 * forwards. Make sure the recipient won't look at the
6480 * original HTML part by mistake. */
6481 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6482 ainfo->name = g_strdup_printf(_("Original %s part"),
6483 mimepart->subtype);
6485 if (ainfo->charset)
6486 g_hash_table_insert(mimepart->typeparameters,
6487 g_strdup("charset"), g_strdup(ainfo->charset));
6489 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6490 if (mimepart->type == MIMETYPE_APPLICATION &&
6491 !g_strcmp0(mimepart->subtype, "octet-stream"))
6492 g_hash_table_insert(mimepart->typeparameters,
6493 g_strdup("name"), g_strdup(ainfo->name));
6494 g_hash_table_insert(mimepart->dispositionparameters,
6495 g_strdup("filename"), g_strdup(ainfo->name));
6496 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6499 if (mimepart->type == MIMETYPE_MESSAGE
6500 || mimepart->type == MIMETYPE_MULTIPART)
6501 ainfo->encoding = ENC_BINARY;
6502 else if (compose->use_signing || compose->fwdinfo != NULL) {
6503 if (ainfo->encoding == ENC_7BIT)
6504 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6505 else if (ainfo->encoding == ENC_8BIT)
6506 ainfo->encoding = ENC_BASE64;
6509 procmime_encode_content(mimepart, ainfo->encoding);
6511 g_node_append(parent->node, mimepart->node);
6512 } while (gtk_tree_model_iter_next(model, &iter));
6514 return 0;
6517 static gchar *compose_quote_list_of_addresses(gchar *str)
6519 GSList *list = NULL, *item = NULL;
6520 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6522 list = address_list_append_with_comments(list, str);
6523 for (item = list; item != NULL; item = item->next) {
6524 gchar *spec = item->data;
6525 gchar *endofname = strstr(spec, " <");
6526 if (endofname != NULL) {
6527 gchar * qqname;
6528 *endofname = '\0';
6529 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6530 qqname = escape_internal_quotes(qname, '"');
6531 *endofname = ' ';
6532 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6533 gchar *addr = g_strdup(endofname);
6534 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6535 faddr = g_strconcat(name, addr, NULL);
6536 g_free(name);
6537 g_free(addr);
6538 debug_print("new auto-quoted address: '%s'\n", faddr);
6541 if (result == NULL)
6542 result = g_strdup((faddr != NULL)? faddr: spec);
6543 else {
6544 gchar *tmp = g_strconcat(result,
6545 ", ",
6546 (faddr != NULL)? faddr: spec,
6547 NULL);
6548 g_free(result);
6549 result = tmp;
6551 if (faddr != NULL) {
6552 g_free(faddr);
6553 faddr = NULL;
6556 slist_free_strings_full(list);
6558 return result;
6561 #define IS_IN_CUSTOM_HEADER(header) \
6562 (compose->account->add_customhdr && \
6563 custom_header_find(compose->account->customhdr_list, header) != NULL)
6565 static const gchar *compose_untranslated_header_name(gchar *header_name)
6567 /* return the untranslated header name, if header_name is a known
6568 header name, in either its translated or untranslated form, with
6569 or without trailing colon. otherwise, returns header_name. */
6570 gchar *translated_header_name;
6571 gchar *translated_header_name_wcolon;
6572 const gchar *untranslated_header_name;
6573 const gchar *untranslated_header_name_wcolon;
6574 gint i;
6576 cm_return_val_if_fail(header_name != NULL, NULL);
6578 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6579 untranslated_header_name = HEADERS[i].header_name;
6580 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6582 translated_header_name = gettext(untranslated_header_name);
6583 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6585 if (!strcmp(header_name, untranslated_header_name) ||
6586 !strcmp(header_name, translated_header_name)) {
6587 return untranslated_header_name;
6588 } else {
6589 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6590 !strcmp(header_name, translated_header_name_wcolon)) {
6591 return untranslated_header_name_wcolon;
6595 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6596 return header_name;
6599 static void compose_add_headerfield_from_headerlist(Compose *compose,
6600 GString *header,
6601 const gchar *fieldname,
6602 const gchar *seperator)
6604 gchar *str, *fieldname_w_colon;
6605 gboolean add_field = FALSE;
6606 GSList *list;
6607 ComposeHeaderEntry *headerentry;
6608 const gchar *headerentryname;
6609 const gchar *trans_fieldname;
6610 GString *fieldstr;
6612 if (IS_IN_CUSTOM_HEADER(fieldname))
6613 return;
6615 debug_print("Adding %s-fields\n", fieldname);
6617 fieldstr = g_string_sized_new(64);
6619 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6620 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6622 for (list = compose->header_list; list; list = list->next) {
6623 headerentry = ((ComposeHeaderEntry *)list->data);
6624 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6626 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6627 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6628 g_strstrip(ustr);
6629 str = compose_quote_list_of_addresses(ustr);
6630 g_free(ustr);
6631 if (str != NULL && str[0] != '\0') {
6632 if (add_field)
6633 g_string_append(fieldstr, seperator);
6634 g_string_append(fieldstr, str);
6635 add_field = TRUE;
6637 g_free(str);
6640 if (add_field) {
6641 gchar *buf;
6643 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6644 compose_convert_header
6645 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6646 strlen(fieldname) + 2, TRUE);
6647 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6648 g_free(buf);
6651 g_free(fieldname_w_colon);
6652 g_string_free(fieldstr, TRUE);
6654 return;
6657 static gchar *compose_get_manual_headers_info(Compose *compose)
6659 GString *sh_header = g_string_new(" ");
6660 GSList *list;
6661 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6663 for (list = compose->header_list; list; list = list->next) {
6664 ComposeHeaderEntry *headerentry;
6665 gchar *tmp;
6666 gchar *headername;
6667 gchar *headername_wcolon;
6668 const gchar *headername_trans;
6669 gchar **string;
6670 gboolean standard_header = FALSE;
6672 headerentry = ((ComposeHeaderEntry *)list->data);
6674 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6675 g_strstrip(tmp);
6676 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6677 g_free(tmp);
6678 continue;
6681 if (!strstr(tmp, ":")) {
6682 headername_wcolon = g_strconcat(tmp, ":", NULL);
6683 headername = g_strdup(tmp);
6684 } else {
6685 headername_wcolon = g_strdup(tmp);
6686 headername = g_strdup(strtok(tmp, ":"));
6688 g_free(tmp);
6690 string = std_headers;
6691 while (*string != NULL) {
6692 headername_trans = prefs_common_translated_header_name(*string);
6693 if (!strcmp(headername_trans, headername_wcolon))
6694 standard_header = TRUE;
6695 string++;
6697 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6698 g_string_append_printf(sh_header, "%s ", headername);
6699 g_free(headername);
6700 g_free(headername_wcolon);
6702 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6703 return g_string_free(sh_header, FALSE);
6706 static gchar *compose_get_header(Compose *compose)
6708 gchar date[RFC822_DATE_BUFFSIZE];
6709 gchar buf[BUFFSIZE];
6710 const gchar *entry_str;
6711 gchar *str;
6712 gchar *name;
6713 GSList *list;
6714 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6715 GString *header;
6716 gchar *from_name = NULL, *from_address = NULL;
6717 gchar *tmp;
6719 cm_return_val_if_fail(compose->account != NULL, NULL);
6720 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6722 header = g_string_sized_new(64);
6724 /* Date */
6725 if (prefs_common.hide_timezone)
6726 get_rfc822_date_hide_tz(date, sizeof(date));
6727 else
6728 get_rfc822_date(date, sizeof(date));
6729 g_string_append_printf(header, "Date: %s\n", date);
6731 /* From */
6733 if (compose->account->name && *compose->account->name) {
6734 gchar *buf;
6735 QUOTE_IF_REQUIRED(buf, compose->account->name);
6736 tmp = g_strdup_printf("%s <%s>",
6737 buf, compose->account->address);
6738 } else {
6739 tmp = g_strdup_printf("%s",
6740 compose->account->address);
6742 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6743 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6744 /* use default */
6745 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6746 from_address = g_strdup(compose->account->address);
6747 } else {
6748 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6749 /* extract name and address */
6750 if (strstr(spec, " <") && strstr(spec, ">")) {
6751 from_address = g_strdup(strrchr(spec, '<')+1);
6752 *(strrchr(from_address, '>')) = '\0';
6753 from_name = g_strdup(spec);
6754 *(strrchr(from_name, '<')) = '\0';
6755 } else {
6756 from_name = NULL;
6757 from_address = g_strdup(spec);
6759 g_free(spec);
6761 g_free(tmp);
6764 if (from_name && *from_name) {
6765 gchar *qname;
6766 compose_convert_header
6767 (compose, buf, sizeof(buf), from_name,
6768 strlen("From: "), TRUE);
6769 QUOTE_IF_REQUIRED(name, buf);
6770 qname = escape_internal_quotes(name, '"');
6772 g_string_append_printf(header, "From: %s <%s>\n",
6773 qname, from_address);
6774 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6775 compose->return_receipt) {
6776 compose_convert_header(compose, buf, sizeof(buf), from_name,
6777 strlen("Disposition-Notification-To: "),
6778 TRUE);
6779 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6781 if (qname != name)
6782 g_free(qname);
6783 } else {
6784 g_string_append_printf(header, "From: %s\n", from_address);
6785 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6786 compose->return_receipt)
6787 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6790 g_free(from_name);
6791 g_free(from_address);
6793 /* To */
6794 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6796 /* Newsgroups */
6797 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6799 /* Cc */
6800 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6802 /* Bcc */
6804 * If this account is a NNTP account remove Bcc header from
6805 * message body since it otherwise will be publicly shown
6807 if (compose->account->protocol != A_NNTP)
6808 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6810 /* Subject */
6811 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6813 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6814 g_strstrip(str);
6815 if (*str != '\0') {
6816 compose_convert_header(compose, buf, sizeof(buf), str,
6817 strlen("Subject: "), FALSE);
6818 g_string_append_printf(header, "Subject: %s\n", buf);
6821 g_free(str);
6823 /* Message-ID */
6824 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6825 g_string_append_printf(header, "Message-ID: <%s>\n",
6826 compose->msgid);
6829 if (compose->remove_references == FALSE) {
6830 /* In-Reply-To */
6831 if (compose->inreplyto && compose->to_list)
6832 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6834 /* References */
6835 if (compose->references)
6836 g_string_append_printf(header, "References: %s\n", compose->references);
6839 /* Followup-To */
6840 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6842 /* Reply-To */
6843 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6845 /* Organization */
6846 if (compose->account->organization &&
6847 strlen(compose->account->organization) &&
6848 !IS_IN_CUSTOM_HEADER("Organization")) {
6849 compose_convert_header(compose, buf, sizeof(buf),
6850 compose->account->organization,
6851 strlen("Organization: "), FALSE);
6852 g_string_append_printf(header, "Organization: %s\n", buf);
6855 /* Program version and system info */
6856 if (compose->account->gen_xmailer &&
6857 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6858 !compose->newsgroup_list) {
6859 g_string_append_printf(header, "X-Mailer: %s (GTK %d.%d.%d; %s)\n",
6860 prog_version,
6861 gtk_major_version, gtk_minor_version, gtk_micro_version,
6862 TARGET_ALIAS);
6864 if (compose->account->gen_xmailer &&
6865 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6866 g_string_append_printf(header, "X-Newsreader: %s (GTK %d.%d.%d; %s)\n",
6867 prog_version,
6868 gtk_major_version, gtk_minor_version, gtk_micro_version,
6869 TARGET_ALIAS);
6872 /* custom headers */
6873 if (compose->account->add_customhdr) {
6874 GSList *cur;
6876 for (cur = compose->account->customhdr_list; cur != NULL;
6877 cur = cur->next) {
6878 CustomHeader *chdr = (CustomHeader *)cur->data;
6880 if (custom_header_is_allowed(chdr->name)
6881 && chdr->value != NULL
6882 && *(chdr->value) != '\0') {
6883 compose_convert_header
6884 (compose, buf, sizeof(buf),
6885 chdr->value,
6886 strlen(chdr->name) + 2, FALSE);
6887 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6892 /* Automatic Faces and X-Faces */
6893 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6894 g_string_append_printf(header, "X-Face: %s\n", buf);
6896 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6897 g_string_append_printf(header, "X-Face: %s\n", buf);
6899 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6900 g_string_append_printf(header, "Face: %s\n", buf);
6902 else if (get_default_face (buf, sizeof(buf)) == 0) {
6903 g_string_append_printf(header, "Face: %s\n", buf);
6906 /* PRIORITY */
6907 switch (compose->priority) {
6908 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6909 "X-Priority: 1 (Highest)\n");
6910 break;
6911 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6912 "X-Priority: 2 (High)\n");
6913 break;
6914 case PRIORITY_NORMAL: break;
6915 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6916 "X-Priority: 4 (Low)\n");
6917 break;
6918 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6919 "X-Priority: 5 (Lowest)\n");
6920 break;
6921 default: debug_print("compose: priority unknown : %d\n",
6922 compose->priority);
6925 /* get special headers */
6926 for (list = compose->header_list; list; list = list->next) {
6927 ComposeHeaderEntry *headerentry;
6928 gchar *tmp;
6929 gchar *headername;
6930 gchar *headername_wcolon;
6931 const gchar *headername_trans;
6932 gchar *headervalue;
6933 gchar **string;
6934 gboolean standard_header = FALSE;
6936 headerentry = ((ComposeHeaderEntry *)list->data);
6938 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6939 g_strstrip(tmp);
6940 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6941 g_free(tmp);
6942 continue;
6945 if (!strstr(tmp, ":")) {
6946 headername_wcolon = g_strconcat(tmp, ":", NULL);
6947 headername = g_strdup(tmp);
6948 } else {
6949 headername_wcolon = g_strdup(tmp);
6950 headername = g_strdup(strtok(tmp, ":"));
6952 g_free(tmp);
6954 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6955 Xstrdup_a(headervalue, entry_str, {
6956 g_free(headername);
6957 g_free(headername_wcolon);
6958 g_string_free(header, TRUE);
6959 return NULL;
6961 subst_char(headervalue, '\r', ' ');
6962 subst_char(headervalue, '\n', ' ');
6963 g_strstrip(headervalue);
6964 if (*headervalue != '\0') {
6965 string = std_headers;
6966 while (*string != NULL && !standard_header) {
6967 headername_trans = prefs_common_translated_header_name(*string);
6968 /* support mixed translated and untranslated headers */
6969 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6970 standard_header = TRUE;
6971 string++;
6973 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6974 /* store untranslated header name */
6975 g_string_append_printf(header, "%s %s\n",
6976 compose_untranslated_header_name(headername_wcolon), headervalue);
6979 g_free(headername);
6980 g_free(headername_wcolon);
6983 return g_string_free(header, FALSE);
6986 #undef IS_IN_CUSTOM_HEADER
6988 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6989 gint header_len, gboolean addr_field)
6991 gchar *tmpstr = NULL;
6992 const gchar *out_codeset = NULL;
6994 cm_return_if_fail(src != NULL);
6995 cm_return_if_fail(dest != NULL);
6997 if (len < 1) return;
6999 tmpstr = g_strdup(src);
7001 subst_char(tmpstr, '\n', ' ');
7002 subst_char(tmpstr, '\r', ' ');
7003 g_strchomp(tmpstr);
7005 if (!g_utf8_validate(tmpstr, -1, NULL)) {
7006 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
7007 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
7008 g_free(tmpstr);
7009 tmpstr = mybuf;
7012 codeconv_set_strict(TRUE);
7013 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7014 conv_get_charset_str(compose->out_encoding));
7015 codeconv_set_strict(FALSE);
7017 if (!dest || *dest == '\0') {
7018 gchar *test_conv_global_out = NULL;
7019 gchar *test_conv_reply = NULL;
7021 /* automatic mode. be automatic. */
7022 codeconv_set_strict(TRUE);
7024 out_codeset = conv_get_outgoing_charset_str();
7025 if (out_codeset) {
7026 debug_print("trying to convert to %s\n", out_codeset);
7027 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7030 if (!test_conv_global_out && compose->orig_charset
7031 && strcmp(compose->orig_charset, CS_US_ASCII)) {
7032 out_codeset = compose->orig_charset;
7033 debug_print("failure; trying to convert to %s\n", out_codeset);
7034 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7037 if (!test_conv_global_out && !test_conv_reply) {
7038 /* we're lost */
7039 out_codeset = CS_INTERNAL;
7040 debug_print("finally using %s\n", out_codeset);
7042 g_free(test_conv_global_out);
7043 g_free(test_conv_reply);
7044 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7045 out_codeset);
7046 codeconv_set_strict(FALSE);
7048 g_free(tmpstr);
7051 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7053 gchar *address;
7055 cm_return_if_fail(user_data != NULL);
7057 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7058 g_strstrip(address);
7059 if (*address != '\0') {
7060 gchar *name = procheader_get_fromname(address);
7061 extract_address(address);
7062 #ifndef USE_ALT_ADDRBOOK
7063 addressbook_add_contact(name, address, NULL, NULL);
7064 #else
7065 debug_print("%s: %s\n", name, address);
7066 if (addressadd_selection(name, address, NULL, NULL)) {
7067 debug_print( "addressbook_add_contact - added\n" );
7069 #endif
7071 g_free(address);
7074 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7076 GtkWidget *menuitem;
7077 gchar *address;
7079 cm_return_if_fail(menu != NULL);
7080 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7082 menuitem = gtk_separator_menu_item_new();
7083 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7084 gtk_widget_show(menuitem);
7086 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7087 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7089 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7090 g_strstrip(address);
7091 if (*address == '\0') {
7092 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7095 g_signal_connect(G_OBJECT(menuitem), "activate",
7096 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7097 gtk_widget_show(menuitem);
7100 void compose_add_extra_header(gchar *header, GtkListStore *model)
7102 GtkTreeIter iter;
7103 if (strcmp(header, "")) {
7104 COMBOBOX_ADD(model, header, COMPOSE_TO);
7108 void compose_add_extra_header_entries(GtkListStore *model)
7110 FILE *exh;
7111 gchar *exhrc;
7112 gchar buf[BUFFSIZE];
7113 gint lastc;
7115 if (extra_headers == NULL) {
7116 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7117 if ((exh = claws_fopen(exhrc, "rb")) == NULL) {
7118 debug_print("extra headers file not found\n");
7119 goto extra_headers_done;
7121 while (claws_fgets(buf, BUFFSIZE, exh) != NULL) {
7122 lastc = strlen(buf) - 1; /* remove trailing control chars */
7123 while (lastc >= 0 && buf[lastc] != ':')
7124 buf[lastc--] = '\0';
7125 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7126 buf[lastc] = '\0'; /* remove trailing : for comparison */
7127 if (custom_header_is_allowed(buf)) {
7128 buf[lastc] = ':';
7129 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7131 else
7132 g_message("disallowed extra header line: %s\n", buf);
7134 else {
7135 if (buf[0] != '#')
7136 g_message("invalid extra header line: %s\n", buf);
7139 claws_fclose(exh);
7140 extra_headers_done:
7141 g_free(exhrc);
7142 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7143 extra_headers = g_slist_reverse(extra_headers);
7145 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7148 #ifdef USE_LDAP
7149 static void _ldap_srv_func(gpointer data, gpointer user_data)
7151 LdapServer *server = (LdapServer *)data;
7152 gboolean *enable = (gboolean *)user_data;
7154 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7155 server->searchFlag = *enable;
7157 #endif
7159 static void compose_create_header_entry(Compose *compose)
7161 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7163 GtkWidget *combo;
7164 GtkWidget *entry;
7165 GtkWidget *button;
7166 GtkWidget *hbox;
7167 gchar **string;
7168 const gchar *header = NULL;
7169 ComposeHeaderEntry *headerentry;
7170 gboolean standard_header = FALSE;
7171 GtkListStore *model;
7172 GtkTreeIter iter;
7174 headerentry = g_new0(ComposeHeaderEntry, 1);
7176 /* Combo box model */
7177 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7178 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7179 COMPOSE_TO);
7180 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7181 COMPOSE_CC);
7182 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7183 COMPOSE_BCC);
7184 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7185 COMPOSE_NEWSGROUPS);
7186 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7187 COMPOSE_REPLYTO);
7188 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7189 COMPOSE_FOLLOWUPTO);
7190 compose_add_extra_header_entries(model);
7192 /* Combo box */
7193 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7194 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7195 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7196 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7197 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7198 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7199 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7200 G_CALLBACK(compose_grab_focus_cb), compose);
7201 gtk_widget_show(combo);
7203 gtk_grid_attach(GTK_GRID(compose->header_table), combo, 0, compose->header_nextrow,
7204 1, 1);
7205 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7206 const gchar *last_header_entry = gtk_entry_get_text(
7207 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7208 string = headers;
7209 while (*string != NULL) {
7210 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7211 standard_header = TRUE;
7212 string++;
7214 if (standard_header)
7215 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7217 if (!compose->header_last || !standard_header) {
7218 switch(compose->account->protocol) {
7219 case A_NNTP:
7220 header = prefs_common_translated_header_name("Newsgroups:");
7221 break;
7222 default:
7223 header = prefs_common_translated_header_name("To:");
7224 break;
7227 if (header)
7228 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7230 gtk_editable_set_editable(
7231 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7232 prefs_common.type_any_header);
7234 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7235 G_CALLBACK(compose_grab_focus_cb), compose);
7237 /* Entry field with cleanup button */
7238 button = gtk_button_new_from_icon_name("edit-clear", GTK_ICON_SIZE_MENU);
7239 gtk_widget_show(button);
7240 CLAWS_SET_TIP(button,
7241 _("Delete entry contents"));
7242 entry = gtk_entry_new();
7243 gtk_widget_show(entry);
7244 CLAWS_SET_TIP(entry,
7245 _("Use <tab> to autocomplete from addressbook"));
7246 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
7247 gtk_widget_show(hbox);
7248 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7249 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7250 gtk_grid_attach(GTK_GRID(compose->header_table), hbox, 1, compose->header_nextrow,
7251 1, 1);
7252 gtk_widget_set_hexpand(hbox, TRUE);
7253 gtk_widget_set_halign(hbox, GTK_ALIGN_FILL);
7255 g_signal_connect(G_OBJECT(entry), "key-press-event",
7256 G_CALLBACK(compose_headerentry_key_press_event_cb),
7257 headerentry);
7258 g_signal_connect(G_OBJECT(entry), "changed",
7259 G_CALLBACK(compose_headerentry_changed_cb),
7260 headerentry);
7261 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7262 G_CALLBACK(compose_grab_focus_cb), compose);
7264 g_signal_connect(G_OBJECT(button), "clicked",
7265 G_CALLBACK(compose_headerentry_button_clicked_cb),
7266 headerentry);
7268 /* email dnd */
7269 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7270 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7271 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7272 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7273 G_CALLBACK(compose_header_drag_received_cb),
7274 entry);
7275 g_signal_connect(G_OBJECT(entry), "drag-drop",
7276 G_CALLBACK(compose_drag_drop),
7277 compose);
7278 g_signal_connect(G_OBJECT(entry), "populate-popup",
7279 G_CALLBACK(compose_entry_popup_extend),
7280 NULL);
7282 #ifdef USE_LDAP
7283 #ifndef PASSWORD_CRYPTO_OLD
7284 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7285 if (pwd_servers != NULL && primary_passphrase() == NULL) {
7286 gboolean enable = FALSE;
7287 debug_print("Primary passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7288 /* Temporarily disable password-protected LDAP servers,
7289 * because user did not provide a primary passphrase.
7290 * We can safely enable searchFlag on all servers in this list
7291 * later, since addrindex_get_password_protected_ldap_servers()
7292 * includes servers which have it enabled initially. */
7293 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7294 compose->passworded_ldap_servers = pwd_servers;
7296 #endif /* PASSWORD_CRYPTO_OLD */
7297 #endif /* USE_LDAP */
7299 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7301 headerentry->compose = compose;
7302 headerentry->combo = combo;
7303 headerentry->entry = entry;
7304 headerentry->button = button;
7305 headerentry->hbox = hbox;
7306 headerentry->headernum = compose->header_nextrow;
7307 headerentry->type = PREF_NONE;
7309 compose->header_nextrow++;
7310 compose->header_last = headerentry;
7311 compose->header_list =
7312 g_slist_append(compose->header_list,
7313 headerentry);
7316 static void compose_add_header_entry(Compose *compose, const gchar *header,
7317 gchar *text, ComposePrefType pref_type)
7319 ComposeHeaderEntry *last_header = compose->header_last;
7320 gchar *tmp = g_strdup(text), *email;
7321 gboolean replyto_hdr;
7323 replyto_hdr = (!strcasecmp(header,
7324 prefs_common_translated_header_name("Reply-To:")) ||
7325 !strcasecmp(header,
7326 prefs_common_translated_header_name("Followup-To:")) ||
7327 !strcasecmp(header,
7328 prefs_common_translated_header_name("In-Reply-To:")));
7330 extract_address(tmp);
7331 email = g_utf8_strdown(tmp, -1);
7333 if (replyto_hdr == FALSE &&
7334 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7336 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7337 header, text, (gint) pref_type);
7338 g_free(email);
7339 g_free(tmp);
7340 return;
7343 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7344 gtk_entry_set_text(GTK_ENTRY(
7345 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7346 else
7347 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7348 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7349 last_header->type = pref_type;
7351 if (replyto_hdr == FALSE)
7352 g_hash_table_insert(compose->email_hashtable, email,
7353 GUINT_TO_POINTER(1));
7354 else
7355 g_free(email);
7357 g_free(tmp);
7360 static void compose_destroy_headerentry(Compose *compose,
7361 ComposeHeaderEntry *headerentry)
7363 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7364 gchar *email;
7366 extract_address(text);
7367 email = g_utf8_strdown(text, -1);
7368 g_hash_table_remove(compose->email_hashtable, email);
7369 g_free(text);
7370 g_free(email);
7372 gtk_widget_destroy(headerentry->combo);
7373 gtk_widget_destroy(headerentry->entry);
7374 gtk_widget_destroy(headerentry->button);
7375 gtk_widget_destroy(headerentry->hbox);
7376 g_free(headerentry);
7379 static void compose_remove_header_entries(Compose *compose)
7381 GSList *list;
7382 for (list = compose->header_list; list; list = list->next)
7383 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7385 compose->header_last = NULL;
7386 g_slist_free(compose->header_list);
7387 compose->header_list = NULL;
7388 compose->header_nextrow = 1;
7389 compose_create_header_entry(compose);
7392 static GtkWidget *compose_create_header(Compose *compose)
7394 GtkWidget *from_optmenu_hbox;
7395 GtkWidget *header_table_main;
7396 GtkWidget *header_scrolledwin;
7397 GtkWidget *header_table;
7399 /* parent with account selection and from header */
7400 header_table_main = gtk_grid_new();
7401 gtk_widget_show(header_table_main);
7402 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7404 from_optmenu_hbox = compose_account_option_menu_create(compose);
7405 gtk_grid_attach(GTK_GRID(header_table_main),from_optmenu_hbox, 0, 0, 1, 1);
7406 gtk_widget_set_hexpand(from_optmenu_hbox, TRUE);
7407 gtk_widget_set_halign(from_optmenu_hbox, GTK_ALIGN_FILL);
7409 /* child with header labels and entries */
7410 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7411 gtk_widget_show(header_scrolledwin);
7412 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7414 header_table = gtk_grid_new();
7415 gtk_widget_show(header_table);
7416 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7417 gtk_container_add(GTK_CONTAINER(header_scrolledwin), header_table);
7418 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7419 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7420 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7422 gtk_grid_attach(GTK_GRID(header_table_main), header_scrolledwin, 0, 1, 1, 1);
7423 gtk_widget_set_vexpand(header_scrolledwin, TRUE);
7424 gtk_widget_set_valign(header_scrolledwin, GTK_ALIGN_FILL);
7426 compose->header_table = header_table;
7427 compose->header_list = NULL;
7428 compose->header_nextrow = 0;
7430 compose_create_header_entry(compose);
7432 compose->table = NULL;
7434 return header_table_main;
7437 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7439 Compose *compose = (Compose *)data;
7440 GdkEventButton event;
7442 event.button = 3;
7443 event.time = gtk_get_current_event_time();
7445 return attach_button_pressed(compose->attach_clist, &event, compose);
7448 static GtkWidget *compose_create_attach(Compose *compose)
7450 GtkWidget *attach_scrwin;
7451 GtkWidget *attach_clist;
7453 GtkListStore *store;
7454 GtkCellRenderer *renderer;
7455 GtkTreeViewColumn *column;
7456 GtkTreeSelection *selection;
7458 /* attachment list */
7459 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7460 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7461 GTK_POLICY_AUTOMATIC,
7462 GTK_POLICY_AUTOMATIC);
7463 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7465 store = gtk_list_store_new(N_ATTACH_COLS,
7466 G_TYPE_STRING,
7467 G_TYPE_STRING,
7468 G_TYPE_STRING,
7469 G_TYPE_STRING,
7470 G_TYPE_POINTER,
7471 G_TYPE_AUTO_POINTER,
7472 -1);
7473 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7474 (GTK_TREE_MODEL(store)));
7475 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7476 g_object_unref(store);
7478 renderer = gtk_cell_renderer_text_new();
7479 column = gtk_tree_view_column_new_with_attributes
7480 (_("Mime type"), renderer, "text",
7481 COL_MIMETYPE, NULL);
7482 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7484 renderer = gtk_cell_renderer_text_new();
7485 column = gtk_tree_view_column_new_with_attributes
7486 (_("Size"), renderer, "text",
7487 COL_SIZE, NULL);
7488 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7490 renderer = gtk_cell_renderer_text_new();
7491 column = gtk_tree_view_column_new_with_attributes
7492 (_("Name"), renderer, "text",
7493 COL_NAME, NULL);
7494 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7496 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7497 prefs_common.use_stripes_everywhere);
7498 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7499 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7501 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7502 G_CALLBACK(attach_selected), compose);
7503 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7504 G_CALLBACK(attach_button_pressed), compose);
7505 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7506 G_CALLBACK(popup_attach_button_pressed), compose);
7507 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7508 G_CALLBACK(attach_key_pressed), compose);
7510 /* drag and drop */
7511 gtk_drag_dest_set(attach_clist,
7512 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7513 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7514 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7515 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7516 G_CALLBACK(compose_attach_drag_received_cb),
7517 compose);
7518 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7519 G_CALLBACK(compose_drag_drop),
7520 compose);
7522 compose->attach_scrwin = attach_scrwin;
7523 compose->attach_clist = attach_clist;
7525 return attach_scrwin;
7528 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7530 static GtkWidget *compose_create_others(Compose *compose)
7532 GtkWidget *table;
7533 GtkWidget *savemsg_checkbtn;
7534 GtkWidget *savemsg_combo;
7535 GtkWidget *savemsg_select;
7537 guint rowcount = 0;
7538 gchar *folderidentifier;
7540 /* Table for settings */
7541 table = gtk_grid_new();
7542 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7543 gtk_widget_show(table);
7544 gtk_grid_set_row_spacing(GTK_GRID(table), VSPACING_NARROW);
7545 rowcount = 0;
7547 /* Save Message to folder */
7548 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7549 gtk_widget_show(savemsg_checkbtn);
7550 gtk_grid_attach(GTK_GRID(table), savemsg_checkbtn, 0, rowcount, 1, 1);
7551 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7552 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7555 savemsg_combo = gtk_combo_box_text_new_with_entry();
7556 compose->savemsg_checkbtn = savemsg_checkbtn;
7557 compose->savemsg_combo = savemsg_combo;
7558 gtk_widget_show(savemsg_combo);
7560 if (prefs_common.compose_save_to_history)
7561 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7562 prefs_common.compose_save_to_history);
7563 gtk_grid_attach(GTK_GRID(table), savemsg_combo, 1, rowcount, 1, 1);
7564 gtk_widget_set_hexpand(savemsg_combo, TRUE);
7565 gtk_widget_set_halign(savemsg_combo, GTK_ALIGN_FILL);
7566 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7567 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7568 G_CALLBACK(compose_grab_focus_cb), compose);
7569 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7570 if (compose->account->set_sent_folder || prefs_common.savemsg)
7571 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), TRUE);
7572 else
7573 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), FALSE);
7574 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), TRUE);
7575 folderidentifier = folder_item_get_identifier(account_get_special_folder
7576 (compose->account, F_OUTBOX));
7577 compose_set_save_to(compose, folderidentifier);
7578 g_free(folderidentifier);
7581 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7582 gtk_widget_show(savemsg_select);
7583 gtk_grid_attach(GTK_GRID(table), savemsg_select, 2, rowcount, 1, 1);
7584 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7585 G_CALLBACK(compose_savemsg_select_cb),
7586 compose);
7588 return table;
7591 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7593 FolderItem *dest;
7594 gchar * path;
7596 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7597 _("Select folder to save message to"));
7598 if (!dest) return;
7600 path = folder_item_get_identifier(dest);
7602 compose_set_save_to(compose, path);
7603 g_free(path);
7606 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7607 GdkAtom clip, GtkTextIter *insert_place);
7610 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7611 Compose *compose)
7613 gint prev_autowrap;
7614 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7615 #if USE_ENCHANT
7616 if (event->button == 3) {
7617 GtkTextIter iter;
7618 GtkTextIter sel_start, sel_end;
7619 gboolean stuff_selected;
7620 gint x, y;
7621 /* move the cursor to allow GtkAspell to check the word
7622 * under the mouse */
7623 if (event->x && event->y) {
7624 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7625 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7626 &x, &y);
7627 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7628 &iter, x, y);
7629 } else {
7630 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7631 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7633 /* get selection */
7634 stuff_selected = gtk_text_buffer_get_selection_bounds(
7635 buffer,
7636 &sel_start, &sel_end);
7638 gtk_text_buffer_place_cursor (buffer, &iter);
7639 /* reselect stuff */
7640 if (stuff_selected
7641 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7642 gtk_text_buffer_select_range(buffer,
7643 &sel_start, &sel_end);
7645 return FALSE; /* pass the event so that the right-click goes through */
7647 #endif
7648 if (event->button == 2) {
7649 GtkTextIter iter;
7650 gint x, y;
7651 BLOCK_WRAP();
7653 /* get the middle-click position to paste at the correct place */
7654 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7655 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7656 &x, &y);
7657 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7658 &iter, x, y);
7660 entry_paste_clipboard(compose, text,
7661 prefs_common.linewrap_pastes,
7662 GDK_SELECTION_PRIMARY, &iter);
7663 UNBLOCK_WRAP();
7664 return TRUE;
7666 return FALSE;
7669 #if USE_ENCHANT
7670 static void compose_spell_menu_changed(void *data)
7672 Compose *compose = (Compose *)data;
7673 GSList *items;
7674 GtkWidget *menuitem;
7675 GtkWidget *parent_item;
7676 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7677 GSList *spell_menu;
7679 if (compose->gtkaspell == NULL)
7680 return;
7682 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7683 "/Menu/Spelling/Options");
7685 /* setting the submenu removes /Spelling/Options from the factory
7686 * so we need to save it */
7688 if (parent_item == NULL) {
7689 parent_item = compose->aspell_options_menu;
7690 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7691 } else
7692 compose->aspell_options_menu = parent_item;
7694 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7696 spell_menu = g_slist_reverse(spell_menu);
7697 for (items = spell_menu;
7698 items; items = items->next) {
7699 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7700 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7701 gtk_widget_show(GTK_WIDGET(menuitem));
7703 g_slist_free(spell_menu);
7705 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7706 gtk_widget_show(parent_item);
7709 static void compose_dict_changed(void *data)
7711 Compose *compose = (Compose *) data;
7713 if(!compose->gtkaspell)
7714 return;
7715 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7716 return;
7718 gtkaspell_highlight_all(compose->gtkaspell);
7719 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7721 #endif
7723 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7725 Compose *compose = (Compose *)data;
7726 GdkEventButton event;
7728 event.button = 3;
7729 event.time = gtk_get_current_event_time();
7730 event.x = 0;
7731 event.y = 0;
7733 return text_clicked(compose->text, &event, compose);
7736 static gboolean compose_force_window_origin = TRUE;
7737 static Compose *compose_create(PrefsAccount *account,
7738 FolderItem *folder,
7739 ComposeMode mode,
7740 gboolean batch)
7742 Compose *compose;
7743 GtkWidget *window;
7744 GtkWidget *vbox;
7745 GtkWidget *menubar;
7746 GtkWidget *handlebox;
7748 GtkWidget *notebook;
7750 GtkWidget *attach_hbox;
7751 GtkWidget *attach_lab1;
7752 GtkWidget *attach_lab2;
7754 GtkWidget *vbox2;
7756 GtkWidget *label;
7757 GtkWidget *subject_hbox;
7758 GtkWidget *subject_frame;
7759 GtkWidget *subject_entry;
7760 GtkWidget *subject;
7761 GtkWidget *paned;
7763 GtkWidget *edit_vbox;
7764 GtkWidget *ruler_hbox;
7765 GtkWidget *ruler;
7766 GtkWidget *scrolledwin;
7767 GtkWidget *text;
7768 GtkTextBuffer *buffer;
7769 GtkClipboard *clipboard;
7771 UndoMain *undostruct;
7773 GtkWidget *popupmenu;
7774 GtkWidget *tmpl_menu;
7775 GtkActionGroup *action_group = NULL;
7777 #if USE_ENCHANT
7778 GtkAspell * gtkaspell = NULL;
7779 #endif
7781 static GdkGeometry geometry;
7782 GdkRectangle workarea = {0};
7784 cm_return_val_if_fail(account != NULL, NULL);
7786 default_header_bgcolor = prefs_common.color[COL_DEFAULT_HEADER_BG],
7787 default_header_color = prefs_common.color[COL_DEFAULT_HEADER],
7789 debug_print("Creating compose window...\n");
7790 compose = g_new0(Compose, 1);
7792 compose->batch = batch;
7793 compose->account = account;
7794 compose->folder = folder;
7796 g_mutex_init(&compose->mutex);
7797 compose->set_cursor_pos = -1;
7799 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7801 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7802 gtk_window_set_default_size(GTK_WINDOW(window), prefs_common.compose_width,
7803 prefs_common.compose_height);
7805 gdk_monitor_get_workarea(gdk_display_get_primary_monitor(gdk_display_get_default()),
7806 &workarea);
7808 if (!geometry.max_width) {
7809 geometry.max_width = workarea.width;
7810 geometry.max_height = workarea.height;
7813 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7814 &geometry, GDK_HINT_MAX_SIZE);
7815 if (!geometry.min_width) {
7816 geometry.min_width = 600;
7817 geometry.min_height = 440;
7819 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7820 &geometry, GDK_HINT_MIN_SIZE);
7822 #ifndef GENERIC_UMPC
7823 if (compose_force_window_origin)
7824 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7825 prefs_common.compose_y);
7826 #endif
7827 g_signal_connect(G_OBJECT(window), "delete_event",
7828 G_CALLBACK(compose_delete_cb), compose);
7829 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7830 gtk_widget_realize(window);
7832 gtkut_widget_set_composer_icon(window);
7834 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
7835 gtk_container_add(GTK_CONTAINER(window), vbox);
7837 compose->ui_manager = gtk_ui_manager_new();
7838 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7839 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7840 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7841 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7842 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7843 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7844 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7845 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7846 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7847 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7853 #ifdef USE_ENCHANT
7854 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7855 #endif
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7860 /* Compose menu */
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7875 /* Edit menu */
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7885 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7898 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7915 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7918 #if USE_ENCHANT
7919 /* Spelling menu */
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7922 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7926 #endif
7928 /* Options menu */
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7935 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7936 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7937 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7938 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7942 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7943 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7944 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7946 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7947 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7948 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7950 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7951 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7952 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7953 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7954 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7956 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7958 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7959 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7960 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7961 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7962 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7964 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7965 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)
7966 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)
7967 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7969 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7971 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7972 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)
7973 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)
7975 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7977 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7978 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)
7979 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7981 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7982 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)
7983 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7985 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7987 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7988 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)
7989 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7990 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7991 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7992 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7994 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7995 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)
7996 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)
7997 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7998 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
8000 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
8001 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
8002 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
8003 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
8004 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
8005 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
8007 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
8008 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
8009 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)
8011 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
8012 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
8013 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
8014 /* phew. */
8016 /* Tools menu */
8017 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
8018 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
8019 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
8020 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8021 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
8022 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8024 /* Help menu */
8025 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
8027 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
8028 gtk_widget_show_all(menubar);
8030 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
8031 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
8033 handlebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8034 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8036 gtk_widget_realize(handlebox);
8037 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8038 (gpointer)compose);
8040 vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
8041 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8042 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8044 /* Notebook */
8045 notebook = gtk_notebook_new();
8046 gtk_widget_show(notebook);
8048 /* header labels and entries */
8049 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8050 compose_create_header(compose),
8051 gtk_label_new_with_mnemonic(_("Hea_der")));
8052 /* attachment list */
8053 attach_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8054 gtk_widget_show(attach_hbox);
8056 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8057 gtk_widget_show(attach_lab1);
8058 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8060 attach_lab2 = gtk_label_new("");
8061 gtk_widget_show(attach_lab2);
8062 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8064 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8065 compose_create_attach(compose),
8066 attach_hbox);
8067 /* Others Tab */
8068 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8069 compose_create_others(compose),
8070 gtk_label_new_with_mnemonic(_("Othe_rs")));
8072 /* Subject */
8073 subject_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8074 gtk_widget_show(subject_hbox);
8076 subject_frame = gtk_frame_new(NULL);
8077 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8078 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8079 gtk_widget_show(subject_frame);
8081 subject = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, HSPACING_NARROW);
8082 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8083 gtk_widget_show(subject);
8085 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8086 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8087 gtk_widget_show(label);
8089 #ifdef USE_ENCHANT
8090 subject_entry = claws_spell_entry_new();
8091 #else
8092 subject_entry = gtk_entry_new();
8093 #endif
8094 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8095 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8096 G_CALLBACK(compose_grab_focus_cb), compose);
8097 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8098 gtk_widget_show(subject_entry);
8099 compose->subject_entry = subject_entry;
8100 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8102 edit_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
8104 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8106 /* ruler */
8107 ruler_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8108 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8110 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8111 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8112 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8113 BORDER_WIDTH);
8115 /* text widget */
8116 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8117 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8118 GTK_POLICY_AUTOMATIC,
8119 GTK_POLICY_AUTOMATIC);
8120 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8121 GTK_SHADOW_IN);
8122 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8124 text = gtk_text_view_new();
8125 if (prefs_common.show_compose_margin) {
8126 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8127 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8129 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8130 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8131 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8132 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8133 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8135 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8136 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8137 G_CALLBACK(compose_edit_size_alloc),
8138 ruler);
8139 g_signal_connect(G_OBJECT(buffer), "changed",
8140 G_CALLBACK(compose_changed_cb), compose);
8141 g_signal_connect(G_OBJECT(text), "grab_focus",
8142 G_CALLBACK(compose_grab_focus_cb), compose);
8143 g_signal_connect(G_OBJECT(buffer), "insert_text",
8144 G_CALLBACK(text_inserted), compose);
8145 g_signal_connect(G_OBJECT(text), "button_press_event",
8146 G_CALLBACK(text_clicked), compose);
8147 g_signal_connect(G_OBJECT(text), "popup-menu",
8148 G_CALLBACK(compose_popup_menu), compose);
8149 g_signal_connect(G_OBJECT(subject_entry), "changed",
8150 G_CALLBACK(compose_changed_cb), compose);
8151 g_signal_connect(G_OBJECT(subject_entry), "activate",
8152 G_CALLBACK(compose_subject_entry_activated), compose);
8154 /* drag and drop */
8155 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8156 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8157 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8158 g_signal_connect(G_OBJECT(text), "drag_data_received",
8159 G_CALLBACK(compose_insert_drag_received_cb),
8160 compose);
8161 g_signal_connect(G_OBJECT(text), "drag-drop",
8162 G_CALLBACK(compose_drag_drop),
8163 compose);
8164 g_signal_connect(G_OBJECT(text), "key-press-event",
8165 G_CALLBACK(completion_set_focus_to_subject),
8166 compose);
8167 gtk_widget_show_all(vbox);
8169 /* pane between attach clist and text */
8170 paned = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
8171 gtk_box_pack_start(GTK_BOX(vbox2), paned, TRUE, TRUE, 0);
8172 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8173 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8174 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8175 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8176 G_CALLBACK(compose_notebook_size_alloc), paned);
8178 gtk_widget_show_all(paned);
8181 if (prefs_common.textfont) {
8182 PangoFontDescription *font_desc;
8184 font_desc = pango_font_description_from_string
8185 (prefs_common.textfont);
8186 if (font_desc) {
8187 gtk_widget_override_font(text, font_desc);
8188 pango_font_description_free(font_desc);
8192 gtk_action_group_add_actions(action_group, compose_popup_entries,
8193 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8194 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8195 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8196 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8197 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8198 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8199 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8201 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8203 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8204 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8205 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8207 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8209 undostruct = undo_init(text);
8210 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8211 compose);
8213 address_completion_start(window);
8215 compose->window = window;
8216 compose->vbox = vbox;
8217 compose->menubar = menubar;
8218 compose->handlebox = handlebox;
8220 compose->vbox2 = vbox2;
8222 compose->paned = paned;
8224 compose->attach_label = attach_lab2;
8226 compose->notebook = notebook;
8227 compose->edit_vbox = edit_vbox;
8228 compose->ruler_hbox = ruler_hbox;
8229 compose->ruler = ruler;
8230 compose->scrolledwin = scrolledwin;
8231 compose->text = text;
8233 compose->focused_editable = NULL;
8235 compose->popupmenu = popupmenu;
8237 compose->tmpl_menu = tmpl_menu;
8239 compose->mode = mode;
8240 compose->rmode = mode;
8242 compose->targetinfo = NULL;
8243 compose->replyinfo = NULL;
8244 compose->fwdinfo = NULL;
8246 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8247 g_str_equal, (GDestroyNotify) g_free, NULL);
8249 compose->replyto = NULL;
8250 compose->cc = NULL;
8251 compose->bcc = NULL;
8252 compose->followup_to = NULL;
8254 compose->ml_post = NULL;
8256 compose->inreplyto = NULL;
8257 compose->references = NULL;
8258 compose->msgid = NULL;
8259 compose->boundary = NULL;
8261 compose->autowrap = prefs_common.autowrap;
8262 compose->autoindent = prefs_common.auto_indent;
8263 compose->use_signing = FALSE;
8264 compose->use_encryption = FALSE;
8265 compose->privacy_system = NULL;
8266 compose->encdata = NULL;
8268 compose->modified = FALSE;
8270 compose->return_receipt = FALSE;
8272 compose->to_list = NULL;
8273 compose->newsgroup_list = NULL;
8275 compose->undostruct = undostruct;
8277 compose->sig_str = NULL;
8279 compose->exteditor_file = NULL;
8280 compose->exteditor_pid = INVALID_PID;
8281 compose->exteditor_tag = -1;
8282 compose->exteditor_socket = NULL;
8283 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8285 compose->folder_update_callback_id =
8286 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8287 compose_update_folder_hook,
8288 (gpointer) compose);
8290 #if USE_ENCHANT
8291 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8292 if (mode != COMPOSE_REDIRECT) {
8293 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8294 strcmp(prefs_common.dictionary, "")) {
8295 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8296 prefs_common.alt_dictionary,
8297 conv_get_locale_charset_str(),
8298 prefs_common.color[COL_MISSPELLED],
8299 prefs_common.check_while_typing,
8300 prefs_common.recheck_when_changing_dict,
8301 prefs_common.use_alternate,
8302 prefs_common.use_both_dicts,
8303 GTK_TEXT_VIEW(text),
8304 GTK_WINDOW(compose->window),
8305 compose_dict_changed,
8306 compose_spell_menu_changed,
8307 compose);
8308 if (!gtkaspell) {
8309 alertpanel_error(_("Spell checker could not "
8310 "be started.\n%s"),
8311 gtkaspell_checkers_strerror());
8312 gtkaspell_checkers_reset_error();
8313 } else {
8314 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8318 compose->gtkaspell = gtkaspell;
8319 compose_spell_menu_changed(compose);
8320 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8321 #endif
8323 compose_select_account(compose, account, TRUE);
8325 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8326 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8328 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8329 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8331 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8332 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8334 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8335 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8337 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8338 if (account->protocol != A_NNTP)
8339 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8340 prefs_common_translated_header_name("To:"));
8341 else
8342 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8343 prefs_common_translated_header_name("Newsgroups:"));
8345 #ifndef USE_ALT_ADDRBOOK
8346 addressbook_set_target_compose(compose);
8347 #endif
8348 if (mode != COMPOSE_REDIRECT)
8349 compose_set_template_menu(compose);
8350 else {
8351 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8354 compose_list = g_list_append(compose_list, compose);
8356 if (!prefs_common.show_ruler)
8357 gtk_widget_hide(ruler_hbox);
8359 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8361 /* Priority */
8362 compose->priority = PRIORITY_NORMAL;
8363 compose_update_priority_menu_item(compose);
8365 compose_set_out_encoding(compose);
8367 /* Actions menu */
8368 compose_update_actions_menu(compose);
8370 /* Privacy Systems menu */
8371 compose_update_privacy_systems_menu(compose);
8372 compose_activate_privacy_system(compose, account, TRUE);
8374 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8375 if (batch) {
8376 gtk_widget_realize(window);
8377 } else {
8378 gtk_widget_show(window);
8381 return compose;
8384 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8386 GList *accounts;
8387 GtkWidget *hbox;
8388 GtkWidget *optmenu;
8389 GtkWidget *optmenubox;
8390 GtkWidget *fromlabel;
8391 GtkListStore *menu;
8392 GtkTreeIter iter;
8393 GtkWidget *from_name = NULL;
8395 gint num = 0, def_menu = 0;
8397 accounts = account_get_list();
8398 cm_return_val_if_fail(accounts != NULL, NULL);
8400 optmenubox = gtk_event_box_new();
8401 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8402 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8404 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
8405 from_name = gtk_entry_new();
8407 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8408 G_CALLBACK(compose_grab_focus_cb), compose);
8409 g_signal_connect_after(G_OBJECT(from_name), "activate",
8410 G_CALLBACK(from_name_activate_cb), optmenu);
8412 for (; accounts != NULL; accounts = accounts->next, num++) {
8413 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8414 gchar *name, *from = NULL;
8416 if (ac == compose->account) def_menu = num;
8418 name = g_markup_printf_escaped("<i>%s</i>",
8419 ac->account_name);
8421 if (ac == compose->account) {
8422 if (ac->name && *ac->name) {
8423 gchar *buf;
8424 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8425 from = g_strdup_printf("%s <%s>",
8426 buf, ac->address);
8427 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8428 } else {
8429 from = g_strdup_printf("%s",
8430 ac->address);
8431 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8433 if (cur_account != compose->account) {
8434 GdkColor color;
8436 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_bgcolor, color);
8437 gtk_widget_modify_base(
8438 GTK_WIDGET(from_name),
8439 GTK_STATE_NORMAL, &color);
8440 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_color, color);
8441 gtk_widget_modify_text(
8442 GTK_WIDGET(from_name),
8443 GTK_STATE_NORMAL, &color);
8446 COMBOBOX_ADD(menu, name, ac->account_id);
8447 g_free(name);
8448 g_free(from);
8451 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8453 g_signal_connect(G_OBJECT(optmenu), "changed",
8454 G_CALLBACK(account_activated),
8455 compose);
8456 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8457 G_CALLBACK(compose_entry_popup_extend),
8458 NULL);
8460 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8461 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8463 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8464 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8465 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8467 CLAWS_SET_TIP(optmenubox,
8468 _("Account to use for this email"));
8469 CLAWS_SET_TIP(from_name,
8470 _("Sender address to be used"));
8472 compose->account_combo = optmenu;
8473 compose->from_name = from_name;
8475 return hbox;
8478 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8480 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8481 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8482 Compose *compose = (Compose *) data;
8483 if (active) {
8484 compose->priority = value;
8488 static void compose_reply_change_mode(Compose *compose,
8489 ComposeMode action)
8491 gboolean was_modified = compose->modified;
8493 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8495 cm_return_if_fail(compose->replyinfo != NULL);
8497 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8498 ml = TRUE;
8499 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8500 followup = TRUE;
8501 if (action == COMPOSE_REPLY_TO_ALL)
8502 all = TRUE;
8503 if (action == COMPOSE_REPLY_TO_SENDER)
8504 sender = TRUE;
8505 if (action == COMPOSE_REPLY_TO_LIST)
8506 ml = TRUE;
8508 compose_remove_header_entries(compose);
8509 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8510 if (compose->account->set_autocc && compose->account->auto_cc)
8511 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8513 if (compose->account->set_autobcc && compose->account->auto_bcc)
8514 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8516 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8517 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8518 compose_show_first_last_header(compose, TRUE);
8519 compose->modified = was_modified;
8520 compose_set_title(compose);
8523 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8525 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8526 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8527 Compose *compose = (Compose *) data;
8529 if (active)
8530 compose_reply_change_mode(compose, value);
8533 static void compose_update_priority_menu_item(Compose * compose)
8535 GtkWidget *menuitem = NULL;
8536 switch (compose->priority) {
8537 case PRIORITY_HIGHEST:
8538 menuitem = gtk_ui_manager_get_widget
8539 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8540 break;
8541 case PRIORITY_HIGH:
8542 menuitem = gtk_ui_manager_get_widget
8543 (compose->ui_manager, "/Menu/Options/Priority/High");
8544 break;
8545 case PRIORITY_NORMAL:
8546 menuitem = gtk_ui_manager_get_widget
8547 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8548 break;
8549 case PRIORITY_LOW:
8550 menuitem = gtk_ui_manager_get_widget
8551 (compose->ui_manager, "/Menu/Options/Priority/Low");
8552 break;
8553 case PRIORITY_LOWEST:
8554 menuitem = gtk_ui_manager_get_widget
8555 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8556 break;
8558 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8561 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8563 Compose *compose = (Compose *) data;
8564 gchar *systemid;
8565 gboolean can_sign = FALSE, can_encrypt = FALSE;
8567 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8569 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8570 return;
8572 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8573 g_free(compose->privacy_system);
8574 compose->privacy_system = NULL;
8575 g_free(compose->encdata);
8576 compose->encdata = NULL;
8577 if (systemid != NULL) {
8578 compose->privacy_system = g_strdup(systemid);
8580 can_sign = privacy_system_can_sign(systemid);
8581 can_encrypt = privacy_system_can_encrypt(systemid);
8584 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8586 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8587 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8588 if (compose->toolbar->privacy_sign_btn != NULL) {
8589 gtk_widget_set_sensitive(
8590 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8591 can_sign);
8592 gtk_toggle_tool_button_set_active(
8593 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8594 can_sign ? compose->use_signing : FALSE);
8596 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8597 gtk_widget_set_sensitive(
8598 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8599 can_encrypt);
8600 gtk_toggle_tool_button_set_active(
8601 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8602 can_encrypt ? compose->use_encryption : FALSE);
8606 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8608 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8609 GtkWidget *menuitem = NULL;
8610 GList *children, *amenu;
8611 gboolean can_sign = FALSE, can_encrypt = FALSE;
8612 gboolean found = FALSE;
8614 if (compose->privacy_system != NULL) {
8615 gchar *systemid;
8616 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8617 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8618 cm_return_if_fail(menuitem != NULL);
8620 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8621 amenu = children;
8622 menuitem = NULL;
8623 while (amenu != NULL) {
8624 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8625 if (systemid != NULL) {
8626 if (strcmp(systemid, compose->privacy_system) == 0 &&
8627 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8628 menuitem = GTK_WIDGET(amenu->data);
8630 can_sign = privacy_system_can_sign(systemid);
8631 can_encrypt = privacy_system_can_encrypt(systemid);
8632 found = TRUE;
8633 break;
8635 } else if (strlen(compose->privacy_system) == 0 &&
8636 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8637 menuitem = GTK_WIDGET(amenu->data);
8639 can_sign = FALSE;
8640 can_encrypt = FALSE;
8641 found = TRUE;
8642 break;
8645 amenu = amenu->next;
8647 g_list_free(children);
8648 if (menuitem != NULL)
8649 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8651 if (warn && !found && strlen(compose->privacy_system)) {
8652 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8653 "will not be able to sign or encrypt this message."),
8654 compose->privacy_system);
8658 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8659 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8660 if (compose->toolbar->privacy_sign_btn != NULL) {
8661 gtk_widget_set_sensitive(
8662 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8663 can_sign);
8665 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8666 gtk_widget_set_sensitive(
8667 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8668 can_encrypt);
8672 static void compose_set_out_encoding(Compose *compose)
8674 CharSet out_encoding;
8675 const gchar *branch = NULL;
8676 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8678 switch(out_encoding) {
8679 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8680 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8681 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8682 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8683 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8684 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8685 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8686 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8687 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8688 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8689 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8690 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8691 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8692 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8693 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8694 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8695 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8696 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8697 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8698 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8699 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8700 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8701 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8702 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8703 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8704 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8705 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8706 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8707 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8708 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8709 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8710 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8711 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8712 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8714 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8717 static void compose_set_template_menu(Compose *compose)
8719 GSList *tmpl_list, *cur;
8720 GtkWidget *menu;
8721 GtkWidget *item;
8723 tmpl_list = template_get_config();
8725 menu = gtk_menu_new();
8727 gtk_menu_set_accel_group (GTK_MENU (menu),
8728 gtk_ui_manager_get_accel_group(compose->ui_manager));
8729 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8730 Template *tmpl = (Template *)cur->data;
8731 gchar *accel_path = NULL;
8732 item = gtk_menu_item_new_with_label(tmpl->name);
8733 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8734 g_signal_connect(G_OBJECT(item), "activate",
8735 G_CALLBACK(compose_template_activate_cb),
8736 compose);
8737 g_object_set_data(G_OBJECT(item), "template", tmpl);
8738 gtk_widget_show(item);
8739 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8740 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8741 g_free(accel_path);
8744 gtk_widget_show(menu);
8745 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8748 void compose_update_actions_menu(Compose *compose)
8750 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8753 static void compose_update_privacy_systems_menu(Compose *compose)
8755 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8756 GSList *systems, *cur;
8757 GtkWidget *widget;
8758 GtkWidget *system_none;
8759 GSList *group;
8760 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8761 GtkWidget *privacy_menu = gtk_menu_new();
8763 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8764 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8766 g_signal_connect(G_OBJECT(system_none), "activate",
8767 G_CALLBACK(compose_set_privacy_system_cb), compose);
8769 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8770 gtk_widget_show(system_none);
8772 systems = privacy_get_system_ids();
8773 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8774 gchar *systemid = cur->data;
8776 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8777 widget = gtk_radio_menu_item_new_with_label(group,
8778 privacy_system_get_name(systemid));
8779 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8780 g_strdup(systemid), g_free);
8781 g_signal_connect(G_OBJECT(widget), "activate",
8782 G_CALLBACK(compose_set_privacy_system_cb), compose);
8784 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8785 gtk_widget_show(widget);
8786 g_free(systemid);
8788 g_slist_free(systems);
8789 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8790 gtk_widget_show_all(privacy_menu);
8791 gtk_widget_show_all(privacy_menuitem);
8794 void compose_reflect_prefs_all(void)
8796 GList *cur;
8797 Compose *compose;
8799 for (cur = compose_list; cur != NULL; cur = cur->next) {
8800 compose = (Compose *)cur->data;
8801 compose_set_template_menu(compose);
8805 void compose_reflect_prefs_pixmap_theme(void)
8807 GList *cur;
8808 Compose *compose;
8810 for (cur = compose_list; cur != NULL; cur = cur->next) {
8811 compose = (Compose *)cur->data;
8812 toolbar_update(TOOLBAR_COMPOSE, compose);
8816 static const gchar *compose_quote_char_from_context(Compose *compose)
8818 const gchar *qmark = NULL;
8820 cm_return_val_if_fail(compose != NULL, NULL);
8822 switch (compose->mode) {
8823 /* use forward-specific quote char */
8824 case COMPOSE_FORWARD:
8825 case COMPOSE_FORWARD_AS_ATTACH:
8826 case COMPOSE_FORWARD_INLINE:
8827 if (compose->folder && compose->folder->prefs &&
8828 compose->folder->prefs->forward_with_format)
8829 qmark = compose->folder->prefs->forward_quotemark;
8830 else if (compose->account->forward_with_format)
8831 qmark = compose->account->forward_quotemark;
8832 else
8833 qmark = prefs_common.fw_quotemark;
8834 break;
8836 /* use reply-specific quote char in all other modes */
8837 default:
8838 if (compose->folder && compose->folder->prefs &&
8839 compose->folder->prefs->reply_with_format)
8840 qmark = compose->folder->prefs->reply_quotemark;
8841 else if (compose->account->reply_with_format)
8842 qmark = compose->account->reply_quotemark;
8843 else
8844 qmark = prefs_common.quotemark;
8845 break;
8848 if (qmark == NULL || *qmark == '\0')
8849 qmark = "> ";
8851 return qmark;
8854 static void compose_template_apply(Compose *compose, Template *tmpl,
8855 gboolean replace)
8857 GtkTextView *text;
8858 GtkTextBuffer *buffer;
8859 GtkTextMark *mark;
8860 GtkTextIter iter;
8861 const gchar *qmark;
8862 gchar *parsed_str = NULL;
8863 gint cursor_pos = 0;
8864 const gchar *err_msg = _("The body of the template has an error at line %d.");
8865 if (!tmpl) return;
8867 /* process the body */
8869 text = GTK_TEXT_VIEW(compose->text);
8870 buffer = gtk_text_view_get_buffer(text);
8872 if (tmpl->value) {
8873 qmark = compose_quote_char_from_context(compose);
8875 if (compose->replyinfo != NULL) {
8877 if (replace)
8878 gtk_text_buffer_set_text(buffer, "", -1);
8879 mark = gtk_text_buffer_get_insert(buffer);
8880 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8882 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8883 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8885 } else if (compose->fwdinfo != 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->fwdinfo,
8893 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8895 } else {
8896 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8898 GtkTextIter start, end;
8899 gchar *tmp = NULL;
8901 gtk_text_buffer_get_start_iter(buffer, &start);
8902 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8903 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8905 /* clear the buffer now */
8906 if (replace)
8907 gtk_text_buffer_set_text(buffer, "", -1);
8909 parsed_str = compose_quote_fmt(compose, dummyinfo,
8910 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8911 procmsg_msginfo_free( &dummyinfo );
8913 g_free( tmp );
8915 } else {
8916 if (replace)
8917 gtk_text_buffer_set_text(buffer, "", -1);
8918 mark = gtk_text_buffer_get_insert(buffer);
8919 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8922 if (replace && parsed_str && compose->account->auto_sig)
8923 compose_insert_sig(compose, FALSE);
8925 if (replace && parsed_str) {
8926 gtk_text_buffer_get_start_iter(buffer, &iter);
8927 gtk_text_buffer_place_cursor(buffer, &iter);
8930 if (parsed_str) {
8931 cursor_pos = quote_fmt_get_cursor_pos();
8932 compose->set_cursor_pos = cursor_pos;
8933 if (cursor_pos == -1)
8934 cursor_pos = 0;
8935 gtk_text_buffer_get_start_iter(buffer, &iter);
8936 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8937 gtk_text_buffer_place_cursor(buffer, &iter);
8940 /* process the other fields */
8942 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8943 compose_template_apply_fields(compose, tmpl);
8944 quote_fmt_reset_vartable();
8945 quote_fmtlex_destroy();
8947 compose_changed_cb(NULL, compose);
8949 #ifdef USE_ENCHANT
8950 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8951 gtkaspell_highlight_all(compose->gtkaspell);
8952 #endif
8955 static void compose_template_apply_fields_error(const gchar *header)
8957 gchar *tr;
8958 gchar *text;
8960 tr = g_strdup(C_("'%s' stands for a header name",
8961 "Template '%s' format error."));
8962 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8963 alertpanel_error("%s", text);
8965 g_free(text);
8966 g_free(tr);
8969 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8971 MsgInfo* dummyinfo = NULL;
8972 MsgInfo *msginfo = NULL;
8973 gchar *buf = NULL;
8975 if (compose->replyinfo != NULL)
8976 msginfo = compose->replyinfo;
8977 else if (compose->fwdinfo != NULL)
8978 msginfo = compose->fwdinfo;
8979 else {
8980 dummyinfo = compose_msginfo_new_from_compose(compose);
8981 msginfo = dummyinfo;
8984 if (tmpl->from && *tmpl->from != '\0') {
8985 #ifdef USE_ENCHANT
8986 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8987 compose->gtkaspell);
8988 #else
8989 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8990 #endif
8991 quote_fmt_scan_string(tmpl->from);
8992 quote_fmt_parse();
8994 buf = quote_fmt_get_buffer();
8995 if (buf == NULL) {
8996 compose_template_apply_fields_error("From");
8997 } else {
8998 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
9001 quote_fmt_reset_vartable();
9002 quote_fmtlex_destroy();
9005 if (tmpl->to && *tmpl->to != '\0') {
9006 #ifdef USE_ENCHANT
9007 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9008 compose->gtkaspell);
9009 #else
9010 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9011 #endif
9012 quote_fmt_scan_string(tmpl->to);
9013 quote_fmt_parse();
9015 buf = quote_fmt_get_buffer();
9016 if (buf == NULL) {
9017 compose_template_apply_fields_error("To");
9018 } else {
9019 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
9022 quote_fmt_reset_vartable();
9023 quote_fmtlex_destroy();
9026 if (tmpl->cc && *tmpl->cc != '\0') {
9027 #ifdef USE_ENCHANT
9028 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9029 compose->gtkaspell);
9030 #else
9031 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9032 #endif
9033 quote_fmt_scan_string(tmpl->cc);
9034 quote_fmt_parse();
9036 buf = quote_fmt_get_buffer();
9037 if (buf == NULL) {
9038 compose_template_apply_fields_error("Cc");
9039 } else {
9040 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9043 quote_fmt_reset_vartable();
9044 quote_fmtlex_destroy();
9047 if (tmpl->bcc && *tmpl->bcc != '\0') {
9048 #ifdef USE_ENCHANT
9049 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9050 compose->gtkaspell);
9051 #else
9052 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9053 #endif
9054 quote_fmt_scan_string(tmpl->bcc);
9055 quote_fmt_parse();
9057 buf = quote_fmt_get_buffer();
9058 if (buf == NULL) {
9059 compose_template_apply_fields_error("Bcc");
9060 } else {
9061 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9064 quote_fmt_reset_vartable();
9065 quote_fmtlex_destroy();
9068 if (tmpl->replyto && *tmpl->replyto != '\0') {
9069 #ifdef USE_ENCHANT
9070 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9071 compose->gtkaspell);
9072 #else
9073 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9074 #endif
9075 quote_fmt_scan_string(tmpl->replyto);
9076 quote_fmt_parse();
9078 buf = quote_fmt_get_buffer();
9079 if (buf == NULL) {
9080 compose_template_apply_fields_error("Reply-To");
9081 } else {
9082 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9085 quote_fmt_reset_vartable();
9086 quote_fmtlex_destroy();
9089 /* process the subject */
9090 if (tmpl->subject && *tmpl->subject != '\0') {
9091 #ifdef USE_ENCHANT
9092 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9093 compose->gtkaspell);
9094 #else
9095 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9096 #endif
9097 quote_fmt_scan_string(tmpl->subject);
9098 quote_fmt_parse();
9100 buf = quote_fmt_get_buffer();
9101 if (buf == NULL) {
9102 compose_template_apply_fields_error("Subject");
9103 } else {
9104 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9107 quote_fmt_reset_vartable();
9108 quote_fmtlex_destroy();
9111 procmsg_msginfo_free( &dummyinfo );
9114 static void compose_destroy(Compose *compose)
9116 GtkAllocation allocation;
9117 GtkTextBuffer *buffer;
9118 GtkClipboard *clipboard;
9120 compose_list = g_list_remove(compose_list, compose);
9122 #ifdef USE_LDAP
9123 gboolean enable = TRUE;
9124 g_slist_foreach(compose->passworded_ldap_servers,
9125 _ldap_srv_func, &enable);
9126 g_slist_free(compose->passworded_ldap_servers);
9127 #endif
9129 if (compose->updating) {
9130 debug_print("danger, not destroying anything now\n");
9131 compose->deferred_destroy = TRUE;
9132 return;
9135 /* NOTE: address_completion_end() does nothing with the window
9136 * however this may change. */
9137 address_completion_end(compose->window);
9139 slist_free_strings_full(compose->to_list);
9140 slist_free_strings_full(compose->newsgroup_list);
9141 slist_free_strings_full(compose->header_list);
9143 slist_free_strings_full(extra_headers);
9144 extra_headers = NULL;
9146 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9148 g_hash_table_destroy(compose->email_hashtable);
9150 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9151 compose->folder_update_callback_id);
9153 procmsg_msginfo_free(&(compose->targetinfo));
9154 procmsg_msginfo_free(&(compose->replyinfo));
9155 procmsg_msginfo_free(&(compose->fwdinfo));
9157 g_free(compose->replyto);
9158 g_free(compose->cc);
9159 g_free(compose->bcc);
9160 g_free(compose->newsgroups);
9161 g_free(compose->followup_to);
9163 g_free(compose->ml_post);
9165 g_free(compose->inreplyto);
9166 g_free(compose->references);
9167 g_free(compose->msgid);
9168 g_free(compose->boundary);
9170 g_free(compose->redirect_filename);
9171 if (compose->undostruct)
9172 undo_destroy(compose->undostruct);
9174 g_free(compose->sig_str);
9176 g_free(compose->exteditor_file);
9178 g_free(compose->orig_charset);
9180 g_free(compose->privacy_system);
9181 g_free(compose->encdata);
9183 #ifndef USE_ALT_ADDRBOOK
9184 if (addressbook_get_target_compose() == compose)
9185 addressbook_set_target_compose(NULL);
9186 #endif
9187 #if USE_ENCHANT
9188 if (compose->gtkaspell) {
9189 gtkaspell_delete(compose->gtkaspell);
9190 compose->gtkaspell = NULL;
9192 #endif
9194 if (!compose->batch) {
9195 gtk_window_get_size(GTK_WINDOW(compose->window),
9196 &allocation.width, &allocation.height);
9197 prefs_common.compose_width = allocation.width;
9198 prefs_common.compose_height = allocation.height;
9201 if (!gtk_widget_get_parent(compose->paned))
9202 gtk_widget_destroy(compose->paned);
9203 gtk_widget_destroy(compose->popupmenu);
9205 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9206 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9207 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9209 message_search_close(compose);
9210 gtk_widget_destroy(compose->window);
9211 toolbar_destroy(compose->toolbar);
9212 g_free(compose->toolbar);
9213 g_mutex_clear(&compose->mutex);
9214 g_free(compose);
9217 static void compose_attach_info_free(AttachInfo *ainfo)
9219 g_free(ainfo->file);
9220 g_free(ainfo->content_type);
9221 g_free(ainfo->name);
9222 g_free(ainfo->charset);
9223 g_free(ainfo);
9226 static void compose_attach_update_label(Compose *compose)
9228 GtkTreeIter iter;
9229 gint i = 1;
9230 gchar *text;
9231 GtkTreeModel *model;
9232 goffset total_size;
9233 AttachInfo *ainfo;
9235 if (compose == NULL)
9236 return;
9238 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9239 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9240 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9241 return;
9244 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9245 total_size = ainfo->size;
9246 while(gtk_tree_model_iter_next(model, &iter)) {
9247 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9248 total_size += ainfo->size;
9249 i++;
9251 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9252 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9253 g_free(text);
9256 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9258 Compose *compose = (Compose *)data;
9259 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9260 GtkTreeSelection *selection;
9261 GList *sel, *cur;
9262 GtkTreeModel *model;
9264 selection = gtk_tree_view_get_selection(tree_view);
9265 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9266 cm_return_if_fail(sel);
9268 for (cur = sel; cur != NULL; cur = cur->next) {
9269 GtkTreePath *path = cur->data;
9270 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9271 (model, cur->data);
9272 cur->data = ref;
9273 gtk_tree_path_free(path);
9276 for (cur = sel; cur != NULL; cur = cur->next) {
9277 GtkTreeRowReference *ref = cur->data;
9278 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9279 GtkTreeIter iter;
9281 if (gtk_tree_model_get_iter(model, &iter, path))
9282 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9284 gtk_tree_path_free(path);
9285 gtk_tree_row_reference_free(ref);
9288 g_list_free(sel);
9289 compose_attach_update_label(compose);
9292 static struct _AttachProperty
9294 GtkWidget *window;
9295 GtkWidget *mimetype_entry;
9296 GtkWidget *encoding_optmenu;
9297 GtkWidget *path_entry;
9298 GtkWidget *filename_entry;
9299 GtkWidget *ok_btn;
9300 GtkWidget *cancel_btn;
9301 } attach_prop;
9303 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9305 gtk_tree_path_free((GtkTreePath *)ptr);
9308 static void compose_attach_property(GtkAction *action, gpointer data)
9310 Compose *compose = (Compose *)data;
9311 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9312 AttachInfo *ainfo;
9313 GtkComboBox *optmenu;
9314 GtkTreeSelection *selection;
9315 GList *sel;
9316 GtkTreeModel *model;
9317 GtkTreeIter iter;
9318 GtkTreePath *path;
9319 static gboolean cancelled;
9321 /* only if one selected */
9322 selection = gtk_tree_view_get_selection(tree_view);
9323 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9324 return;
9326 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9327 cm_return_if_fail(sel);
9329 path = (GtkTreePath *) sel->data;
9330 gtk_tree_model_get_iter(model, &iter, path);
9331 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9333 if (!ainfo) {
9334 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9335 g_list_free(sel);
9336 return;
9338 g_list_free(sel);
9340 if (!attach_prop.window)
9341 compose_attach_property_create(&cancelled);
9342 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9343 gtk_widget_grab_focus(attach_prop.ok_btn);
9344 gtk_widget_show(attach_prop.window);
9345 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9346 GTK_WINDOW(compose->window));
9348 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9349 if (ainfo->encoding == ENC_UNKNOWN)
9350 combobox_select_by_data(optmenu, ENC_BASE64);
9351 else
9352 combobox_select_by_data(optmenu, ainfo->encoding);
9354 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9355 ainfo->content_type ? ainfo->content_type : "");
9356 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9357 ainfo->file ? ainfo->file : "");
9358 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9359 ainfo->name ? ainfo->name : "");
9361 for (;;) {
9362 const gchar *entry_text;
9363 gchar *text;
9364 gchar *cnttype = NULL;
9365 gchar *file = NULL;
9366 off_t size = 0;
9368 cancelled = FALSE;
9369 gtk_main();
9371 gtk_widget_hide(attach_prop.window);
9372 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9374 if (cancelled)
9375 break;
9377 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9378 if (*entry_text != '\0') {
9379 gchar *p;
9381 text = g_strstrip(g_strdup(entry_text));
9382 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9383 cnttype = g_strdup(text);
9384 g_free(text);
9385 } else {
9386 alertpanel_error(_("Invalid MIME type."));
9387 g_free(text);
9388 continue;
9392 ainfo->encoding = combobox_get_active_data(optmenu);
9394 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9395 if (*entry_text != '\0') {
9396 if (is_file_exist(entry_text) &&
9397 (size = get_file_size(entry_text)) > 0)
9398 file = g_strdup(entry_text);
9399 else {
9400 alertpanel_error
9401 (_("File doesn't exist or is empty."));
9402 g_free(cnttype);
9403 continue;
9407 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9408 if (*entry_text != '\0') {
9409 g_free(ainfo->name);
9410 ainfo->name = g_strdup(entry_text);
9413 if (cnttype) {
9414 g_free(ainfo->content_type);
9415 ainfo->content_type = cnttype;
9417 if (file) {
9418 g_free(ainfo->file);
9419 ainfo->file = file;
9421 if (size)
9422 ainfo->size = (goffset)size;
9424 /* update tree store */
9425 text = to_human_readable(ainfo->size);
9426 gtk_tree_model_get_iter(model, &iter, path);
9427 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9428 COL_MIMETYPE, ainfo->content_type,
9429 COL_SIZE, text,
9430 COL_NAME, ainfo->name,
9431 COL_CHARSET, ainfo->charset,
9432 -1);
9434 break;
9437 gtk_tree_path_free(path);
9440 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9442 label = gtk_label_new(str); \
9443 gtk_grid_attach(GTK_GRID(table), label, 0, top, 1, 1); \
9444 gtk_label_set_xalign(GTK_LABEL(label), 0.0); \
9445 entry = gtk_entry_new(); \
9446 gtk_grid_attach(GTK_GRID(table), entry, 1, top, 1, 1); \
9449 static void compose_attach_property_create(gboolean *cancelled)
9451 GtkWidget *window;
9452 GtkWidget *vbox;
9453 GtkWidget *table;
9454 GtkWidget *label;
9455 GtkWidget *mimetype_entry;
9456 GtkWidget *hbox;
9457 GtkWidget *optmenu;
9458 GtkListStore *optmenu_menu;
9459 GtkWidget *path_entry;
9460 GtkWidget *filename_entry;
9461 GtkWidget *hbbox;
9462 GtkWidget *ok_btn;
9463 GtkWidget *cancel_btn;
9464 GList *mime_type_list, *strlist;
9465 GtkTreeIter iter;
9467 debug_print("Creating attach_property window...\n");
9469 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9470 gtk_widget_set_size_request(window, 480, -1);
9471 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9472 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9473 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9474 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
9475 g_signal_connect(G_OBJECT(window), "delete_event",
9476 G_CALLBACK(attach_property_delete_event),
9477 cancelled);
9478 g_signal_connect(G_OBJECT(window), "key_press_event",
9479 G_CALLBACK(attach_property_key_pressed),
9480 cancelled);
9482 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
9483 gtk_container_add(GTK_CONTAINER(window), vbox);
9485 table = gtk_grid_new();
9486 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9487 gtk_grid_set_row_spacing(GTK_GRID(table), 8);
9488 gtk_grid_set_column_spacing(GTK_GRID(table), 8);
9490 label = gtk_label_new(_("MIME type"));
9491 gtk_grid_attach(GTK_GRID(table), label, 0, 0, 1, 1);
9492 gtk_label_set_xalign(GTK_LABEL(label), 0.0);
9493 mimetype_entry = gtk_combo_box_text_new_with_entry();
9494 gtk_grid_attach(GTK_GRID(table), mimetype_entry, 1, 0, 1, 1);
9495 gtk_widget_set_hexpand(mimetype_entry, TRUE);
9496 gtk_widget_set_halign(mimetype_entry, GTK_ALIGN_FILL);
9498 /* stuff with list */
9499 mime_type_list = procmime_get_mime_type_list();
9500 strlist = NULL;
9501 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9502 MimeType *type = (MimeType *) mime_type_list->data;
9503 gchar *tmp;
9505 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9507 if (g_list_find_custom(strlist, tmp, (GCompareFunc)g_strcmp0))
9508 g_free(tmp);
9509 else
9510 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9511 (GCompareFunc)g_strcmp0);
9514 for (mime_type_list = strlist; mime_type_list != NULL;
9515 mime_type_list = mime_type_list->next) {
9516 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9517 g_free(mime_type_list->data);
9519 g_list_free(strlist);
9520 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9521 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9523 label = gtk_label_new(_("Encoding"));
9524 gtk_grid_attach(GTK_GRID(table), label, 0, 1, 1, 1);
9525 gtk_label_set_xalign(GTK_LABEL(label), 0.0);
9527 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
9528 gtk_grid_attach(GTK_GRID(table), hbox, 1, 1, 1, 1);
9529 gtk_widget_set_hexpand(hbox, TRUE);
9530 gtk_widget_set_halign(hbox, GTK_ALIGN_FILL);
9532 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9533 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9535 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9536 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9537 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9538 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9539 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9541 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9543 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9544 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9546 gtkut_stock_button_set_create(&hbbox, &cancel_btn, NULL, _("_Cancel"),
9547 &ok_btn, NULL, _("_OK"),
9548 NULL, NULL, NULL);
9549 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9550 gtk_widget_grab_default(ok_btn);
9552 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9553 G_CALLBACK(attach_property_ok),
9554 cancelled);
9555 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9556 G_CALLBACK(attach_property_cancel),
9557 cancelled);
9559 gtk_widget_show_all(vbox);
9561 attach_prop.window = window;
9562 attach_prop.mimetype_entry = mimetype_entry;
9563 attach_prop.encoding_optmenu = optmenu;
9564 attach_prop.path_entry = path_entry;
9565 attach_prop.filename_entry = filename_entry;
9566 attach_prop.ok_btn = ok_btn;
9567 attach_prop.cancel_btn = cancel_btn;
9570 #undef SET_LABEL_AND_ENTRY
9572 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9574 *cancelled = FALSE;
9575 gtk_main_quit();
9578 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9580 *cancelled = TRUE;
9581 gtk_main_quit();
9584 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9585 gboolean *cancelled)
9587 *cancelled = TRUE;
9588 gtk_main_quit();
9590 return TRUE;
9593 static gboolean attach_property_key_pressed(GtkWidget *widget,
9594 GdkEventKey *event,
9595 gboolean *cancelled)
9597 if (event && event->keyval == GDK_KEY_Escape) {
9598 *cancelled = TRUE;
9599 gtk_main_quit();
9601 if (event && (event->keyval == GDK_KEY_KP_Enter ||
9602 event->keyval == GDK_KEY_Return)) {
9603 *cancelled = FALSE;
9604 gtk_main_quit();
9605 return TRUE;
9607 return FALSE;
9610 static gboolean compose_can_autosave(Compose *compose)
9612 if (compose->privacy_system && compose->use_encryption)
9613 return prefs_common.autosave && prefs_common.autosave_encrypted;
9614 else
9615 return prefs_common.autosave;
9619 * compose_exec_ext_editor:
9621 * Open (and optionally embed) external editor
9623 static void compose_exec_ext_editor(Compose *compose)
9625 gchar *tmp;
9626 #ifdef GDK_WINDOWING_X11
9627 GtkWidget *socket;
9628 Window socket_wid = 0;
9629 gchar *p, *s;
9630 #endif /* GDK_WINDOWING_X11 */
9631 GPid pid;
9632 GError *error = NULL;
9633 gchar *cmd = NULL;
9634 gchar **argv;
9636 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9637 G_DIR_SEPARATOR, compose);
9639 if (compose_write_body_to_file(compose, tmp) < 0) {
9640 alertpanel_error(_("Could not write the body to file:\n%s"),
9641 tmp);
9642 g_free(tmp);
9643 return;
9646 #ifdef GDK_WINDOWING_X11
9647 if (compose_get_ext_editor_uses_socket()) {
9648 if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
9649 /* Only allow one socket */
9650 if (compose->exteditor_socket != NULL) {
9651 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9652 /* Move the focus off of the socket */
9653 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9655 g_free(tmp);
9656 return;
9658 /* Create the receiving GtkSocket */
9659 socket = gtk_socket_new ();
9660 g_signal_connect (G_OBJECT(socket), "plug-removed",
9661 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9662 compose);
9663 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9664 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9665 /* Realize the socket so that we can use its ID */
9666 gtk_widget_realize(socket);
9667 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9668 compose->exteditor_socket = socket;
9669 } else
9670 debug_print("Socket communication with an external editor is only available on X11.\n");
9672 #else
9673 if (compose_get_ext_editor_uses_socket()) {
9674 alertpanel_error(_("Socket communication with an external editor is only available on X11."));
9675 g_free(tmp);
9676 return;
9678 #endif /* GDK_WINDOWING_X11 */
9680 if (compose_get_ext_editor_cmd_valid()) {
9681 #ifdef GDK_WINDOWING_X11
9682 if (compose_get_ext_editor_uses_socket() && GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
9683 p = g_strdup(prefs_common_get_ext_editor_cmd());
9684 s = strstr(p, "%w");
9685 s[1] = 'u';
9686 if (strstr(p, "%s") < s)
9687 cmd = g_strdup_printf(p, tmp, socket_wid);
9688 else
9689 cmd = g_strdup_printf(p, socket_wid, tmp);
9690 g_free(p);
9691 } else {
9692 cmd = g_strdup_printf(prefs_common_get_ext_editor_cmd(), tmp);
9694 #else
9695 cmd = g_strdup_printf(prefs_common_get_ext_editor_cmd(), tmp);
9696 #endif /* GDK_WINDOWING_X11 */
9697 } else {
9698 if (prefs_common_get_ext_editor_cmd())
9699 g_warning("external editor command-line is invalid: '%s'",
9700 prefs_common_get_ext_editor_cmd());
9701 cmd = g_strdup_printf(DEFAULT_EDITOR_CMD, tmp);
9704 argv = strsplit_with_quote(cmd, " ", 0);
9706 if (!g_spawn_async(NULL, argv, NULL,
9707 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
9708 NULL, NULL, &pid, &error)) {
9709 alertpanel_error(_("Could not spawn the following "
9710 "external editor command:\n%s\n%s"),
9711 cmd, error ? error->message : _("Unknown error"));
9712 if (error)
9713 g_error_free(error);
9714 g_free(tmp);
9715 g_free(cmd);
9716 g_strfreev(argv);
9717 return;
9719 g_free(cmd);
9720 g_strfreev(argv);
9722 compose->exteditor_file = g_strdup(tmp);
9723 compose->exteditor_pid = pid;
9724 compose->exteditor_tag = g_child_watch_add(pid,
9725 compose_ext_editor_closed_cb,
9726 compose);
9728 compose_set_ext_editor_sensitive(compose, FALSE);
9730 g_free(tmp);
9734 * compose_ext_editor_cb:
9736 * External editor has closed (called by g_child_watch)
9738 static void compose_ext_editor_closed_cb(GPid pid, gint exit_status, gpointer data)
9740 Compose *compose = (Compose *)data;
9741 GError *error = NULL;
9742 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9743 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9744 GtkTextIter start, end;
9745 gchar *chars;
9747 #if GLIB_CHECK_VERSION(2,70,0)
9748 if (!g_spawn_check_wait_status(exit_status, &error)) {
9749 #else
9750 if (!g_spawn_check_exit_status(exit_status, &error)) {
9751 #endif
9752 alertpanel_error(
9753 _("External editor stopped with an error: %s"),
9754 error ? error->message : _("Unknown error"));
9755 if (error)
9756 g_error_free(error);
9758 g_spawn_close_pid(compose->exteditor_pid);
9760 gtk_text_buffer_set_text(buffer, "", -1);
9761 compose_insert_file(compose, compose->exteditor_file);
9762 compose_changed_cb(NULL, compose);
9764 /* Check if we should save the draft or not */
9765 if (compose_can_autosave(compose))
9766 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9768 if (claws_unlink(compose->exteditor_file) < 0)
9769 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9771 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9772 gtk_text_buffer_get_start_iter(buffer, &start);
9773 gtk_text_buffer_get_end_iter(buffer, &end);
9774 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9775 if (chars && strlen(chars) > 0)
9776 compose->modified = TRUE;
9777 g_free(chars);
9779 compose_set_ext_editor_sensitive(compose, TRUE);
9781 g_free(compose->exteditor_file);
9782 compose->exteditor_file = NULL;
9783 compose->exteditor_pid = INVALID_PID;
9784 compose->exteditor_tag = -1;
9785 #ifdef GDK_WINDOWING_X11
9786 if (compose->exteditor_socket && GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
9787 gtk_widget_destroy(compose->exteditor_socket);
9788 compose->exteditor_socket = NULL;
9790 #endif /* GDK_WINDOWING_X11 */
9794 static gboolean compose_get_ext_editor_cmd_valid()
9796 gboolean has_s = FALSE;
9797 gboolean has_w = FALSE;
9798 const gchar *p = prefs_common_get_ext_editor_cmd();
9799 if (!p)
9800 return FALSE;
9801 while ((p = strchr(p, '%'))) {
9802 p++;
9803 if (*p == 's') {
9804 if (has_s)
9805 return FALSE;
9806 has_s = TRUE;
9807 } else if (*p == 'w') {
9808 if (has_w)
9809 return FALSE;
9810 has_w = TRUE;
9811 } else {
9812 return FALSE;
9815 return TRUE;
9818 static gboolean compose_ext_editor_kill(Compose *compose)
9820 GPid pid = compose->exteditor_pid;
9821 gchar *pidmsg = NULL;
9823 if (pid > 0) {
9824 AlertValue val;
9825 gchar *msg;
9827 pidmsg = g_strdup_printf(_("process id: %" G_PID_FORMAT), pid);
9829 msg = g_strdup_printf
9830 (_("The external editor is still working.\n"
9831 "Force terminating the process?\n"
9832 "%s"), pidmsg);
9833 val = alertpanel_full(_("Notice"), msg, NULL, _("_No"), NULL, _("_Yes"),
9834 NULL, NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9835 ALERT_WARNING);
9836 g_free(msg);
9838 if (val == G_ALERTALTERNATE) {
9839 g_source_remove(compose->exteditor_tag);
9841 #ifdef G_OS_WIN32
9842 if (!TerminateProcess(compose->exteditor_pid, 0))
9843 perror("TerminateProcess");
9844 #else
9845 if (kill(pid, SIGTERM) < 0) perror("kill");
9846 waitpid(compose->exteditor_pid, NULL, 0);
9847 #endif /* G_OS_WIN32 */
9849 g_warning("terminated %s, temporary file: %s",
9850 pidmsg, compose->exteditor_file);
9851 g_spawn_close_pid(compose->exteditor_pid);
9853 compose_set_ext_editor_sensitive(compose, TRUE);
9855 g_free(compose->exteditor_file);
9856 compose->exteditor_file = NULL;
9857 compose->exteditor_pid = INVALID_PID;
9858 compose->exteditor_tag = -1;
9859 } else {
9860 g_free(pidmsg);
9861 return FALSE;
9865 if (pidmsg)
9866 g_free(pidmsg);
9867 return TRUE;
9870 static char *ext_editor_menu_entries[] = {
9871 "Menu/Message/Send",
9872 "Menu/Message/SendLater",
9873 "Menu/Message/InsertFile",
9874 "Menu/Message/InsertSig",
9875 "Menu/Message/ReplaceSig",
9876 "Menu/Message/Save",
9877 "Menu/Message/Print",
9878 "Menu/Edit",
9879 #if USE_ENCHANT
9880 "Menu/Spelling",
9881 #endif
9882 "Menu/Tools/ShowRuler",
9883 "Menu/Tools/Actions",
9884 "Menu/Help",
9885 NULL
9888 static void compose_set_ext_editor_sensitive(Compose *compose,
9889 gboolean sensitive)
9891 int i;
9893 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9894 cm_menu_set_sensitive_full(compose->ui_manager,
9895 ext_editor_menu_entries[i], sensitive);
9898 #ifdef GDK_WINDOWING_X11
9899 if (compose_get_ext_editor_uses_socket() && GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
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 #else
9923 gtk_widget_set_sensitive(compose->text, sensitive);
9924 #endif /* GDK_WINDOWING_X11 */
9925 if (compose->toolbar->send_btn)
9926 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9927 if (compose->toolbar->sendl_btn)
9928 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9929 if (compose->toolbar->draft_btn)
9930 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9931 if (compose->toolbar->insert_btn)
9932 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9933 if (compose->toolbar->sig_btn)
9934 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9935 if (compose->toolbar->exteditor_btn)
9936 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9937 if (compose->toolbar->linewrap_current_btn)
9938 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9939 if (compose->toolbar->linewrap_all_btn)
9940 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9943 static gboolean compose_get_ext_editor_uses_socket()
9945 return (prefs_common_get_ext_editor_cmd() &&
9946 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9949 #ifdef GDK_WINDOWING_X11
9950 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9952 compose->exteditor_socket = NULL;
9953 /* returning FALSE allows destruction of the socket */
9954 return FALSE;
9956 #endif /* GDK_WINDOWING_X11 */
9959 * compose_undo_state_changed:
9961 * Change the sensivity of the menuentries undo and redo
9963 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9964 gint redo_state, gpointer data)
9966 Compose *compose = (Compose *)data;
9968 switch (undo_state) {
9969 case UNDO_STATE_TRUE:
9970 if (!undostruct->undo_state) {
9971 undostruct->undo_state = TRUE;
9972 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9974 break;
9975 case UNDO_STATE_FALSE:
9976 if (undostruct->undo_state) {
9977 undostruct->undo_state = FALSE;
9978 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9980 break;
9981 case UNDO_STATE_UNCHANGED:
9982 break;
9983 case UNDO_STATE_REFRESH:
9984 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9985 break;
9986 default:
9987 g_warning("undo state not recognized");
9988 break;
9991 switch (redo_state) {
9992 case UNDO_STATE_TRUE:
9993 if (!undostruct->redo_state) {
9994 undostruct->redo_state = TRUE;
9995 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9997 break;
9998 case UNDO_STATE_FALSE:
9999 if (undostruct->redo_state) {
10000 undostruct->redo_state = FALSE;
10001 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
10003 break;
10004 case UNDO_STATE_UNCHANGED:
10005 break;
10006 case UNDO_STATE_REFRESH:
10007 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
10008 break;
10009 default:
10010 g_warning("redo state not recognized");
10011 break;
10015 /* callback functions */
10017 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10018 GtkAllocation *allocation,
10019 GtkPaned *paned)
10021 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10024 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10025 * includes "non-client" (windows-izm) in calculation, so this calculation
10026 * may not be accurate.
10028 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10029 GtkAllocation *allocation,
10030 GtkSHRuler *shruler)
10032 if (prefs_common.show_ruler) {
10033 gint char_width = 0, char_height = 0;
10034 gint line_width_in_chars;
10036 gtkut_get_font_size(GTK_WIDGET(widget),
10037 &char_width, &char_height);
10038 line_width_in_chars =
10039 (allocation->width - allocation->x) / char_width;
10041 /* got the maximum */
10042 gtk_shruler_set_range(GTK_SHRULER(shruler),
10043 0.0, line_width_in_chars, 0);
10046 return TRUE;
10049 typedef struct {
10050 gchar *header;
10051 gchar *entry;
10052 ComposePrefType type;
10053 gboolean entry_marked;
10054 } HeaderEntryState;
10056 static void account_activated(GtkComboBox *optmenu, gpointer data)
10058 Compose *compose = (Compose *)data;
10060 PrefsAccount *ac;
10061 gchar *folderidentifier;
10062 gint account_id = 0;
10063 GtkTreeModel *menu;
10064 GtkTreeIter iter;
10065 GSList *list, *saved_list = NULL;
10066 HeaderEntryState *state;
10068 /* Get ID of active account in the combo box */
10069 menu = gtk_combo_box_get_model(optmenu);
10070 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10071 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10073 ac = account_find_from_id(account_id);
10074 cm_return_if_fail(ac != NULL);
10076 if (ac != compose->account) {
10077 compose_select_account(compose, ac, FALSE);
10079 for (list = compose->header_list; list; list = list->next) {
10080 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10082 if (hentry->type == PREF_ACCOUNT || !list->next) {
10083 compose_destroy_headerentry(compose, hentry);
10084 continue;
10086 state = g_malloc0(sizeof(HeaderEntryState));
10087 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10088 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10089 state->entry = gtk_editable_get_chars(
10090 GTK_EDITABLE(hentry->entry), 0, -1);
10091 state->type = hentry->type;
10093 saved_list = g_slist_append(saved_list, state);
10094 compose_destroy_headerentry(compose, hentry);
10097 compose->header_last = NULL;
10098 g_slist_free(compose->header_list);
10099 compose->header_list = NULL;
10100 compose->header_nextrow = 1;
10101 compose_create_header_entry(compose);
10103 if (ac->set_autocc && ac->auto_cc)
10104 compose_entry_append(compose, ac->auto_cc,
10105 COMPOSE_CC, PREF_ACCOUNT);
10106 if (ac->set_autobcc && ac->auto_bcc)
10107 compose_entry_append(compose, ac->auto_bcc,
10108 COMPOSE_BCC, PREF_ACCOUNT);
10109 if (ac->set_autoreplyto && ac->auto_replyto)
10110 compose_entry_append(compose, ac->auto_replyto,
10111 COMPOSE_REPLYTO, PREF_ACCOUNT);
10113 for (list = saved_list; list; list = list->next) {
10114 state = (HeaderEntryState *) list->data;
10116 compose_add_header_entry(compose, state->header,
10117 state->entry, state->type);
10119 g_free(state->header);
10120 g_free(state->entry);
10121 g_free(state);
10123 g_slist_free(saved_list);
10125 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10126 (ac->protocol == A_NNTP) ?
10127 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10130 /* Set message save folder */
10131 compose_set_save_to(compose, NULL);
10132 if (compose->folder && compose->folder->prefs && compose->folder->prefs->save_copy_to_folder) {
10133 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10134 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10135 folderidentifier = folder_item_get_identifier(compose->folder);
10136 compose_set_save_to(compose, folderidentifier);
10137 g_free(folderidentifier);
10138 } else if (account_get_special_folder(compose->account, F_OUTBOX)) {
10139 if (compose->account->set_sent_folder || prefs_common.savemsg)
10140 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10141 else
10142 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE);
10143 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10144 folderidentifier = folder_item_get_identifier(account_get_special_folder
10145 (compose->account, F_OUTBOX));
10146 compose_set_save_to(compose, folderidentifier);
10147 g_free(folderidentifier);
10151 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10152 GtkTreeViewColumn *column, Compose *compose)
10154 compose_attach_property(NULL, compose);
10157 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10158 gpointer data)
10160 Compose *compose = (Compose *)data;
10161 GtkTreeSelection *attach_selection;
10162 gint attach_nr_selected;
10163 GtkTreePath *path;
10165 if (!event || compose->redirect_filename != NULL)
10166 return FALSE;
10168 if (event->button == 3) {
10169 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10170 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10172 /* If no rows, or just one row is selected, right-click should
10173 * open menu relevant to the row being right-clicked on. We
10174 * achieve that by selecting the clicked row first. If more
10175 * than one row is selected, we shouldn't modify the selection,
10176 * as user may want to remove selected rows (attachments). */
10177 if (attach_nr_selected < 2) {
10178 gtk_tree_selection_unselect_all(attach_selection);
10179 attach_nr_selected = 0;
10180 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10181 event->x, event->y, &path, NULL, NULL, NULL);
10182 if (path != NULL) {
10183 gtk_tree_selection_select_path(attach_selection, path);
10184 gtk_tree_path_free(path);
10185 attach_nr_selected++;
10189 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10190 /* Properties menu item makes no sense with more than one row
10191 * selected, the properties dialog can only edit one attachment. */
10192 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10194 gtk_menu_popup_at_pointer(GTK_MENU(compose->popupmenu), NULL);
10196 return TRUE;
10199 return FALSE;
10202 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10203 gpointer data)
10205 Compose *compose = (Compose *)data;
10207 if (!event) return FALSE;
10209 switch (event->keyval) {
10210 case GDK_KEY_Delete:
10211 compose_attach_remove_selected(NULL, compose);
10212 break;
10214 return FALSE;
10217 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10219 toolbar_comp_set_sensitive(compose, allow);
10220 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10221 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10222 #if USE_ENCHANT
10223 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10224 #endif
10225 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10226 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10227 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10229 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10233 static void compose_send_cb(GtkAction *action, gpointer data)
10235 Compose *compose = (Compose *)data;
10237 #ifdef G_OS_UNIX
10238 if (compose->exteditor_tag != -1) {
10239 debug_print("ignoring send: external editor still open\n");
10240 return;
10242 #endif
10243 if (prefs_common.work_offline &&
10244 !inc_offline_should_override(TRUE,
10245 _("Claws Mail needs network access in order "
10246 "to send this email.")))
10247 return;
10249 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10250 g_source_remove(compose->draft_timeout_tag);
10251 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10254 compose_send(compose);
10257 static void compose_send_later_cb(GtkAction *action, gpointer data)
10259 Compose *compose = (Compose *)data;
10260 ComposeQueueResult val;
10262 inc_lock();
10263 compose_allow_user_actions(compose, FALSE);
10264 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10265 compose_allow_user_actions(compose, TRUE);
10266 inc_unlock();
10268 if (val == COMPOSE_QUEUE_SUCCESS) {
10269 compose_close(compose);
10270 } else {
10271 _display_queue_error(val);
10274 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10277 #define DRAFTED_AT_EXIT "drafted_at_exit"
10278 static void compose_register_draft(MsgInfo *info)
10280 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10281 DRAFTED_AT_EXIT, NULL);
10282 FILE *fp = claws_fopen(filepath, "ab");
10284 if (fp) {
10285 gchar *name = folder_item_get_identifier(info->folder);
10286 fprintf(fp, "%s\t%d\n", name, info->msgnum);
10287 g_free(name);
10288 claws_fclose(fp);
10291 g_free(filepath);
10294 gboolean compose_draft (gpointer data, guint action)
10296 Compose *compose = (Compose *)data;
10297 FolderItem *draft;
10298 FolderItemPrefs *prefs;
10299 gchar *tmp;
10300 gchar *sheaders;
10301 gint msgnum;
10302 MsgFlags flag = {0, 0};
10303 static gboolean lock = FALSE;
10304 MsgInfo *newmsginfo;
10305 FILE *fp;
10306 gboolean target_locked = FALSE;
10307 gboolean err = FALSE;
10308 gint filemode = 0;
10310 if (lock) return FALSE;
10312 if (compose->sending)
10313 return TRUE;
10315 draft = account_get_special_folder(compose->account, F_DRAFT);
10316 cm_return_val_if_fail(draft != NULL, FALSE);
10318 if (!g_mutex_trylock(&compose->mutex)) {
10319 /* we don't want to lock the mutex once it's available,
10320 * because as the only other part of compose.c locking
10321 * it is compose_close - which means once unlocked,
10322 * the compose struct will be freed */
10323 debug_print("couldn't lock mutex, probably sending\n");
10324 return FALSE;
10327 lock = TRUE;
10329 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10330 G_DIR_SEPARATOR, compose);
10331 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10332 FILE_OP_ERROR(tmp, "claws_fopen");
10333 goto warn_err;
10336 /* chmod for security unless folder chmod is set */
10337 prefs = draft->prefs;
10338 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) {
10339 filemode = prefs->folder_chmod;
10340 if (filemode & S_IRGRP) filemode |= S_IWGRP;
10341 if (filemode & S_IROTH) filemode |= S_IWOTH;
10342 if (chmod(tmp, filemode) < 0)
10343 FILE_OP_ERROR(tmp, "chmod");
10344 } else if (change_file_mode_rw(fp, tmp) < 0) {
10345 FILE_OP_ERROR(tmp, "chmod");
10346 g_warning("can't change file mode");
10349 /* Save draft infos */
10350 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10351 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10353 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10354 gchar *savefolderid;
10356 savefolderid = compose_get_save_to(compose);
10357 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10358 g_free(savefolderid);
10360 if (compose->return_receipt) {
10361 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10363 if (compose->privacy_system) {
10364 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10365 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10366 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10369 /* Message-ID of message replying to */
10370 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10371 gchar *folderid = NULL;
10373 if (compose->replyinfo->folder)
10374 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10375 if (folderid == NULL)
10376 folderid = g_strdup("NULL");
10378 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10379 g_free(folderid);
10381 /* Message-ID of message forwarding to */
10382 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10383 gchar *folderid = NULL;
10385 if (compose->fwdinfo->folder)
10386 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10387 if (folderid == NULL)
10388 folderid = g_strdup("NULL");
10390 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10391 g_free(folderid);
10394 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10395 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10397 sheaders = compose_get_manual_headers_info(compose);
10398 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10399 g_free(sheaders);
10401 /* end of headers */
10402 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10404 if (err) {
10405 claws_fclose(fp);
10406 goto warn_err;
10409 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10410 claws_fclose(fp);
10411 goto warn_err;
10413 if (claws_safe_fclose(fp) == EOF) {
10414 goto warn_err;
10417 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10418 if (compose->targetinfo) {
10419 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10420 if (target_locked)
10421 flag.perm_flags |= MSG_LOCKED;
10423 flag.tmp_flags = MSG_DRAFT;
10425 folder_item_scan(draft);
10426 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10427 MsgInfo *tmpinfo = NULL;
10428 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10429 if (compose->msgid) {
10430 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10432 if (tmpinfo) {
10433 msgnum = tmpinfo->msgnum;
10434 procmsg_msginfo_free(&tmpinfo);
10435 debug_print("got draft msgnum %d from scanning\n", msgnum);
10436 } else {
10437 debug_print("didn't get draft msgnum after scanning\n");
10439 } else {
10440 debug_print("got draft msgnum %d from adding\n", msgnum);
10442 if (msgnum < 0) {
10443 warn_err:
10444 claws_unlink(tmp);
10445 g_free(tmp);
10446 if (action != COMPOSE_AUTO_SAVE) {
10447 if (action != COMPOSE_DRAFT_FOR_EXIT)
10448 alertpanel_error(_("Could not save draft."));
10449 else {
10450 AlertValue val;
10451 gtkut_window_popup(compose->window);
10452 val = alertpanel_full(_("Could not save draft"),
10453 _("Could not save draft.\n"
10454 "Do you want to cancel exit or discard this email?"),
10455 NULL, _("_Cancel exit"), NULL, _("_Discard email"),
10456 NULL, NULL, ALERTFOCUS_FIRST, FALSE, NULL, ALERT_QUESTION);
10457 if (val == G_ALERTALTERNATE) {
10458 lock = FALSE;
10459 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10460 compose_close(compose);
10461 return TRUE;
10462 } else {
10463 lock = FALSE;
10464 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10465 return FALSE;
10469 goto unlock;
10471 g_free(tmp);
10473 if (compose->mode == COMPOSE_REEDIT) {
10474 compose_remove_reedit_target(compose, TRUE);
10477 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10479 if (newmsginfo) {
10480 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10481 if (target_locked)
10482 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10483 else
10484 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10485 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10486 procmsg_msginfo_set_flags(newmsginfo, 0,
10487 MSG_HAS_ATTACHMENT);
10489 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10490 compose_register_draft(newmsginfo);
10492 procmsg_msginfo_free(&newmsginfo);
10495 folder_item_scan(draft);
10497 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10498 lock = FALSE;
10499 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10500 compose_close(compose);
10501 return TRUE;
10502 } else {
10503 #ifdef G_OS_WIN32
10504 GFile *f;
10505 GFileInfo *fi;
10506 GTimeVal tv;
10507 GError *error = NULL;
10508 #else
10509 GStatBuf s;
10510 #endif
10511 gchar *path;
10512 goffset size, mtime;
10514 path = folder_item_fetch_msg(draft, msgnum);
10515 if (path == NULL) {
10516 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10517 goto unlock;
10519 #ifdef G_OS_WIN32
10520 f = g_file_new_for_path(path);
10521 fi = g_file_query_info(f, "standard::size,time::modified",
10522 G_FILE_QUERY_INFO_NONE, NULL, &error);
10523 if (error != NULL) {
10524 debug_print("couldn't query file info for '%s': %s\n",
10525 path, error->message);
10526 g_error_free(error);
10527 g_free(path);
10528 g_object_unref(f);
10529 goto unlock;
10531 size = g_file_info_get_size(fi);
10532 g_file_info_get_modification_time(fi, &tv);
10533 mtime = tv.tv_sec;
10534 g_object_unref(fi);
10535 g_object_unref(f);
10536 #else
10537 if (g_stat(path, &s) < 0) {
10538 FILE_OP_ERROR(path, "stat");
10539 g_free(path);
10540 goto unlock;
10542 size = s.st_size;
10543 mtime = s.st_mtime;
10544 #endif
10545 g_free(path);
10547 procmsg_msginfo_free(&(compose->targetinfo));
10548 compose->targetinfo = procmsg_msginfo_new();
10549 compose->targetinfo->msgnum = msgnum;
10550 compose->targetinfo->size = size;
10551 compose->targetinfo->mtime = mtime;
10552 compose->targetinfo->folder = draft;
10553 if (target_locked)
10554 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10555 compose->mode = COMPOSE_REEDIT;
10557 if (action == COMPOSE_AUTO_SAVE) {
10558 compose->modified = FALSE;
10559 compose->autosaved_draft = compose->targetinfo;
10561 compose_set_title(compose);
10563 unlock:
10564 lock = FALSE;
10565 g_mutex_unlock(&compose->mutex);
10566 return TRUE;
10569 void compose_clear_exit_drafts(void)
10571 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10572 DRAFTED_AT_EXIT, NULL);
10573 if (is_file_exist(filepath))
10574 claws_unlink(filepath);
10576 g_free(filepath);
10579 void compose_reopen_exit_drafts(void)
10581 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10582 DRAFTED_AT_EXIT, NULL);
10583 FILE *fp = claws_fopen(filepath, "rb");
10584 gchar buf[1024];
10586 if (fp) {
10587 while (claws_fgets(buf, sizeof(buf), fp)) {
10588 gchar **parts = g_strsplit(buf, "\t", 2);
10589 const gchar *folder = parts[0];
10590 int msgnum = parts[1] ? atoi(parts[1]):-1;
10592 if (folder && *folder && msgnum > -1) {
10593 FolderItem *item = folder_find_item_from_identifier(folder);
10594 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10595 if (info)
10596 compose_reedit(info, FALSE);
10598 g_strfreev(parts);
10600 claws_fclose(fp);
10602 g_free(filepath);
10603 compose_clear_exit_drafts();
10606 static void compose_save_cb(GtkAction *action, gpointer data)
10608 Compose *compose = (Compose *)data;
10609 compose_draft(compose, COMPOSE_KEEP_EDITING);
10610 compose->rmode = COMPOSE_REEDIT;
10611 compose->modified = FALSE;
10612 compose_set_title(compose);
10615 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10617 if (compose && file_list) {
10618 GList *tmp;
10620 for ( tmp = file_list; tmp; tmp = tmp->next) {
10621 gchar *file = (gchar *) tmp->data;
10622 gchar *utf8_filename = conv_filename_to_utf8(file);
10623 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10624 compose_changed_cb(NULL, compose);
10625 if (free_data) {
10626 g_free(file);
10627 tmp->data = NULL;
10629 g_free(utf8_filename);
10634 static void compose_attach_cb(GtkAction *action, gpointer data)
10636 Compose *compose = (Compose *)data;
10637 GList *file_list;
10639 if (compose->redirect_filename != NULL)
10640 return;
10642 /* Set focus_window properly, in case we were called via popup menu,
10643 * which unsets it (via focus_out_event callback on compose window). */
10644 manage_window_focus_in(compose->window, NULL, NULL);
10646 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10648 if (file_list) {
10649 compose_attach_from_list(compose, file_list, TRUE);
10650 g_list_free(file_list);
10654 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10656 Compose *compose = (Compose *)data;
10657 GList *file_list;
10658 gint files_inserted = 0;
10660 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10662 if (file_list) {
10663 GList *tmp;
10665 for ( tmp = file_list; tmp; tmp = tmp->next) {
10666 gchar *file = (gchar *) tmp->data;
10667 gchar *filedup = g_strdup(file);
10668 gchar *shortfile = g_path_get_basename(filedup);
10669 ComposeInsertResult res;
10670 /* insert the file if the file is short or if the user confirmed that
10671 he/she wants to insert the large file */
10672 res = compose_insert_file(compose, file);
10673 if (res == COMPOSE_INSERT_READ_ERROR) {
10674 alertpanel_error(_("File '%s' could not be read."), shortfile);
10675 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10676 alertpanel_error(_("File '%s' contained invalid characters\n"
10677 "for the current encoding, insertion may be incorrect."),
10678 shortfile);
10679 } else if (res == COMPOSE_INSERT_SUCCESS)
10680 files_inserted++;
10682 g_free(shortfile);
10683 g_free(filedup);
10684 g_free(file);
10686 g_list_free(file_list);
10689 #ifdef USE_ENCHANT
10690 if (files_inserted > 0 && compose->gtkaspell &&
10691 compose->gtkaspell->check_while_typing)
10692 gtkaspell_highlight_all(compose->gtkaspell);
10693 #endif
10696 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10698 Compose *compose = (Compose *)data;
10700 compose_insert_sig(compose, FALSE);
10703 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10705 Compose *compose = (Compose *)data;
10707 compose_insert_sig(compose, TRUE);
10710 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10711 gpointer data)
10713 gint x, y;
10714 Compose *compose = (Compose *)data;
10716 gtk_window_get_position(GTK_WINDOW(widget), &x, &y);
10717 if (!compose->batch) {
10718 prefs_common.compose_x = x;
10719 prefs_common.compose_y = y;
10721 if (compose->sending || compose->updating)
10722 return TRUE;
10723 compose_close_cb(NULL, compose);
10724 return TRUE;
10727 void compose_close_toolbar(Compose *compose)
10729 compose_close_cb(NULL, compose);
10732 static void compose_close_cb(GtkAction *action, gpointer data)
10734 Compose *compose = (Compose *)data;
10735 AlertValue val;
10737 if (compose->exteditor_tag != -1) {
10738 if (!compose_ext_editor_kill(compose))
10739 return;
10742 if (compose->modified) {
10743 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10744 if (!g_mutex_trylock(&compose->mutex)) {
10745 /* we don't want to lock the mutex once it's available,
10746 * because as the only other part of compose.c locking
10747 * it is compose_close - which means once unlocked,
10748 * the compose struct will be freed */
10749 debug_print("couldn't lock mutex, probably sending\n");
10750 return;
10752 if (!reedit || (compose->folder != NULL && compose->folder->stype == F_DRAFT)) {
10753 val = alertpanel(_("Discard message"),
10754 _("This message has been modified. Discard it?"),
10755 NULL, _("_Discard"), NULL, _("_Save to Drafts"), NULL, _("_Cancel"),
10756 ALERTFOCUS_FIRST);
10757 } else {
10758 val = alertpanel(_("Save changes"),
10759 _("This message has been modified. Save the latest changes?"),
10760 NULL, _("_Don't save"), NULL, _("_Save to Drafts"), NULL, _("_Cancel"),
10761 ALERTFOCUS_SECOND);
10763 g_mutex_unlock(&compose->mutex);
10764 switch (val) {
10765 case G_ALERTDEFAULT:
10766 if (compose_can_autosave(compose) && !reedit)
10767 compose_remove_draft(compose);
10768 break;
10769 case G_ALERTALTERNATE:
10770 compose_draft(data, COMPOSE_QUIT_EDITING);
10771 return;
10772 default:
10773 return;
10777 compose_close(compose);
10780 static void compose_print_cb(GtkAction *action, gpointer data)
10782 Compose *compose = (Compose *) data;
10784 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10785 if (compose->targetinfo)
10786 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10789 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10791 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10792 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10793 Compose *compose = (Compose *) data;
10795 if (active)
10796 compose->out_encoding = (CharSet)value;
10799 static void compose_address_cb(GtkAction *action, gpointer data)
10801 Compose *compose = (Compose *)data;
10803 #ifndef USE_ALT_ADDRBOOK
10804 addressbook_open(compose);
10805 #else
10806 GError* error = NULL;
10807 addressbook_connect_signals(compose);
10808 addressbook_dbus_open(TRUE, &error);
10809 if (error) {
10810 g_warning("%s", error->message);
10811 g_error_free(error);
10813 #endif
10816 static void about_show_cb(GtkAction *action, gpointer data)
10818 about_show();
10821 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10823 Compose *compose = (Compose *)data;
10824 Template *tmpl;
10825 gchar *msg;
10826 AlertValue val;
10828 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10829 cm_return_if_fail(tmpl != NULL);
10831 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10832 tmpl->name);
10833 val = alertpanel(_("Apply template"), msg,
10834 NULL, _("_Replace"), NULL, _("_Insert"), NULL, _("_Cancel"),
10835 ALERTFOCUS_FIRST);
10836 g_free(msg);
10838 if (val == G_ALERTDEFAULT)
10839 compose_template_apply(compose, tmpl, TRUE);
10840 else if (val == G_ALERTALTERNATE)
10841 compose_template_apply(compose, tmpl, FALSE);
10844 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10846 Compose *compose = (Compose *)data;
10848 if (compose->exteditor_tag != -1) {
10849 debug_print("ignoring open external editor: external editor still open\n");
10850 return;
10852 compose_exec_ext_editor(compose);
10855 static void compose_undo_cb(GtkAction *action, gpointer data)
10857 Compose *compose = (Compose *)data;
10858 gboolean prev_autowrap = compose->autowrap;
10860 compose->autowrap = FALSE;
10861 undo_undo(compose->undostruct);
10862 compose->autowrap = prev_autowrap;
10865 static void compose_redo_cb(GtkAction *action, gpointer data)
10867 Compose *compose = (Compose *)data;
10868 gboolean prev_autowrap = compose->autowrap;
10870 compose->autowrap = FALSE;
10871 undo_redo(compose->undostruct);
10872 compose->autowrap = prev_autowrap;
10875 static void entry_cut_clipboard(GtkWidget *entry)
10877 if (GTK_IS_EDITABLE(entry))
10878 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10879 else if (GTK_IS_TEXT_VIEW(entry))
10880 gtk_text_buffer_cut_clipboard(
10881 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10882 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10883 TRUE);
10886 static void entry_copy_clipboard(GtkWidget *entry)
10888 if (GTK_IS_EDITABLE(entry))
10889 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10890 else if (GTK_IS_TEXT_VIEW(entry))
10891 gtk_text_buffer_copy_clipboard(
10892 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10893 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10896 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10897 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10899 if (GTK_IS_TEXT_VIEW(entry)) {
10900 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10901 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10902 GtkTextIter start_iter, end_iter;
10903 gint start, end;
10904 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10906 if (contents == NULL)
10907 return;
10909 /* we shouldn't delete the selection when middle-click-pasting, or we
10910 * can't mid-click-paste our own selection */
10911 if (clip != GDK_SELECTION_PRIMARY) {
10912 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10913 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10916 if (insert_place == NULL) {
10917 /* if insert_place isn't specified, insert at the cursor.
10918 * used for Ctrl-V pasting */
10919 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10920 start = gtk_text_iter_get_offset(&start_iter);
10921 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10922 } else {
10923 /* if insert_place is specified, paste here.
10924 * used for mid-click-pasting */
10925 start = gtk_text_iter_get_offset(insert_place);
10926 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10927 if (prefs_common.primary_paste_unselects)
10928 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10931 if (!wrap) {
10932 /* paste unwrapped: mark the paste so it's not wrapped later */
10933 end = start + strlen(contents);
10934 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10935 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10936 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10937 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10938 /* rewrap paragraph now (after a mid-click-paste) */
10939 mark_start = gtk_text_buffer_get_insert(buffer);
10940 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10941 gtk_text_iter_backward_char(&start_iter);
10942 compose_beautify_paragraph(compose, &start_iter, TRUE);
10944 } else if (GTK_IS_EDITABLE(entry))
10945 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10947 compose->modified = TRUE;
10950 static void entry_allsel(GtkWidget *entry)
10952 if (GTK_IS_EDITABLE(entry))
10953 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10954 else if (GTK_IS_TEXT_VIEW(entry)) {
10955 GtkTextIter startiter, enditer;
10956 GtkTextBuffer *textbuf;
10958 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10959 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10960 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10962 gtk_text_buffer_move_mark_by_name(textbuf,
10963 "selection_bound", &startiter);
10964 gtk_text_buffer_move_mark_by_name(textbuf,
10965 "insert", &enditer);
10969 static void compose_cut_cb(GtkAction *action, gpointer data)
10971 Compose *compose = (Compose *)data;
10972 if (compose->focused_editable
10973 #ifndef GENERIC_UMPC
10974 && gtk_widget_has_focus(compose->focused_editable)
10975 #endif
10977 entry_cut_clipboard(compose->focused_editable);
10980 static void compose_copy_cb(GtkAction *action, gpointer data)
10982 Compose *compose = (Compose *)data;
10983 if (compose->focused_editable
10984 #ifndef GENERIC_UMPC
10985 && gtk_widget_has_focus(compose->focused_editable)
10986 #endif
10988 entry_copy_clipboard(compose->focused_editable);
10991 static void compose_paste_cb(GtkAction *action, gpointer data)
10993 Compose *compose = (Compose *)data;
10994 gint prev_autowrap;
10995 GtkTextBuffer *buffer;
10996 BLOCK_WRAP();
10997 if (compose->focused_editable
10998 #ifndef GENERIC_UMPC
10999 && gtk_widget_has_focus(compose->focused_editable)
11000 #endif
11002 entry_paste_clipboard(compose, compose->focused_editable,
11003 prefs_common.linewrap_pastes,
11004 GDK_SELECTION_CLIPBOARD, NULL);
11005 UNBLOCK_WRAP();
11007 #ifdef USE_ENCHANT
11008 if (
11009 #ifndef GENERIC_UMPC
11010 gtk_widget_has_focus(compose->text) &&
11011 #endif
11012 compose->gtkaspell &&
11013 compose->gtkaspell->check_while_typing)
11014 gtkaspell_highlight_all(compose->gtkaspell);
11015 #endif
11018 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11020 Compose *compose = (Compose *)data;
11021 gint wrap_quote = prefs_common.linewrap_quote;
11022 if (compose->focused_editable
11023 #ifndef GENERIC_UMPC
11024 && gtk_widget_has_focus(compose->focused_editable)
11025 #endif
11027 /* let text_insert() (called directly or at a later time
11028 * after the gtk_editable_paste_clipboard) know that
11029 * text is to be inserted as a quotation. implemented
11030 * by using a simple refcount... */
11031 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11032 G_OBJECT(compose->focused_editable),
11033 "paste_as_quotation"));
11034 g_object_set_data(G_OBJECT(compose->focused_editable),
11035 "paste_as_quotation",
11036 GINT_TO_POINTER(paste_as_quotation + 1));
11037 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11038 entry_paste_clipboard(compose, compose->focused_editable,
11039 prefs_common.linewrap_pastes,
11040 GDK_SELECTION_CLIPBOARD, NULL);
11041 prefs_common.linewrap_quote = wrap_quote;
11045 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11047 Compose *compose = (Compose *)data;
11048 gint prev_autowrap;
11049 GtkTextBuffer *buffer;
11050 BLOCK_WRAP();
11051 if (compose->focused_editable
11052 #ifndef GENERIC_UMPC
11053 && gtk_widget_has_focus(compose->focused_editable)
11054 #endif
11056 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11057 GDK_SELECTION_CLIPBOARD, NULL);
11058 UNBLOCK_WRAP();
11060 #ifdef USE_ENCHANT
11061 if (
11062 #ifndef GENERIC_UMPC
11063 gtk_widget_has_focus(compose->text) &&
11064 #endif
11065 compose->gtkaspell &&
11066 compose->gtkaspell->check_while_typing)
11067 gtkaspell_highlight_all(compose->gtkaspell);
11068 #endif
11071 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11073 Compose *compose = (Compose *)data;
11074 gint prev_autowrap;
11075 GtkTextBuffer *buffer;
11076 BLOCK_WRAP();
11077 if (compose->focused_editable
11078 #ifndef GENERIC_UMPC
11079 && gtk_widget_has_focus(compose->focused_editable)
11080 #endif
11082 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11083 GDK_SELECTION_CLIPBOARD, NULL);
11084 UNBLOCK_WRAP();
11086 #ifdef USE_ENCHANT
11087 if (
11088 #ifndef GENERIC_UMPC
11089 gtk_widget_has_focus(compose->text) &&
11090 #endif
11091 compose->gtkaspell &&
11092 compose->gtkaspell->check_while_typing)
11093 gtkaspell_highlight_all(compose->gtkaspell);
11094 #endif
11097 static void compose_allsel_cb(GtkAction *action, gpointer data)
11099 Compose *compose = (Compose *)data;
11100 if (compose->focused_editable
11101 #ifndef GENERIC_UMPC
11102 && gtk_widget_has_focus(compose->focused_editable)
11103 #endif
11105 entry_allsel(compose->focused_editable);
11108 static void textview_move_beginning_of_line (GtkTextView *text)
11110 GtkTextBuffer *buffer;
11111 GtkTextMark *mark;
11112 GtkTextIter ins;
11114 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11116 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11117 mark = gtk_text_buffer_get_insert(buffer);
11118 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11119 gtk_text_iter_set_line_offset(&ins, 0);
11120 gtk_text_buffer_place_cursor(buffer, &ins);
11123 static void textview_move_forward_character (GtkTextView *text)
11125 GtkTextBuffer *buffer;
11126 GtkTextMark *mark;
11127 GtkTextIter ins;
11129 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11131 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11132 mark = gtk_text_buffer_get_insert(buffer);
11133 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11134 if (gtk_text_iter_forward_cursor_position(&ins))
11135 gtk_text_buffer_place_cursor(buffer, &ins);
11138 static void textview_move_backward_character (GtkTextView *text)
11140 GtkTextBuffer *buffer;
11141 GtkTextMark *mark;
11142 GtkTextIter ins;
11144 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11146 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11147 mark = gtk_text_buffer_get_insert(buffer);
11148 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11149 if (gtk_text_iter_backward_cursor_position(&ins))
11150 gtk_text_buffer_place_cursor(buffer, &ins);
11153 static void textview_move_forward_word (GtkTextView *text)
11155 GtkTextBuffer *buffer;
11156 GtkTextMark *mark;
11157 GtkTextIter ins;
11158 gint count;
11160 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11162 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11163 mark = gtk_text_buffer_get_insert(buffer);
11164 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11165 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11166 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11167 gtk_text_iter_backward_word_start(&ins);
11168 gtk_text_buffer_place_cursor(buffer, &ins);
11172 static void textview_move_backward_word (GtkTextView *text)
11174 GtkTextBuffer *buffer;
11175 GtkTextMark *mark;
11176 GtkTextIter ins;
11178 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11180 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11181 mark = gtk_text_buffer_get_insert(buffer);
11182 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11183 if (gtk_text_iter_backward_word_starts(&ins, 1))
11184 gtk_text_buffer_place_cursor(buffer, &ins);
11187 static void textview_move_end_of_line (GtkTextView *text)
11189 GtkTextBuffer *buffer;
11190 GtkTextMark *mark;
11191 GtkTextIter ins;
11193 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11195 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11196 mark = gtk_text_buffer_get_insert(buffer);
11197 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11198 if (gtk_text_iter_forward_to_line_end(&ins))
11199 gtk_text_buffer_place_cursor(buffer, &ins);
11202 static void textview_move_next_line (GtkTextView *text)
11204 GtkTextBuffer *buffer;
11205 GtkTextMark *mark;
11206 GtkTextIter ins;
11207 gint offset;
11209 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11211 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11212 mark = gtk_text_buffer_get_insert(buffer);
11213 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11214 offset = gtk_text_iter_get_line_offset(&ins);
11215 if (gtk_text_iter_forward_line(&ins)) {
11216 gtk_text_iter_set_line_offset(&ins, offset);
11217 gtk_text_buffer_place_cursor(buffer, &ins);
11221 static void textview_move_previous_line (GtkTextView *text)
11223 GtkTextBuffer *buffer;
11224 GtkTextMark *mark;
11225 GtkTextIter ins;
11226 gint offset;
11228 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11230 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11231 mark = gtk_text_buffer_get_insert(buffer);
11232 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11233 offset = gtk_text_iter_get_line_offset(&ins);
11234 if (gtk_text_iter_backward_line(&ins)) {
11235 gtk_text_iter_set_line_offset(&ins, offset);
11236 gtk_text_buffer_place_cursor(buffer, &ins);
11240 static void textview_delete_forward_character (GtkTextView *text)
11242 GtkTextBuffer *buffer;
11243 GtkTextMark *mark;
11244 GtkTextIter ins, end_iter;
11246 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11248 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11249 mark = gtk_text_buffer_get_insert(buffer);
11250 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11251 end_iter = ins;
11252 if (gtk_text_iter_forward_char(&end_iter)) {
11253 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11257 static void textview_delete_backward_character (GtkTextView *text)
11259 GtkTextBuffer *buffer;
11260 GtkTextMark *mark;
11261 GtkTextIter ins, end_iter;
11263 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11265 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11266 mark = gtk_text_buffer_get_insert(buffer);
11267 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11268 end_iter = ins;
11269 if (gtk_text_iter_backward_char(&end_iter)) {
11270 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11274 static void textview_delete_forward_word (GtkTextView *text)
11276 GtkTextBuffer *buffer;
11277 GtkTextMark *mark;
11278 GtkTextIter ins, end_iter;
11280 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11282 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11283 mark = gtk_text_buffer_get_insert(buffer);
11284 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11285 end_iter = ins;
11286 if (gtk_text_iter_forward_word_end(&end_iter)) {
11287 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11291 static void textview_delete_backward_word (GtkTextView *text)
11293 GtkTextBuffer *buffer;
11294 GtkTextMark *mark;
11295 GtkTextIter ins, end_iter;
11297 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11299 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11300 mark = gtk_text_buffer_get_insert(buffer);
11301 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11302 end_iter = ins;
11303 if (gtk_text_iter_backward_word_start(&end_iter)) {
11304 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11308 static void textview_delete_line (GtkTextView *text)
11310 GtkTextBuffer *buffer;
11311 GtkTextMark *mark;
11312 GtkTextIter ins, start_iter, end_iter;
11314 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11316 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11317 mark = gtk_text_buffer_get_insert(buffer);
11318 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11320 start_iter = ins;
11321 gtk_text_iter_set_line_offset(&start_iter, 0);
11323 end_iter = ins;
11324 if (gtk_text_iter_ends_line(&end_iter)){
11325 if (!gtk_text_iter_forward_char(&end_iter))
11326 gtk_text_iter_backward_char(&start_iter);
11328 else
11329 gtk_text_iter_forward_to_line_end(&end_iter);
11330 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11333 static void textview_delete_to_line_end (GtkTextView *text)
11335 GtkTextBuffer *buffer;
11336 GtkTextMark *mark;
11337 GtkTextIter ins, end_iter;
11339 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11341 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11342 mark = gtk_text_buffer_get_insert(buffer);
11343 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11344 end_iter = ins;
11345 if (gtk_text_iter_ends_line(&end_iter))
11346 gtk_text_iter_forward_char(&end_iter);
11347 else
11348 gtk_text_iter_forward_to_line_end(&end_iter);
11349 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11352 #define DO_ACTION(name, act) { \
11353 if(!strcmp(name, a_name)) { \
11354 return act; \
11357 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11359 const gchar *a_name = gtk_action_get_name(action);
11360 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11361 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11362 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11363 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11364 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11365 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11366 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11367 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11368 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11369 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11370 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11371 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11372 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11373 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11374 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11377 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11379 Compose *compose = (Compose *)data;
11380 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11381 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11383 action = compose_call_advanced_action_from_path(gaction);
11385 static struct {
11386 void (*do_action) (GtkTextView *text);
11387 } action_table[] = {
11388 {textview_move_beginning_of_line},
11389 {textview_move_forward_character},
11390 {textview_move_backward_character},
11391 {textview_move_forward_word},
11392 {textview_move_backward_word},
11393 {textview_move_end_of_line},
11394 {textview_move_next_line},
11395 {textview_move_previous_line},
11396 {textview_delete_forward_character},
11397 {textview_delete_backward_character},
11398 {textview_delete_forward_word},
11399 {textview_delete_backward_word},
11400 {textview_delete_line},
11401 {textview_delete_to_line_end}
11404 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11406 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11407 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11408 if (action_table[action].do_action)
11409 action_table[action].do_action(text);
11410 else
11411 g_warning("not implemented yet");
11415 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11417 GtkAllocation allocation;
11418 GtkWidget *parent;
11419 gchar *str = NULL;
11421 if (GTK_IS_EDITABLE(widget)) {
11422 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11423 gtk_editable_set_position(GTK_EDITABLE(widget),
11424 strlen(str));
11425 g_free(str);
11426 if ((parent = gtk_widget_get_parent(widget))
11427 && (parent = gtk_widget_get_parent(parent))
11428 && (parent = gtk_widget_get_parent(parent))) {
11429 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11430 gtk_widget_get_allocation(widget, &allocation);
11431 gint y = allocation.y;
11432 gint height = allocation.height;
11433 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11434 (GTK_SCROLLED_WINDOW(parent));
11436 gfloat value = gtk_adjustment_get_value(shown);
11437 gfloat upper = gtk_adjustment_get_upper(shown);
11438 gfloat page_size = gtk_adjustment_get_page_size(shown);
11439 if (y < (int)value) {
11440 gtk_adjustment_set_value(shown, y - 1);
11442 if ((y + height) > ((int)value + (int)page_size)) {
11443 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11444 gtk_adjustment_set_value(shown,
11445 y + height - (int)page_size - 1);
11446 } else {
11447 gtk_adjustment_set_value(shown,
11448 (int)upper - (int)page_size - 1);
11455 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11456 compose->focused_editable = widget;
11458 #ifdef GENERIC_UMPC
11459 if (GTK_IS_TEXT_VIEW(widget)
11460 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11461 g_object_ref(compose->notebook);
11462 g_object_ref(compose->edit_vbox);
11463 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11464 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11465 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11466 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11467 g_object_unref(compose->notebook);
11468 g_object_unref(compose->edit_vbox);
11469 g_signal_handlers_block_by_func(G_OBJECT(widget),
11470 G_CALLBACK(compose_grab_focus_cb),
11471 compose);
11472 gtk_widget_grab_focus(widget);
11473 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11474 G_CALLBACK(compose_grab_focus_cb),
11475 compose);
11476 } else if (!GTK_IS_TEXT_VIEW(widget)
11477 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11478 g_object_ref(compose->notebook);
11479 g_object_ref(compose->edit_vbox);
11480 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11481 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11482 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11483 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11484 g_object_unref(compose->notebook);
11485 g_object_unref(compose->edit_vbox);
11486 g_signal_handlers_block_by_func(G_OBJECT(widget),
11487 G_CALLBACK(compose_grab_focus_cb),
11488 compose);
11489 gtk_widget_grab_focus(widget);
11490 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11491 G_CALLBACK(compose_grab_focus_cb),
11492 compose);
11494 #endif
11497 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11499 compose->modified = TRUE;
11500 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11501 #ifndef GENERIC_UMPC
11502 compose_set_title(compose);
11503 #endif
11506 static void compose_wrap_cb(GtkAction *action, gpointer data)
11508 Compose *compose = (Compose *)data;
11509 compose_beautify_paragraph(compose, NULL, TRUE);
11512 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11514 Compose *compose = (Compose *)data;
11515 compose_wrap_all_full(compose, TRUE);
11518 static void compose_find_cb(GtkAction *action, gpointer data)
11520 Compose *compose = (Compose *)data;
11522 message_search_compose(compose);
11525 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11526 gpointer data)
11528 Compose *compose = (Compose *)data;
11529 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11530 if (compose->autowrap)
11531 compose_wrap_all_full(compose, TRUE);
11532 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11535 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11536 gpointer data)
11538 Compose *compose = (Compose *)data;
11539 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11542 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11544 Compose *compose = (Compose *)data;
11546 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11547 if (compose->toolbar->privacy_sign_btn != NULL)
11548 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11551 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11553 Compose *compose = (Compose *)data;
11555 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11556 if (compose->toolbar->privacy_encrypt_btn != NULL)
11557 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11560 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11562 g_free(compose->privacy_system);
11563 g_free(compose->encdata);
11565 compose->privacy_system = g_strdup(account->default_privacy_system);
11566 compose_update_privacy_system_menu_item(compose, warn);
11569 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11571 if (folder_item != NULL) {
11572 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT &&
11573 privacy_system_can_sign(compose->privacy_system)) {
11574 compose_use_signing(compose,
11575 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11577 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT &&
11578 privacy_system_can_encrypt(compose->privacy_system)) {
11579 compose_use_encryption(compose,
11580 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11585 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11587 Compose *compose = (Compose *)data;
11589 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11590 gtk_widget_show(compose->ruler_hbox);
11591 prefs_common.show_ruler = TRUE;
11592 } else {
11593 gtk_widget_hide(compose->ruler_hbox);
11594 gtk_widget_queue_resize(compose->edit_vbox);
11595 prefs_common.show_ruler = FALSE;
11599 static void compose_attach_drag_received_cb (GtkWidget *widget,
11600 GdkDragContext *context,
11601 gint x,
11602 gint y,
11603 GtkSelectionData *data,
11604 guint info,
11605 guint time,
11606 gpointer user_data)
11608 Compose *compose = (Compose *)user_data;
11609 GList *list, *tmp;
11610 GdkAtom type;
11612 type = gtk_selection_data_get_data_type(data);
11613 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11614 && gtk_drag_get_source_widget(context) !=
11615 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11616 list = uri_list_extract_filenames(
11617 (const gchar *)gtk_selection_data_get_data(data));
11618 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11619 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11620 compose_attach_append
11621 (compose, (const gchar *)tmp->data,
11622 utf8_filename, NULL, NULL);
11623 g_free(utf8_filename);
11625 if (list)
11626 compose_changed_cb(NULL, compose);
11627 list_free_strings_full(list);
11628 } else if (gtk_drag_get_source_widget(context)
11629 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11630 /* comes from our summaryview */
11631 SummaryView * summaryview = NULL;
11632 GSList * list = NULL, *cur = NULL;
11634 if (mainwindow_get_mainwindow())
11635 summaryview = mainwindow_get_mainwindow()->summaryview;
11637 if (summaryview)
11638 list = summary_get_selected_msg_list(summaryview);
11640 for (cur = list; cur; cur = cur->next) {
11641 MsgInfo *msginfo = (MsgInfo *)cur->data;
11642 gchar *file = NULL;
11643 if (msginfo)
11644 file = procmsg_get_message_file_full(msginfo,
11645 TRUE, TRUE);
11646 if (file) {
11647 compose_attach_append(compose, (const gchar *)file,
11648 (const gchar *)file, "message/rfc822", NULL);
11649 g_free(file);
11652 g_slist_free(list);
11656 static gboolean compose_drag_drop(GtkWidget *widget,
11657 GdkDragContext *drag_context,
11658 gint x, gint y,
11659 guint time, gpointer user_data)
11661 /* not handling this signal makes compose_insert_drag_received_cb
11662 * called twice */
11663 return TRUE;
11666 static gboolean completion_set_focus_to_subject
11667 (GtkWidget *widget,
11668 GdkEventKey *event,
11669 Compose *compose)
11671 cm_return_val_if_fail(compose != NULL, FALSE);
11673 /* make backtab move to subject field */
11674 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11675 gtk_widget_grab_focus(compose->subject_entry);
11676 return TRUE;
11678 return FALSE;
11681 static void compose_insert_drag_received_cb (GtkWidget *widget,
11682 GdkDragContext *drag_context,
11683 gint x,
11684 gint y,
11685 GtkSelectionData *data,
11686 guint info,
11687 guint time,
11688 gpointer user_data)
11690 Compose *compose = (Compose *)user_data;
11691 GList *list, *tmp;
11692 GdkAtom type;
11693 guint num_files;
11694 gchar *msg;
11696 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11697 * does not work */
11698 type = gtk_selection_data_get_data_type(data);
11699 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11700 AlertValue val = G_ALERTDEFAULT;
11701 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11703 list = uri_list_extract_filenames(ddata);
11704 num_files = g_list_length(list);
11705 if (list == NULL && strstr(ddata, "://")) {
11706 /* Assume a list of no files, and data has ://, is a remote link */
11707 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11708 gchar *tmpfile = get_tmp_file();
11709 str_write_to_file(tmpdata, tmpfile, TRUE);
11710 g_free(tmpdata);
11711 compose_insert_file(compose, tmpfile);
11712 claws_unlink(tmpfile);
11713 g_free(tmpfile);
11714 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11715 compose_beautify_paragraph(compose, NULL, TRUE);
11716 return;
11718 switch (prefs_common.compose_dnd_mode) {
11719 case COMPOSE_DND_ASK:
11720 msg = g_strdup_printf(
11721 ngettext(
11722 "Do you want to insert the contents of the file "
11723 "into the message body, or attach it to the email?",
11724 "Do you want to insert the contents of the %d files "
11725 "into the message body, or attach them to the email?",
11726 num_files),
11727 num_files);
11728 val = alertpanel_full(_("Insert or attach?"), msg,
11729 NULL, _("_Cancel"), NULL, _("_Insert"), NULL, _("_Attach"),
11730 ALERTFOCUS_SECOND, TRUE, NULL, ALERT_QUESTION);
11731 g_free(msg);
11732 break;
11733 case COMPOSE_DND_INSERT:
11734 val = G_ALERTALTERNATE;
11735 break;
11736 case COMPOSE_DND_ATTACH:
11737 val = G_ALERTOTHER;
11738 break;
11739 default:
11740 /* unexpected case */
11741 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11744 if (val & G_ALERTDISABLE) {
11745 val &= ~G_ALERTDISABLE;
11746 /* remember what action to perform by default, only if we don't click Cancel */
11747 if (val == G_ALERTALTERNATE)
11748 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11749 else if (val == G_ALERTOTHER)
11750 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11753 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11754 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11755 list_free_strings_full(list);
11756 return;
11757 } else if (val == G_ALERTOTHER) {
11758 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11759 list_free_strings_full(list);
11760 return;
11763 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11764 compose_insert_file(compose, (const gchar *)tmp->data);
11766 list_free_strings_full(list);
11767 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11768 return;
11772 static void compose_header_drag_received_cb (GtkWidget *widget,
11773 GdkDragContext *drag_context,
11774 gint x,
11775 gint y,
11776 GtkSelectionData *data,
11777 guint info,
11778 guint time,
11779 gpointer user_data)
11781 GtkEditable *entry = (GtkEditable *)user_data;
11782 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11784 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11785 * does not work */
11787 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11788 gchar *decoded=g_new(gchar, strlen(email));
11789 int start = 0;
11791 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11792 gtk_editable_delete_text(entry, 0, -1);
11793 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11794 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11795 g_free(decoded);
11796 return;
11798 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11801 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11803 Compose *compose = (Compose *)data;
11805 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11806 compose->return_receipt = TRUE;
11807 else
11808 compose->return_receipt = FALSE;
11811 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11813 Compose *compose = (Compose *)data;
11815 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11816 compose->remove_references = TRUE;
11817 else
11818 compose->remove_references = FALSE;
11821 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11822 ComposeHeaderEntry *headerentry)
11824 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11825 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11826 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11827 return FALSE;
11830 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11831 GdkEventKey *event,
11832 ComposeHeaderEntry *headerentry)
11834 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11835 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11836 !(event->state & GDK_MODIFIER_MASK) &&
11837 (event->keyval == GDK_KEY_BackSpace) &&
11838 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11839 gtk_container_remove
11840 (GTK_CONTAINER(headerentry->compose->header_table),
11841 headerentry->combo);
11842 gtk_container_remove
11843 (GTK_CONTAINER(headerentry->compose->header_table),
11844 headerentry->entry);
11845 headerentry->compose->header_list =
11846 g_slist_remove(headerentry->compose->header_list,
11847 headerentry);
11848 g_free(headerentry);
11849 } else if (event->keyval == GDK_KEY_Tab) {
11850 if (headerentry->compose->header_last == headerentry) {
11851 /* Override default next focus, and give it to subject_entry
11852 * instead of notebook tabs
11854 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11855 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11856 return TRUE;
11859 return FALSE;
11862 static gboolean scroll_postpone(gpointer data)
11864 Compose *compose = (Compose *)data;
11866 if (compose->batch)
11867 return FALSE;
11869 GTK_EVENTS_FLUSH();
11870 compose_show_first_last_header(compose, FALSE);
11871 return FALSE;
11874 static void compose_headerentry_changed_cb(GtkWidget *entry,
11875 ComposeHeaderEntry *headerentry)
11877 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11878 compose_create_header_entry(headerentry->compose);
11879 g_signal_handlers_disconnect_matched
11880 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11881 0, 0, NULL, NULL, headerentry);
11883 if (!headerentry->compose->batch)
11884 g_timeout_add(0, scroll_postpone, headerentry->compose);
11888 static gboolean compose_defer_auto_save_draft(Compose *compose)
11890 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11891 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11892 return FALSE;
11895 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11897 GtkAdjustment *vadj;
11899 cm_return_if_fail(compose);
11901 if(compose->batch)
11902 return;
11904 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11905 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11906 vadj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(gtk_widget_get_parent(compose->header_table)));
11907 gtk_adjustment_set_value(vadj, (show_first ?
11908 gtk_adjustment_get_lower(vadj) :
11909 (gtk_adjustment_get_upper(vadj) -
11910 gtk_adjustment_get_page_size(vadj))));
11913 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11914 const gchar *text, gint len, Compose *compose)
11916 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11917 (G_OBJECT(compose->text), "paste_as_quotation"));
11918 GtkTextMark *mark;
11920 cm_return_if_fail(text != NULL);
11922 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11923 G_CALLBACK(text_inserted),
11924 compose);
11925 if (paste_as_quotation) {
11926 gchar *new_text;
11927 const gchar *qmark;
11928 guint pos = 0;
11929 GtkTextIter start_iter;
11931 if (len < 0)
11932 len = strlen(text);
11934 new_text = g_strndup(text, len);
11936 qmark = compose_quote_char_from_context(compose);
11938 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11939 gtk_text_buffer_place_cursor(buffer, iter);
11941 pos = gtk_text_iter_get_offset(iter);
11943 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11944 _("Quote format error at line %d."));
11945 quote_fmt_reset_vartable();
11946 g_free(new_text);
11947 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11948 GINT_TO_POINTER(paste_as_quotation - 1));
11950 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11951 gtk_text_buffer_place_cursor(buffer, iter);
11952 gtk_text_buffer_delete_mark(buffer, mark);
11954 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11955 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11956 compose_beautify_paragraph(compose, &start_iter, FALSE);
11957 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11958 gtk_text_buffer_delete_mark(buffer, mark);
11959 } else {
11960 if (strcmp(text, "\n") || compose->automatic_break
11961 || gtk_text_iter_starts_line(iter)) {
11962 GtkTextIter before_ins;
11963 gtk_text_buffer_insert(buffer, iter, text, len);
11964 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11965 before_ins = *iter;
11966 gtk_text_iter_backward_chars(&before_ins, len);
11967 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11969 } else {
11970 /* check if the preceding is just whitespace or quote */
11971 GtkTextIter start_line;
11972 gchar *tmp = NULL, *quote = NULL;
11973 gint quote_len = 0, is_normal = 0;
11974 start_line = *iter;
11975 gtk_text_iter_set_line_offset(&start_line, 0);
11976 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11977 g_strstrip(tmp);
11979 if (*tmp == '\0') {
11980 is_normal = 1;
11981 } else {
11982 quote = compose_get_quote_str(buffer, &start_line, &quote_len);
11983 if (quote)
11984 is_normal = 1;
11985 g_free(quote);
11987 g_free(tmp);
11989 if (is_normal) {
11990 gtk_text_buffer_insert(buffer, iter, text, len);
11991 } else {
11992 gtk_text_buffer_insert_with_tags_by_name(buffer,
11993 iter, text, len, "no_join", NULL);
11998 if (!paste_as_quotation) {
11999 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
12000 compose_beautify_paragraph(compose, iter, FALSE);
12001 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12002 gtk_text_buffer_delete_mark(buffer, mark);
12005 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
12006 G_CALLBACK(text_inserted),
12007 compose);
12008 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
12010 if (compose_can_autosave(compose) &&
12011 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
12012 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
12013 compose->draft_timeout_tag = g_timeout_add
12014 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
12017 #if USE_ENCHANT
12018 static void compose_check_all(GtkAction *action, gpointer data)
12020 Compose *compose = (Compose *)data;
12021 if (!compose->gtkaspell)
12022 return;
12024 if (gtk_widget_has_focus(compose->subject_entry))
12025 claws_spell_entry_check_all(
12026 CLAWS_SPELL_ENTRY(compose->subject_entry));
12027 else
12028 gtkaspell_check_all(compose->gtkaspell);
12031 static void compose_highlight_all(GtkAction *action, gpointer data)
12033 Compose *compose = (Compose *)data;
12034 if (compose->gtkaspell) {
12035 claws_spell_entry_recheck_all(
12036 CLAWS_SPELL_ENTRY(compose->subject_entry));
12037 gtkaspell_highlight_all(compose->gtkaspell);
12041 static void compose_check_backwards(GtkAction *action, gpointer data)
12043 Compose *compose = (Compose *)data;
12044 if (!compose->gtkaspell) {
12045 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12046 return;
12049 if (gtk_widget_has_focus(compose->subject_entry))
12050 claws_spell_entry_check_backwards(
12051 CLAWS_SPELL_ENTRY(compose->subject_entry));
12052 else
12053 gtkaspell_check_backwards(compose->gtkaspell);
12056 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12058 Compose *compose = (Compose *)data;
12059 if (!compose->gtkaspell) {
12060 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12061 return;
12064 if (gtk_widget_has_focus(compose->subject_entry))
12065 claws_spell_entry_check_forwards_go(
12066 CLAWS_SPELL_ENTRY(compose->subject_entry));
12067 else
12068 gtkaspell_check_forwards_go(compose->gtkaspell);
12070 #endif
12073 *\brief Guess originating forward account from MsgInfo and several
12074 * "common preference" settings. Return NULL if no guess.
12076 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12078 PrefsAccount *account = NULL;
12080 cm_return_val_if_fail(msginfo, NULL);
12081 cm_return_val_if_fail(msginfo->folder, NULL);
12082 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12084 if (msginfo->folder->prefs->enable_default_account)
12085 account = account_find_from_id(msginfo->folder->prefs->default_account);
12087 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12088 gchar *to;
12089 Xstrdup_a(to, msginfo->to, return NULL);
12090 extract_address(to);
12091 account = account_find_from_address(to, FALSE);
12094 if (!account && prefs_common.forward_account_autosel) {
12095 gchar *cc = NULL;
12096 if (!procheader_get_header_from_msginfo
12097 (msginfo, &cc, "Cc:")) {
12098 gchar *buf = cc + strlen("Cc:");
12099 extract_address(buf);
12100 account = account_find_from_address(buf, FALSE);
12101 g_free(cc);
12105 if (!account && prefs_common.forward_account_autosel) {
12106 gchar *deliveredto = NULL;
12107 if (!procheader_get_header_from_msginfo
12108 (msginfo, &deliveredto, "Delivered-To:")) {
12109 gchar *buf = deliveredto + strlen("Delivered-To:");
12110 extract_address(buf);
12111 account = account_find_from_address(buf, FALSE);
12112 g_free(deliveredto);
12116 if (!account)
12117 account = msginfo->folder->folder->account;
12119 return account;
12122 gboolean compose_close(Compose *compose)
12124 gint x, y;
12126 cm_return_val_if_fail(compose, FALSE);
12128 if (!g_mutex_trylock(&compose->mutex)) {
12129 /* we have to wait for the (possibly deferred by auto-save)
12130 * drafting to be done, before destroying the compose under
12131 * it. */
12132 debug_print("waiting for drafting to finish...\n");
12133 compose_allow_user_actions(compose, FALSE);
12134 if (compose->close_timeout_tag == 0) {
12135 compose->close_timeout_tag =
12136 g_timeout_add (500, (GSourceFunc) compose_close,
12137 compose);
12139 return TRUE;
12142 if (compose->draft_timeout_tag >= 0) {
12143 g_source_remove(compose->draft_timeout_tag);
12144 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12147 gtk_window_get_position(GTK_WINDOW(compose->window), &x, &y);
12148 if (!compose->batch) {
12149 prefs_common.compose_x = x;
12150 prefs_common.compose_y = y;
12152 g_mutex_unlock(&compose->mutex);
12153 compose_destroy(compose);
12154 return FALSE;
12158 * Add entry field for each address in list.
12159 * \param compose E-Mail composition object.
12160 * \param listAddress List of (formatted) E-Mail addresses.
12162 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12163 GList *node;
12164 gchar *addr;
12165 node = listAddress;
12166 while( node ) {
12167 addr = ( gchar * ) node->data;
12168 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12169 node = g_list_next( node );
12173 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12174 guint action, gboolean opening_multiple)
12176 gchar *body = NULL;
12177 GSList *new_msglist = NULL;
12178 MsgInfo *tmp_msginfo = NULL;
12179 gboolean originally_enc = FALSE;
12180 gboolean originally_sig = FALSE;
12181 Compose *compose = NULL;
12182 gchar *s_system = NULL;
12184 cm_return_if_fail(msginfo_list != NULL);
12186 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12187 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12188 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12190 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12191 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12192 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12193 orig_msginfo, mimeinfo);
12194 if (tmp_msginfo != NULL) {
12195 new_msglist = g_slist_append(NULL, tmp_msginfo);
12197 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12198 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12199 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12201 tmp_msginfo->folder = orig_msginfo->folder;
12202 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12203 if (orig_msginfo->tags) {
12204 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12205 tmp_msginfo->folder->tags_dirty = TRUE;
12211 if (!opening_multiple && msgview != NULL)
12212 body = messageview_get_selection(msgview);
12214 if (new_msglist) {
12215 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12216 procmsg_msginfo_free(&tmp_msginfo);
12217 g_slist_free(new_msglist);
12218 } else
12219 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12221 if (compose && originally_enc) {
12222 compose_force_encryption(compose, compose->account, FALSE, s_system);
12225 if (compose && originally_sig && compose->account->default_sign_reply) {
12226 compose_force_signing(compose, compose->account, s_system);
12228 g_free(s_system);
12229 g_free(body);
12230 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12233 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12234 guint action)
12236 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12237 && msginfo_list != NULL
12238 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12239 GSList *cur = msginfo_list;
12240 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12241 "messages. Opening the windows "
12242 "could take some time. Do you "
12243 "want to continue?"),
12244 g_slist_length(msginfo_list));
12245 if (g_slist_length(msginfo_list) > 9
12246 && alertpanel(_("Warning"), msg, NULL, _("_Cancel"), NULL, _("_Yes"),
12247 NULL, NULL, ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12248 g_free(msg);
12249 return;
12251 g_free(msg);
12252 /* We'll open multiple compose windows */
12253 /* let the WM place the next windows */
12254 compose_force_window_origin = FALSE;
12255 for (; cur; cur = cur->next) {
12256 GSList tmplist;
12257 tmplist.data = cur->data;
12258 tmplist.next = NULL;
12259 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12261 compose_force_window_origin = TRUE;
12262 } else {
12263 /* forwarding multiple mails as attachments is done via a
12264 * single compose window */
12265 if (msginfo_list != NULL) {
12266 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12267 } else if (msgview != NULL) {
12268 GSList tmplist;
12269 tmplist.data = msgview->msginfo;
12270 tmplist.next = NULL;
12271 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12272 } else {
12273 debug_print("Nothing to reply to\n");
12278 void compose_check_for_email_account(Compose *compose)
12280 PrefsAccount *ac = NULL, *curr = NULL;
12281 GList *list;
12283 if (!compose)
12284 return;
12286 if (compose->account && compose->account->protocol == A_NNTP) {
12287 ac = account_get_cur_account();
12288 if (ac->protocol == A_NNTP) {
12289 list = account_get_list();
12291 for( ; list != NULL ; list = g_list_next(list)) {
12292 curr = (PrefsAccount *) list->data;
12293 if (curr->protocol != A_NNTP) {
12294 ac = curr;
12295 break;
12299 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12300 ac->account_id);
12304 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12305 const gchar *address)
12307 GSList *msginfo_list = NULL;
12308 gchar *body = messageview_get_selection(msgview);
12309 Compose *compose;
12311 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12313 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12314 compose_check_for_email_account(compose);
12315 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12316 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12317 compose_reply_set_subject(compose, msginfo);
12319 g_free(body);
12320 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12323 void compose_set_position(Compose *compose, gint pos)
12325 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12327 gtkut_text_view_set_position(text, pos);
12330 gboolean compose_search_string(Compose *compose,
12331 const gchar *str, gboolean case_sens)
12333 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12335 return gtkut_text_view_search_string(text, str, case_sens);
12338 gboolean compose_search_string_backward(Compose *compose,
12339 const gchar *str, gboolean case_sens)
12341 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12343 return gtkut_text_view_search_string_backward(text, str, case_sens);
12346 /* allocate a msginfo structure and populate its data from a compose data structure */
12347 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12349 MsgInfo *newmsginfo;
12350 GSList *list;
12351 gchar date[RFC822_DATE_BUFFSIZE];
12353 cm_return_val_if_fail( compose != NULL, NULL );
12355 newmsginfo = procmsg_msginfo_new();
12357 /* date is now */
12358 get_rfc822_date(date, sizeof(date));
12359 newmsginfo->date = g_strdup(date);
12361 /* from */
12362 if (compose->from_name) {
12363 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12364 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12367 /* subject */
12368 if (compose->subject_entry)
12369 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12371 /* to, cc, reply-to, newsgroups */
12372 for (list = compose->header_list; list; list = list->next) {
12373 gchar *header = gtk_editable_get_chars(
12374 GTK_EDITABLE(
12375 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12376 gchar *entry = gtk_editable_get_chars(
12377 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12379 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12380 if ( newmsginfo->to == NULL ) {
12381 newmsginfo->to = g_strdup(entry);
12382 } else if (entry && *entry) {
12383 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12384 g_free(newmsginfo->to);
12385 newmsginfo->to = tmp;
12387 } else
12388 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12389 if ( newmsginfo->cc == NULL ) {
12390 newmsginfo->cc = g_strdup(entry);
12391 } else if (entry && *entry) {
12392 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12393 g_free(newmsginfo->cc);
12394 newmsginfo->cc = tmp;
12396 } else
12397 if ( strcasecmp(header,
12398 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12399 if ( newmsginfo->newsgroups == NULL ) {
12400 newmsginfo->newsgroups = g_strdup(entry);
12401 } else if (entry && *entry) {
12402 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12403 g_free(newmsginfo->newsgroups);
12404 newmsginfo->newsgroups = tmp;
12408 g_free(header);
12409 g_free(entry);
12412 /* other data is unset */
12414 return newmsginfo;
12417 #ifdef USE_ENCHANT
12418 /* update compose's dictionaries from folder dict settings */
12419 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12420 FolderItem *folder_item)
12422 cm_return_if_fail(compose != NULL);
12424 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12425 FolderItemPrefs *prefs = folder_item->prefs;
12427 if (prefs->enable_default_dictionary)
12428 gtkaspell_change_dict(compose->gtkaspell,
12429 prefs->default_dictionary, FALSE);
12430 if (folder_item->prefs->enable_default_alt_dictionary)
12431 gtkaspell_change_alt_dict(compose->gtkaspell,
12432 prefs->default_alt_dictionary);
12433 if (prefs->enable_default_dictionary
12434 || prefs->enable_default_alt_dictionary)
12435 compose_spell_menu_changed(compose);
12438 #endif
12440 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12442 Compose *compose = (Compose *)data;
12444 cm_return_if_fail(compose != NULL);
12446 gtk_widget_grab_focus(compose->text);
12449 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12451 gtk_combo_box_popup(GTK_COMBO_BOX(data));
12456 * End of Source.