revert bbb1f7285d0f033f39e835be9994a12c335a8fd8 and 4f9e295d34c6dfa69aa9fc8fc526c11bc...
[claws.git] / src / compose.c
blob2318263f684b1379db403f3a5a9d1a99165416a9
1 /*
2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2024 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 MAX_REFERENCES_LEN 999
193 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
194 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
196 #define COMPOSE_PRIVACY_WARNING() { \
197 alertpanel_error(_("You have opted to sign and/or encrypt this " \
198 "message but have not selected a privacy system.\n\n" \
199 "Signing and encrypting have been disabled for this " \
200 "message.")); \
203 #ifdef G_OS_WIN32
204 #define INVALID_PID INVALID_HANDLE_VALUE
205 #else
206 #define INVALID_PID -1
207 #endif
209 static GdkRGBA default_header_bgcolor =
210 {0, 0, 0, 1};
212 static GdkRGBA default_header_color =
213 {0, 0, 0, 1};
215 static GList *compose_list = NULL;
216 static GSList *extra_headers = NULL;
218 static Compose *compose_generic_new (PrefsAccount *account,
219 const gchar *to,
220 FolderItem *item,
221 GList *attach_files,
222 GList *listAddress );
224 static Compose *compose_create (PrefsAccount *account,
225 FolderItem *item,
226 ComposeMode mode,
227 gboolean batch);
229 static void compose_entry_indicate (Compose *compose,
230 const gchar *address);
231 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
232 ComposeQuoteMode quote_mode,
233 gboolean to_all,
234 gboolean to_sender,
235 const gchar *body);
236 static Compose *compose_forward_multiple (PrefsAccount *account,
237 GSList *msginfo_list);
238 static Compose *compose_reply (MsgInfo *msginfo,
239 ComposeQuoteMode quote_mode,
240 gboolean to_all,
241 gboolean to_ml,
242 gboolean to_sender,
243 const gchar *body);
244 static Compose *compose_reply_mode (ComposeMode mode,
245 GSList *msginfo_list,
246 gchar *body);
247 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
248 static void compose_update_privacy_systems_menu(Compose *compose);
250 static GtkWidget *compose_account_option_menu_create
251 (Compose *compose);
252 static void compose_set_out_encoding (Compose *compose);
253 static void compose_set_template_menu (Compose *compose);
254 static void compose_destroy (Compose *compose);
256 static MailField compose_entries_set (Compose *compose,
257 const gchar *mailto,
258 ComposeEntryType to_type);
259 static gint compose_parse_header (Compose *compose,
260 MsgInfo *msginfo);
261 static gint compose_parse_manual_headers (Compose *compose,
262 MsgInfo *msginfo,
263 HeaderEntry *entries);
264 static gchar *compose_parse_references (const gchar *ref,
265 const gchar *msgid);
267 static gchar *compose_quote_fmt (Compose *compose,
268 MsgInfo *msginfo,
269 const gchar *fmt,
270 const gchar *qmark,
271 const gchar *body,
272 gboolean rewrap,
273 gboolean need_unescape,
274 const gchar *err_msg);
276 static void compose_reply_set_entry (Compose *compose,
277 MsgInfo *msginfo,
278 gboolean to_all,
279 gboolean to_ml,
280 gboolean to_sender,
281 gboolean
282 followup_and_reply_to);
283 static void compose_reedit_set_entry (Compose *compose,
284 MsgInfo *msginfo);
286 static void compose_insert_sig (Compose *compose,
287 gboolean replace);
288 static ComposeInsertResult compose_insert_file (Compose *compose,
289 const gchar *file);
291 static gboolean compose_attach_append (Compose *compose,
292 const gchar *file,
293 const gchar *type,
294 const gchar *content_type,
295 const gchar *charset);
296 static void compose_attach_parts (Compose *compose,
297 MsgInfo *msginfo);
299 static gboolean compose_beautify_paragraph (Compose *compose,
300 GtkTextIter *par_iter,
301 gboolean force);
302 static void compose_wrap_all (Compose *compose);
303 static void compose_wrap_all_full (Compose *compose,
304 gboolean autowrap);
306 static void compose_set_title (Compose *compose);
307 static void compose_select_account (Compose *compose,
308 PrefsAccount *account,
309 gboolean init);
311 static PrefsAccount *compose_current_mail_account(void);
312 /* static gint compose_send (Compose *compose); */
313 static gboolean compose_check_for_valid_recipient
314 (Compose *compose);
315 static gboolean compose_check_entries (Compose *compose,
316 gboolean check_everything);
317 static gint compose_write_to_file (Compose *compose,
318 FILE *fp,
319 gint action,
320 gboolean attach_parts);
321 static gint compose_write_body_to_file (Compose *compose,
322 const gchar *file);
323 static gint compose_remove_reedit_target (Compose *compose,
324 gboolean force);
325 static void compose_remove_draft (Compose *compose);
326 static ComposeQueueResult compose_queue_sub (Compose *compose,
327 gint *msgnum,
328 FolderItem **item,
329 gchar **msgpath,
330 gboolean perform_checks,
331 gboolean remove_reedit_target);
332 static int compose_add_attachments (Compose *compose,
333 MimeInfo *parent,
334 gint action);
335 static gchar *compose_get_header (Compose *compose);
336 static gchar *compose_get_manual_headers_info (Compose *compose);
338 static void compose_convert_header (Compose *compose,
339 gchar *dest,
340 gint len,
341 gchar *src,
342 gint header_len,
343 gboolean addr_field);
345 static void compose_attach_info_free (AttachInfo *ainfo);
346 static void compose_attach_remove_selected (GtkAction *action,
347 gpointer data);
349 static void compose_template_apply (Compose *compose,
350 Template *tmpl,
351 gboolean replace);
352 static void compose_attach_property (GtkAction *action,
353 gpointer data);
354 static void compose_attach_property_create (gboolean *cancelled);
355 static void attach_property_ok (GtkWidget *widget,
356 gboolean *cancelled);
357 static void attach_property_cancel (GtkWidget *widget,
358 gboolean *cancelled);
359 static gint attach_property_delete_event (GtkWidget *widget,
360 GdkEventAny *event,
361 gboolean *cancelled);
362 static gboolean attach_property_key_pressed (GtkWidget *widget,
363 GdkEventKey *event,
364 gboolean *cancelled);
366 static void compose_exec_ext_editor (Compose *compose);
367 static gboolean compose_ext_editor_kill (Compose *compose);
368 static void compose_ext_editor_closed_cb (GPid pid,
369 gint exit_status,
370 gpointer data);
371 static void compose_set_ext_editor_sensitive (Compose *compose,
372 gboolean sensitive);
373 static gboolean compose_get_ext_editor_cmd_valid();
374 static gboolean compose_get_ext_editor_uses_socket();
375 #ifdef GDK_WINDOWING_X11
376 static gboolean compose_ext_editor_plug_removed_cb
377 (GtkSocket *socket,
378 Compose *compose);
379 #endif /* GDK_WINDOWING_X11 */
381 static void compose_undo_state_changed (UndoMain *undostruct,
382 gint undo_state,
383 gint redo_state,
384 gpointer data);
386 static void compose_create_header_entry (Compose *compose);
387 static void compose_add_header_entry (Compose *compose, const gchar *header,
388 gchar *text, ComposePrefType pref_type);
389 static void compose_remove_header_entries(Compose *compose);
391 static void compose_update_priority_menu_item(Compose * compose);
392 #if USE_ENCHANT
393 static void compose_spell_menu_changed (void *data);
394 static void compose_dict_changed (void *data);
395 #endif
396 static void compose_add_field_list ( Compose *compose,
397 GList *listAddress );
399 /* callback functions */
401 static void compose_notebook_size_alloc (GtkNotebook *notebook,
402 GtkAllocation *allocation,
403 GtkPaned *paned);
404 static gboolean compose_edit_size_alloc (GtkEditable *widget,
405 GtkAllocation *allocation,
406 GtkSHRuler *shruler);
407 static void account_activated (GtkComboBox *optmenu,
408 gpointer data);
409 static void attach_selected (GtkTreeView *tree_view,
410 GtkTreePath *tree_path,
411 GtkTreeViewColumn *column,
412 Compose *compose);
413 static gboolean attach_button_pressed (GtkWidget *widget,
414 GdkEventButton *event,
415 gpointer data);
416 static gboolean attach_key_pressed (GtkWidget *widget,
417 GdkEventKey *event,
418 gpointer data);
419 static void compose_send_cb (GtkAction *action, gpointer data);
420 static void compose_send_later_cb (GtkAction *action, gpointer data);
422 static void compose_save_cb (GtkAction *action,
423 gpointer data);
425 static void compose_attach_cb (GtkAction *action,
426 gpointer data);
427 static void compose_insert_file_cb (GtkAction *action,
428 gpointer data);
429 static void compose_insert_sig_cb (GtkAction *action,
430 gpointer data);
431 static void compose_replace_sig_cb (GtkAction *action,
432 gpointer data);
434 static void compose_close_cb (GtkAction *action,
435 gpointer data);
436 static void compose_print_cb (GtkAction *action,
437 gpointer data);
439 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
441 static void compose_address_cb (GtkAction *action,
442 gpointer data);
443 static void about_show_cb (GtkAction *action,
444 gpointer data);
445 static void compose_template_activate_cb(GtkWidget *widget,
446 gpointer data);
448 static void compose_ext_editor_cb (GtkAction *action,
449 gpointer data);
451 static gint compose_delete_cb (GtkWidget *widget,
452 GdkEventAny *event,
453 gpointer data);
455 static void compose_undo_cb (GtkAction *action,
456 gpointer data);
457 static void compose_redo_cb (GtkAction *action,
458 gpointer data);
459 static void compose_cut_cb (GtkAction *action,
460 gpointer data);
461 static void compose_copy_cb (GtkAction *action,
462 gpointer data);
463 static void compose_paste_cb (GtkAction *action,
464 gpointer data);
465 static void compose_paste_as_quote_cb (GtkAction *action,
466 gpointer data);
467 static void compose_paste_no_wrap_cb (GtkAction *action,
468 gpointer data);
469 static void compose_paste_wrap_cb (GtkAction *action,
470 gpointer data);
471 static void compose_allsel_cb (GtkAction *action,
472 gpointer data);
474 static void compose_advanced_action_cb (GtkAction *action,
475 gpointer data);
477 static void compose_grab_focus_cb (GtkWidget *widget,
478 Compose *compose);
480 static void compose_changed_cb (GtkTextBuffer *textbuf,
481 Compose *compose);
483 static void compose_wrap_cb (GtkAction *action,
484 gpointer data);
485 static void compose_wrap_all_cb (GtkAction *action,
486 gpointer data);
487 static void compose_find_cb (GtkAction *action,
488 gpointer data);
489 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
490 gpointer data);
491 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
492 gpointer data);
494 static void compose_toggle_ruler_cb (GtkToggleAction *action,
495 gpointer data);
496 static void compose_toggle_sign_cb (GtkToggleAction *action,
497 gpointer data);
498 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
499 gpointer data);
500 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
501 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
502 static void compose_activate_privacy_system (Compose *compose,
503 PrefsAccount *account,
504 gboolean warn);
505 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item);
506 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
507 gpointer data);
508 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
509 gpointer data);
510 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
511 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
512 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
514 static void compose_attach_drag_received_cb (GtkWidget *widget,
515 GdkDragContext *drag_context,
516 gint x,
517 gint y,
518 GtkSelectionData *data,
519 guint info,
520 guint time,
521 gpointer user_data);
522 static void compose_insert_drag_received_cb (GtkWidget *widget,
523 GdkDragContext *drag_context,
524 gint x,
525 gint y,
526 GtkSelectionData *data,
527 guint info,
528 guint time,
529 gpointer user_data);
530 static void compose_header_drag_received_cb (GtkWidget *widget,
531 GdkDragContext *drag_context,
532 gint x,
533 gint y,
534 GtkSelectionData *data,
535 guint info,
536 guint time,
537 gpointer user_data);
539 static gboolean compose_drag_drop (GtkWidget *widget,
540 GdkDragContext *drag_context,
541 gint x, gint y,
542 guint time, gpointer user_data);
543 static gboolean completion_set_focus_to_subject
544 (GtkWidget *widget,
545 GdkEventKey *event,
546 Compose *user_data);
548 static void text_inserted (GtkTextBuffer *buffer,
549 GtkTextIter *iter,
550 const gchar *text,
551 gint len,
552 Compose *compose);
553 static Compose *compose_generic_reply(MsgInfo *msginfo,
554 ComposeQuoteMode quote_mode,
555 gboolean to_all,
556 gboolean to_ml,
557 gboolean to_sender,
558 gboolean followup_and_reply_to,
559 const gchar *body);
561 static void compose_headerentry_changed_cb (GtkWidget *entry,
562 ComposeHeaderEntry *headerentry);
563 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
564 GdkEventKey *event,
565 ComposeHeaderEntry *headerentry);
566 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
567 ComposeHeaderEntry *headerentry);
569 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
571 static void compose_allow_user_actions (Compose *compose, gboolean allow);
573 static void compose_nothing_cb (GtkAction *action, gpointer data)
578 #if USE_ENCHANT
579 static void compose_check_all (GtkAction *action, gpointer data);
580 static void compose_highlight_all (GtkAction *action, gpointer data);
581 static void compose_check_backwards (GtkAction *action, gpointer data);
582 static void compose_check_forwards_go (GtkAction *action, gpointer data);
583 #endif
585 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
587 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
589 #ifdef USE_ENCHANT
590 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
591 FolderItem *folder_item);
592 #endif
593 static void compose_attach_update_label(Compose *compose);
594 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
595 gboolean respect_default_to);
596 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
597 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
599 static GtkActionEntry compose_popup_entries[] =
601 {"Compose", NULL, "Compose", NULL, NULL, NULL },
602 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
603 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
604 {"Compose/---", NULL, "---", NULL, NULL, NULL },
605 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
608 /* make sure to keep the key bindings in the tables below in sync with the default_menurc[] in prefs_other.c
609 as well as with tables in messageview.c */
610 static GtkActionEntry compose_entries[] =
612 {"Menu", NULL, "Menu", NULL, NULL, NULL },
613 /* menus */
614 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
615 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
616 #if USE_ENCHANT
617 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
618 #endif
619 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
620 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
621 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
622 /* Message menu */
623 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
624 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
625 {"Message/---", NULL, "---", NULL, NULL, NULL },
627 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
628 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
629 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
630 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
631 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
632 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
633 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
634 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
635 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
636 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
638 /* Edit menu */
639 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
640 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
641 {"Edit/---", NULL, "---", NULL, NULL, NULL },
643 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
644 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
645 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
647 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
648 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
649 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
650 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
652 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
654 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
655 {"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*/
656 {"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*/
657 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
658 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
659 {"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*/
660 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), "<control>E", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
661 {"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*/
662 {"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*/
663 {"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*/
664 {"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*/
665 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
666 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
667 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
668 {"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*/
670 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
671 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
673 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
674 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
675 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
676 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
677 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
678 #if USE_ENCHANT
679 /* Spelling menu */
680 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
681 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
682 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
683 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
685 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
686 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
687 #endif
689 /* Options menu */
690 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
691 {"Options/---", NULL, "---", NULL, NULL, NULL },
692 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
693 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
695 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
696 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
698 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
699 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
700 #define ENC_ACTION(cs_char,c_char,string) \
701 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
703 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
704 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
705 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
706 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
707 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
708 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
709 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
710 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
711 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
713 /* Tools menu */
714 {"Tools/AddressBook", NULL, N_("_Address book"), "<shift><control>A", NULL, G_CALLBACK(compose_address_cb) },
716 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
717 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
718 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
719 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
721 /* Help menu */
722 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
725 static GtkToggleActionEntry compose_toggle_entries[] =
727 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
728 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
729 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
730 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
731 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
732 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
733 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
736 static GtkRadioActionEntry compose_radio_rm_entries[] =
738 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
739 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
740 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
741 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
744 static GtkRadioActionEntry compose_radio_prio_entries[] =
746 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
747 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
748 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
749 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
750 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
753 static GtkRadioActionEntry compose_radio_enc_entries[] =
755 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
777 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
778 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
779 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
780 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
781 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
782 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
783 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
784 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
785 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
786 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
787 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
790 static GtkTargetEntry compose_mime_types[] =
792 {"text/uri-list", 0, 0},
793 {"UTF8_STRING", 0, 0},
794 {"text/plain", 0, 0}
797 static gboolean compose_put_existing_to_front(MsgInfo *info)
799 const GList *compose_list = compose_get_compose_list();
800 const GList *elem = NULL;
802 if (compose_list) {
803 for (elem = compose_list; elem != NULL && elem->data != NULL;
804 elem = elem->next) {
805 Compose *c = (Compose*)elem->data;
807 if (!c->targetinfo || !c->targetinfo->msgid ||
808 !info->msgid)
809 continue;
811 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
812 gtkut_window_popup(c->window);
813 return TRUE;
817 return FALSE;
820 static GdkRGBA quote_color1 =
821 {0, 0, 0, 1};
822 static GdkRGBA quote_color2 =
823 {0, 0, 0, 1};
824 static GdkRGBA quote_color3 =
825 {0, 0, 0, 1};
827 static GdkRGBA quote_bgcolor1 =
828 {0, 0, 0, 1};
829 static GdkRGBA quote_bgcolor2 =
830 {0, 0, 0, 1};
831 static GdkRGBA quote_bgcolor3 =
832 {0, 0, 0, 1};
834 static GdkRGBA signature_color =
835 {0.5, 0.5, 0.5, 1};
837 static GdkRGBA uri_color =
838 {0, 0, 0, 1};
840 static void compose_create_tags(GtkTextView *text, Compose *compose)
842 GtkTextBuffer *buffer;
843 GdkRGBA black = { 0, 0, 0, 1 };
845 buffer = gtk_text_view_get_buffer(text);
847 if (prefs_common.enable_color) {
848 /* grab the quote colors, converting from an int to a GdkColor */
849 quote_color1 = prefs_common.color[COL_QUOTE_LEVEL1];
850 quote_color2 = prefs_common.color[COL_QUOTE_LEVEL2];
851 quote_color3 = prefs_common.color[COL_QUOTE_LEVEL3];
852 quote_bgcolor1 = prefs_common.color[COL_QUOTE_LEVEL1_BG];
853 quote_bgcolor2 = prefs_common.color[COL_QUOTE_LEVEL2_BG];
854 quote_bgcolor3 = prefs_common.color[COL_QUOTE_LEVEL3_BG];
855 signature_color = prefs_common.color[COL_SIGNATURE];
856 uri_color = prefs_common.color[COL_URI];
857 } else {
858 signature_color = quote_color1 = quote_color2 = quote_color3 =
859 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
862 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
863 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
864 "foreground-rgba", &quote_color1,
865 "paragraph-background-rgba", &quote_bgcolor1,
866 NULL);
867 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
868 "foreground-rgba", &quote_color2,
869 "paragraph-background-rgba", &quote_bgcolor2,
870 NULL);
871 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
872 "foreground-rgba", &quote_color3,
873 "paragraph-background-rgba", &quote_bgcolor3,
874 NULL);
875 } else {
876 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
877 "foreground-rgba", &quote_color1,
878 NULL);
879 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
880 "foreground-rgba", &quote_color2,
881 NULL);
882 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
883 "foreground-rgba", &quote_color3,
884 NULL);
887 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
888 "foreground-rgba", &signature_color,
889 NULL);
891 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
892 "foreground-rgba", &uri_color,
893 NULL);
894 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
895 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
898 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
899 GList *attach_files)
901 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
904 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
906 return compose_generic_new(account, mailto, item, NULL, NULL);
909 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
911 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
914 #define SCROLL_TO_CURSOR(compose) { \
915 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
916 gtk_text_view_get_buffer( \
917 GTK_TEXT_VIEW(compose->text))); \
918 gtk_text_view_scroll_mark_onscreen( \
919 GTK_TEXT_VIEW(compose->text), \
920 cmark); \
923 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
925 GtkEditable *entry;
926 if (folderidentifier) {
927 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
928 prefs_common.compose_save_to_history = add_history(
929 prefs_common.compose_save_to_history, folderidentifier);
930 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
931 prefs_common.compose_save_to_history);
934 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
935 if (folderidentifier)
936 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
937 else
938 gtk_entry_set_text(GTK_ENTRY(entry), "");
941 static gchar *compose_get_save_to(Compose *compose)
943 GtkEditable *entry;
944 gchar *result = NULL;
945 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
946 result = gtk_editable_get_chars(entry, 0, -1);
948 if (result) {
949 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
950 prefs_common.compose_save_to_history = add_history(
951 prefs_common.compose_save_to_history, result);
952 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
953 prefs_common.compose_save_to_history);
955 return result;
958 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
959 GList *attach_files, GList *listAddress )
961 Compose *compose;
962 GtkTextView *textview;
963 GtkTextBuffer *textbuf;
964 GtkTextIter iter;
965 const gchar *subject_format = NULL;
966 const gchar *body_format = NULL;
967 gchar *mailto_from = NULL;
968 PrefsAccount *mailto_account = NULL;
969 MsgInfo* dummyinfo = NULL;
970 gint cursor_pos = -1;
971 MailField mfield = NO_FIELD_PRESENT;
972 gchar* buf;
973 GtkTextMark *mark;
975 /* check if mailto defines a from */
976 if (mailto && *mailto != '\0') {
977 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
978 /* mailto defines a from, check if we can get account prefs from it,
979 if not, the account prefs will be guessed using other ways, but we'll keep
980 the from anyway */
981 if (mailto_from) {
982 mailto_account = account_find_from_address(mailto_from, TRUE);
983 if (mailto_account == NULL) {
984 gchar *tmp_from;
985 Xstrdup_a(tmp_from, mailto_from, return NULL);
986 extract_address(tmp_from);
987 mailto_account = account_find_from_address(tmp_from, TRUE);
990 if (mailto_account)
991 account = mailto_account;
994 /* if no account prefs set from mailto, set if from folder prefs (if any) */
995 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
996 account = account_find_from_id(item->prefs->default_account);
998 /* if no account prefs set, fallback to the current one */
999 if (!account) account = cur_account;
1000 cm_return_val_if_fail(account != NULL, NULL);
1002 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1003 compose_apply_folder_privacy_settings(compose, item);
1005 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1006 (account->default_encrypt || account->default_sign))
1007 COMPOSE_PRIVACY_WARNING();
1009 /* override from name if mailto asked for it */
1010 if (mailto_from) {
1011 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1012 g_free(mailto_from);
1013 } else
1014 /* override from name according to folder properties */
1015 if (item && item->prefs &&
1016 item->prefs->compose_with_format &&
1017 item->prefs->compose_override_from_format &&
1018 *item->prefs->compose_override_from_format != '\0') {
1020 gchar *tmp = NULL;
1021 gchar *buf = NULL;
1023 dummyinfo = compose_msginfo_new_from_compose(compose);
1025 /* decode \-escape sequences in the internal representation of the quote format */
1026 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1027 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1029 #ifdef USE_ENCHANT
1030 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1031 compose->gtkaspell);
1032 #else
1033 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1034 #endif
1035 quote_fmt_scan_string(tmp);
1036 quote_fmt_parse();
1038 buf = quote_fmt_get_buffer();
1039 if (buf == NULL)
1040 alertpanel_error(_("New message From format error."));
1041 else
1042 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1043 quote_fmt_reset_vartable();
1044 quote_fmtlex_destroy();
1046 g_free(tmp);
1049 compose->replyinfo = NULL;
1050 compose->fwdinfo = NULL;
1052 textview = GTK_TEXT_VIEW(compose->text);
1053 textbuf = gtk_text_view_get_buffer(textview);
1054 compose_create_tags(textview, compose);
1056 undo_block(compose->undostruct);
1057 #ifdef USE_ENCHANT
1058 compose_set_dictionaries_from_folder_prefs(compose, item);
1059 #endif
1061 if (account->auto_sig)
1062 compose_insert_sig(compose, FALSE);
1063 gtk_text_buffer_get_start_iter(textbuf, &iter);
1064 gtk_text_buffer_place_cursor(textbuf, &iter);
1066 if (account->protocol != A_NNTP) {
1067 if (mailto && *mailto != '\0') {
1068 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1070 } else {
1071 compose_set_folder_prefs(compose, item, TRUE);
1073 if (item && item->ret_rcpt) {
1074 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1076 } else {
1077 if (mailto && *mailto != '\0') {
1078 if (!strchr(mailto, '@'))
1079 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1080 else
1081 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1082 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1083 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1084 mfield = TO_FIELD_PRESENT;
1087 * CLAWS: just don't allow return receipt request, even if the user
1088 * may want to send an email. simple but foolproof.
1090 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1092 compose_add_field_list( compose, listAddress );
1094 if (item && item->prefs && item->prefs->compose_with_format) {
1095 subject_format = item->prefs->compose_subject_format;
1096 body_format = item->prefs->compose_body_format;
1097 } else if (account->compose_with_format) {
1098 subject_format = account->compose_subject_format;
1099 body_format = account->compose_body_format;
1100 } else if (prefs_common.compose_with_format) {
1101 subject_format = prefs_common.compose_subject_format;
1102 body_format = prefs_common.compose_body_format;
1105 if (subject_format || body_format) {
1107 if ( subject_format
1108 && *subject_format != '\0' )
1110 gchar *subject = NULL;
1111 gchar *tmp = NULL;
1112 gchar *buf = NULL;
1114 if (!dummyinfo)
1115 dummyinfo = compose_msginfo_new_from_compose(compose);
1117 /* decode \-escape sequences in the internal representation of the quote format */
1118 tmp = g_malloc(strlen(subject_format)+1);
1119 pref_get_unescaped_pref(tmp, subject_format);
1121 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1122 #ifdef USE_ENCHANT
1123 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1124 compose->gtkaspell);
1125 #else
1126 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1127 #endif
1128 quote_fmt_scan_string(tmp);
1129 quote_fmt_parse();
1131 buf = quote_fmt_get_buffer();
1132 if (buf == NULL)
1133 alertpanel_error(_("New message subject format error."));
1134 else
1135 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1136 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1137 quote_fmt_reset_vartable();
1138 quote_fmtlex_destroy();
1140 g_free(subject);
1141 g_free(tmp);
1142 mfield = SUBJECT_FIELD_PRESENT;
1145 if ( body_format
1146 && *body_format != '\0' )
1148 GtkTextView *text;
1149 GtkTextBuffer *buffer;
1150 GtkTextIter start, end;
1151 gchar *tmp = NULL;
1153 if (!dummyinfo)
1154 dummyinfo = compose_msginfo_new_from_compose(compose);
1156 text = GTK_TEXT_VIEW(compose->text);
1157 buffer = gtk_text_view_get_buffer(text);
1158 gtk_text_buffer_get_start_iter(buffer, &start);
1159 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1160 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1162 compose_quote_fmt(compose, dummyinfo,
1163 body_format,
1164 NULL, tmp, FALSE, TRUE,
1165 _("The body of the \"New message\" template has an error at line %d."));
1166 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1167 quote_fmt_reset_vartable();
1169 g_free(tmp);
1170 #ifdef USE_ENCHANT
1171 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1172 gtkaspell_highlight_all(compose->gtkaspell);
1173 #endif
1174 mfield = BODY_FIELD_PRESENT;
1178 procmsg_msginfo_free( &dummyinfo );
1180 if (attach_files) {
1181 GList *curr;
1182 AttachInfo *ainfo;
1184 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1185 ainfo = (AttachInfo *) curr->data;
1186 if (ainfo->insert)
1187 compose_insert_file(compose, ainfo->file);
1188 else
1189 compose_attach_append(compose, ainfo->file, ainfo->file,
1190 ainfo->content_type, ainfo->charset);
1194 compose_show_first_last_header(compose, TRUE);
1196 /* Set save folder */
1197 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1198 gchar *folderidentifier;
1200 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1201 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1202 folderidentifier = folder_item_get_identifier(item);
1203 compose_set_save_to(compose, folderidentifier);
1204 g_free(folderidentifier);
1207 /* Place cursor according to provided input (mfield) */
1208 switch (mfield) {
1209 case NO_FIELD_PRESENT:
1210 if (compose->header_last)
1211 gtk_widget_grab_focus(compose->header_last->entry);
1212 break;
1213 case TO_FIELD_PRESENT:
1214 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1215 if (buf) {
1216 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1217 g_free(buf);
1219 gtk_widget_grab_focus(compose->subject_entry);
1220 break;
1221 case SUBJECT_FIELD_PRESENT:
1222 textview = GTK_TEXT_VIEW(compose->text);
1223 if (!textview)
1224 break;
1225 textbuf = gtk_text_view_get_buffer(textview);
1226 if (!textbuf)
1227 break;
1228 mark = gtk_text_buffer_get_insert(textbuf);
1229 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1230 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1232 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1233 * only defers where it comes to the variable body
1234 * is not null. If no body is present compose->text
1235 * will be null in which case you cannot place the
1236 * cursor inside the component so. An empty component
1237 * is therefore created before placing the cursor
1239 case BODY_FIELD_PRESENT:
1240 cursor_pos = quote_fmt_get_cursor_pos();
1241 if (cursor_pos == -1)
1242 gtk_widget_grab_focus(compose->header_last->entry);
1243 else
1244 gtk_widget_grab_focus(compose->text);
1245 break;
1248 undo_unblock(compose->undostruct);
1250 if (prefs_common.auto_exteditor)
1251 compose_exec_ext_editor(compose);
1253 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1255 SCROLL_TO_CURSOR(compose);
1257 compose->modified = FALSE;
1258 compose_set_title(compose);
1260 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1262 return compose;
1265 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1266 gboolean override_pref, const gchar *system)
1268 const gchar *privacy = NULL;
1270 cm_return_if_fail(compose != NULL);
1271 cm_return_if_fail(account != NULL);
1273 if (privacy_system_can_encrypt(compose->privacy_system) == FALSE ||
1274 (override_pref == FALSE && account->default_encrypt_reply == FALSE))
1275 return;
1277 if (account->default_privacy_system && strlen(account->default_privacy_system))
1278 privacy = account->default_privacy_system;
1279 else if (system)
1280 privacy = system;
1281 else {
1282 GSList *privacy_avail = privacy_get_system_ids();
1283 if (privacy_avail && g_slist_length(privacy_avail)) {
1284 privacy = (gchar *)(privacy_avail->data);
1286 g_slist_free_full(privacy_avail, g_free);
1288 if (privacy != NULL) {
1289 if (system) {
1290 g_free(compose->privacy_system);
1291 compose->privacy_system = NULL;
1292 g_free(compose->encdata);
1293 compose->encdata = NULL;
1295 if (compose->privacy_system == NULL)
1296 compose->privacy_system = g_strdup(privacy);
1297 else if (*(compose->privacy_system) == '\0') {
1298 g_free(compose->privacy_system);
1299 g_free(compose->encdata);
1300 compose->encdata = NULL;
1301 compose->privacy_system = g_strdup(privacy);
1303 compose_update_privacy_system_menu_item(compose, FALSE);
1304 compose_use_encryption(compose, TRUE);
1308 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1310 const gchar *privacy = NULL;
1311 if (privacy_system_can_sign(compose->privacy_system) == FALSE)
1312 return;
1314 if (account->default_privacy_system && strlen(account->default_privacy_system))
1315 privacy = account->default_privacy_system;
1316 else if (system)
1317 privacy = system;
1318 else {
1319 GSList *privacy_avail = privacy_get_system_ids();
1320 if (privacy_avail && g_slist_length(privacy_avail)) {
1321 privacy = (gchar *)(privacy_avail->data);
1325 if (privacy != NULL) {
1326 if (system) {
1327 g_free(compose->privacy_system);
1328 compose->privacy_system = NULL;
1329 g_free(compose->encdata);
1330 compose->encdata = NULL;
1332 if (compose->privacy_system == NULL)
1333 compose->privacy_system = g_strdup(privacy);
1334 compose_update_privacy_system_menu_item(compose, FALSE);
1335 compose_use_signing(compose, TRUE);
1339 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1341 MsgInfo *msginfo;
1342 guint list_len;
1343 Compose *compose = NULL;
1345 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1347 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1348 cm_return_val_if_fail(msginfo != NULL, NULL);
1350 list_len = g_slist_length(msginfo_list);
1352 switch (mode) {
1353 case COMPOSE_REPLY:
1354 case COMPOSE_REPLY_TO_ADDRESS:
1355 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1356 FALSE, prefs_common.default_reply_list, FALSE, body);
1357 break;
1358 case COMPOSE_REPLY_WITH_QUOTE:
1359 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1360 FALSE, prefs_common.default_reply_list, FALSE, body);
1361 break;
1362 case COMPOSE_REPLY_WITHOUT_QUOTE:
1363 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1364 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1365 break;
1366 case COMPOSE_REPLY_TO_SENDER:
1367 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1368 FALSE, FALSE, TRUE, body);
1369 break;
1370 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1371 compose = compose_followup_and_reply_to(msginfo,
1372 COMPOSE_QUOTE_CHECK,
1373 FALSE, FALSE, body);
1374 break;
1375 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1376 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1377 FALSE, FALSE, TRUE, body);
1378 break;
1379 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1380 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1381 FALSE, FALSE, TRUE, NULL);
1382 break;
1383 case COMPOSE_REPLY_TO_ALL:
1384 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1385 TRUE, FALSE, FALSE, body);
1386 break;
1387 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1388 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1389 TRUE, FALSE, FALSE, body);
1390 break;
1391 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1392 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1393 TRUE, FALSE, FALSE, NULL);
1394 break;
1395 case COMPOSE_REPLY_TO_LIST:
1396 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1397 FALSE, TRUE, FALSE, body);
1398 break;
1399 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1400 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1401 FALSE, TRUE, FALSE, body);
1402 break;
1403 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1404 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1405 FALSE, TRUE, FALSE, NULL);
1406 break;
1407 case COMPOSE_FORWARD:
1408 if (prefs_common.forward_as_attachment) {
1409 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1410 return compose;
1411 } else {
1412 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1413 return compose;
1415 break;
1416 case COMPOSE_FORWARD_INLINE:
1417 /* check if we reply to more than one Message */
1418 if (list_len == 1) {
1419 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1420 break;
1422 /* more messages FALL THROUGH */
1423 case COMPOSE_FORWARD_AS_ATTACH:
1424 compose = compose_forward_multiple(NULL, msginfo_list);
1425 break;
1426 case COMPOSE_REDIRECT:
1427 compose = compose_redirect(NULL, msginfo, FALSE);
1428 break;
1429 default:
1430 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1433 if (compose == NULL) {
1434 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1435 return NULL;
1438 compose->rmode = mode;
1439 switch (compose->rmode) {
1440 case COMPOSE_REPLY:
1441 case COMPOSE_REPLY_WITH_QUOTE:
1442 case COMPOSE_REPLY_WITHOUT_QUOTE:
1443 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1444 debug_print("reply mode Normal\n");
1445 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1446 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1447 break;
1448 case COMPOSE_REPLY_TO_SENDER:
1449 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1450 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1451 debug_print("reply mode Sender\n");
1452 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1453 break;
1454 case COMPOSE_REPLY_TO_ALL:
1455 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1456 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1457 debug_print("reply mode All\n");
1458 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1459 break;
1460 case COMPOSE_REPLY_TO_LIST:
1461 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1462 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1463 debug_print("reply mode List\n");
1464 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1465 break;
1466 case COMPOSE_REPLY_TO_ADDRESS:
1467 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1468 break;
1469 default:
1470 break;
1472 return compose;
1475 static Compose *compose_reply(MsgInfo *msginfo,
1476 ComposeQuoteMode quote_mode,
1477 gboolean to_all,
1478 gboolean to_ml,
1479 gboolean to_sender,
1480 const gchar *body)
1482 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1483 to_sender, FALSE, body);
1486 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1487 ComposeQuoteMode quote_mode,
1488 gboolean to_all,
1489 gboolean to_sender,
1490 const gchar *body)
1492 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1493 to_sender, TRUE, body);
1496 static void compose_extract_original_charset(Compose *compose)
1498 MsgInfo *info = NULL;
1499 if (compose->replyinfo) {
1500 info = compose->replyinfo;
1501 } else if (compose->fwdinfo) {
1502 info = compose->fwdinfo;
1503 } else if (compose->targetinfo) {
1504 info = compose->targetinfo;
1506 if (info) {
1507 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1508 MimeInfo *partinfo = mimeinfo;
1509 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1510 partinfo = procmime_mimeinfo_next(partinfo);
1511 if (partinfo) {
1512 compose->orig_charset =
1513 g_strdup(procmime_mimeinfo_get_parameter(
1514 partinfo, "charset"));
1516 procmime_mimeinfo_free_all(&mimeinfo);
1520 #define SIGNAL_BLOCK(buffer) { \
1521 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1522 G_CALLBACK(compose_changed_cb), \
1523 compose); \
1524 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1525 G_CALLBACK(text_inserted), \
1526 compose); \
1529 #define SIGNAL_UNBLOCK(buffer) { \
1530 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1531 G_CALLBACK(compose_changed_cb), \
1532 compose); \
1533 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1534 G_CALLBACK(text_inserted), \
1535 compose); \
1538 static Compose *compose_generic_reply(MsgInfo *msginfo,
1539 ComposeQuoteMode quote_mode,
1540 gboolean to_all, gboolean to_ml,
1541 gboolean to_sender,
1542 gboolean followup_and_reply_to,
1543 const gchar *body)
1545 Compose *compose;
1546 PrefsAccount *account = NULL;
1547 GtkTextView *textview;
1548 GtkTextBuffer *textbuf;
1549 gboolean quote = FALSE;
1550 const gchar *qmark = NULL;
1551 const gchar *body_fmt = NULL;
1552 gchar *s_system = NULL;
1553 START_TIMING("");
1554 cm_return_val_if_fail(msginfo != NULL, NULL);
1555 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1557 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1559 cm_return_val_if_fail(account != NULL, NULL);
1561 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1562 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1564 compose->updating = TRUE;
1566 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1567 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1569 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1570 if (!compose->replyinfo)
1571 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1573 compose_extract_original_charset(compose);
1575 if (msginfo->folder && msginfo->folder->ret_rcpt)
1576 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1578 /* Set save folder */
1579 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1580 gchar *folderidentifier;
1582 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1583 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1584 folderidentifier = folder_item_get_identifier(msginfo->folder);
1585 compose_set_save_to(compose, folderidentifier);
1586 g_free(folderidentifier);
1589 if (compose_parse_header(compose, msginfo) < 0) {
1590 compose->updating = FALSE;
1591 compose_destroy(compose);
1592 return NULL;
1595 /* override from name according to folder properties */
1596 if (msginfo->folder && msginfo->folder->prefs &&
1597 msginfo->folder->prefs->reply_with_format &&
1598 msginfo->folder->prefs->reply_override_from_format &&
1599 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1601 gchar *tmp = NULL;
1602 gchar *buf = NULL;
1604 /* decode \-escape sequences in the internal representation of the quote format */
1605 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1606 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1608 #ifdef USE_ENCHANT
1609 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1610 compose->gtkaspell);
1611 #else
1612 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1613 #endif
1614 quote_fmt_scan_string(tmp);
1615 quote_fmt_parse();
1617 buf = quote_fmt_get_buffer();
1618 if (buf == NULL)
1619 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1620 else
1621 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1622 quote_fmt_reset_vartable();
1623 quote_fmtlex_destroy();
1625 g_free(tmp);
1628 textview = (GTK_TEXT_VIEW(compose->text));
1629 textbuf = gtk_text_view_get_buffer(textview);
1630 compose_create_tags(textview, compose);
1632 undo_block(compose->undostruct);
1633 #ifdef USE_ENCHANT
1634 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1635 gtkaspell_block_check(compose->gtkaspell);
1636 #endif
1638 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1639 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1640 /* use the reply format of folder (if enabled), or the account's one
1641 (if enabled) or fallback to the global reply format, which is always
1642 enabled (even if empty), and use the relevant quotemark */
1643 quote = TRUE;
1644 if (msginfo->folder && msginfo->folder->prefs &&
1645 msginfo->folder->prefs->reply_with_format) {
1646 qmark = msginfo->folder->prefs->reply_quotemark;
1647 body_fmt = msginfo->folder->prefs->reply_body_format;
1649 } else if (account->reply_with_format) {
1650 qmark = account->reply_quotemark;
1651 body_fmt = account->reply_body_format;
1653 } else {
1654 qmark = prefs_common.quotemark;
1655 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1656 body_fmt = gettext(prefs_common.quotefmt);
1657 else
1658 body_fmt = "";
1662 if (quote) {
1663 /* empty quotemark is not allowed */
1664 if (qmark == NULL || *qmark == '\0')
1665 qmark = "> ";
1666 compose_quote_fmt(compose, compose->replyinfo,
1667 body_fmt, qmark, body, FALSE, TRUE,
1668 _("The body of the \"Reply\" template has an error at line %d."));
1669 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1670 quote_fmt_reset_vartable();
1673 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1674 compose_force_encryption(compose, account, FALSE, s_system);
1677 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1678 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1679 compose_force_signing(compose, account, s_system);
1681 g_free(s_system);
1683 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1684 ((account->default_encrypt || account->default_sign) ||
1685 (account->default_encrypt_reply && MSG_IS_ENCRYPTED(compose->replyinfo->flags)) ||
1686 (account->default_sign_reply && MSG_IS_SIGNED(compose->replyinfo->flags))))
1687 COMPOSE_PRIVACY_WARNING();
1689 SIGNAL_BLOCK(textbuf);
1691 if (account->auto_sig)
1692 compose_insert_sig(compose, FALSE);
1694 compose_wrap_all(compose);
1696 #ifdef USE_ENCHANT
1697 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1698 gtkaspell_highlight_all(compose->gtkaspell);
1699 gtkaspell_unblock_check(compose->gtkaspell);
1700 #endif
1701 SIGNAL_UNBLOCK(textbuf);
1703 gtk_widget_grab_focus(compose->text);
1705 undo_unblock(compose->undostruct);
1707 if (prefs_common.auto_exteditor)
1708 compose_exec_ext_editor(compose);
1710 compose->modified = FALSE;
1711 compose_set_title(compose);
1713 compose->updating = FALSE;
1714 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1715 SCROLL_TO_CURSOR(compose);
1717 if (compose->deferred_destroy) {
1718 compose_destroy(compose);
1719 return NULL;
1721 END_TIMING();
1723 return compose;
1726 #define INSERT_FW_HEADER(var, hdr) \
1727 if (msginfo->var && *msginfo->var) { \
1728 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1729 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1730 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1733 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1734 gboolean as_attach, const gchar *body,
1735 gboolean no_extedit,
1736 gboolean batch)
1738 Compose *compose;
1739 GtkTextView *textview;
1740 GtkTextBuffer *textbuf;
1741 gint cursor_pos = -1;
1742 ComposeMode mode;
1744 cm_return_val_if_fail(msginfo != NULL, NULL);
1745 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1747 if (!account && !(account = compose_find_account(msginfo)))
1748 account = cur_account;
1750 if (!prefs_common.forward_as_attachment)
1751 mode = COMPOSE_FORWARD_INLINE;
1752 else
1753 mode = COMPOSE_FORWARD;
1754 compose = compose_create(account, msginfo->folder, mode, batch);
1756 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1757 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1759 if (compose_parse_header(compose, msginfo) < 0) {
1760 compose->updating = FALSE;
1761 compose_destroy(compose);
1762 return NULL;
1765 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1767 compose->updating = TRUE;
1768 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1769 if (!compose->fwdinfo)
1770 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1772 compose_extract_original_charset(compose);
1774 if (msginfo->subject && *msginfo->subject) {
1775 gchar *buf, *buf2, *p;
1777 buf = p = g_strdup(msginfo->subject);
1778 p += subject_get_prefix_length(p);
1779 memmove(buf, p, strlen(p) + 1);
1781 buf2 = g_strdup_printf("Fw: %s", buf);
1782 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1784 g_free(buf);
1785 g_free(buf2);
1788 /* override from name according to folder properties */
1789 if (msginfo->folder && msginfo->folder->prefs &&
1790 msginfo->folder->prefs->forward_with_format &&
1791 msginfo->folder->prefs->forward_override_from_format &&
1792 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1794 gchar *tmp = NULL;
1795 gchar *buf = NULL;
1796 MsgInfo *full_msginfo = NULL;
1798 if (!as_attach)
1799 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1800 if (!full_msginfo)
1801 full_msginfo = procmsg_msginfo_copy(msginfo);
1803 /* decode \-escape sequences in the internal representation of the quote format */
1804 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1805 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1807 #ifdef USE_ENCHANT
1808 gtkaspell_block_check(compose->gtkaspell);
1809 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1810 compose->gtkaspell);
1811 #else
1812 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1813 #endif
1814 quote_fmt_scan_string(tmp);
1815 quote_fmt_parse();
1817 buf = quote_fmt_get_buffer();
1818 if (buf == NULL)
1819 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1820 else
1821 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1822 quote_fmt_reset_vartable();
1823 quote_fmtlex_destroy();
1825 g_free(tmp);
1826 procmsg_msginfo_free(&full_msginfo);
1829 textview = GTK_TEXT_VIEW(compose->text);
1830 textbuf = gtk_text_view_get_buffer(textview);
1831 compose_create_tags(textview, compose);
1833 undo_block(compose->undostruct);
1834 if (as_attach) {
1835 gchar *msgfile;
1837 msgfile = procmsg_get_message_file(msginfo);
1838 if (!is_file_exist(msgfile))
1839 g_warning("%s: file does not exist", msgfile);
1840 else
1841 compose_attach_append(compose, msgfile, msgfile,
1842 "message/rfc822", NULL);
1844 g_free(msgfile);
1845 } else {
1846 const gchar *qmark = NULL;
1847 const gchar *body_fmt = NULL;
1848 MsgInfo *full_msginfo;
1850 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1851 if (!full_msginfo)
1852 full_msginfo = procmsg_msginfo_copy(msginfo);
1854 /* use the forward format of folder (if enabled), or the account's one
1855 (if enabled) or fallback to the global forward format, which is always
1856 enabled (even if empty), and use the relevant quotemark */
1857 if (msginfo->folder && msginfo->folder->prefs &&
1858 msginfo->folder->prefs->forward_with_format) {
1859 qmark = msginfo->folder->prefs->forward_quotemark;
1860 body_fmt = msginfo->folder->prefs->forward_body_format;
1862 } else if (account->forward_with_format) {
1863 qmark = account->forward_quotemark;
1864 body_fmt = account->forward_body_format;
1866 } else {
1867 qmark = prefs_common.fw_quotemark;
1868 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1869 body_fmt = gettext(prefs_common.fw_quotefmt);
1870 else
1871 body_fmt = "";
1874 /* empty quotemark is not allowed */
1875 if (qmark == NULL || *qmark == '\0')
1876 qmark = "> ";
1878 compose_quote_fmt(compose, full_msginfo,
1879 body_fmt, qmark, body, FALSE, TRUE,
1880 _("The body of the \"Forward\" template has an error at line %d."));
1881 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1882 quote_fmt_reset_vartable();
1883 compose_attach_parts(compose, msginfo);
1885 procmsg_msginfo_free(&full_msginfo);
1888 SIGNAL_BLOCK(textbuf);
1890 if (account->auto_sig)
1891 compose_insert_sig(compose, FALSE);
1893 compose_wrap_all(compose);
1895 #ifdef USE_ENCHANT
1896 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1897 gtkaspell_highlight_all(compose->gtkaspell);
1898 gtkaspell_unblock_check(compose->gtkaspell);
1899 #endif
1900 SIGNAL_UNBLOCK(textbuf);
1902 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1903 (account->default_encrypt || account->default_sign))
1904 COMPOSE_PRIVACY_WARNING();
1906 cursor_pos = quote_fmt_get_cursor_pos();
1907 if (cursor_pos == -1)
1908 gtk_widget_grab_focus(compose->header_last->entry);
1909 else
1910 gtk_widget_grab_focus(compose->text);
1912 if (!no_extedit && prefs_common.auto_exteditor)
1913 compose_exec_ext_editor(compose);
1915 /*save folder*/
1916 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1917 gchar *folderidentifier;
1919 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1920 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1921 folderidentifier = folder_item_get_identifier(msginfo->folder);
1922 compose_set_save_to(compose, folderidentifier);
1923 g_free(folderidentifier);
1926 undo_unblock(compose->undostruct);
1928 compose->modified = FALSE;
1929 compose_set_title(compose);
1931 compose->updating = FALSE;
1932 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1933 SCROLL_TO_CURSOR(compose);
1935 if (compose->deferred_destroy) {
1936 compose_destroy(compose);
1937 return NULL;
1940 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1942 return compose;
1945 #undef INSERT_FW_HEADER
1947 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1949 Compose *compose;
1950 GtkTextView *textview;
1951 GtkTextBuffer *textbuf;
1952 GtkTextIter iter;
1953 GSList *msginfo;
1954 gchar *msgfile;
1955 gboolean single_mail = TRUE;
1957 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1959 if (g_slist_length(msginfo_list) > 1)
1960 single_mail = FALSE;
1962 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1963 if (((MsgInfo *)msginfo->data)->folder == NULL)
1964 return NULL;
1966 /* guess account from first selected message */
1967 if (!account &&
1968 !(account = compose_find_account(msginfo_list->data)))
1969 account = cur_account;
1971 cm_return_val_if_fail(account != NULL, NULL);
1973 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1974 if (msginfo->data) {
1975 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1976 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1980 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1981 g_warning("no msginfo_list");
1982 return NULL;
1985 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1986 compose_apply_folder_privacy_settings(compose, ((MsgInfo *)msginfo_list->data)->folder);
1987 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1988 (account->default_encrypt || account->default_sign))
1989 COMPOSE_PRIVACY_WARNING();
1991 compose->updating = TRUE;
1993 /* override from name according to folder properties */
1994 if (msginfo_list->data) {
1995 MsgInfo *msginfo = msginfo_list->data;
1997 if (msginfo->folder && msginfo->folder->prefs &&
1998 msginfo->folder->prefs->forward_with_format &&
1999 msginfo->folder->prefs->forward_override_from_format &&
2000 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2002 gchar *tmp = NULL;
2003 gchar *buf = NULL;
2005 /* decode \-escape sequences in the internal representation of the quote format */
2006 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2007 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2009 #ifdef USE_ENCHANT
2010 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2011 compose->gtkaspell);
2012 #else
2013 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2014 #endif
2015 quote_fmt_scan_string(tmp);
2016 quote_fmt_parse();
2018 buf = quote_fmt_get_buffer();
2019 if (buf == NULL)
2020 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2021 else
2022 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2023 quote_fmt_reset_vartable();
2024 quote_fmtlex_destroy();
2026 g_free(tmp);
2030 textview = GTK_TEXT_VIEW(compose->text);
2031 textbuf = gtk_text_view_get_buffer(textview);
2032 compose_create_tags(textview, compose);
2034 undo_block(compose->undostruct);
2035 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2036 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2038 if (!is_file_exist(msgfile))
2039 g_warning("%s: file does not exist", msgfile);
2040 else
2041 compose_attach_append(compose, msgfile, msgfile,
2042 "message/rfc822", NULL);
2043 g_free(msgfile);
2046 if (single_mail) {
2047 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2048 if (info->subject && *info->subject) {
2049 gchar *buf, *buf2, *p;
2051 buf = p = g_strdup(info->subject);
2052 p += subject_get_prefix_length(p);
2053 memmove(buf, p, strlen(p) + 1);
2055 buf2 = g_strdup_printf("Fw: %s", buf);
2056 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2058 g_free(buf);
2059 g_free(buf2);
2061 } else {
2062 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2063 _("Fw: multiple emails"));
2066 SIGNAL_BLOCK(textbuf);
2068 if (account->auto_sig)
2069 compose_insert_sig(compose, FALSE);
2071 compose_wrap_all(compose);
2073 SIGNAL_UNBLOCK(textbuf);
2075 gtk_text_buffer_get_start_iter(textbuf, &iter);
2076 gtk_text_buffer_place_cursor(textbuf, &iter);
2078 if (prefs_common.auto_exteditor)
2079 compose_exec_ext_editor(compose);
2081 gtk_widget_grab_focus(compose->header_last->entry);
2082 undo_unblock(compose->undostruct);
2083 compose->modified = FALSE;
2084 compose_set_title(compose);
2086 compose->updating = FALSE;
2087 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2088 SCROLL_TO_CURSOR(compose);
2090 if (compose->deferred_destroy) {
2091 compose_destroy(compose);
2092 return NULL;
2095 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2097 return compose;
2100 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2102 GtkTextIter start = *iter;
2103 GtkTextIter end_iter;
2104 int start_pos = gtk_text_iter_get_offset(&start);
2105 gchar *str = NULL;
2106 if (!compose->account->sig_sep)
2107 return FALSE;
2109 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2110 start_pos+strlen(compose->account->sig_sep));
2112 /* check sig separator */
2113 str = gtk_text_iter_get_text(&start, &end_iter);
2114 if (!strcmp(str, compose->account->sig_sep)) {
2115 gchar *tmp = NULL;
2116 /* check end of line (\n) */
2117 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2118 start_pos+strlen(compose->account->sig_sep));
2119 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2120 start_pos+strlen(compose->account->sig_sep)+1);
2121 tmp = gtk_text_iter_get_text(&start, &end_iter);
2122 if (!strcmp(tmp,"\n")) {
2123 g_free(str);
2124 g_free(tmp);
2125 return TRUE;
2127 g_free(tmp);
2129 g_free(str);
2131 return FALSE;
2134 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2136 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2137 Compose *compose = (Compose *)data;
2138 FolderItem *old_item = NULL;
2139 FolderItem *new_item = NULL;
2140 gchar *old_id, *new_id;
2142 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2143 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2144 return FALSE;
2146 old_item = hookdata->item;
2147 new_item = hookdata->item2;
2149 old_id = folder_item_get_identifier(old_item);
2150 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2152 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2153 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2154 compose->targetinfo->folder = new_item;
2157 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2158 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2159 compose->replyinfo->folder = new_item;
2162 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2163 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2164 compose->fwdinfo->folder = new_item;
2167 g_free(old_id);
2168 g_free(new_id);
2169 return FALSE;
2172 static void compose_colorize_signature(Compose *compose)
2174 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2175 GtkTextIter iter;
2176 GtkTextIter end_iter;
2177 gtk_text_buffer_get_start_iter(buffer, &iter);
2178 while (gtk_text_iter_forward_line(&iter))
2179 if (compose_is_sig_separator(compose, buffer, &iter)) {
2180 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2181 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2185 #define BLOCK_WRAP() { \
2186 prev_autowrap = compose->autowrap; \
2187 buffer = gtk_text_view_get_buffer( \
2188 GTK_TEXT_VIEW(compose->text)); \
2189 compose->autowrap = FALSE; \
2191 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2192 G_CALLBACK(compose_changed_cb), \
2193 compose); \
2194 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2195 G_CALLBACK(text_inserted), \
2196 compose); \
2198 #define UNBLOCK_WRAP() { \
2199 compose->autowrap = prev_autowrap; \
2200 if (compose->autowrap) { \
2201 gint old = compose->draft_timeout_tag; \
2202 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2203 compose_wrap_all(compose); \
2204 compose->draft_timeout_tag = old; \
2207 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2208 G_CALLBACK(compose_changed_cb), \
2209 compose); \
2210 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2211 G_CALLBACK(text_inserted), \
2212 compose); \
2215 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2217 Compose *compose = NULL;
2218 PrefsAccount *account = NULL;
2219 GtkTextView *textview;
2220 GtkTextBuffer *textbuf;
2221 GtkTextMark *mark;
2222 GtkTextIter iter;
2223 FILE *fp;
2224 gboolean use_signing = FALSE;
2225 gboolean use_encryption = FALSE;
2226 gchar *privacy_system = NULL;
2227 int priority = PRIORITY_NORMAL;
2228 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2229 gboolean autowrap = prefs_common.autowrap;
2230 gboolean autoindent = prefs_common.auto_indent;
2231 HeaderEntry *manual_headers = NULL;
2233 cm_return_val_if_fail(msginfo != NULL, NULL);
2234 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2236 if (compose_put_existing_to_front(msginfo)) {
2237 return NULL;
2240 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2241 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2242 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2243 gchar *queueheader_buf = NULL;
2244 gint id, param;
2246 /* Select Account from queue headers */
2247 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2248 "X-Claws-Account-Id:")) {
2249 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2250 account = account_find_from_id(id);
2251 g_free(queueheader_buf);
2253 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2254 "X-Sylpheed-Account-Id:")) {
2255 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2256 account = account_find_from_id(id);
2257 g_free(queueheader_buf);
2259 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2260 "NAID:")) {
2261 id = atoi(&queueheader_buf[strlen("NAID:")]);
2262 account = account_find_from_id(id);
2263 g_free(queueheader_buf);
2265 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2266 "MAID:")) {
2267 id = atoi(&queueheader_buf[strlen("MAID:")]);
2268 account = account_find_from_id(id);
2269 g_free(queueheader_buf);
2271 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2272 "S:")) {
2273 account = account_find_from_address(queueheader_buf, FALSE);
2274 g_free(queueheader_buf);
2276 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2277 "X-Claws-Sign:")) {
2278 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2279 use_signing = param;
2280 g_free(queueheader_buf);
2282 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2283 "X-Sylpheed-Sign:")) {
2284 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2285 use_signing = param;
2286 g_free(queueheader_buf);
2288 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2289 "X-Claws-Encrypt:")) {
2290 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2291 use_encryption = param;
2292 g_free(queueheader_buf);
2294 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2295 "X-Sylpheed-Encrypt:")) {
2296 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2297 use_encryption = param;
2298 g_free(queueheader_buf);
2300 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2301 "X-Claws-Auto-Wrapping:")) {
2302 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2303 autowrap = param;
2304 g_free(queueheader_buf);
2306 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2307 "X-Claws-Auto-Indent:")) {
2308 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2309 autoindent = param;
2310 g_free(queueheader_buf);
2312 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2313 "X-Claws-Privacy-System:")) {
2314 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2315 g_free(queueheader_buf);
2317 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2318 "X-Sylpheed-Privacy-System:")) {
2319 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2320 g_free(queueheader_buf);
2322 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2323 "X-Priority: ")) {
2324 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2325 priority = param;
2326 g_free(queueheader_buf);
2328 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2329 "RMID:")) {
2330 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2331 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2332 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2333 if (orig_item != NULL) {
2334 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2337 if (tokens)
2338 g_strfreev(tokens);
2339 g_free(queueheader_buf);
2341 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2342 "FMID:")) {
2343 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2344 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2345 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2346 if (orig_item != NULL) {
2347 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2350 if (tokens)
2351 g_strfreev(tokens);
2352 g_free(queueheader_buf);
2354 /* Get manual headers */
2355 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2356 "X-Claws-Manual-Headers:")) {
2357 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2358 if (listmh && *listmh != '\0') {
2359 debug_print("Got manual headers: %s\n", listmh);
2360 manual_headers = procheader_entries_from_str(listmh);
2362 if (listmh)
2363 g_free(listmh);
2364 g_free(queueheader_buf);
2366 } else {
2367 account = msginfo->folder->folder->account;
2370 if (!account && prefs_common.reedit_account_autosel) {
2371 gchar *from = NULL;
2372 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2373 extract_address(from);
2374 account = account_find_from_address(from, FALSE);
2376 if (from)
2377 g_free(from);
2379 if (!account) {
2380 account = cur_account;
2382 if (!account) {
2383 g_warning("can't select account");
2384 if (manual_headers)
2385 procheader_entries_free(manual_headers);
2386 return NULL;
2389 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2391 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
2392 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
2393 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2394 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2395 compose->autowrap = autowrap;
2396 compose->replyinfo = replyinfo;
2397 compose->fwdinfo = fwdinfo;
2399 compose->updating = TRUE;
2400 compose->priority = priority;
2402 if (privacy_system != NULL) {
2403 compose->privacy_system = privacy_system;
2404 compose_use_signing(compose, use_signing);
2405 compose_use_encryption(compose, use_encryption);
2406 compose_update_privacy_system_menu_item(compose, FALSE);
2407 } else {
2408 compose_activate_privacy_system(compose, account, FALSE);
2410 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2411 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
2412 (account->default_encrypt || account->default_sign))
2413 COMPOSE_PRIVACY_WARNING();
2415 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2416 compose->targetinfo->tags = g_slist_copy(msginfo->tags);
2418 compose_extract_original_charset(compose);
2420 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2421 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2422 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2423 gchar *queueheader_buf = NULL;
2425 /* Set message save folder */
2426 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2427 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2428 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2429 compose_set_save_to(compose, &queueheader_buf[4]);
2430 g_free(queueheader_buf);
2432 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2433 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2434 if (active) {
2435 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2437 g_free(queueheader_buf);
2441 if (compose_parse_header(compose, msginfo) < 0) {
2442 compose->updating = FALSE;
2443 compose_destroy(compose);
2444 if (manual_headers)
2445 procheader_entries_free(manual_headers);
2446 return NULL;
2448 compose_reedit_set_entry(compose, msginfo);
2450 textview = GTK_TEXT_VIEW(compose->text);
2451 textbuf = gtk_text_view_get_buffer(textview);
2452 compose_create_tags(textview, compose);
2454 mark = gtk_text_buffer_get_insert(textbuf);
2455 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2457 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2458 G_CALLBACK(compose_changed_cb),
2459 compose);
2461 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2462 fp = procmime_get_first_encrypted_text_content(msginfo);
2463 if (fp) {
2464 compose_force_encryption(compose, account, TRUE, NULL);
2466 } else {
2467 fp = procmime_get_first_text_content(msginfo);
2469 if (fp == NULL) {
2470 g_warning("can't get text part");
2473 if (fp != NULL) {
2474 gchar buf[BUFFSIZE];
2475 gboolean prev_autowrap;
2476 GtkTextBuffer *buffer;
2477 BLOCK_WRAP();
2478 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2479 strcrchomp(buf);
2480 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2482 UNBLOCK_WRAP();
2483 claws_fclose(fp);
2486 compose_attach_parts(compose, msginfo);
2488 compose_colorize_signature(compose);
2490 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2491 G_CALLBACK(compose_changed_cb),
2492 compose);
2494 if (manual_headers != NULL) {
2495 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2496 procheader_entries_free(manual_headers);
2497 compose->updating = FALSE;
2498 compose_destroy(compose);
2499 return NULL;
2501 procheader_entries_free(manual_headers);
2504 gtk_widget_grab_focus(compose->text);
2506 if (prefs_common.auto_exteditor) {
2507 compose_exec_ext_editor(compose);
2509 compose->modified = FALSE;
2510 compose_set_title(compose);
2512 compose->updating = FALSE;
2513 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2514 SCROLL_TO_CURSOR(compose);
2516 if (compose->deferred_destroy) {
2517 compose_destroy(compose);
2518 return NULL;
2521 compose->sig_str = account_get_signature_str(compose->account);
2523 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2525 return compose;
2528 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2529 gboolean batch)
2531 Compose *compose;
2532 gchar *filename;
2533 FolderItem *item;
2535 cm_return_val_if_fail(msginfo != NULL, NULL);
2537 if (!account)
2538 account = account_get_reply_account(msginfo,
2539 prefs_common.reply_account_autosel);
2540 cm_return_val_if_fail(account != NULL, NULL);
2542 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2544 compose->updating = TRUE;
2546 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2547 compose->replyinfo = NULL;
2548 compose->fwdinfo = NULL;
2550 compose_show_first_last_header(compose, TRUE);
2552 gtk_widget_grab_focus(compose->header_last->entry);
2554 filename = procmsg_get_message_file(msginfo);
2556 if (filename == NULL) {
2557 compose->updating = FALSE;
2558 compose_destroy(compose);
2560 return NULL;
2563 compose->redirect_filename = filename;
2565 /* Set save folder */
2566 item = msginfo->folder;
2567 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2568 gchar *folderidentifier;
2570 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2571 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2572 folderidentifier = folder_item_get_identifier(item);
2573 compose_set_save_to(compose, folderidentifier);
2574 g_free(folderidentifier);
2577 compose_attach_parts(compose, msginfo);
2579 if (msginfo->subject)
2580 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2581 msginfo->subject);
2582 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2584 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2585 _("The body of the \"Redirect\" template has an error at line %d."));
2586 quote_fmt_reset_vartable();
2587 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2589 compose_colorize_signature(compose);
2591 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2592 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2593 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2595 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", FALSE);
2596 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2597 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2598 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2599 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2600 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2601 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2602 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2603 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2604 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2606 if (compose->toolbar->sendl_btn)
2607 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, FALSE);
2608 if (compose->toolbar->draft_btn)
2609 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2610 if (compose->toolbar->insert_btn)
2611 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2612 if (compose->toolbar->attach_btn)
2613 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2614 if (compose->toolbar->sig_btn)
2615 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2616 if (compose->toolbar->exteditor_btn)
2617 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2618 if (compose->toolbar->linewrap_current_btn)
2619 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2620 if (compose->toolbar->linewrap_all_btn)
2621 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2622 if (compose->toolbar->privacy_sign_btn)
2623 gtk_widget_set_sensitive(compose->toolbar->privacy_sign_btn, FALSE);
2624 if (compose->toolbar->privacy_encrypt_btn)
2625 gtk_widget_set_sensitive(compose->toolbar->privacy_encrypt_btn, FALSE);
2627 compose->modified = FALSE;
2628 compose_set_title(compose);
2629 compose->updating = FALSE;
2630 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2631 SCROLL_TO_CURSOR(compose);
2633 if (compose->deferred_destroy) {
2634 compose_destroy(compose);
2635 return NULL;
2638 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2640 return compose;
2643 const GList *compose_get_compose_list(void)
2645 return compose_list;
2648 void compose_entry_append(Compose *compose, const gchar *address,
2649 ComposeEntryType type, ComposePrefType pref_type)
2651 const gchar *header;
2652 gchar *cur, *begin;
2653 gboolean in_quote = FALSE;
2654 if (!address || *address == '\0') return;
2656 switch (type) {
2657 case COMPOSE_CC:
2658 header = N_("Cc:");
2659 break;
2660 case COMPOSE_BCC:
2661 header = N_("Bcc:");
2662 break;
2663 case COMPOSE_REPLYTO:
2664 header = N_("Reply-To:");
2665 break;
2666 case COMPOSE_NEWSGROUPS:
2667 header = N_("Newsgroups:");
2668 break;
2669 case COMPOSE_FOLLOWUPTO:
2670 header = N_( "Followup-To:");
2671 break;
2672 case COMPOSE_INREPLYTO:
2673 header = N_( "In-Reply-To:");
2674 break;
2675 case COMPOSE_TO:
2676 default:
2677 header = N_("To:");
2678 break;
2680 header = prefs_common_translated_header_name(header);
2682 cur = begin = (gchar *)address;
2684 /* we separate the line by commas, but not if we're inside a quoted
2685 * string */
2686 while (*cur != '\0') {
2687 if (*cur == '"')
2688 in_quote = !in_quote;
2689 if (*cur == ',' && !in_quote) {
2690 gchar *tmp = g_strdup(begin);
2691 gchar *o_tmp = tmp;
2692 tmp[cur-begin]='\0';
2693 cur++;
2694 begin = cur;
2695 while (*tmp == ' ' || *tmp == '\t')
2696 tmp++;
2697 compose_add_header_entry(compose, header, tmp, pref_type);
2698 compose_entry_indicate(compose, tmp);
2699 g_free(o_tmp);
2700 continue;
2702 cur++;
2704 if (begin < cur) {
2705 gchar *tmp = g_strdup(begin);
2706 gchar *o_tmp = tmp;
2707 tmp[cur-begin]='\0';
2708 while (*tmp == ' ' || *tmp == '\t')
2709 tmp++;
2710 compose_add_header_entry(compose, header, tmp, pref_type);
2711 compose_entry_indicate(compose, tmp);
2712 g_free(o_tmp);
2716 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2718 GSList *h_list;
2719 GtkEntry *entry;
2720 GdkColor color;
2722 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2723 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2724 if (gtk_entry_get_text(entry) &&
2725 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2726 /* Modify background color */
2727 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_bgcolor, color);
2728 gtk_widget_modify_base(
2729 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2730 GTK_STATE_NORMAL, &color);
2732 /* Modify foreground color */
2733 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_color, color);
2734 gtk_widget_modify_text(
2735 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2736 GTK_STATE_NORMAL, &color);
2741 void compose_toolbar_cb(gint action, gpointer data)
2743 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2744 Compose *compose = (Compose*)toolbar_item->parent;
2746 cm_return_if_fail(compose != NULL);
2748 switch(action) {
2749 case A_SEND:
2750 compose_send_cb(NULL, compose);
2751 break;
2752 case A_SEND_LATER:
2753 compose_send_later_cb(NULL, compose);
2754 break;
2755 case A_DRAFT:
2756 compose_draft(compose, COMPOSE_QUIT_EDITING);
2757 break;
2758 case A_INSERT:
2759 compose_insert_file_cb(NULL, compose);
2760 break;
2761 case A_ATTACH:
2762 compose_attach_cb(NULL, compose);
2763 break;
2764 case A_SIG:
2765 compose_insert_sig(compose, FALSE);
2766 break;
2767 case A_REP_SIG:
2768 compose_insert_sig(compose, TRUE);
2769 break;
2770 case A_EXTEDITOR:
2771 compose_ext_editor_cb(NULL, compose);
2772 break;
2773 case A_LINEWRAP_CURRENT:
2774 compose_beautify_paragraph(compose, NULL, TRUE);
2775 break;
2776 case A_LINEWRAP_ALL:
2777 compose_wrap_all_full(compose, TRUE);
2778 break;
2779 case A_ADDRBOOK:
2780 compose_address_cb(NULL, compose);
2781 break;
2782 #ifdef USE_ENCHANT
2783 case A_CHECK_SPELLING:
2784 compose_check_all(NULL, compose);
2785 break;
2786 #endif
2787 case A_PRIVACY_SIGN:
2788 break;
2789 case A_PRIVACY_ENCRYPT:
2790 break;
2791 default:
2792 break;
2796 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2798 gchar *to = NULL;
2799 gchar *cc = NULL;
2800 gchar *bcc = NULL;
2801 gchar *subject = NULL;
2802 gchar *body = NULL;
2803 gchar *temp = NULL;
2804 gsize len = 0;
2805 gchar **attach = NULL;
2806 gchar *inreplyto = NULL;
2807 MailField mfield = NO_FIELD_PRESENT;
2809 /* get mailto parts but skip from */
2810 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2812 if (to) {
2813 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2814 mfield = TO_FIELD_PRESENT;
2816 if (cc)
2817 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2818 if (bcc)
2819 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2820 if (subject) {
2821 if (!g_utf8_validate (subject, -1, NULL)) {
2822 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2823 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2824 g_free(temp);
2825 } else {
2826 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2828 mfield = SUBJECT_FIELD_PRESENT;
2830 if (body) {
2831 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2832 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2833 GtkTextMark *mark;
2834 GtkTextIter iter;
2835 gboolean prev_autowrap = compose->autowrap;
2837 compose->autowrap = FALSE;
2839 mark = gtk_text_buffer_get_insert(buffer);
2840 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2842 if (!g_utf8_validate (body, -1, NULL)) {
2843 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2844 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2845 g_free(temp);
2846 } else {
2847 gtk_text_buffer_insert(buffer, &iter, body, -1);
2849 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2851 compose->autowrap = prev_autowrap;
2852 if (compose->autowrap)
2853 compose_wrap_all(compose);
2854 mfield = BODY_FIELD_PRESENT;
2857 if (attach) {
2858 gint i = 0, att = 0;
2859 gchar *warn_files = NULL;
2860 while (attach[i] != NULL) {
2861 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2862 if (utf8_filename) {
2863 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2864 gchar *tmp = g_strdup_printf("%s%s\n",
2865 warn_files?warn_files:"",
2866 utf8_filename);
2867 g_free(warn_files);
2868 warn_files = tmp;
2869 att++;
2871 g_free(utf8_filename);
2872 } else {
2873 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2875 i++;
2877 if (warn_files) {
2878 alertpanel_notice(ngettext(
2879 "The following file has been attached: \n%s",
2880 "The following files have been attached: \n%s", att), warn_files);
2881 g_free(warn_files);
2884 if (inreplyto)
2885 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2887 g_free(to);
2888 g_free(cc);
2889 g_free(bcc);
2890 g_free(subject);
2891 g_free(body);
2892 g_strfreev(attach);
2893 g_free(inreplyto);
2895 return mfield;
2898 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2900 static HeaderEntry hentry[] = {
2901 {"Reply-To:", NULL, TRUE },
2902 {"Cc:", NULL, TRUE },
2903 {"References:", NULL, FALSE },
2904 {"Bcc:", NULL, TRUE },
2905 {"Newsgroups:", NULL, TRUE },
2906 {"Followup-To:", NULL, TRUE },
2907 {"List-Post:", NULL, FALSE },
2908 {"X-Priority:", NULL, FALSE },
2909 {NULL, NULL, FALSE }
2912 enum
2914 H_REPLY_TO = 0,
2915 H_CC = 1,
2916 H_REFERENCES = 2,
2917 H_BCC = 3,
2918 H_NEWSGROUPS = 4,
2919 H_FOLLOWUP_TO = 5,
2920 H_LIST_POST = 6,
2921 H_X_PRIORITY = 7
2924 FILE *fp;
2926 cm_return_val_if_fail(msginfo != NULL, -1);
2928 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
2929 procheader_get_header_fields(fp, hentry);
2930 claws_fclose(fp);
2932 if (hentry[H_REPLY_TO].body != NULL) {
2933 if (hentry[H_REPLY_TO].body[0] != '\0') {
2934 compose->replyto =
2935 conv_unmime_header(hentry[H_REPLY_TO].body,
2936 NULL, TRUE);
2938 g_free(hentry[H_REPLY_TO].body);
2939 hentry[H_REPLY_TO].body = NULL;
2941 if (hentry[H_CC].body != NULL) {
2942 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2943 g_free(hentry[H_CC].body);
2944 hentry[H_CC].body = NULL;
2946 if (hentry[H_REFERENCES].body != NULL) {
2947 if (compose->mode == COMPOSE_REEDIT)
2948 compose->references = hentry[H_REFERENCES].body;
2949 else {
2950 compose->references = compose_parse_references
2951 (hentry[H_REFERENCES].body, msginfo->msgid);
2952 g_free(hentry[H_REFERENCES].body);
2954 hentry[H_REFERENCES].body = NULL;
2956 if (hentry[H_BCC].body != NULL) {
2957 if (compose->mode == COMPOSE_REEDIT)
2958 compose->bcc =
2959 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2960 g_free(hentry[H_BCC].body);
2961 hentry[H_BCC].body = NULL;
2963 if (hentry[H_NEWSGROUPS].body != NULL) {
2964 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2965 hentry[H_NEWSGROUPS].body = NULL;
2967 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2968 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2969 compose->followup_to =
2970 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2971 NULL, TRUE);
2973 g_free(hentry[H_FOLLOWUP_TO].body);
2974 hentry[H_FOLLOWUP_TO].body = NULL;
2976 if (hentry[H_LIST_POST].body != NULL) {
2977 gchar *to = NULL, *start = NULL;
2979 extract_address(hentry[H_LIST_POST].body);
2980 if (hentry[H_LIST_POST].body[0] != '\0') {
2981 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2983 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2984 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2986 if (to) {
2987 g_free(compose->ml_post);
2988 compose->ml_post = to;
2991 g_free(hentry[H_LIST_POST].body);
2992 hentry[H_LIST_POST].body = NULL;
2995 /* CLAWS - X-Priority */
2996 if (compose->mode == COMPOSE_REEDIT)
2997 if (hentry[H_X_PRIORITY].body != NULL) {
2998 gint priority;
3000 priority = atoi(hentry[H_X_PRIORITY].body);
3001 g_free(hentry[H_X_PRIORITY].body);
3003 hentry[H_X_PRIORITY].body = NULL;
3005 if (priority < PRIORITY_HIGHEST ||
3006 priority > PRIORITY_LOWEST)
3007 priority = PRIORITY_NORMAL;
3009 compose->priority = priority;
3012 if (compose->mode == COMPOSE_REEDIT) {
3013 if (msginfo->inreplyto && *msginfo->inreplyto)
3014 compose->inreplyto = g_strdup(msginfo->inreplyto);
3016 if (msginfo->msgid && *msginfo->msgid &&
3017 compose->folder != NULL &&
3018 compose->folder->stype == F_DRAFT)
3019 compose->msgid = g_strdup(msginfo->msgid);
3020 } else {
3021 if (msginfo->msgid && *msginfo->msgid &&
3022 (compose->mode != COMPOSE_FORWARD &&
3023 compose->mode != COMPOSE_FORWARD_INLINE &&
3024 compose->mode != COMPOSE_FORWARD_AS_ATTACH))
3025 compose->inreplyto = g_strdup(msginfo->msgid);
3027 if (!compose->references) {
3028 if (msginfo->msgid && *msginfo->msgid) {
3029 if (msginfo->inreplyto && *msginfo->inreplyto)
3030 compose->references =
3031 g_strdup_printf("<%s>\n\t<%s>",
3032 msginfo->inreplyto,
3033 msginfo->msgid);
3034 else
3035 compose->references =
3036 g_strconcat("<", msginfo->msgid, ">",
3037 NULL);
3038 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3039 compose->references =
3040 g_strconcat("<", msginfo->inreplyto, ">",
3041 NULL);
3046 return 0;
3049 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3051 FILE *fp;
3052 HeaderEntry *he;
3054 cm_return_val_if_fail(msginfo != NULL, -1);
3056 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
3057 procheader_get_header_fields(fp, entries);
3058 claws_fclose(fp);
3060 he = entries;
3061 while (he != NULL && he->name != NULL) {
3062 GtkTreeIter iter;
3063 GtkListStore *model = NULL;
3065 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3066 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3067 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3068 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3069 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3070 ++he;
3073 return 0;
3076 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3078 GSList *ref_id_list, *cur;
3079 GString *new_ref;
3081 ref_id_list = references_list_append(NULL, ref);
3082 if (!ref_id_list) return NULL;
3083 if (msgid && *msgid)
3084 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3086 for (;;) {
3087 gint len = 0;
3089 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3090 /* "<" + Message-ID + ">" + CR+LF+TAB */
3091 len += strlen((gchar *)cur->data) + 5;
3093 if (len > MAX_REFERENCES_LEN) {
3094 /* remove second message-ID */
3095 if (ref_id_list && ref_id_list->next &&
3096 ref_id_list->next->next) {
3097 g_free(ref_id_list->next->data);
3098 ref_id_list = g_slist_remove
3099 (ref_id_list, ref_id_list->next->data);
3100 } else {
3101 slist_free_strings_full(ref_id_list);
3102 return NULL;
3104 } else
3105 break;
3108 new_ref = g_string_new("");
3109 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3110 if (new_ref->len > 0)
3111 g_string_append(new_ref, "\n\t");
3112 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3115 slist_free_strings_full(ref_id_list);
3117 return g_string_free(new_ref, FALSE);
3120 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3121 const gchar *fmt, const gchar *qmark,
3122 const gchar *body, gboolean rewrap,
3123 gboolean need_unescape,
3124 const gchar *err_msg)
3126 MsgInfo* dummyinfo = NULL;
3127 gchar *quote_str = NULL;
3128 gchar *buf;
3129 gboolean prev_autowrap;
3130 const gchar *trimmed_body = body;
3131 gint cursor_pos = -1;
3132 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3133 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3134 GtkTextIter iter;
3135 GtkTextMark *mark;
3138 SIGNAL_BLOCK(buffer);
3140 if (!msginfo) {
3141 dummyinfo = compose_msginfo_new_from_compose(compose);
3142 msginfo = dummyinfo;
3145 if (qmark != NULL) {
3146 #ifdef USE_ENCHANT
3147 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3148 compose->gtkaspell);
3149 #else
3150 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3151 #endif
3152 quote_fmt_scan_string(qmark);
3153 quote_fmt_parse();
3155 buf = quote_fmt_get_buffer();
3157 if (buf == NULL)
3158 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3159 else
3160 Xstrdup_a(quote_str, buf, goto error)
3163 if (fmt && *fmt != '\0') {
3165 if (trimmed_body)
3166 while (*trimmed_body == '\n')
3167 trimmed_body++;
3169 #ifdef USE_ENCHANT
3170 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3171 compose->gtkaspell);
3172 #else
3173 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3174 #endif
3175 if (need_unescape) {
3176 gchar *tmp = NULL;
3178 /* decode \-escape sequences in the internal representation of the quote format */
3179 tmp = g_malloc(strlen(fmt)+1);
3180 pref_get_unescaped_pref(tmp, fmt);
3181 quote_fmt_scan_string(tmp);
3182 quote_fmt_parse();
3183 g_free(tmp);
3184 } else {
3185 quote_fmt_scan_string(fmt);
3186 quote_fmt_parse();
3189 buf = quote_fmt_get_buffer();
3191 if (buf == NULL) {
3192 gint line = quote_fmt_get_line();
3193 alertpanel_error(err_msg, line);
3195 goto error;
3198 } else
3199 buf = "";
3201 prev_autowrap = compose->autowrap;
3202 compose->autowrap = FALSE;
3204 mark = gtk_text_buffer_get_insert(buffer);
3205 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3206 if (g_utf8_validate(buf, -1, NULL)) {
3207 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3208 } else {
3209 gchar *tmpout = NULL;
3210 tmpout = conv_codeset_strdup
3211 (buf, conv_get_locale_charset_str_no_utf8(),
3212 CS_INTERNAL);
3213 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3214 g_free(tmpout);
3215 tmpout = g_malloc(strlen(buf)*2+1);
3216 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3218 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3219 g_free(tmpout);
3222 cursor_pos = quote_fmt_get_cursor_pos();
3223 if (cursor_pos == -1)
3224 cursor_pos = gtk_text_iter_get_offset(&iter);
3225 compose->set_cursor_pos = cursor_pos;
3227 gtk_text_buffer_get_start_iter(buffer, &iter);
3228 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3229 gtk_text_buffer_place_cursor(buffer, &iter);
3231 compose->autowrap = prev_autowrap;
3232 if (compose->autowrap && rewrap)
3233 compose_wrap_all(compose);
3235 goto ok;
3237 error:
3238 buf = NULL;
3240 SIGNAL_UNBLOCK(buffer);
3242 procmsg_msginfo_free( &dummyinfo );
3244 return buf;
3247 /* if ml_post is of type addr@host and from is of type
3248 * addr-anything@host, return TRUE
3250 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3252 gchar *left_ml = NULL;
3253 gchar *right_ml = NULL;
3254 gchar *left_from = NULL;
3255 gchar *right_from = NULL;
3256 gboolean result = FALSE;
3258 if (!ml_post || !from)
3259 return FALSE;
3261 left_ml = g_strdup(ml_post);
3262 if (strstr(left_ml, "@")) {
3263 right_ml = strstr(left_ml, "@")+1;
3264 *(strstr(left_ml, "@")) = '\0';
3267 left_from = g_strdup(from);
3268 if (strstr(left_from, "@")) {
3269 right_from = strstr(left_from, "@")+1;
3270 *(strstr(left_from, "@")) = '\0';
3273 if (right_ml && right_from
3274 && !strncmp(left_from, left_ml, strlen(left_ml))
3275 && !strcmp(right_from, right_ml)) {
3276 result = TRUE;
3278 g_free(left_ml);
3279 g_free(left_from);
3281 return result;
3284 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3285 gboolean respect_default_to)
3287 if (!compose)
3288 return;
3289 if (!folder || !folder->prefs)
3290 return;
3292 if (folder->prefs->enable_default_from) {
3293 gtk_entry_set_text(GTK_ENTRY(compose->from_name), folder->prefs->default_from);
3294 compose_entry_indicate(compose, folder->prefs->default_from);
3296 if (respect_default_to && folder->prefs->enable_default_to) {
3297 compose_entry_append(compose, folder->prefs->default_to,
3298 COMPOSE_TO, PREF_FOLDER);
3299 compose_entry_indicate(compose, folder->prefs->default_to);
3301 if (folder->prefs->enable_default_cc) {
3302 compose_entry_append(compose, folder->prefs->default_cc,
3303 COMPOSE_CC, PREF_FOLDER);
3304 compose_entry_indicate(compose, folder->prefs->default_cc);
3306 if (folder->prefs->enable_default_bcc) {
3307 compose_entry_append(compose, folder->prefs->default_bcc,
3308 COMPOSE_BCC, PREF_FOLDER);
3309 compose_entry_indicate(compose, folder->prefs->default_bcc);
3311 if (folder->prefs->enable_default_replyto) {
3312 compose_entry_append(compose, folder->prefs->default_replyto,
3313 COMPOSE_REPLYTO, PREF_FOLDER);
3314 compose_entry_indicate(compose, folder->prefs->default_replyto);
3318 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3320 gchar *buf, *buf2;
3321 gchar *p;
3323 if (!compose || !msginfo)
3324 return;
3326 if (msginfo->subject && *msginfo->subject) {
3327 buf = p = g_strdup(msginfo->subject);
3328 p += subject_get_prefix_length(p);
3329 memmove(buf, p, strlen(p) + 1);
3331 buf2 = g_strdup_printf("Re: %s", buf);
3332 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3334 g_free(buf2);
3335 g_free(buf);
3336 } else
3337 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3340 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3341 gboolean to_all, gboolean to_ml,
3342 gboolean to_sender,
3343 gboolean followup_and_reply_to)
3345 GSList *cc_list = NULL;
3346 GSList *cur;
3347 gchar *from = NULL;
3348 gchar *replyto = NULL;
3349 gchar *ac_email = NULL;
3351 gboolean reply_to_ml = FALSE;
3352 gboolean default_reply_to = FALSE;
3354 cm_return_if_fail(compose->account != NULL);
3355 cm_return_if_fail(msginfo != NULL);
3357 reply_to_ml = to_ml && compose->ml_post;
3359 default_reply_to = msginfo->folder &&
3360 msginfo->folder->prefs->enable_default_reply_to;
3362 if (compose->account->protocol != A_NNTP) {
3363 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3365 if (reply_to_ml && !default_reply_to) {
3367 gboolean is_subscr = is_subscription(compose->ml_post,
3368 msginfo->from);
3369 if (!is_subscr) {
3370 /* normal answer to ml post with a reply-to */
3371 compose_entry_append(compose,
3372 compose->ml_post,
3373 COMPOSE_TO, PREF_ML);
3374 if (compose->replyto)
3375 compose_entry_append(compose,
3376 compose->replyto,
3377 COMPOSE_CC, PREF_ML);
3378 } else {
3379 /* answer to subscription confirmation */
3380 if (compose->replyto)
3381 compose_entry_append(compose,
3382 compose->replyto,
3383 COMPOSE_TO, PREF_ML);
3384 else if (msginfo->from)
3385 compose_entry_append(compose,
3386 msginfo->from,
3387 COMPOSE_TO, PREF_ML);
3390 else if (!(to_all || to_sender) && default_reply_to) {
3391 compose_entry_append(compose,
3392 msginfo->folder->prefs->default_reply_to,
3393 COMPOSE_TO, PREF_FOLDER);
3394 compose_entry_indicate(compose,
3395 msginfo->folder->prefs->default_reply_to);
3396 } else {
3397 gchar *tmp1 = NULL;
3398 if (!msginfo->from)
3399 return;
3400 if (to_sender)
3401 compose_entry_append(compose, msginfo->from,
3402 COMPOSE_TO, PREF_NONE);
3403 else if (to_all) {
3404 Xstrdup_a(tmp1, msginfo->from, return);
3405 extract_address(tmp1);
3406 compose_entry_append(compose,
3407 (!account_find_from_address(tmp1, FALSE))
3408 ? msginfo->from :
3409 msginfo->to,
3410 COMPOSE_TO, PREF_NONE);
3411 if (compose->replyto)
3412 compose_entry_append(compose,
3413 compose->replyto,
3414 COMPOSE_CC, PREF_NONE);
3415 } else {
3416 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3417 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3418 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3419 if (compose->replyto) {
3420 compose_entry_append(compose,
3421 compose->replyto,
3422 COMPOSE_TO, PREF_NONE);
3423 } else {
3424 compose_entry_append(compose,
3425 msginfo->from ? msginfo->from : "",
3426 COMPOSE_TO, PREF_NONE);
3428 } else {
3429 /* replying to own mail, use original recp */
3430 compose_entry_append(compose,
3431 msginfo->to ? msginfo->to : "",
3432 COMPOSE_TO, PREF_NONE);
3433 compose_entry_append(compose,
3434 msginfo->cc ? msginfo->cc : "",
3435 COMPOSE_CC, PREF_NONE);
3439 } else {
3440 if (to_sender || (compose->followup_to &&
3441 !strncmp(compose->followup_to, "poster", 6)))
3442 compose_entry_append
3443 (compose,
3444 (compose->replyto ? compose->replyto :
3445 msginfo->from ? msginfo->from : ""),
3446 COMPOSE_TO, PREF_NONE);
3448 else if (followup_and_reply_to || to_all) {
3449 compose_entry_append
3450 (compose,
3451 (compose->replyto ? compose->replyto :
3452 msginfo->from ? msginfo->from : ""),
3453 COMPOSE_TO, PREF_NONE);
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_entry_append
3462 (compose,
3463 msginfo->cc ? msginfo->cc : "",
3464 COMPOSE_CC, PREF_NONE);
3466 else
3467 compose_entry_append
3468 (compose,
3469 compose->followup_to ? compose->followup_to :
3470 compose->newsgroups ? compose->newsgroups : "",
3471 COMPOSE_NEWSGROUPS, PREF_NONE);
3473 compose_reply_set_subject(compose, msginfo);
3475 if (to_ml && compose->ml_post) return;
3476 if (!to_all || compose->account->protocol == A_NNTP) return;
3478 if (compose->replyto) {
3479 Xstrdup_a(replyto, compose->replyto, return);
3480 extract_address(replyto);
3482 if (msginfo->from) {
3483 Xstrdup_a(from, msginfo->from, return);
3484 extract_address(from);
3487 if (replyto && from)
3488 cc_list = address_list_append_with_comments(cc_list, from);
3489 if (to_all && msginfo->folder &&
3490 msginfo->folder->prefs->enable_default_reply_to)
3491 cc_list = address_list_append_with_comments(cc_list,
3492 msginfo->folder->prefs->default_reply_to);
3493 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3494 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3496 ac_email = g_utf8_strdown(compose->account->address, -1);
3498 if (cc_list) {
3499 for (cur = cc_list; cur != NULL; cur = cur->next) {
3500 gchar *addr = g_utf8_strdown(cur->data, -1);
3501 extract_address(addr);
3503 if (strcmp(ac_email, addr))
3504 compose_entry_append(compose, (gchar *)cur->data,
3505 COMPOSE_CC, PREF_NONE);
3506 else
3507 debug_print("Cc address same as compose account's, ignoring\n");
3509 g_free(addr);
3512 slist_free_strings_full(cc_list);
3515 g_free(ac_email);
3518 #define SET_ENTRY(entry, str) \
3520 if (str && *str) \
3521 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3524 #define SET_ADDRESS(type, str) \
3526 if (str && *str) \
3527 compose_entry_append(compose, str, type, PREF_NONE); \
3530 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3532 cm_return_if_fail(msginfo != NULL);
3534 SET_ENTRY(subject_entry, msginfo->subject);
3535 SET_ENTRY(from_name, msginfo->from);
3536 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3537 SET_ADDRESS(COMPOSE_CC, compose->cc);
3538 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3539 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3540 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3541 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3543 compose_update_priority_menu_item(compose);
3544 compose_update_privacy_system_menu_item(compose, FALSE);
3545 compose_show_first_last_header(compose, TRUE);
3548 #undef SET_ENTRY
3549 #undef SET_ADDRESS
3551 static void compose_insert_sig(Compose *compose, gboolean replace)
3553 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3554 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3555 GtkTextMark *mark;
3556 GtkTextIter iter, iter_end;
3557 gint cur_pos, ins_pos;
3558 gboolean prev_autowrap;
3559 gboolean found = FALSE;
3560 gboolean exists = FALSE;
3562 cm_return_if_fail(compose->account != NULL);
3564 BLOCK_WRAP();
3566 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3567 G_CALLBACK(compose_changed_cb),
3568 compose);
3570 mark = gtk_text_buffer_get_insert(buffer);
3571 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3572 cur_pos = gtk_text_iter_get_offset (&iter);
3573 ins_pos = cur_pos;
3575 gtk_text_buffer_get_end_iter(buffer, &iter);
3577 exists = (compose->sig_str != NULL);
3579 if (replace) {
3580 GtkTextIter first_iter, start_iter, end_iter;
3582 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3584 if (!exists || compose->sig_str[0] == '\0')
3585 found = FALSE;
3586 else
3587 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3588 compose->signature_tag);
3590 if (found) {
3591 /* include previous \n\n */
3592 gtk_text_iter_backward_chars(&first_iter, 1);
3593 start_iter = first_iter;
3594 end_iter = first_iter;
3595 /* skip re-start */
3596 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3597 compose->signature_tag);
3598 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3599 compose->signature_tag);
3600 if (found) {
3601 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3602 iter = start_iter;
3607 g_free(compose->sig_str);
3608 compose->sig_str = account_get_signature_str(compose->account);
3610 cur_pos = gtk_text_iter_get_offset(&iter);
3612 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3613 g_free(compose->sig_str);
3614 compose->sig_str = NULL;
3615 } else {
3616 if (compose->sig_inserted == FALSE)
3617 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3618 compose->sig_inserted = TRUE;
3620 cur_pos = gtk_text_iter_get_offset(&iter);
3621 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3622 /* remove \n\n */
3623 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3624 gtk_text_iter_forward_chars(&iter, 1);
3625 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3626 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3628 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3629 cur_pos = gtk_text_buffer_get_char_count (buffer);
3632 /* put the cursor where it should be
3633 * either where the quote_fmt says, either where it was */
3634 if (compose->set_cursor_pos < 0)
3635 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3636 else
3637 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3638 compose->set_cursor_pos);
3640 compose->set_cursor_pos = -1;
3641 gtk_text_buffer_place_cursor(buffer, &iter);
3642 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3643 G_CALLBACK(compose_changed_cb),
3644 compose);
3646 UNBLOCK_WRAP();
3649 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3651 GtkTextView *text;
3652 GtkTextBuffer *buffer;
3653 GtkTextMark *mark;
3654 GtkTextIter iter;
3655 const gchar *cur_encoding;
3656 gchar buf[BUFFSIZE];
3657 gint len;
3658 FILE *fp;
3659 gboolean prev_autowrap;
3660 #ifdef G_OS_WIN32
3661 GFile *f;
3662 GFileInfo *fi;
3663 GError *error = NULL;
3664 #else
3665 GStatBuf file_stat;
3666 #endif
3667 int ret;
3668 goffset size;
3669 GString *file_contents = NULL;
3670 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3672 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3674 /* get the size of the file we are about to insert */
3675 #ifdef G_OS_WIN32
3676 f = g_file_new_for_path(file);
3677 fi = g_file_query_info(f, "standard::size",
3678 G_FILE_QUERY_INFO_NONE, NULL, &error);
3679 ret = 0;
3680 if (error != NULL) {
3681 g_warning(error->message);
3682 ret = 1;
3683 g_error_free(error);
3684 g_object_unref(f);
3686 #else
3687 ret = g_stat(file, &file_stat);
3688 #endif
3689 if (ret != 0) {
3690 gchar *shortfile = g_path_get_basename(file);
3691 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3692 g_free(shortfile);
3693 return COMPOSE_INSERT_NO_FILE;
3694 } else if (prefs_common.warn_large_insert == TRUE) {
3695 #ifdef G_OS_WIN32
3696 size = g_file_info_get_size(fi);
3697 g_object_unref(fi);
3698 g_object_unref(f);
3699 #else
3700 size = file_stat.st_size;
3701 #endif
3703 /* ask user for confirmation if the file is large */
3704 if (prefs_common.warn_large_insert_size < 0 ||
3705 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3706 AlertValue aval;
3707 gchar *msg;
3709 msg = g_strdup_printf(_("You are about to insert a file of %s "
3710 "in the message body. Are you sure you want to do that?"),
3711 to_human_readable(size));
3712 aval = alertpanel_full(_("Are you sure?"), msg, NULL, _("_Cancel"),
3713 NULL, _("_Insert"), NULL, NULL, ALERTFOCUS_SECOND, TRUE,
3714 NULL, ALERT_QUESTION);
3715 g_free(msg);
3717 /* do we ask for confirmation next time? */
3718 if (aval & G_ALERTDISABLE) {
3719 /* no confirmation next time, disable feature in preferences */
3720 aval &= ~G_ALERTDISABLE;
3721 prefs_common.warn_large_insert = FALSE;
3724 /* abort file insertion if user canceled action */
3725 if (aval != G_ALERTALTERNATE) {
3726 return COMPOSE_INSERT_NO_FILE;
3732 if ((fp = claws_fopen(file, "rb")) == NULL) {
3733 FILE_OP_ERROR(file, "claws_fopen");
3734 return COMPOSE_INSERT_READ_ERROR;
3737 prev_autowrap = compose->autowrap;
3738 compose->autowrap = FALSE;
3740 text = GTK_TEXT_VIEW(compose->text);
3741 buffer = gtk_text_view_get_buffer(text);
3742 mark = gtk_text_buffer_get_insert(buffer);
3743 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3745 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3746 G_CALLBACK(text_inserted),
3747 compose);
3749 cur_encoding = conv_get_locale_charset_str_no_utf8();
3751 file_contents = g_string_new("");
3752 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3753 gchar *str;
3755 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3756 str = g_strdup(buf);
3757 else {
3758 codeconv_set_strict(TRUE);
3759 str = conv_codeset_strdup
3760 (buf, cur_encoding, CS_INTERNAL);
3761 codeconv_set_strict(FALSE);
3763 if (!str) {
3764 result = COMPOSE_INSERT_INVALID_CHARACTER;
3765 break;
3768 if (!str) continue;
3770 /* strip <CR> if DOS/Windows file,
3771 replace <CR> with <LF> if Macintosh file. */
3772 strcrchomp(str);
3773 len = strlen(str);
3774 if (len > 0 && str[len - 1] != '\n') {
3775 while (--len >= 0)
3776 if (str[len] == '\r') str[len] = '\n';
3779 file_contents = g_string_append(file_contents, str);
3780 g_free(str);
3783 if (result == COMPOSE_INSERT_SUCCESS) {
3784 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3786 compose_changed_cb(NULL, compose);
3787 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3788 G_CALLBACK(text_inserted),
3789 compose);
3790 compose->autowrap = prev_autowrap;
3791 if (compose->autowrap)
3792 compose_wrap_all(compose);
3793 } else {
3794 gchar *filename = g_path_get_basename(file);
3795 debug_print("Can't insert file '%s' (invalid character)\n", filename);
3796 g_free(filename);
3799 g_string_free(file_contents, TRUE);
3800 claws_fclose(fp);
3802 return result;
3805 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3806 const gchar *filename,
3807 const gchar *content_type,
3808 const gchar *charset)
3810 AttachInfo *ainfo;
3811 GtkTreeIter iter;
3812 FILE *fp;
3813 off_t size;
3814 GAuto *auto_ainfo;
3815 gchar *size_text;
3816 GtkListStore *store;
3817 gchar *name;
3818 gboolean has_binary = FALSE;
3820 if (!is_file_exist(file)) {
3821 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3822 gboolean result = FALSE;
3823 if (file_from_uri && is_file_exist(file_from_uri)) {
3824 result = compose_attach_append(
3825 compose, file_from_uri,
3826 filename, content_type,
3827 charset);
3829 g_free(file_from_uri);
3830 if (result)
3831 return TRUE;
3832 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3833 return FALSE;
3835 if ((size = get_file_size(file)) < 0) {
3836 alertpanel_error("Can't get file size of %s\n", filename);
3837 return FALSE;
3840 /* In batch mode, we allow 0-length files to be attached no questions asked */
3841 if (size == 0 && !compose->batch) {
3842 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3843 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3844 NULL, _("_Cancel"), NULL, _("_Attach anyway"),
3845 NULL, NULL, ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3846 g_free(msg);
3848 if (aval != G_ALERTALTERNATE) {
3849 return FALSE;
3852 if ((fp = claws_fopen(file, "rb")) == NULL) {
3853 alertpanel_error(_("Can't read %s."), filename);
3854 return FALSE;
3856 claws_fclose(fp);
3858 ainfo = g_new0(AttachInfo, 1);
3859 auto_ainfo = g_auto_pointer_new_with_free
3860 (ainfo, (GFreeFunc) compose_attach_info_free);
3861 ainfo->file = g_strdup(file);
3863 if (content_type) {
3864 ainfo->content_type = g_strdup(content_type);
3865 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3866 MsgInfo *msginfo;
3867 MsgFlags flags = {0, 0};
3869 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3870 ainfo->encoding = ENC_7BIT;
3871 else
3872 ainfo->encoding = ENC_8BIT;
3874 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3875 if (msginfo && msginfo->subject)
3876 name = g_strdup(msginfo->subject);
3877 else
3878 name = g_path_get_basename(filename ? filename : file);
3880 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3882 procmsg_msginfo_free(&msginfo);
3883 } else {
3884 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3885 ainfo->charset = g_strdup(charset);
3886 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3887 } else {
3888 ainfo->encoding = ENC_BASE64;
3890 name = g_path_get_basename(filename ? filename : file);
3891 ainfo->name = g_strdup(name);
3893 g_free(name);
3894 } else {
3895 ainfo->content_type = procmime_get_mime_type(file);
3896 if (!ainfo->content_type) {
3897 ainfo->content_type =
3898 g_strdup("application/octet-stream");
3899 ainfo->encoding = ENC_BASE64;
3900 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3901 ainfo->encoding =
3902 procmime_get_encoding_for_text_file(file, &has_binary);
3903 else
3904 ainfo->encoding = ENC_BASE64;
3905 name = g_path_get_basename(filename ? filename : file);
3906 ainfo->name = g_strdup(name);
3907 g_free(name);
3910 if (ainfo->name != NULL
3911 && !strcmp(ainfo->name, ".")) {
3912 g_free(ainfo->name);
3913 ainfo->name = NULL;
3916 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3917 g_free(ainfo->content_type);
3918 ainfo->content_type = g_strdup("application/octet-stream");
3919 g_free(ainfo->charset);
3920 ainfo->charset = NULL;
3923 ainfo->size = (goffset)size;
3924 size_text = to_human_readable((goffset)size);
3926 store = GTK_LIST_STORE(gtk_tree_view_get_model
3927 (GTK_TREE_VIEW(compose->attach_clist)));
3929 gtk_list_store_append(store, &iter);
3930 gtk_list_store_set(store, &iter,
3931 COL_MIMETYPE, ainfo->content_type,
3932 COL_SIZE, size_text,
3933 COL_NAME, ainfo->name,
3934 COL_CHARSET, ainfo->charset,
3935 COL_DATA, ainfo,
3936 COL_AUTODATA, auto_ainfo,
3937 -1);
3939 g_auto_pointer_free(auto_ainfo);
3940 compose_attach_update_label(compose);
3941 return TRUE;
3944 void compose_use_signing(Compose *compose, gboolean use_signing)
3946 compose->use_signing = use_signing;
3947 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3950 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3952 compose->use_encryption = use_encryption;
3953 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3956 #define NEXT_PART_NOT_CHILD(info) \
3958 node = info->node; \
3959 while (node->children) \
3960 node = g_node_last_child(node); \
3961 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3964 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3966 MimeInfo *mimeinfo;
3967 MimeInfo *child;
3968 MimeInfo *firsttext = NULL;
3969 MimeInfo *encrypted = NULL;
3970 GNode *node;
3971 gchar *outfile;
3972 const gchar *partname = NULL;
3974 mimeinfo = procmime_scan_message(msginfo);
3975 if (!mimeinfo) return;
3977 if (mimeinfo->node->children == NULL) {
3978 procmime_mimeinfo_free_all(&mimeinfo);
3979 return;
3982 /* find first content part */
3983 child = (MimeInfo *) mimeinfo->node->children->data;
3984 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3985 child = (MimeInfo *)child->node->children->data;
3987 if (child) {
3988 if (child->type == MIMETYPE_TEXT) {
3989 firsttext = child;
3990 debug_print("First text part found\n");
3991 } else if (compose->mode == COMPOSE_REEDIT &&
3992 child->type == MIMETYPE_APPLICATION &&
3993 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3994 encrypted = (MimeInfo *)child->node->parent->data;
3997 child = (MimeInfo *) mimeinfo->node->children->data;
3998 while (child != NULL) {
3999 gint err;
4001 if (child == encrypted) {
4002 /* skip this part of tree */
4003 NEXT_PART_NOT_CHILD(child);
4004 continue;
4007 if (child->type == MIMETYPE_MULTIPART) {
4008 /* get the actual content */
4009 child = procmime_mimeinfo_next(child);
4010 continue;
4013 if (child == firsttext) {
4014 child = procmime_mimeinfo_next(child);
4015 continue;
4018 outfile = procmime_get_tmp_file_name(child);
4019 if ((err = procmime_get_part(outfile, child)) < 0)
4020 g_warning("can't get the part of multipart message. (%s)", g_strerror(-err));
4021 else {
4022 gchar *content_type;
4024 content_type = procmime_get_content_type_str(child->type, child->subtype);
4026 /* if we meet a pgp signature, we don't attach it, but
4027 * we force signing. */
4028 if ((strcmp(content_type, "application/pgp-signature") &&
4029 strcmp(content_type, "application/pkcs7-signature") &&
4030 strcmp(content_type, "application/x-pkcs7-signature"))
4031 || compose->mode == COMPOSE_REDIRECT) {
4032 partname = procmime_mimeinfo_get_parameter(child, "filename");
4033 if (partname == NULL)
4034 partname = procmime_mimeinfo_get_parameter(child, "name");
4035 if (partname == NULL)
4036 partname = "";
4037 compose_attach_append(compose, outfile,
4038 partname, content_type,
4039 procmime_mimeinfo_get_parameter(child, "charset"));
4040 } else {
4041 compose_force_signing(compose, compose->account, NULL);
4043 g_free(content_type);
4045 g_free(outfile);
4046 NEXT_PART_NOT_CHILD(child);
4048 procmime_mimeinfo_free_all(&mimeinfo);
4051 #undef NEXT_PART_NOT_CHILD
4055 typedef enum {
4056 WAIT_FOR_INDENT_CHAR,
4057 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4058 } IndentState;
4060 /* return indent length, we allow:
4061 indent characters followed by indent characters or spaces/tabs,
4062 alphabets and numbers immediately followed by indent characters,
4063 and the repeating sequences of the above
4064 If quote ends with multiple spaces, only the first one is included. */
4065 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4066 const GtkTextIter *start, gint *len)
4068 GtkTextIter iter = *start;
4069 gunichar wc;
4070 gchar ch[6];
4071 gint clen;
4072 IndentState state = WAIT_FOR_INDENT_CHAR;
4073 gboolean is_space;
4074 gboolean is_indent;
4075 gint alnum_count = 0;
4076 gint space_count = 0;
4077 gint quote_len = 0;
4079 if (prefs_common.quote_chars == NULL) {
4080 return 0 ;
4083 while (!gtk_text_iter_ends_line(&iter)) {
4084 wc = gtk_text_iter_get_char(&iter);
4085 if (g_unichar_iswide(wc))
4086 break;
4087 clen = g_unichar_to_utf8(wc, ch);
4088 if (clen != 1)
4089 break;
4091 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4092 is_space = g_unichar_isspace(wc);
4094 if (state == WAIT_FOR_INDENT_CHAR) {
4095 if (!is_indent && !g_unichar_isalnum(wc))
4096 break;
4097 if (is_indent) {
4098 quote_len += alnum_count + space_count + 1;
4099 alnum_count = space_count = 0;
4100 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4101 } else
4102 alnum_count++;
4103 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4104 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4105 break;
4106 if (is_space)
4107 space_count++;
4108 else if (is_indent) {
4109 quote_len += alnum_count + space_count + 1;
4110 alnum_count = space_count = 0;
4111 } else {
4112 alnum_count++;
4113 state = WAIT_FOR_INDENT_CHAR;
4117 gtk_text_iter_forward_char(&iter);
4120 if (quote_len > 0 && space_count > 0)
4121 quote_len++;
4123 if (len)
4124 *len = quote_len;
4126 if (quote_len > 0) {
4127 iter = *start;
4128 gtk_text_iter_forward_chars(&iter, quote_len);
4129 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4132 return NULL;
4135 /* return >0 if the line is itemized */
4136 static int compose_itemized_length(GtkTextBuffer *buffer,
4137 const GtkTextIter *start)
4139 GtkTextIter iter = *start;
4140 gunichar wc;
4141 gchar ch[6];
4142 gint clen;
4143 gint len = 0;
4144 if (gtk_text_iter_ends_line(&iter))
4145 return 0;
4147 while (1) {
4148 len++;
4149 wc = gtk_text_iter_get_char(&iter);
4150 if (!g_unichar_isspace(wc))
4151 break;
4152 gtk_text_iter_forward_char(&iter);
4153 if (gtk_text_iter_ends_line(&iter))
4154 return 0;
4157 clen = g_unichar_to_utf8(wc, ch);
4158 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4159 (clen == 3 && (
4160 wc == 0x2022 || /* BULLET */
4161 wc == 0x2023 || /* TRIANGULAR BULLET */
4162 wc == 0x2043 || /* HYPHEN BULLET */
4163 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4164 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4165 wc == 0x2219 || /* BULLET OPERATOR */
4166 wc == 0x25d8 || /* INVERSE BULLET */
4167 wc == 0x25e6 || /* WHITE BULLET */
4168 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4169 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4170 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4171 wc == 0x29be || /* CIRCLED WHITE BULLET */
4172 wc == 0x29bf /* CIRCLED BULLET */
4173 ))))
4174 return 0;
4176 gtk_text_iter_forward_char(&iter);
4177 if (gtk_text_iter_ends_line(&iter))
4178 return 0;
4179 wc = gtk_text_iter_get_char(&iter);
4180 if (g_unichar_isspace(wc)) {
4181 return len+1;
4183 return 0;
4186 /* return the string at the start of the itemization */
4187 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4188 const GtkTextIter *start)
4190 GtkTextIter iter = *start;
4191 gunichar wc;
4192 gint len = 0;
4193 GString *item_chars = g_string_new("");
4195 if (gtk_text_iter_ends_line(&iter)) {
4196 g_string_free(item_chars, TRUE);
4197 return NULL;
4200 while (1) {
4201 len++;
4202 wc = gtk_text_iter_get_char(&iter);
4203 if (!g_unichar_isspace(wc))
4204 break;
4205 gtk_text_iter_forward_char(&iter);
4206 if (gtk_text_iter_ends_line(&iter))
4207 break;
4208 g_string_append_unichar(item_chars, wc);
4211 return g_string_free(item_chars, FALSE);
4214 /* return the number of spaces at a line's start */
4215 static int compose_left_offset_length(GtkTextBuffer *buffer,
4216 const GtkTextIter *start)
4218 GtkTextIter iter = *start;
4219 gunichar wc;
4220 gint len = 0;
4221 if (gtk_text_iter_ends_line(&iter))
4222 return 0;
4224 while (1) {
4225 wc = gtk_text_iter_get_char(&iter);
4226 if (!g_unichar_isspace(wc))
4227 break;
4228 len++;
4229 gtk_text_iter_forward_char(&iter);
4230 if (gtk_text_iter_ends_line(&iter))
4231 return 0;
4234 gtk_text_iter_forward_char(&iter);
4235 if (gtk_text_iter_ends_line(&iter))
4236 return 0;
4237 return len;
4240 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4241 const GtkTextIter *start,
4242 GtkTextIter *break_pos,
4243 gint max_col,
4244 gint quote_len)
4246 GtkTextIter iter = *start, line_end = *start;
4247 PangoLogAttr *attrs;
4248 gchar *str;
4249 gchar *p;
4250 gint len;
4251 gint i;
4252 gint col = 0;
4253 gint pos = 0;
4254 gboolean can_break = FALSE;
4255 gboolean do_break = FALSE;
4256 gboolean was_white = FALSE;
4257 gboolean prev_dont_break = FALSE;
4259 gtk_text_iter_forward_to_line_end(&line_end);
4260 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4261 len = g_utf8_strlen(str, -1);
4263 if (len == 0) {
4264 g_free(str);
4265 g_warning("compose_get_line_break_pos: len = 0!");
4266 return FALSE;
4269 /* g_print("breaking line: %d: %s (len = %d)\n",
4270 gtk_text_iter_get_line(&iter), str, len); */
4272 attrs = g_new(PangoLogAttr, len + 1);
4274 pango_default_break(str, -1, NULL, attrs, len + 1);
4276 p = str;
4278 /* skip quote and leading spaces */
4279 for (i = 0; *p != '\0' && i < len; i++) {
4280 gunichar wc;
4282 wc = g_utf8_get_char(p);
4283 if (i >= quote_len && !g_unichar_isspace(wc))
4284 break;
4285 if (g_unichar_iswide(wc))
4286 col += 2;
4287 else if (*p == '\t')
4288 col += 8;
4289 else
4290 col++;
4291 p = g_utf8_next_char(p);
4294 for (; *p != '\0' && i < len; i++) {
4295 PangoLogAttr *attr = attrs + i;
4296 gunichar wc = g_utf8_get_char(p);
4297 gint uri_len;
4299 /* attr->is_line_break will be false for some characters that
4300 * we want to break a line before, like '/' or ':', so we
4301 * also allow breaking on any non-wide character. The
4302 * mentioned pango attribute is still useful to decide on
4303 * line breaks when wide characters are involved. */
4304 if ((!g_unichar_iswide(wc) || attr->is_line_break)
4305 && can_break && was_white && !prev_dont_break)
4306 pos = i;
4308 was_white = attr->is_white;
4310 /* don't wrap URI */
4311 if ((uri_len = get_uri_len(p)) > 0) {
4312 col += uri_len;
4313 if (pos > 0 && col > max_col) {
4314 do_break = TRUE;
4315 break;
4317 i += uri_len - 1;
4318 p += uri_len;
4319 can_break = TRUE;
4320 continue;
4323 if (g_unichar_iswide(wc)) {
4324 col += 2;
4325 if (prev_dont_break && can_break && attr->is_line_break)
4326 pos = i;
4327 } else if (*p == '\t')
4328 col += 8;
4329 else
4330 col++;
4331 if (pos > 0 && col > max_col) {
4332 do_break = TRUE;
4333 break;
4336 if (*p == '-' || *p == '/')
4337 prev_dont_break = TRUE;
4338 else
4339 prev_dont_break = FALSE;
4341 p = g_utf8_next_char(p);
4342 can_break = TRUE;
4345 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4347 g_free(attrs);
4348 g_free(str);
4350 *break_pos = *start;
4351 gtk_text_iter_set_line_offset(break_pos, pos);
4353 return do_break;
4356 static gboolean compose_join_next_line(Compose *compose,
4357 GtkTextBuffer *buffer,
4358 GtkTextIter *iter,
4359 const gchar *quote_str)
4361 GtkTextIter iter_ = *iter, cur, prev, next, end;
4362 PangoLogAttr attrs[3];
4363 gchar *str;
4364 gchar *next_quote_str;
4365 gunichar wc1, wc2;
4366 gint quote_len;
4367 gboolean keep_cursor = FALSE;
4369 if (!gtk_text_iter_forward_line(&iter_) ||
4370 gtk_text_iter_ends_line(&iter_)) {
4371 return FALSE;
4373 next_quote_str = compose_get_quote_str(buffer, &iter_, &quote_len);
4375 if ((quote_str || next_quote_str) &&
4376 g_strcmp0(quote_str, next_quote_str) != 0) {
4377 g_free(next_quote_str);
4378 return FALSE;
4380 g_free(next_quote_str);
4382 end = iter_;
4383 if (quote_len > 0) {
4384 gtk_text_iter_forward_chars(&end, quote_len);
4385 if (gtk_text_iter_ends_line(&end)) {
4386 return FALSE;
4390 /* don't join itemized lines */
4391 if (compose_itemized_length(buffer, &end) > 0) {
4392 return FALSE;
4395 /* don't join signature separator */
4396 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4397 return FALSE;
4399 /* delete quote str */
4400 if (quote_len > 0)
4401 gtk_text_buffer_delete(buffer, &iter_, &end);
4403 /* don't join line breaks put by the user */
4404 prev = cur = iter_;
4405 gtk_text_iter_backward_char(&cur);
4406 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4407 gtk_text_iter_forward_char(&cur);
4408 *iter = cur;
4409 return FALSE;
4411 gtk_text_iter_forward_char(&cur);
4412 /* delete linebreak and extra spaces */
4413 while (gtk_text_iter_backward_char(&cur)) {
4414 wc1 = gtk_text_iter_get_char(&cur);
4415 if (!g_unichar_isspace(wc1))
4416 break;
4417 prev = cur;
4419 next = cur = iter_;
4420 while (!gtk_text_iter_ends_line(&cur)) {
4421 wc1 = gtk_text_iter_get_char(&cur);
4422 if (!g_unichar_isspace(wc1))
4423 break;
4424 gtk_text_iter_forward_char(&cur);
4425 next = cur;
4427 if (!gtk_text_iter_equal(&prev, &next)) {
4428 GtkTextMark *mark;
4430 mark = gtk_text_buffer_get_insert(buffer);
4431 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4432 if (gtk_text_iter_equal(&prev, &cur))
4433 keep_cursor = TRUE;
4434 gtk_text_buffer_delete(buffer, &prev, &next);
4436 iter_ = prev;
4438 /* insert space if required */
4439 gtk_text_iter_backward_char(&prev);
4440 wc1 = gtk_text_iter_get_char(&prev);
4441 wc2 = gtk_text_iter_get_char(&next);
4442 gtk_text_iter_forward_char(&next);
4443 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4444 pango_default_break(str, -1, NULL, attrs, 3);
4445 if (!attrs[1].is_line_break ||
4446 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4447 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4448 if (keep_cursor) {
4449 gtk_text_iter_backward_char(&iter_);
4450 gtk_text_buffer_place_cursor(buffer, &iter_);
4453 g_free(str);
4455 *iter = iter_;
4456 return TRUE;
4459 #define ADD_TXT_POS(bp_, ep_, pti_) \
4460 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4461 last = last->next; \
4462 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4463 last->next = NULL; \
4464 } else { \
4465 g_warning("alloc error scanning URIs"); \
4468 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4470 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4471 GtkTextBuffer *buffer;
4472 GtkTextIter iter, break_pos, end_of_line;
4473 gchar *quote_str = NULL;
4474 gint quote_len;
4475 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4476 gboolean prev_autowrap = compose->autowrap;
4477 gint startq_offset = -1, noq_offset = -1;
4478 gint uri_start = -1, uri_stop = -1;
4479 gint nouri_start = -1, nouri_stop = -1;
4480 gint num_blocks = 0;
4481 gint quotelevel = -1;
4482 gboolean modified = force;
4483 gboolean removed = FALSE;
4484 gboolean modified_before_remove = FALSE;
4485 gint lines = 0;
4486 gboolean start = TRUE;
4487 gint itemized_len = 0, rem_item_len = 0;
4488 gchar *itemized_chars = NULL;
4489 gboolean item_continuation = FALSE;
4491 if (force) {
4492 modified = TRUE;
4494 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4495 modified = TRUE;
4498 compose->autowrap = FALSE;
4500 buffer = gtk_text_view_get_buffer(text);
4501 undo_wrapping(compose->undostruct, TRUE);
4502 if (par_iter) {
4503 iter = *par_iter;
4504 } else {
4505 GtkTextMark *mark;
4506 mark = gtk_text_buffer_get_insert(buffer);
4507 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4511 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4512 if (gtk_text_iter_ends_line(&iter)) {
4513 while (gtk_text_iter_ends_line(&iter) &&
4514 gtk_text_iter_forward_line(&iter))
4516 } else {
4517 while (gtk_text_iter_backward_line(&iter)) {
4518 if (gtk_text_iter_ends_line(&iter)) {
4519 gtk_text_iter_forward_line(&iter);
4520 break;
4524 } else {
4525 /* move to line start */
4526 gtk_text_iter_set_line_offset(&iter, 0);
4529 itemized_len = compose_itemized_length(buffer, &iter);
4531 if (!itemized_len) {
4532 itemized_len = compose_left_offset_length(buffer, &iter);
4533 item_continuation = TRUE;
4536 if (itemized_len)
4537 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4539 /* go until paragraph end (empty line) */
4540 while (start || !gtk_text_iter_ends_line(&iter)) {
4541 gchar *scanpos = NULL;
4542 /* parse table - in order of priority */
4543 struct table {
4544 const gchar *needle; /* token */
4546 /* token search function */
4547 gchar *(*search) (const gchar *haystack,
4548 const gchar *needle);
4549 /* part parsing function */
4550 gboolean (*parse) (const gchar *start,
4551 const gchar *scanpos,
4552 const gchar **bp_,
4553 const gchar **ep_,
4554 gboolean hdr);
4555 /* part to URI function */
4556 gchar *(*build_uri) (const gchar *bp,
4557 const gchar *ep);
4560 static struct table parser[] = {
4561 {"http://", strcasestr, get_uri_part, make_uri_string},
4562 {"https://", strcasestr, get_uri_part, make_uri_string},
4563 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4564 {"ftps://", strcasestr, get_uri_part, make_uri_string},
4565 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4566 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4567 {"www.", strcasestr, get_uri_part, make_http_string},
4568 {"webcal://",strcasestr, get_uri_part, make_uri_string},
4569 {"webcals://",strcasestr, get_uri_part, make_uri_string},
4570 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4571 {"@", strcasestr, get_email_part, make_email_string}
4573 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4574 gint last_index = PARSE_ELEMS;
4575 gint n;
4576 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4577 gint walk_pos;
4579 start = FALSE;
4580 if (!prev_autowrap && num_blocks == 0) {
4581 num_blocks++;
4582 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4583 G_CALLBACK(text_inserted),
4584 compose);
4586 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4587 goto colorize;
4589 uri_start = uri_stop = -1;
4590 quote_len = 0;
4591 quote_str = compose_get_quote_str(buffer, &iter, &quote_len);
4593 if (quote_str) {
4594 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4595 if (startq_offset == -1)
4596 startq_offset = gtk_text_iter_get_offset(&iter);
4597 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4598 if (quotelevel > 2) {
4599 /* recycle colors */
4600 if (prefs_common.recycle_quote_colors)
4601 quotelevel %= 3;
4602 else
4603 quotelevel = 2;
4605 if (!wrap_quote) {
4606 goto colorize;
4608 } else {
4609 if (startq_offset == -1)
4610 noq_offset = gtk_text_iter_get_offset(&iter);
4611 quotelevel = -1;
4614 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4615 goto colorize;
4617 if (gtk_text_iter_ends_line(&iter)) {
4618 goto colorize;
4619 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4620 prefs_common.linewrap_len,
4621 quote_len)) {
4622 GtkTextIter prev, next, cur;
4623 if (prev_autowrap != FALSE || force) {
4624 compose->automatic_break = TRUE;
4625 modified = TRUE;
4626 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4627 compose->automatic_break = FALSE;
4628 if (itemized_len && compose->autoindent) {
4629 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4630 if (!item_continuation)
4631 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4633 } else if (quote_str && wrap_quote) {
4634 compose->automatic_break = TRUE;
4635 modified = TRUE;
4636 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4637 compose->automatic_break = FALSE;
4638 if (itemized_len && compose->autoindent) {
4639 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4640 if (!item_continuation)
4641 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4643 } else
4644 goto colorize;
4645 /* remove trailing spaces */
4646 cur = break_pos;
4647 rem_item_len = itemized_len;
4648 while (compose->autoindent && rem_item_len-- > 0)
4649 gtk_text_iter_backward_char(&cur);
4650 gtk_text_iter_backward_char(&cur);
4652 prev = next = cur;
4653 while (!gtk_text_iter_starts_line(&cur)) {
4654 gunichar wc;
4656 gtk_text_iter_backward_char(&cur);
4657 wc = gtk_text_iter_get_char(&cur);
4658 if (!g_unichar_isspace(wc))
4659 break;
4660 prev = cur;
4662 if (!gtk_text_iter_equal(&prev, &next)) {
4663 gtk_text_buffer_delete(buffer, &prev, &next);
4664 break_pos = next;
4665 gtk_text_iter_forward_char(&break_pos);
4668 if (quote_str)
4669 gtk_text_buffer_insert(buffer, &break_pos,
4670 quote_str, -1);
4672 iter = break_pos;
4673 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4675 /* move iter to current line start */
4676 gtk_text_iter_set_line_offset(&iter, 0);
4677 if (quote_str) {
4678 g_free(quote_str);
4679 quote_str = NULL;
4681 continue;
4682 } else {
4683 /* move iter to next line start */
4684 iter = break_pos;
4685 lines++;
4688 colorize:
4689 if (!prev_autowrap && num_blocks > 0) {
4690 num_blocks--;
4691 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4692 G_CALLBACK(text_inserted),
4693 compose);
4695 end_of_line = iter;
4696 while (!gtk_text_iter_ends_line(&end_of_line)) {
4697 gtk_text_iter_forward_char(&end_of_line);
4699 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4701 nouri_start = gtk_text_iter_get_offset(&iter);
4702 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4704 walk_pos = gtk_text_iter_get_offset(&iter);
4705 /* FIXME: this looks phony. scanning for anything in the parse table */
4706 for (n = 0; n < PARSE_ELEMS; n++) {
4707 gchar *tmp;
4709 tmp = parser[n].search(walk, parser[n].needle);
4710 if (tmp) {
4711 if (scanpos == NULL || tmp < scanpos) {
4712 scanpos = tmp;
4713 last_index = n;
4718 bp = ep = 0;
4719 if (scanpos) {
4720 /* check if URI can be parsed */
4721 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4722 (const gchar **)&ep, FALSE)
4723 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4724 walk = ep;
4725 } else
4726 walk = scanpos +
4727 strlen(parser[last_index].needle);
4729 if (bp && ep) {
4730 uri_start = walk_pos + (bp - o_walk);
4731 uri_stop = walk_pos + (ep - o_walk);
4733 g_free(o_walk);
4734 o_walk = NULL;
4735 gtk_text_iter_forward_line(&iter);
4736 g_free(quote_str);
4737 quote_str = NULL;
4738 if (startq_offset != -1) {
4739 GtkTextIter startquote, endquote;
4740 gtk_text_buffer_get_iter_at_offset(
4741 buffer, &startquote, startq_offset);
4742 endquote = iter;
4744 switch (quotelevel) {
4745 case 0:
4746 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4747 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4748 gtk_text_buffer_apply_tag_by_name(
4749 buffer, "quote0", &startquote, &endquote);
4750 gtk_text_buffer_remove_tag_by_name(
4751 buffer, "quote1", &startquote, &endquote);
4752 gtk_text_buffer_remove_tag_by_name(
4753 buffer, "quote2", &startquote, &endquote);
4754 modified = TRUE;
4756 break;
4757 case 1:
4758 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4759 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4760 gtk_text_buffer_apply_tag_by_name(
4761 buffer, "quote1", &startquote, &endquote);
4762 gtk_text_buffer_remove_tag_by_name(
4763 buffer, "quote0", &startquote, &endquote);
4764 gtk_text_buffer_remove_tag_by_name(
4765 buffer, "quote2", &startquote, &endquote);
4766 modified = TRUE;
4768 break;
4769 case 2:
4770 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4771 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4772 gtk_text_buffer_apply_tag_by_name(
4773 buffer, "quote2", &startquote, &endquote);
4774 gtk_text_buffer_remove_tag_by_name(
4775 buffer, "quote0", &startquote, &endquote);
4776 gtk_text_buffer_remove_tag_by_name(
4777 buffer, "quote1", &startquote, &endquote);
4778 modified = TRUE;
4780 break;
4782 startq_offset = -1;
4783 } else if (noq_offset != -1) {
4784 GtkTextIter startnoquote, endnoquote;
4785 gtk_text_buffer_get_iter_at_offset(
4786 buffer, &startnoquote, noq_offset);
4787 endnoquote = iter;
4789 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4790 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4791 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4792 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4793 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4794 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4795 gtk_text_buffer_remove_tag_by_name(
4796 buffer, "quote0", &startnoquote, &endnoquote);
4797 gtk_text_buffer_remove_tag_by_name(
4798 buffer, "quote1", &startnoquote, &endnoquote);
4799 gtk_text_buffer_remove_tag_by_name(
4800 buffer, "quote2", &startnoquote, &endnoquote);
4801 modified = TRUE;
4803 noq_offset = -1;
4806 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4807 GtkTextIter nouri_start_iter, nouri_end_iter;
4808 gtk_text_buffer_get_iter_at_offset(
4809 buffer, &nouri_start_iter, nouri_start);
4810 gtk_text_buffer_get_iter_at_offset(
4811 buffer, &nouri_end_iter, nouri_stop);
4812 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4813 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4814 gtk_text_buffer_remove_tag_by_name(
4815 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4816 modified_before_remove = modified;
4817 modified = TRUE;
4818 removed = TRUE;
4821 if (uri_start >= 0 && uri_stop > 0) {
4822 GtkTextIter uri_start_iter, uri_end_iter, back;
4823 gtk_text_buffer_get_iter_at_offset(
4824 buffer, &uri_start_iter, uri_start);
4825 gtk_text_buffer_get_iter_at_offset(
4826 buffer, &uri_end_iter, uri_stop);
4827 back = uri_end_iter;
4828 gtk_text_iter_backward_char(&back);
4829 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4830 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4831 gtk_text_buffer_apply_tag_by_name(
4832 buffer, "link", &uri_start_iter, &uri_end_iter);
4833 modified = TRUE;
4834 if (removed && !modified_before_remove) {
4835 modified = FALSE;
4839 if (!modified) {
4840 /* debug_print("not modified, out after %d lines\n", lines); */
4841 goto end;
4844 /* debug_print("modified, out after %d lines\n", lines); */
4845 end:
4846 g_free(itemized_chars);
4847 if (par_iter)
4848 *par_iter = iter;
4849 undo_wrapping(compose->undostruct, FALSE);
4850 compose->autowrap = prev_autowrap;
4852 return modified;
4855 void compose_action_cb(void *data)
4857 Compose *compose = (Compose *)data;
4858 compose_wrap_all(compose);
4861 static void compose_wrap_all(Compose *compose)
4863 compose_wrap_all_full(compose, FALSE);
4866 static void compose_wrap_all_full(Compose *compose, gboolean force)
4868 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4869 GtkTextBuffer *buffer;
4870 GtkTextIter iter;
4871 gboolean modified = TRUE;
4873 buffer = gtk_text_view_get_buffer(text);
4875 gtk_text_buffer_get_start_iter(buffer, &iter);
4877 undo_wrapping(compose->undostruct, TRUE);
4879 while (!gtk_text_iter_is_end(&iter) && modified)
4880 modified = compose_beautify_paragraph(compose, &iter, force);
4882 undo_wrapping(compose->undostruct, FALSE);
4886 static void compose_set_title(Compose *compose)
4888 gchar *str;
4889 gchar *edited;
4890 gchar *subject;
4892 edited = compose->modified ? _(" [Edited]") : "";
4894 subject = gtk_editable_get_chars(
4895 GTK_EDITABLE(compose->subject_entry), 0, -1);
4897 #ifndef GENERIC_UMPC
4898 if (subject && strlen(subject))
4899 str = g_strdup_printf(_("%s - Compose message%s"),
4900 subject, edited);
4901 else
4902 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4903 #else
4904 str = g_strdup(_("Compose message"));
4905 #endif
4907 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4908 g_free(str);
4909 g_free(subject);
4913 * compose_current_mail_account:
4915 * Find a current mail account (the currently selected account, or the
4916 * default account, if a news account is currently selected). If a
4917 * mail account cannot be found, display an error message.
4919 * Return value: Mail account, or NULL if not found.
4921 static PrefsAccount *
4922 compose_current_mail_account(void)
4924 PrefsAccount *ac;
4926 if (cur_account && cur_account->protocol != A_NNTP)
4927 ac = cur_account;
4928 else {
4929 ac = account_get_default();
4930 if (!ac || ac->protocol == A_NNTP) {
4931 alertpanel_error(_("Account for sending mail is not specified.\n"
4932 "Please select a mail account before sending."));
4933 return NULL;
4936 return ac;
4939 #define QUOTE_IF_REQUIRED(out, str) \
4941 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4942 gchar *__tmp; \
4943 gint len; \
4945 len = strlen(str) + 3; \
4946 if ((__tmp = alloca(len)) == NULL) { \
4947 g_warning("can't allocate memory"); \
4948 g_string_free(header, TRUE); \
4949 return NULL; \
4951 g_snprintf(__tmp, len, "\"%s\"", str); \
4952 out = __tmp; \
4953 } else { \
4954 gchar *__tmp; \
4956 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4957 g_warning("can't allocate memory"); \
4958 g_string_free(header, TRUE); \
4959 return NULL; \
4960 } else \
4961 strcpy(__tmp, str); \
4963 out = __tmp; \
4967 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4969 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4970 gchar *__tmp; \
4971 gint len; \
4973 len = strlen(str) + 3; \
4974 if ((__tmp = alloca(len)) == NULL) { \
4975 g_warning("can't allocate memory"); \
4976 errret; \
4978 g_snprintf(__tmp, len, "\"%s\"", str); \
4979 out = __tmp; \
4980 } else { \
4981 gchar *__tmp; \
4983 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4984 g_warning("can't allocate memory"); \
4985 errret; \
4986 } else \
4987 strcpy(__tmp, str); \
4989 out = __tmp; \
4993 static void compose_select_account(Compose *compose, PrefsAccount *account,
4994 gboolean init)
4996 gchar *from = NULL, *header = NULL;
4997 ComposeHeaderEntry *header_entry;
4998 GtkTreeIter iter;
5000 cm_return_if_fail(account != NULL);
5002 compose->account = account;
5003 if (account->name && *account->name) {
5004 gchar *buf, *qbuf;
5005 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
5006 qbuf = escape_internal_quotes(buf, '"');
5007 from = g_strdup_printf("%s <%s>",
5008 qbuf, account->address);
5009 if (qbuf != buf)
5010 g_free(qbuf);
5011 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
5012 } else {
5013 from = g_strdup_printf("<%s>",
5014 account->address);
5015 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
5018 g_free(from);
5020 compose_set_title(compose);
5022 compose_activate_privacy_system(compose, account, FALSE);
5024 if (account->default_sign && privacy_system_can_sign(compose->privacy_system) &&
5025 compose->mode != COMPOSE_REDIRECT)
5026 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
5027 else
5028 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
5029 if (account->default_encrypt && privacy_system_can_encrypt(compose->privacy_system) &&
5030 compose->mode != COMPOSE_REDIRECT)
5031 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
5032 else
5033 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
5035 if (!init && compose->mode != COMPOSE_REDIRECT) {
5036 undo_block(compose->undostruct);
5037 compose_insert_sig(compose, TRUE);
5038 undo_unblock(compose->undostruct);
5041 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
5042 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
5043 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
5044 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5046 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5047 if (account->protocol == A_NNTP) {
5048 if (!strcmp(header, _("To:")))
5049 combobox_select_by_text(
5050 GTK_COMBO_BOX(header_entry->combo),
5051 _("Newsgroups:"));
5052 } else {
5053 if (!strcmp(header, _("Newsgroups:")))
5054 combobox_select_by_text(
5055 GTK_COMBO_BOX(header_entry->combo),
5056 _("To:"));
5060 g_free(header);
5062 #ifdef USE_ENCHANT
5063 /* use account's dict info if set */
5064 if (compose->gtkaspell) {
5065 if (account->enable_default_dictionary)
5066 gtkaspell_change_dict(compose->gtkaspell,
5067 account->default_dictionary, FALSE);
5068 if (account->enable_default_alt_dictionary)
5069 gtkaspell_change_alt_dict(compose->gtkaspell,
5070 account->default_alt_dictionary);
5071 if (account->enable_default_dictionary
5072 || account->enable_default_alt_dictionary)
5073 compose_spell_menu_changed(compose);
5075 #endif
5078 gboolean compose_check_for_valid_recipient(Compose *compose) {
5079 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5080 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5081 gboolean recipient_found = FALSE;
5082 GSList *list;
5083 gchar **strptr;
5085 /* free to and newsgroup list */
5086 slist_free_strings_full(compose->to_list);
5087 compose->to_list = NULL;
5089 slist_free_strings_full(compose->newsgroup_list);
5090 compose->newsgroup_list = NULL;
5092 /* search header entries for to and newsgroup entries */
5093 for (list = compose->header_list; list; list = list->next) {
5094 gchar *header;
5095 gchar *entry;
5096 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5097 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5098 g_strstrip(entry);
5099 g_strstrip(header);
5100 if (entry[0] != '\0') {
5101 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5102 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5103 compose->to_list = address_list_append(compose->to_list, entry);
5104 recipient_found = TRUE;
5107 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5108 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5109 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5110 recipient_found = TRUE;
5114 g_free(header);
5115 g_free(entry);
5117 return recipient_found;
5120 static gboolean compose_check_for_set_recipients(Compose *compose)
5122 if (compose->account->set_autocc && compose->account->auto_cc) {
5123 gboolean found_other = FALSE;
5124 GSList *list;
5125 /* search header entries for to and newsgroup entries */
5126 for (list = compose->header_list; list; list = list->next) {
5127 gchar *entry;
5128 gchar *header;
5129 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5130 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5131 g_strstrip(entry);
5132 g_strstrip(header);
5133 if (strcmp(entry, compose->account->auto_cc)
5134 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5135 found_other = TRUE;
5136 g_free(entry);
5137 break;
5139 g_free(entry);
5140 g_free(header);
5142 if (!found_other) {
5143 AlertValue aval;
5144 gchar *text;
5145 if (compose->batch) {
5146 gtk_widget_show_all(compose->window);
5148 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5149 prefs_common_translated_header_name("Cc"));
5150 aval = alertpanel(_("Send"),
5151 text,
5152 NULL, _("_Cancel"), NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND);
5153 g_free(text);
5154 if (aval != G_ALERTALTERNATE)
5155 return FALSE;
5158 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5159 gboolean found_other = FALSE;
5160 GSList *list;
5161 /* search header entries for to and newsgroup entries */
5162 for (list = compose->header_list; list; list = list->next) {
5163 gchar *entry;
5164 gchar *header;
5165 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5166 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5167 g_strstrip(entry);
5168 g_strstrip(header);
5169 if (strcmp(entry, compose->account->auto_bcc)
5170 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5171 found_other = TRUE;
5172 g_free(entry);
5173 g_free(header);
5174 break;
5176 g_free(entry);
5177 g_free(header);
5179 if (!found_other) {
5180 AlertValue aval;
5181 gchar *text;
5182 if (compose->batch) {
5183 gtk_widget_show_all(compose->window);
5185 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5186 prefs_common_translated_header_name("Bcc"));
5187 aval = alertpanel(_("Send"),
5188 text,
5189 NULL, _("_Cancel"), NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND);
5190 g_free(text);
5191 if (aval != G_ALERTALTERNATE)
5192 return FALSE;
5195 return TRUE;
5198 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5200 const gchar *str;
5202 if (compose_check_for_valid_recipient(compose) == FALSE) {
5203 if (compose->batch) {
5204 gtk_widget_show_all(compose->window);
5206 alertpanel_error(_("Recipient is not specified."));
5207 return FALSE;
5210 if (compose_check_for_set_recipients(compose) == FALSE) {
5211 return FALSE;
5214 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5215 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5216 if (*str == '\0' && check_everything == TRUE &&
5217 compose->mode != COMPOSE_REDIRECT) {
5218 AlertValue aval;
5219 gchar *message;
5221 message = g_strdup_printf(_("Subject is empty. %s"),
5222 compose->sending?_("Send it anyway?"):
5223 _("Queue it anyway?"));
5225 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5226 NULL, _("_Cancel"), NULL, compose->sending?_("_Send"):_("_Queue"),
5227 NULL, NULL, ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5228 g_free(message);
5229 if (aval & G_ALERTDISABLE) {
5230 aval &= ~G_ALERTDISABLE;
5231 prefs_common.warn_empty_subj = FALSE;
5233 if (aval != G_ALERTALTERNATE)
5234 return FALSE;
5238 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5239 && check_everything == TRUE) {
5240 GSList *list;
5241 gint cnt = 0;
5243 /* count To and Cc recipients */
5244 for (list = compose->header_list; list; list = list->next) {
5245 gchar *header;
5246 gchar *entry;
5248 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5249 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5250 g_strstrip(header);
5251 g_strstrip(entry);
5252 if ((entry[0] != '\0') &&
5253 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5254 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5255 cnt++;
5257 g_free(header);
5258 g_free(entry);
5260 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5261 AlertValue aval;
5262 gchar *message;
5264 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5265 compose->sending?_("Send it anyway?"):
5266 _("Queue it anyway?"));
5268 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5269 NULL, _("_Cancel"), NULL, compose->sending?_("_Send"):_("_Queue"),
5270 NULL, NULL, ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5271 g_free(message);
5272 if (aval & G_ALERTDISABLE) {
5273 aval &= ~G_ALERTDISABLE;
5274 prefs_common.warn_sending_many_recipients_num = 0;
5276 if (aval != G_ALERTALTERNATE)
5277 return FALSE;
5281 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5282 return FALSE;
5284 return TRUE;
5287 static void _display_queue_error(ComposeQueueResult val)
5289 switch (val) {
5290 case COMPOSE_QUEUE_SUCCESS:
5291 break;
5292 case COMPOSE_QUEUE_ERROR_NO_MSG:
5293 alertpanel_error(_("Could not queue message."));
5294 break;
5295 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5296 alertpanel_error(_("Could not queue message:\n\n%s."),
5297 g_strerror(errno));
5298 break;
5299 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5300 alertpanel_error(_("Could not queue message for sending:\n\n"
5301 "Signature failed: %s"),
5302 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5303 break;
5304 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5305 alertpanel_error(_("Could not queue message for sending:\n\n"
5306 "Encryption failed: %s"),
5307 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5308 break;
5309 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5310 alertpanel_error(_("Could not queue message for sending:\n\n"
5311 "Charset conversion failed."));
5312 break;
5313 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5314 alertpanel_error(_("Could not queue message for sending:\n\n"
5315 "Couldn't get recipient encryption key."));
5316 break;
5317 case COMPOSE_QUEUE_SIGNING_CANCELLED:
5318 debug_print("signing cancelled\n");
5319 break;
5320 default:
5321 /* unhandled error */
5322 debug_print("oops, unhandled compose_queue() return value %d\n",
5323 val);
5324 break;
5328 gint compose_send(Compose *compose)
5330 gint msgnum;
5331 FolderItem *folder = NULL;
5332 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5333 gchar *msgpath = NULL;
5334 gboolean discard_window = FALSE;
5335 gchar *errstr = NULL;
5336 gchar *tmsgid = NULL;
5337 MainWindow *mainwin = mainwindow_get_mainwindow();
5338 gboolean queued_removed = FALSE;
5340 if (prefs_common.send_dialog_invisible
5341 || compose->batch == TRUE)
5342 discard_window = TRUE;
5344 compose_allow_user_actions (compose, FALSE);
5345 compose->sending = TRUE;
5347 if (compose_check_entries(compose, TRUE) == FALSE) {
5348 if (compose->batch) {
5349 gtk_widget_show_all(compose->window);
5351 goto bail;
5354 inc_lock();
5355 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5357 if (val != COMPOSE_QUEUE_SUCCESS) {
5358 if (compose->batch) {
5359 gtk_widget_show_all(compose->window);
5362 _display_queue_error(val);
5364 goto bail;
5367 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5368 if (discard_window) {
5369 compose->sending = FALSE;
5370 compose_close(compose);
5371 /* No more compose access in the normal codepath
5372 * after this point! */
5373 compose = NULL;
5376 if (msgnum == 0) {
5377 alertpanel_error(_("The message was queued but could not be "
5378 "sent.\nUse \"Send queued messages\" from "
5379 "the main window to retry."));
5380 if (!discard_window) {
5381 goto bail;
5383 inc_unlock();
5384 g_free(tmsgid);
5385 return -1;
5387 if (msgpath == NULL) {
5388 msgpath = folder_item_fetch_msg(folder, msgnum);
5389 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5390 g_free(msgpath);
5391 } else {
5392 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5393 claws_unlink(msgpath);
5394 g_free(msgpath);
5396 if (!discard_window) {
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);
5413 if (val == 0) {
5414 if (!queued_removed)
5415 folder_item_remove_msg(folder, msgnum);
5416 folder_item_scan(folder);
5417 if (tmsgid) {
5418 /* make sure we delete that */
5419 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5420 if (tmp) {
5421 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5422 folder_item_remove_msg(folder, tmp->msgnum);
5423 procmsg_msginfo_free(&tmp);
5426 if (!discard_window) {
5427 compose->sending = FALSE;
5428 compose_allow_user_actions (compose, TRUE);
5429 compose_close(compose);
5431 } else {
5432 if (errstr) {
5433 alertpanel_error_log(_("%s\nYou can try to \"Send\" again "
5434 "or queue the message with \"Send later\""), errstr);
5435 g_free(errstr);
5436 } else {
5437 alertpanel_error_log(_("The message was queued but could not be "
5438 "sent.\nUse \"Send queued messages\" from "
5439 "the main window to retry."));
5441 if (!discard_window) {
5442 goto bail;
5444 inc_unlock();
5445 g_free(tmsgid);
5446 return -1;
5448 g_free(tmsgid);
5449 inc_unlock();
5450 toolbar_main_set_sensitive(mainwin);
5451 main_window_set_menu_sensitive(mainwin);
5452 return 0;
5454 bail:
5455 inc_unlock();
5456 g_free(tmsgid);
5457 compose_allow_user_actions (compose, TRUE);
5458 compose->sending = FALSE;
5459 compose->modified = TRUE;
5460 toolbar_main_set_sensitive(mainwin);
5461 main_window_set_menu_sensitive(mainwin);
5463 return -1;
5466 static gboolean compose_use_attach(Compose *compose)
5468 GtkTreeModel *model = gtk_tree_view_get_model
5469 (GTK_TREE_VIEW(compose->attach_clist));
5470 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5473 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5474 FILE *fp)
5476 gchar buf[BUFFSIZE];
5477 gchar *str;
5478 gboolean first_to_address;
5479 gboolean first_cc_address;
5480 GSList *list;
5481 ComposeHeaderEntry *headerentry;
5482 const gchar *headerentryname;
5483 const gchar *cc_hdr;
5484 const gchar *to_hdr;
5485 gboolean err = FALSE;
5487 debug_print("Writing redirect header\n");
5489 cc_hdr = prefs_common_translated_header_name("Cc:");
5490 to_hdr = prefs_common_translated_header_name("To:");
5492 first_to_address = TRUE;
5493 for (list = compose->header_list; list; list = list->next) {
5494 headerentry = ((ComposeHeaderEntry *)list->data);
5495 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5497 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5498 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5499 Xstrdup_a(str, entstr, return -1);
5500 g_strstrip(str);
5501 if (str[0] != '\0') {
5502 compose_convert_header
5503 (compose, buf, sizeof(buf), str,
5504 strlen("Resent-To") + 2, TRUE);
5506 if (first_to_address) {
5507 err |= (fprintf(fp, "Resent-To: ") < 0);
5508 first_to_address = FALSE;
5509 } else {
5510 err |= (fprintf(fp, ",") < 0);
5512 err |= (fprintf(fp, "%s", buf) < 0);
5516 if (!first_to_address) {
5517 err |= (fprintf(fp, "\n") < 0);
5520 first_cc_address = TRUE;
5521 for (list = compose->header_list; list; list = list->next) {
5522 headerentry = ((ComposeHeaderEntry *)list->data);
5523 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5525 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5526 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5527 Xstrdup_a(str, strg, return -1);
5528 g_strstrip(str);
5529 if (str[0] != '\0') {
5530 compose_convert_header
5531 (compose, buf, sizeof(buf), str,
5532 strlen("Resent-Cc") + 2, TRUE);
5534 if (first_cc_address) {
5535 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5536 first_cc_address = FALSE;
5537 } else {
5538 err |= (fprintf(fp, ",") < 0);
5540 err |= (fprintf(fp, "%s", buf) < 0);
5544 if (!first_cc_address) {
5545 err |= (fprintf(fp, "\n") < 0);
5548 return (err ? -1:0);
5551 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5553 gchar date[RFC822_DATE_BUFFSIZE];
5554 gchar buf[BUFFSIZE];
5555 gchar *str;
5556 const gchar *entstr;
5557 /* struct utsname utsbuf; */
5558 gboolean err = FALSE;
5560 cm_return_val_if_fail(fp != NULL, -1);
5561 cm_return_val_if_fail(compose->account != NULL, -1);
5562 cm_return_val_if_fail(compose->account->address != NULL, -1);
5564 /* Resent-Date */
5565 if (prefs_common.hide_timezone)
5566 get_rfc822_date_hide_tz(date, sizeof(date));
5567 else
5568 get_rfc822_date(date, sizeof(date));
5569 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5571 /* Resent-From */
5572 if (compose->account->name && *compose->account->name) {
5573 compose_convert_header
5574 (compose, buf, sizeof(buf), compose->account->name,
5575 strlen("From: "), TRUE);
5576 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5577 buf, compose->account->address) < 0);
5578 } else
5579 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5581 /* Subject */
5582 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5583 if (*entstr != '\0') {
5584 Xstrdup_a(str, entstr, return -1);
5585 g_strstrip(str);
5586 if (*str != '\0') {
5587 compose_convert_header(compose, buf, sizeof(buf), str,
5588 strlen("Subject: "), FALSE);
5589 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5593 /* Resent-Message-ID */
5594 if (compose->account->gen_msgid) {
5595 gchar *addr = prefs_account_generate_msgid(compose->account);
5596 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5597 if (compose->msgid)
5598 g_free(compose->msgid);
5599 compose->msgid = addr;
5600 } else {
5601 compose->msgid = NULL;
5604 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5605 return -1;
5607 /* separator between header and body */
5608 err |= (claws_fputs("\n", fp) == EOF);
5610 return (err ? -1:0);
5613 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5615 FILE *fp;
5616 size_t len;
5617 gchar *buf = NULL;
5618 gchar rewrite_buf[BUFFSIZE];
5619 int i = 0;
5620 gboolean skip = FALSE;
5621 gboolean err = FALSE;
5622 gchar *not_included[]={
5623 "Return-Path:", "Delivered-To:", "Received:",
5624 "Subject:", "X-UIDL:", "AF:",
5625 "NF:", "PS:", "SRH:",
5626 "SFN:", "DSR:", "MID:",
5627 "CFG:", "PT:", "S:",
5628 "RQ:", "SSV:", "NSV:",
5629 "SSH:", "R:", "MAID:",
5630 "NAID:", "RMID:", "FMID:",
5631 "SCF:", "RRCPT:", "NG:",
5632 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5633 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5634 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5635 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5636 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5637 NULL
5639 gint ret = 0;
5641 if ((fp = claws_fopen(compose->redirect_filename, "rb")) == NULL) {
5642 FILE_OP_ERROR(compose->redirect_filename, "claws_fopen");
5643 return -1;
5646 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5647 skip = FALSE;
5648 for (i = 0; not_included[i] != NULL; i++) {
5649 if (g_ascii_strncasecmp(buf, not_included[i],
5650 strlen(not_included[i])) == 0) {
5651 skip = TRUE;
5652 break;
5655 if (skip) {
5656 g_free(buf);
5657 buf = NULL;
5658 continue;
5660 if (claws_fputs(buf, fdest) == -1) {
5661 g_free(buf);
5662 buf = NULL;
5663 goto error;
5666 if (!prefs_common.redirect_keep_from) {
5667 if (g_ascii_strncasecmp(buf, "From:",
5668 strlen("From:")) == 0) {
5669 err |= (claws_fputs(" (by way of ", fdest) == EOF);
5670 if (compose->account->name
5671 && *compose->account->name) {
5672 gchar buffer[BUFFSIZE];
5674 compose_convert_header
5675 (compose, buffer, sizeof(buffer),
5676 compose->account->name,
5677 strlen("From: "),
5678 FALSE);
5679 err |= (fprintf(fdest, "%s <%s>",
5680 buffer,
5681 compose->account->address) < 0);
5682 } else
5683 err |= (fprintf(fdest, "%s",
5684 compose->account->address) < 0);
5685 err |= (claws_fputs(")", fdest) == EOF);
5689 g_free(buf);
5690 buf = NULL;
5691 if (claws_fputs("\n", fdest) == -1)
5692 goto error;
5695 if (err)
5696 goto error;
5698 if (compose_redirect_write_headers(compose, fdest))
5699 goto error;
5701 while ((len = claws_fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5702 if (claws_fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5703 goto error;
5706 claws_fclose(fp);
5708 return 0;
5710 error:
5711 claws_fclose(fp);
5713 return -1;
5716 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5718 GtkTextBuffer *buffer;
5719 GtkTextIter start, end, tmp;
5720 gchar *chars, *tmp_enc_file = NULL, *content;
5721 gchar *buf, *msg;
5722 const gchar *out_codeset;
5723 EncodingType encoding = ENC_UNKNOWN;
5724 MimeInfo *mimemsg, *mimetext;
5725 gint line;
5726 const gchar *src_codeset = CS_INTERNAL;
5727 gchar *from_addr = NULL;
5728 gchar *from_name = NULL;
5729 FolderItem *outbox;
5731 if (action == COMPOSE_WRITE_FOR_SEND) {
5732 attach_parts = TRUE;
5734 /* We're sending the message, generate a Message-ID
5735 * if necessary. */
5736 if (compose->msgid == NULL &&
5737 compose->account->gen_msgid) {
5738 compose->msgid = prefs_account_generate_msgid(compose->account);
5742 /* create message MimeInfo */
5743 mimemsg = procmime_mimeinfo_new();
5744 mimemsg->type = MIMETYPE_MESSAGE;
5745 mimemsg->subtype = g_strdup("rfc822");
5746 mimemsg->content = MIMECONTENT_MEM;
5747 mimemsg->tmp = TRUE; /* must free content later */
5748 mimemsg->data.mem = compose_get_header(compose);
5750 /* Create text part MimeInfo */
5751 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5752 gtk_text_buffer_get_end_iter(buffer, &end);
5753 tmp = end;
5755 /* We make sure that there is a newline at the end. */
5756 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5757 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5758 if (*chars != '\n') {
5759 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5761 g_free(chars);
5764 /* get all composed text */
5765 gtk_text_buffer_get_start_iter(buffer, &start);
5766 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5768 out_codeset = conv_get_charset_str(compose->out_encoding);
5770 if (!out_codeset && is_ascii_str(chars)) {
5771 out_codeset = CS_US_ASCII;
5772 } else if (prefs_common.outgoing_fallback_to_ascii &&
5773 is_ascii_str(chars)) {
5774 out_codeset = CS_US_ASCII;
5775 encoding = ENC_7BIT;
5778 if (!out_codeset) {
5779 gchar *test_conv_global_out = NULL;
5780 gchar *test_conv_reply = NULL;
5782 /* automatic mode. be automatic. */
5783 codeconv_set_strict(TRUE);
5785 out_codeset = conv_get_outgoing_charset_str();
5786 if (out_codeset) {
5787 debug_print("trying to convert to %s\n", out_codeset);
5788 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5791 if (!test_conv_global_out && compose->orig_charset
5792 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5793 out_codeset = compose->orig_charset;
5794 debug_print("failure; trying to convert to %s\n", out_codeset);
5795 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5798 if (!test_conv_global_out && !test_conv_reply) {
5799 /* we're lost */
5800 out_codeset = CS_INTERNAL;
5801 debug_print("failure; finally using %s\n", out_codeset);
5803 g_free(test_conv_global_out);
5804 g_free(test_conv_reply);
5805 codeconv_set_strict(FALSE);
5808 if (encoding == ENC_UNKNOWN) {
5809 if (prefs_common.encoding_method == CTE_BASE64)
5810 encoding = ENC_BASE64;
5811 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5812 encoding = ENC_QUOTED_PRINTABLE;
5813 else if (prefs_common.encoding_method == CTE_8BIT)
5814 encoding = ENC_8BIT;
5815 else
5816 encoding = procmime_get_encoding_for_charset(out_codeset);
5819 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5820 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5822 if (action == COMPOSE_WRITE_FOR_SEND) {
5823 codeconv_set_strict(TRUE);
5824 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5825 codeconv_set_strict(FALSE);
5827 if (!buf) {
5828 AlertValue aval;
5830 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5831 "to the specified %s charset.\n"
5832 "Send it as %s?"), out_codeset, src_codeset);
5833 aval = alertpanel_full(_("Error"), msg, NULL, _("_Cancel"),
5834 NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND, FALSE,
5835 NULL, ALERT_ERROR);
5836 g_free(msg);
5838 if (aval != G_ALERTALTERNATE) {
5839 g_free(chars);
5840 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5841 } else {
5842 buf = chars;
5843 out_codeset = src_codeset;
5844 chars = NULL;
5847 } else {
5848 buf = chars;
5849 out_codeset = src_codeset;
5850 chars = NULL;
5852 g_free(chars);
5854 /* check for line length limit */
5855 if (action == COMPOSE_WRITE_FOR_SEND &&
5856 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5857 check_line_length(buf, 1000, &line) < 0) {
5858 if (encoding == ENC_8BIT) {
5859 AlertValue aval;
5861 msg = g_strdup_printf
5862 (_("Line %d exceeds the line length limit (998 bytes).\n"
5863 "The contents of the message might be broken on the way "
5864 "to the recipient."), line + 1);
5865 aval = alertpanel(_("Warning"), msg, NULL, _("Send safely"), NULL, _("Send as-is"),
5866 NULL, NULL, ALERTFOCUS_FIRST);
5867 g_free(msg);
5868 if (aval != G_ALERTALTERNATE)
5869 encoding = ENC_QUOTED_PRINTABLE;
5870 } else {
5871 debug_print("Line %d exceeds the line length limit (998 bytes), "
5872 "switching to QP transfer encoding\n", line + 1);
5873 encoding = ENC_QUOTED_PRINTABLE;
5877 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5878 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5879 strstr(buf, "\nFrom ") != NULL) {
5880 encoding = ENC_QUOTED_PRINTABLE;
5884 mimetext = procmime_mimeinfo_new();
5885 mimetext->content = MIMECONTENT_MEM;
5886 mimetext->tmp = TRUE; /* must free content later */
5887 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5888 * and free the data, which we need later. */
5889 mimetext->data.mem = g_strdup(buf);
5890 mimetext->type = MIMETYPE_TEXT;
5891 mimetext->subtype = g_strdup("plain");
5892 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5893 g_strdup(out_codeset));
5895 /* protect trailing spaces when signing message */
5896 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5897 privacy_system_can_sign(compose->privacy_system)) {
5898 encoding = ENC_QUOTED_PRINTABLE;
5901 debug_print("main text: %" G_GSIZE_FORMAT " bytes encoded as %s in %d\n",
5902 strlen(buf), out_codeset, encoding);
5904 if (encoding != ENC_UNKNOWN)
5905 procmime_encode_content(mimetext, encoding);
5907 /* append attachment parts */
5908 if (compose_use_attach(compose) && attach_parts) {
5909 MimeInfo *mimempart;
5910 gchar *boundary = NULL;
5911 mimempart = procmime_mimeinfo_new();
5912 mimempart->content = MIMECONTENT_EMPTY;
5913 mimempart->type = MIMETYPE_MULTIPART;
5914 mimempart->subtype = g_strdup("mixed");
5916 do {
5917 g_free(boundary);
5918 boundary = generate_mime_boundary(NULL);
5919 } while (strstr(buf, boundary) != NULL);
5921 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5922 boundary);
5924 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5926 g_node_append(mimempart->node, mimetext->node);
5927 g_node_append(mimemsg->node, mimempart->node);
5929 if (compose_add_attachments(compose, mimempart, action) < 0)
5930 return COMPOSE_QUEUE_ERROR_NO_MSG;
5931 } else
5932 g_node_append(mimemsg->node, mimetext->node);
5934 g_free(buf);
5936 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5937 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5938 /* extract name and address */
5939 if (strstr(spec, " <") && strstr(spec, ">")) {
5940 from_addr = g_strdup(strrchr(spec, '<')+1);
5941 *(strrchr(from_addr, '>')) = '\0';
5942 from_name = g_strdup(spec);
5943 *(strrchr(from_name, '<')) = '\0';
5944 } else {
5945 from_name = NULL;
5946 from_addr = NULL;
5948 g_free(spec);
5950 /* sign message if sending */
5951 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5952 privacy_system_can_sign(compose->privacy_system))
5953 if (!privacy_sign(compose->privacy_system, mimemsg,
5954 compose->account, from_addr)) {
5955 g_free(from_name);
5956 g_free(from_addr);
5957 if (!privacy_peek_error())
5958 return COMPOSE_QUEUE_SIGNING_CANCELLED;
5959 else
5960 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5962 g_free(from_name);
5963 g_free(from_addr);
5965 if (compose->use_encryption) {
5966 if (compose->encdata != NULL &&
5967 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5969 /* First, write an unencrypted copy and save it to outbox, if
5970 * user wants that. */
5971 if (compose->account->save_encrypted_as_clear_text) {
5972 debug_print("saving sent message unencrypted...\n");
5973 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5974 if (tmpfp) {
5975 claws_fclose(tmpfp);
5977 /* fp now points to a file with headers written,
5978 * let's make a copy. */
5979 rewind(fp);
5980 content = file_read_stream_to_str(fp);
5982 str_write_to_file(content, tmp_enc_file, TRUE);
5983 g_free(content);
5985 /* Now write the unencrypted body. */
5986 if ((tmpfp = claws_fopen(tmp_enc_file, "a")) != NULL) {
5987 procmime_write_mimeinfo(mimemsg, tmpfp);
5988 claws_fclose(tmpfp);
5990 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5991 if (!outbox)
5992 outbox = folder_get_default_outbox();
5994 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5995 claws_unlink(tmp_enc_file);
5996 } else {
5997 g_warning("can't open file '%s'", tmp_enc_file);
5999 } else {
6000 g_warning("couldn't get tempfile");
6003 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
6004 debug_print("Couldn't encrypt mime structure: %s.\n",
6005 privacy_get_error());
6006 if (tmp_enc_file)
6007 g_free(tmp_enc_file);
6008 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
6012 if (tmp_enc_file)
6013 g_free(tmp_enc_file);
6015 procmime_write_mimeinfo(mimemsg, fp);
6017 procmime_mimeinfo_free_all(&mimemsg);
6019 return 0;
6022 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
6024 GtkTextBuffer *buffer;
6025 GtkTextIter start, end;
6026 FILE *fp;
6027 size_t len;
6028 gchar *chars, *tmp;
6030 if ((fp = claws_fopen(file, "wb")) == NULL) {
6031 FILE_OP_ERROR(file, "claws_fopen");
6032 return -1;
6035 /* chmod for security */
6036 if (change_file_mode_rw(fp, file) < 0) {
6037 FILE_OP_ERROR(file, "chmod");
6038 g_warning("can't change file mode");
6041 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
6042 gtk_text_buffer_get_start_iter(buffer, &start);
6043 gtk_text_buffer_get_end_iter(buffer, &end);
6044 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
6046 chars = conv_codeset_strdup
6047 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6049 g_free(tmp);
6050 if (!chars) {
6051 claws_fclose(fp);
6052 claws_unlink(file);
6053 return -1;
6055 /* write body */
6056 len = strlen(chars);
6057 if (claws_fwrite(chars, sizeof(gchar), len, fp) != len) {
6058 FILE_OP_ERROR(file, "claws_fwrite");
6059 g_free(chars);
6060 claws_fclose(fp);
6061 claws_unlink(file);
6062 return -1;
6065 g_free(chars);
6067 if (claws_safe_fclose(fp) == EOF) {
6068 FILE_OP_ERROR(file, "claws_fclose");
6069 claws_unlink(file);
6070 return -1;
6072 return 0;
6075 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6077 FolderItem *item;
6078 MsgInfo *msginfo = compose->targetinfo;
6080 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6081 if (!msginfo) return -1;
6083 if (!force && MSG_IS_LOCKED(msginfo->flags))
6084 return 0;
6086 item = msginfo->folder;
6087 cm_return_val_if_fail(item != NULL, -1);
6089 if (procmsg_msg_exist(msginfo) &&
6090 (folder_has_parent_of_type(item, F_QUEUE) ||
6091 folder_has_parent_of_type(item, F_DRAFT)
6092 || msginfo == compose->autosaved_draft)) {
6093 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6094 g_warning("can't remove the old message");
6095 return -1;
6096 } else {
6097 debug_print("removed reedit target %d\n", msginfo->msgnum);
6101 return 0;
6104 static void compose_remove_draft(Compose *compose)
6106 FolderItem *drafts;
6107 MsgInfo *msginfo = compose->targetinfo;
6108 drafts = account_get_special_folder(compose->account, F_DRAFT);
6110 if (procmsg_msg_exist(msginfo)) {
6111 folder_item_remove_msg(drafts, msginfo->msgnum);
6116 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6117 gboolean remove_reedit_target)
6119 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6122 static gboolean compose_warn_encryption(Compose *compose)
6124 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6125 AlertValue val = G_ALERTALTERNATE;
6127 if (warning == NULL)
6128 return TRUE;
6130 val = alertpanel_full(_("Encryption warning"), warning,
6131 NULL, _("_Cancel"), NULL, _("C_ontinue"), NULL, NULL,
6132 ALERTFOCUS_SECOND, TRUE, NULL, ALERT_WARNING);
6133 if (val & G_ALERTDISABLE) {
6134 val &= ~G_ALERTDISABLE;
6135 if (val == G_ALERTALTERNATE)
6136 privacy_inhibit_encrypt_warning(compose->privacy_system,
6137 TRUE);
6140 if (val == G_ALERTALTERNATE) {
6141 return TRUE;
6142 } else {
6143 return FALSE;
6147 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6148 gchar **msgpath, gboolean perform_checks,
6149 gboolean remove_reedit_target)
6151 FolderItem *queue;
6152 gchar *tmp;
6153 FILE *fp;
6154 GSList *cur;
6155 gint num;
6156 PrefsAccount *mailac = NULL, *newsac = NULL;
6157 gboolean err = FALSE;
6159 debug_print("queueing message...\n");
6160 cm_return_val_if_fail(compose->account != NULL, -1);
6162 if (compose_check_entries(compose, perform_checks) == FALSE) {
6163 if (compose->batch) {
6164 gtk_widget_show_all(compose->window);
6166 return COMPOSE_QUEUE_ERROR_NO_MSG;
6169 if (!compose->to_list && !compose->newsgroup_list) {
6170 g_warning("can't get recipient list");
6171 return COMPOSE_QUEUE_ERROR_NO_MSG;
6174 if (compose->to_list) {
6175 mailac = compose->account;
6176 if (!mailac && cur_account && cur_account->protocol != A_NNTP)
6177 mailac = cur_account;
6178 else if (!mailac && !(mailac = compose_current_mail_account())) {
6179 alertpanel_error(_("No account for sending mails available!"));
6180 return COMPOSE_QUEUE_ERROR_NO_MSG;
6184 if (compose->newsgroup_list) {
6185 if (compose->account->protocol == A_NNTP)
6186 newsac = compose->account;
6187 else {
6188 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6189 return COMPOSE_QUEUE_ERROR_NO_MSG;
6193 /* write queue header */
6194 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6195 G_DIR_SEPARATOR, compose, (guint) rand());
6196 debug_print("queuing to %s\n", tmp);
6197 if ((fp = claws_fopen(tmp, "w+b")) == NULL) {
6198 FILE_OP_ERROR(tmp, "claws_fopen");
6199 g_free(tmp);
6200 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6203 if (change_file_mode_rw(fp, tmp) < 0) {
6204 FILE_OP_ERROR(tmp, "chmod");
6205 g_warning("can't change file mode");
6208 /* queueing variables */
6209 err |= (fprintf(fp, "AF:\n") < 0);
6210 err |= (fprintf(fp, "NF:0\n") < 0);
6211 err |= (fprintf(fp, "PS:10\n") < 0);
6212 err |= (fprintf(fp, "SRH:1\n") < 0);
6213 err |= (fprintf(fp, "SFN:\n") < 0);
6214 err |= (fprintf(fp, "DSR:\n") < 0);
6215 if (compose->msgid)
6216 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6217 else
6218 err |= (fprintf(fp, "MID:\n") < 0);
6219 err |= (fprintf(fp, "CFG:\n") < 0);
6220 err |= (fprintf(fp, "PT:0\n") < 0);
6221 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6222 err |= (fprintf(fp, "RQ:\n") < 0);
6223 if (mailac)
6224 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6225 else
6226 err |= (fprintf(fp, "SSV:\n") < 0);
6227 if (newsac)
6228 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6229 else
6230 err |= (fprintf(fp, "NSV:\n") < 0);
6231 err |= (fprintf(fp, "SSH:\n") < 0);
6232 /* write recipient list */
6233 if (compose->to_list) {
6234 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6235 for (cur = compose->to_list->next; cur != NULL;
6236 cur = cur->next)
6237 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6238 err |= (fprintf(fp, "\n") < 0);
6240 /* write newsgroup list */
6241 if (compose->newsgroup_list) {
6242 err |= (fprintf(fp, "NG:") < 0);
6243 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6244 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6245 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6246 err |= (fprintf(fp, "\n") < 0);
6248 /* account IDs */
6249 if (mailac)
6250 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6251 if (newsac)
6252 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6255 if (compose->privacy_system != NULL) {
6256 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6257 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6258 if (compose->use_encryption) {
6259 if (!compose_warn_encryption(compose)) {
6260 claws_fclose(fp);
6261 claws_unlink(tmp);
6262 g_free(tmp);
6263 return COMPOSE_QUEUE_ERROR_NO_MSG;
6265 if (mailac && mailac->encrypt_to_self) {
6266 GSList *tmp_list = g_slist_copy(compose->to_list);
6267 tmp_list = g_slist_append(tmp_list, compose->account->address);
6268 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6269 g_slist_free(tmp_list);
6270 } else {
6271 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6273 if (compose->encdata != NULL) {
6274 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6275 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6276 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6277 compose->encdata) < 0);
6278 } /* else we finally dont want to encrypt */
6279 } else {
6280 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6281 /* and if encdata was null, it means there's been a problem in
6282 * key selection */
6283 if (err == TRUE)
6284 g_warning("failed to write queue message");
6285 claws_fclose(fp);
6286 claws_unlink(tmp);
6287 g_free(tmp);
6288 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6293 /* Save copy folder */
6294 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6295 gchar *savefolderid;
6297 savefolderid = compose_get_save_to(compose);
6298 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6299 g_free(savefolderid);
6301 /* Save copy folder */
6302 if (compose->return_receipt) {
6303 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6305 /* Message-ID of message replying to */
6306 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6307 gchar *folderid = NULL;
6309 if (compose->replyinfo->folder)
6310 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6311 if (folderid == NULL)
6312 folderid = g_strdup("NULL");
6314 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6315 g_free(folderid);
6317 /* Message-ID of message forwarding to */
6318 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6319 gchar *folderid = NULL;
6321 if (compose->fwdinfo->folder)
6322 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6323 if (folderid == NULL)
6324 folderid = g_strdup("NULL");
6326 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6327 g_free(folderid);
6330 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6331 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6333 /* end of headers */
6334 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6336 if (compose->redirect_filename != NULL) {
6337 if (compose_redirect_write_to_file(compose, fp) < 0) {
6338 claws_fclose(fp);
6339 claws_unlink(tmp);
6340 g_free(tmp);
6341 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6343 } else {
6344 gint result = 0;
6345 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6346 claws_fclose(fp);
6347 claws_unlink(tmp);
6348 g_free(tmp);
6349 return result;
6352 if (err == TRUE) {
6353 g_warning("failed to write queue message");
6354 claws_fclose(fp);
6355 claws_unlink(tmp);
6356 g_free(tmp);
6357 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6359 if (claws_safe_fclose(fp) == EOF) {
6360 FILE_OP_ERROR(tmp, "claws_fclose");
6361 claws_unlink(tmp);
6362 g_free(tmp);
6363 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6366 if (item && *item) {
6367 queue = *item;
6368 } else {
6369 queue = account_get_special_folder(compose->account, F_QUEUE);
6371 if (!queue) {
6372 g_warning("can't find queue folder");
6373 claws_unlink(tmp);
6374 g_free(tmp);
6375 return COMPOSE_QUEUE_ERROR_NO_MSG;
6377 folder_item_scan(queue);
6378 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6379 g_warning("can't queue the message");
6380 claws_unlink(tmp);
6381 g_free(tmp);
6382 return COMPOSE_QUEUE_ERROR_NO_MSG;
6385 if (msgpath == NULL) {
6386 claws_unlink(tmp);
6387 g_free(tmp);
6388 } else
6389 *msgpath = tmp;
6391 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6392 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6393 if (mi) {
6394 procmsg_msginfo_change_flags(mi,
6395 compose->targetinfo->flags.perm_flags,
6396 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6397 0, 0);
6399 g_slist_free(mi->tags);
6400 mi->tags = g_slist_copy(compose->targetinfo->tags);
6401 procmsg_msginfo_free(&mi);
6405 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6406 compose_remove_reedit_target(compose, FALSE);
6409 if ((msgnum != NULL) && (item != NULL)) {
6410 *msgnum = num;
6411 *item = queue;
6414 return COMPOSE_QUEUE_SUCCESS;
6417 static int compose_add_attachments(Compose *compose, MimeInfo *parent, gint action)
6419 AttachInfo *ainfo;
6420 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6421 MimeInfo *mimepart;
6422 #ifdef G_OS_WIN32
6423 GFile *f;
6424 GFileInfo *fi;
6425 GError *error = NULL;
6426 #else
6427 GStatBuf statbuf;
6428 #endif
6429 goffset size;
6430 gchar *type, *subtype;
6431 GtkTreeModel *model;
6432 GtkTreeIter iter;
6434 model = gtk_tree_view_get_model(tree_view);
6436 if (!gtk_tree_model_get_iter_first(model, &iter))
6437 return 0;
6438 do {
6439 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6441 if (!is_file_exist(ainfo->file)) {
6442 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6443 AlertValue val = alertpanel_full(_("Warning"), msg, NULL,
6444 action == COMPOSE_WRITE_FOR_STORE? _("Cancel drafting"): _("Cancel sending"),
6445 NULL, _("Ignore attachment"), NULL, NULL,
6446 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6447 g_free(msg);
6448 if (val == G_ALERTDEFAULT) {
6449 return -1;
6451 continue;
6453 #ifdef G_OS_WIN32
6454 f = g_file_new_for_path(ainfo->file);
6455 fi = g_file_query_info(f, "standard::size",
6456 G_FILE_QUERY_INFO_NONE, NULL, &error);
6457 if (error != NULL) {
6458 g_warning(error->message);
6459 g_error_free(error);
6460 g_object_unref(f);
6461 return -1;
6463 size = g_file_info_get_size(fi);
6464 g_object_unref(fi);
6465 g_object_unref(f);
6466 #else
6467 if (g_stat(ainfo->file, &statbuf) < 0)
6468 return -1;
6469 size = statbuf.st_size;
6470 #endif
6472 mimepart = procmime_mimeinfo_new();
6473 mimepart->content = MIMECONTENT_FILE;
6474 mimepart->data.filename = g_strdup(ainfo->file);
6475 mimepart->tmp = FALSE; /* or we destroy our attachment */
6476 mimepart->offset = 0;
6477 mimepart->length = size;
6479 type = g_strdup(ainfo->content_type);
6481 if (!strchr(type, '/')) {
6482 g_free(type);
6483 type = g_strdup("application/octet-stream");
6486 subtype = strchr(type, '/') + 1;
6487 *(subtype - 1) = '\0';
6488 mimepart->type = procmime_get_media_type(type);
6489 mimepart->subtype = g_strdup(subtype);
6490 g_free(type);
6492 if (mimepart->type == MIMETYPE_MESSAGE &&
6493 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6494 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6495 } else if (mimepart->type == MIMETYPE_TEXT) {
6496 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6497 /* Text parts with no name come from multipart/alternative
6498 * forwards. Make sure the recipient won't look at the
6499 * original HTML part by mistake. */
6500 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6501 ainfo->name = g_strdup_printf(_("Original %s part"),
6502 mimepart->subtype);
6504 if (ainfo->charset)
6505 g_hash_table_insert(mimepart->typeparameters,
6506 g_strdup("charset"), g_strdup(ainfo->charset));
6508 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6509 if (mimepart->type == MIMETYPE_APPLICATION &&
6510 !g_strcmp0(mimepart->subtype, "octet-stream"))
6511 g_hash_table_insert(mimepart->typeparameters,
6512 g_strdup("name"), g_strdup(ainfo->name));
6513 g_hash_table_insert(mimepart->dispositionparameters,
6514 g_strdup("filename"), g_strdup(ainfo->name));
6515 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6518 if (mimepart->type == MIMETYPE_MESSAGE
6519 || mimepart->type == MIMETYPE_MULTIPART)
6520 ainfo->encoding = ENC_BINARY;
6521 else if (compose->use_signing || compose->fwdinfo != NULL) {
6522 if (ainfo->encoding == ENC_7BIT)
6523 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6524 else if (ainfo->encoding == ENC_8BIT)
6525 ainfo->encoding = ENC_BASE64;
6528 procmime_encode_content(mimepart, ainfo->encoding);
6530 g_node_append(parent->node, mimepart->node);
6531 } while (gtk_tree_model_iter_next(model, &iter));
6533 return 0;
6536 static gchar *compose_quote_list_of_addresses(gchar *str)
6538 GSList *list = NULL, *item = NULL;
6539 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6541 list = address_list_append_with_comments(list, str);
6542 for (item = list; item != NULL; item = item->next) {
6543 gchar *spec = item->data;
6544 gchar *endofname = strstr(spec, " <");
6545 if (endofname != NULL) {
6546 gchar * qqname;
6547 *endofname = '\0';
6548 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6549 qqname = escape_internal_quotes(qname, '"');
6550 *endofname = ' ';
6551 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6552 gchar *addr = g_strdup(endofname);
6553 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6554 faddr = g_strconcat(name, addr, NULL);
6555 g_free(name);
6556 g_free(addr);
6557 debug_print("new auto-quoted address: '%s'\n", faddr);
6560 if (result == NULL)
6561 result = g_strdup((faddr != NULL)? faddr: spec);
6562 else {
6563 gchar *tmp = g_strconcat(result,
6564 ", ",
6565 (faddr != NULL)? faddr: spec,
6566 NULL);
6567 g_free(result);
6568 result = tmp;
6570 if (faddr != NULL) {
6571 g_free(faddr);
6572 faddr = NULL;
6575 slist_free_strings_full(list);
6577 return result;
6580 #define IS_IN_CUSTOM_HEADER(header) \
6581 (compose->account->add_customhdr && \
6582 custom_header_find(compose->account->customhdr_list, header) != NULL)
6584 static const gchar *compose_untranslated_header_name(gchar *header_name)
6586 /* return the untranslated header name, if header_name is a known
6587 header name, in either its translated or untranslated form, with
6588 or without trailing colon. otherwise, returns header_name. */
6589 gchar *translated_header_name;
6590 gchar *translated_header_name_wcolon;
6591 const gchar *untranslated_header_name;
6592 const gchar *untranslated_header_name_wcolon;
6593 gint i;
6595 cm_return_val_if_fail(header_name != NULL, NULL);
6597 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6598 untranslated_header_name = HEADERS[i].header_name;
6599 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6601 translated_header_name = gettext(untranslated_header_name);
6602 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6604 if (!strcmp(header_name, untranslated_header_name) ||
6605 !strcmp(header_name, translated_header_name)) {
6606 return untranslated_header_name;
6607 } else {
6608 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6609 !strcmp(header_name, translated_header_name_wcolon)) {
6610 return untranslated_header_name_wcolon;
6614 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6615 return header_name;
6618 static void compose_add_headerfield_from_headerlist(Compose *compose,
6619 GString *header,
6620 const gchar *fieldname,
6621 const gchar *seperator)
6623 gchar *str, *fieldname_w_colon;
6624 gboolean add_field = FALSE;
6625 GSList *list;
6626 ComposeHeaderEntry *headerentry;
6627 const gchar *headerentryname;
6628 const gchar *trans_fieldname;
6629 GString *fieldstr;
6631 if (IS_IN_CUSTOM_HEADER(fieldname))
6632 return;
6634 debug_print("Adding %s-fields\n", fieldname);
6636 fieldstr = g_string_sized_new(64);
6638 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6639 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6641 for (list = compose->header_list; list; list = list->next) {
6642 headerentry = ((ComposeHeaderEntry *)list->data);
6643 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6645 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6646 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6647 g_strstrip(ustr);
6648 str = compose_quote_list_of_addresses(ustr);
6649 g_free(ustr);
6650 if (str != NULL && str[0] != '\0') {
6651 if (add_field)
6652 g_string_append(fieldstr, seperator);
6653 g_string_append(fieldstr, str);
6654 add_field = TRUE;
6656 g_free(str);
6659 if (add_field) {
6660 gchar *buf;
6662 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6663 compose_convert_header
6664 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6665 strlen(fieldname) + 2, TRUE);
6666 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6667 g_free(buf);
6670 g_free(fieldname_w_colon);
6671 g_string_free(fieldstr, TRUE);
6673 return;
6676 static gchar *compose_get_manual_headers_info(Compose *compose)
6678 GString *sh_header = g_string_new(" ");
6679 GSList *list;
6680 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6682 for (list = compose->header_list; list; list = list->next) {
6683 ComposeHeaderEntry *headerentry;
6684 gchar *tmp;
6685 gchar *headername;
6686 gchar *headername_wcolon;
6687 const gchar *headername_trans;
6688 gchar **string;
6689 gboolean standard_header = FALSE;
6691 headerentry = ((ComposeHeaderEntry *)list->data);
6693 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6694 g_strstrip(tmp);
6695 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6696 g_free(tmp);
6697 continue;
6700 if (!strstr(tmp, ":")) {
6701 headername_wcolon = g_strconcat(tmp, ":", NULL);
6702 headername = g_strdup(tmp);
6703 } else {
6704 headername_wcolon = g_strdup(tmp);
6705 headername = g_strdup(strtok(tmp, ":"));
6707 g_free(tmp);
6709 string = std_headers;
6710 while (*string != NULL) {
6711 headername_trans = prefs_common_translated_header_name(*string);
6712 if (!strcmp(headername_trans, headername_wcolon))
6713 standard_header = TRUE;
6714 string++;
6716 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6717 g_string_append_printf(sh_header, "%s ", headername);
6718 g_free(headername);
6719 g_free(headername_wcolon);
6721 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6722 return g_string_free(sh_header, FALSE);
6725 static gchar *compose_get_header(Compose *compose)
6727 gchar date[RFC822_DATE_BUFFSIZE];
6728 gchar buf[BUFFSIZE];
6729 const gchar *entry_str;
6730 gchar *str;
6731 gchar *name;
6732 GSList *list;
6733 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6734 GString *header;
6735 gchar *from_name = NULL, *from_address = NULL;
6736 gchar *tmp;
6738 cm_return_val_if_fail(compose->account != NULL, NULL);
6739 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6741 header = g_string_sized_new(64);
6743 /* Date */
6744 if (prefs_common.hide_timezone)
6745 get_rfc822_date_hide_tz(date, sizeof(date));
6746 else
6747 get_rfc822_date(date, sizeof(date));
6748 g_string_append_printf(header, "Date: %s\n", date);
6750 /* From */
6752 if (compose->account->name && *compose->account->name) {
6753 gchar *buf;
6754 QUOTE_IF_REQUIRED(buf, compose->account->name);
6755 tmp = g_strdup_printf("%s <%s>",
6756 buf, compose->account->address);
6757 } else {
6758 tmp = g_strdup_printf("%s",
6759 compose->account->address);
6761 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6762 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6763 /* use default */
6764 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6765 from_address = g_strdup(compose->account->address);
6766 } else {
6767 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6768 /* extract name and address */
6769 if (strstr(spec, " <") && strstr(spec, ">")) {
6770 from_address = g_strdup(strrchr(spec, '<')+1);
6771 *(strrchr(from_address, '>')) = '\0';
6772 from_name = g_strdup(spec);
6773 *(strrchr(from_name, '<')) = '\0';
6774 } else {
6775 from_name = NULL;
6776 from_address = g_strdup(spec);
6778 g_free(spec);
6780 g_free(tmp);
6783 if (from_name && *from_name) {
6784 gchar *qname;
6785 compose_convert_header
6786 (compose, buf, sizeof(buf), from_name,
6787 strlen("From: "), TRUE);
6788 QUOTE_IF_REQUIRED(name, buf);
6789 qname = escape_internal_quotes(name, '"');
6791 g_string_append_printf(header, "From: %s <%s>\n",
6792 qname, from_address);
6793 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6794 compose->return_receipt) {
6795 compose_convert_header(compose, buf, sizeof(buf), from_name,
6796 strlen("Disposition-Notification-To: "),
6797 TRUE);
6798 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6800 if (qname != name)
6801 g_free(qname);
6802 } else {
6803 g_string_append_printf(header, "From: %s\n", from_address);
6804 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6805 compose->return_receipt)
6806 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6809 g_free(from_name);
6810 g_free(from_address);
6812 /* To */
6813 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6815 /* Newsgroups */
6816 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6818 /* Cc */
6819 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6821 /* Bcc */
6823 * If this account is a NNTP account remove Bcc header from
6824 * message body since it otherwise will be publicly shown
6826 if (compose->account->protocol != A_NNTP)
6827 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6829 /* Subject */
6830 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6832 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6833 g_strstrip(str);
6834 if (*str != '\0') {
6835 compose_convert_header(compose, buf, sizeof(buf), str,
6836 strlen("Subject: "), FALSE);
6837 g_string_append_printf(header, "Subject: %s\n", buf);
6840 g_free(str);
6842 /* Message-ID */
6843 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6844 g_string_append_printf(header, "Message-ID: <%s>\n",
6845 compose->msgid);
6848 if (compose->remove_references == FALSE) {
6849 /* In-Reply-To */
6850 if (compose->inreplyto && compose->to_list)
6851 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6853 /* References */
6854 if (compose->references)
6855 g_string_append_printf(header, "References: %s\n", compose->references);
6858 /* Followup-To */
6859 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6861 /* Reply-To */
6862 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6864 /* Organization */
6865 if (compose->account->organization &&
6866 strlen(compose->account->organization) &&
6867 !IS_IN_CUSTOM_HEADER("Organization")) {
6868 compose_convert_header(compose, buf, sizeof(buf),
6869 compose->account->organization,
6870 strlen("Organization: "), FALSE);
6871 g_string_append_printf(header, "Organization: %s\n", buf);
6874 /* Program version and system info */
6875 if (compose->account->gen_xmailer &&
6876 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6877 !compose->newsgroup_list) {
6878 g_string_append_printf(header, "X-Mailer: %s (GTK %d.%d.%d; %s)\n",
6879 prog_version,
6880 gtk_major_version, gtk_minor_version, gtk_micro_version,
6881 TARGET_ALIAS);
6883 if (compose->account->gen_xmailer &&
6884 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6885 g_string_append_printf(header, "X-Newsreader: %s (GTK %d.%d.%d; %s)\n",
6886 prog_version,
6887 gtk_major_version, gtk_minor_version, gtk_micro_version,
6888 TARGET_ALIAS);
6891 /* custom headers */
6892 if (compose->account->add_customhdr) {
6893 GSList *cur;
6895 for (cur = compose->account->customhdr_list; cur != NULL;
6896 cur = cur->next) {
6897 CustomHeader *chdr = (CustomHeader *)cur->data;
6899 if (custom_header_is_allowed(chdr->name)
6900 && chdr->value != NULL
6901 && *(chdr->value) != '\0') {
6902 compose_convert_header
6903 (compose, buf, sizeof(buf),
6904 chdr->value,
6905 strlen(chdr->name) + 2, FALSE);
6906 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6911 /* Automatic Faces and X-Faces */
6912 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6913 g_string_append_printf(header, "X-Face: %s\n", buf);
6915 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6916 g_string_append_printf(header, "X-Face: %s\n", buf);
6918 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6919 g_string_append_printf(header, "Face: %s\n", buf);
6921 else if (get_default_face (buf, sizeof(buf)) == 0) {
6922 g_string_append_printf(header, "Face: %s\n", buf);
6925 /* PRIORITY */
6926 switch (compose->priority) {
6927 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6928 "X-Priority: 1 (Highest)\n");
6929 break;
6930 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6931 "X-Priority: 2 (High)\n");
6932 break;
6933 case PRIORITY_NORMAL: break;
6934 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6935 "X-Priority: 4 (Low)\n");
6936 break;
6937 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6938 "X-Priority: 5 (Lowest)\n");
6939 break;
6940 default: debug_print("compose: priority unknown : %d\n",
6941 compose->priority);
6944 /* get special headers */
6945 for (list = compose->header_list; list; list = list->next) {
6946 ComposeHeaderEntry *headerentry;
6947 gchar *tmp;
6948 gchar *headername;
6949 gchar *headername_wcolon;
6950 const gchar *headername_trans;
6951 gchar *headervalue;
6952 gchar **string;
6953 gboolean standard_header = FALSE;
6955 headerentry = ((ComposeHeaderEntry *)list->data);
6957 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6958 g_strstrip(tmp);
6959 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6960 g_free(tmp);
6961 continue;
6964 if (!strstr(tmp, ":")) {
6965 headername_wcolon = g_strconcat(tmp, ":", NULL);
6966 headername = g_strdup(tmp);
6967 } else {
6968 headername_wcolon = g_strdup(tmp);
6969 headername = g_strdup(strtok(tmp, ":"));
6971 g_free(tmp);
6973 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6974 Xstrdup_a(headervalue, entry_str, {
6975 g_free(headername);
6976 g_free(headername_wcolon);
6977 g_string_free(header, TRUE);
6978 return NULL;
6980 subst_char(headervalue, '\r', ' ');
6981 subst_char(headervalue, '\n', ' ');
6982 g_strstrip(headervalue);
6983 if (*headervalue != '\0') {
6984 string = std_headers;
6985 while (*string != NULL && !standard_header) {
6986 headername_trans = prefs_common_translated_header_name(*string);
6987 /* support mixed translated and untranslated headers */
6988 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6989 standard_header = TRUE;
6990 string++;
6992 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6993 /* store untranslated header name */
6994 g_string_append_printf(header, "%s %s\n",
6995 compose_untranslated_header_name(headername_wcolon), headervalue);
6998 g_free(headername);
6999 g_free(headername_wcolon);
7002 return g_string_free(header, FALSE);
7005 #undef IS_IN_CUSTOM_HEADER
7007 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
7008 gint header_len, gboolean addr_field)
7010 gchar *tmpstr = NULL;
7011 const gchar *out_codeset = NULL;
7013 cm_return_if_fail(src != NULL);
7014 cm_return_if_fail(dest != NULL);
7016 if (len < 1) return;
7018 tmpstr = g_strdup(src);
7020 subst_char(tmpstr, '\n', ' ');
7021 subst_char(tmpstr, '\r', ' ');
7022 g_strchomp(tmpstr);
7024 if (!g_utf8_validate(tmpstr, -1, NULL)) {
7025 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
7026 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
7027 g_free(tmpstr);
7028 tmpstr = mybuf;
7031 codeconv_set_strict(TRUE);
7032 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7033 conv_get_charset_str(compose->out_encoding));
7034 codeconv_set_strict(FALSE);
7036 if (!dest || *dest == '\0') {
7037 gchar *test_conv_global_out = NULL;
7038 gchar *test_conv_reply = NULL;
7040 /* automatic mode. be automatic. */
7041 codeconv_set_strict(TRUE);
7043 out_codeset = conv_get_outgoing_charset_str();
7044 if (out_codeset) {
7045 debug_print("trying to convert to %s\n", out_codeset);
7046 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7049 if (!test_conv_global_out && compose->orig_charset
7050 && strcmp(compose->orig_charset, CS_US_ASCII)) {
7051 out_codeset = compose->orig_charset;
7052 debug_print("failure; trying to convert to %s\n", out_codeset);
7053 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7056 if (!test_conv_global_out && !test_conv_reply) {
7057 /* we're lost */
7058 out_codeset = CS_INTERNAL;
7059 debug_print("finally using %s\n", out_codeset);
7061 g_free(test_conv_global_out);
7062 g_free(test_conv_reply);
7063 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7064 out_codeset);
7065 codeconv_set_strict(FALSE);
7067 g_free(tmpstr);
7070 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7072 gchar *address;
7074 cm_return_if_fail(user_data != NULL);
7076 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7077 g_strstrip(address);
7078 if (*address != '\0') {
7079 gchar *name = procheader_get_fromname(address);
7080 extract_address(address);
7081 #ifndef USE_ALT_ADDRBOOK
7082 addressbook_add_contact(name, address, NULL, NULL);
7083 #else
7084 debug_print("%s: %s\n", name, address);
7085 if (addressadd_selection(name, address, NULL, NULL)) {
7086 debug_print( "addressbook_add_contact - added\n" );
7088 #endif
7090 g_free(address);
7093 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7095 GtkWidget *menuitem;
7096 gchar *address;
7098 cm_return_if_fail(menu != NULL);
7099 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7101 menuitem = gtk_separator_menu_item_new();
7102 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7103 gtk_widget_show(menuitem);
7105 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7106 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7108 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7109 g_strstrip(address);
7110 if (*address == '\0') {
7111 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7114 g_signal_connect(G_OBJECT(menuitem), "activate",
7115 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7116 gtk_widget_show(menuitem);
7119 void compose_add_extra_header(gchar *header, GtkListStore *model)
7121 GtkTreeIter iter;
7122 if (strcmp(header, "")) {
7123 COMBOBOX_ADD(model, header, COMPOSE_TO);
7127 void compose_add_extra_header_entries(GtkListStore *model)
7129 FILE *exh;
7130 gchar *exhrc;
7131 gchar buf[BUFFSIZE];
7132 gint lastc;
7134 if (extra_headers == NULL) {
7135 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7136 if ((exh = claws_fopen(exhrc, "rb")) == NULL) {
7137 debug_print("extra headers file not found\n");
7138 goto extra_headers_done;
7140 while (claws_fgets(buf, BUFFSIZE, exh) != NULL) {
7141 lastc = strlen(buf) - 1; /* remove trailing control chars */
7142 while (lastc >= 0 && buf[lastc] != ':')
7143 buf[lastc--] = '\0';
7144 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7145 buf[lastc] = '\0'; /* remove trailing : for comparison */
7146 if (custom_header_is_allowed(buf)) {
7147 buf[lastc] = ':';
7148 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7150 else
7151 g_message("disallowed extra header line: %s\n", buf);
7153 else {
7154 if (buf[0] != '#')
7155 g_message("invalid extra header line: %s\n", buf);
7158 claws_fclose(exh);
7159 extra_headers_done:
7160 g_free(exhrc);
7161 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7162 extra_headers = g_slist_reverse(extra_headers);
7164 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7167 #ifdef USE_LDAP
7168 static void _ldap_srv_func(gpointer data, gpointer user_data)
7170 LdapServer *server = (LdapServer *)data;
7171 gboolean *enable = (gboolean *)user_data;
7173 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7174 server->searchFlag = *enable;
7176 #endif
7178 static void compose_create_header_entry(Compose *compose)
7180 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7182 GtkWidget *combo;
7183 GtkWidget *entry;
7184 GtkWidget *button;
7185 GtkWidget *hbox;
7186 gchar **string;
7187 const gchar *header = NULL;
7188 ComposeHeaderEntry *headerentry;
7189 gboolean standard_header = FALSE;
7190 GtkListStore *model;
7191 GtkTreeIter iter;
7193 headerentry = g_new0(ComposeHeaderEntry, 1);
7195 /* Combo box model */
7196 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7197 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7198 COMPOSE_TO);
7199 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7200 COMPOSE_CC);
7201 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7202 COMPOSE_BCC);
7203 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7204 COMPOSE_NEWSGROUPS);
7205 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7206 COMPOSE_REPLYTO);
7207 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7208 COMPOSE_FOLLOWUPTO);
7209 compose_add_extra_header_entries(model);
7211 /* Combo box */
7212 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7213 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7214 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7215 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7216 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7217 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7218 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7219 G_CALLBACK(compose_grab_focus_cb), compose);
7220 gtk_widget_show(combo);
7222 gtk_grid_attach(GTK_GRID(compose->header_table), combo, 0, compose->header_nextrow,
7223 1, 1);
7224 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7225 const gchar *last_header_entry = gtk_entry_get_text(
7226 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7227 string = headers;
7228 while (*string != NULL) {
7229 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7230 standard_header = TRUE;
7231 string++;
7233 if (standard_header)
7234 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7236 if (!compose->header_last || !standard_header) {
7237 switch(compose->account->protocol) {
7238 case A_NNTP:
7239 header = prefs_common_translated_header_name("Newsgroups:");
7240 break;
7241 default:
7242 header = prefs_common_translated_header_name("To:");
7243 break;
7246 if (header)
7247 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7249 gtk_editable_set_editable(
7250 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7251 prefs_common.type_any_header);
7253 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7254 G_CALLBACK(compose_grab_focus_cb), compose);
7256 /* Entry field with cleanup button */
7257 button = gtk_button_new_from_icon_name("edit-clear", GTK_ICON_SIZE_MENU);
7258 gtk_widget_show(button);
7259 CLAWS_SET_TIP(button,
7260 _("Delete entry contents"));
7261 entry = gtk_entry_new();
7262 gtk_widget_show(entry);
7263 CLAWS_SET_TIP(entry,
7264 _("Use <tab> to autocomplete from addressbook"));
7265 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
7266 gtk_widget_show(hbox);
7267 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7268 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7269 gtk_grid_attach(GTK_GRID(compose->header_table), hbox, 1, compose->header_nextrow,
7270 1, 1);
7271 gtk_widget_set_hexpand(hbox, TRUE);
7272 gtk_widget_set_halign(hbox, GTK_ALIGN_FILL);
7274 g_signal_connect(G_OBJECT(entry), "key-press-event",
7275 G_CALLBACK(compose_headerentry_key_press_event_cb),
7276 headerentry);
7277 g_signal_connect(G_OBJECT(entry), "changed",
7278 G_CALLBACK(compose_headerentry_changed_cb),
7279 headerentry);
7280 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7281 G_CALLBACK(compose_grab_focus_cb), compose);
7283 g_signal_connect(G_OBJECT(button), "clicked",
7284 G_CALLBACK(compose_headerentry_button_clicked_cb),
7285 headerentry);
7287 /* email dnd */
7288 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7289 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7290 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7291 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7292 G_CALLBACK(compose_header_drag_received_cb),
7293 entry);
7294 g_signal_connect(G_OBJECT(entry), "drag-drop",
7295 G_CALLBACK(compose_drag_drop),
7296 compose);
7297 g_signal_connect(G_OBJECT(entry), "populate-popup",
7298 G_CALLBACK(compose_entry_popup_extend),
7299 NULL);
7301 #ifdef USE_LDAP
7302 #ifndef PASSWORD_CRYPTO_OLD
7303 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7304 if (pwd_servers != NULL && primary_passphrase() == NULL) {
7305 gboolean enable = FALSE;
7306 debug_print("Primary passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7307 /* Temporarily disable password-protected LDAP servers,
7308 * because user did not provide a primary passphrase.
7309 * We can safely enable searchFlag on all servers in this list
7310 * later, since addrindex_get_password_protected_ldap_servers()
7311 * includes servers which have it enabled initially. */
7312 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7313 compose->passworded_ldap_servers = pwd_servers;
7315 #endif /* PASSWORD_CRYPTO_OLD */
7316 #endif /* USE_LDAP */
7318 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7320 headerentry->compose = compose;
7321 headerentry->combo = combo;
7322 headerentry->entry = entry;
7323 headerentry->button = button;
7324 headerentry->hbox = hbox;
7325 headerentry->headernum = compose->header_nextrow;
7326 headerentry->type = PREF_NONE;
7328 compose->header_nextrow++;
7329 compose->header_last = headerentry;
7330 compose->header_list =
7331 g_slist_append(compose->header_list,
7332 headerentry);
7335 static void compose_add_header_entry(Compose *compose, const gchar *header,
7336 gchar *text, ComposePrefType pref_type)
7338 ComposeHeaderEntry *last_header = compose->header_last;
7339 gchar *tmp = g_strdup(text), *email;
7340 gboolean replyto_hdr;
7342 replyto_hdr = (!strcasecmp(header,
7343 prefs_common_translated_header_name("Reply-To:")) ||
7344 !strcasecmp(header,
7345 prefs_common_translated_header_name("Followup-To:")) ||
7346 !strcasecmp(header,
7347 prefs_common_translated_header_name("In-Reply-To:")));
7349 extract_address(tmp);
7350 email = g_utf8_strdown(tmp, -1);
7352 if (replyto_hdr == FALSE &&
7353 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7355 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7356 header, text, (gint) pref_type);
7357 g_free(email);
7358 g_free(tmp);
7359 return;
7362 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7363 gtk_entry_set_text(GTK_ENTRY(
7364 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7365 else
7366 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7367 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7368 last_header->type = pref_type;
7370 if (replyto_hdr == FALSE)
7371 g_hash_table_insert(compose->email_hashtable, email,
7372 GUINT_TO_POINTER(1));
7373 else
7374 g_free(email);
7376 g_free(tmp);
7379 static void compose_destroy_headerentry(Compose *compose,
7380 ComposeHeaderEntry *headerentry)
7382 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7383 gchar *email;
7385 extract_address(text);
7386 email = g_utf8_strdown(text, -1);
7387 g_hash_table_remove(compose->email_hashtable, email);
7388 g_free(text);
7389 g_free(email);
7391 gtk_widget_destroy(headerentry->combo);
7392 gtk_widget_destroy(headerentry->entry);
7393 gtk_widget_destroy(headerentry->button);
7394 gtk_widget_destroy(headerentry->hbox);
7395 g_free(headerentry);
7398 static void compose_remove_header_entries(Compose *compose)
7400 GSList *list;
7401 for (list = compose->header_list; list; list = list->next)
7402 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7404 compose->header_last = NULL;
7405 g_slist_free(compose->header_list);
7406 compose->header_list = NULL;
7407 compose->header_nextrow = 1;
7408 compose_create_header_entry(compose);
7411 static GtkWidget *compose_create_header(Compose *compose)
7413 GtkWidget *from_optmenu_hbox;
7414 GtkWidget *header_table_main;
7415 GtkWidget *header_scrolledwin;
7416 GtkWidget *header_table;
7418 /* parent with account selection and from header */
7419 header_table_main = gtk_grid_new();
7420 gtk_widget_show(header_table_main);
7421 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7423 from_optmenu_hbox = compose_account_option_menu_create(compose);
7424 gtk_grid_attach(GTK_GRID(header_table_main),from_optmenu_hbox, 0, 0, 1, 1);
7425 gtk_widget_set_hexpand(from_optmenu_hbox, TRUE);
7426 gtk_widget_set_halign(from_optmenu_hbox, GTK_ALIGN_FILL);
7428 /* child with header labels and entries */
7429 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7430 gtk_widget_show(header_scrolledwin);
7431 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7433 header_table = gtk_grid_new();
7434 gtk_widget_show(header_table);
7435 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7436 gtk_container_add(GTK_CONTAINER(header_scrolledwin), header_table);
7437 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7438 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7439 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7441 gtk_grid_attach(GTK_GRID(header_table_main), header_scrolledwin, 0, 1, 1, 1);
7442 gtk_widget_set_vexpand(header_scrolledwin, TRUE);
7443 gtk_widget_set_valign(header_scrolledwin, GTK_ALIGN_FILL);
7445 compose->header_table = header_table;
7446 compose->header_list = NULL;
7447 compose->header_nextrow = 0;
7449 compose_create_header_entry(compose);
7451 compose->table = NULL;
7453 return header_table_main;
7456 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7458 Compose *compose = (Compose *)data;
7459 GdkEventButton event;
7461 event.button = 3;
7462 event.time = gtk_get_current_event_time();
7464 return attach_button_pressed(compose->attach_clist, &event, compose);
7467 static GtkWidget *compose_create_attach(Compose *compose)
7469 GtkWidget *attach_scrwin;
7470 GtkWidget *attach_clist;
7472 GtkListStore *store;
7473 GtkCellRenderer *renderer;
7474 GtkTreeViewColumn *column;
7475 GtkTreeSelection *selection;
7477 /* attachment list */
7478 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7479 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7480 GTK_POLICY_AUTOMATIC,
7481 GTK_POLICY_AUTOMATIC);
7482 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7484 store = gtk_list_store_new(N_ATTACH_COLS,
7485 G_TYPE_STRING,
7486 G_TYPE_STRING,
7487 G_TYPE_STRING,
7488 G_TYPE_STRING,
7489 G_TYPE_POINTER,
7490 G_TYPE_AUTO_POINTER,
7491 -1);
7492 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7493 (GTK_TREE_MODEL(store)));
7494 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7495 g_object_unref(store);
7497 renderer = gtk_cell_renderer_text_new();
7498 column = gtk_tree_view_column_new_with_attributes
7499 (_("Mime type"), renderer, "text",
7500 COL_MIMETYPE, NULL);
7501 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7503 renderer = gtk_cell_renderer_text_new();
7504 column = gtk_tree_view_column_new_with_attributes
7505 (_("Size"), renderer, "text",
7506 COL_SIZE, NULL);
7507 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7509 renderer = gtk_cell_renderer_text_new();
7510 column = gtk_tree_view_column_new_with_attributes
7511 (_("Name"), renderer, "text",
7512 COL_NAME, NULL);
7513 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7515 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7516 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7518 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7519 G_CALLBACK(attach_selected), compose);
7520 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7521 G_CALLBACK(attach_button_pressed), compose);
7522 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7523 G_CALLBACK(popup_attach_button_pressed), compose);
7524 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7525 G_CALLBACK(attach_key_pressed), compose);
7527 /* drag and drop */
7528 gtk_drag_dest_set(attach_clist,
7529 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7530 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7531 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7532 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7533 G_CALLBACK(compose_attach_drag_received_cb),
7534 compose);
7535 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7536 G_CALLBACK(compose_drag_drop),
7537 compose);
7539 compose->attach_scrwin = attach_scrwin;
7540 compose->attach_clist = attach_clist;
7542 return attach_scrwin;
7545 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7547 static GtkWidget *compose_create_others(Compose *compose)
7549 GtkWidget *table;
7550 GtkWidget *savemsg_checkbtn;
7551 GtkWidget *savemsg_combo;
7552 GtkWidget *savemsg_select;
7554 guint rowcount = 0;
7555 gchar *folderidentifier;
7557 /* Table for settings */
7558 table = gtk_grid_new();
7559 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7560 gtk_widget_show(table);
7561 gtk_grid_set_row_spacing(GTK_GRID(table), VSPACING_NARROW);
7562 rowcount = 0;
7564 /* Save Message to folder */
7565 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7566 gtk_widget_show(savemsg_checkbtn);
7567 gtk_grid_attach(GTK_GRID(table), savemsg_checkbtn, 0, rowcount, 1, 1);
7568 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7569 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7572 savemsg_combo = gtk_combo_box_text_new_with_entry();
7573 compose->savemsg_checkbtn = savemsg_checkbtn;
7574 compose->savemsg_combo = savemsg_combo;
7575 gtk_widget_show(savemsg_combo);
7577 if (prefs_common.compose_save_to_history)
7578 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7579 prefs_common.compose_save_to_history);
7580 gtk_grid_attach(GTK_GRID(table), savemsg_combo, 1, rowcount, 1, 1);
7581 gtk_widget_set_hexpand(savemsg_combo, TRUE);
7582 gtk_widget_set_halign(savemsg_combo, GTK_ALIGN_FILL);
7583 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7584 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7585 G_CALLBACK(compose_grab_focus_cb), compose);
7586 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7587 if (compose->account->set_sent_folder || prefs_common.savemsg)
7588 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), TRUE);
7589 else
7590 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), FALSE);
7591 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), TRUE);
7592 folderidentifier = folder_item_get_identifier(account_get_special_folder
7593 (compose->account, F_OUTBOX));
7594 compose_set_save_to(compose, folderidentifier);
7595 g_free(folderidentifier);
7598 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7599 gtk_widget_show(savemsg_select);
7600 gtk_grid_attach(GTK_GRID(table), savemsg_select, 2, rowcount, 1, 1);
7601 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7602 G_CALLBACK(compose_savemsg_select_cb),
7603 compose);
7605 return table;
7608 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7610 FolderItem *dest;
7611 gchar * path;
7613 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7614 _("Select folder to save message to"));
7615 if (!dest) return;
7617 path = folder_item_get_identifier(dest);
7619 compose_set_save_to(compose, path);
7620 g_free(path);
7623 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7624 GdkAtom clip, GtkTextIter *insert_place);
7627 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7628 Compose *compose)
7630 gint prev_autowrap;
7631 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7632 #if USE_ENCHANT
7633 if (event->button == 3) {
7634 GtkTextIter iter;
7635 GtkTextIter sel_start, sel_end;
7636 gboolean stuff_selected;
7637 gint x, y;
7638 /* move the cursor to allow GtkAspell to check the word
7639 * under the mouse */
7640 if (event->x && event->y) {
7641 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7642 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7643 &x, &y);
7644 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7645 &iter, x, y);
7646 } else {
7647 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7648 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7650 /* get selection */
7651 stuff_selected = gtk_text_buffer_get_selection_bounds(
7652 buffer,
7653 &sel_start, &sel_end);
7655 gtk_text_buffer_place_cursor (buffer, &iter);
7656 /* reselect stuff */
7657 if (stuff_selected
7658 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7659 gtk_text_buffer_select_range(buffer,
7660 &sel_start, &sel_end);
7662 return FALSE; /* pass the event so that the right-click goes through */
7664 #endif
7665 if (event->button == 2) {
7666 GtkTextIter iter;
7667 gint x, y;
7668 BLOCK_WRAP();
7670 /* get the middle-click position to paste at the correct place */
7671 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7672 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7673 &x, &y);
7674 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7675 &iter, x, y);
7677 entry_paste_clipboard(compose, text,
7678 prefs_common.linewrap_pastes,
7679 GDK_SELECTION_PRIMARY, &iter);
7680 UNBLOCK_WRAP();
7681 return TRUE;
7683 return FALSE;
7686 #if USE_ENCHANT
7687 static void compose_spell_menu_changed(void *data)
7689 Compose *compose = (Compose *)data;
7690 GSList *items;
7691 GtkWidget *menuitem;
7692 GtkWidget *parent_item;
7693 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7694 GSList *spell_menu;
7696 if (compose->gtkaspell == NULL)
7697 return;
7699 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7700 "/Menu/Spelling/Options");
7702 /* setting the submenu removes /Spelling/Options from the factory
7703 * so we need to save it */
7705 if (parent_item == NULL) {
7706 parent_item = compose->aspell_options_menu;
7707 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7708 } else
7709 compose->aspell_options_menu = parent_item;
7711 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7713 spell_menu = g_slist_reverse(spell_menu);
7714 for (items = spell_menu;
7715 items; items = items->next) {
7716 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7717 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7718 gtk_widget_show(GTK_WIDGET(menuitem));
7720 g_slist_free(spell_menu);
7722 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7723 gtk_widget_show(parent_item);
7726 static void compose_dict_changed(void *data)
7728 Compose *compose = (Compose *) data;
7730 if(!compose->gtkaspell)
7731 return;
7732 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7733 return;
7735 gtkaspell_highlight_all(compose->gtkaspell);
7736 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7738 #endif
7740 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7742 Compose *compose = (Compose *)data;
7743 GdkEventButton event;
7745 event.button = 3;
7746 event.time = gtk_get_current_event_time();
7747 event.x = 0;
7748 event.y = 0;
7750 return text_clicked(compose->text, &event, compose);
7753 static gboolean compose_force_window_origin = TRUE;
7754 static Compose *compose_create(PrefsAccount *account,
7755 FolderItem *folder,
7756 ComposeMode mode,
7757 gboolean batch)
7759 Compose *compose;
7760 GtkWidget *window;
7761 GtkWidget *vbox;
7762 GtkWidget *menubar;
7763 GtkWidget *handlebox;
7765 GtkWidget *notebook;
7767 GtkWidget *attach_hbox;
7768 GtkWidget *attach_lab1;
7769 GtkWidget *attach_lab2;
7771 GtkWidget *vbox2;
7773 GtkWidget *label;
7774 GtkWidget *subject_hbox;
7775 GtkWidget *subject_frame;
7776 GtkWidget *subject_entry;
7777 GtkWidget *subject;
7778 GtkWidget *paned;
7780 GtkWidget *edit_vbox;
7781 GtkWidget *ruler_hbox;
7782 GtkWidget *ruler;
7783 GtkWidget *scrolledwin;
7784 GtkWidget *text;
7785 GtkTextBuffer *buffer;
7786 GtkClipboard *clipboard;
7788 UndoMain *undostruct;
7790 GtkWidget *popupmenu;
7791 GtkWidget *tmpl_menu;
7792 GtkActionGroup *action_group = NULL;
7794 #if USE_ENCHANT
7795 GtkAspell * gtkaspell = NULL;
7796 #endif
7798 static GdkGeometry geometry;
7799 GdkRectangle workarea = {0};
7801 cm_return_val_if_fail(account != NULL, NULL);
7803 default_header_bgcolor = prefs_common.color[COL_DEFAULT_HEADER_BG],
7804 default_header_color = prefs_common.color[COL_DEFAULT_HEADER],
7806 debug_print("Creating compose window...\n");
7807 compose = g_new0(Compose, 1);
7809 compose->batch = batch;
7810 compose->account = account;
7811 compose->folder = folder;
7813 g_mutex_init(&compose->mutex);
7814 compose->set_cursor_pos = -1;
7816 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7818 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7819 gtk_window_set_default_size(GTK_WINDOW(window), prefs_common.compose_width,
7820 prefs_common.compose_height);
7822 gdk_monitor_get_workarea(gdk_display_get_primary_monitor(gdk_display_get_default()),
7823 &workarea);
7825 if (!geometry.max_width) {
7826 geometry.max_width = workarea.width;
7827 geometry.max_height = workarea.height;
7830 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7831 &geometry, GDK_HINT_MAX_SIZE);
7832 if (!geometry.min_width) {
7833 geometry.min_width = 600;
7834 geometry.min_height = 440;
7836 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7837 &geometry, GDK_HINT_MIN_SIZE);
7839 #ifndef GENERIC_UMPC
7840 if (compose_force_window_origin)
7841 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7842 prefs_common.compose_y);
7843 #endif
7844 g_signal_connect(G_OBJECT(window), "delete_event",
7845 G_CALLBACK(compose_delete_cb), compose);
7846 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7847 gtk_widget_realize(window);
7849 gtkut_widget_set_composer_icon(window);
7851 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
7852 gtk_container_add(GTK_CONTAINER(window), vbox);
7854 compose->ui_manager = gtk_ui_manager_new();
7855 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7856 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7857 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7858 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7859 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7860 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7861 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7862 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7863 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7864 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7870 #ifdef USE_ENCHANT
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7872 #endif
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7877 /* Compose menu */
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7885 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7892 /* Edit menu */
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7898 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7908 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7915 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7922 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7935 #if USE_ENCHANT
7936 /* Spelling menu */
7937 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7938 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7940 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7941 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7942 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7943 #endif
7945 /* Options menu */
7946 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7947 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7948 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7949 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7950 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7952 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7953 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7954 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7955 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7956 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7959 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7960 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7961 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7962 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7963 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7964 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7965 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7967 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7968 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7969 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7970 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7971 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7973 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7975 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7976 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7977 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7978 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7979 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7981 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7982 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)
7983 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)
7984 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7986 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7988 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7989 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)
7990 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)
7992 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7994 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7995 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)
7996 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7998 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7999 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)
8000 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
8002 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
8004 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
8005 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)
8006 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
8007 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
8008 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
8009 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
8011 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
8012 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)
8013 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)
8014 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
8015 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
8017 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
8018 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
8019 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
8020 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
8021 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
8022 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
8024 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
8025 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
8026 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)
8028 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
8029 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
8030 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
8031 /* phew. */
8033 /* Tools menu */
8034 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
8035 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
8036 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
8037 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8038 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
8039 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8041 /* Help menu */
8042 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
8044 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
8045 gtk_widget_show_all(menubar);
8047 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
8048 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
8050 handlebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8051 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8053 gtk_widget_realize(handlebox);
8054 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8055 (gpointer)compose);
8057 vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
8058 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8059 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8061 /* Notebook */
8062 notebook = gtk_notebook_new();
8063 gtk_widget_show(notebook);
8065 /* header labels and entries */
8066 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8067 compose_create_header(compose),
8068 gtk_label_new_with_mnemonic(_("Hea_der")));
8069 /* attachment list */
8070 attach_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8071 gtk_widget_show(attach_hbox);
8073 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8074 gtk_widget_show(attach_lab1);
8075 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8077 attach_lab2 = gtk_label_new("");
8078 gtk_widget_show(attach_lab2);
8079 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8081 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8082 compose_create_attach(compose),
8083 attach_hbox);
8084 /* Others Tab */
8085 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8086 compose_create_others(compose),
8087 gtk_label_new_with_mnemonic(_("Othe_rs")));
8089 /* Subject */
8090 subject_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8091 gtk_widget_show(subject_hbox);
8093 subject_frame = gtk_frame_new(NULL);
8094 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8095 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8096 gtk_widget_show(subject_frame);
8098 subject = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, HSPACING_NARROW);
8099 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8100 gtk_widget_show(subject);
8102 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8103 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8104 gtk_widget_show(label);
8106 #ifdef USE_ENCHANT
8107 subject_entry = claws_spell_entry_new();
8108 #else
8109 subject_entry = gtk_entry_new();
8110 #endif
8111 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8112 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8113 G_CALLBACK(compose_grab_focus_cb), compose);
8114 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8115 gtk_widget_show(subject_entry);
8116 compose->subject_entry = subject_entry;
8117 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8119 edit_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
8121 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8123 /* ruler */
8124 ruler_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
8125 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8127 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8128 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8129 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8130 BORDER_WIDTH);
8132 /* text widget */
8133 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8134 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8135 GTK_POLICY_AUTOMATIC,
8136 GTK_POLICY_AUTOMATIC);
8137 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8138 GTK_SHADOW_IN);
8139 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8141 text = gtk_text_view_new();
8142 if (prefs_common.show_compose_margin) {
8143 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8144 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8146 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8147 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8148 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8149 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8150 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8152 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8153 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8154 G_CALLBACK(compose_edit_size_alloc),
8155 ruler);
8156 g_signal_connect(G_OBJECT(buffer), "changed",
8157 G_CALLBACK(compose_changed_cb), compose);
8158 g_signal_connect(G_OBJECT(text), "grab_focus",
8159 G_CALLBACK(compose_grab_focus_cb), compose);
8160 g_signal_connect(G_OBJECT(buffer), "insert_text",
8161 G_CALLBACK(text_inserted), compose);
8162 g_signal_connect(G_OBJECT(text), "button_press_event",
8163 G_CALLBACK(text_clicked), compose);
8164 g_signal_connect(G_OBJECT(text), "popup-menu",
8165 G_CALLBACK(compose_popup_menu), compose);
8166 g_signal_connect(G_OBJECT(subject_entry), "changed",
8167 G_CALLBACK(compose_changed_cb), compose);
8168 g_signal_connect(G_OBJECT(subject_entry), "activate",
8169 G_CALLBACK(compose_subject_entry_activated), compose);
8171 /* drag and drop */
8172 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8173 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8174 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8175 g_signal_connect(G_OBJECT(text), "drag_data_received",
8176 G_CALLBACK(compose_insert_drag_received_cb),
8177 compose);
8178 g_signal_connect(G_OBJECT(text), "drag-drop",
8179 G_CALLBACK(compose_drag_drop),
8180 compose);
8181 g_signal_connect(G_OBJECT(text), "key-press-event",
8182 G_CALLBACK(completion_set_focus_to_subject),
8183 compose);
8184 gtk_widget_show_all(vbox);
8186 /* pane between attach clist and text */
8187 paned = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
8188 gtk_box_pack_start(GTK_BOX(vbox2), paned, TRUE, TRUE, 0);
8189 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8190 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8191 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8192 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8193 G_CALLBACK(compose_notebook_size_alloc), paned);
8195 gtk_widget_show_all(paned);
8198 if (prefs_common.textfont) {
8199 PangoFontDescription *font_desc;
8201 font_desc = pango_font_description_from_string
8202 (prefs_common.textfont);
8203 if (font_desc) {
8204 gtk_widget_override_font(text, font_desc);
8205 pango_font_description_free(font_desc);
8209 gtk_action_group_add_actions(action_group, compose_popup_entries,
8210 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8211 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8212 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8213 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8214 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8215 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8216 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8218 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8220 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8221 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8222 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8224 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8226 undostruct = undo_init(text);
8227 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8228 compose);
8230 address_completion_start(window);
8232 compose->window = window;
8233 compose->vbox = vbox;
8234 compose->menubar = menubar;
8235 compose->handlebox = handlebox;
8237 compose->vbox2 = vbox2;
8239 compose->paned = paned;
8241 compose->attach_label = attach_lab2;
8243 compose->notebook = notebook;
8244 compose->edit_vbox = edit_vbox;
8245 compose->ruler_hbox = ruler_hbox;
8246 compose->ruler = ruler;
8247 compose->scrolledwin = scrolledwin;
8248 compose->text = text;
8250 compose->focused_editable = NULL;
8252 compose->popupmenu = popupmenu;
8254 compose->tmpl_menu = tmpl_menu;
8256 compose->mode = mode;
8257 compose->rmode = mode;
8259 compose->targetinfo = NULL;
8260 compose->replyinfo = NULL;
8261 compose->fwdinfo = NULL;
8263 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8264 g_str_equal, (GDestroyNotify) g_free, NULL);
8266 compose->replyto = NULL;
8267 compose->cc = NULL;
8268 compose->bcc = NULL;
8269 compose->followup_to = NULL;
8271 compose->ml_post = NULL;
8273 compose->inreplyto = NULL;
8274 compose->references = NULL;
8275 compose->msgid = NULL;
8276 compose->boundary = NULL;
8278 compose->autowrap = prefs_common.autowrap;
8279 compose->autoindent = prefs_common.auto_indent;
8280 compose->use_signing = FALSE;
8281 compose->use_encryption = FALSE;
8282 compose->privacy_system = NULL;
8283 compose->encdata = NULL;
8285 compose->modified = FALSE;
8287 compose->return_receipt = FALSE;
8289 compose->to_list = NULL;
8290 compose->newsgroup_list = NULL;
8292 compose->undostruct = undostruct;
8294 compose->sig_str = NULL;
8296 compose->exteditor_file = NULL;
8297 compose->exteditor_pid = INVALID_PID;
8298 compose->exteditor_tag = -1;
8299 compose->exteditor_socket = NULL;
8300 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8302 compose->folder_update_callback_id =
8303 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8304 compose_update_folder_hook,
8305 (gpointer) compose);
8307 #if USE_ENCHANT
8308 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8309 if (mode != COMPOSE_REDIRECT) {
8310 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8311 strcmp(prefs_common.dictionary, "")) {
8312 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8313 prefs_common.alt_dictionary,
8314 conv_get_locale_charset_str(),
8315 prefs_common.color[COL_MISSPELLED],
8316 prefs_common.check_while_typing,
8317 prefs_common.recheck_when_changing_dict,
8318 prefs_common.use_alternate,
8319 prefs_common.use_both_dicts,
8320 GTK_TEXT_VIEW(text),
8321 GTK_WINDOW(compose->window),
8322 compose_dict_changed,
8323 compose_spell_menu_changed,
8324 compose);
8325 if (!gtkaspell) {
8326 alertpanel_error(_("Spell checker could not "
8327 "be started.\n%s"),
8328 gtkaspell_checkers_strerror());
8329 gtkaspell_checkers_reset_error();
8330 } else {
8331 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8335 compose->gtkaspell = gtkaspell;
8336 compose_spell_menu_changed(compose);
8337 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8338 #endif
8340 compose_select_account(compose, account, TRUE);
8342 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8343 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8345 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8346 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8348 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8349 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8351 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8352 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8354 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8355 if (account->protocol != A_NNTP)
8356 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8357 prefs_common_translated_header_name("To:"));
8358 else
8359 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8360 prefs_common_translated_header_name("Newsgroups:"));
8362 #ifndef USE_ALT_ADDRBOOK
8363 addressbook_set_target_compose(compose);
8364 #endif
8365 if (mode != COMPOSE_REDIRECT)
8366 compose_set_template_menu(compose);
8367 else {
8368 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8371 compose_list = g_list_append(compose_list, compose);
8373 if (!prefs_common.show_ruler)
8374 gtk_widget_hide(ruler_hbox);
8376 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8378 /* Priority */
8379 compose->priority = PRIORITY_NORMAL;
8380 compose_update_priority_menu_item(compose);
8382 compose_set_out_encoding(compose);
8384 /* Actions menu */
8385 compose_update_actions_menu(compose);
8387 /* Privacy Systems menu */
8388 compose_update_privacy_systems_menu(compose);
8389 compose_activate_privacy_system(compose, account, TRUE);
8391 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8392 if (batch) {
8393 gtk_widget_realize(window);
8394 } else {
8395 gtk_widget_show(window);
8398 return compose;
8401 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8403 GList *accounts;
8404 GtkWidget *hbox;
8405 GtkWidget *optmenu;
8406 GtkWidget *optmenubox;
8407 GtkWidget *fromlabel;
8408 GtkListStore *menu;
8409 GtkTreeIter iter;
8410 GtkWidget *from_name = NULL;
8412 gint num = 0, def_menu = 0;
8414 accounts = account_get_list();
8415 cm_return_val_if_fail(accounts != NULL, NULL);
8417 optmenubox = gtk_event_box_new();
8418 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8419 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8421 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
8422 from_name = gtk_entry_new();
8424 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8425 G_CALLBACK(compose_grab_focus_cb), compose);
8426 g_signal_connect_after(G_OBJECT(from_name), "activate",
8427 G_CALLBACK(from_name_activate_cb), optmenu);
8429 for (; accounts != NULL; accounts = accounts->next, num++) {
8430 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8431 gchar *name, *from = NULL;
8433 if (ac == compose->account) def_menu = num;
8435 name = g_markup_printf_escaped("<i>%s</i>",
8436 ac->account_name);
8438 if (ac == compose->account) {
8439 if (ac->name && *ac->name) {
8440 gchar *buf;
8441 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8442 from = g_strdup_printf("%s <%s>",
8443 buf, ac->address);
8444 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8445 } else {
8446 from = g_strdup_printf("%s",
8447 ac->address);
8448 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8450 if (cur_account != compose->account) {
8451 GdkColor color;
8453 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_bgcolor, color);
8454 gtk_widget_modify_base(
8455 GTK_WIDGET(from_name),
8456 GTK_STATE_NORMAL, &color);
8457 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_color, color);
8458 gtk_widget_modify_text(
8459 GTK_WIDGET(from_name),
8460 GTK_STATE_NORMAL, &color);
8463 COMBOBOX_ADD(menu, name, ac->account_id);
8464 g_free(name);
8465 g_free(from);
8468 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8470 g_signal_connect(G_OBJECT(optmenu), "changed",
8471 G_CALLBACK(account_activated),
8472 compose);
8473 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8474 G_CALLBACK(compose_entry_popup_extend),
8475 NULL);
8477 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8478 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8480 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8481 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8482 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8484 CLAWS_SET_TIP(optmenubox,
8485 _("Account to use for this email"));
8486 CLAWS_SET_TIP(from_name,
8487 _("Sender address to be used"));
8489 compose->account_combo = optmenu;
8490 compose->from_name = from_name;
8492 return hbox;
8495 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8497 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8498 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8499 Compose *compose = (Compose *) data;
8500 if (active) {
8501 compose->priority = value;
8505 static void compose_reply_change_mode(Compose *compose,
8506 ComposeMode action)
8508 gboolean was_modified = compose->modified;
8510 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8512 cm_return_if_fail(compose->replyinfo != NULL);
8514 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8515 ml = TRUE;
8516 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8517 followup = TRUE;
8518 if (action == COMPOSE_REPLY_TO_ALL)
8519 all = TRUE;
8520 if (action == COMPOSE_REPLY_TO_SENDER)
8521 sender = TRUE;
8522 if (action == COMPOSE_REPLY_TO_LIST)
8523 ml = TRUE;
8525 compose_remove_header_entries(compose);
8526 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8527 if (compose->account->set_autocc && compose->account->auto_cc)
8528 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8530 if (compose->account->set_autobcc && compose->account->auto_bcc)
8531 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8533 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8534 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8535 compose_show_first_last_header(compose, TRUE);
8536 compose->modified = was_modified;
8537 compose_set_title(compose);
8540 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8542 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8543 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8544 Compose *compose = (Compose *) data;
8546 if (active)
8547 compose_reply_change_mode(compose, value);
8550 static void compose_update_priority_menu_item(Compose * compose)
8552 GtkWidget *menuitem = NULL;
8553 switch (compose->priority) {
8554 case PRIORITY_HIGHEST:
8555 menuitem = gtk_ui_manager_get_widget
8556 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8557 break;
8558 case PRIORITY_HIGH:
8559 menuitem = gtk_ui_manager_get_widget
8560 (compose->ui_manager, "/Menu/Options/Priority/High");
8561 break;
8562 case PRIORITY_NORMAL:
8563 menuitem = gtk_ui_manager_get_widget
8564 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8565 break;
8566 case PRIORITY_LOW:
8567 menuitem = gtk_ui_manager_get_widget
8568 (compose->ui_manager, "/Menu/Options/Priority/Low");
8569 break;
8570 case PRIORITY_LOWEST:
8571 menuitem = gtk_ui_manager_get_widget
8572 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8573 break;
8575 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8578 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8580 Compose *compose = (Compose *) data;
8581 gchar *systemid;
8582 gboolean can_sign = FALSE, can_encrypt = FALSE;
8584 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8586 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8587 return;
8589 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8590 g_free(compose->privacy_system);
8591 compose->privacy_system = NULL;
8592 g_free(compose->encdata);
8593 compose->encdata = NULL;
8594 if (systemid != NULL) {
8595 compose->privacy_system = g_strdup(systemid);
8597 can_sign = privacy_system_can_sign(systemid);
8598 can_encrypt = privacy_system_can_encrypt(systemid);
8601 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8603 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8604 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8605 if (compose->toolbar->privacy_sign_btn != NULL) {
8606 gtk_widget_set_sensitive(
8607 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8608 can_sign);
8609 gtk_toggle_tool_button_set_active(
8610 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8611 can_sign ? compose->use_signing : FALSE);
8613 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8614 gtk_widget_set_sensitive(
8615 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8616 can_encrypt);
8617 gtk_toggle_tool_button_set_active(
8618 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8619 can_encrypt ? compose->use_encryption : FALSE);
8623 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8625 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8626 GtkWidget *menuitem = NULL;
8627 GList *children, *amenu;
8628 gboolean can_sign = FALSE, can_encrypt = FALSE;
8629 gboolean found = FALSE;
8631 if (compose->privacy_system != NULL) {
8632 gchar *systemid;
8633 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8634 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8635 cm_return_if_fail(menuitem != NULL);
8637 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8638 amenu = children;
8639 menuitem = NULL;
8640 while (amenu != NULL) {
8641 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8642 if (systemid != NULL) {
8643 if (strcmp(systemid, compose->privacy_system) == 0 &&
8644 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8645 menuitem = GTK_WIDGET(amenu->data);
8647 can_sign = privacy_system_can_sign(systemid);
8648 can_encrypt = privacy_system_can_encrypt(systemid);
8649 found = TRUE;
8650 break;
8652 } else if (strlen(compose->privacy_system) == 0 &&
8653 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8654 menuitem = GTK_WIDGET(amenu->data);
8656 can_sign = FALSE;
8657 can_encrypt = FALSE;
8658 found = TRUE;
8659 break;
8662 amenu = amenu->next;
8664 g_list_free(children);
8665 if (menuitem != NULL)
8666 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8668 if (warn && !found && strlen(compose->privacy_system)) {
8669 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8670 "will not be able to sign or encrypt this message."),
8671 compose->privacy_system);
8675 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8676 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8677 if (compose->toolbar->privacy_sign_btn != NULL) {
8678 gtk_widget_set_sensitive(
8679 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8680 can_sign);
8682 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8683 gtk_widget_set_sensitive(
8684 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8685 can_encrypt);
8689 static void compose_set_out_encoding(Compose *compose)
8691 CharSet out_encoding;
8692 const gchar *branch = NULL;
8693 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8695 switch(out_encoding) {
8696 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8697 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8698 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8699 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8700 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8701 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8702 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8703 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8704 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8705 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8706 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8707 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8708 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8709 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8710 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8711 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8712 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8713 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8714 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8715 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8716 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8717 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8718 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8719 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8720 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8721 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8722 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8723 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8724 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8725 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8726 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8727 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8728 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8729 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8731 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8734 static void compose_set_template_menu(Compose *compose)
8736 GSList *tmpl_list, *cur;
8737 GtkWidget *menu;
8738 GtkWidget *item;
8740 tmpl_list = template_get_config();
8742 menu = gtk_menu_new();
8744 gtk_menu_set_accel_group (GTK_MENU (menu),
8745 gtk_ui_manager_get_accel_group(compose->ui_manager));
8746 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8747 Template *tmpl = (Template *)cur->data;
8748 gchar *accel_path = NULL;
8749 item = gtk_menu_item_new_with_label(tmpl->name);
8750 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8751 g_signal_connect(G_OBJECT(item), "activate",
8752 G_CALLBACK(compose_template_activate_cb),
8753 compose);
8754 g_object_set_data(G_OBJECT(item), "template", tmpl);
8755 gtk_widget_show(item);
8756 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8757 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8758 g_free(accel_path);
8761 gtk_widget_show(menu);
8762 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8765 void compose_update_actions_menu(Compose *compose)
8767 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8770 static void compose_update_privacy_systems_menu(Compose *compose)
8772 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8773 GSList *systems, *cur;
8774 GtkWidget *widget;
8775 GtkWidget *system_none;
8776 GSList *group;
8777 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8778 GtkWidget *privacy_menu = gtk_menu_new();
8780 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8781 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8783 g_signal_connect(G_OBJECT(system_none), "activate",
8784 G_CALLBACK(compose_set_privacy_system_cb), compose);
8786 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8787 gtk_widget_show(system_none);
8789 systems = privacy_get_system_ids();
8790 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8791 gchar *systemid = cur->data;
8793 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8794 widget = gtk_radio_menu_item_new_with_label(group,
8795 privacy_system_get_name(systemid));
8796 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8797 g_strdup(systemid), g_free);
8798 g_signal_connect(G_OBJECT(widget), "activate",
8799 G_CALLBACK(compose_set_privacy_system_cb), compose);
8801 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8802 gtk_widget_show(widget);
8803 g_free(systemid);
8805 g_slist_free(systems);
8806 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8807 gtk_widget_show_all(privacy_menu);
8808 gtk_widget_show_all(privacy_menuitem);
8811 void compose_reflect_prefs_all(void)
8813 GList *cur;
8814 Compose *compose;
8816 for (cur = compose_list; cur != NULL; cur = cur->next) {
8817 compose = (Compose *)cur->data;
8818 compose_set_template_menu(compose);
8822 void compose_reflect_prefs_pixmap_theme(void)
8824 GList *cur;
8825 Compose *compose;
8827 for (cur = compose_list; cur != NULL; cur = cur->next) {
8828 compose = (Compose *)cur->data;
8829 toolbar_update(TOOLBAR_COMPOSE, compose);
8833 static const gchar *compose_quote_char_from_context(Compose *compose)
8835 const gchar *qmark = NULL;
8837 cm_return_val_if_fail(compose != NULL, NULL);
8839 switch (compose->mode) {
8840 /* use forward-specific quote char */
8841 case COMPOSE_FORWARD:
8842 case COMPOSE_FORWARD_AS_ATTACH:
8843 case COMPOSE_FORWARD_INLINE:
8844 if (compose->folder && compose->folder->prefs &&
8845 compose->folder->prefs->forward_with_format)
8846 qmark = compose->folder->prefs->forward_quotemark;
8847 else if (compose->account->forward_with_format)
8848 qmark = compose->account->forward_quotemark;
8849 else
8850 qmark = prefs_common.fw_quotemark;
8851 break;
8853 /* use reply-specific quote char in all other modes */
8854 default:
8855 if (compose->folder && compose->folder->prefs &&
8856 compose->folder->prefs->reply_with_format)
8857 qmark = compose->folder->prefs->reply_quotemark;
8858 else if (compose->account->reply_with_format)
8859 qmark = compose->account->reply_quotemark;
8860 else
8861 qmark = prefs_common.quotemark;
8862 break;
8865 if (qmark == NULL || *qmark == '\0')
8866 qmark = "> ";
8868 return qmark;
8871 static void compose_template_apply(Compose *compose, Template *tmpl,
8872 gboolean replace)
8874 GtkTextView *text;
8875 GtkTextBuffer *buffer;
8876 GtkTextMark *mark;
8877 GtkTextIter iter;
8878 const gchar *qmark;
8879 gchar *parsed_str = NULL;
8880 gint cursor_pos = 0;
8881 const gchar *err_msg = _("The body of the template has an error at line %d.");
8882 if (!tmpl) return;
8884 /* process the body */
8886 text = GTK_TEXT_VIEW(compose->text);
8887 buffer = gtk_text_view_get_buffer(text);
8889 if (tmpl->value) {
8890 qmark = compose_quote_char_from_context(compose);
8892 if (compose->replyinfo != NULL) {
8894 if (replace)
8895 gtk_text_buffer_set_text(buffer, "", -1);
8896 mark = gtk_text_buffer_get_insert(buffer);
8897 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8899 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8900 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8902 } else if (compose->fwdinfo != NULL) {
8904 if (replace)
8905 gtk_text_buffer_set_text(buffer, "", -1);
8906 mark = gtk_text_buffer_get_insert(buffer);
8907 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8909 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8910 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8912 } else {
8913 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8915 GtkTextIter start, end;
8916 gchar *tmp = NULL;
8918 gtk_text_buffer_get_start_iter(buffer, &start);
8919 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8920 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8922 /* clear the buffer now */
8923 if (replace)
8924 gtk_text_buffer_set_text(buffer, "", -1);
8926 parsed_str = compose_quote_fmt(compose, dummyinfo,
8927 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8928 procmsg_msginfo_free( &dummyinfo );
8930 g_free( tmp );
8932 } else {
8933 if (replace)
8934 gtk_text_buffer_set_text(buffer, "", -1);
8935 mark = gtk_text_buffer_get_insert(buffer);
8936 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8939 if (replace && parsed_str && compose->account->auto_sig)
8940 compose_insert_sig(compose, FALSE);
8942 if (replace && parsed_str) {
8943 gtk_text_buffer_get_start_iter(buffer, &iter);
8944 gtk_text_buffer_place_cursor(buffer, &iter);
8947 if (parsed_str) {
8948 cursor_pos = quote_fmt_get_cursor_pos();
8949 compose->set_cursor_pos = cursor_pos;
8950 if (cursor_pos == -1)
8951 cursor_pos = 0;
8952 gtk_text_buffer_get_start_iter(buffer, &iter);
8953 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8954 gtk_text_buffer_place_cursor(buffer, &iter);
8957 /* process the other fields */
8959 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8960 compose_template_apply_fields(compose, tmpl);
8961 quote_fmt_reset_vartable();
8962 quote_fmtlex_destroy();
8964 compose_changed_cb(NULL, compose);
8966 #ifdef USE_ENCHANT
8967 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8968 gtkaspell_highlight_all(compose->gtkaspell);
8969 #endif
8972 static void compose_template_apply_fields_error(const gchar *header)
8974 gchar *tr;
8975 gchar *text;
8977 tr = g_strdup(C_("'%s' stands for a header name",
8978 "Template '%s' format error."));
8979 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8980 alertpanel_error("%s", text);
8982 g_free(text);
8983 g_free(tr);
8986 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8988 MsgInfo* dummyinfo = NULL;
8989 MsgInfo *msginfo = NULL;
8990 gchar *buf = NULL;
8992 if (compose->replyinfo != NULL)
8993 msginfo = compose->replyinfo;
8994 else if (compose->fwdinfo != NULL)
8995 msginfo = compose->fwdinfo;
8996 else {
8997 dummyinfo = compose_msginfo_new_from_compose(compose);
8998 msginfo = dummyinfo;
9001 if (tmpl->from && *tmpl->from != '\0') {
9002 #ifdef USE_ENCHANT
9003 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9004 compose->gtkaspell);
9005 #else
9006 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9007 #endif
9008 quote_fmt_scan_string(tmpl->from);
9009 quote_fmt_parse();
9011 buf = quote_fmt_get_buffer();
9012 if (buf == NULL) {
9013 compose_template_apply_fields_error("From");
9014 } else {
9015 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
9018 quote_fmt_reset_vartable();
9019 quote_fmtlex_destroy();
9022 if (tmpl->to && *tmpl->to != '\0') {
9023 #ifdef USE_ENCHANT
9024 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9025 compose->gtkaspell);
9026 #else
9027 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9028 #endif
9029 quote_fmt_scan_string(tmpl->to);
9030 quote_fmt_parse();
9032 buf = quote_fmt_get_buffer();
9033 if (buf == NULL) {
9034 compose_template_apply_fields_error("To");
9035 } else {
9036 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
9039 quote_fmt_reset_vartable();
9040 quote_fmtlex_destroy();
9043 if (tmpl->cc && *tmpl->cc != '\0') {
9044 #ifdef USE_ENCHANT
9045 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9046 compose->gtkaspell);
9047 #else
9048 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9049 #endif
9050 quote_fmt_scan_string(tmpl->cc);
9051 quote_fmt_parse();
9053 buf = quote_fmt_get_buffer();
9054 if (buf == NULL) {
9055 compose_template_apply_fields_error("Cc");
9056 } else {
9057 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9060 quote_fmt_reset_vartable();
9061 quote_fmtlex_destroy();
9064 if (tmpl->bcc && *tmpl->bcc != '\0') {
9065 #ifdef USE_ENCHANT
9066 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9067 compose->gtkaspell);
9068 #else
9069 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9070 #endif
9071 quote_fmt_scan_string(tmpl->bcc);
9072 quote_fmt_parse();
9074 buf = quote_fmt_get_buffer();
9075 if (buf == NULL) {
9076 compose_template_apply_fields_error("Bcc");
9077 } else {
9078 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9081 quote_fmt_reset_vartable();
9082 quote_fmtlex_destroy();
9085 if (tmpl->replyto && *tmpl->replyto != '\0') {
9086 #ifdef USE_ENCHANT
9087 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9088 compose->gtkaspell);
9089 #else
9090 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9091 #endif
9092 quote_fmt_scan_string(tmpl->replyto);
9093 quote_fmt_parse();
9095 buf = quote_fmt_get_buffer();
9096 if (buf == NULL) {
9097 compose_template_apply_fields_error("Reply-To");
9098 } else {
9099 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9102 quote_fmt_reset_vartable();
9103 quote_fmtlex_destroy();
9106 /* process the subject */
9107 if (tmpl->subject && *tmpl->subject != '\0') {
9108 #ifdef USE_ENCHANT
9109 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9110 compose->gtkaspell);
9111 #else
9112 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9113 #endif
9114 quote_fmt_scan_string(tmpl->subject);
9115 quote_fmt_parse();
9117 buf = quote_fmt_get_buffer();
9118 if (buf == NULL) {
9119 compose_template_apply_fields_error("Subject");
9120 } else {
9121 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9124 quote_fmt_reset_vartable();
9125 quote_fmtlex_destroy();
9128 procmsg_msginfo_free( &dummyinfo );
9131 static void compose_destroy(Compose *compose)
9133 GtkAllocation allocation;
9134 GtkTextBuffer *buffer;
9135 GtkClipboard *clipboard;
9137 compose_list = g_list_remove(compose_list, compose);
9139 #ifdef USE_LDAP
9140 gboolean enable = TRUE;
9141 g_slist_foreach(compose->passworded_ldap_servers,
9142 _ldap_srv_func, &enable);
9143 g_slist_free(compose->passworded_ldap_servers);
9144 #endif
9146 if (compose->updating) {
9147 debug_print("danger, not destroying anything now\n");
9148 compose->deferred_destroy = TRUE;
9149 return;
9152 /* NOTE: address_completion_end() does nothing with the window
9153 * however this may change. */
9154 address_completion_end(compose->window);
9156 slist_free_strings_full(compose->to_list);
9157 slist_free_strings_full(compose->newsgroup_list);
9158 slist_free_strings_full(compose->header_list);
9160 slist_free_strings_full(extra_headers);
9161 extra_headers = NULL;
9163 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9165 g_hash_table_destroy(compose->email_hashtable);
9167 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9168 compose->folder_update_callback_id);
9170 procmsg_msginfo_free(&(compose->targetinfo));
9171 procmsg_msginfo_free(&(compose->replyinfo));
9172 procmsg_msginfo_free(&(compose->fwdinfo));
9174 g_free(compose->replyto);
9175 g_free(compose->cc);
9176 g_free(compose->bcc);
9177 g_free(compose->newsgroups);
9178 g_free(compose->followup_to);
9180 g_free(compose->ml_post);
9182 g_free(compose->inreplyto);
9183 g_free(compose->references);
9184 g_free(compose->msgid);
9185 g_free(compose->boundary);
9187 g_free(compose->redirect_filename);
9188 if (compose->undostruct)
9189 undo_destroy(compose->undostruct);
9191 g_free(compose->sig_str);
9193 g_free(compose->exteditor_file);
9195 g_free(compose->orig_charset);
9197 g_free(compose->privacy_system);
9198 g_free(compose->encdata);
9200 #ifndef USE_ALT_ADDRBOOK
9201 if (addressbook_get_target_compose() == compose)
9202 addressbook_set_target_compose(NULL);
9203 #endif
9204 #if USE_ENCHANT
9205 if (compose->gtkaspell) {
9206 gtkaspell_delete(compose->gtkaspell);
9207 compose->gtkaspell = NULL;
9209 #endif
9211 if (!compose->batch) {
9212 gtk_window_get_size(GTK_WINDOW(compose->window),
9213 &allocation.width, &allocation.height);
9214 prefs_common.compose_width = allocation.width;
9215 prefs_common.compose_height = allocation.height;
9218 if (!gtk_widget_get_parent(compose->paned))
9219 gtk_widget_destroy(compose->paned);
9220 gtk_widget_destroy(compose->popupmenu);
9222 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9223 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9224 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9226 message_search_close(compose);
9227 gtk_widget_destroy(compose->window);
9228 toolbar_destroy(compose->toolbar);
9229 g_free(compose->toolbar);
9230 g_mutex_clear(&compose->mutex);
9231 g_free(compose);
9234 static void compose_attach_info_free(AttachInfo *ainfo)
9236 g_free(ainfo->file);
9237 g_free(ainfo->content_type);
9238 g_free(ainfo->name);
9239 g_free(ainfo->charset);
9240 g_free(ainfo);
9243 static void compose_attach_update_label(Compose *compose)
9245 GtkTreeIter iter;
9246 gint i = 1;
9247 gchar *text;
9248 GtkTreeModel *model;
9249 goffset total_size;
9250 AttachInfo *ainfo;
9252 if (compose == NULL)
9253 return;
9255 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9256 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9257 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9258 return;
9261 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9262 total_size = ainfo->size;
9263 while(gtk_tree_model_iter_next(model, &iter)) {
9264 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9265 total_size += ainfo->size;
9266 i++;
9268 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9269 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9270 g_free(text);
9273 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9275 Compose *compose = (Compose *)data;
9276 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9277 GtkTreeSelection *selection;
9278 GList *sel, *cur;
9279 GtkTreeModel *model;
9281 selection = gtk_tree_view_get_selection(tree_view);
9282 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9283 cm_return_if_fail(sel);
9285 for (cur = sel; cur != NULL; cur = cur->next) {
9286 GtkTreePath *path = cur->data;
9287 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9288 (model, cur->data);
9289 cur->data = ref;
9290 gtk_tree_path_free(path);
9293 for (cur = sel; cur != NULL; cur = cur->next) {
9294 GtkTreeRowReference *ref = cur->data;
9295 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9296 GtkTreeIter iter;
9298 if (gtk_tree_model_get_iter(model, &iter, path))
9299 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9301 gtk_tree_path_free(path);
9302 gtk_tree_row_reference_free(ref);
9305 g_list_free(sel);
9306 compose_attach_update_label(compose);
9309 static struct _AttachProperty
9311 GtkWidget *window;
9312 GtkWidget *mimetype_entry;
9313 GtkWidget *encoding_optmenu;
9314 GtkWidget *path_entry;
9315 GtkWidget *filename_entry;
9316 GtkWidget *ok_btn;
9317 GtkWidget *cancel_btn;
9318 } attach_prop;
9320 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9322 gtk_tree_path_free((GtkTreePath *)ptr);
9325 static void compose_attach_property(GtkAction *action, gpointer data)
9327 Compose *compose = (Compose *)data;
9328 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9329 AttachInfo *ainfo;
9330 GtkComboBox *optmenu;
9331 GtkTreeSelection *selection;
9332 GList *sel;
9333 GtkTreeModel *model;
9334 GtkTreeIter iter;
9335 GtkTreePath *path;
9336 static gboolean cancelled;
9338 /* only if one selected */
9339 selection = gtk_tree_view_get_selection(tree_view);
9340 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9341 return;
9343 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9344 cm_return_if_fail(sel);
9346 path = (GtkTreePath *) sel->data;
9347 gtk_tree_model_get_iter(model, &iter, path);
9348 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9350 if (!ainfo) {
9351 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9352 g_list_free(sel);
9353 return;
9355 g_list_free(sel);
9357 if (!attach_prop.window)
9358 compose_attach_property_create(&cancelled);
9359 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9360 gtk_widget_grab_focus(attach_prop.ok_btn);
9361 gtk_widget_show(attach_prop.window);
9362 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9363 GTK_WINDOW(compose->window));
9365 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9366 if (ainfo->encoding == ENC_UNKNOWN)
9367 combobox_select_by_data(optmenu, ENC_BASE64);
9368 else
9369 combobox_select_by_data(optmenu, ainfo->encoding);
9371 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9372 ainfo->content_type ? ainfo->content_type : "");
9373 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9374 ainfo->file ? ainfo->file : "");
9375 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9376 ainfo->name ? ainfo->name : "");
9378 for (;;) {
9379 const gchar *entry_text;
9380 gchar *text;
9381 gchar *cnttype = NULL;
9382 gchar *file = NULL;
9383 off_t size = 0;
9385 cancelled = FALSE;
9386 gtk_main();
9388 gtk_widget_hide(attach_prop.window);
9389 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9391 if (cancelled)
9392 break;
9394 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9395 if (*entry_text != '\0') {
9396 gchar *p;
9398 text = g_strstrip(g_strdup(entry_text));
9399 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9400 cnttype = g_strdup(text);
9401 g_free(text);
9402 } else {
9403 alertpanel_error(_("Invalid MIME type."));
9404 g_free(text);
9405 continue;
9409 ainfo->encoding = combobox_get_active_data(optmenu);
9411 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9412 if (*entry_text != '\0') {
9413 if (is_file_exist(entry_text) &&
9414 (size = get_file_size(entry_text)) > 0)
9415 file = g_strdup(entry_text);
9416 else {
9417 alertpanel_error
9418 (_("File doesn't exist or is empty."));
9419 g_free(cnttype);
9420 continue;
9424 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9425 if (*entry_text != '\0') {
9426 g_free(ainfo->name);
9427 ainfo->name = g_strdup(entry_text);
9430 if (cnttype) {
9431 g_free(ainfo->content_type);
9432 ainfo->content_type = cnttype;
9434 if (file) {
9435 g_free(ainfo->file);
9436 ainfo->file = file;
9438 if (size)
9439 ainfo->size = (goffset)size;
9441 /* update tree store */
9442 text = to_human_readable(ainfo->size);
9443 gtk_tree_model_get_iter(model, &iter, path);
9444 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9445 COL_MIMETYPE, ainfo->content_type,
9446 COL_SIZE, text,
9447 COL_NAME, ainfo->name,
9448 COL_CHARSET, ainfo->charset,
9449 -1);
9451 break;
9454 gtk_tree_path_free(path);
9457 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9459 label = gtk_label_new(str); \
9460 gtk_grid_attach(GTK_GRID(table), label, 0, top, 1, 1); \
9461 gtk_label_set_xalign(GTK_LABEL(label), 0.0); \
9462 entry = gtk_entry_new(); \
9463 gtk_grid_attach(GTK_GRID(table), entry, 1, top, 1, 1); \
9466 static void compose_attach_property_create(gboolean *cancelled)
9468 GtkWidget *window;
9469 GtkWidget *vbox;
9470 GtkWidget *table;
9471 GtkWidget *label;
9472 GtkWidget *mimetype_entry;
9473 GtkWidget *hbox;
9474 GtkWidget *optmenu;
9475 GtkListStore *optmenu_menu;
9476 GtkWidget *path_entry;
9477 GtkWidget *filename_entry;
9478 GtkWidget *hbbox;
9479 GtkWidget *ok_btn;
9480 GtkWidget *cancel_btn;
9481 GList *mime_type_list, *strlist;
9482 GtkTreeIter iter;
9484 debug_print("Creating attach_property window...\n");
9486 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9487 gtk_widget_set_size_request(window, 480, -1);
9488 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9489 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9490 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9491 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
9492 g_signal_connect(G_OBJECT(window), "delete_event",
9493 G_CALLBACK(attach_property_delete_event),
9494 cancelled);
9495 g_signal_connect(G_OBJECT(window), "key_press_event",
9496 G_CALLBACK(attach_property_key_pressed),
9497 cancelled);
9499 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
9500 gtk_container_add(GTK_CONTAINER(window), vbox);
9502 table = gtk_grid_new();
9503 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9504 gtk_grid_set_row_spacing(GTK_GRID(table), 8);
9505 gtk_grid_set_column_spacing(GTK_GRID(table), 8);
9507 label = gtk_label_new(_("MIME type"));
9508 gtk_grid_attach(GTK_GRID(table), label, 0, 0, 1, 1);
9509 gtk_label_set_xalign(GTK_LABEL(label), 0.0);
9510 mimetype_entry = gtk_combo_box_text_new_with_entry();
9511 gtk_grid_attach(GTK_GRID(table), mimetype_entry, 1, 0, 1, 1);
9512 gtk_widget_set_hexpand(mimetype_entry, TRUE);
9513 gtk_widget_set_halign(mimetype_entry, GTK_ALIGN_FILL);
9515 /* stuff with list */
9516 mime_type_list = procmime_get_mime_type_list();
9517 strlist = NULL;
9518 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9519 MimeType *type = (MimeType *) mime_type_list->data;
9520 gchar *tmp;
9522 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9524 if (g_list_find_custom(strlist, tmp, (GCompareFunc)g_strcmp0))
9525 g_free(tmp);
9526 else
9527 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9528 (GCompareFunc)g_strcmp0);
9531 for (mime_type_list = strlist; mime_type_list != NULL;
9532 mime_type_list = mime_type_list->next) {
9533 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9534 g_free(mime_type_list->data);
9536 g_list_free(strlist);
9537 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9538 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9540 label = gtk_label_new(_("Encoding"));
9541 gtk_grid_attach(GTK_GRID(table), label, 0, 1, 1, 1);
9542 gtk_label_set_xalign(GTK_LABEL(label), 0.0);
9544 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
9545 gtk_grid_attach(GTK_GRID(table), hbox, 1, 1, 1, 1);
9546 gtk_widget_set_hexpand(hbox, TRUE);
9547 gtk_widget_set_halign(hbox, GTK_ALIGN_FILL);
9549 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9550 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9552 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9553 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9554 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9555 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9556 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9558 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9560 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9561 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9563 gtkut_stock_button_set_create(&hbbox, &cancel_btn, NULL, _("_Cancel"),
9564 &ok_btn, NULL, _("_OK"),
9565 NULL, NULL, NULL);
9566 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9567 gtk_widget_grab_default(ok_btn);
9569 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9570 G_CALLBACK(attach_property_ok),
9571 cancelled);
9572 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9573 G_CALLBACK(attach_property_cancel),
9574 cancelled);
9576 gtk_widget_show_all(vbox);
9578 attach_prop.window = window;
9579 attach_prop.mimetype_entry = mimetype_entry;
9580 attach_prop.encoding_optmenu = optmenu;
9581 attach_prop.path_entry = path_entry;
9582 attach_prop.filename_entry = filename_entry;
9583 attach_prop.ok_btn = ok_btn;
9584 attach_prop.cancel_btn = cancel_btn;
9587 #undef SET_LABEL_AND_ENTRY
9589 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9591 *cancelled = FALSE;
9592 gtk_main_quit();
9595 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9597 *cancelled = TRUE;
9598 gtk_main_quit();
9601 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9602 gboolean *cancelled)
9604 *cancelled = TRUE;
9605 gtk_main_quit();
9607 return TRUE;
9610 static gboolean attach_property_key_pressed(GtkWidget *widget,
9611 GdkEventKey *event,
9612 gboolean *cancelled)
9614 if (event && event->keyval == GDK_KEY_Escape) {
9615 *cancelled = TRUE;
9616 gtk_main_quit();
9618 if (event && (event->keyval == GDK_KEY_KP_Enter ||
9619 event->keyval == GDK_KEY_Return)) {
9620 *cancelled = FALSE;
9621 gtk_main_quit();
9622 return TRUE;
9624 return FALSE;
9627 static gboolean compose_can_autosave(Compose *compose)
9629 if (compose->privacy_system && compose->use_encryption)
9630 return prefs_common.autosave && prefs_common.autosave_encrypted;
9631 else
9632 return prefs_common.autosave;
9636 * compose_exec_ext_editor:
9638 * Open (and optionally embed) external editor
9640 static void compose_exec_ext_editor(Compose *compose)
9642 gchar *tmp;
9643 #ifdef GDK_WINDOWING_X11
9644 GtkWidget *socket;
9645 Window socket_wid = 0;
9646 gchar *p, *s;
9647 #endif /* GDK_WINDOWING_X11 */
9648 GPid pid;
9649 GError *error = NULL;
9650 gchar *cmd = NULL;
9651 gchar **argv;
9653 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9654 G_DIR_SEPARATOR, compose);
9656 if (compose_write_body_to_file(compose, tmp) < 0) {
9657 alertpanel_error(_("Could not write the body to file:\n%s"),
9658 tmp);
9659 g_free(tmp);
9660 return;
9663 #ifdef GDK_WINDOWING_X11
9664 if (compose_get_ext_editor_uses_socket()) {
9665 if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
9666 /* Only allow one socket */
9667 if (compose->exteditor_socket != NULL) {
9668 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9669 /* Move the focus off of the socket */
9670 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9672 g_free(tmp);
9673 return;
9675 /* Create the receiving GtkSocket */
9676 socket = gtk_socket_new ();
9677 g_signal_connect (G_OBJECT(socket), "plug-removed",
9678 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9679 compose);
9680 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9681 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9682 /* Realize the socket so that we can use its ID */
9683 gtk_widget_realize(socket);
9684 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9685 compose->exteditor_socket = socket;
9686 } else
9687 debug_print("Socket communication with an external editor is only available on X11.\n");
9689 #else
9690 if (compose_get_ext_editor_uses_socket()) {
9691 alertpanel_error(_("Socket communication with an external editor is only available on X11."));
9692 g_free(tmp);
9693 return;
9695 #endif /* GDK_WINDOWING_X11 */
9697 if (compose_get_ext_editor_cmd_valid()) {
9698 #ifdef GDK_WINDOWING_X11
9699 if (compose_get_ext_editor_uses_socket() && GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
9700 p = g_strdup(prefs_common_get_ext_editor_cmd());
9701 s = strstr(p, "%w");
9702 s[1] = 'u';
9703 if (strstr(p, "%s") < s)
9704 cmd = g_strdup_printf(p, tmp, socket_wid);
9705 else
9706 cmd = g_strdup_printf(p, socket_wid, tmp);
9707 g_free(p);
9708 } else {
9709 cmd = g_strdup_printf(prefs_common_get_ext_editor_cmd(), tmp);
9711 #else
9712 cmd = g_strdup_printf(prefs_common_get_ext_editor_cmd(), tmp);
9713 #endif /* GDK_WINDOWING_X11 */
9714 } else {
9715 if (prefs_common_get_ext_editor_cmd())
9716 g_warning("external editor command-line is invalid: '%s'",
9717 prefs_common_get_ext_editor_cmd());
9718 cmd = g_strdup_printf(DEFAULT_EDITOR_CMD, tmp);
9721 argv = strsplit_with_quote(cmd, " ", 0);
9723 if (!g_spawn_async(NULL, argv, NULL,
9724 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
9725 NULL, NULL, &pid, &error)) {
9726 alertpanel_error(_("Could not spawn the following "
9727 "external editor command:\n%s\n%s"),
9728 cmd, error ? error->message : _("Unknown error"));
9729 if (error)
9730 g_error_free(error);
9731 g_free(tmp);
9732 g_free(cmd);
9733 g_strfreev(argv);
9734 return;
9736 g_free(cmd);
9737 g_strfreev(argv);
9739 compose->exteditor_file = g_strdup(tmp);
9740 compose->exteditor_pid = pid;
9741 compose->exteditor_tag = g_child_watch_add(pid,
9742 compose_ext_editor_closed_cb,
9743 compose);
9745 compose_set_ext_editor_sensitive(compose, FALSE);
9747 g_free(tmp);
9751 * compose_ext_editor_cb:
9753 * External editor has closed (called by g_child_watch)
9755 static void compose_ext_editor_closed_cb(GPid pid, gint exit_status, gpointer data)
9757 Compose *compose = (Compose *)data;
9758 GError *error = NULL;
9759 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9760 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9761 GtkTextIter start, end;
9762 gchar *chars;
9764 #if GLIB_CHECK_VERSION(2,70,0)
9765 if (!g_spawn_check_wait_status(exit_status, &error)) {
9766 #else
9767 if (!g_spawn_check_exit_status(exit_status, &error)) {
9768 #endif
9769 alertpanel_error(
9770 _("External editor stopped with an error: %s"),
9771 error ? error->message : _("Unknown error"));
9772 if (error)
9773 g_error_free(error);
9775 g_spawn_close_pid(compose->exteditor_pid);
9777 gtk_text_buffer_set_text(buffer, "", -1);
9778 compose_insert_file(compose, compose->exteditor_file);
9779 compose_changed_cb(NULL, compose);
9781 /* Check if we should save the draft or not */
9782 if (compose_can_autosave(compose))
9783 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9785 if (claws_unlink(compose->exteditor_file) < 0)
9786 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9788 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9789 gtk_text_buffer_get_start_iter(buffer, &start);
9790 gtk_text_buffer_get_end_iter(buffer, &end);
9791 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9792 if (chars && strlen(chars) > 0)
9793 compose->modified = TRUE;
9794 g_free(chars);
9796 compose_set_ext_editor_sensitive(compose, TRUE);
9798 g_free(compose->exteditor_file);
9799 compose->exteditor_file = NULL;
9800 compose->exteditor_pid = INVALID_PID;
9801 compose->exteditor_tag = -1;
9802 #ifdef GDK_WINDOWING_X11
9803 if (compose->exteditor_socket && GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
9804 gtk_widget_destroy(compose->exteditor_socket);
9805 compose->exteditor_socket = NULL;
9807 #endif /* GDK_WINDOWING_X11 */
9811 static gboolean compose_get_ext_editor_cmd_valid()
9813 gboolean has_s = FALSE;
9814 gboolean has_w = FALSE;
9815 const gchar *p = prefs_common_get_ext_editor_cmd();
9816 if (!p)
9817 return FALSE;
9818 while ((p = strchr(p, '%'))) {
9819 p++;
9820 if (*p == 's') {
9821 if (has_s)
9822 return FALSE;
9823 has_s = TRUE;
9824 } else if (*p == 'w') {
9825 if (has_w)
9826 return FALSE;
9827 has_w = TRUE;
9828 } else {
9829 return FALSE;
9832 return TRUE;
9835 static gboolean compose_ext_editor_kill(Compose *compose)
9837 GPid pid = compose->exteditor_pid;
9838 gchar *pidmsg = NULL;
9840 if (pid > 0) {
9841 AlertValue val;
9842 gchar *msg;
9844 pidmsg = g_strdup_printf(_("process id: %" G_PID_FORMAT), pid);
9846 msg = g_strdup_printf
9847 (_("The external editor is still working.\n"
9848 "Force terminating the process?\n"
9849 "%s"), pidmsg);
9850 val = alertpanel_full(_("Notice"), msg, NULL, _("_No"), NULL, _("_Yes"),
9851 NULL, NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9852 ALERT_WARNING);
9853 g_free(msg);
9855 if (val == G_ALERTALTERNATE) {
9856 g_source_remove(compose->exteditor_tag);
9858 #ifdef G_OS_WIN32
9859 if (!TerminateProcess(compose->exteditor_pid, 0))
9860 perror("TerminateProcess");
9861 #else
9862 if (kill(pid, SIGTERM) < 0) perror("kill");
9863 waitpid(compose->exteditor_pid, NULL, 0);
9864 #endif /* G_OS_WIN32 */
9866 g_warning("terminated %s, temporary file: %s",
9867 pidmsg, compose->exteditor_file);
9868 g_spawn_close_pid(compose->exteditor_pid);
9870 compose_set_ext_editor_sensitive(compose, TRUE);
9872 g_free(compose->exteditor_file);
9873 compose->exteditor_file = NULL;
9874 compose->exteditor_pid = INVALID_PID;
9875 compose->exteditor_tag = -1;
9876 } else {
9877 g_free(pidmsg);
9878 return FALSE;
9882 if (pidmsg)
9883 g_free(pidmsg);
9884 return TRUE;
9887 static char *ext_editor_menu_entries[] = {
9888 "Menu/Message/Send",
9889 "Menu/Message/SendLater",
9890 "Menu/Message/InsertFile",
9891 "Menu/Message/InsertSig",
9892 "Menu/Message/ReplaceSig",
9893 "Menu/Message/Save",
9894 "Menu/Message/Print",
9895 "Menu/Edit",
9896 #if USE_ENCHANT
9897 "Menu/Spelling",
9898 #endif
9899 "Menu/Tools/ShowRuler",
9900 "Menu/Tools/Actions",
9901 "Menu/Help",
9902 NULL
9905 static void compose_set_ext_editor_sensitive(Compose *compose,
9906 gboolean sensitive)
9908 int i;
9910 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9911 cm_menu_set_sensitive_full(compose->ui_manager,
9912 ext_editor_menu_entries[i], sensitive);
9915 #ifdef GDK_WINDOWING_X11
9916 if (compose_get_ext_editor_uses_socket() && GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
9917 if (sensitive) {
9918 if (compose->exteditor_socket)
9919 gtk_widget_hide(compose->exteditor_socket);
9920 gtk_widget_show(compose->scrolledwin);
9921 if (prefs_common.show_ruler)
9922 gtk_widget_show(compose->ruler_hbox);
9923 /* Fix the focus, as it doesn't go anywhere when the
9924 * socket is hidden or destroyed */
9925 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9926 } else {
9927 g_assert (compose->exteditor_socket != NULL);
9928 /* Fix the focus, as it doesn't go anywhere when the
9929 * edit box is hidden */
9930 if (gtk_widget_is_focus(compose->text))
9931 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9932 gtk_widget_hide(compose->scrolledwin);
9933 gtk_widget_hide(compose->ruler_hbox);
9934 gtk_widget_show(compose->exteditor_socket);
9936 } else {
9937 gtk_widget_set_sensitive(compose->text, sensitive);
9939 #else
9940 gtk_widget_set_sensitive(compose->text, sensitive);
9941 #endif /* GDK_WINDOWING_X11 */
9942 if (compose->toolbar->send_btn)
9943 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9944 if (compose->toolbar->sendl_btn)
9945 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9946 if (compose->toolbar->draft_btn)
9947 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9948 if (compose->toolbar->insert_btn)
9949 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9950 if (compose->toolbar->sig_btn)
9951 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9952 if (compose->toolbar->exteditor_btn)
9953 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9954 if (compose->toolbar->linewrap_current_btn)
9955 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9956 if (compose->toolbar->linewrap_all_btn)
9957 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9960 static gboolean compose_get_ext_editor_uses_socket()
9962 return (prefs_common_get_ext_editor_cmd() &&
9963 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9966 #ifdef GDK_WINDOWING_X11
9967 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9969 compose->exteditor_socket = NULL;
9970 /* returning FALSE allows destruction of the socket */
9971 return FALSE;
9973 #endif /* GDK_WINDOWING_X11 */
9976 * compose_undo_state_changed:
9978 * Change the sensivity of the menuentries undo and redo
9980 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9981 gint redo_state, gpointer data)
9983 Compose *compose = (Compose *)data;
9985 switch (undo_state) {
9986 case UNDO_STATE_TRUE:
9987 if (!undostruct->undo_state) {
9988 undostruct->undo_state = TRUE;
9989 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9991 break;
9992 case UNDO_STATE_FALSE:
9993 if (undostruct->undo_state) {
9994 undostruct->undo_state = FALSE;
9995 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9997 break;
9998 case UNDO_STATE_UNCHANGED:
9999 break;
10000 case UNDO_STATE_REFRESH:
10001 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
10002 break;
10003 default:
10004 g_warning("undo state not recognized");
10005 break;
10008 switch (redo_state) {
10009 case UNDO_STATE_TRUE:
10010 if (!undostruct->redo_state) {
10011 undostruct->redo_state = TRUE;
10012 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
10014 break;
10015 case UNDO_STATE_FALSE:
10016 if (undostruct->redo_state) {
10017 undostruct->redo_state = FALSE;
10018 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
10020 break;
10021 case UNDO_STATE_UNCHANGED:
10022 break;
10023 case UNDO_STATE_REFRESH:
10024 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
10025 break;
10026 default:
10027 g_warning("redo state not recognized");
10028 break;
10032 /* callback functions */
10034 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10035 GtkAllocation *allocation,
10036 GtkPaned *paned)
10038 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10041 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10042 * includes "non-client" (windows-izm) in calculation, so this calculation
10043 * may not be accurate.
10045 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10046 GtkAllocation *allocation,
10047 GtkSHRuler *shruler)
10049 if (prefs_common.show_ruler) {
10050 gint char_width = 0, char_height = 0;
10051 gint line_width_in_chars;
10053 gtkut_get_font_size(GTK_WIDGET(widget),
10054 &char_width, &char_height);
10055 line_width_in_chars =
10056 (allocation->width - allocation->x) / char_width;
10058 /* got the maximum */
10059 gtk_shruler_set_range(GTK_SHRULER(shruler),
10060 0.0, line_width_in_chars, 0);
10063 return TRUE;
10066 typedef struct {
10067 gchar *header;
10068 gchar *entry;
10069 ComposePrefType type;
10070 gboolean entry_marked;
10071 } HeaderEntryState;
10073 static void account_activated(GtkComboBox *optmenu, gpointer data)
10075 Compose *compose = (Compose *)data;
10077 PrefsAccount *ac;
10078 gchar *folderidentifier;
10079 gint account_id = 0;
10080 GtkTreeModel *menu;
10081 GtkTreeIter iter;
10082 GSList *list, *saved_list = NULL;
10083 HeaderEntryState *state;
10085 /* Get ID of active account in the combo box */
10086 menu = gtk_combo_box_get_model(optmenu);
10087 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10088 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10090 ac = account_find_from_id(account_id);
10091 cm_return_if_fail(ac != NULL);
10093 if (ac != compose->account) {
10094 compose_select_account(compose, ac, FALSE);
10096 for (list = compose->header_list; list; list = list->next) {
10097 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10099 if (hentry->type == PREF_ACCOUNT || !list->next) {
10100 compose_destroy_headerentry(compose, hentry);
10101 continue;
10103 state = g_malloc0(sizeof(HeaderEntryState));
10104 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10105 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10106 state->entry = gtk_editable_get_chars(
10107 GTK_EDITABLE(hentry->entry), 0, -1);
10108 state->type = hentry->type;
10110 saved_list = g_slist_append(saved_list, state);
10111 compose_destroy_headerentry(compose, hentry);
10114 compose->header_last = NULL;
10115 g_slist_free(compose->header_list);
10116 compose->header_list = NULL;
10117 compose->header_nextrow = 1;
10118 compose_create_header_entry(compose);
10120 if (ac->set_autocc && ac->auto_cc)
10121 compose_entry_append(compose, ac->auto_cc,
10122 COMPOSE_CC, PREF_ACCOUNT);
10123 if (ac->set_autobcc && ac->auto_bcc)
10124 compose_entry_append(compose, ac->auto_bcc,
10125 COMPOSE_BCC, PREF_ACCOUNT);
10126 if (ac->set_autoreplyto && ac->auto_replyto)
10127 compose_entry_append(compose, ac->auto_replyto,
10128 COMPOSE_REPLYTO, PREF_ACCOUNT);
10130 for (list = saved_list; list; list = list->next) {
10131 state = (HeaderEntryState *) list->data;
10133 compose_add_header_entry(compose, state->header,
10134 state->entry, state->type);
10136 g_free(state->header);
10137 g_free(state->entry);
10138 g_free(state);
10140 g_slist_free(saved_list);
10142 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10143 (ac->protocol == A_NNTP) ?
10144 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10147 /* Set message save folder */
10148 compose_set_save_to(compose, NULL);
10149 if (compose->folder && compose->folder->prefs && compose->folder->prefs->save_copy_to_folder) {
10150 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10151 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10152 folderidentifier = folder_item_get_identifier(compose->folder);
10153 compose_set_save_to(compose, folderidentifier);
10154 g_free(folderidentifier);
10155 } else if (account_get_special_folder(compose->account, F_OUTBOX)) {
10156 if (compose->account->set_sent_folder || prefs_common.savemsg)
10157 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10158 else
10159 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE);
10160 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10161 folderidentifier = folder_item_get_identifier(account_get_special_folder
10162 (compose->account, F_OUTBOX));
10163 compose_set_save_to(compose, folderidentifier);
10164 g_free(folderidentifier);
10168 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10169 GtkTreeViewColumn *column, Compose *compose)
10171 compose_attach_property(NULL, compose);
10174 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10175 gpointer data)
10177 Compose *compose = (Compose *)data;
10178 GtkTreeSelection *attach_selection;
10179 gint attach_nr_selected;
10180 GtkTreePath *path;
10182 if (!event || compose->redirect_filename != NULL)
10183 return FALSE;
10185 if (event->button == 3) {
10186 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10187 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10189 /* If no rows, or just one row is selected, right-click should
10190 * open menu relevant to the row being right-clicked on. We
10191 * achieve that by selecting the clicked row first. If more
10192 * than one row is selected, we shouldn't modify the selection,
10193 * as user may want to remove selected rows (attachments). */
10194 if (attach_nr_selected < 2) {
10195 gtk_tree_selection_unselect_all(attach_selection);
10196 attach_nr_selected = 0;
10197 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10198 event->x, event->y, &path, NULL, NULL, NULL);
10199 if (path != NULL) {
10200 gtk_tree_selection_select_path(attach_selection, path);
10201 gtk_tree_path_free(path);
10202 attach_nr_selected++;
10206 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10207 /* Properties menu item makes no sense with more than one row
10208 * selected, the properties dialog can only edit one attachment. */
10209 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10211 gtk_menu_popup_at_pointer(GTK_MENU(compose->popupmenu), NULL);
10213 return TRUE;
10216 return FALSE;
10219 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10220 gpointer data)
10222 Compose *compose = (Compose *)data;
10224 if (!event) return FALSE;
10226 switch (event->keyval) {
10227 case GDK_KEY_Delete:
10228 compose_attach_remove_selected(NULL, compose);
10229 break;
10231 return FALSE;
10234 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10236 toolbar_comp_set_sensitive(compose, allow);
10237 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10238 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10239 #if USE_ENCHANT
10240 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10241 #endif
10242 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10243 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10244 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10246 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10250 static void compose_send_cb(GtkAction *action, gpointer data)
10252 Compose *compose = (Compose *)data;
10254 #ifdef G_OS_UNIX
10255 if (compose->exteditor_tag != -1) {
10256 debug_print("ignoring send: external editor still open\n");
10257 return;
10259 #endif
10260 if (prefs_common.work_offline &&
10261 !inc_offline_should_override(TRUE,
10262 _("Claws Mail needs network access in order "
10263 "to send this email.")))
10264 return;
10266 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10267 g_source_remove(compose->draft_timeout_tag);
10268 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10271 compose_send(compose);
10274 static void compose_send_later_cb(GtkAction *action, gpointer data)
10276 Compose *compose = (Compose *)data;
10277 ComposeQueueResult val;
10279 inc_lock();
10280 compose_allow_user_actions(compose, FALSE);
10281 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10282 compose_allow_user_actions(compose, TRUE);
10283 inc_unlock();
10285 if (val == COMPOSE_QUEUE_SUCCESS) {
10286 compose_close(compose);
10287 } else {
10288 _display_queue_error(val);
10291 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10294 #define DRAFTED_AT_EXIT "drafted_at_exit"
10295 static void compose_register_draft(MsgInfo *info)
10297 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10298 DRAFTED_AT_EXIT, NULL);
10299 FILE *fp = claws_fopen(filepath, "ab");
10301 if (fp) {
10302 gchar *name = folder_item_get_identifier(info->folder);
10303 fprintf(fp, "%s\t%d\n", name, info->msgnum);
10304 g_free(name);
10305 claws_fclose(fp);
10308 g_free(filepath);
10311 gboolean compose_draft (gpointer data, guint action)
10313 Compose *compose = (Compose *)data;
10314 FolderItem *draft;
10315 FolderItemPrefs *prefs;
10316 gchar *tmp;
10317 gchar *sheaders;
10318 gint msgnum;
10319 MsgFlags flag = {0, 0};
10320 static gboolean lock = FALSE;
10321 MsgInfo *newmsginfo;
10322 FILE *fp;
10323 gboolean target_locked = FALSE;
10324 gboolean err = FALSE;
10325 gint filemode = 0;
10327 if (lock) return FALSE;
10329 if (compose->sending)
10330 return TRUE;
10332 draft = account_get_special_folder(compose->account, F_DRAFT);
10333 cm_return_val_if_fail(draft != NULL, FALSE);
10335 if (!g_mutex_trylock(&compose->mutex)) {
10336 /* we don't want to lock the mutex once it's available,
10337 * because as the only other part of compose.c locking
10338 * it is compose_close - which means once unlocked,
10339 * the compose struct will be freed */
10340 debug_print("couldn't lock mutex, probably sending\n");
10341 return FALSE;
10344 lock = TRUE;
10346 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10347 G_DIR_SEPARATOR, compose);
10348 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10349 FILE_OP_ERROR(tmp, "claws_fopen");
10350 goto warn_err;
10353 /* chmod for security unless folder chmod is set */
10354 prefs = draft->prefs;
10355 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) {
10356 filemode = prefs->folder_chmod;
10357 if (filemode & S_IRGRP) filemode |= S_IWGRP;
10358 if (filemode & S_IROTH) filemode |= S_IWOTH;
10359 if (chmod(tmp, filemode) < 0)
10360 FILE_OP_ERROR(tmp, "chmod");
10361 } else if (change_file_mode_rw(fp, tmp) < 0) {
10362 FILE_OP_ERROR(tmp, "chmod");
10363 g_warning("can't change file mode");
10366 /* Save draft infos */
10367 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10368 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10370 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10371 gchar *savefolderid;
10373 savefolderid = compose_get_save_to(compose);
10374 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10375 g_free(savefolderid);
10377 if (compose->return_receipt) {
10378 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10380 if (compose->privacy_system) {
10381 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10382 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10383 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10386 /* Message-ID of message replying to */
10387 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10388 gchar *folderid = NULL;
10390 if (compose->replyinfo->folder)
10391 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10392 if (folderid == NULL)
10393 folderid = g_strdup("NULL");
10395 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10396 g_free(folderid);
10398 /* Message-ID of message forwarding to */
10399 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10400 gchar *folderid = NULL;
10402 if (compose->fwdinfo->folder)
10403 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10404 if (folderid == NULL)
10405 folderid = g_strdup("NULL");
10407 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10408 g_free(folderid);
10411 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10412 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10414 sheaders = compose_get_manual_headers_info(compose);
10415 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10416 g_free(sheaders);
10418 /* end of headers */
10419 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10421 if (err) {
10422 claws_fclose(fp);
10423 goto warn_err;
10426 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10427 claws_fclose(fp);
10428 goto warn_err;
10430 if (claws_safe_fclose(fp) == EOF) {
10431 goto warn_err;
10434 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10435 if (compose->targetinfo) {
10436 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10437 if (target_locked)
10438 flag.perm_flags |= MSG_LOCKED;
10440 flag.tmp_flags = MSG_DRAFT;
10442 folder_item_scan(draft);
10443 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10444 MsgInfo *tmpinfo = NULL;
10445 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10446 if (compose->msgid) {
10447 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10449 if (tmpinfo) {
10450 msgnum = tmpinfo->msgnum;
10451 procmsg_msginfo_free(&tmpinfo);
10452 debug_print("got draft msgnum %d from scanning\n", msgnum);
10453 } else {
10454 debug_print("didn't get draft msgnum after scanning\n");
10456 } else {
10457 debug_print("got draft msgnum %d from adding\n", msgnum);
10459 if (msgnum < 0) {
10460 warn_err:
10461 claws_unlink(tmp);
10462 g_free(tmp);
10463 if (action != COMPOSE_AUTO_SAVE) {
10464 if (action != COMPOSE_DRAFT_FOR_EXIT)
10465 alertpanel_error(_("Could not save draft."));
10466 else {
10467 AlertValue val;
10468 gtkut_window_popup(compose->window);
10469 val = alertpanel_full(_("Could not save draft"),
10470 _("Could not save draft.\n"
10471 "Do you want to cancel exit or discard this email?"),
10472 NULL, _("_Cancel exit"), NULL, _("_Discard email"),
10473 NULL, NULL, ALERTFOCUS_FIRST, FALSE, NULL, ALERT_QUESTION);
10474 if (val == G_ALERTALTERNATE) {
10475 lock = FALSE;
10476 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10477 compose_close(compose);
10478 return TRUE;
10479 } else {
10480 lock = FALSE;
10481 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10482 return FALSE;
10486 goto unlock;
10488 g_free(tmp);
10490 if (compose->mode == COMPOSE_REEDIT) {
10491 compose_remove_reedit_target(compose, TRUE);
10494 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10496 if (newmsginfo) {
10497 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10498 if (target_locked)
10499 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10500 else
10501 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10502 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10503 procmsg_msginfo_set_flags(newmsginfo, 0,
10504 MSG_HAS_ATTACHMENT);
10506 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10507 compose_register_draft(newmsginfo);
10509 procmsg_msginfo_free(&newmsginfo);
10512 folder_item_scan(draft);
10514 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10515 lock = FALSE;
10516 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10517 compose_close(compose);
10518 return TRUE;
10519 } else {
10520 #ifdef G_OS_WIN32
10521 GFile *f;
10522 GFileInfo *fi;
10523 GTimeVal tv;
10524 GError *error = NULL;
10525 #else
10526 GStatBuf s;
10527 #endif
10528 gchar *path;
10529 goffset size, mtime;
10531 path = folder_item_fetch_msg(draft, msgnum);
10532 if (path == NULL) {
10533 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10534 goto unlock;
10536 #ifdef G_OS_WIN32
10537 f = g_file_new_for_path(path);
10538 fi = g_file_query_info(f, "standard::size,time::modified",
10539 G_FILE_QUERY_INFO_NONE, NULL, &error);
10540 if (error != NULL) {
10541 debug_print("couldn't query file info for '%s': %s\n",
10542 path, error->message);
10543 g_error_free(error);
10544 g_free(path);
10545 g_object_unref(f);
10546 goto unlock;
10548 size = g_file_info_get_size(fi);
10549 g_file_info_get_modification_time(fi, &tv);
10550 mtime = tv.tv_sec;
10551 g_object_unref(fi);
10552 g_object_unref(f);
10553 #else
10554 if (g_stat(path, &s) < 0) {
10555 FILE_OP_ERROR(path, "stat");
10556 g_free(path);
10557 goto unlock;
10559 size = s.st_size;
10560 mtime = s.st_mtime;
10561 #endif
10562 g_free(path);
10564 procmsg_msginfo_free(&(compose->targetinfo));
10565 compose->targetinfo = procmsg_msginfo_new();
10566 compose->targetinfo->msgnum = msgnum;
10567 compose->targetinfo->size = size;
10568 compose->targetinfo->mtime = mtime;
10569 compose->targetinfo->folder = draft;
10570 if (target_locked)
10571 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10572 compose->mode = COMPOSE_REEDIT;
10574 if (action == COMPOSE_AUTO_SAVE) {
10575 compose->modified = FALSE;
10576 compose->autosaved_draft = compose->targetinfo;
10578 compose_set_title(compose);
10580 unlock:
10581 lock = FALSE;
10582 g_mutex_unlock(&compose->mutex);
10583 return TRUE;
10586 void compose_clear_exit_drafts(void)
10588 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10589 DRAFTED_AT_EXIT, NULL);
10590 if (is_file_exist(filepath))
10591 claws_unlink(filepath);
10593 g_free(filepath);
10596 void compose_reopen_exit_drafts(void)
10598 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10599 DRAFTED_AT_EXIT, NULL);
10600 FILE *fp = claws_fopen(filepath, "rb");
10601 gchar buf[1024];
10603 if (fp) {
10604 while (claws_fgets(buf, sizeof(buf), fp)) {
10605 gchar **parts = g_strsplit(buf, "\t", 2);
10606 const gchar *folder = parts[0];
10607 int msgnum = parts[1] ? atoi(parts[1]):-1;
10609 if (folder && *folder && msgnum > -1) {
10610 FolderItem *item = folder_find_item_from_identifier(folder);
10611 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10612 if (info)
10613 compose_reedit(info, FALSE);
10615 g_strfreev(parts);
10617 claws_fclose(fp);
10619 g_free(filepath);
10620 compose_clear_exit_drafts();
10623 static void compose_save_cb(GtkAction *action, gpointer data)
10625 Compose *compose = (Compose *)data;
10626 compose_draft(compose, COMPOSE_KEEP_EDITING);
10627 compose->rmode = COMPOSE_REEDIT;
10628 compose->modified = FALSE;
10629 compose_set_title(compose);
10632 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10634 if (compose && file_list) {
10635 GList *tmp;
10637 for ( tmp = file_list; tmp; tmp = tmp->next) {
10638 gchar *file = (gchar *) tmp->data;
10639 gchar *utf8_filename = conv_filename_to_utf8(file);
10640 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10641 compose_changed_cb(NULL, compose);
10642 if (free_data) {
10643 g_free(file);
10644 tmp->data = NULL;
10646 g_free(utf8_filename);
10651 static void compose_attach_cb(GtkAction *action, gpointer data)
10653 Compose *compose = (Compose *)data;
10654 GList *file_list;
10656 if (compose->redirect_filename != NULL)
10657 return;
10659 /* Set focus_window properly, in case we were called via popup menu,
10660 * which unsets it (via focus_out_event callback on compose window). */
10661 manage_window_focus_in(compose->window, NULL, NULL);
10663 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10665 if (file_list) {
10666 compose_attach_from_list(compose, file_list, TRUE);
10667 g_list_free(file_list);
10671 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10673 Compose *compose = (Compose *)data;
10674 GList *file_list;
10675 gint files_inserted = 0;
10677 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10679 if (file_list) {
10680 GList *tmp;
10682 for ( tmp = file_list; tmp; tmp = tmp->next) {
10683 gchar *file = (gchar *) tmp->data;
10684 gchar *filedup = g_strdup(file);
10685 gchar *shortfile = g_path_get_basename(filedup);
10686 ComposeInsertResult res;
10687 /* insert the file if the file is short or if the user confirmed that
10688 he/she wants to insert the large file */
10689 res = compose_insert_file(compose, file);
10690 if (res == COMPOSE_INSERT_READ_ERROR) {
10691 alertpanel_error(_("File '%s' could not be read."), shortfile);
10692 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10693 alertpanel_error(_("File '%s' contained invalid characters\n"
10694 "for the current encoding, insertion may be incorrect."),
10695 shortfile);
10696 } else if (res == COMPOSE_INSERT_SUCCESS)
10697 files_inserted++;
10699 g_free(shortfile);
10700 g_free(filedup);
10701 g_free(file);
10703 g_list_free(file_list);
10706 #ifdef USE_ENCHANT
10707 if (files_inserted > 0 && compose->gtkaspell &&
10708 compose->gtkaspell->check_while_typing)
10709 gtkaspell_highlight_all(compose->gtkaspell);
10710 #endif
10713 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10715 Compose *compose = (Compose *)data;
10717 compose_insert_sig(compose, FALSE);
10720 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10722 Compose *compose = (Compose *)data;
10724 compose_insert_sig(compose, TRUE);
10727 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10728 gpointer data)
10730 gint x, y;
10731 Compose *compose = (Compose *)data;
10733 gtk_window_get_position(GTK_WINDOW(widget), &x, &y);
10734 if (!compose->batch) {
10735 prefs_common.compose_x = x;
10736 prefs_common.compose_y = y;
10738 if (compose->sending || compose->updating)
10739 return TRUE;
10740 compose_close_cb(NULL, compose);
10741 return TRUE;
10744 void compose_close_toolbar(Compose *compose)
10746 compose_close_cb(NULL, compose);
10749 static void compose_close_cb(GtkAction *action, gpointer data)
10751 Compose *compose = (Compose *)data;
10752 AlertValue val;
10754 if (compose->exteditor_tag != -1) {
10755 if (!compose_ext_editor_kill(compose))
10756 return;
10759 if (compose->modified) {
10760 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10761 if (!g_mutex_trylock(&compose->mutex)) {
10762 /* we don't want to lock the mutex once it's available,
10763 * because as the only other part of compose.c locking
10764 * it is compose_close - which means once unlocked,
10765 * the compose struct will be freed */
10766 debug_print("couldn't lock mutex, probably sending\n");
10767 return;
10769 if (!reedit || (compose->folder != NULL && compose->folder->stype == F_DRAFT)) {
10770 val = alertpanel(_("Discard message"),
10771 _("This message has been modified. Discard it?"),
10772 NULL, _("_Discard"), NULL, _("_Save to Drafts"), NULL, _("_Cancel"),
10773 ALERTFOCUS_FIRST);
10774 } else {
10775 val = alertpanel(_("Save changes"),
10776 _("This message has been modified. Save the latest changes?"),
10777 NULL, _("_Don't save"), NULL, _("_Save to Drafts"), NULL, _("_Cancel"),
10778 ALERTFOCUS_SECOND);
10780 g_mutex_unlock(&compose->mutex);
10781 switch (val) {
10782 case G_ALERTDEFAULT:
10783 if (compose_can_autosave(compose) && !reedit)
10784 compose_remove_draft(compose);
10785 break;
10786 case G_ALERTALTERNATE:
10787 compose_draft(data, COMPOSE_QUIT_EDITING);
10788 return;
10789 default:
10790 return;
10794 compose_close(compose);
10797 static void compose_print_cb(GtkAction *action, gpointer data)
10799 Compose *compose = (Compose *) data;
10801 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10802 if (compose->targetinfo)
10803 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10806 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10808 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10809 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10810 Compose *compose = (Compose *) data;
10812 if (active)
10813 compose->out_encoding = (CharSet)value;
10816 static void compose_address_cb(GtkAction *action, gpointer data)
10818 Compose *compose = (Compose *)data;
10820 #ifndef USE_ALT_ADDRBOOK
10821 addressbook_open(compose);
10822 #else
10823 GError* error = NULL;
10824 addressbook_connect_signals(compose);
10825 addressbook_dbus_open(TRUE, &error);
10826 if (error) {
10827 g_warning("%s", error->message);
10828 g_error_free(error);
10830 #endif
10833 static void about_show_cb(GtkAction *action, gpointer data)
10835 about_show();
10838 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10840 Compose *compose = (Compose *)data;
10841 Template *tmpl;
10842 gchar *msg;
10843 AlertValue val;
10845 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10846 cm_return_if_fail(tmpl != NULL);
10848 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10849 tmpl->name);
10850 val = alertpanel(_("Apply template"), msg,
10851 NULL, _("_Replace"), NULL, _("_Insert"), NULL, _("_Cancel"),
10852 ALERTFOCUS_FIRST);
10853 g_free(msg);
10855 if (val == G_ALERTDEFAULT)
10856 compose_template_apply(compose, tmpl, TRUE);
10857 else if (val == G_ALERTALTERNATE)
10858 compose_template_apply(compose, tmpl, FALSE);
10861 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10863 Compose *compose = (Compose *)data;
10865 if (compose->exteditor_tag != -1) {
10866 debug_print("ignoring open external editor: external editor still open\n");
10867 return;
10869 compose_exec_ext_editor(compose);
10872 static void compose_undo_cb(GtkAction *action, gpointer data)
10874 Compose *compose = (Compose *)data;
10875 gboolean prev_autowrap = compose->autowrap;
10877 compose->autowrap = FALSE;
10878 undo_undo(compose->undostruct);
10879 compose->autowrap = prev_autowrap;
10882 static void compose_redo_cb(GtkAction *action, gpointer data)
10884 Compose *compose = (Compose *)data;
10885 gboolean prev_autowrap = compose->autowrap;
10887 compose->autowrap = FALSE;
10888 undo_redo(compose->undostruct);
10889 compose->autowrap = prev_autowrap;
10892 static void entry_cut_clipboard(GtkWidget *entry)
10894 if (GTK_IS_EDITABLE(entry))
10895 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10896 else if (GTK_IS_TEXT_VIEW(entry))
10897 gtk_text_buffer_cut_clipboard(
10898 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10899 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10900 TRUE);
10903 static void entry_copy_clipboard(GtkWidget *entry)
10905 if (GTK_IS_EDITABLE(entry))
10906 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10907 else if (GTK_IS_TEXT_VIEW(entry))
10908 gtk_text_buffer_copy_clipboard(
10909 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10910 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10913 static void paste_text(Compose *compose, GtkWidget *entry,
10914 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place,
10915 const gchar *contents)
10917 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10918 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10919 GtkTextIter start_iter, end_iter;
10920 gint start, end;
10922 if (contents == NULL)
10923 return;
10925 /* we shouldn't delete the selection when middle-click-pasting, or we
10926 * can't mid-click-paste our own selection */
10927 if (clip != GDK_SELECTION_PRIMARY) {
10928 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10929 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10932 if (insert_place == NULL) {
10933 /* if insert_place isn't specified, insert at the cursor.
10934 * used for Ctrl-V pasting */
10935 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10936 start = gtk_text_iter_get_offset(&start_iter);
10937 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10938 } else {
10939 /* if insert_place is specified, paste here.
10940 * used for mid-click-pasting */
10941 start = gtk_text_iter_get_offset(insert_place);
10942 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10943 if (prefs_common.primary_paste_unselects)
10944 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10947 if (!wrap) {
10948 /* paste unwrapped: mark the paste so it's not wrapped later */
10949 end = start + strlen(contents);
10950 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10951 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10952 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10953 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10954 /* rewrap paragraph now (after a mid-click-paste) */
10955 mark_start = gtk_text_buffer_get_insert(buffer);
10956 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10957 gtk_text_iter_backward_char(&start_iter);
10958 compose_beautify_paragraph(compose, &start_iter, TRUE);
10960 compose->modified = TRUE;
10963 static void attach_uri_list(Compose *compose, GtkSelectionData *data)
10965 GList *list, *tmp;
10966 int att = 0;
10967 gchar *warn_files = NULL;
10969 list = uri_list_extract_filenames(
10970 (const gchar *)gtk_selection_data_get_data(data));
10971 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10972 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10973 gchar *tmp_f = g_strdup_printf("%s%s\n",
10974 warn_files?warn_files:"",
10975 utf8_filename);
10976 g_free(warn_files);
10977 warn_files = tmp_f;
10978 att++;
10979 compose_attach_append
10980 (compose, (const gchar *)tmp->data,
10981 utf8_filename, NULL, NULL);
10982 g_free(utf8_filename);
10984 if (list) {
10985 AlertValue val = 0;
10986 compose_changed_cb(NULL, compose);
10987 if (prefs_common.notify_pasted_attachments) {
10988 gchar *msg;
10990 msg = g_strdup_printf(ngettext("The following file has been attached: \n%s",
10991 "The following files have been attached: \n%s", att), warn_files);
10992 val = alertpanel_full(_("Notice"), msg,
10993 NULL, _("_Close"), NULL, NULL, NULL, NULL,
10994 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_NOTICE);
10995 g_free(msg);
10997 g_free(warn_files);
10998 if (val & G_ALERTDISABLE)
10999 prefs_common.notify_pasted_attachments = FALSE;
11001 list_free_strings_full(list);
11004 int attach_image(Compose *compose, GtkSelectionData *data, const gchar *subtype)
11006 FILE *fp;
11007 const guchar *contents;
11008 gchar *tmpf;
11009 gchar *file;
11010 gchar *type;
11011 size_t len;
11012 int r;
11014 cm_return_val_if_fail(data != NULL, -1);
11016 contents = gtk_selection_data_get_data(data);
11017 len = gtk_selection_data_get_length(data);
11019 tmpf = get_tmp_file();
11020 file = g_strconcat(tmpf, "-image.", subtype, NULL);
11021 g_free(tmpf);
11023 debug_print("writing image to %s\n", file);
11025 if ((fp = claws_fopen(file, "wb")) == NULL) {
11026 FILE_OP_ERROR(file, "claws_fopen");
11027 g_free(file);
11028 return -1;
11031 if (claws_fwrite(contents, 1, len, fp) != len) {
11032 FILE_OP_ERROR(file, "claws_fwrite");
11033 claws_fclose(fp);
11034 if (claws_unlink(file) < 0)
11035 FILE_OP_ERROR(file, "unlink");
11036 g_free(file);
11037 return -1;
11040 r = claws_safe_fclose(fp);
11042 if (r == EOF) {
11043 FILE_OP_ERROR(file, "claws_fclose");
11044 if (claws_unlink(file) < 0)
11045 FILE_OP_ERROR(file, "unlink");
11046 g_free(file);
11047 return -1;
11050 type = g_strconcat("image/", subtype, NULL);
11052 compose_attach_append(compose, (const gchar *)file,
11053 (const gchar *)file, type, NULL);
11055 alertpanel_notice(_("The pasted image has been attached as: \n%s"), file);
11057 g_free(file);
11058 g_free(type);
11060 return 0;
11063 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
11064 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
11066 if (GTK_IS_TEXT_VIEW(entry)) {
11067 GdkAtom types = gdk_atom_intern ("TARGETS", FALSE);
11068 GdkAtom *targets = NULL;
11069 int n_targets = 0, i;
11070 gboolean paste_done = FALSE;
11071 GtkClipboard *clipboard = gtk_clipboard_get(clip);
11073 GtkSelectionData *contents = gtk_clipboard_wait_for_contents(
11074 clipboard, types);
11076 if (contents != NULL) {
11077 gtk_selection_data_get_targets(contents, &targets, &n_targets);
11078 gtk_selection_data_free(contents);
11081 for (i = 0; i < n_targets; i++) {
11082 GdkAtom atom = targets[i];
11083 gchar *atom_type = gdk_atom_name(atom);
11085 if (atom_type != NULL && strchr(atom_type, '/')) {
11086 GtkSelectionData *data = gtk_clipboard_wait_for_contents(
11087 clipboard, atom);
11088 debug_print("got contents of type %s\n", atom_type);
11089 if (!strcmp(atom_type, "text/plain")) {
11090 /* let the default text handler handle it */
11091 break;
11092 } else if (!strcmp(atom_type, "text/uri-list")) {
11093 attach_uri_list(compose, data);
11095 paste_done = TRUE;
11096 break;
11097 } else if (!strncmp(atom_type, "image/", strlen("image/"))) {
11098 gchar *subtype = g_strdup((gchar *)(strstr(atom_type, "/")+1));
11099 debug_print("image of type %s\n", subtype);
11100 attach_image(compose, data, subtype);
11101 g_free(subtype);
11103 paste_done = TRUE;
11104 break;
11108 if (!paste_done) {
11109 gchar *def_text = gtk_clipboard_wait_for_text(clipboard);
11110 paste_text(compose, entry, wrap, clip,
11111 insert_place, def_text);
11112 g_free(def_text);
11114 g_free(targets);
11115 } else if (GTK_IS_EDITABLE(entry)) {
11116 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
11117 compose->modified = TRUE;
11121 static void entry_allsel(GtkWidget *entry)
11123 if (GTK_IS_EDITABLE(entry))
11124 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
11125 else if (GTK_IS_TEXT_VIEW(entry)) {
11126 GtkTextIter startiter, enditer;
11127 GtkTextBuffer *textbuf;
11129 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
11130 gtk_text_buffer_get_start_iter(textbuf, &startiter);
11131 gtk_text_buffer_get_end_iter(textbuf, &enditer);
11133 gtk_text_buffer_move_mark_by_name(textbuf,
11134 "selection_bound", &startiter);
11135 gtk_text_buffer_move_mark_by_name(textbuf,
11136 "insert", &enditer);
11140 static void compose_cut_cb(GtkAction *action, gpointer data)
11142 Compose *compose = (Compose *)data;
11143 if (compose->focused_editable
11144 #ifndef GENERIC_UMPC
11145 && gtk_widget_has_focus(compose->focused_editable)
11146 #endif
11148 entry_cut_clipboard(compose->focused_editable);
11151 static void compose_copy_cb(GtkAction *action, gpointer data)
11153 Compose *compose = (Compose *)data;
11154 if (compose->focused_editable
11155 #ifndef GENERIC_UMPC
11156 && gtk_widget_has_focus(compose->focused_editable)
11157 #endif
11159 entry_copy_clipboard(compose->focused_editable);
11162 static void compose_paste_cb(GtkAction *action, gpointer data)
11164 Compose *compose = (Compose *)data;
11165 gint prev_autowrap;
11166 GtkTextBuffer *buffer;
11167 BLOCK_WRAP();
11168 if (compose->focused_editable
11169 #ifndef GENERIC_UMPC
11170 && gtk_widget_has_focus(compose->focused_editable)
11171 #endif
11173 entry_paste_clipboard(compose, compose->focused_editable,
11174 prefs_common.linewrap_pastes,
11175 GDK_SELECTION_CLIPBOARD, NULL);
11176 UNBLOCK_WRAP();
11178 #ifdef USE_ENCHANT
11179 if (
11180 #ifndef GENERIC_UMPC
11181 gtk_widget_has_focus(compose->text) &&
11182 #endif
11183 compose->gtkaspell &&
11184 compose->gtkaspell->check_while_typing)
11185 gtkaspell_highlight_all(compose->gtkaspell);
11186 #endif
11189 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11191 Compose *compose = (Compose *)data;
11192 gint wrap_quote = prefs_common.linewrap_quote;
11193 if (compose->focused_editable
11194 #ifndef GENERIC_UMPC
11195 && gtk_widget_has_focus(compose->focused_editable)
11196 #endif
11198 /* let text_insert() (called directly or at a later time
11199 * after the gtk_editable_paste_clipboard) know that
11200 * text is to be inserted as a quotation. implemented
11201 * by using a simple refcount... */
11202 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11203 G_OBJECT(compose->focused_editable),
11204 "paste_as_quotation"));
11205 g_object_set_data(G_OBJECT(compose->focused_editable),
11206 "paste_as_quotation",
11207 GINT_TO_POINTER(paste_as_quotation + 1));
11208 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11209 entry_paste_clipboard(compose, compose->focused_editable,
11210 prefs_common.linewrap_pastes,
11211 GDK_SELECTION_CLIPBOARD, NULL);
11212 prefs_common.linewrap_quote = wrap_quote;
11216 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11218 Compose *compose = (Compose *)data;
11219 gint prev_autowrap;
11220 GtkTextBuffer *buffer;
11221 BLOCK_WRAP();
11222 if (compose->focused_editable
11223 #ifndef GENERIC_UMPC
11224 && gtk_widget_has_focus(compose->focused_editable)
11225 #endif
11227 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11228 GDK_SELECTION_CLIPBOARD, NULL);
11229 UNBLOCK_WRAP();
11231 #ifdef USE_ENCHANT
11232 if (
11233 #ifndef GENERIC_UMPC
11234 gtk_widget_has_focus(compose->text) &&
11235 #endif
11236 compose->gtkaspell &&
11237 compose->gtkaspell->check_while_typing)
11238 gtkaspell_highlight_all(compose->gtkaspell);
11239 #endif
11242 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11244 Compose *compose = (Compose *)data;
11245 gint prev_autowrap;
11246 GtkTextBuffer *buffer;
11247 BLOCK_WRAP();
11248 if (compose->focused_editable
11249 #ifndef GENERIC_UMPC
11250 && gtk_widget_has_focus(compose->focused_editable)
11251 #endif
11253 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11254 GDK_SELECTION_CLIPBOARD, NULL);
11255 UNBLOCK_WRAP();
11257 #ifdef USE_ENCHANT
11258 if (
11259 #ifndef GENERIC_UMPC
11260 gtk_widget_has_focus(compose->text) &&
11261 #endif
11262 compose->gtkaspell &&
11263 compose->gtkaspell->check_while_typing)
11264 gtkaspell_highlight_all(compose->gtkaspell);
11265 #endif
11268 static void compose_allsel_cb(GtkAction *action, gpointer data)
11270 Compose *compose = (Compose *)data;
11271 if (compose->focused_editable
11272 #ifndef GENERIC_UMPC
11273 && gtk_widget_has_focus(compose->focused_editable)
11274 #endif
11276 entry_allsel(compose->focused_editable);
11279 static void textview_move_beginning_of_line (GtkTextView *text)
11281 GtkTextBuffer *buffer;
11282 GtkTextMark *mark;
11283 GtkTextIter ins;
11285 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11287 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11288 mark = gtk_text_buffer_get_insert(buffer);
11289 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11290 gtk_text_iter_set_line_offset(&ins, 0);
11291 gtk_text_buffer_place_cursor(buffer, &ins);
11294 static void textview_move_forward_character (GtkTextView *text)
11296 GtkTextBuffer *buffer;
11297 GtkTextMark *mark;
11298 GtkTextIter ins;
11300 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11302 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11303 mark = gtk_text_buffer_get_insert(buffer);
11304 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11305 if (gtk_text_iter_forward_cursor_position(&ins))
11306 gtk_text_buffer_place_cursor(buffer, &ins);
11309 static void textview_move_backward_character (GtkTextView *text)
11311 GtkTextBuffer *buffer;
11312 GtkTextMark *mark;
11313 GtkTextIter ins;
11315 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11317 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11318 mark = gtk_text_buffer_get_insert(buffer);
11319 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11320 if (gtk_text_iter_backward_cursor_position(&ins))
11321 gtk_text_buffer_place_cursor(buffer, &ins);
11324 static void textview_move_forward_word (GtkTextView *text)
11326 GtkTextBuffer *buffer;
11327 GtkTextMark *mark;
11328 GtkTextIter ins;
11329 gint count;
11331 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11333 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11334 mark = gtk_text_buffer_get_insert(buffer);
11335 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11336 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11337 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11338 gtk_text_iter_backward_word_start(&ins);
11339 gtk_text_buffer_place_cursor(buffer, &ins);
11343 static void textview_move_backward_word (GtkTextView *text)
11345 GtkTextBuffer *buffer;
11346 GtkTextMark *mark;
11347 GtkTextIter ins;
11349 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11351 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11352 mark = gtk_text_buffer_get_insert(buffer);
11353 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11354 if (gtk_text_iter_backward_word_starts(&ins, 1))
11355 gtk_text_buffer_place_cursor(buffer, &ins);
11358 static void textview_move_end_of_line (GtkTextView *text)
11360 GtkTextBuffer *buffer;
11361 GtkTextMark *mark;
11362 GtkTextIter ins;
11364 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11366 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11367 mark = gtk_text_buffer_get_insert(buffer);
11368 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11369 if (gtk_text_iter_forward_to_line_end(&ins))
11370 gtk_text_buffer_place_cursor(buffer, &ins);
11373 static void textview_move_next_line (GtkTextView *text)
11375 GtkTextBuffer *buffer;
11376 GtkTextMark *mark;
11377 GtkTextIter ins;
11378 gint offset;
11380 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11382 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11383 mark = gtk_text_buffer_get_insert(buffer);
11384 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11385 offset = gtk_text_iter_get_line_offset(&ins);
11386 if (gtk_text_iter_forward_line(&ins)) {
11387 gtk_text_iter_set_line_offset(&ins, offset);
11388 gtk_text_buffer_place_cursor(buffer, &ins);
11392 static void textview_move_previous_line (GtkTextView *text)
11394 GtkTextBuffer *buffer;
11395 GtkTextMark *mark;
11396 GtkTextIter ins;
11397 gint offset;
11399 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11401 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11402 mark = gtk_text_buffer_get_insert(buffer);
11403 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11404 offset = gtk_text_iter_get_line_offset(&ins);
11405 if (gtk_text_iter_backward_line(&ins)) {
11406 gtk_text_iter_set_line_offset(&ins, offset);
11407 gtk_text_buffer_place_cursor(buffer, &ins);
11411 static void textview_delete_forward_character (GtkTextView *text)
11413 GtkTextBuffer *buffer;
11414 GtkTextMark *mark;
11415 GtkTextIter ins, end_iter;
11417 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11419 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11420 mark = gtk_text_buffer_get_insert(buffer);
11421 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11422 end_iter = ins;
11423 if (gtk_text_iter_forward_char(&end_iter)) {
11424 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11428 static void textview_delete_backward_character (GtkTextView *text)
11430 GtkTextBuffer *buffer;
11431 GtkTextMark *mark;
11432 GtkTextIter ins, end_iter;
11434 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11436 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11437 mark = gtk_text_buffer_get_insert(buffer);
11438 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11439 end_iter = ins;
11440 if (gtk_text_iter_backward_char(&end_iter)) {
11441 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11445 static void textview_delete_forward_word (GtkTextView *text)
11447 GtkTextBuffer *buffer;
11448 GtkTextMark *mark;
11449 GtkTextIter ins, end_iter;
11451 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11453 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11454 mark = gtk_text_buffer_get_insert(buffer);
11455 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11456 end_iter = ins;
11457 if (gtk_text_iter_forward_word_end(&end_iter)) {
11458 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11462 static void textview_delete_backward_word (GtkTextView *text)
11464 GtkTextBuffer *buffer;
11465 GtkTextMark *mark;
11466 GtkTextIter ins, end_iter;
11468 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11470 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11471 mark = gtk_text_buffer_get_insert(buffer);
11472 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11473 end_iter = ins;
11474 if (gtk_text_iter_backward_word_start(&end_iter)) {
11475 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11479 static void textview_delete_line (GtkTextView *text)
11481 GtkTextBuffer *buffer;
11482 GtkTextMark *mark;
11483 GtkTextIter ins, start_iter, end_iter;
11485 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11487 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11488 mark = gtk_text_buffer_get_insert(buffer);
11489 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11491 start_iter = ins;
11492 gtk_text_iter_set_line_offset(&start_iter, 0);
11494 end_iter = ins;
11495 if (gtk_text_iter_ends_line(&end_iter)){
11496 if (!gtk_text_iter_forward_char(&end_iter))
11497 gtk_text_iter_backward_char(&start_iter);
11499 else
11500 gtk_text_iter_forward_to_line_end(&end_iter);
11501 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11504 static void textview_delete_to_line_end (GtkTextView *text)
11506 GtkTextBuffer *buffer;
11507 GtkTextMark *mark;
11508 GtkTextIter ins, end_iter;
11510 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11512 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11513 mark = gtk_text_buffer_get_insert(buffer);
11514 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11515 end_iter = ins;
11516 if (gtk_text_iter_ends_line(&end_iter))
11517 gtk_text_iter_forward_char(&end_iter);
11518 else
11519 gtk_text_iter_forward_to_line_end(&end_iter);
11520 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11523 #define DO_ACTION(name, act) { \
11524 if(!strcmp(name, a_name)) { \
11525 return act; \
11528 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11530 const gchar *a_name = gtk_action_get_name(action);
11531 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11532 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11533 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11534 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11535 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11536 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11537 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11538 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11539 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11540 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11541 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11542 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11543 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11544 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11545 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11548 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11550 Compose *compose = (Compose *)data;
11551 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11552 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11554 action = compose_call_advanced_action_from_path(gaction);
11556 static struct {
11557 void (*do_action) (GtkTextView *text);
11558 } action_table[] = {
11559 {textview_move_beginning_of_line},
11560 {textview_move_forward_character},
11561 {textview_move_backward_character},
11562 {textview_move_forward_word},
11563 {textview_move_backward_word},
11564 {textview_move_end_of_line},
11565 {textview_move_next_line},
11566 {textview_move_previous_line},
11567 {textview_delete_forward_character},
11568 {textview_delete_backward_character},
11569 {textview_delete_forward_word},
11570 {textview_delete_backward_word},
11571 {textview_delete_line},
11572 {textview_delete_to_line_end}
11575 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11577 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11578 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11579 if (action_table[action].do_action)
11580 action_table[action].do_action(text);
11581 else
11582 g_warning("not implemented yet");
11586 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11588 GtkAllocation allocation;
11589 GtkWidget *parent;
11590 gchar *str = NULL;
11592 if (GTK_IS_EDITABLE(widget)) {
11593 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11594 gtk_editable_set_position(GTK_EDITABLE(widget),
11595 strlen(str));
11596 g_free(str);
11597 if ((parent = gtk_widget_get_parent(widget))
11598 && (parent = gtk_widget_get_parent(parent))
11599 && (parent = gtk_widget_get_parent(parent))) {
11600 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11601 gtk_widget_get_allocation(widget, &allocation);
11602 gint y = allocation.y;
11603 gint height = allocation.height;
11604 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11605 (GTK_SCROLLED_WINDOW(parent));
11607 gfloat value = gtk_adjustment_get_value(shown);
11608 gfloat upper = gtk_adjustment_get_upper(shown);
11609 gfloat page_size = gtk_adjustment_get_page_size(shown);
11610 if (y < (int)value) {
11611 gtk_adjustment_set_value(shown, y - 1);
11613 if ((y + height) > ((int)value + (int)page_size)) {
11614 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11615 gtk_adjustment_set_value(shown,
11616 y + height - (int)page_size - 1);
11617 } else {
11618 gtk_adjustment_set_value(shown,
11619 (int)upper - (int)page_size - 1);
11626 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11627 compose->focused_editable = widget;
11629 #ifdef GENERIC_UMPC
11630 if (GTK_IS_TEXT_VIEW(widget)
11631 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11632 g_object_ref(compose->notebook);
11633 g_object_ref(compose->edit_vbox);
11634 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11635 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11636 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11637 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11638 g_object_unref(compose->notebook);
11639 g_object_unref(compose->edit_vbox);
11640 g_signal_handlers_block_by_func(G_OBJECT(widget),
11641 G_CALLBACK(compose_grab_focus_cb),
11642 compose);
11643 gtk_widget_grab_focus(widget);
11644 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11645 G_CALLBACK(compose_grab_focus_cb),
11646 compose);
11647 } else if (!GTK_IS_TEXT_VIEW(widget)
11648 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11649 g_object_ref(compose->notebook);
11650 g_object_ref(compose->edit_vbox);
11651 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11652 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11653 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11654 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11655 g_object_unref(compose->notebook);
11656 g_object_unref(compose->edit_vbox);
11657 g_signal_handlers_block_by_func(G_OBJECT(widget),
11658 G_CALLBACK(compose_grab_focus_cb),
11659 compose);
11660 gtk_widget_grab_focus(widget);
11661 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11662 G_CALLBACK(compose_grab_focus_cb),
11663 compose);
11665 #endif
11668 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11670 compose->modified = TRUE;
11671 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11672 #ifndef GENERIC_UMPC
11673 compose_set_title(compose);
11674 #endif
11677 static void compose_wrap_cb(GtkAction *action, gpointer data)
11679 Compose *compose = (Compose *)data;
11680 compose_beautify_paragraph(compose, NULL, TRUE);
11683 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11685 Compose *compose = (Compose *)data;
11686 compose_wrap_all_full(compose, TRUE);
11689 static void compose_find_cb(GtkAction *action, gpointer data)
11691 Compose *compose = (Compose *)data;
11693 message_search_compose(compose);
11696 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11697 gpointer data)
11699 Compose *compose = (Compose *)data;
11700 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11701 if (compose->autowrap)
11702 compose_wrap_all_full(compose, TRUE);
11703 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11706 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11707 gpointer data)
11709 Compose *compose = (Compose *)data;
11710 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11713 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11715 Compose *compose = (Compose *)data;
11717 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11718 if (compose->toolbar->privacy_sign_btn != NULL)
11719 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11722 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11724 Compose *compose = (Compose *)data;
11726 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11727 if (compose->toolbar->privacy_encrypt_btn != NULL)
11728 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11731 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11733 g_free(compose->privacy_system);
11734 g_free(compose->encdata);
11736 compose->privacy_system = g_strdup(account->default_privacy_system);
11737 compose_update_privacy_system_menu_item(compose, warn);
11740 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11742 if (folder_item != NULL) {
11743 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT &&
11744 privacy_system_can_sign(compose->privacy_system)) {
11745 compose_use_signing(compose,
11746 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11748 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT &&
11749 privacy_system_can_encrypt(compose->privacy_system)) {
11750 compose_use_encryption(compose,
11751 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11756 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11758 Compose *compose = (Compose *)data;
11760 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11761 gtk_widget_show(compose->ruler_hbox);
11762 prefs_common.show_ruler = TRUE;
11763 } else {
11764 gtk_widget_hide(compose->ruler_hbox);
11765 gtk_widget_queue_resize(compose->edit_vbox);
11766 prefs_common.show_ruler = FALSE;
11770 static void compose_attach_drag_received_cb (GtkWidget *widget,
11771 GdkDragContext *context,
11772 gint x,
11773 gint y,
11774 GtkSelectionData *data,
11775 guint info,
11776 guint time,
11777 gpointer user_data)
11779 Compose *compose = (Compose *)user_data;
11780 GdkAtom type;
11782 type = gtk_selection_data_get_data_type(data);
11783 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11784 && gtk_drag_get_source_widget(context) !=
11785 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11786 attach_uri_list(compose, data);
11787 } else if (gtk_drag_get_source_widget(context)
11788 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11789 /* comes from our summaryview */
11790 SummaryView * summaryview = NULL;
11791 GSList * list = NULL, *cur = NULL;
11793 if (mainwindow_get_mainwindow())
11794 summaryview = mainwindow_get_mainwindow()->summaryview;
11796 if (summaryview)
11797 list = summary_get_selected_msg_list(summaryview);
11799 for (cur = list; cur; cur = cur->next) {
11800 MsgInfo *msginfo = (MsgInfo *)cur->data;
11801 gchar *file = NULL;
11802 if (msginfo)
11803 file = procmsg_get_message_file_full(msginfo,
11804 TRUE, TRUE);
11805 if (file) {
11806 compose_attach_append(compose, (const gchar *)file,
11807 (const gchar *)file, "message/rfc822", NULL);
11808 g_free(file);
11811 g_slist_free(list);
11815 static gboolean compose_drag_drop(GtkWidget *widget,
11816 GdkDragContext *drag_context,
11817 gint x, gint y,
11818 guint time, gpointer user_data)
11820 /* not handling this signal makes compose_insert_drag_received_cb
11821 * called twice */
11822 return TRUE;
11825 static gboolean completion_set_focus_to_subject
11826 (GtkWidget *widget,
11827 GdkEventKey *event,
11828 Compose *compose)
11830 cm_return_val_if_fail(compose != NULL, FALSE);
11832 /* make backtab move to subject field */
11833 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11834 gtk_widget_grab_focus(compose->subject_entry);
11835 return TRUE;
11837 return FALSE;
11840 static void compose_insert_drag_received_cb (GtkWidget *widget,
11841 GdkDragContext *drag_context,
11842 gint x,
11843 gint y,
11844 GtkSelectionData *data,
11845 guint info,
11846 guint time,
11847 gpointer user_data)
11849 Compose *compose = (Compose *)user_data;
11850 GList *list, *tmp;
11851 GdkAtom type;
11852 guint num_files;
11853 gchar *msg;
11855 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11856 * does not work */
11857 type = gtk_selection_data_get_data_type(data);
11858 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11859 AlertValue val = G_ALERTDEFAULT;
11860 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11862 list = uri_list_extract_filenames(ddata);
11863 num_files = g_list_length(list);
11864 if (list == NULL && strstr(ddata, "://")) {
11865 /* Assume a list of no files, and data has ://, is a remote link */
11866 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11867 gchar *tmpfile = get_tmp_file();
11868 str_write_to_file(tmpdata, tmpfile, TRUE);
11869 g_free(tmpdata);
11870 compose_insert_file(compose, tmpfile);
11871 claws_unlink(tmpfile);
11872 g_free(tmpfile);
11873 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11874 compose_beautify_paragraph(compose, NULL, TRUE);
11875 return;
11877 switch (prefs_common.compose_dnd_mode) {
11878 case COMPOSE_DND_ASK:
11879 msg = g_strdup_printf(
11880 ngettext(
11881 "Do you want to insert the contents of the file "
11882 "into the message body, or attach it to the email?",
11883 "Do you want to insert the contents of the %d files "
11884 "into the message body, or attach them to the email?",
11885 num_files),
11886 num_files);
11887 val = alertpanel_full(_("Insert or attach?"), msg,
11888 NULL, _("_Cancel"), NULL, _("_Insert"), NULL, _("_Attach"),
11889 ALERTFOCUS_SECOND, TRUE, NULL, ALERT_QUESTION);
11890 g_free(msg);
11891 break;
11892 case COMPOSE_DND_INSERT:
11893 val = G_ALERTALTERNATE;
11894 break;
11895 case COMPOSE_DND_ATTACH:
11896 val = G_ALERTOTHER;
11897 break;
11898 default:
11899 /* unexpected case */
11900 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11903 if (val & G_ALERTDISABLE) {
11904 val &= ~G_ALERTDISABLE;
11905 /* remember what action to perform by default, only if we don't click Cancel */
11906 if (val == G_ALERTALTERNATE)
11907 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11908 else if (val == G_ALERTOTHER)
11909 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11912 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11913 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11914 list_free_strings_full(list);
11915 return;
11916 } else if (val == G_ALERTOTHER) {
11917 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11918 list_free_strings_full(list);
11919 return;
11922 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11923 compose_insert_file(compose, (const gchar *)tmp->data);
11925 list_free_strings_full(list);
11926 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11927 return;
11931 static void compose_header_drag_received_cb (GtkWidget *widget,
11932 GdkDragContext *drag_context,
11933 gint x,
11934 gint y,
11935 GtkSelectionData *data,
11936 guint info,
11937 guint time,
11938 gpointer user_data)
11940 GtkEditable *entry = (GtkEditable *)user_data;
11941 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11943 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11944 * does not work */
11946 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11947 gchar *decoded=g_new(gchar, strlen(email));
11948 int start = 0;
11950 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11951 gtk_editable_delete_text(entry, 0, -1);
11952 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11953 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11954 g_free(decoded);
11955 return;
11957 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11960 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11962 Compose *compose = (Compose *)data;
11964 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11965 compose->return_receipt = TRUE;
11966 else
11967 compose->return_receipt = FALSE;
11970 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11972 Compose *compose = (Compose *)data;
11974 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11975 compose->remove_references = TRUE;
11976 else
11977 compose->remove_references = FALSE;
11980 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11981 ComposeHeaderEntry *headerentry)
11983 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11984 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11985 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11986 return FALSE;
11989 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11990 GdkEventKey *event,
11991 ComposeHeaderEntry *headerentry)
11993 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11994 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11995 !(event->state & GDK_MODIFIER_MASK) &&
11996 (event->keyval == GDK_KEY_BackSpace) &&
11997 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11998 gtk_container_remove
11999 (GTK_CONTAINER(headerentry->compose->header_table),
12000 headerentry->combo);
12001 gtk_container_remove
12002 (GTK_CONTAINER(headerentry->compose->header_table),
12003 headerentry->entry);
12004 headerentry->compose->header_list =
12005 g_slist_remove(headerentry->compose->header_list,
12006 headerentry);
12007 g_free(headerentry);
12008 } else if (event->keyval == GDK_KEY_Tab) {
12009 if (headerentry->compose->header_last == headerentry) {
12010 /* Override default next focus, and give it to subject_entry
12011 * instead of notebook tabs
12013 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
12014 gtk_widget_grab_focus(headerentry->compose->subject_entry);
12015 return TRUE;
12018 return FALSE;
12021 static gboolean scroll_postpone(gpointer data)
12023 Compose *compose = (Compose *)data;
12025 if (compose->batch)
12026 return FALSE;
12028 GTK_EVENTS_FLUSH();
12029 compose_show_first_last_header(compose, FALSE);
12030 return FALSE;
12033 static void compose_headerentry_changed_cb(GtkWidget *entry,
12034 ComposeHeaderEntry *headerentry)
12036 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
12037 compose_create_header_entry(headerentry->compose);
12038 g_signal_handlers_disconnect_matched
12039 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
12040 0, 0, NULL, NULL, headerentry);
12042 if (!headerentry->compose->batch)
12043 g_timeout_add(0, scroll_postpone, headerentry->compose);
12047 static gboolean compose_defer_auto_save_draft(Compose *compose)
12049 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
12050 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
12051 return FALSE;
12054 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
12056 GtkAdjustment *vadj;
12058 cm_return_if_fail(compose);
12060 if(compose->batch)
12061 return;
12063 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
12064 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
12065 vadj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(gtk_widget_get_parent(compose->header_table)));
12066 gtk_adjustment_set_value(vadj, (show_first ?
12067 gtk_adjustment_get_lower(vadj) :
12068 (gtk_adjustment_get_upper(vadj) -
12069 gtk_adjustment_get_page_size(vadj))));
12072 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
12073 const gchar *text, gint len, Compose *compose)
12075 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
12076 (G_OBJECT(compose->text), "paste_as_quotation"));
12077 GtkTextMark *mark;
12079 cm_return_if_fail(text != NULL);
12081 g_signal_handlers_block_by_func(G_OBJECT(buffer),
12082 G_CALLBACK(text_inserted),
12083 compose);
12084 if (paste_as_quotation) {
12085 gchar *new_text;
12086 const gchar *qmark;
12087 guint pos = 0;
12088 GtkTextIter start_iter;
12090 if (len < 0)
12091 len = strlen(text);
12093 new_text = g_strndup(text, len);
12095 qmark = compose_quote_char_from_context(compose);
12097 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
12098 gtk_text_buffer_place_cursor(buffer, iter);
12100 pos = gtk_text_iter_get_offset(iter);
12102 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
12103 _("Quote format error at line %d."));
12104 quote_fmt_reset_vartable();
12105 g_free(new_text);
12106 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
12107 GINT_TO_POINTER(paste_as_quotation - 1));
12109 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12110 gtk_text_buffer_place_cursor(buffer, iter);
12111 gtk_text_buffer_delete_mark(buffer, mark);
12113 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
12114 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
12115 compose_beautify_paragraph(compose, &start_iter, FALSE);
12116 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
12117 gtk_text_buffer_delete_mark(buffer, mark);
12118 } else {
12119 if (strcmp(text, "\n") || compose->automatic_break
12120 || gtk_text_iter_starts_line(iter)) {
12121 GtkTextIter before_ins;
12122 gtk_text_buffer_insert(buffer, iter, text, len);
12123 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
12124 before_ins = *iter;
12125 gtk_text_iter_backward_chars(&before_ins, len);
12126 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
12128 } else {
12129 /* check if the preceding is just whitespace or quote */
12130 GtkTextIter start_line;
12131 gchar *tmp = NULL, *quote = NULL;
12132 gint quote_len = 0, is_normal = 0;
12133 start_line = *iter;
12134 gtk_text_iter_set_line_offset(&start_line, 0);
12135 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
12136 g_strstrip(tmp);
12138 if (*tmp == '\0') {
12139 is_normal = 1;
12140 } else {
12141 quote = compose_get_quote_str(buffer, &start_line, &quote_len);
12142 if (quote)
12143 is_normal = 1;
12144 g_free(quote);
12146 g_free(tmp);
12148 if (is_normal) {
12149 gtk_text_buffer_insert(buffer, iter, text, len);
12150 } else {
12151 gtk_text_buffer_insert_with_tags_by_name(buffer,
12152 iter, text, len, "no_join", NULL);
12157 if (!paste_as_quotation) {
12158 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
12159 compose_beautify_paragraph(compose, iter, FALSE);
12160 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12161 gtk_text_buffer_delete_mark(buffer, mark);
12164 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
12165 G_CALLBACK(text_inserted),
12166 compose);
12167 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
12169 if (compose_can_autosave(compose) &&
12170 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
12171 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
12172 compose->draft_timeout_tag = g_timeout_add
12173 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
12176 #if USE_ENCHANT
12177 static void compose_check_all(GtkAction *action, gpointer data)
12179 Compose *compose = (Compose *)data;
12180 if (!compose->gtkaspell)
12181 return;
12183 if (gtk_widget_has_focus(compose->subject_entry))
12184 claws_spell_entry_check_all(
12185 CLAWS_SPELL_ENTRY(compose->subject_entry));
12186 else
12187 gtkaspell_check_all(compose->gtkaspell);
12190 static void compose_highlight_all(GtkAction *action, gpointer data)
12192 Compose *compose = (Compose *)data;
12193 if (compose->gtkaspell) {
12194 claws_spell_entry_recheck_all(
12195 CLAWS_SPELL_ENTRY(compose->subject_entry));
12196 gtkaspell_highlight_all(compose->gtkaspell);
12200 static void compose_check_backwards(GtkAction *action, gpointer data)
12202 Compose *compose = (Compose *)data;
12203 if (!compose->gtkaspell) {
12204 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12205 return;
12208 if (gtk_widget_has_focus(compose->subject_entry))
12209 claws_spell_entry_check_backwards(
12210 CLAWS_SPELL_ENTRY(compose->subject_entry));
12211 else
12212 gtkaspell_check_backwards(compose->gtkaspell);
12215 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12217 Compose *compose = (Compose *)data;
12218 if (!compose->gtkaspell) {
12219 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12220 return;
12223 if (gtk_widget_has_focus(compose->subject_entry))
12224 claws_spell_entry_check_forwards_go(
12225 CLAWS_SPELL_ENTRY(compose->subject_entry));
12226 else
12227 gtkaspell_check_forwards_go(compose->gtkaspell);
12229 #endif
12232 *\brief Guess originating forward account from MsgInfo and several
12233 * "common preference" settings. Return NULL if no guess.
12235 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12237 PrefsAccount *account = NULL;
12239 cm_return_val_if_fail(msginfo, NULL);
12240 cm_return_val_if_fail(msginfo->folder, NULL);
12241 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12243 if (msginfo->folder->prefs->enable_default_account)
12244 account = account_find_from_id(msginfo->folder->prefs->default_account);
12246 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12247 gchar *to;
12248 Xstrdup_a(to, msginfo->to, return NULL);
12249 extract_address(to);
12250 account = account_find_from_address(to, FALSE);
12253 if (!account && prefs_common.forward_account_autosel) {
12254 gchar *cc = NULL;
12255 if (!procheader_get_header_from_msginfo
12256 (msginfo, &cc, "Cc:")) {
12257 gchar *buf = cc + strlen("Cc:");
12258 extract_address(buf);
12259 account = account_find_from_address(buf, FALSE);
12260 g_free(cc);
12264 if (!account && prefs_common.forward_account_autosel) {
12265 gchar *deliveredto = NULL;
12266 if (!procheader_get_header_from_msginfo
12267 (msginfo, &deliveredto, "Delivered-To:")) {
12268 gchar *buf = deliveredto + strlen("Delivered-To:");
12269 extract_address(buf);
12270 account = account_find_from_address(buf, FALSE);
12271 g_free(deliveredto);
12275 if (!account)
12276 account = msginfo->folder->folder->account;
12278 return account;
12281 gboolean compose_close(Compose *compose)
12283 gint x, y;
12285 cm_return_val_if_fail(compose, FALSE);
12287 if (!g_mutex_trylock(&compose->mutex)) {
12288 /* we have to wait for the (possibly deferred by auto-save)
12289 * drafting to be done, before destroying the compose under
12290 * it. */
12291 debug_print("waiting for drafting to finish...\n");
12292 compose_allow_user_actions(compose, FALSE);
12293 if (compose->close_timeout_tag == 0) {
12294 compose->close_timeout_tag =
12295 g_timeout_add (500, (GSourceFunc) compose_close,
12296 compose);
12298 return TRUE;
12301 if (compose->draft_timeout_tag >= 0) {
12302 g_source_remove(compose->draft_timeout_tag);
12303 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12306 gtk_window_get_position(GTK_WINDOW(compose->window), &x, &y);
12307 if (!compose->batch) {
12308 prefs_common.compose_x = x;
12309 prefs_common.compose_y = y;
12311 g_mutex_unlock(&compose->mutex);
12312 compose_destroy(compose);
12313 return FALSE;
12317 * Add entry field for each address in list.
12318 * \param compose E-Mail composition object.
12319 * \param listAddress List of (formatted) E-Mail addresses.
12321 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12322 GList *node;
12323 gchar *addr;
12324 node = listAddress;
12325 while( node ) {
12326 addr = ( gchar * ) node->data;
12327 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12328 node = g_list_next( node );
12332 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12333 guint action, gboolean opening_multiple)
12335 gchar *body = NULL;
12336 GSList *new_msglist = NULL;
12337 MsgInfo *tmp_msginfo = NULL;
12338 gboolean originally_enc = FALSE;
12339 gboolean originally_sig = FALSE;
12340 Compose *compose = NULL;
12341 gchar *s_system = NULL;
12343 cm_return_if_fail(msginfo_list != NULL);
12345 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12346 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12347 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12349 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12350 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12351 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12352 orig_msginfo, mimeinfo);
12353 if (tmp_msginfo != NULL) {
12354 new_msglist = g_slist_append(NULL, tmp_msginfo);
12356 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12357 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12358 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12360 tmp_msginfo->folder = orig_msginfo->folder;
12361 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12362 if (orig_msginfo->tags) {
12363 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12364 tmp_msginfo->folder->tags_dirty = TRUE;
12370 if (!opening_multiple && msgview != NULL)
12371 body = messageview_get_selection(msgview);
12373 if (new_msglist) {
12374 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12375 procmsg_msginfo_free(&tmp_msginfo);
12376 g_slist_free(new_msglist);
12377 } else
12378 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12380 if (compose && originally_enc) {
12381 compose_force_encryption(compose, compose->account, FALSE, s_system);
12384 if (compose && originally_sig && compose->account->default_sign_reply) {
12385 compose_force_signing(compose, compose->account, s_system);
12387 g_free(s_system);
12388 g_free(body);
12389 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12392 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12393 guint action)
12395 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12396 && msginfo_list != NULL
12397 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12398 GSList *cur = msginfo_list;
12399 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12400 "messages. Opening the windows "
12401 "could take some time. Do you "
12402 "want to continue?"),
12403 g_slist_length(msginfo_list));
12404 if (g_slist_length(msginfo_list) > 9
12405 && alertpanel(_("Warning"), msg, NULL, _("_Cancel"), NULL, _("_Yes"),
12406 NULL, NULL, ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12407 g_free(msg);
12408 return;
12410 g_free(msg);
12411 /* We'll open multiple compose windows */
12412 /* let the WM place the next windows */
12413 compose_force_window_origin = FALSE;
12414 for (; cur; cur = cur->next) {
12415 GSList tmplist;
12416 tmplist.data = cur->data;
12417 tmplist.next = NULL;
12418 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12420 compose_force_window_origin = TRUE;
12421 } else {
12422 /* forwarding multiple mails as attachments is done via a
12423 * single compose window */
12424 if (msginfo_list != NULL) {
12425 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12426 } else if (msgview != NULL) {
12427 GSList tmplist;
12428 tmplist.data = msgview->msginfo;
12429 tmplist.next = NULL;
12430 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12431 } else {
12432 debug_print("Nothing to reply to\n");
12437 void compose_check_for_email_account(Compose *compose)
12439 PrefsAccount *ac = NULL, *curr = NULL;
12440 GList *list;
12442 if (!compose)
12443 return;
12445 if (compose->account && compose->account->protocol == A_NNTP) {
12446 ac = account_get_cur_account();
12447 if (ac->protocol == A_NNTP) {
12448 list = account_get_list();
12450 for( ; list != NULL ; list = g_list_next(list)) {
12451 curr = (PrefsAccount *) list->data;
12452 if (curr->protocol != A_NNTP) {
12453 ac = curr;
12454 break;
12458 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12459 ac->account_id);
12463 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12464 const gchar *address)
12466 GSList *msginfo_list = NULL;
12467 gchar *body = messageview_get_selection(msgview);
12468 Compose *compose;
12470 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12472 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12473 compose_check_for_email_account(compose);
12474 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12475 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12476 compose_reply_set_subject(compose, msginfo);
12478 g_free(body);
12479 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12482 void compose_set_position(Compose *compose, gint pos)
12484 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12486 gtkut_text_view_set_position(text, pos);
12489 gboolean compose_search_string(Compose *compose,
12490 const gchar *str, gboolean case_sens)
12492 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12494 return gtkut_text_view_search_string(text, str, case_sens);
12497 gboolean compose_search_string_backward(Compose *compose,
12498 const gchar *str, gboolean case_sens)
12500 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12502 return gtkut_text_view_search_string_backward(text, str, case_sens);
12505 /* allocate a msginfo structure and populate its data from a compose data structure */
12506 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12508 MsgInfo *newmsginfo;
12509 GSList *list;
12510 gchar date[RFC822_DATE_BUFFSIZE];
12512 cm_return_val_if_fail( compose != NULL, NULL );
12514 newmsginfo = procmsg_msginfo_new();
12516 /* date is now */
12517 get_rfc822_date(date, sizeof(date));
12518 newmsginfo->date = g_strdup(date);
12520 /* from */
12521 if (compose->from_name) {
12522 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12523 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12526 /* subject */
12527 if (compose->subject_entry)
12528 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12530 /* to, cc, reply-to, newsgroups */
12531 for (list = compose->header_list; list; list = list->next) {
12532 gchar *header = gtk_editable_get_chars(
12533 GTK_EDITABLE(
12534 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12535 gchar *entry = gtk_editable_get_chars(
12536 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12538 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12539 if ( newmsginfo->to == NULL ) {
12540 newmsginfo->to = g_strdup(entry);
12541 } else if (entry && *entry) {
12542 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12543 g_free(newmsginfo->to);
12544 newmsginfo->to = tmp;
12546 } else
12547 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12548 if ( newmsginfo->cc == NULL ) {
12549 newmsginfo->cc = g_strdup(entry);
12550 } else if (entry && *entry) {
12551 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12552 g_free(newmsginfo->cc);
12553 newmsginfo->cc = tmp;
12555 } else
12556 if ( strcasecmp(header,
12557 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12558 if ( newmsginfo->newsgroups == NULL ) {
12559 newmsginfo->newsgroups = g_strdup(entry);
12560 } else if (entry && *entry) {
12561 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12562 g_free(newmsginfo->newsgroups);
12563 newmsginfo->newsgroups = tmp;
12567 g_free(header);
12568 g_free(entry);
12571 /* other data is unset */
12573 return newmsginfo;
12576 #ifdef USE_ENCHANT
12577 /* update compose's dictionaries from folder dict settings */
12578 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12579 FolderItem *folder_item)
12581 cm_return_if_fail(compose != NULL);
12583 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12584 FolderItemPrefs *prefs = folder_item->prefs;
12586 if (prefs->enable_default_dictionary)
12587 gtkaspell_change_dict(compose->gtkaspell,
12588 prefs->default_dictionary, FALSE);
12589 if (folder_item->prefs->enable_default_alt_dictionary)
12590 gtkaspell_change_alt_dict(compose->gtkaspell,
12591 prefs->default_alt_dictionary);
12592 if (prefs->enable_default_dictionary
12593 || prefs->enable_default_alt_dictionary)
12594 compose_spell_menu_changed(compose);
12597 #endif
12599 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12601 Compose *compose = (Compose *)data;
12603 cm_return_if_fail(compose != NULL);
12605 gtk_widget_grab_focus(compose->text);
12608 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12610 gtk_combo_box_popup(GTK_COMBO_BOX(data));
12615 * End of Source.