Forgot to adjust default window size
[claws.git] / src / compose.c
blobd216a4b62ddd0f826ab84286dd8a2aa067bdefea
1 /*
2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2016 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #include "claws-features.h"
22 #endif
24 #include "defs.h"
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
28 #endif
30 #include <glib.h>
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtk.h>
35 #include <pango/pango-break.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <ctype.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <unistd.h>
43 #include <time.h>
44 #include <stdlib.h>
45 #if HAVE_SYS_WAIT_H
46 # include <sys/wait.h>
47 #endif
48 #include <signal.h>
49 #include <errno.h>
50 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
51 #include <libgen.h>
52 #endif
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
55 # include <wchar.h>
56 # include <wctype.h>
57 #endif
59 #include "claws.h"
60 #include "main.h"
61 #include "mainwindow.h"
62 #include "compose.h"
63 #ifndef USE_ALT_ADDRBOOK
64 #include "addressbook.h"
65 #else
66 #include "addressbook-dbus.h"
67 #include "addressadd.h"
68 #endif
69 #include "folderview.h"
70 #include "procmsg.h"
71 #include "menu.h"
72 #include "stock_pixmap.h"
73 #include "send_message.h"
74 #include "imap.h"
75 #include "news.h"
76 #include "customheader.h"
77 #include "prefs_common.h"
78 #include "prefs_account.h"
79 #include "action.h"
80 #include "account.h"
81 #include "filesel.h"
82 #include "procheader.h"
83 #include "procmime.h"
84 #include "statusbar.h"
85 #include "about.h"
86 #include "quoted-printable.h"
87 #include "codeconv.h"
88 #include "utils.h"
89 #include "gtkutils.h"
90 #include "gtkshruler.h"
91 #include "socket.h"
92 #include "alertpanel.h"
93 #include "manage_window.h"
94 #include "folder.h"
95 #include "folder_item_prefs.h"
96 #include "addr_compl.h"
97 #include "quote_fmt.h"
98 #include "undo.h"
99 #include "foldersel.h"
100 #include "toolbar.h"
101 #include "inc.h"
102 #include "message_search.h"
103 #include "combobox.h"
104 #include "hooks.h"
105 #include "privacy.h"
106 #include "timing.h"
107 #include "autofaces.h"
108 #include "spell_entry.h"
110 enum
112 COL_MIMETYPE = 0,
113 COL_SIZE = 1,
114 COL_NAME = 2,
115 COL_CHARSET = 3,
116 COL_DATA = 4,
117 COL_AUTODATA = 5,
118 N_COL_COLUMNS
121 #define N_ATTACH_COLS (N_COL_COLUMNS)
123 typedef enum
125 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
126 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
134 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
139 } ComposeCallAdvancedAction;
141 typedef enum
143 PRIORITY_HIGHEST = 1,
144 PRIORITY_HIGH,
145 PRIORITY_NORMAL,
146 PRIORITY_LOW,
147 PRIORITY_LOWEST
148 } PriorityLevel;
150 typedef enum
152 COMPOSE_INSERT_SUCCESS,
153 COMPOSE_INSERT_READ_ERROR,
154 COMPOSE_INSERT_INVALID_CHARACTER,
155 COMPOSE_INSERT_NO_FILE
156 } ComposeInsertResult;
158 typedef enum
160 COMPOSE_WRITE_FOR_SEND,
161 COMPOSE_WRITE_FOR_STORE
162 } ComposeWriteType;
164 typedef enum
166 COMPOSE_QUOTE_FORCED,
167 COMPOSE_QUOTE_CHECK,
168 COMPOSE_QUOTE_SKIP
169 } ComposeQuoteMode;
171 typedef enum {
172 TO_FIELD_PRESENT,
173 SUBJECT_FIELD_PRESENT,
174 BODY_FIELD_PRESENT,
175 NO_FIELD_PRESENT
176 } MailField;
178 #define B64_LINE_SIZE 57
179 #define B64_BUFFSIZE 77
181 #define MAX_REFERENCES_LEN 999
183 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
184 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
186 static GList *compose_list = NULL;
187 static GSList *extra_headers = NULL;
189 static Compose *compose_generic_new (PrefsAccount *account,
190 const gchar *to,
191 FolderItem *item,
192 GList *attach_files,
193 GList *listAddress );
195 static Compose *compose_create (PrefsAccount *account,
196 FolderItem *item,
197 ComposeMode mode,
198 gboolean batch);
200 static void compose_entry_mark_default_to (Compose *compose,
201 const gchar *address);
202 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
203 ComposeQuoteMode quote_mode,
204 gboolean to_all,
205 gboolean to_sender,
206 const gchar *body);
207 static Compose *compose_forward_multiple (PrefsAccount *account,
208 GSList *msginfo_list);
209 static Compose *compose_reply (MsgInfo *msginfo,
210 ComposeQuoteMode quote_mode,
211 gboolean to_all,
212 gboolean to_ml,
213 gboolean to_sender,
214 const gchar *body);
215 static Compose *compose_reply_mode (ComposeMode mode,
216 GSList *msginfo_list,
217 gchar *body);
218 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
219 static void compose_update_privacy_systems_menu(Compose *compose);
221 static GtkWidget *compose_account_option_menu_create
222 (Compose *compose);
223 static void compose_set_out_encoding (Compose *compose);
224 static void compose_set_template_menu (Compose *compose);
225 static void compose_destroy (Compose *compose);
227 static MailField compose_entries_set (Compose *compose,
228 const gchar *mailto,
229 ComposeEntryType to_type);
230 static gint compose_parse_header (Compose *compose,
231 MsgInfo *msginfo);
232 static gint compose_parse_manual_headers (Compose *compose,
233 MsgInfo *msginfo,
234 HeaderEntry *entries);
235 static gchar *compose_parse_references (const gchar *ref,
236 const gchar *msgid);
238 static gchar *compose_quote_fmt (Compose *compose,
239 MsgInfo *msginfo,
240 const gchar *fmt,
241 const gchar *qmark,
242 const gchar *body,
243 gboolean rewrap,
244 gboolean need_unescape,
245 const gchar *err_msg);
247 static void compose_reply_set_entry (Compose *compose,
248 MsgInfo *msginfo,
249 gboolean to_all,
250 gboolean to_ml,
251 gboolean to_sender,
252 gboolean
253 followup_and_reply_to);
254 static void compose_reedit_set_entry (Compose *compose,
255 MsgInfo *msginfo);
257 static void compose_insert_sig (Compose *compose,
258 gboolean replace);
259 static ComposeInsertResult compose_insert_file (Compose *compose,
260 const gchar *file);
262 static gboolean compose_attach_append (Compose *compose,
263 const gchar *file,
264 const gchar *type,
265 const gchar *content_type,
266 const gchar *charset);
267 static void compose_attach_parts (Compose *compose,
268 MsgInfo *msginfo);
270 static gboolean compose_beautify_paragraph (Compose *compose,
271 GtkTextIter *par_iter,
272 gboolean force);
273 static void compose_wrap_all (Compose *compose);
274 static void compose_wrap_all_full (Compose *compose,
275 gboolean autowrap);
277 static void compose_set_title (Compose *compose);
278 static void compose_select_account (Compose *compose,
279 PrefsAccount *account,
280 gboolean init);
282 static PrefsAccount *compose_current_mail_account(void);
283 /* static gint compose_send (Compose *compose); */
284 static gboolean compose_check_for_valid_recipient
285 (Compose *compose);
286 static gboolean compose_check_entries (Compose *compose,
287 gboolean check_everything);
288 static gint compose_write_to_file (Compose *compose,
289 FILE *fp,
290 gint action,
291 gboolean attach_parts);
292 static gint compose_write_body_to_file (Compose *compose,
293 const gchar *file);
294 static gint compose_remove_reedit_target (Compose *compose,
295 gboolean force);
296 static void compose_remove_draft (Compose *compose);
297 static gint compose_queue_sub (Compose *compose,
298 gint *msgnum,
299 FolderItem **item,
300 gchar **msgpath,
301 gboolean check_subject,
302 gboolean remove_reedit_target);
303 static int compose_add_attachments (Compose *compose,
304 MimeInfo *parent);
305 static gchar *compose_get_header (Compose *compose);
306 static gchar *compose_get_manual_headers_info (Compose *compose);
308 static void compose_convert_header (Compose *compose,
309 gchar *dest,
310 gint len,
311 gchar *src,
312 gint header_len,
313 gboolean addr_field);
315 static void compose_attach_info_free (AttachInfo *ainfo);
316 static void compose_attach_remove_selected (GtkAction *action,
317 gpointer data);
319 static void compose_template_apply (Compose *compose,
320 Template *tmpl,
321 gboolean replace);
322 static void compose_attach_property (GtkAction *action,
323 gpointer data);
324 static void compose_attach_property_create (gboolean *cancelled);
325 static void attach_property_ok (GtkWidget *widget,
326 gboolean *cancelled);
327 static void attach_property_cancel (GtkWidget *widget,
328 gboolean *cancelled);
329 static gint attach_property_delete_event (GtkWidget *widget,
330 GdkEventAny *event,
331 gboolean *cancelled);
332 static gboolean attach_property_key_pressed (GtkWidget *widget,
333 GdkEventKey *event,
334 gboolean *cancelled);
336 static void compose_exec_ext_editor (Compose *compose);
337 #ifdef G_OS_UNIX
338 static gint compose_exec_ext_editor_real (const gchar *file,
339 GdkNativeWindow socket_wid);
340 static gboolean compose_ext_editor_kill (Compose *compose);
341 static gboolean compose_input_cb (GIOChannel *source,
342 GIOCondition condition,
343 gpointer data);
344 static void compose_set_ext_editor_sensitive (Compose *compose,
345 gboolean sensitive);
346 static gboolean compose_get_ext_editor_cmd_valid();
347 static gboolean compose_get_ext_editor_uses_socket();
348 static gboolean compose_ext_editor_plug_removed_cb
349 (GtkSocket *socket,
350 Compose *compose);
351 #endif /* G_OS_UNIX */
353 static void compose_undo_state_changed (UndoMain *undostruct,
354 gint undo_state,
355 gint redo_state,
356 gpointer data);
358 static void compose_create_header_entry (Compose *compose);
359 static void compose_add_header_entry (Compose *compose, const gchar *header,
360 gchar *text, ComposePrefType pref_type);
361 static void compose_remove_header_entries(Compose *compose);
363 static void compose_update_priority_menu_item(Compose * compose);
364 #if USE_ENCHANT
365 static void compose_spell_menu_changed (void *data);
366 static void compose_dict_changed (void *data);
367 #endif
368 static void compose_add_field_list ( Compose *compose,
369 GList *listAddress );
371 /* callback functions */
373 static void compose_notebook_size_alloc (GtkNotebook *notebook,
374 GtkAllocation *allocation,
375 GtkPaned *paned);
376 static gboolean compose_edit_size_alloc (GtkEditable *widget,
377 GtkAllocation *allocation,
378 GtkSHRuler *shruler);
379 static void account_activated (GtkComboBox *optmenu,
380 gpointer data);
381 static void attach_selected (GtkTreeView *tree_view,
382 GtkTreePath *tree_path,
383 GtkTreeViewColumn *column,
384 Compose *compose);
385 static gboolean attach_button_pressed (GtkWidget *widget,
386 GdkEventButton *event,
387 gpointer data);
388 static gboolean attach_key_pressed (GtkWidget *widget,
389 GdkEventKey *event,
390 gpointer data);
391 static void compose_send_cb (GtkAction *action, gpointer data);
392 static void compose_send_later_cb (GtkAction *action, gpointer data);
394 static void compose_save_cb (GtkAction *action,
395 gpointer data);
397 static void compose_attach_cb (GtkAction *action,
398 gpointer data);
399 static void compose_insert_file_cb (GtkAction *action,
400 gpointer data);
401 static void compose_insert_sig_cb (GtkAction *action,
402 gpointer data);
403 static void compose_replace_sig_cb (GtkAction *action,
404 gpointer data);
406 static void compose_close_cb (GtkAction *action,
407 gpointer data);
408 static void compose_print_cb (GtkAction *action,
409 gpointer data);
411 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
413 static void compose_address_cb (GtkAction *action,
414 gpointer data);
415 static void about_show_cb (GtkAction *action,
416 gpointer data);
417 static void compose_template_activate_cb(GtkWidget *widget,
418 gpointer data);
420 static void compose_ext_editor_cb (GtkAction *action,
421 gpointer data);
423 static gint compose_delete_cb (GtkWidget *widget,
424 GdkEventAny *event,
425 gpointer data);
427 static void compose_undo_cb (GtkAction *action,
428 gpointer data);
429 static void compose_redo_cb (GtkAction *action,
430 gpointer data);
431 static void compose_cut_cb (GtkAction *action,
432 gpointer data);
433 static void compose_copy_cb (GtkAction *action,
434 gpointer data);
435 static void compose_paste_cb (GtkAction *action,
436 gpointer data);
437 static void compose_paste_as_quote_cb (GtkAction *action,
438 gpointer data);
439 static void compose_paste_no_wrap_cb (GtkAction *action,
440 gpointer data);
441 static void compose_paste_wrap_cb (GtkAction *action,
442 gpointer data);
443 static void compose_allsel_cb (GtkAction *action,
444 gpointer data);
446 static void compose_advanced_action_cb (GtkAction *action,
447 gpointer data);
449 static void compose_grab_focus_cb (GtkWidget *widget,
450 Compose *compose);
452 static void compose_changed_cb (GtkTextBuffer *textbuf,
453 Compose *compose);
455 static void compose_wrap_cb (GtkAction *action,
456 gpointer data);
457 static void compose_wrap_all_cb (GtkAction *action,
458 gpointer data);
459 static void compose_find_cb (GtkAction *action,
460 gpointer data);
461 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
462 gpointer data);
463 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
464 gpointer data);
466 static void compose_toggle_ruler_cb (GtkToggleAction *action,
467 gpointer data);
468 static void compose_toggle_sign_cb (GtkToggleAction *action,
469 gpointer data);
470 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
471 gpointer data);
472 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
473 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
474 static void activate_privacy_system (Compose *compose,
475 PrefsAccount *account,
476 gboolean warn);
477 static void compose_use_signing(Compose *compose, gboolean use_signing);
478 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
479 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
480 gpointer data);
481 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
482 gpointer data);
483 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
484 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
485 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
487 static void compose_attach_drag_received_cb (GtkWidget *widget,
488 GdkDragContext *drag_context,
489 gint x,
490 gint y,
491 GtkSelectionData *data,
492 guint info,
493 guint time,
494 gpointer user_data);
495 static void compose_insert_drag_received_cb (GtkWidget *widget,
496 GdkDragContext *drag_context,
497 gint x,
498 gint y,
499 GtkSelectionData *data,
500 guint info,
501 guint time,
502 gpointer user_data);
503 static void compose_header_drag_received_cb (GtkWidget *widget,
504 GdkDragContext *drag_context,
505 gint x,
506 gint y,
507 GtkSelectionData *data,
508 guint info,
509 guint time,
510 gpointer user_data);
512 static gboolean compose_drag_drop (GtkWidget *widget,
513 GdkDragContext *drag_context,
514 gint x, gint y,
515 guint time, gpointer user_data);
516 static gboolean completion_set_focus_to_subject
517 (GtkWidget *widget,
518 GdkEventKey *event,
519 Compose *user_data);
521 static void text_inserted (GtkTextBuffer *buffer,
522 GtkTextIter *iter,
523 const gchar *text,
524 gint len,
525 Compose *compose);
526 static Compose *compose_generic_reply(MsgInfo *msginfo,
527 ComposeQuoteMode quote_mode,
528 gboolean to_all,
529 gboolean to_ml,
530 gboolean to_sender,
531 gboolean followup_and_reply_to,
532 const gchar *body);
534 static void compose_headerentry_changed_cb (GtkWidget *entry,
535 ComposeHeaderEntry *headerentry);
536 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
537 GdkEventKey *event,
538 ComposeHeaderEntry *headerentry);
539 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
540 ComposeHeaderEntry *headerentry);
542 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
544 static void compose_allow_user_actions (Compose *compose, gboolean allow);
546 static void compose_nothing_cb (GtkAction *action, gpointer data)
551 #if USE_ENCHANT
552 static void compose_check_all (GtkAction *action, gpointer data);
553 static void compose_highlight_all (GtkAction *action, gpointer data);
554 static void compose_check_backwards (GtkAction *action, gpointer data);
555 static void compose_check_forwards_go (GtkAction *action, gpointer data);
556 #endif
558 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
560 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
562 #ifdef USE_ENCHANT
563 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
564 FolderItem *folder_item);
565 #endif
566 static void compose_attach_update_label(Compose *compose);
567 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
568 gboolean respect_default_to);
569 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
570 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
572 static GtkActionEntry compose_popup_entries[] =
574 {"Compose", NULL, "Compose" },
575 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
576 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
577 {"Compose/---", NULL, "---", NULL, NULL, NULL },
578 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
581 static GtkActionEntry compose_entries[] =
583 {"Menu", NULL, "Menu" },
584 /* menus */
585 {"Message", NULL, N_("_Message") },
586 {"Edit", NULL, N_("_Edit") },
587 #if USE_ENCHANT
588 {"Spelling", NULL, N_("_Spelling") },
589 #endif
590 {"Options", NULL, N_("_Options") },
591 {"Tools", NULL, N_("_Tools") },
592 {"Help", NULL, N_("_Help") },
593 /* Message menu */
594 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
595 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
596 {"Message/---", NULL, "---" },
598 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
599 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
600 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
601 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
602 /* {"Message/---", NULL, "---" }, */
603 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
604 /* {"Message/---", NULL, "---" }, */
605 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
606 /* {"Message/---", NULL, "---" }, */
607 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
609 /* Edit menu */
610 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
611 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
612 {"Edit/---", NULL, "---" },
614 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
615 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
616 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
618 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
619 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
620 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
621 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
623 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
625 {"Edit/Advanced", NULL, N_("A_dvanced") },
626 {"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*/
627 {"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*/
628 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
629 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
630 {"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*/
631 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
632 {"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*/
633 {"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*/
634 {"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*/
635 {"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*/
636 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
637 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
638 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
639 {"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*/
641 /* {"Edit/---", NULL, "---" }, */
642 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
644 /* {"Edit/---", NULL, "---" }, */
645 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
646 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
647 /* {"Edit/---", NULL, "---" }, */
648 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
649 #if USE_ENCHANT
650 /* Spelling menu */
651 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
652 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
653 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
654 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
656 {"Spelling/---", NULL, "---" },
657 {"Spelling/Options", NULL, N_("_Options") },
658 #endif
660 /* Options menu */
662 {"Options/ReplyMode", NULL, N_("Reply _mode") },
663 {"Options/---", NULL, "---" },
664 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
665 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
667 /* {"Options/---", NULL, "---" }, */
669 {"Options/Priority", NULL, N_("_Priority") },
671 {"Options/Encoding", NULL, N_("Character _encoding") },
672 {"Options/Encoding/---", NULL, "---" },
673 #define ENC_ACTION(cs_char,c_char,string) \
674 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
676 {"Options/Encoding/Western", NULL, N_("Western European") },
677 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
678 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
679 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
680 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
681 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
682 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
683 {"Options/Encoding/Korean", NULL, N_("Korean") },
684 {"Options/Encoding/Thai", NULL, N_("Thai") },
686 /* Tools menu */
687 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
689 {"Tools/Template", NULL, N_("_Template") },
690 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
691 {"Tools/Actions", NULL, N_("Actio_ns") },
692 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
694 /* Help menu */
695 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
698 static GtkToggleActionEntry compose_toggle_entries[] =
700 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
701 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
702 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
703 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
704 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
705 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
706 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
709 static GtkRadioActionEntry compose_radio_rm_entries[] =
711 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
712 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
713 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
714 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
717 static GtkRadioActionEntry compose_radio_prio_entries[] =
719 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
720 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
721 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
722 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
723 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
726 static GtkRadioActionEntry compose_radio_enc_entries[] =
728 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
729 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
763 static GtkTargetEntry compose_mime_types[] =
765 {"text/uri-list", 0, 0},
766 {"UTF8_STRING", 0, 0},
767 {"text/plain", 0, 0}
770 static gboolean compose_put_existing_to_front(MsgInfo *info)
772 const GList *compose_list = compose_get_compose_list();
773 const GList *elem = NULL;
775 if (compose_list) {
776 for (elem = compose_list; elem != NULL && elem->data != NULL;
777 elem = elem->next) {
778 Compose *c = (Compose*)elem->data;
780 if (!c->targetinfo || !c->targetinfo->msgid ||
781 !info->msgid)
782 continue;
784 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
785 gtkut_window_popup(c->window);
786 return TRUE;
790 return FALSE;
793 static GdkColor quote_color1 =
794 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
795 static GdkColor quote_color2 =
796 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
797 static GdkColor quote_color3 =
798 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
800 static GdkColor quote_bgcolor1 =
801 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
802 static GdkColor quote_bgcolor2 =
803 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
804 static GdkColor quote_bgcolor3 =
805 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
807 static GdkColor signature_color = {
808 (gulong)0,
809 (gushort)0x7fff,
810 (gushort)0x7fff,
811 (gushort)0x7fff
814 static GdkColor uri_color = {
815 (gulong)0,
816 (gushort)0,
817 (gushort)0,
818 (gushort)0
821 static void compose_create_tags(GtkTextView *text, Compose *compose)
823 GtkTextBuffer *buffer;
824 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
825 #if !GTK_CHECK_VERSION(2, 24, 0)
826 GdkColormap *cmap;
827 gboolean success[8];
828 int i;
829 GdkColor color[8];
830 #endif
832 buffer = gtk_text_view_get_buffer(text);
834 if (prefs_common.enable_color) {
835 /* grab the quote colors, converting from an int to a GdkColor */
836 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
837 &quote_color1);
838 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
839 &quote_color2);
840 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
841 &quote_color3);
842 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
843 &quote_bgcolor1);
844 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
845 &quote_bgcolor2);
846 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
847 &quote_bgcolor3);
848 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
849 &signature_color);
850 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
851 &uri_color);
852 } else {
853 signature_color = quote_color1 = quote_color2 = quote_color3 =
854 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
857 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
858 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
859 "foreground-gdk", &quote_color1,
860 "paragraph-background-gdk", &quote_bgcolor1,
861 NULL);
862 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
863 "foreground-gdk", &quote_color2,
864 "paragraph-background-gdk", &quote_bgcolor2,
865 NULL);
866 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
867 "foreground-gdk", &quote_color3,
868 "paragraph-background-gdk", &quote_bgcolor3,
869 NULL);
870 } else {
871 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
872 "foreground-gdk", &quote_color1,
873 NULL);
874 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
875 "foreground-gdk", &quote_color2,
876 NULL);
877 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
878 "foreground-gdk", &quote_color3,
879 NULL);
882 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
883 "foreground-gdk", &signature_color,
884 NULL);
886 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
887 "foreground-gdk", &uri_color,
888 NULL);
889 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
890 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
892 #if !GTK_CHECK_VERSION(2, 24, 0)
893 color[0] = quote_color1;
894 color[1] = quote_color2;
895 color[2] = quote_color3;
896 color[3] = quote_bgcolor1;
897 color[4] = quote_bgcolor2;
898 color[5] = quote_bgcolor3;
899 color[6] = signature_color;
900 color[7] = uri_color;
902 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
903 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
905 for (i = 0; i < 8; i++) {
906 if (success[i] == FALSE) {
907 g_warning("Compose: color allocation failed.");
908 quote_color1 = quote_color2 = quote_color3 =
909 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
910 signature_color = uri_color = black;
913 #endif
916 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
917 GList *attach_files)
919 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
922 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
924 return compose_generic_new(account, mailto, item, NULL, NULL);
927 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
929 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
932 #define SCROLL_TO_CURSOR(compose) { \
933 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
934 gtk_text_view_get_buffer( \
935 GTK_TEXT_VIEW(compose->text))); \
936 gtk_text_view_scroll_mark_onscreen( \
937 GTK_TEXT_VIEW(compose->text), \
938 cmark); \
941 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
943 GtkEditable *entry;
944 if (folderidentifier) {
945 #if !GTK_CHECK_VERSION(2, 24, 0)
946 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
947 #else
948 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
949 #endif
950 prefs_common.compose_save_to_history = add_history(
951 prefs_common.compose_save_to_history, folderidentifier);
952 #if !GTK_CHECK_VERSION(2, 24, 0)
953 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
954 prefs_common.compose_save_to_history);
955 #else
956 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
957 prefs_common.compose_save_to_history);
958 #endif
961 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
962 if (folderidentifier)
963 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
964 else
965 gtk_entry_set_text(GTK_ENTRY(entry), "");
968 static gchar *compose_get_save_to(Compose *compose)
970 GtkEditable *entry;
971 gchar *result = NULL;
972 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
973 result = gtk_editable_get_chars(entry, 0, -1);
975 if (result) {
976 #if !GTK_CHECK_VERSION(2, 24, 0)
977 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
978 #else
979 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
980 #endif
981 prefs_common.compose_save_to_history = add_history(
982 prefs_common.compose_save_to_history, result);
983 #if !GTK_CHECK_VERSION(2, 24, 0)
984 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
985 prefs_common.compose_save_to_history);
986 #else
987 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
988 prefs_common.compose_save_to_history);
989 #endif
991 return result;
994 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
995 GList *attach_files, GList *listAddress )
997 Compose *compose;
998 GtkTextView *textview;
999 GtkTextBuffer *textbuf;
1000 GtkTextIter iter;
1001 const gchar *subject_format = NULL;
1002 const gchar *body_format = NULL;
1003 gchar *mailto_from = NULL;
1004 PrefsAccount *mailto_account = NULL;
1005 MsgInfo* dummyinfo = NULL;
1006 gint cursor_pos = -1;
1007 MailField mfield = NO_FIELD_PRESENT;
1008 gchar* buf;
1009 GtkTextMark *mark;
1011 /* check if mailto defines a from */
1012 if (mailto && *mailto != '\0') {
1013 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1014 /* mailto defines a from, check if we can get account prefs from it,
1015 if not, the account prefs will be guessed using other ways, but we'll keep
1016 the from anyway */
1017 if (mailto_from) {
1018 mailto_account = account_find_from_address(mailto_from, TRUE);
1019 if (mailto_account == NULL) {
1020 gchar *tmp_from;
1021 Xstrdup_a(tmp_from, mailto_from, return NULL);
1022 extract_address(tmp_from);
1023 mailto_account = account_find_from_address(tmp_from, TRUE);
1026 if (mailto_account)
1027 account = mailto_account;
1030 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1031 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1032 account = account_find_from_id(item->prefs->default_account);
1034 /* if no account prefs set, fallback to the current one */
1035 if (!account) account = cur_account;
1036 cm_return_val_if_fail(account != NULL, NULL);
1038 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1040 /* override from name if mailto asked for it */
1041 if (mailto_from) {
1042 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1043 g_free(mailto_from);
1044 } else
1045 /* override from name according to folder properties */
1046 if (item && item->prefs &&
1047 item->prefs->compose_with_format &&
1048 item->prefs->compose_override_from_format &&
1049 *item->prefs->compose_override_from_format != '\0') {
1051 gchar *tmp = NULL;
1052 gchar *buf = NULL;
1054 dummyinfo = compose_msginfo_new_from_compose(compose);
1056 /* decode \-escape sequences in the internal representation of the quote format */
1057 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1058 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1060 #ifdef USE_ENCHANT
1061 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1062 compose->gtkaspell);
1063 #else
1064 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1065 #endif
1066 quote_fmt_scan_string(tmp);
1067 quote_fmt_parse();
1069 buf = quote_fmt_get_buffer();
1070 if (buf == NULL)
1071 alertpanel_error(_("New message From format error."));
1072 else
1073 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1074 quote_fmt_reset_vartable();
1076 g_free(tmp);
1079 compose->replyinfo = NULL;
1080 compose->fwdinfo = NULL;
1082 textview = GTK_TEXT_VIEW(compose->text);
1083 textbuf = gtk_text_view_get_buffer(textview);
1084 compose_create_tags(textview, compose);
1086 undo_block(compose->undostruct);
1087 #ifdef USE_ENCHANT
1088 compose_set_dictionaries_from_folder_prefs(compose, item);
1089 #endif
1091 if (account->auto_sig)
1092 compose_insert_sig(compose, FALSE);
1093 gtk_text_buffer_get_start_iter(textbuf, &iter);
1094 gtk_text_buffer_place_cursor(textbuf, &iter);
1096 if (account->protocol != A_NNTP) {
1097 if (mailto && *mailto != '\0') {
1098 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1100 } else {
1101 compose_set_folder_prefs(compose, item, TRUE);
1103 if (item && item->ret_rcpt) {
1104 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1106 } else {
1107 if (mailto && *mailto != '\0') {
1108 if (!strchr(mailto, '@'))
1109 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1110 else
1111 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1112 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1113 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1114 mfield = TO_FIELD_PRESENT;
1117 * CLAWS: just don't allow return receipt request, even if the user
1118 * may want to send an email. simple but foolproof.
1120 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1122 compose_add_field_list( compose, listAddress );
1124 if (item && item->prefs && item->prefs->compose_with_format) {
1125 subject_format = item->prefs->compose_subject_format;
1126 body_format = item->prefs->compose_body_format;
1127 } else if (account->compose_with_format) {
1128 subject_format = account->compose_subject_format;
1129 body_format = account->compose_body_format;
1130 } else if (prefs_common.compose_with_format) {
1131 subject_format = prefs_common.compose_subject_format;
1132 body_format = prefs_common.compose_body_format;
1135 if (subject_format || body_format) {
1137 if ( subject_format
1138 && *subject_format != '\0' )
1140 gchar *subject = NULL;
1141 gchar *tmp = NULL;
1142 gchar *buf = NULL;
1144 if (!dummyinfo)
1145 dummyinfo = compose_msginfo_new_from_compose(compose);
1147 /* decode \-escape sequences in the internal representation of the quote format */
1148 tmp = g_malloc(strlen(subject_format)+1);
1149 pref_get_unescaped_pref(tmp, subject_format);
1151 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1152 #ifdef USE_ENCHANT
1153 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1154 compose->gtkaspell);
1155 #else
1156 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1157 #endif
1158 quote_fmt_scan_string(tmp);
1159 quote_fmt_parse();
1161 buf = quote_fmt_get_buffer();
1162 if (buf == NULL)
1163 alertpanel_error(_("New message subject format error."));
1164 else
1165 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1166 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1167 quote_fmt_reset_vartable();
1169 g_free(subject);
1170 g_free(tmp);
1171 mfield = SUBJECT_FIELD_PRESENT;
1174 if ( body_format
1175 && *body_format != '\0' )
1177 GtkTextView *text;
1178 GtkTextBuffer *buffer;
1179 GtkTextIter start, end;
1180 gchar *tmp = NULL;
1182 if (!dummyinfo)
1183 dummyinfo = compose_msginfo_new_from_compose(compose);
1185 text = GTK_TEXT_VIEW(compose->text);
1186 buffer = gtk_text_view_get_buffer(text);
1187 gtk_text_buffer_get_start_iter(buffer, &start);
1188 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1189 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1191 compose_quote_fmt(compose, dummyinfo,
1192 body_format,
1193 NULL, tmp, FALSE, TRUE,
1194 _("The body of the \"New message\" template has an error at line %d."));
1195 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1196 quote_fmt_reset_vartable();
1198 g_free(tmp);
1199 #ifdef USE_ENCHANT
1200 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1201 gtkaspell_highlight_all(compose->gtkaspell);
1202 #endif
1203 mfield = BODY_FIELD_PRESENT;
1207 procmsg_msginfo_free( &dummyinfo );
1209 if (attach_files) {
1210 GList *curr;
1211 AttachInfo *ainfo;
1213 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1214 ainfo = (AttachInfo *) curr->data;
1215 compose_attach_append(compose, ainfo->file, ainfo->file,
1216 ainfo->content_type, ainfo->charset);
1220 compose_show_first_last_header(compose, TRUE);
1222 /* Set save folder */
1223 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1224 gchar *folderidentifier;
1226 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1227 folderidentifier = folder_item_get_identifier(item);
1228 compose_set_save_to(compose, folderidentifier);
1229 g_free(folderidentifier);
1232 /* Place cursor according to provided input (mfield) */
1233 switch (mfield) {
1234 case NO_FIELD_PRESENT:
1235 if (compose->header_last)
1236 gtk_widget_grab_focus(compose->header_last->entry);
1237 break;
1238 case TO_FIELD_PRESENT:
1239 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1240 if (buf) {
1241 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1242 g_free(buf);
1244 gtk_widget_grab_focus(compose->subject_entry);
1245 break;
1246 case SUBJECT_FIELD_PRESENT:
1247 textview = GTK_TEXT_VIEW(compose->text);
1248 if (!textview)
1249 break;
1250 textbuf = gtk_text_view_get_buffer(textview);
1251 if (!textbuf)
1252 break;
1253 mark = gtk_text_buffer_get_insert(textbuf);
1254 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1255 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1257 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1258 * only defers where it comes to the variable body
1259 * is not null. If no body is present compose->text
1260 * will be null in which case you cannot place the
1261 * cursor inside the component so. An empty component
1262 * is therefore created before placing the cursor
1264 case BODY_FIELD_PRESENT:
1265 cursor_pos = quote_fmt_get_cursor_pos();
1266 if (cursor_pos == -1)
1267 gtk_widget_grab_focus(compose->header_last->entry);
1268 else
1269 gtk_widget_grab_focus(compose->text);
1270 break;
1273 undo_unblock(compose->undostruct);
1275 if (prefs_common.auto_exteditor)
1276 compose_exec_ext_editor(compose);
1278 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1280 SCROLL_TO_CURSOR(compose);
1282 compose->modified = FALSE;
1283 compose_set_title(compose);
1285 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1287 return compose;
1290 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1291 gboolean override_pref, const gchar *system)
1293 const gchar *privacy = NULL;
1295 cm_return_if_fail(compose != NULL);
1296 cm_return_if_fail(account != NULL);
1298 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1299 return;
1301 if (account->default_privacy_system && strlen(account->default_privacy_system))
1302 privacy = account->default_privacy_system;
1303 else if (system)
1304 privacy = system;
1305 else {
1306 GSList *privacy_avail = privacy_get_system_ids();
1307 if (privacy_avail && g_slist_length(privacy_avail)) {
1308 privacy = (gchar *)(privacy_avail->data);
1311 if (privacy != NULL) {
1312 if (system) {
1313 g_free(compose->privacy_system);
1314 compose->privacy_system = NULL;
1315 g_free(compose->encdata);
1316 compose->encdata = NULL;
1318 if (compose->privacy_system == NULL)
1319 compose->privacy_system = g_strdup(privacy);
1320 else if (*(compose->privacy_system) == '\0') {
1321 g_free(compose->privacy_system);
1322 g_free(compose->encdata);
1323 compose->encdata = NULL;
1324 compose->privacy_system = g_strdup(privacy);
1326 compose_update_privacy_system_menu_item(compose, FALSE);
1327 compose_use_encryption(compose, TRUE);
1331 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1333 const gchar *privacy = NULL;
1335 if (account->default_privacy_system && strlen(account->default_privacy_system))
1336 privacy = account->default_privacy_system;
1337 else if (system)
1338 privacy = system;
1339 else {
1340 GSList *privacy_avail = privacy_get_system_ids();
1341 if (privacy_avail && g_slist_length(privacy_avail)) {
1342 privacy = (gchar *)(privacy_avail->data);
1346 if (privacy != NULL) {
1347 if (system) {
1348 g_free(compose->privacy_system);
1349 compose->privacy_system = NULL;
1350 g_free(compose->encdata);
1351 compose->encdata = NULL;
1353 if (compose->privacy_system == NULL)
1354 compose->privacy_system = g_strdup(privacy);
1355 compose_update_privacy_system_menu_item(compose, FALSE);
1356 compose_use_signing(compose, TRUE);
1360 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1362 MsgInfo *msginfo;
1363 guint list_len;
1364 Compose *compose = NULL;
1366 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1368 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1369 cm_return_val_if_fail(msginfo != NULL, NULL);
1371 list_len = g_slist_length(msginfo_list);
1373 switch (mode) {
1374 case COMPOSE_REPLY:
1375 case COMPOSE_REPLY_TO_ADDRESS:
1376 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1377 FALSE, prefs_common.default_reply_list, FALSE, body);
1378 break;
1379 case COMPOSE_REPLY_WITH_QUOTE:
1380 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1381 FALSE, prefs_common.default_reply_list, FALSE, body);
1382 break;
1383 case COMPOSE_REPLY_WITHOUT_QUOTE:
1384 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1385 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1386 break;
1387 case COMPOSE_REPLY_TO_SENDER:
1388 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1389 FALSE, FALSE, TRUE, body);
1390 break;
1391 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1392 compose = compose_followup_and_reply_to(msginfo,
1393 COMPOSE_QUOTE_CHECK,
1394 FALSE, FALSE, body);
1395 break;
1396 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1397 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1398 FALSE, FALSE, TRUE, body);
1399 break;
1400 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1401 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1402 FALSE, FALSE, TRUE, NULL);
1403 break;
1404 case COMPOSE_REPLY_TO_ALL:
1405 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1406 TRUE, FALSE, FALSE, body);
1407 break;
1408 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1409 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1410 TRUE, FALSE, FALSE, body);
1411 break;
1412 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1413 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1414 TRUE, FALSE, FALSE, NULL);
1415 break;
1416 case COMPOSE_REPLY_TO_LIST:
1417 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1418 FALSE, TRUE, FALSE, body);
1419 break;
1420 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1421 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1422 FALSE, TRUE, FALSE, body);
1423 break;
1424 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1425 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1426 FALSE, TRUE, FALSE, NULL);
1427 break;
1428 case COMPOSE_FORWARD:
1429 if (prefs_common.forward_as_attachment) {
1430 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1431 return compose;
1432 } else {
1433 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1434 return compose;
1436 break;
1437 case COMPOSE_FORWARD_INLINE:
1438 /* check if we reply to more than one Message */
1439 if (list_len == 1) {
1440 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1441 break;
1443 /* more messages FALL THROUGH */
1444 case COMPOSE_FORWARD_AS_ATTACH:
1445 compose = compose_forward_multiple(NULL, msginfo_list);
1446 break;
1447 case COMPOSE_REDIRECT:
1448 compose = compose_redirect(NULL, msginfo, FALSE);
1449 break;
1450 default:
1451 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1454 if (compose == NULL) {
1455 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1456 return NULL;
1459 compose->rmode = mode;
1460 switch (compose->rmode) {
1461 case COMPOSE_REPLY:
1462 case COMPOSE_REPLY_WITH_QUOTE:
1463 case COMPOSE_REPLY_WITHOUT_QUOTE:
1464 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1465 debug_print("reply mode Normal\n");
1466 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1467 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1468 break;
1469 case COMPOSE_REPLY_TO_SENDER:
1470 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1471 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1472 debug_print("reply mode Sender\n");
1473 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1474 break;
1475 case COMPOSE_REPLY_TO_ALL:
1476 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1477 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1478 debug_print("reply mode All\n");
1479 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1480 break;
1481 case COMPOSE_REPLY_TO_LIST:
1482 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1483 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1484 debug_print("reply mode List\n");
1485 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1486 break;
1487 case COMPOSE_REPLY_TO_ADDRESS:
1488 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1489 break;
1490 default:
1491 break;
1493 return compose;
1496 static Compose *compose_reply(MsgInfo *msginfo,
1497 ComposeQuoteMode quote_mode,
1498 gboolean to_all,
1499 gboolean to_ml,
1500 gboolean to_sender,
1501 const gchar *body)
1503 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1504 to_sender, FALSE, body);
1507 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1508 ComposeQuoteMode quote_mode,
1509 gboolean to_all,
1510 gboolean to_sender,
1511 const gchar *body)
1513 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1514 to_sender, TRUE, body);
1517 static void compose_extract_original_charset(Compose *compose)
1519 MsgInfo *info = NULL;
1520 if (compose->replyinfo) {
1521 info = compose->replyinfo;
1522 } else if (compose->fwdinfo) {
1523 info = compose->fwdinfo;
1524 } else if (compose->targetinfo) {
1525 info = compose->targetinfo;
1527 if (info) {
1528 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1529 MimeInfo *partinfo = mimeinfo;
1530 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1531 partinfo = procmime_mimeinfo_next(partinfo);
1532 if (partinfo) {
1533 compose->orig_charset =
1534 g_strdup(procmime_mimeinfo_get_parameter(
1535 partinfo, "charset"));
1537 procmime_mimeinfo_free_all(&mimeinfo);
1541 #define SIGNAL_BLOCK(buffer) { \
1542 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1543 G_CALLBACK(compose_changed_cb), \
1544 compose); \
1545 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1546 G_CALLBACK(text_inserted), \
1547 compose); \
1550 #define SIGNAL_UNBLOCK(buffer) { \
1551 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1552 G_CALLBACK(compose_changed_cb), \
1553 compose); \
1554 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1555 G_CALLBACK(text_inserted), \
1556 compose); \
1559 static Compose *compose_generic_reply(MsgInfo *msginfo,
1560 ComposeQuoteMode quote_mode,
1561 gboolean to_all, gboolean to_ml,
1562 gboolean to_sender,
1563 gboolean followup_and_reply_to,
1564 const gchar *body)
1566 Compose *compose;
1567 PrefsAccount *account = NULL;
1568 GtkTextView *textview;
1569 GtkTextBuffer *textbuf;
1570 gboolean quote = FALSE;
1571 const gchar *qmark = NULL;
1572 const gchar *body_fmt = NULL;
1573 gchar *s_system = NULL;
1574 START_TIMING("");
1575 cm_return_val_if_fail(msginfo != NULL, NULL);
1576 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1578 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1580 cm_return_val_if_fail(account != NULL, NULL);
1582 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1584 compose->updating = TRUE;
1586 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1587 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1589 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1590 if (!compose->replyinfo)
1591 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1593 compose_extract_original_charset(compose);
1595 if (msginfo->folder && msginfo->folder->ret_rcpt)
1596 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1598 /* Set save folder */
1599 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1600 gchar *folderidentifier;
1602 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1603 folderidentifier = folder_item_get_identifier(msginfo->folder);
1604 compose_set_save_to(compose, folderidentifier);
1605 g_free(folderidentifier);
1608 if (compose_parse_header(compose, msginfo) < 0) {
1609 compose->updating = FALSE;
1610 compose_destroy(compose);
1611 return NULL;
1614 /* override from name according to folder properties */
1615 if (msginfo->folder && msginfo->folder->prefs &&
1616 msginfo->folder->prefs->reply_with_format &&
1617 msginfo->folder->prefs->reply_override_from_format &&
1618 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1620 gchar *tmp = NULL;
1621 gchar *buf = NULL;
1623 /* decode \-escape sequences in the internal representation of the quote format */
1624 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1625 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1627 #ifdef USE_ENCHANT
1628 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1629 compose->gtkaspell);
1630 #else
1631 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1632 #endif
1633 quote_fmt_scan_string(tmp);
1634 quote_fmt_parse();
1636 buf = quote_fmt_get_buffer();
1637 if (buf == NULL)
1638 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1639 else
1640 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1641 quote_fmt_reset_vartable();
1643 g_free(tmp);
1646 textview = (GTK_TEXT_VIEW(compose->text));
1647 textbuf = gtk_text_view_get_buffer(textview);
1648 compose_create_tags(textview, compose);
1650 undo_block(compose->undostruct);
1651 #ifdef USE_ENCHANT
1652 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1653 gtkaspell_block_check(compose->gtkaspell);
1654 #endif
1656 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1657 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1658 /* use the reply format of folder (if enabled), or the account's one
1659 (if enabled) or fallback to the global reply format, which is always
1660 enabled (even if empty), and use the relevant quotemark */
1661 quote = TRUE;
1662 if (msginfo->folder && msginfo->folder->prefs &&
1663 msginfo->folder->prefs->reply_with_format) {
1664 qmark = msginfo->folder->prefs->reply_quotemark;
1665 body_fmt = msginfo->folder->prefs->reply_body_format;
1667 } else if (account->reply_with_format) {
1668 qmark = account->reply_quotemark;
1669 body_fmt = account->reply_body_format;
1671 } else {
1672 qmark = prefs_common.quotemark;
1673 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1674 body_fmt = gettext(prefs_common.quotefmt);
1675 else
1676 body_fmt = "";
1680 if (quote) {
1681 /* empty quotemark is not allowed */
1682 if (qmark == NULL || *qmark == '\0')
1683 qmark = "> ";
1684 compose_quote_fmt(compose, compose->replyinfo,
1685 body_fmt, qmark, body, FALSE, TRUE,
1686 _("The body of the \"Reply\" template has an error at line %d."));
1687 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1688 quote_fmt_reset_vartable();
1691 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1692 compose_force_encryption(compose, account, FALSE, s_system);
1695 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1696 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1697 compose_force_signing(compose, account, s_system);
1699 g_free(s_system);
1701 SIGNAL_BLOCK(textbuf);
1703 if (account->auto_sig)
1704 compose_insert_sig(compose, FALSE);
1706 compose_wrap_all(compose);
1708 #ifdef USE_ENCHANT
1709 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1710 gtkaspell_highlight_all(compose->gtkaspell);
1711 gtkaspell_unblock_check(compose->gtkaspell);
1712 #endif
1713 SIGNAL_UNBLOCK(textbuf);
1715 gtk_widget_grab_focus(compose->text);
1717 undo_unblock(compose->undostruct);
1719 if (prefs_common.auto_exteditor)
1720 compose_exec_ext_editor(compose);
1722 compose->modified = FALSE;
1723 compose_set_title(compose);
1725 compose->updating = FALSE;
1726 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1727 SCROLL_TO_CURSOR(compose);
1729 if (compose->deferred_destroy) {
1730 compose_destroy(compose);
1731 return NULL;
1733 END_TIMING();
1735 return compose;
1738 #define INSERT_FW_HEADER(var, hdr) \
1739 if (msginfo->var && *msginfo->var) { \
1740 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1741 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1742 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1745 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1746 gboolean as_attach, const gchar *body,
1747 gboolean no_extedit,
1748 gboolean batch)
1750 Compose *compose;
1751 GtkTextView *textview;
1752 GtkTextBuffer *textbuf;
1753 gint cursor_pos = -1;
1754 ComposeMode mode;
1756 cm_return_val_if_fail(msginfo != NULL, NULL);
1757 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1759 if (!account &&
1760 !(account = compose_guess_forward_account_from_msginfo
1761 (msginfo)))
1762 account = cur_account;
1764 if (!prefs_common.forward_as_attachment)
1765 mode = COMPOSE_FORWARD_INLINE;
1766 else
1767 mode = COMPOSE_FORWARD;
1768 compose = compose_create(account, msginfo->folder, mode, batch);
1770 compose->updating = TRUE;
1771 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1772 if (!compose->fwdinfo)
1773 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1775 compose_extract_original_charset(compose);
1777 if (msginfo->subject && *msginfo->subject) {
1778 gchar *buf, *buf2, *p;
1780 buf = p = g_strdup(msginfo->subject);
1781 p += subject_get_prefix_length(p);
1782 memmove(buf, p, strlen(p) + 1);
1784 buf2 = g_strdup_printf("Fw: %s", buf);
1785 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1787 g_free(buf);
1788 g_free(buf2);
1791 /* override from name according to folder properties */
1792 if (msginfo->folder && msginfo->folder->prefs &&
1793 msginfo->folder->prefs->forward_with_format &&
1794 msginfo->folder->prefs->forward_override_from_format &&
1795 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1797 gchar *tmp = NULL;
1798 gchar *buf = NULL;
1799 MsgInfo *full_msginfo = NULL;
1801 if (!as_attach)
1802 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1803 if (!full_msginfo)
1804 full_msginfo = procmsg_msginfo_copy(msginfo);
1806 /* decode \-escape sequences in the internal representation of the quote format */
1807 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1808 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1810 #ifdef USE_ENCHANT
1811 gtkaspell_block_check(compose->gtkaspell);
1812 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1813 compose->gtkaspell);
1814 #else
1815 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1816 #endif
1817 quote_fmt_scan_string(tmp);
1818 quote_fmt_parse();
1820 buf = quote_fmt_get_buffer();
1821 if (buf == NULL)
1822 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1823 else
1824 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1825 quote_fmt_reset_vartable();
1827 g_free(tmp);
1828 procmsg_msginfo_free(&full_msginfo);
1831 textview = GTK_TEXT_VIEW(compose->text);
1832 textbuf = gtk_text_view_get_buffer(textview);
1833 compose_create_tags(textview, compose);
1835 undo_block(compose->undostruct);
1836 if (as_attach) {
1837 gchar *msgfile;
1839 msgfile = procmsg_get_message_file(msginfo);
1840 if (!is_file_exist(msgfile))
1841 g_warning("%s: file does not exist", msgfile);
1842 else
1843 compose_attach_append(compose, msgfile, msgfile,
1844 "message/rfc822", NULL);
1846 g_free(msgfile);
1847 } else {
1848 const gchar *qmark = NULL;
1849 const gchar *body_fmt = NULL;
1850 MsgInfo *full_msginfo;
1852 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1853 if (!full_msginfo)
1854 full_msginfo = procmsg_msginfo_copy(msginfo);
1856 /* use the forward format of folder (if enabled), or the account's one
1857 (if enabled) or fallback to the global forward format, which is always
1858 enabled (even if empty), and use the relevant quotemark */
1859 if (msginfo->folder && msginfo->folder->prefs &&
1860 msginfo->folder->prefs->forward_with_format) {
1861 qmark = msginfo->folder->prefs->forward_quotemark;
1862 body_fmt = msginfo->folder->prefs->forward_body_format;
1864 } else if (account->forward_with_format) {
1865 qmark = account->forward_quotemark;
1866 body_fmt = account->forward_body_format;
1868 } else {
1869 qmark = prefs_common.fw_quotemark;
1870 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1871 body_fmt = gettext(prefs_common.fw_quotefmt);
1872 else
1873 body_fmt = "";
1876 /* empty quotemark is not allowed */
1877 if (qmark == NULL || *qmark == '\0')
1878 qmark = "> ";
1880 compose_quote_fmt(compose, full_msginfo,
1881 body_fmt, qmark, body, FALSE, TRUE,
1882 _("The body of the \"Forward\" template has an error at line %d."));
1883 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1884 quote_fmt_reset_vartable();
1885 compose_attach_parts(compose, msginfo);
1887 procmsg_msginfo_free(&full_msginfo);
1890 SIGNAL_BLOCK(textbuf);
1892 if (account->auto_sig)
1893 compose_insert_sig(compose, FALSE);
1895 compose_wrap_all(compose);
1897 #ifdef USE_ENCHANT
1898 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1899 gtkaspell_highlight_all(compose->gtkaspell);
1900 gtkaspell_unblock_check(compose->gtkaspell);
1901 #endif
1902 SIGNAL_UNBLOCK(textbuf);
1904 cursor_pos = quote_fmt_get_cursor_pos();
1905 if (cursor_pos == -1)
1906 gtk_widget_grab_focus(compose->header_last->entry);
1907 else
1908 gtk_widget_grab_focus(compose->text);
1910 if (!no_extedit && prefs_common.auto_exteditor)
1911 compose_exec_ext_editor(compose);
1913 /*save folder*/
1914 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1915 gchar *folderidentifier;
1917 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1918 folderidentifier = folder_item_get_identifier(msginfo->folder);
1919 compose_set_save_to(compose, folderidentifier);
1920 g_free(folderidentifier);
1923 undo_unblock(compose->undostruct);
1925 compose->modified = FALSE;
1926 compose_set_title(compose);
1928 compose->updating = FALSE;
1929 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1930 SCROLL_TO_CURSOR(compose);
1932 if (compose->deferred_destroy) {
1933 compose_destroy(compose);
1934 return NULL;
1937 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1939 return compose;
1942 #undef INSERT_FW_HEADER
1944 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1946 Compose *compose;
1947 GtkTextView *textview;
1948 GtkTextBuffer *textbuf;
1949 GtkTextIter iter;
1950 GSList *msginfo;
1951 gchar *msgfile;
1952 gboolean single_mail = TRUE;
1954 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1956 if (g_slist_length(msginfo_list) > 1)
1957 single_mail = FALSE;
1959 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1960 if (((MsgInfo *)msginfo->data)->folder == NULL)
1961 return NULL;
1963 /* guess account from first selected message */
1964 if (!account &&
1965 !(account = compose_guess_forward_account_from_msginfo
1966 (msginfo_list->data)))
1967 account = cur_account;
1969 cm_return_val_if_fail(account != NULL, NULL);
1971 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1972 if (msginfo->data) {
1973 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1974 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1978 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1979 g_warning("no msginfo_list");
1980 return NULL;
1983 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1985 compose->updating = TRUE;
1987 /* override from name according to folder properties */
1988 if (msginfo_list->data) {
1989 MsgInfo *msginfo = msginfo_list->data;
1991 if (msginfo->folder && msginfo->folder->prefs &&
1992 msginfo->folder->prefs->forward_with_format &&
1993 msginfo->folder->prefs->forward_override_from_format &&
1994 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1996 gchar *tmp = NULL;
1997 gchar *buf = NULL;
1999 /* decode \-escape sequences in the internal representation of the quote format */
2000 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2001 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2003 #ifdef USE_ENCHANT
2004 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2005 compose->gtkaspell);
2006 #else
2007 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2008 #endif
2009 quote_fmt_scan_string(tmp);
2010 quote_fmt_parse();
2012 buf = quote_fmt_get_buffer();
2013 if (buf == NULL)
2014 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2015 else
2016 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2017 quote_fmt_reset_vartable();
2019 g_free(tmp);
2023 textview = GTK_TEXT_VIEW(compose->text);
2024 textbuf = gtk_text_view_get_buffer(textview);
2025 compose_create_tags(textview, compose);
2027 undo_block(compose->undostruct);
2028 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2029 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2031 if (!is_file_exist(msgfile))
2032 g_warning("%s: file does not exist", msgfile);
2033 else
2034 compose_attach_append(compose, msgfile, msgfile,
2035 "message/rfc822", NULL);
2036 g_free(msgfile);
2039 if (single_mail) {
2040 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2041 if (info->subject && *info->subject) {
2042 gchar *buf, *buf2, *p;
2044 buf = p = g_strdup(info->subject);
2045 p += subject_get_prefix_length(p);
2046 memmove(buf, p, strlen(p) + 1);
2048 buf2 = g_strdup_printf("Fw: %s", buf);
2049 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2051 g_free(buf);
2052 g_free(buf2);
2054 } else {
2055 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2056 _("Fw: multiple emails"));
2059 SIGNAL_BLOCK(textbuf);
2061 if (account->auto_sig)
2062 compose_insert_sig(compose, FALSE);
2064 compose_wrap_all(compose);
2066 SIGNAL_UNBLOCK(textbuf);
2068 gtk_text_buffer_get_start_iter(textbuf, &iter);
2069 gtk_text_buffer_place_cursor(textbuf, &iter);
2071 if (prefs_common.auto_exteditor)
2072 compose_exec_ext_editor(compose);
2074 gtk_widget_grab_focus(compose->header_last->entry);
2075 undo_unblock(compose->undostruct);
2076 compose->modified = FALSE;
2077 compose_set_title(compose);
2079 compose->updating = FALSE;
2080 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2081 SCROLL_TO_CURSOR(compose);
2083 if (compose->deferred_destroy) {
2084 compose_destroy(compose);
2085 return NULL;
2088 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2090 return compose;
2093 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2095 GtkTextIter start = *iter;
2096 GtkTextIter end_iter;
2097 int start_pos = gtk_text_iter_get_offset(&start);
2098 gchar *str = NULL;
2099 if (!compose->account->sig_sep)
2100 return FALSE;
2102 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2103 start_pos+strlen(compose->account->sig_sep));
2105 /* check sig separator */
2106 str = gtk_text_iter_get_text(&start, &end_iter);
2107 if (!strcmp(str, compose->account->sig_sep)) {
2108 gchar *tmp = NULL;
2109 /* check end of line (\n) */
2110 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2111 start_pos+strlen(compose->account->sig_sep));
2112 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2113 start_pos+strlen(compose->account->sig_sep)+1);
2114 tmp = gtk_text_iter_get_text(&start, &end_iter);
2115 if (!strcmp(tmp,"\n")) {
2116 g_free(str);
2117 g_free(tmp);
2118 return TRUE;
2120 g_free(tmp);
2122 g_free(str);
2124 return FALSE;
2127 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2129 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2130 Compose *compose = (Compose *)data;
2131 FolderItem *old_item = NULL;
2132 FolderItem *new_item = NULL;
2133 gchar *old_id, *new_id;
2135 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2136 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2137 return FALSE;
2139 old_item = hookdata->item;
2140 new_item = hookdata->item2;
2142 old_id = folder_item_get_identifier(old_item);
2143 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2145 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2146 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2147 compose->targetinfo->folder = new_item;
2150 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2151 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2152 compose->replyinfo->folder = new_item;
2155 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2156 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2157 compose->fwdinfo->folder = new_item;
2160 g_free(old_id);
2161 g_free(new_id);
2162 return FALSE;
2165 static void compose_colorize_signature(Compose *compose)
2167 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2168 GtkTextIter iter;
2169 GtkTextIter end_iter;
2170 gtk_text_buffer_get_start_iter(buffer, &iter);
2171 while (gtk_text_iter_forward_line(&iter))
2172 if (compose_is_sig_separator(compose, buffer, &iter)) {
2173 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2174 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2178 #define BLOCK_WRAP() { \
2179 prev_autowrap = compose->autowrap; \
2180 buffer = gtk_text_view_get_buffer( \
2181 GTK_TEXT_VIEW(compose->text)); \
2182 compose->autowrap = FALSE; \
2184 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2185 G_CALLBACK(compose_changed_cb), \
2186 compose); \
2187 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2188 G_CALLBACK(text_inserted), \
2189 compose); \
2191 #define UNBLOCK_WRAP() { \
2192 compose->autowrap = prev_autowrap; \
2193 if (compose->autowrap) { \
2194 gint old = compose->draft_timeout_tag; \
2195 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2196 compose_wrap_all(compose); \
2197 compose->draft_timeout_tag = old; \
2200 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2201 G_CALLBACK(compose_changed_cb), \
2202 compose); \
2203 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2204 G_CALLBACK(text_inserted), \
2205 compose); \
2208 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2210 Compose *compose = NULL;
2211 PrefsAccount *account = NULL;
2212 GtkTextView *textview;
2213 GtkTextBuffer *textbuf;
2214 GtkTextMark *mark;
2215 GtkTextIter iter;
2216 FILE *fp;
2217 gchar buf[BUFFSIZE];
2218 gboolean use_signing = FALSE;
2219 gboolean use_encryption = FALSE;
2220 gchar *privacy_system = NULL;
2221 int priority = PRIORITY_NORMAL;
2222 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2223 gboolean autowrap = prefs_common.autowrap;
2224 gboolean autoindent = prefs_common.auto_indent;
2225 HeaderEntry *manual_headers = NULL;
2227 cm_return_val_if_fail(msginfo != NULL, NULL);
2228 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2230 if (compose_put_existing_to_front(msginfo)) {
2231 return NULL;
2234 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2235 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2236 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2237 gchar queueheader_buf[BUFFSIZE];
2238 gint id, param;
2240 /* Select Account from queue headers */
2241 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2242 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2243 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2244 account = account_find_from_id(id);
2246 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2247 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2248 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2249 account = account_find_from_id(id);
2251 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2252 sizeof(queueheader_buf), "NAID:")) {
2253 id = atoi(&queueheader_buf[strlen("NAID:")]);
2254 account = account_find_from_id(id);
2256 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2257 sizeof(queueheader_buf), "MAID:")) {
2258 id = atoi(&queueheader_buf[strlen("MAID:")]);
2259 account = account_find_from_id(id);
2261 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2262 sizeof(queueheader_buf), "S:")) {
2263 account = account_find_from_address(queueheader_buf, FALSE);
2265 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2266 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2267 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2268 use_signing = param;
2271 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2272 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2273 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2274 use_signing = param;
2277 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2278 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2279 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2280 use_encryption = param;
2282 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2283 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2284 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2285 use_encryption = param;
2287 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2288 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2289 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2290 autowrap = param;
2292 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2293 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2294 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2295 autoindent = param;
2297 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2298 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2299 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2301 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2302 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2303 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2305 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2306 sizeof(queueheader_buf), "X-Priority: ")) {
2307 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2308 priority = param;
2310 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2311 sizeof(queueheader_buf), "RMID:")) {
2312 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2313 if (tokens[0] && tokens[1] && tokens[2]) {
2314 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2315 if (orig_item != NULL) {
2316 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2319 g_strfreev(tokens);
2321 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2322 sizeof(queueheader_buf), "FMID:")) {
2323 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2324 if (tokens[0] && tokens[1] && tokens[2]) {
2325 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2326 if (orig_item != NULL) {
2327 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2330 g_strfreev(tokens);
2332 /* Get manual headers */
2333 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2334 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2335 if (*listmh != '\0') {
2336 debug_print("Got manual headers: %s\n", listmh);
2337 manual_headers = procheader_entries_from_str(listmh);
2339 g_free(listmh);
2341 } else {
2342 account = msginfo->folder->folder->account;
2345 if (!account && prefs_common.reedit_account_autosel) {
2346 gchar from[BUFFSIZE];
2347 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2348 extract_address(from);
2349 account = account_find_from_address(from, FALSE);
2352 if (!account) {
2353 account = cur_account;
2355 cm_return_val_if_fail(account != NULL, NULL);
2357 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2359 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2360 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2361 compose->autowrap = autowrap;
2362 compose->replyinfo = replyinfo;
2363 compose->fwdinfo = fwdinfo;
2365 compose->updating = TRUE;
2366 compose->priority = priority;
2368 if (privacy_system != NULL) {
2369 compose->privacy_system = privacy_system;
2370 compose_use_signing(compose, use_signing);
2371 compose_use_encryption(compose, use_encryption);
2372 compose_update_privacy_system_menu_item(compose, FALSE);
2373 } else {
2374 activate_privacy_system(compose, account, FALSE);
2377 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2379 compose_extract_original_charset(compose);
2381 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2382 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2383 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2384 gchar queueheader_buf[BUFFSIZE];
2386 /* Set message save folder */
2387 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2388 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2389 compose_set_save_to(compose, &queueheader_buf[4]);
2391 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2392 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2393 if (active) {
2394 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2399 if (compose_parse_header(compose, msginfo) < 0) {
2400 compose->updating = FALSE;
2401 compose_destroy(compose);
2402 return NULL;
2404 compose_reedit_set_entry(compose, msginfo);
2406 textview = GTK_TEXT_VIEW(compose->text);
2407 textbuf = gtk_text_view_get_buffer(textview);
2408 compose_create_tags(textview, compose);
2410 mark = gtk_text_buffer_get_insert(textbuf);
2411 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2413 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2414 G_CALLBACK(compose_changed_cb),
2415 compose);
2417 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2418 fp = procmime_get_first_encrypted_text_content(msginfo);
2419 if (fp) {
2420 compose_force_encryption(compose, account, TRUE, NULL);
2422 } else {
2423 fp = procmime_get_first_text_content(msginfo);
2425 if (fp == NULL) {
2426 g_warning("Can't get text part");
2429 if (fp != NULL) {
2430 gboolean prev_autowrap;
2431 GtkTextBuffer *buffer;
2432 BLOCK_WRAP();
2433 while (fgets(buf, sizeof(buf), fp) != NULL) {
2434 strcrchomp(buf);
2435 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2437 UNBLOCK_WRAP();
2438 fclose(fp);
2441 compose_attach_parts(compose, msginfo);
2443 compose_colorize_signature(compose);
2445 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2446 G_CALLBACK(compose_changed_cb),
2447 compose);
2449 if (manual_headers != NULL) {
2450 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2451 procheader_entries_free(manual_headers);
2452 compose->updating = FALSE;
2453 compose_destroy(compose);
2454 return NULL;
2456 procheader_entries_free(manual_headers);
2459 gtk_widget_grab_focus(compose->text);
2461 if (prefs_common.auto_exteditor) {
2462 compose_exec_ext_editor(compose);
2464 compose->modified = FALSE;
2465 compose_set_title(compose);
2467 compose->updating = FALSE;
2468 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2469 SCROLL_TO_CURSOR(compose);
2471 if (compose->deferred_destroy) {
2472 compose_destroy(compose);
2473 return NULL;
2476 compose->sig_str = account_get_signature_str(compose->account);
2478 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2480 return compose;
2483 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2484 gboolean batch)
2486 Compose *compose;
2487 gchar *filename;
2488 FolderItem *item;
2490 cm_return_val_if_fail(msginfo != NULL, NULL);
2492 if (!account)
2493 account = account_get_reply_account(msginfo,
2494 prefs_common.reply_account_autosel);
2495 cm_return_val_if_fail(account != NULL, NULL);
2497 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2499 compose->updating = TRUE;
2501 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2502 compose->replyinfo = NULL;
2503 compose->fwdinfo = NULL;
2505 compose_show_first_last_header(compose, TRUE);
2507 gtk_widget_grab_focus(compose->header_last->entry);
2509 filename = procmsg_get_message_file(msginfo);
2511 if (filename == NULL) {
2512 compose->updating = FALSE;
2513 compose_destroy(compose);
2515 return NULL;
2518 compose->redirect_filename = filename;
2520 /* Set save folder */
2521 item = msginfo->folder;
2522 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2523 gchar *folderidentifier;
2525 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2526 folderidentifier = folder_item_get_identifier(item);
2527 compose_set_save_to(compose, folderidentifier);
2528 g_free(folderidentifier);
2531 compose_attach_parts(compose, msginfo);
2533 if (msginfo->subject)
2534 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2535 msginfo->subject);
2536 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2538 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2539 _("The body of the \"Redirect\" template has an error at line %d."));
2540 quote_fmt_reset_vartable();
2541 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2543 compose_colorize_signature(compose);
2546 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2547 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2548 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2550 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2551 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2552 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2553 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2554 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2555 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2556 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2557 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2558 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2560 if (compose->toolbar->draft_btn)
2561 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2562 if (compose->toolbar->insert_btn)
2563 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2564 if (compose->toolbar->attach_btn)
2565 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2566 if (compose->toolbar->sig_btn)
2567 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2568 if (compose->toolbar->exteditor_btn)
2569 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2570 if (compose->toolbar->linewrap_current_btn)
2571 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2572 if (compose->toolbar->linewrap_all_btn)
2573 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2575 compose->modified = FALSE;
2576 compose_set_title(compose);
2577 compose->updating = FALSE;
2578 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2579 SCROLL_TO_CURSOR(compose);
2581 if (compose->deferred_destroy) {
2582 compose_destroy(compose);
2583 return NULL;
2586 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2588 return compose;
2591 const GList *compose_get_compose_list(void)
2593 return compose_list;
2596 void compose_entry_append(Compose *compose, const gchar *address,
2597 ComposeEntryType type, ComposePrefType pref_type)
2599 const gchar *header;
2600 gchar *cur, *begin;
2601 gboolean in_quote = FALSE;
2602 if (!address || *address == '\0') return;
2604 switch (type) {
2605 case COMPOSE_CC:
2606 header = N_("Cc:");
2607 break;
2608 case COMPOSE_BCC:
2609 header = N_("Bcc:");
2610 break;
2611 case COMPOSE_REPLYTO:
2612 header = N_("Reply-To:");
2613 break;
2614 case COMPOSE_NEWSGROUPS:
2615 header = N_("Newsgroups:");
2616 break;
2617 case COMPOSE_FOLLOWUPTO:
2618 header = N_( "Followup-To:");
2619 break;
2620 case COMPOSE_INREPLYTO:
2621 header = N_( "In-Reply-To:");
2622 break;
2623 case COMPOSE_TO:
2624 default:
2625 header = N_("To:");
2626 break;
2628 header = prefs_common_translated_header_name(header);
2630 cur = begin = (gchar *)address;
2632 /* we separate the line by commas, but not if we're inside a quoted
2633 * string */
2634 while (*cur != '\0') {
2635 if (*cur == '"')
2636 in_quote = !in_quote;
2637 if (*cur == ',' && !in_quote) {
2638 gchar *tmp = g_strdup(begin);
2639 gchar *o_tmp = tmp;
2640 tmp[cur-begin]='\0';
2641 cur++;
2642 begin = cur;
2643 while (*tmp == ' ' || *tmp == '\t')
2644 tmp++;
2645 compose_add_header_entry(compose, header, tmp, pref_type);
2646 g_free(o_tmp);
2647 continue;
2649 cur++;
2651 if (begin < cur) {
2652 gchar *tmp = g_strdup(begin);
2653 gchar *o_tmp = tmp;
2654 tmp[cur-begin]='\0';
2655 while (*tmp == ' ' || *tmp == '\t')
2656 tmp++;
2657 compose_add_header_entry(compose, header, tmp, pref_type);
2658 g_free(o_tmp);
2662 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2664 #if !GTK_CHECK_VERSION(3, 0, 0)
2665 static GdkColor yellow;
2666 static GdkColor black;
2667 static gboolean yellow_initialised = FALSE;
2668 #else
2669 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2670 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2671 #endif
2672 GSList *h_list;
2673 GtkEntry *entry;
2675 #if !GTK_CHECK_VERSION(3, 0, 0)
2676 if (!yellow_initialised) {
2677 gdk_color_parse("#f5f6be", &yellow);
2678 gdk_color_parse("#000000", &black);
2679 yellow_initialised = gdk_colormap_alloc_color(
2680 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2681 yellow_initialised &= gdk_colormap_alloc_color(
2682 gdk_colormap_get_system(), &black, FALSE, TRUE);
2684 #endif
2686 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2687 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2688 if (gtk_entry_get_text(entry) &&
2689 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2690 #if !GTK_CHECK_VERSION(3, 0, 0)
2691 if (yellow_initialised) {
2692 #endif
2693 gtk_widget_modify_base(
2694 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2695 GTK_STATE_NORMAL, &yellow);
2696 gtk_widget_modify_text(
2697 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2698 GTK_STATE_NORMAL, &black);
2699 #if !GTK_CHECK_VERSION(3, 0, 0)
2701 #endif
2706 void compose_toolbar_cb(gint action, gpointer data)
2708 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2709 Compose *compose = (Compose*)toolbar_item->parent;
2711 cm_return_if_fail(compose != NULL);
2713 switch(action) {
2714 case A_SEND:
2715 compose_send_cb(NULL, compose);
2716 break;
2717 case A_SENDL:
2718 compose_send_later_cb(NULL, compose);
2719 break;
2720 case A_DRAFT:
2721 compose_draft(compose, COMPOSE_QUIT_EDITING);
2722 break;
2723 case A_INSERT:
2724 compose_insert_file_cb(NULL, compose);
2725 break;
2726 case A_ATTACH:
2727 compose_attach_cb(NULL, compose);
2728 break;
2729 case A_SIG:
2730 compose_insert_sig(compose, FALSE);
2731 break;
2732 case A_REP_SIG:
2733 compose_insert_sig(compose, TRUE);
2734 break;
2735 case A_EXTEDITOR:
2736 compose_ext_editor_cb(NULL, compose);
2737 break;
2738 case A_LINEWRAP_CURRENT:
2739 compose_beautify_paragraph(compose, NULL, TRUE);
2740 break;
2741 case A_LINEWRAP_ALL:
2742 compose_wrap_all_full(compose, TRUE);
2743 break;
2744 case A_ADDRBOOK:
2745 compose_address_cb(NULL, compose);
2746 break;
2747 #ifdef USE_ENCHANT
2748 case A_CHECK_SPELLING:
2749 compose_check_all(NULL, compose);
2750 break;
2751 #endif
2752 default:
2753 break;
2757 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2759 gchar *to = NULL;
2760 gchar *cc = NULL;
2761 gchar *bcc = NULL;
2762 gchar *subject = NULL;
2763 gchar *body = NULL;
2764 gchar *temp = NULL;
2765 gsize len = 0;
2766 gchar **attach = NULL;
2767 gchar *inreplyto = NULL;
2768 MailField mfield = NO_FIELD_PRESENT;
2770 /* get mailto parts but skip from */
2771 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2773 if (to) {
2774 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2775 mfield = TO_FIELD_PRESENT;
2777 if (cc)
2778 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2779 if (bcc)
2780 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2781 if (subject) {
2782 if (!g_utf8_validate (subject, -1, NULL)) {
2783 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2784 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2785 g_free(temp);
2786 } else {
2787 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2789 mfield = SUBJECT_FIELD_PRESENT;
2791 if (body) {
2792 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2793 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2794 GtkTextMark *mark;
2795 GtkTextIter iter;
2796 gboolean prev_autowrap = compose->autowrap;
2798 compose->autowrap = FALSE;
2800 mark = gtk_text_buffer_get_insert(buffer);
2801 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2803 if (!g_utf8_validate (body, -1, NULL)) {
2804 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2805 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2806 g_free(temp);
2807 } else {
2808 gtk_text_buffer_insert(buffer, &iter, body, -1);
2810 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2812 compose->autowrap = prev_autowrap;
2813 if (compose->autowrap)
2814 compose_wrap_all(compose);
2815 mfield = BODY_FIELD_PRESENT;
2818 if (attach) {
2819 gint i = 0, att = 0;
2820 gchar *warn_files = NULL;
2821 while (attach[i] != NULL) {
2822 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2823 if (utf8_filename) {
2824 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2825 gchar *tmp = g_strdup_printf("%s%s\n",
2826 warn_files?warn_files:"",
2827 utf8_filename);
2828 g_free(warn_files);
2829 warn_files = tmp;
2830 att++;
2832 g_free(utf8_filename);
2833 } else {
2834 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2836 i++;
2838 if (warn_files) {
2839 alertpanel_notice(ngettext(
2840 "The following file has been attached: \n%s",
2841 "The following files have been attached: \n%s", att), warn_files);
2842 g_free(warn_files);
2845 if (inreplyto)
2846 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2848 g_free(to);
2849 g_free(cc);
2850 g_free(bcc);
2851 g_free(subject);
2852 g_free(body);
2853 g_strfreev(attach);
2854 g_free(inreplyto);
2856 return mfield;
2859 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2861 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2862 {"Cc:", NULL, TRUE},
2863 {"References:", NULL, FALSE},
2864 {"Bcc:", NULL, TRUE},
2865 {"Newsgroups:", NULL, TRUE},
2866 {"Followup-To:", NULL, TRUE},
2867 {"List-Post:", NULL, FALSE},
2868 {"X-Priority:", NULL, FALSE},
2869 {NULL, NULL, FALSE}};
2871 enum
2873 H_REPLY_TO = 0,
2874 H_CC = 1,
2875 H_REFERENCES = 2,
2876 H_BCC = 3,
2877 H_NEWSGROUPS = 4,
2878 H_FOLLOWUP_TO = 5,
2879 H_LIST_POST = 6,
2880 H_X_PRIORITY = 7
2883 FILE *fp;
2885 cm_return_val_if_fail(msginfo != NULL, -1);
2887 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2888 procheader_get_header_fields(fp, hentry);
2889 fclose(fp);
2891 if (hentry[H_REPLY_TO].body != NULL) {
2892 if (hentry[H_REPLY_TO].body[0] != '\0') {
2893 compose->replyto =
2894 conv_unmime_header(hentry[H_REPLY_TO].body,
2895 NULL, TRUE);
2897 g_free(hentry[H_REPLY_TO].body);
2898 hentry[H_REPLY_TO].body = NULL;
2900 if (hentry[H_CC].body != NULL) {
2901 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2902 g_free(hentry[H_CC].body);
2903 hentry[H_CC].body = NULL;
2905 if (hentry[H_REFERENCES].body != NULL) {
2906 if (compose->mode == COMPOSE_REEDIT)
2907 compose->references = hentry[H_REFERENCES].body;
2908 else {
2909 compose->references = compose_parse_references
2910 (hentry[H_REFERENCES].body, msginfo->msgid);
2911 g_free(hentry[H_REFERENCES].body);
2913 hentry[H_REFERENCES].body = NULL;
2915 if (hentry[H_BCC].body != NULL) {
2916 if (compose->mode == COMPOSE_REEDIT)
2917 compose->bcc =
2918 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2919 g_free(hentry[H_BCC].body);
2920 hentry[H_BCC].body = NULL;
2922 if (hentry[H_NEWSGROUPS].body != NULL) {
2923 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2924 hentry[H_NEWSGROUPS].body = NULL;
2926 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2927 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2928 compose->followup_to =
2929 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2930 NULL, TRUE);
2932 g_free(hentry[H_FOLLOWUP_TO].body);
2933 hentry[H_FOLLOWUP_TO].body = NULL;
2935 if (hentry[H_LIST_POST].body != NULL) {
2936 gchar *to = NULL, *start = NULL;
2938 extract_address(hentry[H_LIST_POST].body);
2939 if (hentry[H_LIST_POST].body[0] != '\0') {
2940 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2942 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2943 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2945 if (to) {
2946 g_free(compose->ml_post);
2947 compose->ml_post = to;
2950 g_free(hentry[H_LIST_POST].body);
2951 hentry[H_LIST_POST].body = NULL;
2954 /* CLAWS - X-Priority */
2955 if (compose->mode == COMPOSE_REEDIT)
2956 if (hentry[H_X_PRIORITY].body != NULL) {
2957 gint priority;
2959 priority = atoi(hentry[H_X_PRIORITY].body);
2960 g_free(hentry[H_X_PRIORITY].body);
2962 hentry[H_X_PRIORITY].body = NULL;
2964 if (priority < PRIORITY_HIGHEST ||
2965 priority > PRIORITY_LOWEST)
2966 priority = PRIORITY_NORMAL;
2968 compose->priority = priority;
2971 if (compose->mode == COMPOSE_REEDIT) {
2972 if (msginfo->inreplyto && *msginfo->inreplyto)
2973 compose->inreplyto = g_strdup(msginfo->inreplyto);
2974 return 0;
2977 if (msginfo->msgid && *msginfo->msgid)
2978 compose->inreplyto = g_strdup(msginfo->msgid);
2980 if (!compose->references) {
2981 if (msginfo->msgid && *msginfo->msgid) {
2982 if (msginfo->inreplyto && *msginfo->inreplyto)
2983 compose->references =
2984 g_strdup_printf("<%s>\n\t<%s>",
2985 msginfo->inreplyto,
2986 msginfo->msgid);
2987 else
2988 compose->references =
2989 g_strconcat("<", msginfo->msgid, ">",
2990 NULL);
2991 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2992 compose->references =
2993 g_strconcat("<", msginfo->inreplyto, ">",
2994 NULL);
2998 return 0;
3001 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3003 FILE *fp;
3004 HeaderEntry *he;
3006 cm_return_val_if_fail(msginfo != NULL, -1);
3008 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3009 procheader_get_header_fields(fp, entries);
3010 fclose(fp);
3012 he = entries;
3013 while (he != NULL && he->name != NULL) {
3014 GtkTreeIter iter;
3015 GtkListStore *model = NULL;
3017 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3018 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3019 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3020 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3021 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3022 ++he;
3025 return 0;
3028 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3030 GSList *ref_id_list, *cur;
3031 GString *new_ref;
3032 gchar *new_ref_str;
3034 ref_id_list = references_list_append(NULL, ref);
3035 if (!ref_id_list) return NULL;
3036 if (msgid && *msgid)
3037 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3039 for (;;) {
3040 gint len = 0;
3042 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3043 /* "<" + Message-ID + ">" + CR+LF+TAB */
3044 len += strlen((gchar *)cur->data) + 5;
3046 if (len > MAX_REFERENCES_LEN) {
3047 /* remove second message-ID */
3048 if (ref_id_list && ref_id_list->next &&
3049 ref_id_list->next->next) {
3050 g_free(ref_id_list->next->data);
3051 ref_id_list = g_slist_remove
3052 (ref_id_list, ref_id_list->next->data);
3053 } else {
3054 slist_free_strings_full(ref_id_list);
3055 return NULL;
3057 } else
3058 break;
3061 new_ref = g_string_new("");
3062 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3063 if (new_ref->len > 0)
3064 g_string_append(new_ref, "\n\t");
3065 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3068 slist_free_strings_full(ref_id_list);
3070 new_ref_str = new_ref->str;
3071 g_string_free(new_ref, FALSE);
3073 return new_ref_str;
3076 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3077 const gchar *fmt, const gchar *qmark,
3078 const gchar *body, gboolean rewrap,
3079 gboolean need_unescape,
3080 const gchar *err_msg)
3082 MsgInfo* dummyinfo = NULL;
3083 gchar *quote_str = NULL;
3084 gchar *buf;
3085 gboolean prev_autowrap;
3086 const gchar *trimmed_body = body;
3087 gint cursor_pos = -1;
3088 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3089 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3090 GtkTextIter iter;
3091 GtkTextMark *mark;
3094 SIGNAL_BLOCK(buffer);
3096 if (!msginfo) {
3097 dummyinfo = compose_msginfo_new_from_compose(compose);
3098 msginfo = dummyinfo;
3101 if (qmark != NULL) {
3102 #ifdef USE_ENCHANT
3103 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3104 compose->gtkaspell);
3105 #else
3106 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3107 #endif
3108 quote_fmt_scan_string(qmark);
3109 quote_fmt_parse();
3111 buf = quote_fmt_get_buffer();
3112 if (buf == NULL)
3113 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3114 else
3115 Xstrdup_a(quote_str, buf, goto error)
3118 if (fmt && *fmt != '\0') {
3120 if (trimmed_body)
3121 while (*trimmed_body == '\n')
3122 trimmed_body++;
3124 #ifdef USE_ENCHANT
3125 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3126 compose->gtkaspell);
3127 #else
3128 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3129 #endif
3130 if (need_unescape) {
3131 gchar *tmp = NULL;
3133 /* decode \-escape sequences in the internal representation of the quote format */
3134 tmp = g_malloc(strlen(fmt)+1);
3135 pref_get_unescaped_pref(tmp, fmt);
3136 quote_fmt_scan_string(tmp);
3137 quote_fmt_parse();
3138 g_free(tmp);
3139 } else {
3140 quote_fmt_scan_string(fmt);
3141 quote_fmt_parse();
3144 buf = quote_fmt_get_buffer();
3145 if (buf == NULL) {
3146 gint line = quote_fmt_get_line();
3147 alertpanel_error(err_msg, line);
3148 goto error;
3150 } else
3151 buf = "";
3153 prev_autowrap = compose->autowrap;
3154 compose->autowrap = FALSE;
3156 mark = gtk_text_buffer_get_insert(buffer);
3157 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3158 if (g_utf8_validate(buf, -1, NULL)) {
3159 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3160 } else {
3161 gchar *tmpout = NULL;
3162 tmpout = conv_codeset_strdup
3163 (buf, conv_get_locale_charset_str_no_utf8(),
3164 CS_INTERNAL);
3165 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3166 g_free(tmpout);
3167 tmpout = g_malloc(strlen(buf)*2+1);
3168 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3170 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3171 g_free(tmpout);
3174 cursor_pos = quote_fmt_get_cursor_pos();
3175 if (cursor_pos == -1)
3176 cursor_pos = gtk_text_iter_get_offset(&iter);
3177 compose->set_cursor_pos = cursor_pos;
3179 gtk_text_buffer_get_start_iter(buffer, &iter);
3180 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3181 gtk_text_buffer_place_cursor(buffer, &iter);
3183 compose->autowrap = prev_autowrap;
3184 if (compose->autowrap && rewrap)
3185 compose_wrap_all(compose);
3187 goto ok;
3189 error:
3190 buf = NULL;
3192 SIGNAL_UNBLOCK(buffer);
3194 procmsg_msginfo_free( &dummyinfo );
3196 return buf;
3199 /* if ml_post is of type addr@host and from is of type
3200 * addr-anything@host, return TRUE
3202 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3204 gchar *left_ml = NULL;
3205 gchar *right_ml = NULL;
3206 gchar *left_from = NULL;
3207 gchar *right_from = NULL;
3208 gboolean result = FALSE;
3210 if (!ml_post || !from)
3211 return FALSE;
3213 left_ml = g_strdup(ml_post);
3214 if (strstr(left_ml, "@")) {
3215 right_ml = strstr(left_ml, "@")+1;
3216 *(strstr(left_ml, "@")) = '\0';
3219 left_from = g_strdup(from);
3220 if (strstr(left_from, "@")) {
3221 right_from = strstr(left_from, "@")+1;
3222 *(strstr(left_from, "@")) = '\0';
3225 if (right_ml && right_from
3226 && !strncmp(left_from, left_ml, strlen(left_ml))
3227 && !strcmp(right_from, right_ml)) {
3228 result = TRUE;
3230 g_free(left_ml);
3231 g_free(left_from);
3233 return result;
3236 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3237 gboolean respect_default_to)
3239 if (!compose)
3240 return;
3241 if (!folder || !folder->prefs)
3242 return;
3244 if (respect_default_to && folder->prefs->enable_default_to) {
3245 compose_entry_append(compose, folder->prefs->default_to,
3246 COMPOSE_TO, PREF_FOLDER);
3247 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3249 if (folder->prefs->enable_default_cc)
3250 compose_entry_append(compose, folder->prefs->default_cc,
3251 COMPOSE_CC, PREF_FOLDER);
3252 if (folder->prefs->enable_default_bcc)
3253 compose_entry_append(compose, folder->prefs->default_bcc,
3254 COMPOSE_BCC, PREF_FOLDER);
3255 if (folder->prefs->enable_default_replyto)
3256 compose_entry_append(compose, folder->prefs->default_replyto,
3257 COMPOSE_REPLYTO, PREF_FOLDER);
3260 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3262 gchar *buf, *buf2;
3263 gchar *p;
3265 if (!compose || !msginfo)
3266 return;
3268 if (msginfo->subject && *msginfo->subject) {
3269 buf = p = g_strdup(msginfo->subject);
3270 p += subject_get_prefix_length(p);
3271 memmove(buf, p, strlen(p) + 1);
3273 buf2 = g_strdup_printf("Re: %s", buf);
3274 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3276 g_free(buf2);
3277 g_free(buf);
3278 } else
3279 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3282 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3283 gboolean to_all, gboolean to_ml,
3284 gboolean to_sender,
3285 gboolean followup_and_reply_to)
3287 GSList *cc_list = NULL;
3288 GSList *cur;
3289 gchar *from = NULL;
3290 gchar *replyto = NULL;
3291 gchar *ac_email = NULL;
3293 gboolean reply_to_ml = FALSE;
3294 gboolean default_reply_to = FALSE;
3296 cm_return_if_fail(compose->account != NULL);
3297 cm_return_if_fail(msginfo != NULL);
3299 reply_to_ml = to_ml && compose->ml_post;
3301 default_reply_to = msginfo->folder &&
3302 msginfo->folder->prefs->enable_default_reply_to;
3304 if (compose->account->protocol != A_NNTP) {
3305 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3307 if (reply_to_ml && !default_reply_to) {
3309 gboolean is_subscr = is_subscription(compose->ml_post,
3310 msginfo->from);
3311 if (!is_subscr) {
3312 /* normal answer to ml post with a reply-to */
3313 compose_entry_append(compose,
3314 compose->ml_post,
3315 COMPOSE_TO, PREF_ML);
3316 if (compose->replyto)
3317 compose_entry_append(compose,
3318 compose->replyto,
3319 COMPOSE_CC, PREF_ML);
3320 } else {
3321 /* answer to subscription confirmation */
3322 if (compose->replyto)
3323 compose_entry_append(compose,
3324 compose->replyto,
3325 COMPOSE_TO, PREF_ML);
3326 else if (msginfo->from)
3327 compose_entry_append(compose,
3328 msginfo->from,
3329 COMPOSE_TO, PREF_ML);
3332 else if (!(to_all || to_sender) && default_reply_to) {
3333 compose_entry_append(compose,
3334 msginfo->folder->prefs->default_reply_to,
3335 COMPOSE_TO, PREF_FOLDER);
3336 compose_entry_mark_default_to(compose,
3337 msginfo->folder->prefs->default_reply_to);
3338 } else {
3339 gchar *tmp1 = NULL;
3340 if (!msginfo->from)
3341 return;
3342 if (to_sender)
3343 compose_entry_append(compose, msginfo->from,
3344 COMPOSE_TO, PREF_NONE);
3345 else if (to_all) {
3346 Xstrdup_a(tmp1, msginfo->from, return);
3347 extract_address(tmp1);
3348 compose_entry_append(compose,
3349 (!account_find_from_address(tmp1, FALSE))
3350 ? msginfo->from :
3351 msginfo->to,
3352 COMPOSE_TO, PREF_NONE);
3353 if (compose->replyto)
3354 compose_entry_append(compose,
3355 compose->replyto,
3356 COMPOSE_CC, PREF_NONE);
3357 } else {
3358 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3359 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3360 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3361 if (compose->replyto) {
3362 compose_entry_append(compose,
3363 compose->replyto,
3364 COMPOSE_TO, PREF_NONE);
3365 } else {
3366 compose_entry_append(compose,
3367 msginfo->from ? msginfo->from : "",
3368 COMPOSE_TO, PREF_NONE);
3370 } else {
3371 /* replying to own mail, use original recp */
3372 compose_entry_append(compose,
3373 msginfo->to ? msginfo->to : "",
3374 COMPOSE_TO, PREF_NONE);
3375 compose_entry_append(compose,
3376 msginfo->cc ? msginfo->cc : "",
3377 COMPOSE_CC, PREF_NONE);
3381 } else {
3382 if (to_sender || (compose->followup_to &&
3383 !strncmp(compose->followup_to, "poster", 6)))
3384 compose_entry_append
3385 (compose,
3386 (compose->replyto ? compose->replyto :
3387 msginfo->from ? msginfo->from : ""),
3388 COMPOSE_TO, PREF_NONE);
3390 else if (followup_and_reply_to || to_all) {
3391 compose_entry_append
3392 (compose,
3393 (compose->replyto ? compose->replyto :
3394 msginfo->from ? msginfo->from : ""),
3395 COMPOSE_TO, PREF_NONE);
3397 compose_entry_append
3398 (compose,
3399 compose->followup_to ? compose->followup_to :
3400 compose->newsgroups ? compose->newsgroups : "",
3401 COMPOSE_NEWSGROUPS, PREF_NONE);
3403 compose_entry_append
3404 (compose,
3405 msginfo->cc ? msginfo->cc : "",
3406 COMPOSE_CC, PREF_NONE);
3408 else
3409 compose_entry_append
3410 (compose,
3411 compose->followup_to ? compose->followup_to :
3412 compose->newsgroups ? compose->newsgroups : "",
3413 COMPOSE_NEWSGROUPS, PREF_NONE);
3415 compose_reply_set_subject(compose, msginfo);
3417 if (to_ml && compose->ml_post) return;
3418 if (!to_all || compose->account->protocol == A_NNTP) return;
3420 if (compose->replyto) {
3421 Xstrdup_a(replyto, compose->replyto, return);
3422 extract_address(replyto);
3424 if (msginfo->from) {
3425 Xstrdup_a(from, msginfo->from, return);
3426 extract_address(from);
3429 if (replyto && from)
3430 cc_list = address_list_append_with_comments(cc_list, from);
3431 if (to_all && msginfo->folder &&
3432 msginfo->folder->prefs->enable_default_reply_to)
3433 cc_list = address_list_append_with_comments(cc_list,
3434 msginfo->folder->prefs->default_reply_to);
3435 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3436 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3438 ac_email = g_utf8_strdown(compose->account->address, -1);
3440 if (cc_list) {
3441 for (cur = cc_list; cur != NULL; cur = cur->next) {
3442 gchar *addr = g_utf8_strdown(cur->data, -1);
3443 extract_address(addr);
3445 if (strcmp(ac_email, addr))
3446 compose_entry_append(compose, (gchar *)cur->data,
3447 COMPOSE_CC, PREF_NONE);
3448 else
3449 debug_print("Cc address same as compose account's, ignoring\n");
3451 g_free(addr);
3454 slist_free_strings_full(cc_list);
3457 g_free(ac_email);
3460 #define SET_ENTRY(entry, str) \
3462 if (str && *str) \
3463 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3466 #define SET_ADDRESS(type, str) \
3468 if (str && *str) \
3469 compose_entry_append(compose, str, type, PREF_NONE); \
3472 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3474 cm_return_if_fail(msginfo != NULL);
3476 SET_ENTRY(subject_entry, msginfo->subject);
3477 SET_ENTRY(from_name, msginfo->from);
3478 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3479 SET_ADDRESS(COMPOSE_CC, compose->cc);
3480 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3481 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3482 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3483 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3485 compose_update_priority_menu_item(compose);
3486 compose_update_privacy_system_menu_item(compose, FALSE);
3487 compose_show_first_last_header(compose, TRUE);
3490 #undef SET_ENTRY
3491 #undef SET_ADDRESS
3493 static void compose_insert_sig(Compose *compose, gboolean replace)
3495 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3496 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3497 GtkTextMark *mark;
3498 GtkTextIter iter, iter_end;
3499 gint cur_pos, ins_pos;
3500 gboolean prev_autowrap;
3501 gboolean found = FALSE;
3502 gboolean exists = FALSE;
3504 cm_return_if_fail(compose->account != NULL);
3506 BLOCK_WRAP();
3508 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3509 G_CALLBACK(compose_changed_cb),
3510 compose);
3512 mark = gtk_text_buffer_get_insert(buffer);
3513 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3514 cur_pos = gtk_text_iter_get_offset (&iter);
3515 ins_pos = cur_pos;
3517 gtk_text_buffer_get_end_iter(buffer, &iter);
3519 exists = (compose->sig_str != NULL);
3521 if (replace) {
3522 GtkTextIter first_iter, start_iter, end_iter;
3524 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3526 if (!exists || compose->sig_str[0] == '\0')
3527 found = FALSE;
3528 else
3529 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3530 compose->signature_tag);
3532 if (found) {
3533 /* include previous \n\n */
3534 gtk_text_iter_backward_chars(&first_iter, 1);
3535 start_iter = first_iter;
3536 end_iter = first_iter;
3537 /* skip re-start */
3538 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3539 compose->signature_tag);
3540 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3541 compose->signature_tag);
3542 if (found) {
3543 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3544 iter = start_iter;
3549 g_free(compose->sig_str);
3550 compose->sig_str = account_get_signature_str(compose->account);
3552 cur_pos = gtk_text_iter_get_offset(&iter);
3554 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3555 g_free(compose->sig_str);
3556 compose->sig_str = NULL;
3557 } else {
3558 if (compose->sig_inserted == FALSE)
3559 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3560 compose->sig_inserted = TRUE;
3562 cur_pos = gtk_text_iter_get_offset(&iter);
3563 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3564 /* remove \n\n */
3565 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3566 gtk_text_iter_forward_chars(&iter, 1);
3567 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3568 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3570 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3571 cur_pos = gtk_text_buffer_get_char_count (buffer);
3574 /* put the cursor where it should be
3575 * either where the quote_fmt says, either where it was */
3576 if (compose->set_cursor_pos < 0)
3577 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3578 else
3579 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3580 compose->set_cursor_pos);
3582 compose->set_cursor_pos = -1;
3583 gtk_text_buffer_place_cursor(buffer, &iter);
3584 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3585 G_CALLBACK(compose_changed_cb),
3586 compose);
3588 UNBLOCK_WRAP();
3591 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3593 GtkTextView *text;
3594 GtkTextBuffer *buffer;
3595 GtkTextMark *mark;
3596 GtkTextIter iter;
3597 const gchar *cur_encoding;
3598 gchar buf[BUFFSIZE];
3599 gint len;
3600 FILE *fp;
3601 gboolean prev_autowrap;
3602 GStatBuf file_stat;
3603 int ret;
3604 GString *file_contents = NULL;
3605 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3607 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3609 /* get the size of the file we are about to insert */
3610 ret = g_stat(file, &file_stat);
3611 if (ret != 0) {
3612 gchar *shortfile = g_path_get_basename(file);
3613 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3614 g_free(shortfile);
3615 return COMPOSE_INSERT_NO_FILE;
3616 } else if (prefs_common.warn_large_insert == TRUE) {
3618 /* ask user for confirmation if the file is large */
3619 if (prefs_common.warn_large_insert_size < 0 ||
3620 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3621 AlertValue aval;
3622 gchar *msg;
3624 msg = g_strdup_printf(_("You are about to insert a file of %s "
3625 "in the message body. Are you sure you want to do that?"),
3626 to_human_readable(file_stat.st_size));
3627 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3628 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3629 g_free(msg);
3631 /* do we ask for confirmation next time? */
3632 if (aval & G_ALERTDISABLE) {
3633 /* no confirmation next time, disable feature in preferences */
3634 aval &= ~G_ALERTDISABLE;
3635 prefs_common.warn_large_insert = FALSE;
3638 /* abort file insertion if user canceled action */
3639 if (aval != G_ALERTALTERNATE) {
3640 return COMPOSE_INSERT_NO_FILE;
3646 if ((fp = g_fopen(file, "rb")) == NULL) {
3647 FILE_OP_ERROR(file, "fopen");
3648 return COMPOSE_INSERT_READ_ERROR;
3651 prev_autowrap = compose->autowrap;
3652 compose->autowrap = FALSE;
3654 text = GTK_TEXT_VIEW(compose->text);
3655 buffer = gtk_text_view_get_buffer(text);
3656 mark = gtk_text_buffer_get_insert(buffer);
3657 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3659 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3660 G_CALLBACK(text_inserted),
3661 compose);
3663 cur_encoding = conv_get_locale_charset_str_no_utf8();
3665 file_contents = g_string_new("");
3666 while (fgets(buf, sizeof(buf), fp) != NULL) {
3667 gchar *str;
3669 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3670 str = g_strdup(buf);
3671 else {
3672 codeconv_set_strict(TRUE);
3673 str = conv_codeset_strdup
3674 (buf, cur_encoding, CS_INTERNAL);
3675 codeconv_set_strict(FALSE);
3677 if (!str) {
3678 result = COMPOSE_INSERT_INVALID_CHARACTER;
3679 break;
3682 if (!str) continue;
3684 /* strip <CR> if DOS/Windows file,
3685 replace <CR> with <LF> if Macintosh file. */
3686 strcrchomp(str);
3687 len = strlen(str);
3688 if (len > 0 && str[len - 1] != '\n') {
3689 while (--len >= 0)
3690 if (str[len] == '\r') str[len] = '\n';
3693 file_contents = g_string_append(file_contents, str);
3694 g_free(str);
3697 if (result == COMPOSE_INSERT_SUCCESS) {
3698 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3700 compose_changed_cb(NULL, compose);
3701 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3702 G_CALLBACK(text_inserted),
3703 compose);
3704 compose->autowrap = prev_autowrap;
3705 if (compose->autowrap)
3706 compose_wrap_all(compose);
3709 g_string_free(file_contents, TRUE);
3710 fclose(fp);
3712 return result;
3715 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3716 const gchar *filename,
3717 const gchar *content_type,
3718 const gchar *charset)
3720 AttachInfo *ainfo;
3721 GtkTreeIter iter;
3722 FILE *fp;
3723 off_t size;
3724 GAuto *auto_ainfo;
3725 gchar *size_text;
3726 GtkListStore *store;
3727 gchar *name;
3728 gboolean has_binary = FALSE;
3730 if (!is_file_exist(file)) {
3731 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3732 gboolean result = FALSE;
3733 if (file_from_uri && is_file_exist(file_from_uri)) {
3734 result = compose_attach_append(
3735 compose, file_from_uri,
3736 filename, content_type,
3737 charset);
3739 g_free(file_from_uri);
3740 if (result)
3741 return TRUE;
3742 alertpanel_error("File %s doesn't exist\n", filename);
3743 return FALSE;
3745 if ((size = get_file_size(file)) < 0) {
3746 alertpanel_error("Can't get file size of %s\n", filename);
3747 return FALSE;
3750 /* In batch mode, we allow 0-length files to be attached no questions asked */
3751 if (size == 0 && !compose->batch) {
3752 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3753 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3754 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3755 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3756 g_free(msg);
3758 if (aval != G_ALERTALTERNATE) {
3759 return FALSE;
3762 if ((fp = g_fopen(file, "rb")) == NULL) {
3763 alertpanel_error(_("Can't read %s."), filename);
3764 return FALSE;
3766 fclose(fp);
3768 ainfo = g_new0(AttachInfo, 1);
3769 auto_ainfo = g_auto_pointer_new_with_free
3770 (ainfo, (GFreeFunc) compose_attach_info_free);
3771 ainfo->file = g_strdup(file);
3773 if (content_type) {
3774 ainfo->content_type = g_strdup(content_type);
3775 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3776 MsgInfo *msginfo;
3777 MsgFlags flags = {0, 0};
3779 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3780 ainfo->encoding = ENC_7BIT;
3781 else
3782 ainfo->encoding = ENC_8BIT;
3784 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3785 if (msginfo && msginfo->subject)
3786 name = g_strdup(msginfo->subject);
3787 else
3788 name = g_path_get_basename(filename ? filename : file);
3790 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3792 procmsg_msginfo_free(&msginfo);
3793 } else {
3794 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3795 ainfo->charset = g_strdup(charset);
3796 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3797 } else {
3798 ainfo->encoding = ENC_BASE64;
3800 name = g_path_get_basename(filename ? filename : file);
3801 ainfo->name = g_strdup(name);
3803 g_free(name);
3804 } else {
3805 ainfo->content_type = procmime_get_mime_type(file);
3806 if (!ainfo->content_type) {
3807 ainfo->content_type =
3808 g_strdup("application/octet-stream");
3809 ainfo->encoding = ENC_BASE64;
3810 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3811 ainfo->encoding =
3812 procmime_get_encoding_for_text_file(file, &has_binary);
3813 else
3814 ainfo->encoding = ENC_BASE64;
3815 name = g_path_get_basename(filename ? filename : file);
3816 ainfo->name = g_strdup(name);
3817 g_free(name);
3820 if (ainfo->name != NULL
3821 && !strcmp(ainfo->name, ".")) {
3822 g_free(ainfo->name);
3823 ainfo->name = NULL;
3826 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3827 g_free(ainfo->content_type);
3828 ainfo->content_type = g_strdup("application/octet-stream");
3829 g_free(ainfo->charset);
3830 ainfo->charset = NULL;
3833 ainfo->size = (goffset)size;
3834 size_text = to_human_readable((goffset)size);
3836 store = GTK_LIST_STORE(gtk_tree_view_get_model
3837 (GTK_TREE_VIEW(compose->attach_clist)));
3839 gtk_list_store_append(store, &iter);
3840 gtk_list_store_set(store, &iter,
3841 COL_MIMETYPE, ainfo->content_type,
3842 COL_SIZE, size_text,
3843 COL_NAME, ainfo->name,
3844 COL_CHARSET, ainfo->charset,
3845 COL_DATA, ainfo,
3846 COL_AUTODATA, auto_ainfo,
3847 -1);
3849 g_auto_pointer_free(auto_ainfo);
3850 compose_attach_update_label(compose);
3851 return TRUE;
3854 static void compose_use_signing(Compose *compose, gboolean use_signing)
3856 compose->use_signing = use_signing;
3857 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3860 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3862 compose->use_encryption = use_encryption;
3863 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3866 #define NEXT_PART_NOT_CHILD(info) \
3868 node = info->node; \
3869 while (node->children) \
3870 node = g_node_last_child(node); \
3871 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3874 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3876 MimeInfo *mimeinfo;
3877 MimeInfo *child;
3878 MimeInfo *firsttext = NULL;
3879 MimeInfo *encrypted = NULL;
3880 GNode *node;
3881 gchar *outfile;
3882 const gchar *partname = NULL;
3884 mimeinfo = procmime_scan_message(msginfo);
3885 if (!mimeinfo) return;
3887 if (mimeinfo->node->children == NULL) {
3888 procmime_mimeinfo_free_all(&mimeinfo);
3889 return;
3892 /* find first content part */
3893 child = (MimeInfo *) mimeinfo->node->children->data;
3894 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3895 child = (MimeInfo *)child->node->children->data;
3897 if (child) {
3898 if (child->type == MIMETYPE_TEXT) {
3899 firsttext = child;
3900 debug_print("First text part found\n");
3901 } else if (compose->mode == COMPOSE_REEDIT &&
3902 child->type == MIMETYPE_APPLICATION &&
3903 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3904 encrypted = (MimeInfo *)child->node->parent->data;
3907 child = (MimeInfo *) mimeinfo->node->children->data;
3908 while (child != NULL) {
3909 gint err;
3911 if (child == encrypted) {
3912 /* skip this part of tree */
3913 NEXT_PART_NOT_CHILD(child);
3914 continue;
3917 if (child->type == MIMETYPE_MULTIPART) {
3918 /* get the actual content */
3919 child = procmime_mimeinfo_next(child);
3920 continue;
3923 if (child == firsttext) {
3924 child = procmime_mimeinfo_next(child);
3925 continue;
3928 outfile = procmime_get_tmp_file_name(child);
3929 if ((err = procmime_get_part(outfile, child)) < 0)
3930 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3931 else {
3932 gchar *content_type;
3934 content_type = procmime_get_content_type_str(child->type, child->subtype);
3936 /* if we meet a pgp signature, we don't attach it, but
3937 * we force signing. */
3938 if ((strcmp(content_type, "application/pgp-signature") &&
3939 strcmp(content_type, "application/pkcs7-signature") &&
3940 strcmp(content_type, "application/x-pkcs7-signature"))
3941 || compose->mode == COMPOSE_REDIRECT) {
3942 partname = procmime_mimeinfo_get_parameter(child, "filename");
3943 if (partname == NULL)
3944 partname = procmime_mimeinfo_get_parameter(child, "name");
3945 if (partname == NULL)
3946 partname = "";
3947 compose_attach_append(compose, outfile,
3948 partname, content_type,
3949 procmime_mimeinfo_get_parameter(child, "charset"));
3950 } else {
3951 compose_force_signing(compose, compose->account, NULL);
3953 g_free(content_type);
3955 g_free(outfile);
3956 NEXT_PART_NOT_CHILD(child);
3958 procmime_mimeinfo_free_all(&mimeinfo);
3961 #undef NEXT_PART_NOT_CHILD
3965 typedef enum {
3966 WAIT_FOR_INDENT_CHAR,
3967 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3968 } IndentState;
3970 /* return indent length, we allow:
3971 indent characters followed by indent characters or spaces/tabs,
3972 alphabets and numbers immediately followed by indent characters,
3973 and the repeating sequences of the above
3974 If quote ends with multiple spaces, only the first one is included. */
3975 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3976 const GtkTextIter *start, gint *len)
3978 GtkTextIter iter = *start;
3979 gunichar wc;
3980 gchar ch[6];
3981 gint clen;
3982 IndentState state = WAIT_FOR_INDENT_CHAR;
3983 gboolean is_space;
3984 gboolean is_indent;
3985 gint alnum_count = 0;
3986 gint space_count = 0;
3987 gint quote_len = 0;
3989 if (prefs_common.quote_chars == NULL) {
3990 return 0 ;
3993 while (!gtk_text_iter_ends_line(&iter)) {
3994 wc = gtk_text_iter_get_char(&iter);
3995 if (g_unichar_iswide(wc))
3996 break;
3997 clen = g_unichar_to_utf8(wc, ch);
3998 if (clen != 1)
3999 break;
4001 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4002 is_space = g_unichar_isspace(wc);
4004 if (state == WAIT_FOR_INDENT_CHAR) {
4005 if (!is_indent && !g_unichar_isalnum(wc))
4006 break;
4007 if (is_indent) {
4008 quote_len += alnum_count + space_count + 1;
4009 alnum_count = space_count = 0;
4010 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4011 } else
4012 alnum_count++;
4013 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4014 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4015 break;
4016 if (is_space)
4017 space_count++;
4018 else if (is_indent) {
4019 quote_len += alnum_count + space_count + 1;
4020 alnum_count = space_count = 0;
4021 } else {
4022 alnum_count++;
4023 state = WAIT_FOR_INDENT_CHAR;
4027 gtk_text_iter_forward_char(&iter);
4030 if (quote_len > 0 && space_count > 0)
4031 quote_len++;
4033 if (len)
4034 *len = quote_len;
4036 if (quote_len > 0) {
4037 iter = *start;
4038 gtk_text_iter_forward_chars(&iter, quote_len);
4039 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4042 return NULL;
4045 /* return >0 if the line is itemized */
4046 static int compose_itemized_length(GtkTextBuffer *buffer,
4047 const GtkTextIter *start)
4049 GtkTextIter iter = *start;
4050 gunichar wc;
4051 gchar ch[6];
4052 gint clen;
4053 gint len = 0;
4054 if (gtk_text_iter_ends_line(&iter))
4055 return 0;
4057 while (1) {
4058 len++;
4059 wc = gtk_text_iter_get_char(&iter);
4060 if (!g_unichar_isspace(wc))
4061 break;
4062 gtk_text_iter_forward_char(&iter);
4063 if (gtk_text_iter_ends_line(&iter))
4064 return 0;
4067 clen = g_unichar_to_utf8(wc, ch);
4068 if (clen != 1)
4069 return 0;
4071 if (!strchr("*-+", ch[0]))
4072 return 0;
4074 gtk_text_iter_forward_char(&iter);
4075 if (gtk_text_iter_ends_line(&iter))
4076 return 0;
4077 wc = gtk_text_iter_get_char(&iter);
4078 if (g_unichar_isspace(wc)) {
4079 return len+1;
4081 return 0;
4084 /* return the string at the start of the itemization */
4085 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4086 const GtkTextIter *start)
4088 GtkTextIter iter = *start;
4089 gunichar wc;
4090 gint len = 0;
4091 GString *item_chars = g_string_new("");
4092 gchar *str = NULL;
4094 if (gtk_text_iter_ends_line(&iter))
4095 return NULL;
4097 while (1) {
4098 len++;
4099 wc = gtk_text_iter_get_char(&iter);
4100 if (!g_unichar_isspace(wc))
4101 break;
4102 gtk_text_iter_forward_char(&iter);
4103 if (gtk_text_iter_ends_line(&iter))
4104 break;
4105 g_string_append_unichar(item_chars, wc);
4108 str = item_chars->str;
4109 g_string_free(item_chars, FALSE);
4110 return str;
4113 /* return the number of spaces at a line's start */
4114 static int compose_left_offset_length(GtkTextBuffer *buffer,
4115 const GtkTextIter *start)
4117 GtkTextIter iter = *start;
4118 gunichar wc;
4119 gint len = 0;
4120 if (gtk_text_iter_ends_line(&iter))
4121 return 0;
4123 while (1) {
4124 wc = gtk_text_iter_get_char(&iter);
4125 if (!g_unichar_isspace(wc))
4126 break;
4127 len++;
4128 gtk_text_iter_forward_char(&iter);
4129 if (gtk_text_iter_ends_line(&iter))
4130 return 0;
4133 gtk_text_iter_forward_char(&iter);
4134 if (gtk_text_iter_ends_line(&iter))
4135 return 0;
4136 return len;
4139 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4140 const GtkTextIter *start,
4141 GtkTextIter *break_pos,
4142 gint max_col,
4143 gint quote_len)
4145 GtkTextIter iter = *start, line_end = *start;
4146 PangoLogAttr *attrs;
4147 gchar *str;
4148 gchar *p;
4149 gint len;
4150 gint i;
4151 gint col = 0;
4152 gint pos = 0;
4153 gboolean can_break = FALSE;
4154 gboolean do_break = FALSE;
4155 gboolean was_white = FALSE;
4156 gboolean prev_dont_break = FALSE;
4158 gtk_text_iter_forward_to_line_end(&line_end);
4159 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4160 len = g_utf8_strlen(str, -1);
4162 if (len == 0) {
4163 g_free(str);
4164 g_warning("compose_get_line_break_pos: len = 0!");
4165 return FALSE;
4168 /* g_print("breaking line: %d: %s (len = %d)\n",
4169 gtk_text_iter_get_line(&iter), str, len); */
4171 attrs = g_new(PangoLogAttr, len + 1);
4173 pango_default_break(str, -1, NULL, attrs, len + 1);
4175 p = str;
4177 /* skip quote and leading spaces */
4178 for (i = 0; *p != '\0' && i < len; i++) {
4179 gunichar wc;
4181 wc = g_utf8_get_char(p);
4182 if (i >= quote_len && !g_unichar_isspace(wc))
4183 break;
4184 if (g_unichar_iswide(wc))
4185 col += 2;
4186 else if (*p == '\t')
4187 col += 8;
4188 else
4189 col++;
4190 p = g_utf8_next_char(p);
4193 for (; *p != '\0' && i < len; i++) {
4194 PangoLogAttr *attr = attrs + i;
4195 gunichar wc;
4196 gint uri_len;
4198 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4199 pos = i;
4201 was_white = attr->is_white;
4203 /* don't wrap URI */
4204 if ((uri_len = get_uri_len(p)) > 0) {
4205 col += uri_len;
4206 if (pos > 0 && col > max_col) {
4207 do_break = TRUE;
4208 break;
4210 i += uri_len - 1;
4211 p += uri_len;
4212 can_break = TRUE;
4213 continue;
4216 wc = g_utf8_get_char(p);
4217 if (g_unichar_iswide(wc)) {
4218 col += 2;
4219 if (prev_dont_break && can_break && attr->is_line_break)
4220 pos = i;
4221 } else if (*p == '\t')
4222 col += 8;
4223 else
4224 col++;
4225 if (pos > 0 && col > max_col) {
4226 do_break = TRUE;
4227 break;
4230 if (*p == '-' || *p == '/')
4231 prev_dont_break = TRUE;
4232 else
4233 prev_dont_break = FALSE;
4235 p = g_utf8_next_char(p);
4236 can_break = TRUE;
4239 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4241 g_free(attrs);
4242 g_free(str);
4244 *break_pos = *start;
4245 gtk_text_iter_set_line_offset(break_pos, pos);
4247 return do_break;
4250 static gboolean compose_join_next_line(Compose *compose,
4251 GtkTextBuffer *buffer,
4252 GtkTextIter *iter,
4253 const gchar *quote_str)
4255 GtkTextIter iter_ = *iter, cur, prev, next, end;
4256 PangoLogAttr attrs[3];
4257 gchar *str;
4258 gchar *next_quote_str;
4259 gunichar wc1, wc2;
4260 gint quote_len;
4261 gboolean keep_cursor = FALSE;
4263 if (!gtk_text_iter_forward_line(&iter_) ||
4264 gtk_text_iter_ends_line(&iter_)) {
4265 return FALSE;
4267 next_quote_str = compose_get_quote_str(buffer, &iter_, &quote_len);
4269 if ((quote_str || next_quote_str) &&
4270 strcmp2(quote_str, next_quote_str) != 0) {
4271 g_free(next_quote_str);
4272 return FALSE;
4274 g_free(next_quote_str);
4276 end = iter_;
4277 if (quote_len > 0) {
4278 gtk_text_iter_forward_chars(&end, quote_len);
4279 if (gtk_text_iter_ends_line(&end)) {
4280 return FALSE;
4284 /* don't join itemized lines */
4285 if (compose_itemized_length(buffer, &end) > 0) {
4286 return FALSE;
4289 /* don't join signature separator */
4290 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4291 return FALSE;
4293 /* delete quote str */
4294 if (quote_len > 0)
4295 gtk_text_buffer_delete(buffer, &iter_, &end);
4297 /* don't join line breaks put by the user */
4298 prev = cur = iter_;
4299 gtk_text_iter_backward_char(&cur);
4300 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4301 gtk_text_iter_forward_char(&cur);
4302 *iter = cur;
4303 return FALSE;
4305 gtk_text_iter_forward_char(&cur);
4306 /* delete linebreak and extra spaces */
4307 while (gtk_text_iter_backward_char(&cur)) {
4308 wc1 = gtk_text_iter_get_char(&cur);
4309 if (!g_unichar_isspace(wc1))
4310 break;
4311 prev = cur;
4313 next = cur = iter_;
4314 while (!gtk_text_iter_ends_line(&cur)) {
4315 wc1 = gtk_text_iter_get_char(&cur);
4316 if (!g_unichar_isspace(wc1))
4317 break;
4318 gtk_text_iter_forward_char(&cur);
4319 next = cur;
4321 if (!gtk_text_iter_equal(&prev, &next)) {
4322 GtkTextMark *mark;
4324 mark = gtk_text_buffer_get_insert(buffer);
4325 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4326 if (gtk_text_iter_equal(&prev, &cur))
4327 keep_cursor = TRUE;
4328 gtk_text_buffer_delete(buffer, &prev, &next);
4330 iter_ = prev;
4332 /* insert space if required */
4333 gtk_text_iter_backward_char(&prev);
4334 wc1 = gtk_text_iter_get_char(&prev);
4335 wc2 = gtk_text_iter_get_char(&next);
4336 gtk_text_iter_forward_char(&next);
4337 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4338 pango_default_break(str, -1, NULL, attrs, 3);
4339 if (!attrs[1].is_line_break ||
4340 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4341 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4342 if (keep_cursor) {
4343 gtk_text_iter_backward_char(&iter_);
4344 gtk_text_buffer_place_cursor(buffer, &iter_);
4347 g_free(str);
4349 *iter = iter_;
4350 return TRUE;
4353 #define ADD_TXT_POS(bp_, ep_, pti_) \
4354 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4355 last = last->next; \
4356 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4357 last->next = NULL; \
4358 } else { \
4359 g_warning("alloc error scanning URIs"); \
4362 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4364 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4365 GtkTextBuffer *buffer;
4366 GtkTextIter iter, break_pos, end_of_line;
4367 gchar *quote_str = NULL;
4368 gint quote_len;
4369 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4370 gboolean prev_autowrap = compose->autowrap;
4371 gint startq_offset = -1, noq_offset = -1;
4372 gint uri_start = -1, uri_stop = -1;
4373 gint nouri_start = -1, nouri_stop = -1;
4374 gint num_blocks = 0;
4375 gint quotelevel = -1;
4376 gboolean modified = force;
4377 gboolean removed = FALSE;
4378 gboolean modified_before_remove = FALSE;
4379 gint lines = 0;
4380 gboolean start = TRUE;
4381 gint itemized_len = 0, rem_item_len = 0;
4382 gchar *itemized_chars = NULL;
4383 gboolean item_continuation = FALSE;
4385 if (force) {
4386 modified = TRUE;
4388 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4389 modified = TRUE;
4392 compose->autowrap = FALSE;
4394 buffer = gtk_text_view_get_buffer(text);
4395 undo_wrapping(compose->undostruct, TRUE);
4396 if (par_iter) {
4397 iter = *par_iter;
4398 } else {
4399 GtkTextMark *mark;
4400 mark = gtk_text_buffer_get_insert(buffer);
4401 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4405 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4406 if (gtk_text_iter_ends_line(&iter)) {
4407 while (gtk_text_iter_ends_line(&iter) &&
4408 gtk_text_iter_forward_line(&iter))
4410 } else {
4411 while (gtk_text_iter_backward_line(&iter)) {
4412 if (gtk_text_iter_ends_line(&iter)) {
4413 gtk_text_iter_forward_line(&iter);
4414 break;
4418 } else {
4419 /* move to line start */
4420 gtk_text_iter_set_line_offset(&iter, 0);
4423 itemized_len = compose_itemized_length(buffer, &iter);
4425 if (!itemized_len) {
4426 itemized_len = compose_left_offset_length(buffer, &iter);
4427 item_continuation = TRUE;
4430 if (itemized_len)
4431 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4433 /* go until paragraph end (empty line) */
4434 while (start || !gtk_text_iter_ends_line(&iter)) {
4435 gchar *scanpos = NULL;
4436 /* parse table - in order of priority */
4437 struct table {
4438 const gchar *needle; /* token */
4440 /* token search function */
4441 gchar *(*search) (const gchar *haystack,
4442 const gchar *needle);
4443 /* part parsing function */
4444 gboolean (*parse) (const gchar *start,
4445 const gchar *scanpos,
4446 const gchar **bp_,
4447 const gchar **ep_,
4448 gboolean hdr);
4449 /* part to URI function */
4450 gchar *(*build_uri) (const gchar *bp,
4451 const gchar *ep);
4454 static struct table parser[] = {
4455 {"http://", strcasestr, get_uri_part, make_uri_string},
4456 {"https://", strcasestr, get_uri_part, make_uri_string},
4457 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4458 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4459 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4460 {"www.", strcasestr, get_uri_part, make_http_string},
4461 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4462 {"@", strcasestr, get_email_part, make_email_string}
4464 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4465 gint last_index = PARSE_ELEMS;
4466 gint n;
4467 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4468 gint walk_pos;
4470 start = FALSE;
4471 if (!prev_autowrap && num_blocks == 0) {
4472 num_blocks++;
4473 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4474 G_CALLBACK(text_inserted),
4475 compose);
4477 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4478 goto colorize;
4480 uri_start = uri_stop = -1;
4481 quote_len = 0;
4482 quote_str = compose_get_quote_str(buffer, &iter, &quote_len);
4484 if (quote_str) {
4485 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4486 if (startq_offset == -1)
4487 startq_offset = gtk_text_iter_get_offset(&iter);
4488 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4489 if (quotelevel > 2) {
4490 /* recycle colors */
4491 if (prefs_common.recycle_quote_colors)
4492 quotelevel %= 3;
4493 else
4494 quotelevel = 2;
4496 if (!wrap_quote) {
4497 goto colorize;
4499 } else {
4500 if (startq_offset == -1)
4501 noq_offset = gtk_text_iter_get_offset(&iter);
4502 quotelevel = -1;
4505 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4506 goto colorize;
4508 if (gtk_text_iter_ends_line(&iter)) {
4509 goto colorize;
4510 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4511 prefs_common.linewrap_len,
4512 quote_len)) {
4513 GtkTextIter prev, next, cur;
4514 if (prev_autowrap != FALSE || force) {
4515 compose->automatic_break = TRUE;
4516 modified = TRUE;
4517 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4518 compose->automatic_break = FALSE;
4519 if (itemized_len && compose->autoindent) {
4520 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4521 if (!item_continuation)
4522 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4524 } else if (quote_str && wrap_quote) {
4525 compose->automatic_break = TRUE;
4526 modified = TRUE;
4527 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4528 compose->automatic_break = FALSE;
4529 if (itemized_len && compose->autoindent) {
4530 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4531 if (!item_continuation)
4532 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4534 } else
4535 goto colorize;
4536 /* remove trailing spaces */
4537 cur = break_pos;
4538 rem_item_len = itemized_len;
4539 while (compose->autoindent && rem_item_len-- > 0)
4540 gtk_text_iter_backward_char(&cur);
4541 gtk_text_iter_backward_char(&cur);
4543 prev = next = cur;
4544 while (!gtk_text_iter_starts_line(&cur)) {
4545 gunichar wc;
4547 gtk_text_iter_backward_char(&cur);
4548 wc = gtk_text_iter_get_char(&cur);
4549 if (!g_unichar_isspace(wc))
4550 break;
4551 prev = cur;
4553 if (!gtk_text_iter_equal(&prev, &next)) {
4554 gtk_text_buffer_delete(buffer, &prev, &next);
4555 break_pos = next;
4556 gtk_text_iter_forward_char(&break_pos);
4559 if (quote_str)
4560 gtk_text_buffer_insert(buffer, &break_pos,
4561 quote_str, -1);
4563 iter = break_pos;
4564 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4566 /* move iter to current line start */
4567 gtk_text_iter_set_line_offset(&iter, 0);
4568 if (quote_str) {
4569 g_free(quote_str);
4570 quote_str = NULL;
4572 continue;
4573 } else {
4574 /* move iter to next line start */
4575 iter = break_pos;
4576 lines++;
4579 colorize:
4580 if (!prev_autowrap && num_blocks > 0) {
4581 num_blocks--;
4582 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4583 G_CALLBACK(text_inserted),
4584 compose);
4586 end_of_line = iter;
4587 while (!gtk_text_iter_ends_line(&end_of_line)) {
4588 gtk_text_iter_forward_char(&end_of_line);
4590 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4592 nouri_start = gtk_text_iter_get_offset(&iter);
4593 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4595 walk_pos = gtk_text_iter_get_offset(&iter);
4596 /* FIXME: this looks phony. scanning for anything in the parse table */
4597 for (n = 0; n < PARSE_ELEMS; n++) {
4598 gchar *tmp;
4600 tmp = parser[n].search(walk, parser[n].needle);
4601 if (tmp) {
4602 if (scanpos == NULL || tmp < scanpos) {
4603 scanpos = tmp;
4604 last_index = n;
4609 bp = ep = 0;
4610 if (scanpos) {
4611 /* check if URI can be parsed */
4612 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4613 (const gchar **)&ep, FALSE)
4614 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4615 walk = ep;
4616 } else
4617 walk = scanpos +
4618 strlen(parser[last_index].needle);
4620 if (bp && ep) {
4621 uri_start = walk_pos + (bp - o_walk);
4622 uri_stop = walk_pos + (ep - o_walk);
4624 g_free(o_walk);
4625 o_walk = NULL;
4626 gtk_text_iter_forward_line(&iter);
4627 g_free(quote_str);
4628 quote_str = NULL;
4629 if (startq_offset != -1) {
4630 GtkTextIter startquote, endquote;
4631 gtk_text_buffer_get_iter_at_offset(
4632 buffer, &startquote, startq_offset);
4633 endquote = iter;
4635 switch (quotelevel) {
4636 case 0:
4637 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4638 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4639 gtk_text_buffer_apply_tag_by_name(
4640 buffer, "quote0", &startquote, &endquote);
4641 gtk_text_buffer_remove_tag_by_name(
4642 buffer, "quote1", &startquote, &endquote);
4643 gtk_text_buffer_remove_tag_by_name(
4644 buffer, "quote2", &startquote, &endquote);
4645 modified = TRUE;
4647 break;
4648 case 1:
4649 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4650 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4651 gtk_text_buffer_apply_tag_by_name(
4652 buffer, "quote1", &startquote, &endquote);
4653 gtk_text_buffer_remove_tag_by_name(
4654 buffer, "quote0", &startquote, &endquote);
4655 gtk_text_buffer_remove_tag_by_name(
4656 buffer, "quote2", &startquote, &endquote);
4657 modified = TRUE;
4659 break;
4660 case 2:
4661 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4662 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4663 gtk_text_buffer_apply_tag_by_name(
4664 buffer, "quote2", &startquote, &endquote);
4665 gtk_text_buffer_remove_tag_by_name(
4666 buffer, "quote0", &startquote, &endquote);
4667 gtk_text_buffer_remove_tag_by_name(
4668 buffer, "quote1", &startquote, &endquote);
4669 modified = TRUE;
4671 break;
4673 startq_offset = -1;
4674 } else if (noq_offset != -1) {
4675 GtkTextIter startnoquote, endnoquote;
4676 gtk_text_buffer_get_iter_at_offset(
4677 buffer, &startnoquote, noq_offset);
4678 endnoquote = iter;
4680 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4681 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4682 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4683 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4684 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4685 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4686 gtk_text_buffer_remove_tag_by_name(
4687 buffer, "quote0", &startnoquote, &endnoquote);
4688 gtk_text_buffer_remove_tag_by_name(
4689 buffer, "quote1", &startnoquote, &endnoquote);
4690 gtk_text_buffer_remove_tag_by_name(
4691 buffer, "quote2", &startnoquote, &endnoquote);
4692 modified = TRUE;
4694 noq_offset = -1;
4697 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4698 GtkTextIter nouri_start_iter, nouri_end_iter;
4699 gtk_text_buffer_get_iter_at_offset(
4700 buffer, &nouri_start_iter, nouri_start);
4701 gtk_text_buffer_get_iter_at_offset(
4702 buffer, &nouri_end_iter, nouri_stop);
4703 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4704 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4705 gtk_text_buffer_remove_tag_by_name(
4706 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4707 modified_before_remove = modified;
4708 modified = TRUE;
4709 removed = TRUE;
4712 if (uri_start >= 0 && uri_stop > 0) {
4713 GtkTextIter uri_start_iter, uri_end_iter, back;
4714 gtk_text_buffer_get_iter_at_offset(
4715 buffer, &uri_start_iter, uri_start);
4716 gtk_text_buffer_get_iter_at_offset(
4717 buffer, &uri_end_iter, uri_stop);
4718 back = uri_end_iter;
4719 gtk_text_iter_backward_char(&back);
4720 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4721 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4722 gtk_text_buffer_apply_tag_by_name(
4723 buffer, "link", &uri_start_iter, &uri_end_iter);
4724 modified = TRUE;
4725 if (removed && !modified_before_remove) {
4726 modified = FALSE;
4730 if (!modified) {
4731 // debug_print("not modified, out after %d lines\n", lines);
4732 goto end;
4735 // debug_print("modified, out after %d lines\n", lines);
4736 end:
4737 g_free(itemized_chars);
4738 if (par_iter)
4739 *par_iter = iter;
4740 undo_wrapping(compose->undostruct, FALSE);
4741 compose->autowrap = prev_autowrap;
4743 return modified;
4746 void compose_action_cb(void *data)
4748 Compose *compose = (Compose *)data;
4749 compose_wrap_all(compose);
4752 static void compose_wrap_all(Compose *compose)
4754 compose_wrap_all_full(compose, FALSE);
4757 static void compose_wrap_all_full(Compose *compose, gboolean force)
4759 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4760 GtkTextBuffer *buffer;
4761 GtkTextIter iter;
4762 gboolean modified = TRUE;
4764 buffer = gtk_text_view_get_buffer(text);
4766 gtk_text_buffer_get_start_iter(buffer, &iter);
4768 undo_wrapping(compose->undostruct, TRUE);
4770 while (!gtk_text_iter_is_end(&iter) && modified)
4771 modified = compose_beautify_paragraph(compose, &iter, force);
4773 undo_wrapping(compose->undostruct, FALSE);
4777 static void compose_set_title(Compose *compose)
4779 gchar *str;
4780 gchar *edited;
4781 gchar *subject;
4783 edited = compose->modified ? _(" [Edited]") : "";
4785 subject = gtk_editable_get_chars(
4786 GTK_EDITABLE(compose->subject_entry), 0, -1);
4788 #ifndef GENERIC_UMPC
4789 if (subject && strlen(subject))
4790 str = g_strdup_printf(_("%s - Compose message%s"),
4791 subject, edited);
4792 else
4793 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4794 #else
4795 str = g_strdup(_("Compose message"));
4796 #endif
4798 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4799 g_free(str);
4800 g_free(subject);
4804 * compose_current_mail_account:
4806 * Find a current mail account (the currently selected account, or the
4807 * default account, if a news account is currently selected). If a
4808 * mail account cannot be found, display an error message.
4810 * Return value: Mail account, or NULL if not found.
4812 static PrefsAccount *
4813 compose_current_mail_account(void)
4815 PrefsAccount *ac;
4817 if (cur_account && cur_account->protocol != A_NNTP)
4818 ac = cur_account;
4819 else {
4820 ac = account_get_default();
4821 if (!ac || ac->protocol == A_NNTP) {
4822 alertpanel_error(_("Account for sending mail is not specified.\n"
4823 "Please select a mail account before sending."));
4824 return NULL;
4827 return ac;
4830 #define QUOTE_IF_REQUIRED(out, str) \
4832 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4833 gchar *__tmp; \
4834 gint len; \
4836 len = strlen(str) + 3; \
4837 if ((__tmp = alloca(len)) == NULL) { \
4838 g_warning("can't allocate memory"); \
4839 g_string_free(header, TRUE); \
4840 return NULL; \
4842 g_snprintf(__tmp, len, "\"%s\"", str); \
4843 out = __tmp; \
4844 } else { \
4845 gchar *__tmp; \
4847 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4848 g_warning("can't allocate memory"); \
4849 g_string_free(header, TRUE); \
4850 return NULL; \
4851 } else \
4852 strcpy(__tmp, str); \
4854 out = __tmp; \
4858 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4860 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4861 gchar *__tmp; \
4862 gint len; \
4864 len = strlen(str) + 3; \
4865 if ((__tmp = alloca(len)) == NULL) { \
4866 g_warning("can't allocate memory"); \
4867 errret; \
4869 g_snprintf(__tmp, len, "\"%s\"", str); \
4870 out = __tmp; \
4871 } else { \
4872 gchar *__tmp; \
4874 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4875 g_warning("can't allocate memory"); \
4876 errret; \
4877 } else \
4878 strcpy(__tmp, str); \
4880 out = __tmp; \
4884 static void compose_select_account(Compose *compose, PrefsAccount *account,
4885 gboolean init)
4887 gchar *from = NULL, *header = NULL;
4888 ComposeHeaderEntry *header_entry;
4889 #if GTK_CHECK_VERSION(2, 24, 0)
4890 GtkTreeIter iter;
4891 #endif
4893 cm_return_if_fail(account != NULL);
4895 compose->account = account;
4896 if (account->name && *account->name) {
4897 gchar *buf, *qbuf;
4898 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4899 qbuf = escape_internal_quotes(buf, '"');
4900 from = g_strdup_printf("%s <%s>",
4901 qbuf, account->address);
4902 if (qbuf != buf)
4903 g_free(qbuf);
4904 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4905 } else {
4906 from = g_strdup_printf("<%s>",
4907 account->address);
4908 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4911 g_free(from);
4913 compose_set_title(compose);
4915 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4916 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4917 else
4918 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4919 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4920 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4921 else
4922 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4924 activate_privacy_system(compose, account, FALSE);
4926 if (!init && compose->mode != COMPOSE_REDIRECT) {
4927 undo_block(compose->undostruct);
4928 compose_insert_sig(compose, TRUE);
4929 undo_unblock(compose->undostruct);
4932 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4933 #if !GTK_CHECK_VERSION(2, 24, 0)
4934 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4935 #else
4936 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4937 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4938 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4939 #endif
4941 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4942 if (account->protocol == A_NNTP) {
4943 if (!strcmp(header, _("To:")))
4944 combobox_select_by_text(
4945 GTK_COMBO_BOX(header_entry->combo),
4946 _("Newsgroups:"));
4947 } else {
4948 if (!strcmp(header, _("Newsgroups:")))
4949 combobox_select_by_text(
4950 GTK_COMBO_BOX(header_entry->combo),
4951 _("To:"));
4955 g_free(header);
4957 #ifdef USE_ENCHANT
4958 /* use account's dict info if set */
4959 if (compose->gtkaspell) {
4960 if (account->enable_default_dictionary)
4961 gtkaspell_change_dict(compose->gtkaspell,
4962 account->default_dictionary, FALSE);
4963 if (account->enable_default_alt_dictionary)
4964 gtkaspell_change_alt_dict(compose->gtkaspell,
4965 account->default_alt_dictionary);
4966 if (account->enable_default_dictionary
4967 || account->enable_default_alt_dictionary)
4968 compose_spell_menu_changed(compose);
4970 #endif
4973 gboolean compose_check_for_valid_recipient(Compose *compose) {
4974 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4975 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4976 gboolean recipient_found = FALSE;
4977 GSList *list;
4978 gchar **strptr;
4980 /* free to and newsgroup list */
4981 slist_free_strings_full(compose->to_list);
4982 compose->to_list = NULL;
4984 slist_free_strings_full(compose->newsgroup_list);
4985 compose->newsgroup_list = NULL;
4987 /* search header entries for to and newsgroup entries */
4988 for (list = compose->header_list; list; list = list->next) {
4989 gchar *header;
4990 gchar *entry;
4991 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4992 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4993 g_strstrip(entry);
4994 g_strstrip(header);
4995 if (entry[0] != '\0') {
4996 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4997 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4998 compose->to_list = address_list_append(compose->to_list, entry);
4999 recipient_found = TRUE;
5002 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5003 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5004 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5005 recipient_found = TRUE;
5009 g_free(header);
5010 g_free(entry);
5012 return recipient_found;
5015 static gboolean compose_check_for_set_recipients(Compose *compose)
5017 if (compose->account->set_autocc && compose->account->auto_cc) {
5018 gboolean found_other = FALSE;
5019 GSList *list;
5020 /* search header entries for to and newsgroup entries */
5021 for (list = compose->header_list; list; list = list->next) {
5022 gchar *entry;
5023 gchar *header;
5024 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5025 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5026 g_strstrip(entry);
5027 g_strstrip(header);
5028 if (strcmp(entry, compose->account->auto_cc)
5029 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5030 found_other = TRUE;
5031 g_free(entry);
5032 break;
5034 g_free(entry);
5035 g_free(header);
5037 if (!found_other) {
5038 AlertValue aval;
5039 if (compose->batch) {
5040 gtk_widget_show_all(compose->window);
5042 aval = alertpanel(_("Send"),
5043 _("The only recipient is the default CC address. Send anyway?"),
5044 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5045 if (aval != G_ALERTALTERNATE)
5046 return FALSE;
5049 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5050 gboolean found_other = FALSE;
5051 GSList *list;
5052 /* search header entries for to and newsgroup entries */
5053 for (list = compose->header_list; list; list = list->next) {
5054 gchar *entry;
5055 gchar *header;
5056 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5057 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5058 g_strstrip(entry);
5059 g_strstrip(header);
5060 if (strcmp(entry, compose->account->auto_bcc)
5061 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5062 found_other = TRUE;
5063 g_free(entry);
5064 break;
5066 g_free(entry);
5067 g_free(header);
5069 if (!found_other) {
5070 AlertValue aval;
5071 if (compose->batch) {
5072 gtk_widget_show_all(compose->window);
5074 aval = alertpanel(_("Send"),
5075 _("The only recipient is the default BCC address. Send anyway?"),
5076 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5077 if (aval != G_ALERTALTERNATE)
5078 return FALSE;
5081 return TRUE;
5084 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5086 const gchar *str;
5088 if (compose_check_for_valid_recipient(compose) == FALSE) {
5089 if (compose->batch) {
5090 gtk_widget_show_all(compose->window);
5092 alertpanel_error(_("Recipient is not specified."));
5093 return FALSE;
5096 if (compose_check_for_set_recipients(compose) == FALSE) {
5097 return FALSE;
5100 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5101 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5102 if (*str == '\0' && check_everything == TRUE &&
5103 compose->mode != COMPOSE_REDIRECT) {
5104 AlertValue aval;
5105 gchar *button_label;
5106 gchar *message;
5108 if (compose->sending)
5109 button_label = g_strconcat("+", _("_Send"), NULL);
5110 else
5111 button_label = g_strconcat("+", _("_Queue"), NULL);
5112 message = g_strdup_printf(_("Subject is empty. %s"),
5113 compose->sending?_("Send it anyway?"):
5114 _("Queue it anyway?"));
5116 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5117 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5118 ALERT_QUESTION, G_ALERTDEFAULT);
5119 g_free(message);
5120 if (aval & G_ALERTDISABLE) {
5121 aval &= ~G_ALERTDISABLE;
5122 prefs_common.warn_empty_subj = FALSE;
5124 if (aval != G_ALERTALTERNATE)
5125 return FALSE;
5129 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5130 return FALSE;
5132 return TRUE;
5135 gint compose_send(Compose *compose)
5137 gint msgnum;
5138 FolderItem *folder = NULL;
5139 gint val = -1;
5140 gchar *msgpath = NULL;
5141 gboolean discard_window = FALSE;
5142 gchar *errstr = NULL;
5143 gchar *tmsgid = NULL;
5144 MainWindow *mainwin = mainwindow_get_mainwindow();
5145 gboolean queued_removed = FALSE;
5147 if (prefs_common.send_dialog_invisible
5148 || compose->batch == TRUE)
5149 discard_window = TRUE;
5151 compose_allow_user_actions (compose, FALSE);
5152 compose->sending = TRUE;
5154 if (compose_check_entries(compose, TRUE) == FALSE) {
5155 if (compose->batch) {
5156 gtk_widget_show_all(compose->window);
5158 goto bail;
5161 inc_lock();
5162 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5164 if (val) {
5165 if (compose->batch) {
5166 gtk_widget_show_all(compose->window);
5168 if (val == -4) {
5169 alertpanel_error(_("Could not queue message for sending:\n\n"
5170 "Charset conversion failed."));
5171 } else if (val == -5) {
5172 alertpanel_error(_("Could not queue message for sending:\n\n"
5173 "Couldn't get recipient encryption key."));
5174 } else if (val == -6) {
5175 /* silent error */
5176 } else if (val == -3) {
5177 if (privacy_peek_error())
5178 alertpanel_error(_("Could not queue message for sending:\n\n"
5179 "Signature failed: %s"), privacy_get_error());
5180 } else if (val == -2 && errno != 0) {
5181 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5182 } else {
5183 alertpanel_error(_("Could not queue message for sending."));
5185 goto bail;
5188 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5189 if (discard_window) {
5190 compose->sending = FALSE;
5191 compose_close(compose);
5192 /* No more compose access in the normal codepath
5193 * after this point! */
5194 compose = NULL;
5197 if (msgnum == 0) {
5198 alertpanel_error(_("The message was queued but could not be "
5199 "sent.\nUse \"Send queued messages\" from "
5200 "the main window to retry."));
5201 if (!discard_window) {
5202 goto bail;
5204 inc_unlock();
5205 g_free(tmsgid);
5206 return -1;
5208 if (msgpath == NULL) {
5209 msgpath = folder_item_fetch_msg(folder, msgnum);
5210 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5211 g_free(msgpath);
5212 } else {
5213 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5214 claws_unlink(msgpath);
5215 g_free(msgpath);
5217 if (!discard_window) {
5218 if (val != 0) {
5219 if (!queued_removed)
5220 folder_item_remove_msg(folder, msgnum);
5221 folder_item_scan(folder);
5222 if (tmsgid) {
5223 /* make sure we delete that */
5224 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5225 if (tmp) {
5226 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5227 folder_item_remove_msg(folder, tmp->msgnum);
5228 procmsg_msginfo_free(&tmp);
5234 if (val == 0) {
5235 if (!queued_removed)
5236 folder_item_remove_msg(folder, msgnum);
5237 folder_item_scan(folder);
5238 if (tmsgid) {
5239 /* make sure we delete that */
5240 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5241 if (tmp) {
5242 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5243 folder_item_remove_msg(folder, tmp->msgnum);
5244 procmsg_msginfo_free(&tmp);
5247 if (!discard_window) {
5248 compose->sending = FALSE;
5249 compose_allow_user_actions (compose, TRUE);
5250 compose_close(compose);
5252 } else {
5253 if (errstr) {
5254 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5255 "the main window to retry."), errstr);
5256 g_free(errstr);
5257 } else {
5258 alertpanel_error_log(_("The message was queued but could not be "
5259 "sent.\nUse \"Send queued messages\" from "
5260 "the main window to retry."));
5262 if (!discard_window) {
5263 goto bail;
5265 inc_unlock();
5266 g_free(tmsgid);
5267 return -1;
5269 g_free(tmsgid);
5270 inc_unlock();
5271 toolbar_main_set_sensitive(mainwin);
5272 main_window_set_menu_sensitive(mainwin);
5273 return 0;
5275 bail:
5276 inc_unlock();
5277 g_free(tmsgid);
5278 compose_allow_user_actions (compose, TRUE);
5279 compose->sending = FALSE;
5280 compose->modified = TRUE;
5281 toolbar_main_set_sensitive(mainwin);
5282 main_window_set_menu_sensitive(mainwin);
5284 return -1;
5287 static gboolean compose_use_attach(Compose *compose)
5289 GtkTreeModel *model = gtk_tree_view_get_model
5290 (GTK_TREE_VIEW(compose->attach_clist));
5291 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5294 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5295 FILE *fp)
5297 gchar buf[BUFFSIZE];
5298 gchar *str;
5299 gboolean first_to_address;
5300 gboolean first_cc_address;
5301 GSList *list;
5302 ComposeHeaderEntry *headerentry;
5303 const gchar *headerentryname;
5304 const gchar *cc_hdr;
5305 const gchar *to_hdr;
5306 gboolean err = FALSE;
5308 debug_print("Writing redirect header\n");
5310 cc_hdr = prefs_common_translated_header_name("Cc:");
5311 to_hdr = prefs_common_translated_header_name("To:");
5313 first_to_address = TRUE;
5314 for (list = compose->header_list; list; list = list->next) {
5315 headerentry = ((ComposeHeaderEntry *)list->data);
5316 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5318 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5319 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5320 Xstrdup_a(str, entstr, return -1);
5321 g_strstrip(str);
5322 if (str[0] != '\0') {
5323 compose_convert_header
5324 (compose, buf, sizeof(buf), str,
5325 strlen("Resent-To") + 2, TRUE);
5327 if (first_to_address) {
5328 err |= (fprintf(fp, "Resent-To: ") < 0);
5329 first_to_address = FALSE;
5330 } else {
5331 err |= (fprintf(fp, ",") < 0);
5333 err |= (fprintf(fp, "%s", buf) < 0);
5337 if (!first_to_address) {
5338 err |= (fprintf(fp, "\n") < 0);
5341 first_cc_address = TRUE;
5342 for (list = compose->header_list; list; list = list->next) {
5343 headerentry = ((ComposeHeaderEntry *)list->data);
5344 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5346 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5347 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5348 Xstrdup_a(str, strg, return -1);
5349 g_strstrip(str);
5350 if (str[0] != '\0') {
5351 compose_convert_header
5352 (compose, buf, sizeof(buf), str,
5353 strlen("Resent-Cc") + 2, TRUE);
5355 if (first_cc_address) {
5356 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5357 first_cc_address = FALSE;
5358 } else {
5359 err |= (fprintf(fp, ",") < 0);
5361 err |= (fprintf(fp, "%s", buf) < 0);
5365 if (!first_cc_address) {
5366 err |= (fprintf(fp, "\n") < 0);
5369 return (err ? -1:0);
5372 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5374 gchar buf[BUFFSIZE];
5375 gchar *str;
5376 const gchar *entstr;
5377 /* struct utsname utsbuf; */
5378 gboolean err = FALSE;
5380 cm_return_val_if_fail(fp != NULL, -1);
5381 cm_return_val_if_fail(compose->account != NULL, -1);
5382 cm_return_val_if_fail(compose->account->address != NULL, -1);
5384 /* Resent-Date */
5385 if (prefs_common.hide_timezone)
5386 get_rfc822_date_hide_tz(buf, sizeof(buf));
5387 else
5388 get_rfc822_date(buf, sizeof(buf));
5389 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5391 /* Resent-From */
5392 if (compose->account->name && *compose->account->name) {
5393 compose_convert_header
5394 (compose, buf, sizeof(buf), compose->account->name,
5395 strlen("From: "), TRUE);
5396 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5397 buf, compose->account->address) < 0);
5398 } else
5399 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5401 /* Subject */
5402 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5403 if (*entstr != '\0') {
5404 Xstrdup_a(str, entstr, return -1);
5405 g_strstrip(str);
5406 if (*str != '\0') {
5407 compose_convert_header(compose, buf, sizeof(buf), str,
5408 strlen("Subject: "), FALSE);
5409 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5413 /* Resent-Message-ID */
5414 if (compose->account->set_domain && compose->account->domain) {
5415 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5416 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5417 g_snprintf(buf, sizeof(buf), "%s",
5418 strchr(compose->account->address, '@') ?
5419 strchr(compose->account->address, '@')+1 :
5420 compose->account->address);
5421 } else {
5422 g_snprintf(buf, sizeof(buf), "%s", "");
5425 if (compose->account->gen_msgid) {
5426 gchar *addr = NULL;
5427 if (compose->account->msgid_with_addr) {
5428 addr = compose->account->address;
5430 generate_msgid(buf, sizeof(buf), addr);
5431 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5432 if (compose->msgid)
5433 g_free(compose->msgid);
5434 compose->msgid = g_strdup(buf);
5435 } else {
5436 compose->msgid = NULL;
5439 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5440 return -1;
5442 /* separator between header and body */
5443 err |= (fputs("\n", fp) == EOF);
5445 return (err ? -1:0);
5448 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5450 FILE *fp;
5451 size_t len;
5452 gchar buf[BUFFSIZE];
5453 int i = 0;
5454 gboolean skip = FALSE;
5455 gboolean err = FALSE;
5456 gchar *not_included[]={
5457 "Return-Path:", "Delivered-To:", "Received:",
5458 "Subject:", "X-UIDL:", "AF:",
5459 "NF:", "PS:", "SRH:",
5460 "SFN:", "DSR:", "MID:",
5461 "CFG:", "PT:", "S:",
5462 "RQ:", "SSV:", "NSV:",
5463 "SSH:", "R:", "MAID:",
5464 "NAID:", "RMID:", "FMID:",
5465 "SCF:", "RRCPT:", "NG:",
5466 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5467 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5468 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5469 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5470 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5471 NULL
5473 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5474 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5475 return -1;
5478 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5479 skip = FALSE;
5480 for (i = 0; not_included[i] != NULL; i++) {
5481 if (g_ascii_strncasecmp(buf, not_included[i],
5482 strlen(not_included[i])) == 0) {
5483 skip = TRUE;
5484 break;
5487 if (skip)
5488 continue;
5489 if (fputs(buf, fdest) == -1)
5490 goto error;
5492 if (!prefs_common.redirect_keep_from) {
5493 if (g_ascii_strncasecmp(buf, "From:",
5494 strlen("From:")) == 0) {
5495 err |= (fputs(" (by way of ", fdest) == EOF);
5496 if (compose->account->name
5497 && *compose->account->name) {
5498 compose_convert_header
5499 (compose, buf, sizeof(buf),
5500 compose->account->name,
5501 strlen("From: "),
5502 FALSE);
5503 err |= (fprintf(fdest, "%s <%s>",
5504 buf,
5505 compose->account->address) < 0);
5506 } else
5507 err |= (fprintf(fdest, "%s",
5508 compose->account->address) < 0);
5509 err |= (fputs(")", fdest) == EOF);
5513 if (fputs("\n", fdest) == -1)
5514 goto error;
5517 if (err)
5518 goto error;
5520 if (compose_redirect_write_headers(compose, fdest))
5521 goto error;
5523 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5524 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5525 goto error;
5528 fclose(fp);
5530 return 0;
5531 error:
5532 fclose(fp);
5534 return -1;
5537 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5539 GtkTextBuffer *buffer;
5540 GtkTextIter start, end;
5541 gchar *chars, *tmp_enc_file, *content;
5542 gchar *buf, *msg;
5543 const gchar *out_codeset;
5544 EncodingType encoding = ENC_UNKNOWN;
5545 MimeInfo *mimemsg, *mimetext;
5546 gint line;
5547 const gchar *src_codeset = CS_INTERNAL;
5548 gchar *from_addr = NULL;
5549 gchar *from_name = NULL;
5550 FolderItem *outbox;
5552 if (action == COMPOSE_WRITE_FOR_SEND)
5553 attach_parts = TRUE;
5555 /* create message MimeInfo */
5556 mimemsg = procmime_mimeinfo_new();
5557 mimemsg->type = MIMETYPE_MESSAGE;
5558 mimemsg->subtype = g_strdup("rfc822");
5559 mimemsg->content = MIMECONTENT_MEM;
5560 mimemsg->tmp = TRUE; /* must free content later */
5561 mimemsg->data.mem = compose_get_header(compose);
5563 /* Create text part MimeInfo */
5564 /* get all composed text */
5565 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5566 gtk_text_buffer_get_start_iter(buffer, &start);
5567 gtk_text_buffer_get_end_iter(buffer, &end);
5568 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5570 out_codeset = conv_get_charset_str(compose->out_encoding);
5572 if (!out_codeset && is_ascii_str(chars)) {
5573 out_codeset = CS_US_ASCII;
5574 } else if (prefs_common.outgoing_fallback_to_ascii &&
5575 is_ascii_str(chars)) {
5576 out_codeset = CS_US_ASCII;
5577 encoding = ENC_7BIT;
5580 if (!out_codeset) {
5581 gchar *test_conv_global_out = NULL;
5582 gchar *test_conv_reply = NULL;
5584 /* automatic mode. be automatic. */
5585 codeconv_set_strict(TRUE);
5587 out_codeset = conv_get_outgoing_charset_str();
5588 if (out_codeset) {
5589 debug_print("trying to convert to %s\n", out_codeset);
5590 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5593 if (!test_conv_global_out && compose->orig_charset
5594 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5595 out_codeset = compose->orig_charset;
5596 debug_print("failure; trying to convert to %s\n", out_codeset);
5597 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5600 if (!test_conv_global_out && !test_conv_reply) {
5601 /* we're lost */
5602 out_codeset = CS_INTERNAL;
5603 debug_print("failure; finally using %s\n", out_codeset);
5605 g_free(test_conv_global_out);
5606 g_free(test_conv_reply);
5607 codeconv_set_strict(FALSE);
5610 if (encoding == ENC_UNKNOWN) {
5611 if (prefs_common.encoding_method == CTE_BASE64)
5612 encoding = ENC_BASE64;
5613 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5614 encoding = ENC_QUOTED_PRINTABLE;
5615 else if (prefs_common.encoding_method == CTE_8BIT)
5616 encoding = ENC_8BIT;
5617 else
5618 encoding = procmime_get_encoding_for_charset(out_codeset);
5621 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5622 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5624 if (action == COMPOSE_WRITE_FOR_SEND) {
5625 codeconv_set_strict(TRUE);
5626 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5627 codeconv_set_strict(FALSE);
5629 if (!buf) {
5630 AlertValue aval;
5632 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5633 "to the specified %s charset.\n"
5634 "Send it as %s?"), out_codeset, src_codeset);
5635 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5636 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5637 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5638 g_free(msg);
5640 if (aval != G_ALERTALTERNATE) {
5641 g_free(chars);
5642 return -3;
5643 } else {
5644 buf = chars;
5645 out_codeset = src_codeset;
5646 chars = NULL;
5649 } else {
5650 buf = chars;
5651 out_codeset = src_codeset;
5652 chars = NULL;
5654 g_free(chars);
5656 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5657 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5658 strstr(buf, "\nFrom ") != NULL) {
5659 encoding = ENC_QUOTED_PRINTABLE;
5663 mimetext = procmime_mimeinfo_new();
5664 mimetext->content = MIMECONTENT_MEM;
5665 mimetext->tmp = TRUE; /* must free content later */
5666 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5667 * and free the data, which we need later. */
5668 mimetext->data.mem = g_strdup(buf);
5669 mimetext->type = MIMETYPE_TEXT;
5670 mimetext->subtype = g_strdup("plain");
5671 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5672 g_strdup(out_codeset));
5674 /* protect trailing spaces when signing message */
5675 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5676 privacy_system_can_sign(compose->privacy_system)) {
5677 encoding = ENC_QUOTED_PRINTABLE;
5680 debug_print("main text: %zd bytes encoded as %s in %d\n",
5681 strlen(buf), out_codeset, encoding);
5683 /* check for line length limit */
5684 if (action == COMPOSE_WRITE_FOR_SEND &&
5685 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5686 check_line_length(buf, 1000, &line) < 0) {
5687 AlertValue aval;
5689 msg = g_strdup_printf
5690 (_("Line %d exceeds the line length limit (998 bytes).\n"
5691 "The contents of the message might be broken on the way to the delivery.\n"
5692 "\n"
5693 "Send it anyway?"), line + 1);
5694 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5695 g_free(msg);
5696 if (aval != G_ALERTALTERNATE) {
5697 g_free(buf);
5698 return -1;
5702 if (encoding != ENC_UNKNOWN)
5703 procmime_encode_content(mimetext, encoding);
5705 /* append attachment parts */
5706 if (compose_use_attach(compose) && attach_parts) {
5707 MimeInfo *mimempart;
5708 gchar *boundary = NULL;
5709 mimempart = procmime_mimeinfo_new();
5710 mimempart->content = MIMECONTENT_EMPTY;
5711 mimempart->type = MIMETYPE_MULTIPART;
5712 mimempart->subtype = g_strdup("mixed");
5714 do {
5715 g_free(boundary);
5716 boundary = generate_mime_boundary(NULL);
5717 } while (strstr(buf, boundary) != NULL);
5719 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5720 boundary);
5722 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5724 g_node_append(mimempart->node, mimetext->node);
5725 g_node_append(mimemsg->node, mimempart->node);
5727 if (compose_add_attachments(compose, mimempart) < 0)
5728 return -1;
5729 } else
5730 g_node_append(mimemsg->node, mimetext->node);
5732 g_free(buf);
5734 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5735 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5736 /* extract name and address */
5737 if (strstr(spec, " <") && strstr(spec, ">")) {
5738 from_addr = g_strdup(strrchr(spec, '<')+1);
5739 *(strrchr(from_addr, '>')) = '\0';
5740 from_name = g_strdup(spec);
5741 *(strrchr(from_name, '<')) = '\0';
5742 } else {
5743 from_name = NULL;
5744 from_addr = NULL;
5746 g_free(spec);
5748 /* sign message if sending */
5749 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5750 privacy_system_can_sign(compose->privacy_system))
5751 if (!privacy_sign(compose->privacy_system, mimemsg,
5752 compose->account, from_addr)) {
5753 g_free(from_name);
5754 g_free(from_addr);
5755 return -2;
5757 g_free(from_name);
5758 g_free(from_addr);
5760 if (compose->use_encryption) {
5761 if (compose->encdata != NULL &&
5762 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5764 /* First, write an unencrypted copy and save it to outbox, if
5765 * user wants that. */
5766 if (compose->account->save_encrypted_as_clear_text) {
5767 debug_print("saving sent message unencrypted...\n");
5768 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5769 if (tmpfp) {
5770 fclose(tmpfp);
5772 /* fp now points to a file with headers written,
5773 * let's make a copy. */
5774 rewind(fp);
5775 content = file_read_stream_to_str(fp);
5777 str_write_to_file(content, tmp_enc_file);
5778 g_free(content);
5780 /* Now write the unencrypted body. */
5781 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5782 procmime_write_mimeinfo(mimemsg, tmpfp);
5783 fclose(tmpfp);
5785 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5786 if (!outbox)
5787 outbox = folder_get_default_outbox();
5789 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5790 claws_unlink(tmp_enc_file);
5791 } else {
5792 g_warning("Can't open file '%s'", tmp_enc_file);
5794 } else {
5795 g_warning("couldn't get tempfile");
5798 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5799 debug_print("Couldn't encrypt mime structure: %s.\n",
5800 privacy_get_error());
5801 alertpanel_error(_("Couldn't encrypt the email: %s"),
5802 privacy_get_error());
5807 procmime_write_mimeinfo(mimemsg, fp);
5809 procmime_mimeinfo_free_all(&mimemsg);
5811 return 0;
5814 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5816 GtkTextBuffer *buffer;
5817 GtkTextIter start, end;
5818 FILE *fp;
5819 size_t len;
5820 gchar *chars, *tmp;
5822 if ((fp = g_fopen(file, "wb")) == NULL) {
5823 FILE_OP_ERROR(file, "fopen");
5824 return -1;
5827 /* chmod for security */
5828 if (change_file_mode_rw(fp, file) < 0) {
5829 FILE_OP_ERROR(file, "chmod");
5830 g_warning("can't change file mode");
5833 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5834 gtk_text_buffer_get_start_iter(buffer, &start);
5835 gtk_text_buffer_get_end_iter(buffer, &end);
5836 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5838 chars = conv_codeset_strdup
5839 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5841 g_free(tmp);
5842 if (!chars) {
5843 fclose(fp);
5844 claws_unlink(file);
5845 return -1;
5847 /* write body */
5848 len = strlen(chars);
5849 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5850 FILE_OP_ERROR(file, "fwrite");
5851 g_free(chars);
5852 fclose(fp);
5853 claws_unlink(file);
5854 return -1;
5857 g_free(chars);
5859 if (fclose(fp) == EOF) {
5860 FILE_OP_ERROR(file, "fclose");
5861 claws_unlink(file);
5862 return -1;
5864 return 0;
5867 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5869 FolderItem *item;
5870 MsgInfo *msginfo = compose->targetinfo;
5872 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5873 if (!msginfo) return -1;
5875 if (!force && MSG_IS_LOCKED(msginfo->flags))
5876 return 0;
5878 item = msginfo->folder;
5879 cm_return_val_if_fail(item != NULL, -1);
5881 if (procmsg_msg_exist(msginfo) &&
5882 (folder_has_parent_of_type(item, F_QUEUE) ||
5883 folder_has_parent_of_type(item, F_DRAFT)
5884 || msginfo == compose->autosaved_draft)) {
5885 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5886 g_warning("can't remove the old message");
5887 return -1;
5888 } else {
5889 debug_print("removed reedit target %d\n", msginfo->msgnum);
5893 return 0;
5896 static void compose_remove_draft(Compose *compose)
5898 FolderItem *drafts;
5899 MsgInfo *msginfo = compose->targetinfo;
5900 drafts = account_get_special_folder(compose->account, F_DRAFT);
5902 if (procmsg_msg_exist(msginfo)) {
5903 folder_item_remove_msg(drafts, msginfo->msgnum);
5908 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5909 gboolean remove_reedit_target)
5911 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5914 static gboolean compose_warn_encryption(Compose *compose)
5916 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5917 AlertValue val = G_ALERTALTERNATE;
5919 if (warning == NULL)
5920 return TRUE;
5922 val = alertpanel_full(_("Encryption warning"), warning,
5923 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
5924 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5925 if (val & G_ALERTDISABLE) {
5926 val &= ~G_ALERTDISABLE;
5927 if (val == G_ALERTALTERNATE)
5928 privacy_inhibit_encrypt_warning(compose->privacy_system,
5929 TRUE);
5932 if (val == G_ALERTALTERNATE) {
5933 return TRUE;
5934 } else {
5935 return FALSE;
5939 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5940 gchar **msgpath, gboolean check_subject,
5941 gboolean remove_reedit_target)
5943 FolderItem *queue;
5944 gchar *tmp;
5945 FILE *fp;
5946 GSList *cur;
5947 gint num;
5948 PrefsAccount *mailac = NULL, *newsac = NULL;
5949 gboolean err = FALSE;
5951 debug_print("queueing message...\n");
5952 cm_return_val_if_fail(compose->account != NULL, -1);
5954 if (compose_check_entries(compose, check_subject) == FALSE) {
5955 if (compose->batch) {
5956 gtk_widget_show_all(compose->window);
5958 return -1;
5961 if (!compose->to_list && !compose->newsgroup_list) {
5962 g_warning("can't get recipient list.");
5963 return -1;
5966 if (compose->to_list) {
5967 if (compose->account->protocol != A_NNTP)
5968 mailac = compose->account;
5969 else if (cur_account && cur_account->protocol != A_NNTP)
5970 mailac = cur_account;
5971 else if (!(mailac = compose_current_mail_account())) {
5972 alertpanel_error(_("No account for sending mails available!"));
5973 return -1;
5977 if (compose->newsgroup_list) {
5978 if (compose->account->protocol == A_NNTP)
5979 newsac = compose->account;
5980 else {
5981 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5982 return -1;
5986 /* write queue header */
5987 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5988 G_DIR_SEPARATOR, compose, (guint) rand());
5989 debug_print("queuing to %s\n", tmp);
5990 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
5991 FILE_OP_ERROR(tmp, "fopen");
5992 g_free(tmp);
5993 return -2;
5996 if (change_file_mode_rw(fp, tmp) < 0) {
5997 FILE_OP_ERROR(tmp, "chmod");
5998 g_warning("can't change file mode");
6001 /* queueing variables */
6002 err |= (fprintf(fp, "AF:\n") < 0);
6003 err |= (fprintf(fp, "NF:0\n") < 0);
6004 err |= (fprintf(fp, "PS:10\n") < 0);
6005 err |= (fprintf(fp, "SRH:1\n") < 0);
6006 err |= (fprintf(fp, "SFN:\n") < 0);
6007 err |= (fprintf(fp, "DSR:\n") < 0);
6008 if (compose->msgid)
6009 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6010 else
6011 err |= (fprintf(fp, "MID:\n") < 0);
6012 err |= (fprintf(fp, "CFG:\n") < 0);
6013 err |= (fprintf(fp, "PT:0\n") < 0);
6014 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6015 err |= (fprintf(fp, "RQ:\n") < 0);
6016 if (mailac)
6017 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6018 else
6019 err |= (fprintf(fp, "SSV:\n") < 0);
6020 if (newsac)
6021 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6022 else
6023 err |= (fprintf(fp, "NSV:\n") < 0);
6024 err |= (fprintf(fp, "SSH:\n") < 0);
6025 /* write recepient list */
6026 if (compose->to_list) {
6027 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6028 for (cur = compose->to_list->next; cur != NULL;
6029 cur = cur->next)
6030 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6031 err |= (fprintf(fp, "\n") < 0);
6033 /* write newsgroup list */
6034 if (compose->newsgroup_list) {
6035 err |= (fprintf(fp, "NG:") < 0);
6036 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6037 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6038 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6039 err |= (fprintf(fp, "\n") < 0);
6041 /* Sylpheed account IDs */
6042 if (mailac)
6043 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6044 if (newsac)
6045 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6048 if (compose->privacy_system != NULL) {
6049 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6050 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6051 if (compose->use_encryption) {
6052 if (!compose_warn_encryption(compose)) {
6053 fclose(fp);
6054 claws_unlink(tmp);
6055 g_free(tmp);
6056 return -6;
6058 if (mailac && mailac->encrypt_to_self) {
6059 GSList *tmp_list = g_slist_copy(compose->to_list);
6060 tmp_list = g_slist_append(tmp_list, compose->account->address);
6061 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6062 g_slist_free(tmp_list);
6063 } else {
6064 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6066 if (compose->encdata != NULL) {
6067 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6068 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6069 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6070 compose->encdata) < 0);
6071 } /* else we finally dont want to encrypt */
6072 } else {
6073 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6074 /* and if encdata was null, it means there's been a problem in
6075 * key selection */
6076 if (err == TRUE)
6077 g_warning("failed to write queue message");
6078 fclose(fp);
6079 claws_unlink(tmp);
6080 g_free(tmp);
6081 return -5;
6086 /* Save copy folder */
6087 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6088 gchar *savefolderid;
6090 savefolderid = compose_get_save_to(compose);
6091 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6092 g_free(savefolderid);
6094 /* Save copy folder */
6095 if (compose->return_receipt) {
6096 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6098 /* Message-ID of message replying to */
6099 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6100 gchar *folderid = NULL;
6102 if (compose->replyinfo->folder)
6103 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6104 if (folderid == NULL)
6105 folderid = g_strdup("NULL");
6107 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6108 g_free(folderid);
6110 /* Message-ID of message forwarding to */
6111 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6112 gchar *folderid = NULL;
6114 if (compose->fwdinfo->folder)
6115 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6116 if (folderid == NULL)
6117 folderid = g_strdup("NULL");
6119 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6120 g_free(folderid);
6123 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6124 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6126 /* end of headers */
6127 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6129 if (compose->redirect_filename != NULL) {
6130 if (compose_redirect_write_to_file(compose, fp) < 0) {
6131 fclose(fp);
6132 claws_unlink(tmp);
6133 g_free(tmp);
6134 return -2;
6136 } else {
6137 gint result = 0;
6138 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6139 fclose(fp);
6140 claws_unlink(tmp);
6141 g_free(tmp);
6142 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6145 if (err == TRUE) {
6146 g_warning("failed to write queue message");
6147 fclose(fp);
6148 claws_unlink(tmp);
6149 g_free(tmp);
6150 return -2;
6152 if (fclose(fp) == EOF) {
6153 FILE_OP_ERROR(tmp, "fclose");
6154 claws_unlink(tmp);
6155 g_free(tmp);
6156 return -2;
6159 if (item && *item) {
6160 queue = *item;
6161 } else {
6162 queue = account_get_special_folder(compose->account, F_QUEUE);
6164 if (!queue) {
6165 g_warning("can't find queue folder");
6166 claws_unlink(tmp);
6167 g_free(tmp);
6168 return -1;
6170 folder_item_scan(queue);
6171 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6172 g_warning("can't queue the message");
6173 claws_unlink(tmp);
6174 g_free(tmp);
6175 return -1;
6178 if (msgpath == NULL) {
6179 claws_unlink(tmp);
6180 g_free(tmp);
6181 } else
6182 *msgpath = tmp;
6184 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6185 compose_remove_reedit_target(compose, FALSE);
6188 if ((msgnum != NULL) && (item != NULL)) {
6189 *msgnum = num;
6190 *item = queue;
6193 return 0;
6196 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6198 AttachInfo *ainfo;
6199 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6200 MimeInfo *mimepart;
6201 GStatBuf statbuf;
6202 gchar *type, *subtype;
6203 GtkTreeModel *model;
6204 GtkTreeIter iter;
6206 model = gtk_tree_view_get_model(tree_view);
6208 if (!gtk_tree_model_get_iter_first(model, &iter))
6209 return 0;
6210 do {
6211 gtk_tree_model_get(model, &iter,
6212 COL_DATA, &ainfo,
6213 -1);
6215 if (!is_file_exist(ainfo->file)) {
6216 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6217 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6218 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6219 g_free(msg);
6220 if (val == G_ALERTDEFAULT) {
6221 return -1;
6223 continue;
6225 if (g_stat(ainfo->file, &statbuf) < 0)
6226 return -1;
6228 mimepart = procmime_mimeinfo_new();
6229 mimepart->content = MIMECONTENT_FILE;
6230 mimepart->data.filename = g_strdup(ainfo->file);
6231 mimepart->tmp = FALSE; /* or we destroy our attachment */
6232 mimepart->offset = 0;
6233 mimepart->length = statbuf.st_size;
6235 type = g_strdup(ainfo->content_type);
6237 if (!strchr(type, '/')) {
6238 g_free(type);
6239 type = g_strdup("application/octet-stream");
6242 subtype = strchr(type, '/') + 1;
6243 *(subtype - 1) = '\0';
6244 mimepart->type = procmime_get_media_type(type);
6245 mimepart->subtype = g_strdup(subtype);
6246 g_free(type);
6248 if (mimepart->type == MIMETYPE_MESSAGE &&
6249 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6250 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6251 } else if (mimepart->type == MIMETYPE_TEXT) {
6252 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6253 /* Text parts with no name come from multipart/alternative
6254 * forwards. Make sure the recipient won't look at the
6255 * original HTML part by mistake. */
6256 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6257 ainfo->name = g_strdup_printf(_("Original %s part"),
6258 mimepart->subtype);
6260 if (ainfo->charset)
6261 g_hash_table_insert(mimepart->typeparameters,
6262 g_strdup("charset"), g_strdup(ainfo->charset));
6264 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6265 if (mimepart->type == MIMETYPE_APPLICATION &&
6266 !strcmp2(mimepart->subtype, "octet-stream"))
6267 g_hash_table_insert(mimepart->typeparameters,
6268 g_strdup("name"), g_strdup(ainfo->name));
6269 g_hash_table_insert(mimepart->dispositionparameters,
6270 g_strdup("filename"), g_strdup(ainfo->name));
6271 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6274 if (mimepart->type == MIMETYPE_MESSAGE
6275 || mimepart->type == MIMETYPE_MULTIPART)
6276 ainfo->encoding = ENC_BINARY;
6277 else if (compose->use_signing) {
6278 if (ainfo->encoding == ENC_7BIT)
6279 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6280 else if (ainfo->encoding == ENC_8BIT)
6281 ainfo->encoding = ENC_BASE64;
6286 procmime_encode_content(mimepart, ainfo->encoding);
6288 g_node_append(parent->node, mimepart->node);
6289 } while (gtk_tree_model_iter_next(model, &iter));
6291 return 0;
6294 static gchar *compose_quote_list_of_addresses(gchar *str)
6296 GSList *list = NULL, *item = NULL;
6297 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6299 list = address_list_append_with_comments(list, str);
6300 for (item = list; item != NULL; item = item->next) {
6301 gchar *spec = item->data;
6302 gchar *endofname = strstr(spec, " <");
6303 if (endofname != NULL) {
6304 gchar * qqname;
6305 *endofname = '\0';
6306 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6307 qqname = escape_internal_quotes(qname, '"');
6308 *endofname = ' ';
6309 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6310 gchar *addr = g_strdup(endofname);
6311 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6312 faddr = g_strconcat(name, addr, NULL);
6313 g_free(name);
6314 g_free(addr);
6315 debug_print("new auto-quoted address: '%s'\n", faddr);
6318 if (result == NULL)
6319 result = g_strdup((faddr != NULL)? faddr: spec);
6320 else {
6321 result = g_strconcat(result,
6322 ", ",
6323 (faddr != NULL)? faddr: spec,
6324 NULL);
6326 if (faddr != NULL) {
6327 g_free(faddr);
6328 faddr = NULL;
6331 slist_free_strings_full(list);
6333 return result;
6336 #define IS_IN_CUSTOM_HEADER(header) \
6337 (compose->account->add_customhdr && \
6338 custom_header_find(compose->account->customhdr_list, header) != NULL)
6340 static void compose_add_headerfield_from_headerlist(Compose *compose,
6341 GString *header,
6342 const gchar *fieldname,
6343 const gchar *seperator)
6345 gchar *str, *fieldname_w_colon;
6346 gboolean add_field = FALSE;
6347 GSList *list;
6348 ComposeHeaderEntry *headerentry;
6349 const gchar *headerentryname;
6350 const gchar *trans_fieldname;
6351 GString *fieldstr;
6353 if (IS_IN_CUSTOM_HEADER(fieldname))
6354 return;
6356 debug_print("Adding %s-fields\n", fieldname);
6358 fieldstr = g_string_sized_new(64);
6360 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6361 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6363 for (list = compose->header_list; list; list = list->next) {
6364 headerentry = ((ComposeHeaderEntry *)list->data);
6365 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6367 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6368 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6369 g_strstrip(ustr);
6370 str = compose_quote_list_of_addresses(ustr);
6371 g_free(ustr);
6372 if (str != NULL && str[0] != '\0') {
6373 if (add_field)
6374 g_string_append(fieldstr, seperator);
6375 g_string_append(fieldstr, str);
6376 add_field = TRUE;
6378 g_free(str);
6381 if (add_field) {
6382 gchar *buf;
6384 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6385 compose_convert_header
6386 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6387 strlen(fieldname) + 2, TRUE);
6388 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6389 g_free(buf);
6392 g_free(fieldname_w_colon);
6393 g_string_free(fieldstr, TRUE);
6395 return;
6398 static gchar *compose_get_manual_headers_info(Compose *compose)
6400 GString *sh_header = g_string_new(" ");
6401 GSList *list;
6402 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6404 for (list = compose->header_list; list; list = list->next) {
6405 ComposeHeaderEntry *headerentry;
6406 gchar *tmp;
6407 gchar *headername;
6408 gchar *headername_wcolon;
6409 const gchar *headername_trans;
6410 gchar **string;
6411 gboolean standard_header = FALSE;
6413 headerentry = ((ComposeHeaderEntry *)list->data);
6415 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6416 g_strstrip(tmp);
6417 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6418 g_free(tmp);
6419 continue;
6422 if (!strstr(tmp, ":")) {
6423 headername_wcolon = g_strconcat(tmp, ":", NULL);
6424 headername = g_strdup(tmp);
6425 } else {
6426 headername_wcolon = g_strdup(tmp);
6427 headername = g_strdup(strtok(tmp, ":"));
6429 g_free(tmp);
6431 string = std_headers;
6432 while (*string != NULL) {
6433 headername_trans = prefs_common_translated_header_name(*string);
6434 if (!strcmp(headername_trans, headername_wcolon))
6435 standard_header = TRUE;
6436 string++;
6438 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6439 g_string_append_printf(sh_header, "%s ", headername);
6440 g_free(headername);
6441 g_free(headername_wcolon);
6443 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6444 return g_string_free(sh_header, FALSE);
6447 static gchar *compose_get_header(Compose *compose)
6449 gchar buf[BUFFSIZE];
6450 const gchar *entry_str;
6451 gchar *str;
6452 gchar *name;
6453 GSList *list;
6454 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6455 GString *header;
6456 gchar *from_name = NULL, *from_address = NULL;
6457 gchar *tmp;
6459 cm_return_val_if_fail(compose->account != NULL, NULL);
6460 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6462 header = g_string_sized_new(64);
6464 /* Date */
6465 if (prefs_common.hide_timezone)
6466 get_rfc822_date_hide_tz(buf, sizeof(buf));
6467 else
6468 get_rfc822_date(buf, sizeof(buf));
6469 g_string_append_printf(header, "Date: %s\n", buf);
6471 /* From */
6473 if (compose->account->name && *compose->account->name) {
6474 gchar *buf;
6475 QUOTE_IF_REQUIRED(buf, compose->account->name);
6476 tmp = g_strdup_printf("%s <%s>",
6477 buf, compose->account->address);
6478 } else {
6479 tmp = g_strdup_printf("%s",
6480 compose->account->address);
6482 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6483 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6484 /* use default */
6485 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6486 from_address = g_strdup(compose->account->address);
6487 } else {
6488 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6489 /* extract name and address */
6490 if (strstr(spec, " <") && strstr(spec, ">")) {
6491 from_address = g_strdup(strrchr(spec, '<')+1);
6492 *(strrchr(from_address, '>')) = '\0';
6493 from_name = g_strdup(spec);
6494 *(strrchr(from_name, '<')) = '\0';
6495 } else {
6496 from_name = NULL;
6497 from_address = g_strdup(spec);
6499 g_free(spec);
6501 g_free(tmp);
6504 if (from_name && *from_name) {
6505 gchar *qname;
6506 compose_convert_header
6507 (compose, buf, sizeof(buf), from_name,
6508 strlen("From: "), TRUE);
6509 QUOTE_IF_REQUIRED(name, buf);
6510 qname = escape_internal_quotes(name, '"');
6512 g_string_append_printf(header, "From: %s <%s>\n",
6513 qname, from_address);
6514 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6515 compose->return_receipt) {
6516 compose_convert_header(compose, buf, sizeof(buf), from_name,
6517 strlen("Disposition-Notification-To: "),
6518 TRUE);
6519 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6521 if (qname != name)
6522 g_free(qname);
6523 } else {
6524 g_string_append_printf(header, "From: %s\n", from_address);
6525 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6526 compose->return_receipt)
6527 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6530 g_free(from_name);
6531 g_free(from_address);
6533 /* To */
6534 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6536 /* Newsgroups */
6537 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6539 /* Cc */
6540 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6542 /* Bcc */
6544 * If this account is a NNTP account remove Bcc header from
6545 * message body since it otherwise will be publicly shown
6547 if (compose->account->protocol != A_NNTP)
6548 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6550 /* Subject */
6551 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6553 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6554 g_strstrip(str);
6555 if (*str != '\0') {
6556 compose_convert_header(compose, buf, sizeof(buf), str,
6557 strlen("Subject: "), FALSE);
6558 g_string_append_printf(header, "Subject: %s\n", buf);
6561 g_free(str);
6563 /* Message-ID */
6564 if (compose->account->set_domain && compose->account->domain) {
6565 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6566 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6567 g_snprintf(buf, sizeof(buf), "%s",
6568 strchr(compose->account->address, '@') ?
6569 strchr(compose->account->address, '@')+1 :
6570 compose->account->address);
6571 } else {
6572 g_snprintf(buf, sizeof(buf), "%s", "");
6575 if (compose->account->gen_msgid) {
6576 gchar *addr = NULL;
6577 if (compose->account->msgid_with_addr) {
6578 addr = compose->account->address;
6580 generate_msgid(buf, sizeof(buf), addr);
6581 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6582 if (compose->msgid)
6583 g_free(compose->msgid);
6584 compose->msgid = g_strdup(buf);
6585 } else {
6586 compose->msgid = NULL;
6589 if (compose->remove_references == FALSE) {
6590 /* In-Reply-To */
6591 if (compose->inreplyto && compose->to_list)
6592 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6594 /* References */
6595 if (compose->references)
6596 g_string_append_printf(header, "References: %s\n", compose->references);
6599 /* Followup-To */
6600 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6602 /* Reply-To */
6603 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6605 /* Organization */
6606 if (compose->account->organization &&
6607 strlen(compose->account->organization) &&
6608 !IS_IN_CUSTOM_HEADER("Organization")) {
6609 compose_convert_header(compose, buf, sizeof(buf),
6610 compose->account->organization,
6611 strlen("Organization: "), FALSE);
6612 g_string_append_printf(header, "Organization: %s\n", buf);
6615 /* Program version and system info */
6616 if (compose->account->gen_xmailer &&
6617 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6618 !compose->newsgroup_list) {
6619 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6620 prog_version,
6621 gtk_major_version, gtk_minor_version, gtk_micro_version,
6622 TARGET_ALIAS);
6624 if (compose->account->gen_xmailer &&
6625 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6626 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6627 prog_version,
6628 gtk_major_version, gtk_minor_version, gtk_micro_version,
6629 TARGET_ALIAS);
6632 /* custom headers */
6633 if (compose->account->add_customhdr) {
6634 GSList *cur;
6636 for (cur = compose->account->customhdr_list; cur != NULL;
6637 cur = cur->next) {
6638 CustomHeader *chdr = (CustomHeader *)cur->data;
6640 if (custom_header_is_allowed(chdr->name)
6641 && chdr->value != NULL
6642 && *(chdr->value) != '\0') {
6643 compose_convert_header
6644 (compose, buf, sizeof(buf),
6645 chdr->value,
6646 strlen(chdr->name) + 2, FALSE);
6647 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6652 /* Automatic Faces and X-Faces */
6653 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6654 g_string_append_printf(header, "X-Face: %s\n", buf);
6656 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6657 g_string_append_printf(header, "X-Face: %s\n", buf);
6659 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6660 g_string_append_printf(header, "Face: %s\n", buf);
6662 else if (get_default_face (buf, sizeof(buf)) == 0) {
6663 g_string_append_printf(header, "Face: %s\n", buf);
6666 /* PRIORITY */
6667 switch (compose->priority) {
6668 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6669 "X-Priority: 1 (Highest)\n");
6670 break;
6671 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6672 "X-Priority: 2 (High)\n");
6673 break;
6674 case PRIORITY_NORMAL: break;
6675 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6676 "X-Priority: 4 (Low)\n");
6677 break;
6678 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6679 "X-Priority: 5 (Lowest)\n");
6680 break;
6681 default: debug_print("compose: priority unknown : %d\n",
6682 compose->priority);
6685 /* get special headers */
6686 for (list = compose->header_list; list; list = list->next) {
6687 ComposeHeaderEntry *headerentry;
6688 gchar *tmp;
6689 gchar *headername;
6690 gchar *headername_wcolon;
6691 const gchar *headername_trans;
6692 gchar *headervalue;
6693 gchar **string;
6694 gboolean standard_header = FALSE;
6696 headerentry = ((ComposeHeaderEntry *)list->data);
6698 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6699 g_strstrip(tmp);
6700 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6701 g_free(tmp);
6702 continue;
6705 if (!strstr(tmp, ":")) {
6706 headername_wcolon = g_strconcat(tmp, ":", NULL);
6707 headername = g_strdup(tmp);
6708 } else {
6709 headername_wcolon = g_strdup(tmp);
6710 headername = g_strdup(strtok(tmp, ":"));
6712 g_free(tmp);
6714 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6715 Xstrdup_a(headervalue, entry_str, return NULL);
6716 subst_char(headervalue, '\r', ' ');
6717 subst_char(headervalue, '\n', ' ');
6718 string = std_headers;
6719 while (*string != NULL) {
6720 headername_trans = prefs_common_translated_header_name(*string);
6721 if (!strcmp(headername_trans, headername_wcolon))
6722 standard_header = TRUE;
6723 string++;
6725 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6726 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6728 g_free(headername);
6729 g_free(headername_wcolon);
6732 str = header->str;
6733 g_string_free(header, FALSE);
6735 return str;
6738 #undef IS_IN_CUSTOM_HEADER
6740 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6741 gint header_len, gboolean addr_field)
6743 gchar *tmpstr = NULL;
6744 const gchar *out_codeset = NULL;
6746 cm_return_if_fail(src != NULL);
6747 cm_return_if_fail(dest != NULL);
6749 if (len < 1) return;
6751 tmpstr = g_strdup(src);
6753 subst_char(tmpstr, '\n', ' ');
6754 subst_char(tmpstr, '\r', ' ');
6755 g_strchomp(tmpstr);
6757 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6758 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6759 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6760 g_free(tmpstr);
6761 tmpstr = mybuf;
6764 codeconv_set_strict(TRUE);
6765 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6766 conv_get_charset_str(compose->out_encoding));
6767 codeconv_set_strict(FALSE);
6769 if (!dest || *dest == '\0') {
6770 gchar *test_conv_global_out = NULL;
6771 gchar *test_conv_reply = NULL;
6773 /* automatic mode. be automatic. */
6774 codeconv_set_strict(TRUE);
6776 out_codeset = conv_get_outgoing_charset_str();
6777 if (out_codeset) {
6778 debug_print("trying to convert to %s\n", out_codeset);
6779 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6782 if (!test_conv_global_out && compose->orig_charset
6783 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6784 out_codeset = compose->orig_charset;
6785 debug_print("failure; trying to convert to %s\n", out_codeset);
6786 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6789 if (!test_conv_global_out && !test_conv_reply) {
6790 /* we're lost */
6791 out_codeset = CS_INTERNAL;
6792 debug_print("finally using %s\n", out_codeset);
6794 g_free(test_conv_global_out);
6795 g_free(test_conv_reply);
6796 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6797 out_codeset);
6798 codeconv_set_strict(FALSE);
6800 g_free(tmpstr);
6803 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6805 gchar *address;
6807 cm_return_if_fail(user_data != NULL);
6809 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6810 g_strstrip(address);
6811 if (*address != '\0') {
6812 gchar *name = procheader_get_fromname(address);
6813 extract_address(address);
6814 #ifndef USE_ALT_ADDRBOOK
6815 addressbook_add_contact(name, address, NULL, NULL);
6816 #else
6817 debug_print("%s: %s\n", name, address);
6818 if (addressadd_selection(name, address, NULL, NULL)) {
6819 debug_print( "addressbook_add_contact - added\n" );
6821 #endif
6823 g_free(address);
6826 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6828 GtkWidget *menuitem;
6829 gchar *address;
6831 cm_return_if_fail(menu != NULL);
6832 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6834 menuitem = gtk_separator_menu_item_new();
6835 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6836 gtk_widget_show(menuitem);
6838 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6839 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6841 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6842 g_strstrip(address);
6843 if (*address == '\0') {
6844 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6847 g_signal_connect(G_OBJECT(menuitem), "activate",
6848 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6849 gtk_widget_show(menuitem);
6852 void compose_add_extra_header(gchar *header, GtkListStore *model)
6854 GtkTreeIter iter;
6855 if (strcmp(header, "")) {
6856 COMBOBOX_ADD(model, header, COMPOSE_TO);
6860 void compose_add_extra_header_entries(GtkListStore *model)
6862 FILE *exh;
6863 gchar *exhrc;
6864 gchar buf[BUFFSIZE];
6865 gint lastc;
6867 if (extra_headers == NULL) {
6868 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6869 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6870 debug_print("extra headers file not found\n");
6871 goto extra_headers_done;
6873 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6874 lastc = strlen(buf) - 1; /* remove trailing control chars */
6875 while (lastc >= 0 && buf[lastc] != ':')
6876 buf[lastc--] = '\0';
6877 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6878 buf[lastc] = '\0'; /* remove trailing : for comparison */
6879 if (custom_header_is_allowed(buf)) {
6880 buf[lastc] = ':';
6881 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6883 else
6884 g_message("disallowed extra header line: %s\n", buf);
6886 else {
6887 if (buf[0] != '#')
6888 g_message("invalid extra header line: %s\n", buf);
6891 fclose(exh);
6892 extra_headers_done:
6893 g_free(exhrc);
6894 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6895 extra_headers = g_slist_reverse(extra_headers);
6897 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6900 static void compose_create_header_entry(Compose *compose)
6902 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6904 GtkWidget *combo;
6905 GtkWidget *entry;
6906 GtkWidget *button;
6907 GtkWidget *hbox;
6908 gchar **string;
6909 const gchar *header = NULL;
6910 ComposeHeaderEntry *headerentry;
6911 gboolean standard_header = FALSE;
6912 GtkListStore *model;
6913 GtkTreeIter iter;
6915 headerentry = g_new0(ComposeHeaderEntry, 1);
6917 /* Combo box model */
6918 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6919 #if !GTK_CHECK_VERSION(2, 24, 0)
6920 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6921 #endif
6922 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6923 COMPOSE_TO);
6924 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6925 COMPOSE_CC);
6926 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6927 COMPOSE_BCC);
6928 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6929 COMPOSE_NEWSGROUPS);
6930 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6931 COMPOSE_REPLYTO);
6932 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6933 COMPOSE_FOLLOWUPTO);
6934 compose_add_extra_header_entries(model);
6936 /* Combo box */
6937 #if GTK_CHECK_VERSION(2, 24, 0)
6938 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6939 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6940 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6941 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6942 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6943 #endif
6944 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6945 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6946 G_CALLBACK(compose_grab_focus_cb), compose);
6947 gtk_widget_show(combo);
6949 /* Putting only the combobox child into focus chain of its parent causes
6950 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6951 * This eliminates need to pres Tab twice in order to really get from the
6952 * combobox to next widget. */
6953 GList *l = NULL;
6954 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6955 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6956 g_list_free(l);
6958 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6959 compose->header_nextrow, compose->header_nextrow+1,
6960 GTK_SHRINK, GTK_FILL, 0, 0);
6961 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6962 const gchar *last_header_entry = gtk_entry_get_text(
6963 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6964 string = headers;
6965 while (*string != NULL) {
6966 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6967 standard_header = TRUE;
6968 string++;
6970 if (standard_header)
6971 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6973 if (!compose->header_last || !standard_header) {
6974 switch(compose->account->protocol) {
6975 case A_NNTP:
6976 header = prefs_common_translated_header_name("Newsgroups:");
6977 break;
6978 default:
6979 header = prefs_common_translated_header_name("To:");
6980 break;
6983 if (header)
6984 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6986 gtk_editable_set_editable(
6987 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
6988 prefs_common.type_any_header);
6990 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6991 G_CALLBACK(compose_grab_focus_cb), compose);
6993 /* Entry field with cleanup button */
6994 button = gtk_button_new();
6995 gtk_button_set_image(GTK_BUTTON(button),
6996 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6997 gtk_widget_show(button);
6998 CLAWS_SET_TIP(button,
6999 _("Delete entry contents"));
7000 entry = gtk_entry_new();
7001 gtk_widget_show(entry);
7002 CLAWS_SET_TIP(entry,
7003 _("Use <tab> to autocomplete from addressbook"));
7004 hbox = gtk_hbox_new (FALSE, 0);
7005 gtk_widget_show(hbox);
7006 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7007 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7008 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7009 compose->header_nextrow, compose->header_nextrow+1,
7010 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7012 g_signal_connect(G_OBJECT(entry), "key-press-event",
7013 G_CALLBACK(compose_headerentry_key_press_event_cb),
7014 headerentry);
7015 g_signal_connect(G_OBJECT(entry), "changed",
7016 G_CALLBACK(compose_headerentry_changed_cb),
7017 headerentry);
7018 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7019 G_CALLBACK(compose_grab_focus_cb), compose);
7021 g_signal_connect(G_OBJECT(button), "clicked",
7022 G_CALLBACK(compose_headerentry_button_clicked_cb),
7023 headerentry);
7025 /* email dnd */
7026 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7027 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7028 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7029 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7030 G_CALLBACK(compose_header_drag_received_cb),
7031 entry);
7032 g_signal_connect(G_OBJECT(entry), "drag-drop",
7033 G_CALLBACK(compose_drag_drop),
7034 compose);
7035 g_signal_connect(G_OBJECT(entry), "populate-popup",
7036 G_CALLBACK(compose_entry_popup_extend),
7037 NULL);
7039 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7041 headerentry->compose = compose;
7042 headerentry->combo = combo;
7043 headerentry->entry = entry;
7044 headerentry->button = button;
7045 headerentry->hbox = hbox;
7046 headerentry->headernum = compose->header_nextrow;
7047 headerentry->type = PREF_NONE;
7049 compose->header_nextrow++;
7050 compose->header_last = headerentry;
7051 compose->header_list =
7052 g_slist_append(compose->header_list,
7053 headerentry);
7056 static void compose_add_header_entry(Compose *compose, const gchar *header,
7057 gchar *text, ComposePrefType pref_type)
7059 ComposeHeaderEntry *last_header = compose->header_last;
7060 gchar *tmp = g_strdup(text), *email;
7061 gboolean replyto_hdr;
7063 replyto_hdr = (!strcasecmp(header,
7064 prefs_common_translated_header_name("Reply-To:")) ||
7065 !strcasecmp(header,
7066 prefs_common_translated_header_name("Followup-To:")) ||
7067 !strcasecmp(header,
7068 prefs_common_translated_header_name("In-Reply-To:")));
7070 extract_address(tmp);
7071 email = g_utf8_strdown(tmp, -1);
7073 if (replyto_hdr == FALSE &&
7074 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7076 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7077 header, text, (gint) pref_type);
7078 g_free(email);
7079 g_free(tmp);
7080 return;
7083 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7084 gtk_entry_set_text(GTK_ENTRY(
7085 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7086 else
7087 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7088 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7089 last_header->type = pref_type;
7091 if (replyto_hdr == FALSE)
7092 g_hash_table_insert(compose->email_hashtable, email,
7093 GUINT_TO_POINTER(1));
7094 else
7095 g_free(email);
7097 g_free(tmp);
7100 static void compose_destroy_headerentry(Compose *compose,
7101 ComposeHeaderEntry *headerentry)
7103 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7104 gchar *email;
7106 extract_address(text);
7107 email = g_utf8_strdown(text, -1);
7108 g_hash_table_remove(compose->email_hashtable, email);
7109 g_free(text);
7110 g_free(email);
7112 gtk_widget_destroy(headerentry->combo);
7113 gtk_widget_destroy(headerentry->entry);
7114 gtk_widget_destroy(headerentry->button);
7115 gtk_widget_destroy(headerentry->hbox);
7116 g_free(headerentry);
7119 static void compose_remove_header_entries(Compose *compose)
7121 GSList *list;
7122 for (list = compose->header_list; list; list = list->next)
7123 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7125 compose->header_last = NULL;
7126 g_slist_free(compose->header_list);
7127 compose->header_list = NULL;
7128 compose->header_nextrow = 1;
7129 compose_create_header_entry(compose);
7132 static GtkWidget *compose_create_header(Compose *compose)
7134 GtkWidget *from_optmenu_hbox;
7135 GtkWidget *header_table_main;
7136 GtkWidget *header_scrolledwin;
7137 GtkWidget *header_table;
7139 /* parent with account selection and from header */
7140 header_table_main = gtk_table_new(2, 2, FALSE);
7141 gtk_widget_show(header_table_main);
7142 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7144 from_optmenu_hbox = compose_account_option_menu_create(compose);
7145 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7146 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7148 /* child with header labels and entries */
7149 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7150 gtk_widget_show(header_scrolledwin);
7151 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7153 header_table = gtk_table_new(2, 2, FALSE);
7154 gtk_widget_show(header_table);
7155 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7156 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7157 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7158 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7159 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7161 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7162 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7164 compose->header_table = header_table;
7165 compose->header_list = NULL;
7166 compose->header_nextrow = 0;
7168 compose_create_header_entry(compose);
7170 compose->table = NULL;
7172 return header_table_main;
7175 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7177 Compose *compose = (Compose *)data;
7178 GdkEventButton event;
7180 event.button = 3;
7181 event.time = gtk_get_current_event_time();
7183 return attach_button_pressed(compose->attach_clist, &event, compose);
7186 static GtkWidget *compose_create_attach(Compose *compose)
7188 GtkWidget *attach_scrwin;
7189 GtkWidget *attach_clist;
7191 GtkListStore *store;
7192 GtkCellRenderer *renderer;
7193 GtkTreeViewColumn *column;
7194 GtkTreeSelection *selection;
7196 /* attachment list */
7197 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7198 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7199 GTK_POLICY_AUTOMATIC,
7200 GTK_POLICY_AUTOMATIC);
7201 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7203 store = gtk_list_store_new(N_ATTACH_COLS,
7204 G_TYPE_STRING,
7205 G_TYPE_STRING,
7206 G_TYPE_STRING,
7207 G_TYPE_STRING,
7208 G_TYPE_POINTER,
7209 G_TYPE_AUTO_POINTER,
7210 -1);
7211 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7212 (GTK_TREE_MODEL(store)));
7213 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7214 g_object_unref(store);
7216 renderer = gtk_cell_renderer_text_new();
7217 column = gtk_tree_view_column_new_with_attributes
7218 (_("Mime type"), renderer, "text",
7219 COL_MIMETYPE, NULL);
7220 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7222 renderer = gtk_cell_renderer_text_new();
7223 column = gtk_tree_view_column_new_with_attributes
7224 (_("Size"), renderer, "text",
7225 COL_SIZE, NULL);
7226 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7228 renderer = gtk_cell_renderer_text_new();
7229 column = gtk_tree_view_column_new_with_attributes
7230 (_("Name"), renderer, "text",
7231 COL_NAME, NULL);
7232 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7234 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7235 prefs_common.use_stripes_everywhere);
7236 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7237 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7239 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7240 G_CALLBACK(attach_selected), compose);
7241 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7242 G_CALLBACK(attach_button_pressed), compose);
7243 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7244 G_CALLBACK(popup_attach_button_pressed), compose);
7245 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7246 G_CALLBACK(attach_key_pressed), compose);
7248 /* drag and drop */
7249 gtk_drag_dest_set(attach_clist,
7250 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7251 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7252 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7253 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7254 G_CALLBACK(compose_attach_drag_received_cb),
7255 compose);
7256 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7257 G_CALLBACK(compose_drag_drop),
7258 compose);
7260 compose->attach_scrwin = attach_scrwin;
7261 compose->attach_clist = attach_clist;
7263 return attach_scrwin;
7266 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7267 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7269 static GtkWidget *compose_create_others(Compose *compose)
7271 GtkWidget *table;
7272 GtkWidget *savemsg_checkbtn;
7273 GtkWidget *savemsg_combo;
7274 GtkWidget *savemsg_select;
7276 guint rowcount = 0;
7277 gchar *folderidentifier;
7279 /* Table for settings */
7280 table = gtk_table_new(3, 1, FALSE);
7281 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7282 gtk_widget_show(table);
7283 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7284 rowcount = 0;
7286 /* Save Message to folder */
7287 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7288 gtk_widget_show(savemsg_checkbtn);
7289 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7290 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7291 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7293 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7294 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7296 #if !GTK_CHECK_VERSION(2, 24, 0)
7297 savemsg_combo = gtk_combo_box_entry_new_text();
7298 #else
7299 savemsg_combo = gtk_combo_box_text_new_with_entry();
7300 #endif
7301 compose->savemsg_checkbtn = savemsg_checkbtn;
7302 compose->savemsg_combo = savemsg_combo;
7303 gtk_widget_show(savemsg_combo);
7305 if (prefs_common.compose_save_to_history)
7306 #if !GTK_CHECK_VERSION(2, 24, 0)
7307 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7308 prefs_common.compose_save_to_history);
7309 #else
7310 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7311 prefs_common.compose_save_to_history);
7312 #endif
7313 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7314 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7315 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7316 G_CALLBACK(compose_grab_focus_cb), compose);
7317 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7318 folderidentifier = folder_item_get_identifier(account_get_special_folder
7319 (compose->account, F_OUTBOX));
7320 compose_set_save_to(compose, folderidentifier);
7321 g_free(folderidentifier);
7324 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7325 gtk_widget_show(savemsg_select);
7326 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7327 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7328 G_CALLBACK(compose_savemsg_select_cb),
7329 compose);
7331 return table;
7334 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7336 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7337 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7340 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7342 FolderItem *dest;
7343 gchar * path;
7345 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7346 if (!dest) return;
7348 path = folder_item_get_identifier(dest);
7350 compose_set_save_to(compose, path);
7351 g_free(path);
7354 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7355 GdkAtom clip, GtkTextIter *insert_place);
7358 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7359 Compose *compose)
7361 gint prev_autowrap;
7362 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7363 #if USE_ENCHANT
7364 if (event->button == 3) {
7365 GtkTextIter iter;
7366 GtkTextIter sel_start, sel_end;
7367 gboolean stuff_selected;
7368 gint x, y;
7369 /* move the cursor to allow GtkAspell to check the word
7370 * under the mouse */
7371 if (event->x && event->y) {
7372 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7373 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7374 &x, &y);
7375 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7376 &iter, x, y);
7377 } else {
7378 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7379 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7381 /* get selection */
7382 stuff_selected = gtk_text_buffer_get_selection_bounds(
7383 buffer,
7384 &sel_start, &sel_end);
7386 gtk_text_buffer_place_cursor (buffer, &iter);
7387 /* reselect stuff */
7388 if (stuff_selected
7389 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7390 gtk_text_buffer_select_range(buffer,
7391 &sel_start, &sel_end);
7393 return FALSE; /* pass the event so that the right-click goes through */
7395 #endif
7396 if (event->button == 2) {
7397 GtkTextIter iter;
7398 gint x, y;
7399 BLOCK_WRAP();
7401 /* get the middle-click position to paste at the correct place */
7402 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7403 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7404 &x, &y);
7405 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7406 &iter, x, y);
7408 entry_paste_clipboard(compose, text,
7409 prefs_common.linewrap_pastes,
7410 GDK_SELECTION_PRIMARY, &iter);
7411 UNBLOCK_WRAP();
7412 return TRUE;
7414 return FALSE;
7417 #if USE_ENCHANT
7418 static void compose_spell_menu_changed(void *data)
7420 Compose *compose = (Compose *)data;
7421 GSList *items;
7422 GtkWidget *menuitem;
7423 GtkWidget *parent_item;
7424 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7425 GSList *spell_menu;
7427 if (compose->gtkaspell == NULL)
7428 return;
7430 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7431 "/Menu/Spelling/Options");
7433 /* setting the submenu removes /Spelling/Options from the factory
7434 * so we need to save it */
7436 if (parent_item == NULL) {
7437 parent_item = compose->aspell_options_menu;
7438 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7439 } else
7440 compose->aspell_options_menu = parent_item;
7442 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7444 spell_menu = g_slist_reverse(spell_menu);
7445 for (items = spell_menu;
7446 items; items = items->next) {
7447 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7448 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7449 gtk_widget_show(GTK_WIDGET(menuitem));
7451 g_slist_free(spell_menu);
7453 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7454 gtk_widget_show(parent_item);
7457 static void compose_dict_changed(void *data)
7459 Compose *compose = (Compose *) data;
7461 if(!compose->gtkaspell)
7462 return;
7463 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7464 return;
7466 gtkaspell_highlight_all(compose->gtkaspell);
7467 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7469 #endif
7471 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7473 Compose *compose = (Compose *)data;
7474 GdkEventButton event;
7476 event.button = 3;
7477 event.time = gtk_get_current_event_time();
7478 event.x = 0;
7479 event.y = 0;
7481 return text_clicked(compose->text, &event, compose);
7484 static gboolean compose_force_window_origin = TRUE;
7485 static Compose *compose_create(PrefsAccount *account,
7486 FolderItem *folder,
7487 ComposeMode mode,
7488 gboolean batch)
7490 Compose *compose;
7491 GtkWidget *window;
7492 GtkWidget *vbox;
7493 GtkWidget *menubar;
7494 GtkWidget *handlebox;
7496 GtkWidget *notebook;
7498 GtkWidget *attach_hbox;
7499 GtkWidget *attach_lab1;
7500 GtkWidget *attach_lab2;
7502 GtkWidget *vbox2;
7504 GtkWidget *label;
7505 GtkWidget *subject_hbox;
7506 GtkWidget *subject_frame;
7507 GtkWidget *subject_entry;
7508 GtkWidget *subject;
7509 GtkWidget *paned;
7511 GtkWidget *edit_vbox;
7512 GtkWidget *ruler_hbox;
7513 GtkWidget *ruler;
7514 GtkWidget *scrolledwin;
7515 GtkWidget *text;
7516 GtkTextBuffer *buffer;
7517 GtkClipboard *clipboard;
7519 UndoMain *undostruct;
7521 GtkWidget *popupmenu;
7522 GtkWidget *tmpl_menu;
7523 GtkActionGroup *action_group = NULL;
7525 #if USE_ENCHANT
7526 GtkAspell * gtkaspell = NULL;
7527 #endif
7529 static GdkGeometry geometry;
7531 cm_return_val_if_fail(account != NULL, NULL);
7533 debug_print("Creating compose window...\n");
7534 compose = g_new0(Compose, 1);
7536 compose->batch = batch;
7537 compose->account = account;
7538 compose->folder = folder;
7540 compose->mutex = cm_mutex_new();
7541 compose->set_cursor_pos = -1;
7543 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7545 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7546 gtk_widget_set_size_request(window, prefs_common.compose_width,
7547 prefs_common.compose_height);
7549 if (!geometry.max_width) {
7550 geometry.max_width = gdk_screen_width();
7551 geometry.max_height = gdk_screen_height();
7554 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7555 &geometry, GDK_HINT_MAX_SIZE);
7556 if (!geometry.min_width) {
7557 geometry.min_width = 600;
7558 geometry.min_height = 440;
7560 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7561 &geometry, GDK_HINT_MIN_SIZE);
7563 #ifndef GENERIC_UMPC
7564 if (compose_force_window_origin)
7565 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7566 prefs_common.compose_y);
7567 #endif
7568 g_signal_connect(G_OBJECT(window), "delete_event",
7569 G_CALLBACK(compose_delete_cb), compose);
7570 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7571 gtk_widget_realize(window);
7573 gtkut_widget_set_composer_icon(window);
7575 vbox = gtk_vbox_new(FALSE, 0);
7576 gtk_container_add(GTK_CONTAINER(window), vbox);
7578 compose->ui_manager = gtk_ui_manager_new();
7579 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7580 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7581 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7582 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7583 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7584 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7585 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7586 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7587 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7588 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7594 #ifdef USE_ENCHANT
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7596 #endif
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7601 /* Compose menu */
7602 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7616 /* Edit menu */
7617 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7623 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7625 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7628 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7630 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7632 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7633 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7634 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7635 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7636 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7637 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7638 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7639 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7640 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7641 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7642 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7643 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7644 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7645 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7646 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7648 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7650 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7651 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7654 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7656 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7658 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7660 #if USE_ENCHANT
7661 /* Spelling menu */
7662 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7665 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7667 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7668 #endif
7670 /* Options menu */
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7673 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7677 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7684 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7685 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7686 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7687 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7688 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7689 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7690 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7692 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7693 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7694 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7695 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7696 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7698 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7700 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7701 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7702 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7703 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7704 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7706 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7707 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)
7708 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)
7709 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7711 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7713 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7714 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)
7715 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)
7717 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7719 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7720 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)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7724 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)
7725 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7727 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7729 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7730 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)
7731 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7732 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7733 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7734 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7736 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7737 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)
7738 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)
7739 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7742 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7743 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7744 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7745 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7746 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7747 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7750 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7751 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)
7753 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7754 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7755 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7756 /* phew. */
7758 /* Tools menu */
7759 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7760 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7761 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7762 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7763 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7764 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7766 /* Help menu */
7767 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7769 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7770 gtk_widget_show_all(menubar);
7772 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7773 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7775 if (prefs_common.toolbar_detachable) {
7776 handlebox = gtk_handle_box_new();
7777 } else {
7778 handlebox = gtk_hbox_new(FALSE, 0);
7780 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7782 gtk_widget_realize(handlebox);
7783 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7784 (gpointer)compose);
7786 vbox2 = gtk_vbox_new(FALSE, 2);
7787 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7788 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7790 /* Notebook */
7791 notebook = gtk_notebook_new();
7792 gtk_widget_show(notebook);
7794 /* header labels and entries */
7795 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7796 compose_create_header(compose),
7797 gtk_label_new_with_mnemonic(_("Hea_der")));
7798 /* attachment list */
7799 attach_hbox = gtk_hbox_new(FALSE, 0);
7800 gtk_widget_show(attach_hbox);
7802 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7803 gtk_widget_show(attach_lab1);
7804 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7806 attach_lab2 = gtk_label_new("");
7807 gtk_widget_show(attach_lab2);
7808 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7810 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7811 compose_create_attach(compose),
7812 attach_hbox);
7813 /* Others Tab */
7814 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7815 compose_create_others(compose),
7816 gtk_label_new_with_mnemonic(_("Othe_rs")));
7818 /* Subject */
7819 subject_hbox = gtk_hbox_new(FALSE, 0);
7820 gtk_widget_show(subject_hbox);
7822 subject_frame = gtk_frame_new(NULL);
7823 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7824 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7825 gtk_widget_show(subject_frame);
7827 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7828 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7829 gtk_widget_show(subject);
7831 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7832 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7833 gtk_widget_show(label);
7835 #ifdef USE_ENCHANT
7836 subject_entry = claws_spell_entry_new();
7837 #else
7838 subject_entry = gtk_entry_new();
7839 #endif
7840 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7841 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7842 G_CALLBACK(compose_grab_focus_cb), compose);
7843 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7844 gtk_widget_show(subject_entry);
7845 compose->subject_entry = subject_entry;
7846 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7848 edit_vbox = gtk_vbox_new(FALSE, 0);
7850 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7852 /* ruler */
7853 ruler_hbox = gtk_hbox_new(FALSE, 0);
7854 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7856 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7857 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7858 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7859 BORDER_WIDTH);
7861 /* text widget */
7862 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7863 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7864 GTK_POLICY_AUTOMATIC,
7865 GTK_POLICY_AUTOMATIC);
7866 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7867 GTK_SHADOW_IN);
7868 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7870 text = gtk_text_view_new();
7871 if (prefs_common.show_compose_margin) {
7872 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7873 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7875 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7876 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7877 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7878 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7879 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7881 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7882 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7883 G_CALLBACK(compose_edit_size_alloc),
7884 ruler);
7885 g_signal_connect(G_OBJECT(buffer), "changed",
7886 G_CALLBACK(compose_changed_cb), compose);
7887 g_signal_connect(G_OBJECT(text), "grab_focus",
7888 G_CALLBACK(compose_grab_focus_cb), compose);
7889 g_signal_connect(G_OBJECT(buffer), "insert_text",
7890 G_CALLBACK(text_inserted), compose);
7891 g_signal_connect(G_OBJECT(text), "button_press_event",
7892 G_CALLBACK(text_clicked), compose);
7893 g_signal_connect(G_OBJECT(text), "popup-menu",
7894 G_CALLBACK(compose_popup_menu), compose);
7895 g_signal_connect(G_OBJECT(subject_entry), "changed",
7896 G_CALLBACK(compose_changed_cb), compose);
7897 g_signal_connect(G_OBJECT(subject_entry), "activate",
7898 G_CALLBACK(compose_subject_entry_activated), compose);
7900 /* drag and drop */
7901 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7902 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7903 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7904 g_signal_connect(G_OBJECT(text), "drag_data_received",
7905 G_CALLBACK(compose_insert_drag_received_cb),
7906 compose);
7907 g_signal_connect(G_OBJECT(text), "drag-drop",
7908 G_CALLBACK(compose_drag_drop),
7909 compose);
7910 g_signal_connect(G_OBJECT(text), "key-press-event",
7911 G_CALLBACK(completion_set_focus_to_subject),
7912 compose);
7913 gtk_widget_show_all(vbox);
7915 /* pane between attach clist and text */
7916 paned = gtk_vpaned_new();
7917 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7918 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7919 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7920 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7921 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7922 G_CALLBACK(compose_notebook_size_alloc), paned);
7924 gtk_widget_show_all(paned);
7927 if (prefs_common.textfont) {
7928 PangoFontDescription *font_desc;
7930 font_desc = pango_font_description_from_string
7931 (prefs_common.textfont);
7932 if (font_desc) {
7933 gtk_widget_modify_font(text, font_desc);
7934 pango_font_description_free(font_desc);
7938 gtk_action_group_add_actions(action_group, compose_popup_entries,
7939 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7940 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7941 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7942 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7943 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7944 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7947 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7949 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7950 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7951 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7953 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7955 undostruct = undo_init(text);
7956 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7957 compose);
7959 address_completion_start(window);
7961 compose->window = window;
7962 compose->vbox = vbox;
7963 compose->menubar = menubar;
7964 compose->handlebox = handlebox;
7966 compose->vbox2 = vbox2;
7968 compose->paned = paned;
7970 compose->attach_label = attach_lab2;
7972 compose->notebook = notebook;
7973 compose->edit_vbox = edit_vbox;
7974 compose->ruler_hbox = ruler_hbox;
7975 compose->ruler = ruler;
7976 compose->scrolledwin = scrolledwin;
7977 compose->text = text;
7979 compose->focused_editable = NULL;
7981 compose->popupmenu = popupmenu;
7983 compose->tmpl_menu = tmpl_menu;
7985 compose->mode = mode;
7986 compose->rmode = mode;
7988 compose->targetinfo = NULL;
7989 compose->replyinfo = NULL;
7990 compose->fwdinfo = NULL;
7992 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7993 g_str_equal, (GDestroyNotify) g_free, NULL);
7995 compose->replyto = NULL;
7996 compose->cc = NULL;
7997 compose->bcc = NULL;
7998 compose->followup_to = NULL;
8000 compose->ml_post = NULL;
8002 compose->inreplyto = NULL;
8003 compose->references = NULL;
8004 compose->msgid = NULL;
8005 compose->boundary = NULL;
8007 compose->autowrap = prefs_common.autowrap;
8008 compose->autoindent = prefs_common.auto_indent;
8009 compose->use_signing = FALSE;
8010 compose->use_encryption = FALSE;
8011 compose->privacy_system = NULL;
8012 compose->encdata = NULL;
8014 compose->modified = FALSE;
8016 compose->return_receipt = FALSE;
8018 compose->to_list = NULL;
8019 compose->newsgroup_list = NULL;
8021 compose->undostruct = undostruct;
8023 compose->sig_str = NULL;
8025 compose->exteditor_file = NULL;
8026 compose->exteditor_pid = -1;
8027 compose->exteditor_tag = -1;
8028 compose->exteditor_socket = NULL;
8029 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8031 compose->folder_update_callback_id =
8032 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8033 compose_update_folder_hook,
8034 (gpointer) compose);
8036 #if USE_ENCHANT
8037 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8038 if (mode != COMPOSE_REDIRECT) {
8039 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8040 strcmp(prefs_common.dictionary, "")) {
8041 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8042 prefs_common.alt_dictionary,
8043 conv_get_locale_charset_str(),
8044 prefs_common.misspelled_col,
8045 prefs_common.check_while_typing,
8046 prefs_common.recheck_when_changing_dict,
8047 prefs_common.use_alternate,
8048 prefs_common.use_both_dicts,
8049 GTK_TEXT_VIEW(text),
8050 GTK_WINDOW(compose->window),
8051 compose_dict_changed,
8052 compose_spell_menu_changed,
8053 compose);
8054 if (!gtkaspell) {
8055 alertpanel_error(_("Spell checker could not "
8056 "be started.\n%s"),
8057 gtkaspell_checkers_strerror());
8058 gtkaspell_checkers_reset_error();
8059 } else {
8060 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8064 compose->gtkaspell = gtkaspell;
8065 compose_spell_menu_changed(compose);
8066 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8067 #endif
8069 compose_select_account(compose, account, TRUE);
8071 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8072 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8074 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8075 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8077 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8078 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8080 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8081 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8083 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8084 if (account->protocol != A_NNTP)
8085 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8086 prefs_common_translated_header_name("To:"));
8087 else
8088 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8089 prefs_common_translated_header_name("Newsgroups:"));
8091 #ifndef USE_ALT_ADDRBOOK
8092 addressbook_set_target_compose(compose);
8093 #endif
8094 if (mode != COMPOSE_REDIRECT)
8095 compose_set_template_menu(compose);
8096 else {
8097 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8100 compose_list = g_list_append(compose_list, compose);
8102 if (!prefs_common.show_ruler)
8103 gtk_widget_hide(ruler_hbox);
8105 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8107 /* Priority */
8108 compose->priority = PRIORITY_NORMAL;
8109 compose_update_priority_menu_item(compose);
8111 compose_set_out_encoding(compose);
8113 /* Actions menu */
8114 compose_update_actions_menu(compose);
8116 /* Privacy Systems menu */
8117 compose_update_privacy_systems_menu(compose);
8119 activate_privacy_system(compose, account, TRUE);
8120 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8121 if (batch) {
8122 gtk_widget_realize(window);
8123 } else {
8124 gtk_widget_show(window);
8127 return compose;
8130 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8132 GList *accounts;
8133 GtkWidget *hbox;
8134 GtkWidget *optmenu;
8135 GtkWidget *optmenubox;
8136 GtkWidget *fromlabel;
8137 GtkListStore *menu;
8138 GtkTreeIter iter;
8139 GtkWidget *from_name = NULL;
8141 gint num = 0, def_menu = 0;
8143 accounts = account_get_list();
8144 cm_return_val_if_fail(accounts != NULL, NULL);
8146 optmenubox = gtk_event_box_new();
8147 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8148 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8150 hbox = gtk_hbox_new(FALSE, 4);
8151 from_name = gtk_entry_new();
8153 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8154 G_CALLBACK(compose_grab_focus_cb), compose);
8155 g_signal_connect_after(G_OBJECT(from_name), "activate",
8156 G_CALLBACK(from_name_activate_cb), optmenu);
8158 for (; accounts != NULL; accounts = accounts->next, num++) {
8159 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8160 gchar *name, *from = NULL;
8162 if (ac == compose->account) def_menu = num;
8164 name = g_markup_printf_escaped("<i>%s</i>",
8165 ac->account_name);
8167 if (ac == compose->account) {
8168 if (ac->name && *ac->name) {
8169 gchar *buf;
8170 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8171 from = g_strdup_printf("%s <%s>",
8172 buf, ac->address);
8173 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8174 } else {
8175 from = g_strdup_printf("%s",
8176 ac->address);
8177 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8180 COMBOBOX_ADD(menu, name, ac->account_id);
8181 g_free(name);
8182 g_free(from);
8185 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8187 g_signal_connect(G_OBJECT(optmenu), "changed",
8188 G_CALLBACK(account_activated),
8189 compose);
8190 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8191 G_CALLBACK(compose_entry_popup_extend),
8192 NULL);
8194 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8195 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8197 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8198 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8199 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8201 /* Putting only the GtkEntry into focus chain of parent hbox causes
8202 * the account selector combobox next to it to be unreachable when
8203 * navigating widgets in GtkTable with up/down arrow keys.
8204 * Note: gtk_widget_set_can_focus() was not enough. */
8205 GList *l = NULL;
8206 l = g_list_prepend(l, from_name);
8207 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8208 g_list_free(l);
8210 CLAWS_SET_TIP(optmenubox,
8211 _("Account to use for this email"));
8212 CLAWS_SET_TIP(from_name,
8213 _("Sender address to be used"));
8215 compose->account_combo = optmenu;
8216 compose->from_name = from_name;
8218 return hbox;
8221 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8223 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8224 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8225 Compose *compose = (Compose *) data;
8226 if (active) {
8227 compose->priority = value;
8231 static void compose_reply_change_mode(Compose *compose,
8232 ComposeMode action)
8234 gboolean was_modified = compose->modified;
8236 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8238 cm_return_if_fail(compose->replyinfo != NULL);
8240 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8241 ml = TRUE;
8242 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8243 followup = TRUE;
8244 if (action == COMPOSE_REPLY_TO_ALL)
8245 all = TRUE;
8246 if (action == COMPOSE_REPLY_TO_SENDER)
8247 sender = TRUE;
8248 if (action == COMPOSE_REPLY_TO_LIST)
8249 ml = TRUE;
8251 compose_remove_header_entries(compose);
8252 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8253 if (compose->account->set_autocc && compose->account->auto_cc)
8254 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8256 if (compose->account->set_autobcc && compose->account->auto_bcc)
8257 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8259 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8260 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8261 compose_show_first_last_header(compose, TRUE);
8262 compose->modified = was_modified;
8263 compose_set_title(compose);
8266 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8268 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8269 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8270 Compose *compose = (Compose *) data;
8272 if (active)
8273 compose_reply_change_mode(compose, value);
8276 static void compose_update_priority_menu_item(Compose * compose)
8278 GtkWidget *menuitem = NULL;
8279 switch (compose->priority) {
8280 case PRIORITY_HIGHEST:
8281 menuitem = gtk_ui_manager_get_widget
8282 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8283 break;
8284 case PRIORITY_HIGH:
8285 menuitem = gtk_ui_manager_get_widget
8286 (compose->ui_manager, "/Menu/Options/Priority/High");
8287 break;
8288 case PRIORITY_NORMAL:
8289 menuitem = gtk_ui_manager_get_widget
8290 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8291 break;
8292 case PRIORITY_LOW:
8293 menuitem = gtk_ui_manager_get_widget
8294 (compose->ui_manager, "/Menu/Options/Priority/Low");
8295 break;
8296 case PRIORITY_LOWEST:
8297 menuitem = gtk_ui_manager_get_widget
8298 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8299 break;
8301 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8304 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8306 Compose *compose = (Compose *) data;
8307 gchar *systemid;
8308 gboolean can_sign = FALSE, can_encrypt = FALSE;
8310 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8312 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8313 return;
8315 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8316 g_free(compose->privacy_system);
8317 compose->privacy_system = NULL;
8318 g_free(compose->encdata);
8319 compose->encdata = NULL;
8320 if (systemid != NULL) {
8321 compose->privacy_system = g_strdup(systemid);
8323 can_sign = privacy_system_can_sign(systemid);
8324 can_encrypt = privacy_system_can_encrypt(systemid);
8327 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8329 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8330 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8333 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8335 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8336 GtkWidget *menuitem = NULL;
8337 GList *children, *amenu;
8338 gboolean can_sign = FALSE, can_encrypt = FALSE;
8339 gboolean found = FALSE;
8341 if (compose->privacy_system != NULL) {
8342 gchar *systemid;
8343 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8344 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8345 cm_return_if_fail(menuitem != NULL);
8347 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8348 amenu = children;
8349 menuitem = NULL;
8350 while (amenu != NULL) {
8351 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8352 if (systemid != NULL) {
8353 if (strcmp(systemid, compose->privacy_system) == 0 &&
8354 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8355 menuitem = GTK_WIDGET(amenu->data);
8357 can_sign = privacy_system_can_sign(systemid);
8358 can_encrypt = privacy_system_can_encrypt(systemid);
8359 found = TRUE;
8360 break;
8362 } else if (strlen(compose->privacy_system) == 0 &&
8363 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8364 menuitem = GTK_WIDGET(amenu->data);
8366 can_sign = FALSE;
8367 can_encrypt = FALSE;
8368 found = TRUE;
8369 break;
8372 amenu = amenu->next;
8374 g_list_free(children);
8375 if (menuitem != NULL)
8376 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8378 if (warn && !found && strlen(compose->privacy_system)) {
8379 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8380 "will not be able to sign or encrypt this message."),
8381 compose->privacy_system);
8385 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8386 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8389 static void compose_set_out_encoding(Compose *compose)
8391 CharSet out_encoding;
8392 const gchar *branch = NULL;
8393 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8395 switch(out_encoding) {
8396 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8397 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8398 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8399 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8400 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8401 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8402 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8403 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8404 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8405 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8406 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8407 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8408 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8409 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8410 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8411 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8412 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8413 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8414 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8415 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8416 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8417 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8418 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8419 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8420 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8421 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8422 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8423 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8424 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8425 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8426 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8427 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8428 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8429 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8431 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8434 static void compose_set_template_menu(Compose *compose)
8436 GSList *tmpl_list, *cur;
8437 GtkWidget *menu;
8438 GtkWidget *item;
8440 tmpl_list = template_get_config();
8442 menu = gtk_menu_new();
8444 gtk_menu_set_accel_group (GTK_MENU (menu),
8445 gtk_ui_manager_get_accel_group(compose->ui_manager));
8446 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8447 Template *tmpl = (Template *)cur->data;
8448 gchar *accel_path = NULL;
8449 item = gtk_menu_item_new_with_label(tmpl->name);
8450 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8451 g_signal_connect(G_OBJECT(item), "activate",
8452 G_CALLBACK(compose_template_activate_cb),
8453 compose);
8454 g_object_set_data(G_OBJECT(item), "template", tmpl);
8455 gtk_widget_show(item);
8456 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8457 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8458 g_free(accel_path);
8461 gtk_widget_show(menu);
8462 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8465 void compose_update_actions_menu(Compose *compose)
8467 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8470 static void compose_update_privacy_systems_menu(Compose *compose)
8472 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8473 GSList *systems, *cur;
8474 GtkWidget *widget;
8475 GtkWidget *system_none;
8476 GSList *group;
8477 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8478 GtkWidget *privacy_menu = gtk_menu_new();
8480 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8481 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8483 g_signal_connect(G_OBJECT(system_none), "activate",
8484 G_CALLBACK(compose_set_privacy_system_cb), compose);
8486 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8487 gtk_widget_show(system_none);
8489 systems = privacy_get_system_ids();
8490 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8491 gchar *systemid = cur->data;
8493 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8494 widget = gtk_radio_menu_item_new_with_label(group,
8495 privacy_system_get_name(systemid));
8496 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8497 g_strdup(systemid), g_free);
8498 g_signal_connect(G_OBJECT(widget), "activate",
8499 G_CALLBACK(compose_set_privacy_system_cb), compose);
8501 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8502 gtk_widget_show(widget);
8503 g_free(systemid);
8505 g_slist_free(systems);
8506 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8507 gtk_widget_show_all(privacy_menu);
8508 gtk_widget_show_all(privacy_menuitem);
8511 void compose_reflect_prefs_all(void)
8513 GList *cur;
8514 Compose *compose;
8516 for (cur = compose_list; cur != NULL; cur = cur->next) {
8517 compose = (Compose *)cur->data;
8518 compose_set_template_menu(compose);
8522 void compose_reflect_prefs_pixmap_theme(void)
8524 GList *cur;
8525 Compose *compose;
8527 for (cur = compose_list; cur != NULL; cur = cur->next) {
8528 compose = (Compose *)cur->data;
8529 toolbar_update(TOOLBAR_COMPOSE, compose);
8533 static const gchar *compose_quote_char_from_context(Compose *compose)
8535 const gchar *qmark = NULL;
8537 cm_return_val_if_fail(compose != NULL, NULL);
8539 switch (compose->mode) {
8540 /* use forward-specific quote char */
8541 case COMPOSE_FORWARD:
8542 case COMPOSE_FORWARD_AS_ATTACH:
8543 case COMPOSE_FORWARD_INLINE:
8544 if (compose->folder && compose->folder->prefs &&
8545 compose->folder->prefs->forward_with_format)
8546 qmark = compose->folder->prefs->forward_quotemark;
8547 else if (compose->account->forward_with_format)
8548 qmark = compose->account->forward_quotemark;
8549 else
8550 qmark = prefs_common.fw_quotemark;
8551 break;
8553 /* use reply-specific quote char in all other modes */
8554 default:
8555 if (compose->folder && compose->folder->prefs &&
8556 compose->folder->prefs->reply_with_format)
8557 qmark = compose->folder->prefs->reply_quotemark;
8558 else if (compose->account->reply_with_format)
8559 qmark = compose->account->reply_quotemark;
8560 else
8561 qmark = prefs_common.quotemark;
8562 break;
8565 if (qmark == NULL || *qmark == '\0')
8566 qmark = "> ";
8568 return qmark;
8571 static void compose_template_apply(Compose *compose, Template *tmpl,
8572 gboolean replace)
8574 GtkTextView *text;
8575 GtkTextBuffer *buffer;
8576 GtkTextMark *mark;
8577 GtkTextIter iter;
8578 const gchar *qmark;
8579 gchar *parsed_str = NULL;
8580 gint cursor_pos = 0;
8581 const gchar *err_msg = _("The body of the template has an error at line %d.");
8582 if (!tmpl) return;
8584 /* process the body */
8586 text = GTK_TEXT_VIEW(compose->text);
8587 buffer = gtk_text_view_get_buffer(text);
8589 if (tmpl->value) {
8590 qmark = compose_quote_char_from_context(compose);
8592 if (compose->replyinfo != NULL) {
8594 if (replace)
8595 gtk_text_buffer_set_text(buffer, "", -1);
8596 mark = gtk_text_buffer_get_insert(buffer);
8597 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8599 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8600 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8602 } else if (compose->fwdinfo != NULL) {
8604 if (replace)
8605 gtk_text_buffer_set_text(buffer, "", -1);
8606 mark = gtk_text_buffer_get_insert(buffer);
8607 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8609 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8610 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8612 } else {
8613 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8615 GtkTextIter start, end;
8616 gchar *tmp = NULL;
8618 gtk_text_buffer_get_start_iter(buffer, &start);
8619 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8620 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8622 /* clear the buffer now */
8623 if (replace)
8624 gtk_text_buffer_set_text(buffer, "", -1);
8626 parsed_str = compose_quote_fmt(compose, dummyinfo,
8627 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8628 procmsg_msginfo_free( &dummyinfo );
8630 g_free( tmp );
8632 } else {
8633 if (replace)
8634 gtk_text_buffer_set_text(buffer, "", -1);
8635 mark = gtk_text_buffer_get_insert(buffer);
8636 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8639 if (replace && parsed_str && compose->account->auto_sig)
8640 compose_insert_sig(compose, FALSE);
8642 if (replace && parsed_str) {
8643 gtk_text_buffer_get_start_iter(buffer, &iter);
8644 gtk_text_buffer_place_cursor(buffer, &iter);
8647 if (parsed_str) {
8648 cursor_pos = quote_fmt_get_cursor_pos();
8649 compose->set_cursor_pos = cursor_pos;
8650 if (cursor_pos == -1)
8651 cursor_pos = 0;
8652 gtk_text_buffer_get_start_iter(buffer, &iter);
8653 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8654 gtk_text_buffer_place_cursor(buffer, &iter);
8657 /* process the other fields */
8659 compose_template_apply_fields(compose, tmpl);
8660 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8661 quote_fmt_reset_vartable();
8662 compose_changed_cb(NULL, compose);
8664 #ifdef USE_ENCHANT
8665 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8666 gtkaspell_highlight_all(compose->gtkaspell);
8667 #endif
8670 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8672 MsgInfo* dummyinfo = NULL;
8673 MsgInfo *msginfo = NULL;
8674 gchar *buf = NULL;
8676 if (compose->replyinfo != NULL)
8677 msginfo = compose->replyinfo;
8678 else if (compose->fwdinfo != NULL)
8679 msginfo = compose->fwdinfo;
8680 else {
8681 dummyinfo = compose_msginfo_new_from_compose(compose);
8682 msginfo = dummyinfo;
8685 if (tmpl->from && *tmpl->from != '\0') {
8686 #ifdef USE_ENCHANT
8687 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8688 compose->gtkaspell);
8689 #else
8690 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8691 #endif
8692 quote_fmt_scan_string(tmpl->from);
8693 quote_fmt_parse();
8695 buf = quote_fmt_get_buffer();
8696 if (buf == NULL) {
8697 alertpanel_error(_("Template From format error."));
8698 } else {
8699 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8703 if (tmpl->to && *tmpl->to != '\0') {
8704 #ifdef USE_ENCHANT
8705 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8706 compose->gtkaspell);
8707 #else
8708 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8709 #endif
8710 quote_fmt_scan_string(tmpl->to);
8711 quote_fmt_parse();
8713 buf = quote_fmt_get_buffer();
8714 if (buf == NULL) {
8715 alertpanel_error(_("Template To format error."));
8716 } else {
8717 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8721 if (tmpl->cc && *tmpl->cc != '\0') {
8722 #ifdef USE_ENCHANT
8723 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8724 compose->gtkaspell);
8725 #else
8726 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8727 #endif
8728 quote_fmt_scan_string(tmpl->cc);
8729 quote_fmt_parse();
8731 buf = quote_fmt_get_buffer();
8732 if (buf == NULL) {
8733 alertpanel_error(_("Template Cc format error."));
8734 } else {
8735 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8739 if (tmpl->bcc && *tmpl->bcc != '\0') {
8740 #ifdef USE_ENCHANT
8741 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8742 compose->gtkaspell);
8743 #else
8744 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8745 #endif
8746 quote_fmt_scan_string(tmpl->bcc);
8747 quote_fmt_parse();
8749 buf = quote_fmt_get_buffer();
8750 if (buf == NULL) {
8751 alertpanel_error(_("Template Bcc format error."));
8752 } else {
8753 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8757 if (tmpl->replyto && *tmpl->replyto != '\0') {
8758 #ifdef USE_ENCHANT
8759 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8760 compose->gtkaspell);
8761 #else
8762 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8763 #endif
8764 quote_fmt_scan_string(tmpl->replyto);
8765 quote_fmt_parse();
8767 buf = quote_fmt_get_buffer();
8768 if (buf == NULL) {
8769 alertpanel_error(_("Template Reply-To format error."));
8770 } else {
8771 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8775 /* process the subject */
8776 if (tmpl->subject && *tmpl->subject != '\0') {
8777 #ifdef USE_ENCHANT
8778 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8779 compose->gtkaspell);
8780 #else
8781 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8782 #endif
8783 quote_fmt_scan_string(tmpl->subject);
8784 quote_fmt_parse();
8786 buf = quote_fmt_get_buffer();
8787 if (buf == NULL) {
8788 alertpanel_error(_("Template subject format error."));
8789 } else {
8790 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8794 procmsg_msginfo_free( &dummyinfo );
8797 static void compose_destroy(Compose *compose)
8799 GtkAllocation allocation;
8800 GtkTextBuffer *buffer;
8801 GtkClipboard *clipboard;
8803 compose_list = g_list_remove(compose_list, compose);
8805 if (compose->updating) {
8806 debug_print("danger, not destroying anything now\n");
8807 compose->deferred_destroy = TRUE;
8808 return;
8811 /* NOTE: address_completion_end() does nothing with the window
8812 * however this may change. */
8813 address_completion_end(compose->window);
8815 slist_free_strings_full(compose->to_list);
8816 slist_free_strings_full(compose->newsgroup_list);
8817 slist_free_strings_full(compose->header_list);
8819 slist_free_strings_full(extra_headers);
8820 extra_headers = NULL;
8822 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8824 g_hash_table_destroy(compose->email_hashtable);
8826 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8827 compose->folder_update_callback_id);
8829 procmsg_msginfo_free(&(compose->targetinfo));
8830 procmsg_msginfo_free(&(compose->replyinfo));
8831 procmsg_msginfo_free(&(compose->fwdinfo));
8833 g_free(compose->replyto);
8834 g_free(compose->cc);
8835 g_free(compose->bcc);
8836 g_free(compose->newsgroups);
8837 g_free(compose->followup_to);
8839 g_free(compose->ml_post);
8841 g_free(compose->inreplyto);
8842 g_free(compose->references);
8843 g_free(compose->msgid);
8844 g_free(compose->boundary);
8846 g_free(compose->redirect_filename);
8847 if (compose->undostruct)
8848 undo_destroy(compose->undostruct);
8850 g_free(compose->sig_str);
8852 g_free(compose->exteditor_file);
8854 g_free(compose->orig_charset);
8856 g_free(compose->privacy_system);
8857 g_free(compose->encdata);
8859 #ifndef USE_ALT_ADDRBOOK
8860 if (addressbook_get_target_compose() == compose)
8861 addressbook_set_target_compose(NULL);
8862 #endif
8863 #if USE_ENCHANT
8864 if (compose->gtkaspell) {
8865 gtkaspell_delete(compose->gtkaspell);
8866 compose->gtkaspell = NULL;
8868 #endif
8870 if (!compose->batch) {
8871 gtk_widget_get_allocation(compose->window, &allocation);
8872 prefs_common.compose_width = allocation.width;
8873 prefs_common.compose_height = allocation.height;
8876 if (!gtk_widget_get_parent(compose->paned))
8877 gtk_widget_destroy(compose->paned);
8878 gtk_widget_destroy(compose->popupmenu);
8880 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8881 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8882 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8884 gtk_widget_destroy(compose->window);
8885 toolbar_destroy(compose->toolbar);
8886 g_free(compose->toolbar);
8887 cm_mutex_free(compose->mutex);
8888 g_free(compose);
8891 static void compose_attach_info_free(AttachInfo *ainfo)
8893 g_free(ainfo->file);
8894 g_free(ainfo->content_type);
8895 g_free(ainfo->name);
8896 g_free(ainfo->charset);
8897 g_free(ainfo);
8900 static void compose_attach_update_label(Compose *compose)
8902 GtkTreeIter iter;
8903 gint i = 1;
8904 gchar *text;
8905 GtkTreeModel *model;
8907 if(compose == NULL)
8908 return;
8910 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8911 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8912 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8913 return;
8916 while(gtk_tree_model_iter_next(model, &iter))
8917 i++;
8919 text = g_strdup_printf("(%d)", i);
8920 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8921 g_free(text);
8924 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8926 Compose *compose = (Compose *)data;
8927 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8928 GtkTreeSelection *selection;
8929 GList *sel, *cur;
8930 GtkTreeModel *model;
8932 selection = gtk_tree_view_get_selection(tree_view);
8933 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8935 if (!sel)
8936 return;
8938 for (cur = sel; cur != NULL; cur = cur->next) {
8939 GtkTreePath *path = cur->data;
8940 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8941 (model, cur->data);
8942 cur->data = ref;
8943 gtk_tree_path_free(path);
8946 for (cur = sel; cur != NULL; cur = cur->next) {
8947 GtkTreeRowReference *ref = cur->data;
8948 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8949 GtkTreeIter iter;
8951 if (gtk_tree_model_get_iter(model, &iter, path))
8952 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8954 gtk_tree_path_free(path);
8955 gtk_tree_row_reference_free(ref);
8958 g_list_free(sel);
8959 compose_attach_update_label(compose);
8962 static struct _AttachProperty
8964 GtkWidget *window;
8965 GtkWidget *mimetype_entry;
8966 GtkWidget *encoding_optmenu;
8967 GtkWidget *path_entry;
8968 GtkWidget *filename_entry;
8969 GtkWidget *ok_btn;
8970 GtkWidget *cancel_btn;
8971 } attach_prop;
8973 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8975 gtk_tree_path_free((GtkTreePath *)ptr);
8978 static void compose_attach_property(GtkAction *action, gpointer data)
8980 Compose *compose = (Compose *)data;
8981 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8982 AttachInfo *ainfo;
8983 GtkComboBox *optmenu;
8984 GtkTreeSelection *selection;
8985 GList *sel;
8986 GtkTreeModel *model;
8987 GtkTreeIter iter;
8988 GtkTreePath *path;
8989 static gboolean cancelled;
8991 /* only if one selected */
8992 selection = gtk_tree_view_get_selection(tree_view);
8993 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8994 return;
8996 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8997 if (!sel)
8998 return;
9000 path = (GtkTreePath *) sel->data;
9001 gtk_tree_model_get_iter(model, &iter, path);
9002 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9004 if (!ainfo) {
9005 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9006 g_list_free(sel);
9007 return;
9009 g_list_free(sel);
9011 if (!attach_prop.window)
9012 compose_attach_property_create(&cancelled);
9013 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9014 gtk_widget_grab_focus(attach_prop.ok_btn);
9015 gtk_widget_show(attach_prop.window);
9016 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9017 GTK_WINDOW(compose->window));
9019 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9020 if (ainfo->encoding == ENC_UNKNOWN)
9021 combobox_select_by_data(optmenu, ENC_BASE64);
9022 else
9023 combobox_select_by_data(optmenu, ainfo->encoding);
9025 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9026 ainfo->content_type ? ainfo->content_type : "");
9027 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9028 ainfo->file ? ainfo->file : "");
9029 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9030 ainfo->name ? ainfo->name : "");
9032 for (;;) {
9033 const gchar *entry_text;
9034 gchar *text;
9035 gchar *cnttype = NULL;
9036 gchar *file = NULL;
9037 off_t size = 0;
9039 cancelled = FALSE;
9040 gtk_main();
9042 gtk_widget_hide(attach_prop.window);
9043 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9045 if (cancelled)
9046 break;
9048 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9049 if (*entry_text != '\0') {
9050 gchar *p;
9052 text = g_strstrip(g_strdup(entry_text));
9053 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9054 cnttype = g_strdup(text);
9055 g_free(text);
9056 } else {
9057 alertpanel_error(_("Invalid MIME type."));
9058 g_free(text);
9059 continue;
9063 ainfo->encoding = combobox_get_active_data(optmenu);
9065 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9066 if (*entry_text != '\0') {
9067 if (is_file_exist(entry_text) &&
9068 (size = get_file_size(entry_text)) > 0)
9069 file = g_strdup(entry_text);
9070 else {
9071 alertpanel_error
9072 (_("File doesn't exist or is empty."));
9073 g_free(cnttype);
9074 continue;
9078 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9079 if (*entry_text != '\0') {
9080 g_free(ainfo->name);
9081 ainfo->name = g_strdup(entry_text);
9084 if (cnttype) {
9085 g_free(ainfo->content_type);
9086 ainfo->content_type = cnttype;
9088 if (file) {
9089 g_free(ainfo->file);
9090 ainfo->file = file;
9092 if (size)
9093 ainfo->size = (goffset)size;
9095 /* update tree store */
9096 text = to_human_readable(ainfo->size);
9097 gtk_tree_model_get_iter(model, &iter, path);
9098 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9099 COL_MIMETYPE, ainfo->content_type,
9100 COL_SIZE, text,
9101 COL_NAME, ainfo->name,
9102 COL_CHARSET, ainfo->charset,
9103 -1);
9105 break;
9108 gtk_tree_path_free(path);
9111 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9113 label = gtk_label_new(str); \
9114 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9115 GTK_FILL, 0, 0, 0); \
9116 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9118 entry = gtk_entry_new(); \
9119 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9120 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9123 static void compose_attach_property_create(gboolean *cancelled)
9125 GtkWidget *window;
9126 GtkWidget *vbox;
9127 GtkWidget *table;
9128 GtkWidget *label;
9129 GtkWidget *mimetype_entry;
9130 GtkWidget *hbox;
9131 GtkWidget *optmenu;
9132 GtkListStore *optmenu_menu;
9133 GtkWidget *path_entry;
9134 GtkWidget *filename_entry;
9135 GtkWidget *hbbox;
9136 GtkWidget *ok_btn;
9137 GtkWidget *cancel_btn;
9138 GList *mime_type_list, *strlist;
9139 GtkTreeIter iter;
9141 debug_print("Creating attach_property window...\n");
9143 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9144 gtk_widget_set_size_request(window, 480, -1);
9145 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9146 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9147 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9148 g_signal_connect(G_OBJECT(window), "delete_event",
9149 G_CALLBACK(attach_property_delete_event),
9150 cancelled);
9151 g_signal_connect(G_OBJECT(window), "key_press_event",
9152 G_CALLBACK(attach_property_key_pressed),
9153 cancelled);
9155 vbox = gtk_vbox_new(FALSE, 8);
9156 gtk_container_add(GTK_CONTAINER(window), vbox);
9158 table = gtk_table_new(4, 2, FALSE);
9159 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9160 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9161 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9163 label = gtk_label_new(_("MIME type"));
9164 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9165 GTK_FILL, 0, 0, 0);
9166 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9167 #if !GTK_CHECK_VERSION(2, 24, 0)
9168 mimetype_entry = gtk_combo_box_entry_new_text();
9169 #else
9170 mimetype_entry = gtk_combo_box_text_new_with_entry();
9171 #endif
9172 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9173 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9175 /* stuff with list */
9176 mime_type_list = procmime_get_mime_type_list();
9177 strlist = NULL;
9178 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9179 MimeType *type = (MimeType *) mime_type_list->data;
9180 gchar *tmp;
9182 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9184 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9185 g_free(tmp);
9186 else
9187 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9188 (GCompareFunc)strcmp2);
9191 for (mime_type_list = strlist; mime_type_list != NULL;
9192 mime_type_list = mime_type_list->next) {
9193 #if !GTK_CHECK_VERSION(2, 24, 0)
9194 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9195 #else
9196 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9197 #endif
9198 g_free(mime_type_list->data);
9200 g_list_free(strlist);
9201 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9202 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9204 label = gtk_label_new(_("Encoding"));
9205 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9206 GTK_FILL, 0, 0, 0);
9207 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9209 hbox = gtk_hbox_new(FALSE, 0);
9210 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9211 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9213 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9214 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9216 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9217 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9218 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9219 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9220 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9222 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9224 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9225 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9227 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9228 &ok_btn, GTK_STOCK_OK,
9229 NULL, NULL);
9230 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9231 gtk_widget_grab_default(ok_btn);
9233 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9234 G_CALLBACK(attach_property_ok),
9235 cancelled);
9236 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9237 G_CALLBACK(attach_property_cancel),
9238 cancelled);
9240 gtk_widget_show_all(vbox);
9242 attach_prop.window = window;
9243 attach_prop.mimetype_entry = mimetype_entry;
9244 attach_prop.encoding_optmenu = optmenu;
9245 attach_prop.path_entry = path_entry;
9246 attach_prop.filename_entry = filename_entry;
9247 attach_prop.ok_btn = ok_btn;
9248 attach_prop.cancel_btn = cancel_btn;
9251 #undef SET_LABEL_AND_ENTRY
9253 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9255 *cancelled = FALSE;
9256 gtk_main_quit();
9259 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9261 *cancelled = TRUE;
9262 gtk_main_quit();
9265 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9266 gboolean *cancelled)
9268 *cancelled = TRUE;
9269 gtk_main_quit();
9271 return TRUE;
9274 static gboolean attach_property_key_pressed(GtkWidget *widget,
9275 GdkEventKey *event,
9276 gboolean *cancelled)
9278 if (event && event->keyval == GDK_KEY_Escape) {
9279 *cancelled = TRUE;
9280 gtk_main_quit();
9282 if (event && event->keyval == GDK_KEY_Return) {
9283 *cancelled = FALSE;
9284 gtk_main_quit();
9285 return TRUE;
9287 return FALSE;
9290 static void compose_exec_ext_editor(Compose *compose)
9292 #ifdef G_OS_UNIX
9293 gchar *tmp;
9294 GtkWidget *socket;
9295 GdkNativeWindow socket_wid = 0;
9296 pid_t pid;
9297 gint pipe_fds[2];
9299 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9300 G_DIR_SEPARATOR, compose);
9302 if (compose_get_ext_editor_uses_socket()) {
9303 /* Only allow one socket */
9304 if (compose->exteditor_socket != NULL) {
9305 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9306 /* Move the focus off of the socket */
9307 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9309 g_free(tmp);
9310 return;
9312 /* Create the receiving GtkSocket */
9313 socket = gtk_socket_new ();
9314 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9315 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9316 compose);
9317 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9318 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9319 /* Realize the socket so that we can use its ID */
9320 gtk_widget_realize(socket);
9321 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9322 compose->exteditor_socket = socket;
9325 if (pipe(pipe_fds) < 0) {
9326 perror("pipe");
9327 g_free(tmp);
9328 return;
9331 if ((pid = fork()) < 0) {
9332 perror("fork");
9333 g_free(tmp);
9334 return;
9337 if (pid != 0) {
9338 /* close the write side of the pipe */
9339 close(pipe_fds[1]);
9341 compose->exteditor_file = g_strdup(tmp);
9342 compose->exteditor_pid = pid;
9344 compose_set_ext_editor_sensitive(compose, FALSE);
9346 #ifndef G_OS_WIN32
9347 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9348 #else
9349 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9350 #endif
9351 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9352 G_IO_IN,
9353 compose_input_cb,
9354 compose);
9355 } else { /* process-monitoring process */
9356 pid_t pid_ed;
9358 if (setpgid(0, 0))
9359 perror("setpgid");
9361 /* close the read side of the pipe */
9362 close(pipe_fds[0]);
9364 if (compose_write_body_to_file(compose, tmp) < 0) {
9365 fd_write_all(pipe_fds[1], "2\n", 2);
9366 _exit(1);
9369 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9370 if (pid_ed < 0) {
9371 fd_write_all(pipe_fds[1], "1\n", 2);
9372 _exit(1);
9375 /* wait until editor is terminated */
9376 waitpid(pid_ed, NULL, 0);
9378 fd_write_all(pipe_fds[1], "0\n", 2);
9380 close(pipe_fds[1]);
9381 _exit(0);
9384 g_free(tmp);
9385 #endif /* G_OS_UNIX */
9388 #ifdef G_OS_UNIX
9389 static gboolean compose_get_ext_editor_cmd_valid()
9391 gboolean has_s = FALSE;
9392 gboolean has_w = FALSE;
9393 const gchar *p = prefs_common_get_ext_editor_cmd();
9394 if (!p)
9395 return FALSE;
9396 while ((p = strchr(p, '%'))) {
9397 p++;
9398 if (*p == 's') {
9399 if (has_s)
9400 return FALSE;
9401 has_s = TRUE;
9402 } else if (*p == 'w') {
9403 if (has_w)
9404 return FALSE;
9405 has_w = TRUE;
9406 } else {
9407 return FALSE;
9410 return TRUE;
9413 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9415 gchar buf[1024];
9416 gchar *p, *s;
9417 gchar **cmdline;
9418 pid_t pid;
9420 cm_return_val_if_fail(file != NULL, -1);
9422 if ((pid = fork()) < 0) {
9423 perror("fork");
9424 return -1;
9427 if (pid != 0) return pid;
9429 /* grandchild process */
9431 if (setpgid(0, getppid()))
9432 perror("setpgid");
9434 if (compose_get_ext_editor_cmd_valid()) {
9435 if (compose_get_ext_editor_uses_socket()) {
9436 p = g_strdup(prefs_common_get_ext_editor_cmd());
9437 s = strstr(p, "%w");
9438 s[1] = 'u';
9439 if (strstr(p, "%s") < s)
9440 g_snprintf(buf, sizeof(buf), p, file, socket_wid);
9441 else
9442 g_snprintf(buf, sizeof(buf), p, socket_wid, file);
9443 g_free(p);
9444 } else {
9445 g_snprintf(buf, sizeof(buf),
9446 prefs_common_get_ext_editor_cmd(), file);
9448 } else {
9449 if (prefs_common_get_ext_editor_cmd())
9450 g_warning("External editor command-line is invalid: '%s'",
9451 prefs_common_get_ext_editor_cmd());
9452 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9455 cmdline = strsplit_with_quote(buf, " ", 1024);
9456 execvp(cmdline[0], cmdline);
9458 perror("execvp");
9459 g_strfreev(cmdline);
9461 _exit(1);
9464 static gboolean compose_ext_editor_kill(Compose *compose)
9466 pid_t pgid = compose->exteditor_pid * -1;
9467 gint ret;
9469 ret = kill(pgid, 0);
9471 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9472 AlertValue val;
9473 gchar *msg;
9475 msg = g_strdup_printf
9476 (_("The external editor is still working.\n"
9477 "Force terminating the process?\n"
9478 "process group id: %d"), -pgid);
9479 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9480 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9482 g_free(msg);
9484 if (val == G_ALERTALTERNATE) {
9485 g_source_remove(compose->exteditor_tag);
9486 g_io_channel_shutdown(compose->exteditor_ch,
9487 FALSE, NULL);
9488 g_io_channel_unref(compose->exteditor_ch);
9490 if (kill(pgid, SIGTERM) < 0) perror("kill");
9491 waitpid(compose->exteditor_pid, NULL, 0);
9493 g_warning("Terminated process group id: %d. "
9494 "Temporary file: %s", -pgid, compose->exteditor_file);
9496 compose_set_ext_editor_sensitive(compose, TRUE);
9498 g_free(compose->exteditor_file);
9499 compose->exteditor_file = NULL;
9500 compose->exteditor_pid = -1;
9501 compose->exteditor_ch = NULL;
9502 compose->exteditor_tag = -1;
9503 } else
9504 return FALSE;
9507 return TRUE;
9510 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9511 gpointer data)
9513 gchar buf[3] = "3";
9514 Compose *compose = (Compose *)data;
9515 gsize bytes_read;
9517 debug_print("Compose: input from monitoring process\n");
9519 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9520 bytes_read = 0;
9521 buf[0] = '\0';
9524 g_io_channel_shutdown(source, FALSE, NULL);
9525 g_io_channel_unref(source);
9527 waitpid(compose->exteditor_pid, NULL, 0);
9529 if (buf[0] == '0') { /* success */
9530 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9531 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9532 GtkTextIter start, end;
9533 gchar *chars;
9535 gtk_text_buffer_set_text(buffer, "", -1);
9536 compose_insert_file(compose, compose->exteditor_file);
9537 compose_changed_cb(NULL, compose);
9538 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9540 if (claws_unlink(compose->exteditor_file) < 0)
9541 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9543 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9544 gtk_text_buffer_get_start_iter(buffer, &start);
9545 gtk_text_buffer_get_end_iter(buffer, &end);
9546 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9547 if (chars && strlen(chars) > 0)
9548 compose->modified = TRUE;
9549 g_free(chars);
9550 } else if (buf[0] == '1') { /* failed */
9551 g_warning("Couldn't exec external editor");
9552 if (claws_unlink(compose->exteditor_file) < 0)
9553 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9554 } else if (buf[0] == '2') {
9555 g_warning("Couldn't write to file");
9556 } else if (buf[0] == '3') {
9557 g_warning("Pipe read failed");
9560 compose_set_ext_editor_sensitive(compose, TRUE);
9562 g_free(compose->exteditor_file);
9563 compose->exteditor_file = NULL;
9564 compose->exteditor_pid = -1;
9565 compose->exteditor_ch = NULL;
9566 compose->exteditor_tag = -1;
9567 if (compose->exteditor_socket) {
9568 gtk_widget_destroy(compose->exteditor_socket);
9569 compose->exteditor_socket = NULL;
9573 return FALSE;
9576 static char *ext_editor_menu_entries[] = {
9577 "Menu/Message/Send",
9578 "Menu/Message/SendLater",
9579 "Menu/Message/InsertFile",
9580 "Menu/Message/InsertSig",
9581 "Menu/Message/ReplaceSig",
9582 "Menu/Message/Save",
9583 "Menu/Message/Print",
9584 "Menu/Edit",
9585 #if USE_ENCHANT
9586 "Menu/Spelling",
9587 #endif
9588 "Menu/Tools/ShowRuler",
9589 "Menu/Tools/Actions",
9590 "Menu/Help",
9591 NULL
9594 static void compose_set_ext_editor_sensitive(Compose *compose,
9595 gboolean sensitive)
9597 int i;
9599 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9600 cm_menu_set_sensitive_full(compose->ui_manager,
9601 ext_editor_menu_entries[i], sensitive);
9604 if (compose_get_ext_editor_uses_socket()) {
9605 if (sensitive) {
9606 if (compose->exteditor_socket)
9607 gtk_widget_hide(compose->exteditor_socket);
9608 gtk_widget_show(compose->scrolledwin);
9609 if (prefs_common.show_ruler)
9610 gtk_widget_show(compose->ruler_hbox);
9611 /* Fix the focus, as it doesn't go anywhere when the
9612 * socket is hidden or destroyed */
9613 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9614 } else {
9615 g_assert (compose->exteditor_socket != NULL);
9616 /* Fix the focus, as it doesn't go anywhere when the
9617 * edit box is hidden */
9618 if (gtk_widget_is_focus(compose->text))
9619 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9620 gtk_widget_hide(compose->scrolledwin);
9621 gtk_widget_hide(compose->ruler_hbox);
9622 gtk_widget_show(compose->exteditor_socket);
9624 } else {
9625 gtk_widget_set_sensitive(compose->text, sensitive);
9627 if (compose->toolbar->send_btn)
9628 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9629 if (compose->toolbar->sendl_btn)
9630 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9631 if (compose->toolbar->draft_btn)
9632 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9633 if (compose->toolbar->insert_btn)
9634 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9635 if (compose->toolbar->sig_btn)
9636 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9637 if (compose->toolbar->exteditor_btn)
9638 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9639 if (compose->toolbar->linewrap_current_btn)
9640 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9641 if (compose->toolbar->linewrap_all_btn)
9642 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9645 static gboolean compose_get_ext_editor_uses_socket()
9647 return (prefs_common_get_ext_editor_cmd() &&
9648 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9651 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9653 compose->exteditor_socket = NULL;
9654 /* returning FALSE allows destruction of the socket */
9655 return FALSE;
9657 #endif /* G_OS_UNIX */
9660 * compose_undo_state_changed:
9662 * Change the sensivity of the menuentries undo and redo
9664 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9665 gint redo_state, gpointer data)
9667 Compose *compose = (Compose *)data;
9669 switch (undo_state) {
9670 case UNDO_STATE_TRUE:
9671 if (!undostruct->undo_state) {
9672 undostruct->undo_state = TRUE;
9673 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9675 break;
9676 case UNDO_STATE_FALSE:
9677 if (undostruct->undo_state) {
9678 undostruct->undo_state = FALSE;
9679 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9681 break;
9682 case UNDO_STATE_UNCHANGED:
9683 break;
9684 case UNDO_STATE_REFRESH:
9685 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9686 break;
9687 default:
9688 g_warning("Undo state not recognized");
9689 break;
9692 switch (redo_state) {
9693 case UNDO_STATE_TRUE:
9694 if (!undostruct->redo_state) {
9695 undostruct->redo_state = TRUE;
9696 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9698 break;
9699 case UNDO_STATE_FALSE:
9700 if (undostruct->redo_state) {
9701 undostruct->redo_state = FALSE;
9702 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9704 break;
9705 case UNDO_STATE_UNCHANGED:
9706 break;
9707 case UNDO_STATE_REFRESH:
9708 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9709 break;
9710 default:
9711 g_warning("Redo state not recognized");
9712 break;
9716 /* callback functions */
9718 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9719 GtkAllocation *allocation,
9720 GtkPaned *paned)
9722 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9725 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9726 * includes "non-client" (windows-izm) in calculation, so this calculation
9727 * may not be accurate.
9729 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9730 GtkAllocation *allocation,
9731 GtkSHRuler *shruler)
9733 if (prefs_common.show_ruler) {
9734 gint char_width = 0, char_height = 0;
9735 gint line_width_in_chars;
9737 gtkut_get_font_size(GTK_WIDGET(widget),
9738 &char_width, &char_height);
9739 line_width_in_chars =
9740 (allocation->width - allocation->x) / char_width;
9742 /* got the maximum */
9743 gtk_shruler_set_range(GTK_SHRULER(shruler),
9744 0.0, line_width_in_chars, 0);
9747 return TRUE;
9750 typedef struct {
9751 gchar *header;
9752 gchar *entry;
9753 ComposePrefType type;
9754 gboolean entry_marked;
9755 } HeaderEntryState;
9757 static void account_activated(GtkComboBox *optmenu, gpointer data)
9759 Compose *compose = (Compose *)data;
9761 PrefsAccount *ac;
9762 gchar *folderidentifier;
9763 gint account_id = 0;
9764 GtkTreeModel *menu;
9765 GtkTreeIter iter;
9766 GSList *list, *saved_list = NULL;
9767 HeaderEntryState *state;
9768 GtkRcStyle *style = NULL;
9769 #if !GTK_CHECK_VERSION(3, 0, 0)
9770 static GdkColor yellow;
9771 static gboolean color_set = FALSE;
9772 #else
9773 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9774 #endif
9776 /* Get ID of active account in the combo box */
9777 menu = gtk_combo_box_get_model(optmenu);
9778 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9779 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9781 ac = account_find_from_id(account_id);
9782 cm_return_if_fail(ac != NULL);
9784 if (ac != compose->account) {
9785 compose_select_account(compose, ac, FALSE);
9787 for (list = compose->header_list; list; list = list->next) {
9788 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9790 if (hentry->type == PREF_ACCOUNT || !list->next) {
9791 compose_destroy_headerentry(compose, hentry);
9792 continue;
9795 state = g_malloc0(sizeof(HeaderEntryState));
9796 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9797 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9798 state->entry = gtk_editable_get_chars(
9799 GTK_EDITABLE(hentry->entry), 0, -1);
9800 state->type = hentry->type;
9802 #if !GTK_CHECK_VERSION(3, 0, 0)
9803 if (!color_set) {
9804 gdk_color_parse("#f5f6be", &yellow);
9805 color_set = gdk_colormap_alloc_color(
9806 gdk_colormap_get_system(),
9807 &yellow, FALSE, TRUE);
9809 #endif
9811 style = gtk_widget_get_modifier_style(hentry->entry);
9812 state->entry_marked = gdk_color_equal(&yellow,
9813 &style->base[GTK_STATE_NORMAL]);
9815 saved_list = g_slist_append(saved_list, state);
9816 compose_destroy_headerentry(compose, hentry);
9819 compose->header_last = NULL;
9820 g_slist_free(compose->header_list);
9821 compose->header_list = NULL;
9822 compose->header_nextrow = 1;
9823 compose_create_header_entry(compose);
9825 if (ac->set_autocc && ac->auto_cc)
9826 compose_entry_append(compose, ac->auto_cc,
9827 COMPOSE_CC, PREF_ACCOUNT);
9829 if (ac->set_autobcc && ac->auto_bcc)
9830 compose_entry_append(compose, ac->auto_bcc,
9831 COMPOSE_BCC, PREF_ACCOUNT);
9833 if (ac->set_autoreplyto && ac->auto_replyto)
9834 compose_entry_append(compose, ac->auto_replyto,
9835 COMPOSE_REPLYTO, PREF_ACCOUNT);
9837 for (list = saved_list; list; list = list->next) {
9838 state = (HeaderEntryState *) list->data;
9840 compose_add_header_entry(compose, state->header,
9841 state->entry, state->type);
9842 if (state->entry_marked)
9843 compose_entry_mark_default_to(compose, state->entry);
9845 g_free(state->header);
9846 g_free(state->entry);
9847 g_free(state);
9849 g_slist_free(saved_list);
9851 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9852 (ac->protocol == A_NNTP) ?
9853 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9856 /* Set message save folder */
9857 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9858 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9860 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9861 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9863 compose_set_save_to(compose, NULL);
9864 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9865 folderidentifier = folder_item_get_identifier(account_get_special_folder
9866 (compose->account, F_OUTBOX));
9867 compose_set_save_to(compose, folderidentifier);
9868 g_free(folderidentifier);
9872 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9873 GtkTreeViewColumn *column, Compose *compose)
9875 compose_attach_property(NULL, compose);
9878 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9879 gpointer data)
9881 Compose *compose = (Compose *)data;
9882 GtkTreeSelection *attach_selection;
9883 gint attach_nr_selected;
9884 GtkTreePath *path;
9886 if (!event) return FALSE;
9888 if (event->button == 3) {
9889 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9890 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9892 /* If no rows, or just one row is selected, right-click should
9893 * open menu relevant to the row being right-clicked on. We
9894 * achieve that by selecting the clicked row first. If more
9895 * than one row is selected, we shouldn't modify the selection,
9896 * as user may want to remove selected rows (attachments). */
9897 if (attach_nr_selected < 2) {
9898 gtk_tree_selection_unselect_all(attach_selection);
9899 attach_nr_selected = 0;
9900 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
9901 event->x, event->y, &path, NULL, NULL, NULL);
9902 if (path != NULL) {
9903 gtk_tree_selection_select_path(attach_selection, path);
9904 gtk_tree_path_free(path);
9905 attach_nr_selected++;
9909 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9910 /* Properties menu item makes no sense with more than one row
9911 * selected, the properties dialog can only edit one attachment. */
9912 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
9914 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9915 NULL, NULL, event->button, event->time);
9916 return TRUE;
9919 return FALSE;
9922 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9923 gpointer data)
9925 Compose *compose = (Compose *)data;
9927 if (!event) return FALSE;
9929 switch (event->keyval) {
9930 case GDK_KEY_Delete:
9931 compose_attach_remove_selected(NULL, compose);
9932 break;
9934 return FALSE;
9937 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9939 toolbar_comp_set_sensitive(compose, allow);
9940 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9941 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9942 #if USE_ENCHANT
9943 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9944 #endif
9945 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9946 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9947 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9949 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9953 static void compose_send_cb(GtkAction *action, gpointer data)
9955 Compose *compose = (Compose *)data;
9957 #ifdef G_OS_UNIX
9958 if (compose->exteditor_tag != -1) {
9959 debug_print("ignoring send: external editor still open\n");
9960 return;
9962 #endif
9963 if (prefs_common.work_offline &&
9964 !inc_offline_should_override(TRUE,
9965 _("Claws Mail needs network access in order "
9966 "to send this email.")))
9967 return;
9969 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9970 g_source_remove(compose->draft_timeout_tag);
9971 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9974 compose_send(compose);
9977 static void compose_send_later_cb(GtkAction *action, gpointer data)
9979 Compose *compose = (Compose *)data;
9980 gint val;
9982 inc_lock();
9983 compose_allow_user_actions(compose, FALSE);
9984 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9985 compose_allow_user_actions(compose, TRUE);
9986 inc_unlock();
9988 if (!val) {
9989 compose_close(compose);
9990 } else if (val == -1) {
9991 alertpanel_error(_("Could not queue message."));
9992 } else if (val == -2) {
9993 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
9994 } else if (val == -3) {
9995 if (privacy_peek_error())
9996 alertpanel_error(_("Could not queue message for sending:\n\n"
9997 "Signature failed: %s"), privacy_get_error());
9998 } else if (val == -4) {
9999 alertpanel_error(_("Could not queue message for sending:\n\n"
10000 "Charset conversion failed."));
10001 } else if (val == -5) {
10002 alertpanel_error(_("Could not queue message for sending:\n\n"
10003 "Couldn't get recipient encryption key."));
10004 } else if (val == -6) {
10005 /* silent error */
10007 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10010 #define DRAFTED_AT_EXIT "drafted_at_exit"
10011 static void compose_register_draft(MsgInfo *info)
10013 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10014 DRAFTED_AT_EXIT, NULL);
10015 FILE *fp = g_fopen(filepath, "ab");
10017 if (fp) {
10018 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10019 info->msgnum);
10020 fclose(fp);
10023 g_free(filepath);
10026 gboolean compose_draft (gpointer data, guint action)
10028 Compose *compose = (Compose *)data;
10029 FolderItem *draft;
10030 gchar *tmp;
10031 gchar *sheaders;
10032 gint msgnum;
10033 MsgFlags flag = {0, 0};
10034 static gboolean lock = FALSE;
10035 MsgInfo *newmsginfo;
10036 FILE *fp;
10037 gboolean target_locked = FALSE;
10038 gboolean err = FALSE;
10040 if (lock) return FALSE;
10042 if (compose->sending)
10043 return TRUE;
10045 draft = account_get_special_folder(compose->account, F_DRAFT);
10046 cm_return_val_if_fail(draft != NULL, FALSE);
10048 if (!g_mutex_trylock(compose->mutex)) {
10049 /* we don't want to lock the mutex once it's available,
10050 * because as the only other part of compose.c locking
10051 * it is compose_close - which means once unlocked,
10052 * the compose struct will be freed */
10053 debug_print("couldn't lock mutex, probably sending\n");
10054 return FALSE;
10057 lock = TRUE;
10059 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10060 G_DIR_SEPARATOR, compose);
10061 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10062 FILE_OP_ERROR(tmp, "fopen");
10063 goto warn_err;
10066 /* chmod for security */
10067 if (change_file_mode_rw(fp, tmp) < 0) {
10068 FILE_OP_ERROR(tmp, "chmod");
10069 g_warning("can't change file mode");
10072 /* Save draft infos */
10073 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10074 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10076 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10077 gchar *savefolderid;
10079 savefolderid = compose_get_save_to(compose);
10080 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10081 g_free(savefolderid);
10083 if (compose->return_receipt) {
10084 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10086 if (compose->privacy_system) {
10087 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10088 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10089 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10092 /* Message-ID of message replying to */
10093 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10094 gchar *folderid = NULL;
10096 if (compose->replyinfo->folder)
10097 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10098 if (folderid == NULL)
10099 folderid = g_strdup("NULL");
10101 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10102 g_free(folderid);
10104 /* Message-ID of message forwarding to */
10105 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10106 gchar *folderid = NULL;
10108 if (compose->fwdinfo->folder)
10109 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10110 if (folderid == NULL)
10111 folderid = g_strdup("NULL");
10113 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10114 g_free(folderid);
10117 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10118 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10120 sheaders = compose_get_manual_headers_info(compose);
10121 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10122 g_free(sheaders);
10124 /* end of headers */
10125 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10127 if (err) {
10128 fclose(fp);
10129 goto warn_err;
10132 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10133 fclose(fp);
10134 goto warn_err;
10136 if (fclose(fp) == EOF) {
10137 goto warn_err;
10140 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10141 if (compose->targetinfo) {
10142 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10143 if (target_locked)
10144 flag.perm_flags |= MSG_LOCKED;
10146 flag.tmp_flags = MSG_DRAFT;
10148 folder_item_scan(draft);
10149 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10150 MsgInfo *tmpinfo = NULL;
10151 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10152 if (compose->msgid) {
10153 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10155 if (tmpinfo) {
10156 msgnum = tmpinfo->msgnum;
10157 procmsg_msginfo_free(&tmpinfo);
10158 debug_print("got draft msgnum %d from scanning\n", msgnum);
10159 } else {
10160 debug_print("didn't get draft msgnum after scanning\n");
10162 } else {
10163 debug_print("got draft msgnum %d from adding\n", msgnum);
10165 if (msgnum < 0) {
10166 warn_err:
10167 claws_unlink(tmp);
10168 g_free(tmp);
10169 if (action != COMPOSE_AUTO_SAVE) {
10170 if (action != COMPOSE_DRAFT_FOR_EXIT)
10171 alertpanel_error(_("Could not save draft."));
10172 else {
10173 AlertValue val;
10174 gtkut_window_popup(compose->window);
10175 val = alertpanel_full(_("Could not save draft"),
10176 _("Could not save draft.\n"
10177 "Do you want to cancel exit or discard this email?"),
10178 _("_Cancel exit"), _("_Discard email"), NULL,
10179 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10180 if (val == G_ALERTALTERNATE) {
10181 lock = FALSE;
10182 g_mutex_unlock(compose->mutex); /* must be done before closing */
10183 compose_close(compose);
10184 return TRUE;
10185 } else {
10186 lock = FALSE;
10187 g_mutex_unlock(compose->mutex); /* must be done before closing */
10188 return FALSE;
10192 goto unlock;
10194 g_free(tmp);
10196 if (compose->mode == COMPOSE_REEDIT) {
10197 compose_remove_reedit_target(compose, TRUE);
10200 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10202 if (newmsginfo) {
10203 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10204 if (target_locked)
10205 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10206 else
10207 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10208 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10209 procmsg_msginfo_set_flags(newmsginfo, 0,
10210 MSG_HAS_ATTACHMENT);
10212 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10213 compose_register_draft(newmsginfo);
10215 procmsg_msginfo_free(&newmsginfo);
10218 folder_item_scan(draft);
10220 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10221 lock = FALSE;
10222 g_mutex_unlock(compose->mutex); /* must be done before closing */
10223 compose_close(compose);
10224 return TRUE;
10225 } else {
10226 GStatBuf s;
10227 gchar *path;
10229 path = folder_item_fetch_msg(draft, msgnum);
10230 if (path == NULL) {
10231 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10232 goto unlock;
10234 if (g_stat(path, &s) < 0) {
10235 FILE_OP_ERROR(path, "stat");
10236 g_free(path);
10237 goto unlock;
10239 g_free(path);
10241 procmsg_msginfo_free(&(compose->targetinfo));
10242 compose->targetinfo = procmsg_msginfo_new();
10243 compose->targetinfo->msgnum = msgnum;
10244 compose->targetinfo->size = (goffset)s.st_size;
10245 compose->targetinfo->mtime = s.st_mtime;
10246 compose->targetinfo->folder = draft;
10247 if (target_locked)
10248 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10249 compose->mode = COMPOSE_REEDIT;
10251 if (action == COMPOSE_AUTO_SAVE) {
10252 compose->autosaved_draft = compose->targetinfo;
10254 compose->modified = FALSE;
10255 compose_set_title(compose);
10257 unlock:
10258 lock = FALSE;
10259 g_mutex_unlock(compose->mutex);
10260 return TRUE;
10263 void compose_clear_exit_drafts(void)
10265 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10266 DRAFTED_AT_EXIT, NULL);
10267 if (is_file_exist(filepath))
10268 claws_unlink(filepath);
10270 g_free(filepath);
10273 void compose_reopen_exit_drafts(void)
10275 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10276 DRAFTED_AT_EXIT, NULL);
10277 FILE *fp = g_fopen(filepath, "rb");
10278 gchar buf[1024];
10280 if (fp) {
10281 while (fgets(buf, sizeof(buf), fp)) {
10282 gchar **parts = g_strsplit(buf, "\t", 2);
10283 const gchar *folder = parts[0];
10284 int msgnum = parts[1] ? atoi(parts[1]):-1;
10286 if (folder && *folder && msgnum > -1) {
10287 FolderItem *item = folder_find_item_from_identifier(folder);
10288 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10289 if (info)
10290 compose_reedit(info, FALSE);
10292 g_strfreev(parts);
10294 fclose(fp);
10296 g_free(filepath);
10297 compose_clear_exit_drafts();
10300 static void compose_save_cb(GtkAction *action, gpointer data)
10302 Compose *compose = (Compose *)data;
10303 compose_draft(compose, COMPOSE_KEEP_EDITING);
10304 compose->rmode = COMPOSE_REEDIT;
10307 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10309 if (compose && file_list) {
10310 GList *tmp;
10312 for ( tmp = file_list; tmp; tmp = tmp->next) {
10313 gchar *file = (gchar *) tmp->data;
10314 gchar *utf8_filename = conv_filename_to_utf8(file);
10315 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10316 compose_changed_cb(NULL, compose);
10317 if (free_data) {
10318 g_free(file);
10319 tmp->data = NULL;
10321 g_free(utf8_filename);
10326 static void compose_attach_cb(GtkAction *action, gpointer data)
10328 Compose *compose = (Compose *)data;
10329 GList *file_list;
10331 if (compose->redirect_filename != NULL)
10332 return;
10334 /* Set focus_window properly, in case we were called via popup menu,
10335 * which unsets it (via focus_out_event callback on compose window). */
10336 manage_window_focus_in(compose->window, NULL, NULL);
10338 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10340 if (file_list) {
10341 compose_attach_from_list(compose, file_list, TRUE);
10342 g_list_free(file_list);
10346 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10348 Compose *compose = (Compose *)data;
10349 GList *file_list;
10350 gint files_inserted = 0;
10352 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10354 if (file_list) {
10355 GList *tmp;
10357 for ( tmp = file_list; tmp; tmp = tmp->next) {
10358 gchar *file = (gchar *) tmp->data;
10359 gchar *filedup = g_strdup(file);
10360 gchar *shortfile = g_path_get_basename(filedup);
10361 ComposeInsertResult res;
10362 /* insert the file if the file is short or if the user confirmed that
10363 he/she wants to insert the large file */
10364 res = compose_insert_file(compose, file);
10365 if (res == COMPOSE_INSERT_READ_ERROR) {
10366 alertpanel_error(_("File '%s' could not be read."), shortfile);
10367 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10368 alertpanel_error(_("File '%s' contained invalid characters\n"
10369 "for the current encoding, insertion may be incorrect."),
10370 shortfile);
10371 } else if (res == COMPOSE_INSERT_SUCCESS)
10372 files_inserted++;
10374 g_free(shortfile);
10375 g_free(filedup);
10376 g_free(file);
10378 g_list_free(file_list);
10381 #ifdef USE_ENCHANT
10382 if (files_inserted > 0 && compose->gtkaspell &&
10383 compose->gtkaspell->check_while_typing)
10384 gtkaspell_highlight_all(compose->gtkaspell);
10385 #endif
10388 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10390 Compose *compose = (Compose *)data;
10392 compose_insert_sig(compose, FALSE);
10395 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10397 Compose *compose = (Compose *)data;
10399 compose_insert_sig(compose, TRUE);
10402 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10403 gpointer data)
10405 gint x, y;
10406 Compose *compose = (Compose *)data;
10408 gtkut_widget_get_uposition(widget, &x, &y);
10409 if (!compose->batch) {
10410 prefs_common.compose_x = x;
10411 prefs_common.compose_y = y;
10413 if (compose->sending || compose->updating)
10414 return TRUE;
10415 compose_close_cb(NULL, compose);
10416 return TRUE;
10419 void compose_close_toolbar(Compose *compose)
10421 compose_close_cb(NULL, compose);
10424 static gboolean compose_can_autosave(Compose *compose)
10426 if (compose->privacy_system && compose->use_encryption)
10427 return prefs_common.autosave && prefs_common.autosave_encrypted;
10428 else
10429 return prefs_common.autosave;
10432 static void compose_close_cb(GtkAction *action, gpointer data)
10434 Compose *compose = (Compose *)data;
10435 AlertValue val;
10437 #ifdef G_OS_UNIX
10438 if (compose->exteditor_tag != -1) {
10439 if (!compose_ext_editor_kill(compose))
10440 return;
10442 #endif
10444 if (compose->modified) {
10445 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10446 if (!g_mutex_trylock(compose->mutex)) {
10447 /* we don't want to lock the mutex once it's available,
10448 * because as the only other part of compose.c locking
10449 * it is compose_close - which means once unlocked,
10450 * the compose struct will be freed */
10451 debug_print("couldn't lock mutex, probably sending\n");
10452 return;
10454 if (!reedit) {
10455 val = alertpanel(_("Discard message"),
10456 _("This message has been modified. Discard it?"),
10457 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10458 } else {
10459 val = alertpanel(_("Save changes"),
10460 _("This message has been modified. Save the latest changes?"),
10461 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10462 GTK_STOCK_CANCEL);
10464 g_mutex_unlock(compose->mutex);
10465 switch (val) {
10466 case G_ALERTDEFAULT:
10467 if (compose_can_autosave(compose) && !reedit)
10468 compose_remove_draft(compose);
10469 break;
10470 case G_ALERTALTERNATE:
10471 compose_draft(data, COMPOSE_QUIT_EDITING);
10472 return;
10473 default:
10474 return;
10478 compose_close(compose);
10481 static void compose_print_cb(GtkAction *action, gpointer data)
10483 Compose *compose = (Compose *) data;
10485 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10486 if (compose->targetinfo)
10487 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10490 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10492 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10493 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10494 Compose *compose = (Compose *) data;
10496 if (active)
10497 compose->out_encoding = (CharSet)value;
10500 static void compose_address_cb(GtkAction *action, gpointer data)
10502 Compose *compose = (Compose *)data;
10504 #ifndef USE_ALT_ADDRBOOK
10505 addressbook_open(compose);
10506 #else
10507 GError* error = NULL;
10508 addressbook_connect_signals(compose);
10509 addressbook_dbus_open(TRUE, &error);
10510 if (error) {
10511 g_warning("%s", error->message);
10512 g_error_free(error);
10514 #endif
10517 static void about_show_cb(GtkAction *action, gpointer data)
10519 about_show();
10522 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10524 Compose *compose = (Compose *)data;
10525 Template *tmpl;
10526 gchar *msg;
10527 AlertValue val;
10529 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10530 cm_return_if_fail(tmpl != NULL);
10532 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10533 tmpl->name);
10534 val = alertpanel(_("Apply template"), msg,
10535 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10536 g_free(msg);
10538 if (val == G_ALERTDEFAULT)
10539 compose_template_apply(compose, tmpl, TRUE);
10540 else if (val == G_ALERTALTERNATE)
10541 compose_template_apply(compose, tmpl, FALSE);
10544 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10546 Compose *compose = (Compose *)data;
10548 #ifdef G_OS_UNIX
10549 if (compose->exteditor_tag != -1) {
10550 debug_print("ignoring open external editor: external editor still open\n");
10551 return;
10553 #endif
10554 compose_exec_ext_editor(compose);
10557 static void compose_undo_cb(GtkAction *action, gpointer data)
10559 Compose *compose = (Compose *)data;
10560 gboolean prev_autowrap = compose->autowrap;
10562 compose->autowrap = FALSE;
10563 undo_undo(compose->undostruct);
10564 compose->autowrap = prev_autowrap;
10567 static void compose_redo_cb(GtkAction *action, gpointer data)
10569 Compose *compose = (Compose *)data;
10570 gboolean prev_autowrap = compose->autowrap;
10572 compose->autowrap = FALSE;
10573 undo_redo(compose->undostruct);
10574 compose->autowrap = prev_autowrap;
10577 static void entry_cut_clipboard(GtkWidget *entry)
10579 if (GTK_IS_EDITABLE(entry))
10580 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10581 else if (GTK_IS_TEXT_VIEW(entry))
10582 gtk_text_buffer_cut_clipboard(
10583 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10584 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10585 TRUE);
10588 static void entry_copy_clipboard(GtkWidget *entry)
10590 if (GTK_IS_EDITABLE(entry))
10591 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10592 else if (GTK_IS_TEXT_VIEW(entry))
10593 gtk_text_buffer_copy_clipboard(
10594 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10595 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10598 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10599 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10601 if (GTK_IS_TEXT_VIEW(entry)) {
10602 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10603 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10604 GtkTextIter start_iter, end_iter;
10605 gint start, end;
10606 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10608 if (contents == NULL)
10609 return;
10611 /* we shouldn't delete the selection when middle-click-pasting, or we
10612 * can't mid-click-paste our own selection */
10613 if (clip != GDK_SELECTION_PRIMARY) {
10614 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10615 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10618 if (insert_place == NULL) {
10619 /* if insert_place isn't specified, insert at the cursor.
10620 * used for Ctrl-V pasting */
10621 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10622 start = gtk_text_iter_get_offset(&start_iter);
10623 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10624 } else {
10625 /* if insert_place is specified, paste here.
10626 * used for mid-click-pasting */
10627 start = gtk_text_iter_get_offset(insert_place);
10628 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10629 if (prefs_common.primary_paste_unselects)
10630 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10633 if (!wrap) {
10634 /* paste unwrapped: mark the paste so it's not wrapped later */
10635 end = start + strlen(contents);
10636 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10637 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10638 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10639 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10640 /* rewrap paragraph now (after a mid-click-paste) */
10641 mark_start = gtk_text_buffer_get_insert(buffer);
10642 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10643 gtk_text_iter_backward_char(&start_iter);
10644 compose_beautify_paragraph(compose, &start_iter, TRUE);
10646 } else if (GTK_IS_EDITABLE(entry))
10647 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10649 compose->modified = TRUE;
10652 static void entry_allsel(GtkWidget *entry)
10654 if (GTK_IS_EDITABLE(entry))
10655 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10656 else if (GTK_IS_TEXT_VIEW(entry)) {
10657 GtkTextIter startiter, enditer;
10658 GtkTextBuffer *textbuf;
10660 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10661 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10662 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10664 gtk_text_buffer_move_mark_by_name(textbuf,
10665 "selection_bound", &startiter);
10666 gtk_text_buffer_move_mark_by_name(textbuf,
10667 "insert", &enditer);
10671 static void compose_cut_cb(GtkAction *action, gpointer data)
10673 Compose *compose = (Compose *)data;
10674 if (compose->focused_editable
10675 #ifndef GENERIC_UMPC
10676 && gtk_widget_has_focus(compose->focused_editable)
10677 #endif
10679 entry_cut_clipboard(compose->focused_editable);
10682 static void compose_copy_cb(GtkAction *action, gpointer data)
10684 Compose *compose = (Compose *)data;
10685 if (compose->focused_editable
10686 #ifndef GENERIC_UMPC
10687 && gtk_widget_has_focus(compose->focused_editable)
10688 #endif
10690 entry_copy_clipboard(compose->focused_editable);
10693 static void compose_paste_cb(GtkAction *action, gpointer data)
10695 Compose *compose = (Compose *)data;
10696 gint prev_autowrap;
10697 GtkTextBuffer *buffer;
10698 BLOCK_WRAP();
10699 if (compose->focused_editable &&
10700 #ifndef GENERIC_UMPC
10701 gtk_widget_has_focus(compose->focused_editable)
10702 #endif
10704 entry_paste_clipboard(compose, compose->focused_editable,
10705 prefs_common.linewrap_pastes,
10706 GDK_SELECTION_CLIPBOARD, NULL);
10707 UNBLOCK_WRAP();
10709 #ifdef USE_ENCHANT
10710 if (
10711 #ifndef GENERIC_UMPC
10712 gtk_widget_has_focus(compose->text) &&
10713 #endif
10714 compose->gtkaspell &&
10715 compose->gtkaspell->check_while_typing)
10716 gtkaspell_highlight_all(compose->gtkaspell);
10717 #endif
10720 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10722 Compose *compose = (Compose *)data;
10723 gint wrap_quote = prefs_common.linewrap_quote;
10724 if (compose->focused_editable
10725 #ifndef GENERIC_UMPC
10726 && gtk_widget_has_focus(compose->focused_editable)
10727 #endif
10729 /* let text_insert() (called directly or at a later time
10730 * after the gtk_editable_paste_clipboard) know that
10731 * text is to be inserted as a quotation. implemented
10732 * by using a simple refcount... */
10733 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10734 G_OBJECT(compose->focused_editable),
10735 "paste_as_quotation"));
10736 g_object_set_data(G_OBJECT(compose->focused_editable),
10737 "paste_as_quotation",
10738 GINT_TO_POINTER(paste_as_quotation + 1));
10739 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10740 entry_paste_clipboard(compose, compose->focused_editable,
10741 prefs_common.linewrap_pastes,
10742 GDK_SELECTION_CLIPBOARD, NULL);
10743 prefs_common.linewrap_quote = wrap_quote;
10747 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10749 Compose *compose = (Compose *)data;
10750 gint prev_autowrap;
10751 GtkTextBuffer *buffer;
10752 BLOCK_WRAP();
10753 if (compose->focused_editable
10754 #ifndef GENERIC_UMPC
10755 && gtk_widget_has_focus(compose->focused_editable)
10756 #endif
10758 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10759 GDK_SELECTION_CLIPBOARD, NULL);
10760 UNBLOCK_WRAP();
10762 #ifdef USE_ENCHANT
10763 if (
10764 #ifndef GENERIC_UMPC
10765 gtk_widget_has_focus(compose->text) &&
10766 #endif
10767 compose->gtkaspell &&
10768 compose->gtkaspell->check_while_typing)
10769 gtkaspell_highlight_all(compose->gtkaspell);
10770 #endif
10773 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10775 Compose *compose = (Compose *)data;
10776 gint prev_autowrap;
10777 GtkTextBuffer *buffer;
10778 BLOCK_WRAP();
10779 if (compose->focused_editable
10780 #ifndef GENERIC_UMPC
10781 && gtk_widget_has_focus(compose->focused_editable)
10782 #endif
10784 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10785 GDK_SELECTION_CLIPBOARD, NULL);
10786 UNBLOCK_WRAP();
10788 #ifdef USE_ENCHANT
10789 if (
10790 #ifndef GENERIC_UMPC
10791 gtk_widget_has_focus(compose->text) &&
10792 #endif
10793 compose->gtkaspell &&
10794 compose->gtkaspell->check_while_typing)
10795 gtkaspell_highlight_all(compose->gtkaspell);
10796 #endif
10799 static void compose_allsel_cb(GtkAction *action, gpointer data)
10801 Compose *compose = (Compose *)data;
10802 if (compose->focused_editable
10803 #ifndef GENERIC_UMPC
10804 && gtk_widget_has_focus(compose->focused_editable)
10805 #endif
10807 entry_allsel(compose->focused_editable);
10810 static void textview_move_beginning_of_line (GtkTextView *text)
10812 GtkTextBuffer *buffer;
10813 GtkTextMark *mark;
10814 GtkTextIter ins;
10816 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10818 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10819 mark = gtk_text_buffer_get_insert(buffer);
10820 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10821 gtk_text_iter_set_line_offset(&ins, 0);
10822 gtk_text_buffer_place_cursor(buffer, &ins);
10825 static void textview_move_forward_character (GtkTextView *text)
10827 GtkTextBuffer *buffer;
10828 GtkTextMark *mark;
10829 GtkTextIter ins;
10831 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10833 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10834 mark = gtk_text_buffer_get_insert(buffer);
10835 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10836 if (gtk_text_iter_forward_cursor_position(&ins))
10837 gtk_text_buffer_place_cursor(buffer, &ins);
10840 static void textview_move_backward_character (GtkTextView *text)
10842 GtkTextBuffer *buffer;
10843 GtkTextMark *mark;
10844 GtkTextIter ins;
10846 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10848 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10849 mark = gtk_text_buffer_get_insert(buffer);
10850 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10851 if (gtk_text_iter_backward_cursor_position(&ins))
10852 gtk_text_buffer_place_cursor(buffer, &ins);
10855 static void textview_move_forward_word (GtkTextView *text)
10857 GtkTextBuffer *buffer;
10858 GtkTextMark *mark;
10859 GtkTextIter ins;
10860 gint count;
10862 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10864 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10865 mark = gtk_text_buffer_get_insert(buffer);
10866 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10867 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10868 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10869 gtk_text_iter_backward_word_start(&ins);
10870 gtk_text_buffer_place_cursor(buffer, &ins);
10874 static void textview_move_backward_word (GtkTextView *text)
10876 GtkTextBuffer *buffer;
10877 GtkTextMark *mark;
10878 GtkTextIter ins;
10880 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10882 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10883 mark = gtk_text_buffer_get_insert(buffer);
10884 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10885 if (gtk_text_iter_backward_word_starts(&ins, 1))
10886 gtk_text_buffer_place_cursor(buffer, &ins);
10889 static void textview_move_end_of_line (GtkTextView *text)
10891 GtkTextBuffer *buffer;
10892 GtkTextMark *mark;
10893 GtkTextIter ins;
10895 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10897 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10898 mark = gtk_text_buffer_get_insert(buffer);
10899 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10900 if (gtk_text_iter_forward_to_line_end(&ins))
10901 gtk_text_buffer_place_cursor(buffer, &ins);
10904 static void textview_move_next_line (GtkTextView *text)
10906 GtkTextBuffer *buffer;
10907 GtkTextMark *mark;
10908 GtkTextIter ins;
10909 gint offset;
10911 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10913 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10914 mark = gtk_text_buffer_get_insert(buffer);
10915 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10916 offset = gtk_text_iter_get_line_offset(&ins);
10917 if (gtk_text_iter_forward_line(&ins)) {
10918 gtk_text_iter_set_line_offset(&ins, offset);
10919 gtk_text_buffer_place_cursor(buffer, &ins);
10923 static void textview_move_previous_line (GtkTextView *text)
10925 GtkTextBuffer *buffer;
10926 GtkTextMark *mark;
10927 GtkTextIter ins;
10928 gint offset;
10930 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10932 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10933 mark = gtk_text_buffer_get_insert(buffer);
10934 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10935 offset = gtk_text_iter_get_line_offset(&ins);
10936 if (gtk_text_iter_backward_line(&ins)) {
10937 gtk_text_iter_set_line_offset(&ins, offset);
10938 gtk_text_buffer_place_cursor(buffer, &ins);
10942 static void textview_delete_forward_character (GtkTextView *text)
10944 GtkTextBuffer *buffer;
10945 GtkTextMark *mark;
10946 GtkTextIter ins, end_iter;
10948 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10950 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10951 mark = gtk_text_buffer_get_insert(buffer);
10952 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10953 end_iter = ins;
10954 if (gtk_text_iter_forward_char(&end_iter)) {
10955 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10959 static void textview_delete_backward_character (GtkTextView *text)
10961 GtkTextBuffer *buffer;
10962 GtkTextMark *mark;
10963 GtkTextIter ins, end_iter;
10965 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10967 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10968 mark = gtk_text_buffer_get_insert(buffer);
10969 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10970 end_iter = ins;
10971 if (gtk_text_iter_backward_char(&end_iter)) {
10972 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10976 static void textview_delete_forward_word (GtkTextView *text)
10978 GtkTextBuffer *buffer;
10979 GtkTextMark *mark;
10980 GtkTextIter ins, end_iter;
10982 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10984 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10985 mark = gtk_text_buffer_get_insert(buffer);
10986 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10987 end_iter = ins;
10988 if (gtk_text_iter_forward_word_end(&end_iter)) {
10989 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10993 static void textview_delete_backward_word (GtkTextView *text)
10995 GtkTextBuffer *buffer;
10996 GtkTextMark *mark;
10997 GtkTextIter ins, end_iter;
10999 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11001 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11002 mark = gtk_text_buffer_get_insert(buffer);
11003 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11004 end_iter = ins;
11005 if (gtk_text_iter_backward_word_start(&end_iter)) {
11006 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11010 static void textview_delete_line (GtkTextView *text)
11012 GtkTextBuffer *buffer;
11013 GtkTextMark *mark;
11014 GtkTextIter ins, start_iter, end_iter;
11016 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11018 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11019 mark = gtk_text_buffer_get_insert(buffer);
11020 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11022 start_iter = ins;
11023 gtk_text_iter_set_line_offset(&start_iter, 0);
11025 end_iter = ins;
11026 if (gtk_text_iter_ends_line(&end_iter)){
11027 if (!gtk_text_iter_forward_char(&end_iter))
11028 gtk_text_iter_backward_char(&start_iter);
11030 else
11031 gtk_text_iter_forward_to_line_end(&end_iter);
11032 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11035 static void textview_delete_to_line_end (GtkTextView *text)
11037 GtkTextBuffer *buffer;
11038 GtkTextMark *mark;
11039 GtkTextIter ins, end_iter;
11041 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11043 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11044 mark = gtk_text_buffer_get_insert(buffer);
11045 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11046 end_iter = ins;
11047 if (gtk_text_iter_ends_line(&end_iter))
11048 gtk_text_iter_forward_char(&end_iter);
11049 else
11050 gtk_text_iter_forward_to_line_end(&end_iter);
11051 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11054 #define DO_ACTION(name, act) { \
11055 if(!strcmp(name, a_name)) { \
11056 return act; \
11059 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11061 const gchar *a_name = gtk_action_get_name(action);
11062 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11063 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11064 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11065 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11066 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11067 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11068 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11069 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11070 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11071 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11072 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11073 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11074 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11075 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11076 return -1;
11079 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11081 Compose *compose = (Compose *)data;
11082 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11083 ComposeCallAdvancedAction action = -1;
11085 action = compose_call_advanced_action_from_path(gaction);
11087 static struct {
11088 void (*do_action) (GtkTextView *text);
11089 } action_table[] = {
11090 {textview_move_beginning_of_line},
11091 {textview_move_forward_character},
11092 {textview_move_backward_character},
11093 {textview_move_forward_word},
11094 {textview_move_backward_word},
11095 {textview_move_end_of_line},
11096 {textview_move_next_line},
11097 {textview_move_previous_line},
11098 {textview_delete_forward_character},
11099 {textview_delete_backward_character},
11100 {textview_delete_forward_word},
11101 {textview_delete_backward_word},
11102 {textview_delete_line},
11103 {textview_delete_to_line_end}
11106 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11108 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11109 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11110 if (action_table[action].do_action)
11111 action_table[action].do_action(text);
11112 else
11113 g_warning("Not implemented yet.");
11117 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11119 GtkAllocation allocation;
11120 GtkWidget *parent;
11121 gchar *str = NULL;
11123 if (GTK_IS_EDITABLE(widget)) {
11124 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11125 gtk_editable_set_position(GTK_EDITABLE(widget),
11126 strlen(str));
11127 g_free(str);
11128 if ((parent = gtk_widget_get_parent(widget))
11129 && (parent = gtk_widget_get_parent(parent))
11130 && (parent = gtk_widget_get_parent(parent))) {
11131 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11132 gtk_widget_get_allocation(widget, &allocation);
11133 gint y = allocation.y;
11134 gint height = allocation.height;
11135 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11136 (GTK_SCROLLED_WINDOW(parent));
11138 gfloat value = gtk_adjustment_get_value(shown);
11139 gfloat upper = gtk_adjustment_get_upper(shown);
11140 gfloat page_size = gtk_adjustment_get_page_size(shown);
11141 if (y < (int)value) {
11142 gtk_adjustment_set_value(shown, y - 1);
11144 if ((y + height) > ((int)value + (int)page_size)) {
11145 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11146 gtk_adjustment_set_value(shown,
11147 y + height - (int)page_size - 1);
11148 } else {
11149 gtk_adjustment_set_value(shown,
11150 (int)upper - (int)page_size - 1);
11157 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11158 compose->focused_editable = widget;
11160 #ifdef GENERIC_UMPC
11161 if (GTK_IS_TEXT_VIEW(widget)
11162 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11163 g_object_ref(compose->notebook);
11164 g_object_ref(compose->edit_vbox);
11165 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11166 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11167 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11168 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11169 g_object_unref(compose->notebook);
11170 g_object_unref(compose->edit_vbox);
11171 g_signal_handlers_block_by_func(G_OBJECT(widget),
11172 G_CALLBACK(compose_grab_focus_cb),
11173 compose);
11174 gtk_widget_grab_focus(widget);
11175 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11176 G_CALLBACK(compose_grab_focus_cb),
11177 compose);
11178 } else if (!GTK_IS_TEXT_VIEW(widget)
11179 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11180 g_object_ref(compose->notebook);
11181 g_object_ref(compose->edit_vbox);
11182 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11183 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11184 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11185 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11186 g_object_unref(compose->notebook);
11187 g_object_unref(compose->edit_vbox);
11188 g_signal_handlers_block_by_func(G_OBJECT(widget),
11189 G_CALLBACK(compose_grab_focus_cb),
11190 compose);
11191 gtk_widget_grab_focus(widget);
11192 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11193 G_CALLBACK(compose_grab_focus_cb),
11194 compose);
11196 #endif
11199 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11201 compose->modified = TRUE;
11202 // compose_beautify_paragraph(compose, NULL, TRUE);
11203 #ifndef GENERIC_UMPC
11204 compose_set_title(compose);
11205 #endif
11208 static void compose_wrap_cb(GtkAction *action, gpointer data)
11210 Compose *compose = (Compose *)data;
11211 compose_beautify_paragraph(compose, NULL, TRUE);
11214 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11216 Compose *compose = (Compose *)data;
11217 compose_wrap_all_full(compose, TRUE);
11220 static void compose_find_cb(GtkAction *action, gpointer data)
11222 Compose *compose = (Compose *)data;
11224 message_search_compose(compose);
11227 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11228 gpointer data)
11230 Compose *compose = (Compose *)data;
11231 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11232 if (compose->autowrap)
11233 compose_wrap_all_full(compose, TRUE);
11234 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11237 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11238 gpointer data)
11240 Compose *compose = (Compose *)data;
11241 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11244 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11246 Compose *compose = (Compose *)data;
11248 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11251 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11253 Compose *compose = (Compose *)data;
11255 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11258 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11260 g_free(compose->privacy_system);
11261 g_free(compose->encdata);
11263 compose->privacy_system = g_strdup(account->default_privacy_system);
11264 compose_update_privacy_system_menu_item(compose, warn);
11267 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11269 Compose *compose = (Compose *)data;
11271 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11272 gtk_widget_show(compose->ruler_hbox);
11273 prefs_common.show_ruler = TRUE;
11274 } else {
11275 gtk_widget_hide(compose->ruler_hbox);
11276 gtk_widget_queue_resize(compose->edit_vbox);
11277 prefs_common.show_ruler = FALSE;
11281 static void compose_attach_drag_received_cb (GtkWidget *widget,
11282 GdkDragContext *context,
11283 gint x,
11284 gint y,
11285 GtkSelectionData *data,
11286 guint info,
11287 guint time,
11288 gpointer user_data)
11290 Compose *compose = (Compose *)user_data;
11291 GList *list, *tmp;
11292 GdkAtom type;
11294 type = gtk_selection_data_get_data_type(data);
11295 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11296 && gtk_drag_get_source_widget(context) !=
11297 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11298 list = uri_list_extract_filenames(
11299 (const gchar *)gtk_selection_data_get_data(data));
11300 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11301 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11302 compose_attach_append
11303 (compose, (const gchar *)tmp->data,
11304 utf8_filename, NULL, NULL);
11305 g_free(utf8_filename);
11307 if (list) compose_changed_cb(NULL, compose);
11308 list_free_strings(list);
11309 g_list_free(list);
11310 } else if (gtk_drag_get_source_widget(context)
11311 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11312 /* comes from our summaryview */
11313 SummaryView * summaryview = NULL;
11314 GSList * list = NULL, *cur = NULL;
11316 if (mainwindow_get_mainwindow())
11317 summaryview = mainwindow_get_mainwindow()->summaryview;
11319 if (summaryview)
11320 list = summary_get_selected_msg_list(summaryview);
11322 for (cur = list; cur; cur = cur->next) {
11323 MsgInfo *msginfo = (MsgInfo *)cur->data;
11324 gchar *file = NULL;
11325 if (msginfo)
11326 file = procmsg_get_message_file_full(msginfo,
11327 TRUE, TRUE);
11328 if (file) {
11329 compose_attach_append(compose, (const gchar *)file,
11330 (const gchar *)file, "message/rfc822", NULL);
11331 g_free(file);
11334 g_slist_free(list);
11338 static gboolean compose_drag_drop(GtkWidget *widget,
11339 GdkDragContext *drag_context,
11340 gint x, gint y,
11341 guint time, gpointer user_data)
11343 /* not handling this signal makes compose_insert_drag_received_cb
11344 * called twice */
11345 return TRUE;
11348 static gboolean completion_set_focus_to_subject
11349 (GtkWidget *widget,
11350 GdkEventKey *event,
11351 Compose *compose)
11353 cm_return_val_if_fail(compose != NULL, FALSE);
11355 /* make backtab move to subject field */
11356 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11357 gtk_widget_grab_focus(compose->subject_entry);
11358 return TRUE;
11360 return FALSE;
11363 static void compose_insert_drag_received_cb (GtkWidget *widget,
11364 GdkDragContext *drag_context,
11365 gint x,
11366 gint y,
11367 GtkSelectionData *data,
11368 guint info,
11369 guint time,
11370 gpointer user_data)
11372 Compose *compose = (Compose *)user_data;
11373 GList *list, *tmp;
11374 GdkAtom type;
11376 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11377 * does not work */
11378 type = gtk_selection_data_get_data_type(data);
11379 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11380 AlertValue val = G_ALERTDEFAULT;
11381 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11383 list = uri_list_extract_filenames(ddata);
11384 if (list == NULL && strstr(ddata, "://")) {
11385 /* Assume a list of no files, and data has ://, is a remote link */
11386 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11387 gchar *tmpfile = get_tmp_file();
11388 str_write_to_file(tmpdata, tmpfile);
11389 g_free(tmpdata);
11390 compose_insert_file(compose, tmpfile);
11391 claws_unlink(tmpfile);
11392 g_free(tmpfile);
11393 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11394 compose_beautify_paragraph(compose, NULL, TRUE);
11395 return;
11397 switch (prefs_common.compose_dnd_mode) {
11398 case COMPOSE_DND_ASK:
11399 val = alertpanel_full(_("Insert or attach?"),
11400 _("Do you want to insert the contents of the file(s) "
11401 "into the message body, or attach it to the email?"),
11402 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11403 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11404 break;
11405 case COMPOSE_DND_INSERT:
11406 val = G_ALERTALTERNATE;
11407 break;
11408 case COMPOSE_DND_ATTACH:
11409 val = G_ALERTOTHER;
11410 break;
11411 default:
11412 /* unexpected case */
11413 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11416 if (val & G_ALERTDISABLE) {
11417 val &= ~G_ALERTDISABLE;
11418 /* remember what action to perform by default, only if we don't click Cancel */
11419 if (val == G_ALERTALTERNATE)
11420 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11421 else if (val == G_ALERTOTHER)
11422 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11425 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11426 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11427 list_free_strings(list);
11428 g_list_free(list);
11429 return;
11430 } else if (val == G_ALERTOTHER) {
11431 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11432 list_free_strings(list);
11433 g_list_free(list);
11434 return;
11437 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11438 compose_insert_file(compose, (const gchar *)tmp->data);
11440 list_free_strings(list);
11441 g_list_free(list);
11442 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11443 return;
11447 static void compose_header_drag_received_cb (GtkWidget *widget,
11448 GdkDragContext *drag_context,
11449 gint x,
11450 gint y,
11451 GtkSelectionData *data,
11452 guint info,
11453 guint time,
11454 gpointer user_data)
11456 GtkEditable *entry = (GtkEditable *)user_data;
11457 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11459 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11460 * does not work */
11462 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11463 gchar *decoded=g_new(gchar, strlen(email));
11464 int start = 0;
11466 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11467 gtk_editable_delete_text(entry, 0, -1);
11468 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11469 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11470 g_free(decoded);
11471 return;
11473 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11476 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11478 Compose *compose = (Compose *)data;
11480 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11481 compose->return_receipt = TRUE;
11482 else
11483 compose->return_receipt = FALSE;
11486 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11488 Compose *compose = (Compose *)data;
11490 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11491 compose->remove_references = TRUE;
11492 else
11493 compose->remove_references = FALSE;
11496 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11497 ComposeHeaderEntry *headerentry)
11499 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11500 return FALSE;
11503 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11504 GdkEventKey *event,
11505 ComposeHeaderEntry *headerentry)
11507 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11508 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11509 !(event->state & GDK_MODIFIER_MASK) &&
11510 (event->keyval == GDK_KEY_BackSpace) &&
11511 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11512 gtk_container_remove
11513 (GTK_CONTAINER(headerentry->compose->header_table),
11514 headerentry->combo);
11515 gtk_container_remove
11516 (GTK_CONTAINER(headerentry->compose->header_table),
11517 headerentry->entry);
11518 headerentry->compose->header_list =
11519 g_slist_remove(headerentry->compose->header_list,
11520 headerentry);
11521 g_free(headerentry);
11522 } else if (event->keyval == GDK_KEY_Tab) {
11523 if (headerentry->compose->header_last == headerentry) {
11524 /* Override default next focus, and give it to subject_entry
11525 * instead of notebook tabs
11527 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11528 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11529 return TRUE;
11532 return FALSE;
11535 static gboolean scroll_postpone(gpointer data)
11537 Compose *compose = (Compose *)data;
11539 if (compose->batch)
11540 return FALSE;
11542 GTK_EVENTS_FLUSH();
11543 compose_show_first_last_header(compose, FALSE);
11544 return FALSE;
11547 static void compose_headerentry_changed_cb(GtkWidget *entry,
11548 ComposeHeaderEntry *headerentry)
11550 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11551 compose_create_header_entry(headerentry->compose);
11552 g_signal_handlers_disconnect_matched
11553 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11554 0, 0, NULL, NULL, headerentry);
11556 if (!headerentry->compose->batch)
11557 g_timeout_add(0, scroll_postpone, headerentry->compose);
11561 static gboolean compose_defer_auto_save_draft(Compose *compose)
11563 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11564 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11565 return FALSE;
11568 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11570 GtkAdjustment *vadj;
11572 cm_return_if_fail(compose);
11574 if(compose->batch)
11575 return;
11577 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11578 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11579 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11580 gtk_widget_get_parent(compose->header_table)));
11581 gtk_adjustment_set_value(vadj, (show_first ?
11582 gtk_adjustment_get_lower(vadj) :
11583 (gtk_adjustment_get_upper(vadj) -
11584 gtk_adjustment_get_page_size(vadj))));
11585 gtk_adjustment_changed(vadj);
11588 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11589 const gchar *text, gint len, Compose *compose)
11591 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11592 (G_OBJECT(compose->text), "paste_as_quotation"));
11593 GtkTextMark *mark;
11595 cm_return_if_fail(text != NULL);
11597 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11598 G_CALLBACK(text_inserted),
11599 compose);
11600 if (paste_as_quotation) {
11601 gchar *new_text;
11602 const gchar *qmark;
11603 guint pos = 0;
11604 GtkTextIter start_iter;
11606 if (len < 0)
11607 len = strlen(text);
11609 new_text = g_strndup(text, len);
11611 qmark = compose_quote_char_from_context(compose);
11613 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11614 gtk_text_buffer_place_cursor(buffer, iter);
11616 pos = gtk_text_iter_get_offset(iter);
11618 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11619 _("Quote format error at line %d."));
11620 quote_fmt_reset_vartable();
11621 g_free(new_text);
11622 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11623 GINT_TO_POINTER(paste_as_quotation - 1));
11625 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11626 gtk_text_buffer_place_cursor(buffer, iter);
11627 gtk_text_buffer_delete_mark(buffer, mark);
11629 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11630 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11631 compose_beautify_paragraph(compose, &start_iter, FALSE);
11632 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11633 gtk_text_buffer_delete_mark(buffer, mark);
11634 } else {
11635 if (strcmp(text, "\n") || compose->automatic_break
11636 || gtk_text_iter_starts_line(iter)) {
11637 GtkTextIter before_ins;
11638 gtk_text_buffer_insert(buffer, iter, text, len);
11639 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11640 before_ins = *iter;
11641 gtk_text_iter_backward_chars(&before_ins, len);
11642 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11644 } else {
11645 /* check if the preceding is just whitespace or quote */
11646 GtkTextIter start_line;
11647 gchar *tmp = NULL, *quote = NULL;
11648 gint quote_len = 0, is_normal = 0;
11649 start_line = *iter;
11650 gtk_text_iter_set_line_offset(&start_line, 0);
11651 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11652 g_strstrip(tmp);
11654 if (*tmp == '\0') {
11655 is_normal = 1;
11656 } else {
11657 quote = compose_get_quote_str(buffer, &start_line, &quote_len);
11658 if (quote)
11659 is_normal = 1;
11660 g_free(quote);
11662 g_free(tmp);
11664 if (is_normal) {
11665 gtk_text_buffer_insert(buffer, iter, text, len);
11666 } else {
11667 gtk_text_buffer_insert_with_tags_by_name(buffer,
11668 iter, text, len, "no_join", NULL);
11673 if (!paste_as_quotation) {
11674 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11675 compose_beautify_paragraph(compose, iter, FALSE);
11676 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11677 gtk_text_buffer_delete_mark(buffer, mark);
11680 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11681 G_CALLBACK(text_inserted),
11682 compose);
11683 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11685 if (compose_can_autosave(compose) &&
11686 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11687 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11688 compose->draft_timeout_tag = g_timeout_add
11689 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11692 #if USE_ENCHANT
11693 static void compose_check_all(GtkAction *action, gpointer data)
11695 Compose *compose = (Compose *)data;
11696 if (!compose->gtkaspell)
11697 return;
11699 if (gtk_widget_has_focus(compose->subject_entry))
11700 claws_spell_entry_check_all(
11701 CLAWS_SPELL_ENTRY(compose->subject_entry));
11702 else
11703 gtkaspell_check_all(compose->gtkaspell);
11706 static void compose_highlight_all(GtkAction *action, gpointer data)
11708 Compose *compose = (Compose *)data;
11709 if (compose->gtkaspell) {
11710 claws_spell_entry_recheck_all(
11711 CLAWS_SPELL_ENTRY(compose->subject_entry));
11712 gtkaspell_highlight_all(compose->gtkaspell);
11716 static void compose_check_backwards(GtkAction *action, gpointer data)
11718 Compose *compose = (Compose *)data;
11719 if (!compose->gtkaspell) {
11720 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11721 return;
11724 if (gtk_widget_has_focus(compose->subject_entry))
11725 claws_spell_entry_check_backwards(
11726 CLAWS_SPELL_ENTRY(compose->subject_entry));
11727 else
11728 gtkaspell_check_backwards(compose->gtkaspell);
11731 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11733 Compose *compose = (Compose *)data;
11734 if (!compose->gtkaspell) {
11735 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11736 return;
11739 if (gtk_widget_has_focus(compose->subject_entry))
11740 claws_spell_entry_check_forwards_go(
11741 CLAWS_SPELL_ENTRY(compose->subject_entry));
11742 else
11743 gtkaspell_check_forwards_go(compose->gtkaspell);
11745 #endif
11748 *\brief Guess originating forward account from MsgInfo and several
11749 * "common preference" settings. Return NULL if no guess.
11751 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11753 PrefsAccount *account = NULL;
11755 cm_return_val_if_fail(msginfo, NULL);
11756 cm_return_val_if_fail(msginfo->folder, NULL);
11757 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11759 if (msginfo->folder->prefs->enable_default_account)
11760 account = account_find_from_id(msginfo->folder->prefs->default_account);
11762 if (!account)
11763 account = msginfo->folder->folder->account;
11765 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11766 gchar *to;
11767 Xstrdup_a(to, msginfo->to, return NULL);
11768 extract_address(to);
11769 account = account_find_from_address(to, FALSE);
11772 if (!account && prefs_common.forward_account_autosel) {
11773 gchar cc[BUFFSIZE];
11774 if (!procheader_get_header_from_msginfo
11775 (msginfo, cc,sizeof cc , "Cc:")) {
11776 gchar *buf = cc + strlen("Cc:");
11777 extract_address(buf);
11778 account = account_find_from_address(buf, FALSE);
11782 if (!account && prefs_common.forward_account_autosel) {
11783 gchar deliveredto[BUFFSIZE];
11784 if (!procheader_get_header_from_msginfo
11785 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11786 gchar *buf = deliveredto + strlen("Delivered-To:");
11787 extract_address(buf);
11788 account = account_find_from_address(buf, FALSE);
11792 return account;
11795 gboolean compose_close(Compose *compose)
11797 gint x, y;
11799 cm_return_val_if_fail(compose, FALSE);
11801 if (!g_mutex_trylock(compose->mutex)) {
11802 /* we have to wait for the (possibly deferred by auto-save)
11803 * drafting to be done, before destroying the compose under
11804 * it. */
11805 debug_print("waiting for drafting to finish...\n");
11806 compose_allow_user_actions(compose, FALSE);
11807 if (compose->close_timeout_tag == 0) {
11808 compose->close_timeout_tag =
11809 g_timeout_add (500, (GSourceFunc) compose_close,
11810 compose);
11812 return TRUE;
11815 if (compose->draft_timeout_tag >= 0) {
11816 g_source_remove(compose->draft_timeout_tag);
11817 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11820 gtkut_widget_get_uposition(compose->window, &x, &y);
11821 if (!compose->batch) {
11822 prefs_common.compose_x = x;
11823 prefs_common.compose_y = y;
11825 g_mutex_unlock(compose->mutex);
11826 compose_destroy(compose);
11827 return FALSE;
11831 * Add entry field for each address in list.
11832 * \param compose E-Mail composition object.
11833 * \param listAddress List of (formatted) E-Mail addresses.
11835 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11836 GList *node;
11837 gchar *addr;
11838 node = listAddress;
11839 while( node ) {
11840 addr = ( gchar * ) node->data;
11841 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11842 node = g_list_next( node );
11846 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11847 guint action, gboolean opening_multiple)
11849 gchar *body = NULL;
11850 GSList *new_msglist = NULL;
11851 MsgInfo *tmp_msginfo = NULL;
11852 gboolean originally_enc = FALSE;
11853 gboolean originally_sig = FALSE;
11854 Compose *compose = NULL;
11855 gchar *s_system = NULL;
11857 cm_return_if_fail(msgview != NULL);
11859 cm_return_if_fail(msginfo_list != NULL);
11861 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11862 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11863 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11865 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11866 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11867 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11868 orig_msginfo, mimeinfo);
11869 if (tmp_msginfo != NULL) {
11870 new_msglist = g_slist_append(NULL, tmp_msginfo);
11872 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11873 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11874 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11876 tmp_msginfo->folder = orig_msginfo->folder;
11877 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11878 if (orig_msginfo->tags) {
11879 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11880 tmp_msginfo->folder->tags_dirty = TRUE;
11886 if (!opening_multiple)
11887 body = messageview_get_selection(msgview);
11889 if (new_msglist) {
11890 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11891 procmsg_msginfo_free(&tmp_msginfo);
11892 g_slist_free(new_msglist);
11893 } else
11894 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11896 if (compose && originally_enc) {
11897 compose_force_encryption(compose, compose->account, FALSE, s_system);
11900 if (compose && originally_sig && compose->account->default_sign_reply) {
11901 compose_force_signing(compose, compose->account, s_system);
11903 g_free(s_system);
11904 g_free(body);
11905 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11908 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11909 guint action)
11911 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11912 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11913 GSList *cur = msginfo_list;
11914 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11915 "messages. Opening the windows "
11916 "could take some time. Do you "
11917 "want to continue?"),
11918 g_slist_length(msginfo_list));
11919 if (g_slist_length(msginfo_list) > 9
11920 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11921 != G_ALERTALTERNATE) {
11922 g_free(msg);
11923 return;
11925 g_free(msg);
11926 /* We'll open multiple compose windows */
11927 /* let the WM place the next windows */
11928 compose_force_window_origin = FALSE;
11929 for (; cur; cur = cur->next) {
11930 GSList tmplist;
11931 tmplist.data = cur->data;
11932 tmplist.next = NULL;
11933 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11935 compose_force_window_origin = TRUE;
11936 } else {
11937 /* forwarding multiple mails as attachments is done via a
11938 * single compose window */
11939 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11943 void compose_check_for_email_account(Compose *compose)
11945 PrefsAccount *ac = NULL, *curr = NULL;
11946 GList *list;
11948 if (!compose)
11949 return;
11951 if (compose->account && compose->account->protocol == A_NNTP) {
11952 ac = account_get_cur_account();
11953 if (ac->protocol == A_NNTP) {
11954 list = account_get_list();
11956 for( ; list != NULL ; list = g_list_next(list)) {
11957 curr = (PrefsAccount *) list->data;
11958 if (curr->protocol != A_NNTP) {
11959 ac = curr;
11960 break;
11964 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11965 ac->account_id);
11969 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11970 const gchar *address)
11972 GSList *msginfo_list = NULL;
11973 gchar *body = messageview_get_selection(msgview);
11974 Compose *compose;
11976 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11978 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11979 compose_check_for_email_account(compose);
11980 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11981 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11982 compose_reply_set_subject(compose, msginfo);
11984 g_free(body);
11985 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11988 void compose_set_position(Compose *compose, gint pos)
11990 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11992 gtkut_text_view_set_position(text, pos);
11995 gboolean compose_search_string(Compose *compose,
11996 const gchar *str, gboolean case_sens)
11998 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12000 return gtkut_text_view_search_string(text, str, case_sens);
12003 gboolean compose_search_string_backward(Compose *compose,
12004 const gchar *str, gboolean case_sens)
12006 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12008 return gtkut_text_view_search_string_backward(text, str, case_sens);
12011 /* allocate a msginfo structure and populate its data from a compose data structure */
12012 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12014 MsgInfo *newmsginfo;
12015 GSList *list;
12016 gchar buf[BUFFSIZE];
12018 cm_return_val_if_fail( compose != NULL, NULL );
12020 newmsginfo = procmsg_msginfo_new();
12022 /* date is now */
12023 get_rfc822_date(buf, sizeof(buf));
12024 newmsginfo->date = g_strdup(buf);
12026 /* from */
12027 if (compose->from_name) {
12028 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12029 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12032 /* subject */
12033 if (compose->subject_entry)
12034 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12036 /* to, cc, reply-to, newsgroups */
12037 for (list = compose->header_list; list; list = list->next) {
12038 gchar *header = gtk_editable_get_chars(
12039 GTK_EDITABLE(
12040 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12041 gchar *entry = gtk_editable_get_chars(
12042 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12044 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12045 if ( newmsginfo->to == NULL ) {
12046 newmsginfo->to = g_strdup(entry);
12047 } else if (entry && *entry) {
12048 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12049 g_free(newmsginfo->to);
12050 newmsginfo->to = tmp;
12052 } else
12053 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12054 if ( newmsginfo->cc == NULL ) {
12055 newmsginfo->cc = g_strdup(entry);
12056 } else if (entry && *entry) {
12057 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12058 g_free(newmsginfo->cc);
12059 newmsginfo->cc = tmp;
12061 } else
12062 if ( strcasecmp(header,
12063 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12064 if ( newmsginfo->newsgroups == NULL ) {
12065 newmsginfo->newsgroups = g_strdup(entry);
12066 } else if (entry && *entry) {
12067 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12068 g_free(newmsginfo->newsgroups);
12069 newmsginfo->newsgroups = tmp;
12073 g_free(header);
12074 g_free(entry);
12077 /* other data is unset */
12079 return newmsginfo;
12082 #ifdef USE_ENCHANT
12083 /* update compose's dictionaries from folder dict settings */
12084 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12085 FolderItem *folder_item)
12087 cm_return_if_fail(compose != NULL);
12089 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12090 FolderItemPrefs *prefs = folder_item->prefs;
12092 if (prefs->enable_default_dictionary)
12093 gtkaspell_change_dict(compose->gtkaspell,
12094 prefs->default_dictionary, FALSE);
12095 if (folder_item->prefs->enable_default_alt_dictionary)
12096 gtkaspell_change_alt_dict(compose->gtkaspell,
12097 prefs->default_alt_dictionary);
12098 if (prefs->enable_default_dictionary
12099 || prefs->enable_default_alt_dictionary)
12100 compose_spell_menu_changed(compose);
12103 #endif
12105 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12107 Compose *compose = (Compose *)data;
12109 cm_return_if_fail(compose != NULL);
12111 gtk_widget_grab_focus(compose->text);
12114 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12116 gtk_combo_box_popup(GTK_COMBO_BOX(data));
12121 * End of Source.