Fix bug #3574: Template addressing
[claws.git] / src / compose.c
blobda8c9a0784af2fea48b17b23cf776b13efecd145
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 GdkColor default_to_bgcolor = {
187 (gulong)0,
188 (gushort)0,
189 (gushort)0,
190 (gushort)0
193 static GdkColor default_to_color = {
194 (gulong)0,
195 (gushort)0,
196 (gushort)0,
197 (gushort)0
200 static GList *compose_list = NULL;
201 static GSList *extra_headers = NULL;
203 static Compose *compose_generic_new (PrefsAccount *account,
204 const gchar *to,
205 FolderItem *item,
206 GList *attach_files,
207 GList *listAddress );
209 static Compose *compose_create (PrefsAccount *account,
210 FolderItem *item,
211 ComposeMode mode,
212 gboolean batch);
214 static void compose_entry_mark_default_to (Compose *compose,
215 const gchar *address);
216 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
217 ComposeQuoteMode quote_mode,
218 gboolean to_all,
219 gboolean to_sender,
220 const gchar *body);
221 static Compose *compose_forward_multiple (PrefsAccount *account,
222 GSList *msginfo_list);
223 static Compose *compose_reply (MsgInfo *msginfo,
224 ComposeQuoteMode quote_mode,
225 gboolean to_all,
226 gboolean to_ml,
227 gboolean to_sender,
228 const gchar *body);
229 static Compose *compose_reply_mode (ComposeMode mode,
230 GSList *msginfo_list,
231 gchar *body);
232 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
233 static void compose_update_privacy_systems_menu(Compose *compose);
235 static GtkWidget *compose_account_option_menu_create
236 (Compose *compose);
237 static void compose_set_out_encoding (Compose *compose);
238 static void compose_set_template_menu (Compose *compose);
239 static void compose_destroy (Compose *compose);
241 static MailField compose_entries_set (Compose *compose,
242 const gchar *mailto,
243 ComposeEntryType to_type);
244 static gint compose_parse_header (Compose *compose,
245 MsgInfo *msginfo);
246 static gint compose_parse_manual_headers (Compose *compose,
247 MsgInfo *msginfo,
248 HeaderEntry *entries);
249 static gchar *compose_parse_references (const gchar *ref,
250 const gchar *msgid);
252 static gchar *compose_quote_fmt (Compose *compose,
253 MsgInfo *msginfo,
254 const gchar *fmt,
255 const gchar *qmark,
256 const gchar *body,
257 gboolean rewrap,
258 gboolean need_unescape,
259 const gchar *err_msg);
261 static void compose_reply_set_entry (Compose *compose,
262 MsgInfo *msginfo,
263 gboolean to_all,
264 gboolean to_ml,
265 gboolean to_sender,
266 gboolean
267 followup_and_reply_to);
268 static void compose_reedit_set_entry (Compose *compose,
269 MsgInfo *msginfo);
271 static void compose_insert_sig (Compose *compose,
272 gboolean replace);
273 static ComposeInsertResult compose_insert_file (Compose *compose,
274 const gchar *file);
276 static gboolean compose_attach_append (Compose *compose,
277 const gchar *file,
278 const gchar *type,
279 const gchar *content_type,
280 const gchar *charset);
281 static void compose_attach_parts (Compose *compose,
282 MsgInfo *msginfo);
284 static gboolean compose_beautify_paragraph (Compose *compose,
285 GtkTextIter *par_iter,
286 gboolean force);
287 static void compose_wrap_all (Compose *compose);
288 static void compose_wrap_all_full (Compose *compose,
289 gboolean autowrap);
291 static void compose_set_title (Compose *compose);
292 static void compose_select_account (Compose *compose,
293 PrefsAccount *account,
294 gboolean init);
296 static PrefsAccount *compose_current_mail_account(void);
297 /* static gint compose_send (Compose *compose); */
298 static gboolean compose_check_for_valid_recipient
299 (Compose *compose);
300 static gboolean compose_check_entries (Compose *compose,
301 gboolean check_everything);
302 static gint compose_write_to_file (Compose *compose,
303 FILE *fp,
304 gint action,
305 gboolean attach_parts);
306 static gint compose_write_body_to_file (Compose *compose,
307 const gchar *file);
308 static gint compose_remove_reedit_target (Compose *compose,
309 gboolean force);
310 static void compose_remove_draft (Compose *compose);
311 static gint compose_queue_sub (Compose *compose,
312 gint *msgnum,
313 FolderItem **item,
314 gchar **msgpath,
315 gboolean check_subject,
316 gboolean remove_reedit_target);
317 static int compose_add_attachments (Compose *compose,
318 MimeInfo *parent);
319 static gchar *compose_get_header (Compose *compose);
320 static gchar *compose_get_manual_headers_info (Compose *compose);
322 static void compose_convert_header (Compose *compose,
323 gchar *dest,
324 gint len,
325 gchar *src,
326 gint header_len,
327 gboolean addr_field);
329 static void compose_attach_info_free (AttachInfo *ainfo);
330 static void compose_attach_remove_selected (GtkAction *action,
331 gpointer data);
333 static void compose_template_apply (Compose *compose,
334 Template *tmpl,
335 gboolean replace);
336 static void compose_attach_property (GtkAction *action,
337 gpointer data);
338 static void compose_attach_property_create (gboolean *cancelled);
339 static void attach_property_ok (GtkWidget *widget,
340 gboolean *cancelled);
341 static void attach_property_cancel (GtkWidget *widget,
342 gboolean *cancelled);
343 static gint attach_property_delete_event (GtkWidget *widget,
344 GdkEventAny *event,
345 gboolean *cancelled);
346 static gboolean attach_property_key_pressed (GtkWidget *widget,
347 GdkEventKey *event,
348 gboolean *cancelled);
350 static void compose_exec_ext_editor (Compose *compose);
351 #ifdef G_OS_UNIX
352 static gint compose_exec_ext_editor_real (const gchar *file,
353 GdkNativeWindow socket_wid);
354 static gboolean compose_ext_editor_kill (Compose *compose);
355 static gboolean compose_input_cb (GIOChannel *source,
356 GIOCondition condition,
357 gpointer data);
358 static void compose_set_ext_editor_sensitive (Compose *compose,
359 gboolean sensitive);
360 static gboolean compose_get_ext_editor_cmd_valid();
361 static gboolean compose_get_ext_editor_uses_socket();
362 static gboolean compose_ext_editor_plug_removed_cb
363 (GtkSocket *socket,
364 Compose *compose);
365 #endif /* G_OS_UNIX */
367 static void compose_undo_state_changed (UndoMain *undostruct,
368 gint undo_state,
369 gint redo_state,
370 gpointer data);
372 static void compose_create_header_entry (Compose *compose);
373 static void compose_add_header_entry (Compose *compose, const gchar *header,
374 gchar *text, ComposePrefType pref_type);
375 static void compose_remove_header_entries(Compose *compose);
377 static void compose_update_priority_menu_item(Compose * compose);
378 #if USE_ENCHANT
379 static void compose_spell_menu_changed (void *data);
380 static void compose_dict_changed (void *data);
381 #endif
382 static void compose_add_field_list ( Compose *compose,
383 GList *listAddress );
385 /* callback functions */
387 static void compose_notebook_size_alloc (GtkNotebook *notebook,
388 GtkAllocation *allocation,
389 GtkPaned *paned);
390 static gboolean compose_edit_size_alloc (GtkEditable *widget,
391 GtkAllocation *allocation,
392 GtkSHRuler *shruler);
393 static void account_activated (GtkComboBox *optmenu,
394 gpointer data);
395 static void attach_selected (GtkTreeView *tree_view,
396 GtkTreePath *tree_path,
397 GtkTreeViewColumn *column,
398 Compose *compose);
399 static gboolean attach_button_pressed (GtkWidget *widget,
400 GdkEventButton *event,
401 gpointer data);
402 static gboolean attach_key_pressed (GtkWidget *widget,
403 GdkEventKey *event,
404 gpointer data);
405 static void compose_send_cb (GtkAction *action, gpointer data);
406 static void compose_send_later_cb (GtkAction *action, gpointer data);
408 static void compose_save_cb (GtkAction *action,
409 gpointer data);
411 static void compose_attach_cb (GtkAction *action,
412 gpointer data);
413 static void compose_insert_file_cb (GtkAction *action,
414 gpointer data);
415 static void compose_insert_sig_cb (GtkAction *action,
416 gpointer data);
417 static void compose_replace_sig_cb (GtkAction *action,
418 gpointer data);
420 static void compose_close_cb (GtkAction *action,
421 gpointer data);
422 static void compose_print_cb (GtkAction *action,
423 gpointer data);
425 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
427 static void compose_address_cb (GtkAction *action,
428 gpointer data);
429 static void about_show_cb (GtkAction *action,
430 gpointer data);
431 static void compose_template_activate_cb(GtkWidget *widget,
432 gpointer data);
434 static void compose_ext_editor_cb (GtkAction *action,
435 gpointer data);
437 static gint compose_delete_cb (GtkWidget *widget,
438 GdkEventAny *event,
439 gpointer data);
441 static void compose_undo_cb (GtkAction *action,
442 gpointer data);
443 static void compose_redo_cb (GtkAction *action,
444 gpointer data);
445 static void compose_cut_cb (GtkAction *action,
446 gpointer data);
447 static void compose_copy_cb (GtkAction *action,
448 gpointer data);
449 static void compose_paste_cb (GtkAction *action,
450 gpointer data);
451 static void compose_paste_as_quote_cb (GtkAction *action,
452 gpointer data);
453 static void compose_paste_no_wrap_cb (GtkAction *action,
454 gpointer data);
455 static void compose_paste_wrap_cb (GtkAction *action,
456 gpointer data);
457 static void compose_allsel_cb (GtkAction *action,
458 gpointer data);
460 static void compose_advanced_action_cb (GtkAction *action,
461 gpointer data);
463 static void compose_grab_focus_cb (GtkWidget *widget,
464 Compose *compose);
466 static void compose_changed_cb (GtkTextBuffer *textbuf,
467 Compose *compose);
469 static void compose_wrap_cb (GtkAction *action,
470 gpointer data);
471 static void compose_wrap_all_cb (GtkAction *action,
472 gpointer data);
473 static void compose_find_cb (GtkAction *action,
474 gpointer data);
475 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
476 gpointer data);
477 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
478 gpointer data);
480 static void compose_toggle_ruler_cb (GtkToggleAction *action,
481 gpointer data);
482 static void compose_toggle_sign_cb (GtkToggleAction *action,
483 gpointer data);
484 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
485 gpointer data);
486 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
487 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
488 static void activate_privacy_system (Compose *compose,
489 PrefsAccount *account,
490 gboolean warn);
491 static void compose_use_signing(Compose *compose, gboolean use_signing);
492 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
493 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
494 gpointer data);
495 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
496 gpointer data);
497 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
498 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
499 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
501 static void compose_attach_drag_received_cb (GtkWidget *widget,
502 GdkDragContext *drag_context,
503 gint x,
504 gint y,
505 GtkSelectionData *data,
506 guint info,
507 guint time,
508 gpointer user_data);
509 static void compose_insert_drag_received_cb (GtkWidget *widget,
510 GdkDragContext *drag_context,
511 gint x,
512 gint y,
513 GtkSelectionData *data,
514 guint info,
515 guint time,
516 gpointer user_data);
517 static void compose_header_drag_received_cb (GtkWidget *widget,
518 GdkDragContext *drag_context,
519 gint x,
520 gint y,
521 GtkSelectionData *data,
522 guint info,
523 guint time,
524 gpointer user_data);
526 static gboolean compose_drag_drop (GtkWidget *widget,
527 GdkDragContext *drag_context,
528 gint x, gint y,
529 guint time, gpointer user_data);
530 static gboolean completion_set_focus_to_subject
531 (GtkWidget *widget,
532 GdkEventKey *event,
533 Compose *user_data);
535 static void text_inserted (GtkTextBuffer *buffer,
536 GtkTextIter *iter,
537 const gchar *text,
538 gint len,
539 Compose *compose);
540 static Compose *compose_generic_reply(MsgInfo *msginfo,
541 ComposeQuoteMode quote_mode,
542 gboolean to_all,
543 gboolean to_ml,
544 gboolean to_sender,
545 gboolean followup_and_reply_to,
546 const gchar *body);
548 static void compose_headerentry_changed_cb (GtkWidget *entry,
549 ComposeHeaderEntry *headerentry);
550 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
551 GdkEventKey *event,
552 ComposeHeaderEntry *headerentry);
553 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
554 ComposeHeaderEntry *headerentry);
556 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
558 static void compose_allow_user_actions (Compose *compose, gboolean allow);
560 static void compose_nothing_cb (GtkAction *action, gpointer data)
565 #if USE_ENCHANT
566 static void compose_check_all (GtkAction *action, gpointer data);
567 static void compose_highlight_all (GtkAction *action, gpointer data);
568 static void compose_check_backwards (GtkAction *action, gpointer data);
569 static void compose_check_forwards_go (GtkAction *action, gpointer data);
570 #endif
572 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
574 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
576 #ifdef USE_ENCHANT
577 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
578 FolderItem *folder_item);
579 #endif
580 static void compose_attach_update_label(Compose *compose);
581 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
582 gboolean respect_default_to);
583 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
584 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
586 static GtkActionEntry compose_popup_entries[] =
588 {"Compose", NULL, "Compose" },
589 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
590 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
591 {"Compose/---", NULL, "---", NULL, NULL, NULL },
592 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
595 static GtkActionEntry compose_entries[] =
597 {"Menu", NULL, "Menu" },
598 /* menus */
599 {"Message", NULL, N_("_Message") },
600 {"Edit", NULL, N_("_Edit") },
601 #if USE_ENCHANT
602 {"Spelling", NULL, N_("_Spelling") },
603 #endif
604 {"Options", NULL, N_("_Options") },
605 {"Tools", NULL, N_("_Tools") },
606 {"Help", NULL, N_("_Help") },
607 /* Message menu */
608 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
609 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
610 {"Message/---", NULL, "---" },
612 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
613 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
614 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
615 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
616 /* {"Message/---", NULL, "---" }, */
617 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
618 /* {"Message/---", NULL, "---" }, */
619 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
620 /* {"Message/---", NULL, "---" }, */
621 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
623 /* Edit menu */
624 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
625 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
626 {"Edit/---", NULL, "---" },
628 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
629 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
630 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
632 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
633 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
634 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
635 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
637 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
639 {"Edit/Advanced", NULL, N_("A_dvanced") },
640 {"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*/
641 {"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*/
642 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
643 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
644 {"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*/
645 {"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*/
646 {"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*/
647 {"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*/
648 {"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*/
649 {"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*/
650 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
651 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
652 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
653 {"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*/
655 /* {"Edit/---", NULL, "---" }, */
656 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
658 /* {"Edit/---", NULL, "---" }, */
659 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
660 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
661 /* {"Edit/---", NULL, "---" }, */
662 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
663 #if USE_ENCHANT
664 /* Spelling menu */
665 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
666 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
667 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
668 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
670 {"Spelling/---", NULL, "---" },
671 {"Spelling/Options", NULL, N_("_Options") },
672 #endif
674 /* Options menu */
676 {"Options/ReplyMode", NULL, N_("Reply _mode") },
677 {"Options/---", NULL, "---" },
678 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
679 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
681 /* {"Options/---", NULL, "---" }, */
683 {"Options/Priority", NULL, N_("_Priority") },
685 {"Options/Encoding", NULL, N_("Character _encoding") },
686 {"Options/Encoding/---", NULL, "---" },
687 #define ENC_ACTION(cs_char,c_char,string) \
688 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
690 {"Options/Encoding/Western", NULL, N_("Western European") },
691 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
692 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
693 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
694 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
695 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
696 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
697 {"Options/Encoding/Korean", NULL, N_("Korean") },
698 {"Options/Encoding/Thai", NULL, N_("Thai") },
700 /* Tools menu */
701 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
703 {"Tools/Template", NULL, N_("_Template") },
704 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
705 {"Tools/Actions", NULL, N_("Actio_ns") },
706 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
708 /* Help menu */
709 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
712 static GtkToggleActionEntry compose_toggle_entries[] =
714 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
715 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
716 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
717 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
718 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
719 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
720 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
723 static GtkRadioActionEntry compose_radio_rm_entries[] =
725 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
726 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
727 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
728 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
731 static GtkRadioActionEntry compose_radio_prio_entries[] =
733 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
734 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
735 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
736 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
737 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
740 static GtkRadioActionEntry compose_radio_enc_entries[] =
742 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
777 static GtkTargetEntry compose_mime_types[] =
779 {"text/uri-list", 0, 0},
780 {"UTF8_STRING", 0, 0},
781 {"text/plain", 0, 0}
784 static gboolean compose_put_existing_to_front(MsgInfo *info)
786 const GList *compose_list = compose_get_compose_list();
787 const GList *elem = NULL;
789 if (compose_list) {
790 for (elem = compose_list; elem != NULL && elem->data != NULL;
791 elem = elem->next) {
792 Compose *c = (Compose*)elem->data;
794 if (!c->targetinfo || !c->targetinfo->msgid ||
795 !info->msgid)
796 continue;
798 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
799 gtkut_window_popup(c->window);
800 return TRUE;
804 return FALSE;
807 static GdkColor quote_color1 =
808 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
809 static GdkColor quote_color2 =
810 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
811 static GdkColor quote_color3 =
812 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
814 static GdkColor quote_bgcolor1 =
815 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
816 static GdkColor quote_bgcolor2 =
817 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
818 static GdkColor quote_bgcolor3 =
819 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
821 static GdkColor signature_color = {
822 (gulong)0,
823 (gushort)0x7fff,
824 (gushort)0x7fff,
825 (gushort)0x7fff
828 static GdkColor uri_color = {
829 (gulong)0,
830 (gushort)0,
831 (gushort)0,
832 (gushort)0
835 static void compose_create_tags(GtkTextView *text, Compose *compose)
837 GtkTextBuffer *buffer;
838 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
839 #if !GTK_CHECK_VERSION(2, 24, 0)
840 GdkColormap *cmap;
841 gboolean success[8];
842 int i;
843 GdkColor color[8];
844 #endif
846 buffer = gtk_text_view_get_buffer(text);
848 if (prefs_common.enable_color) {
849 /* grab the quote colors, converting from an int to a GdkColor */
850 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
851 &quote_color1);
852 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
853 &quote_color2);
854 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
855 &quote_color3);
856 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
857 &quote_bgcolor1);
858 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
859 &quote_bgcolor2);
860 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
861 &quote_bgcolor3);
862 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
863 &signature_color);
864 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
865 &uri_color);
866 } else {
867 signature_color = quote_color1 = quote_color2 = quote_color3 =
868 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
871 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
872 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
873 "foreground-gdk", &quote_color1,
874 "paragraph-background-gdk", &quote_bgcolor1,
875 NULL);
876 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
877 "foreground-gdk", &quote_color2,
878 "paragraph-background-gdk", &quote_bgcolor2,
879 NULL);
880 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
881 "foreground-gdk", &quote_color3,
882 "paragraph-background-gdk", &quote_bgcolor3,
883 NULL);
884 } else {
885 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
886 "foreground-gdk", &quote_color1,
887 NULL);
888 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
889 "foreground-gdk", &quote_color2,
890 NULL);
891 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
892 "foreground-gdk", &quote_color3,
893 NULL);
896 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
897 "foreground-gdk", &signature_color,
898 NULL);
900 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
901 "foreground-gdk", &uri_color,
902 NULL);
903 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
904 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
906 #if !GTK_CHECK_VERSION(2, 24, 0)
907 color[0] = quote_color1;
908 color[1] = quote_color2;
909 color[2] = quote_color3;
910 color[3] = quote_bgcolor1;
911 color[4] = quote_bgcolor2;
912 color[5] = quote_bgcolor3;
913 color[6] = signature_color;
914 color[7] = uri_color;
916 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
917 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
919 for (i = 0; i < 8; i++) {
920 if (success[i] == FALSE) {
921 g_warning("Compose: color allocation failed.");
922 quote_color1 = quote_color2 = quote_color3 =
923 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
924 signature_color = uri_color = black;
927 #endif
930 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
931 GList *attach_files)
933 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
936 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
938 return compose_generic_new(account, mailto, item, NULL, NULL);
941 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
943 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
946 #define SCROLL_TO_CURSOR(compose) { \
947 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
948 gtk_text_view_get_buffer( \
949 GTK_TEXT_VIEW(compose->text))); \
950 gtk_text_view_scroll_mark_onscreen( \
951 GTK_TEXT_VIEW(compose->text), \
952 cmark); \
955 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
957 GtkEditable *entry;
958 if (folderidentifier) {
959 #if !GTK_CHECK_VERSION(2, 24, 0)
960 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
961 #else
962 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
963 #endif
964 prefs_common.compose_save_to_history = add_history(
965 prefs_common.compose_save_to_history, folderidentifier);
966 #if !GTK_CHECK_VERSION(2, 24, 0)
967 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
968 prefs_common.compose_save_to_history);
969 #else
970 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
971 prefs_common.compose_save_to_history);
972 #endif
975 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
976 if (folderidentifier)
977 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
978 else
979 gtk_entry_set_text(GTK_ENTRY(entry), "");
982 static gchar *compose_get_save_to(Compose *compose)
984 GtkEditable *entry;
985 gchar *result = NULL;
986 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
987 result = gtk_editable_get_chars(entry, 0, -1);
989 if (result) {
990 #if !GTK_CHECK_VERSION(2, 24, 0)
991 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
992 #else
993 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
994 #endif
995 prefs_common.compose_save_to_history = add_history(
996 prefs_common.compose_save_to_history, result);
997 #if !GTK_CHECK_VERSION(2, 24, 0)
998 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
999 prefs_common.compose_save_to_history);
1000 #else
1001 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
1002 prefs_common.compose_save_to_history);
1003 #endif
1005 return result;
1008 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
1009 GList *attach_files, GList *listAddress )
1011 Compose *compose;
1012 GtkTextView *textview;
1013 GtkTextBuffer *textbuf;
1014 GtkTextIter iter;
1015 const gchar *subject_format = NULL;
1016 const gchar *body_format = NULL;
1017 gchar *mailto_from = NULL;
1018 PrefsAccount *mailto_account = NULL;
1019 MsgInfo* dummyinfo = NULL;
1020 gint cursor_pos = -1;
1021 MailField mfield = NO_FIELD_PRESENT;
1022 gchar* buf;
1023 GtkTextMark *mark;
1025 /* check if mailto defines a from */
1026 if (mailto && *mailto != '\0') {
1027 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1028 /* mailto defines a from, check if we can get account prefs from it,
1029 if not, the account prefs will be guessed using other ways, but we'll keep
1030 the from anyway */
1031 if (mailto_from) {
1032 mailto_account = account_find_from_address(mailto_from, TRUE);
1033 if (mailto_account == NULL) {
1034 gchar *tmp_from;
1035 Xstrdup_a(tmp_from, mailto_from, return NULL);
1036 extract_address(tmp_from);
1037 mailto_account = account_find_from_address(tmp_from, TRUE);
1040 if (mailto_account)
1041 account = mailto_account;
1044 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1045 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1046 account = account_find_from_id(item->prefs->default_account);
1048 /* if no account prefs set, fallback to the current one */
1049 if (!account) account = cur_account;
1050 cm_return_val_if_fail(account != NULL, NULL);
1052 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1054 /* override from name if mailto asked for it */
1055 if (mailto_from) {
1056 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1057 g_free(mailto_from);
1058 } else
1059 /* override from name according to folder properties */
1060 if (item && item->prefs &&
1061 item->prefs->compose_with_format &&
1062 item->prefs->compose_override_from_format &&
1063 *item->prefs->compose_override_from_format != '\0') {
1065 gchar *tmp = NULL;
1066 gchar *buf = NULL;
1068 dummyinfo = compose_msginfo_new_from_compose(compose);
1070 /* decode \-escape sequences in the internal representation of the quote format */
1071 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1072 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1074 #ifdef USE_ENCHANT
1075 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1076 compose->gtkaspell);
1077 #else
1078 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1079 #endif
1080 quote_fmt_scan_string(tmp);
1081 quote_fmt_parse();
1083 buf = quote_fmt_get_buffer();
1084 if (buf == NULL)
1085 alertpanel_error(_("New message From format error."));
1086 else
1087 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1088 quote_fmt_reset_vartable();
1090 g_free(tmp);
1093 compose->replyinfo = NULL;
1094 compose->fwdinfo = NULL;
1096 textview = GTK_TEXT_VIEW(compose->text);
1097 textbuf = gtk_text_view_get_buffer(textview);
1098 compose_create_tags(textview, compose);
1100 undo_block(compose->undostruct);
1101 #ifdef USE_ENCHANT
1102 compose_set_dictionaries_from_folder_prefs(compose, item);
1103 #endif
1105 if (account->auto_sig)
1106 compose_insert_sig(compose, FALSE);
1107 gtk_text_buffer_get_start_iter(textbuf, &iter);
1108 gtk_text_buffer_place_cursor(textbuf, &iter);
1110 if (account->protocol != A_NNTP) {
1111 if (mailto && *mailto != '\0') {
1112 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1114 } else {
1115 compose_set_folder_prefs(compose, item, TRUE);
1117 if (item && item->ret_rcpt) {
1118 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1120 } else {
1121 if (mailto && *mailto != '\0') {
1122 if (!strchr(mailto, '@'))
1123 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1124 else
1125 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1126 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1127 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1128 mfield = TO_FIELD_PRESENT;
1131 * CLAWS: just don't allow return receipt request, even if the user
1132 * may want to send an email. simple but foolproof.
1134 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1136 compose_add_field_list( compose, listAddress );
1138 if (item && item->prefs && item->prefs->compose_with_format) {
1139 subject_format = item->prefs->compose_subject_format;
1140 body_format = item->prefs->compose_body_format;
1141 } else if (account->compose_with_format) {
1142 subject_format = account->compose_subject_format;
1143 body_format = account->compose_body_format;
1144 } else if (prefs_common.compose_with_format) {
1145 subject_format = prefs_common.compose_subject_format;
1146 body_format = prefs_common.compose_body_format;
1149 if (subject_format || body_format) {
1151 if ( subject_format
1152 && *subject_format != '\0' )
1154 gchar *subject = NULL;
1155 gchar *tmp = NULL;
1156 gchar *buf = NULL;
1158 if (!dummyinfo)
1159 dummyinfo = compose_msginfo_new_from_compose(compose);
1161 /* decode \-escape sequences in the internal representation of the quote format */
1162 tmp = g_malloc(strlen(subject_format)+1);
1163 pref_get_unescaped_pref(tmp, subject_format);
1165 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1166 #ifdef USE_ENCHANT
1167 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1168 compose->gtkaspell);
1169 #else
1170 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1171 #endif
1172 quote_fmt_scan_string(tmp);
1173 quote_fmt_parse();
1175 buf = quote_fmt_get_buffer();
1176 if (buf == NULL)
1177 alertpanel_error(_("New message subject format error."));
1178 else
1179 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1180 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1181 quote_fmt_reset_vartable();
1183 g_free(subject);
1184 g_free(tmp);
1185 mfield = SUBJECT_FIELD_PRESENT;
1188 if ( body_format
1189 && *body_format != '\0' )
1191 GtkTextView *text;
1192 GtkTextBuffer *buffer;
1193 GtkTextIter start, end;
1194 gchar *tmp = NULL;
1196 if (!dummyinfo)
1197 dummyinfo = compose_msginfo_new_from_compose(compose);
1199 text = GTK_TEXT_VIEW(compose->text);
1200 buffer = gtk_text_view_get_buffer(text);
1201 gtk_text_buffer_get_start_iter(buffer, &start);
1202 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1203 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1205 compose_quote_fmt(compose, dummyinfo,
1206 body_format,
1207 NULL, tmp, FALSE, TRUE,
1208 _("The body of the \"New message\" template has an error at line %d."));
1209 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1210 quote_fmt_reset_vartable();
1212 g_free(tmp);
1213 #ifdef USE_ENCHANT
1214 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1215 gtkaspell_highlight_all(compose->gtkaspell);
1216 #endif
1217 mfield = BODY_FIELD_PRESENT;
1221 procmsg_msginfo_free( &dummyinfo );
1223 if (attach_files) {
1224 GList *curr;
1225 AttachInfo *ainfo;
1227 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1228 ainfo = (AttachInfo *) curr->data;
1229 compose_attach_append(compose, ainfo->file, ainfo->file,
1230 ainfo->content_type, ainfo->charset);
1234 compose_show_first_last_header(compose, TRUE);
1236 /* Set save folder */
1237 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1238 gchar *folderidentifier;
1240 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1241 folderidentifier = folder_item_get_identifier(item);
1242 compose_set_save_to(compose, folderidentifier);
1243 g_free(folderidentifier);
1246 /* Place cursor according to provided input (mfield) */
1247 switch (mfield) {
1248 case NO_FIELD_PRESENT:
1249 if (compose->header_last)
1250 gtk_widget_grab_focus(compose->header_last->entry);
1251 break;
1252 case TO_FIELD_PRESENT:
1253 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1254 if (buf) {
1255 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1256 g_free(buf);
1258 gtk_widget_grab_focus(compose->subject_entry);
1259 break;
1260 case SUBJECT_FIELD_PRESENT:
1261 textview = GTK_TEXT_VIEW(compose->text);
1262 if (!textview)
1263 break;
1264 textbuf = gtk_text_view_get_buffer(textview);
1265 if (!textbuf)
1266 break;
1267 mark = gtk_text_buffer_get_insert(textbuf);
1268 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1269 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1271 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1272 * only defers where it comes to the variable body
1273 * is not null. If no body is present compose->text
1274 * will be null in which case you cannot place the
1275 * cursor inside the component so. An empty component
1276 * is therefore created before placing the cursor
1278 case BODY_FIELD_PRESENT:
1279 cursor_pos = quote_fmt_get_cursor_pos();
1280 if (cursor_pos == -1)
1281 gtk_widget_grab_focus(compose->header_last->entry);
1282 else
1283 gtk_widget_grab_focus(compose->text);
1284 break;
1287 undo_unblock(compose->undostruct);
1289 if (prefs_common.auto_exteditor)
1290 compose_exec_ext_editor(compose);
1292 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1294 SCROLL_TO_CURSOR(compose);
1296 compose->modified = FALSE;
1297 compose_set_title(compose);
1299 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1301 return compose;
1304 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1305 gboolean override_pref, const gchar *system)
1307 const gchar *privacy = NULL;
1309 cm_return_if_fail(compose != NULL);
1310 cm_return_if_fail(account != NULL);
1312 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1313 return;
1315 if (account->default_privacy_system && strlen(account->default_privacy_system))
1316 privacy = account->default_privacy_system;
1317 else if (system)
1318 privacy = system;
1319 else {
1320 GSList *privacy_avail = privacy_get_system_ids();
1321 if (privacy_avail && g_slist_length(privacy_avail)) {
1322 privacy = (gchar *)(privacy_avail->data);
1325 if (privacy != NULL) {
1326 if (system) {
1327 g_free(compose->privacy_system);
1328 compose->privacy_system = NULL;
1329 g_free(compose->encdata);
1330 compose->encdata = NULL;
1332 if (compose->privacy_system == NULL)
1333 compose->privacy_system = g_strdup(privacy);
1334 else if (*(compose->privacy_system) == '\0') {
1335 g_free(compose->privacy_system);
1336 g_free(compose->encdata);
1337 compose->encdata = NULL;
1338 compose->privacy_system = g_strdup(privacy);
1340 compose_update_privacy_system_menu_item(compose, FALSE);
1341 compose_use_encryption(compose, TRUE);
1345 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1347 const gchar *privacy = NULL;
1349 if (account->default_privacy_system && strlen(account->default_privacy_system))
1350 privacy = account->default_privacy_system;
1351 else if (system)
1352 privacy = system;
1353 else {
1354 GSList *privacy_avail = privacy_get_system_ids();
1355 if (privacy_avail && g_slist_length(privacy_avail)) {
1356 privacy = (gchar *)(privacy_avail->data);
1360 if (privacy != NULL) {
1361 if (system) {
1362 g_free(compose->privacy_system);
1363 compose->privacy_system = NULL;
1364 g_free(compose->encdata);
1365 compose->encdata = NULL;
1367 if (compose->privacy_system == NULL)
1368 compose->privacy_system = g_strdup(privacy);
1369 compose_update_privacy_system_menu_item(compose, FALSE);
1370 compose_use_signing(compose, TRUE);
1374 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1376 MsgInfo *msginfo;
1377 guint list_len;
1378 Compose *compose = NULL;
1380 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1382 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1383 cm_return_val_if_fail(msginfo != NULL, NULL);
1385 list_len = g_slist_length(msginfo_list);
1387 switch (mode) {
1388 case COMPOSE_REPLY:
1389 case COMPOSE_REPLY_TO_ADDRESS:
1390 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1391 FALSE, prefs_common.default_reply_list, FALSE, body);
1392 break;
1393 case COMPOSE_REPLY_WITH_QUOTE:
1394 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1395 FALSE, prefs_common.default_reply_list, FALSE, body);
1396 break;
1397 case COMPOSE_REPLY_WITHOUT_QUOTE:
1398 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1399 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1400 break;
1401 case COMPOSE_REPLY_TO_SENDER:
1402 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1403 FALSE, FALSE, TRUE, body);
1404 break;
1405 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1406 compose = compose_followup_and_reply_to(msginfo,
1407 COMPOSE_QUOTE_CHECK,
1408 FALSE, FALSE, body);
1409 break;
1410 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1411 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1412 FALSE, FALSE, TRUE, body);
1413 break;
1414 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1415 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1416 FALSE, FALSE, TRUE, NULL);
1417 break;
1418 case COMPOSE_REPLY_TO_ALL:
1419 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1420 TRUE, FALSE, FALSE, body);
1421 break;
1422 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1423 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1424 TRUE, FALSE, FALSE, body);
1425 break;
1426 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1427 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1428 TRUE, FALSE, FALSE, NULL);
1429 break;
1430 case COMPOSE_REPLY_TO_LIST:
1431 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1432 FALSE, TRUE, FALSE, body);
1433 break;
1434 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1435 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1436 FALSE, TRUE, FALSE, body);
1437 break;
1438 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1439 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1440 FALSE, TRUE, FALSE, NULL);
1441 break;
1442 case COMPOSE_FORWARD:
1443 if (prefs_common.forward_as_attachment) {
1444 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1445 return compose;
1446 } else {
1447 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1448 return compose;
1450 break;
1451 case COMPOSE_FORWARD_INLINE:
1452 /* check if we reply to more than one Message */
1453 if (list_len == 1) {
1454 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1455 break;
1457 /* more messages FALL THROUGH */
1458 case COMPOSE_FORWARD_AS_ATTACH:
1459 compose = compose_forward_multiple(NULL, msginfo_list);
1460 break;
1461 case COMPOSE_REDIRECT:
1462 compose = compose_redirect(NULL, msginfo, FALSE);
1463 break;
1464 default:
1465 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1468 if (compose == NULL) {
1469 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1470 return NULL;
1473 compose->rmode = mode;
1474 switch (compose->rmode) {
1475 case COMPOSE_REPLY:
1476 case COMPOSE_REPLY_WITH_QUOTE:
1477 case COMPOSE_REPLY_WITHOUT_QUOTE:
1478 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1479 debug_print("reply mode Normal\n");
1480 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1481 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1482 break;
1483 case COMPOSE_REPLY_TO_SENDER:
1484 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1485 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1486 debug_print("reply mode Sender\n");
1487 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1488 break;
1489 case COMPOSE_REPLY_TO_ALL:
1490 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1491 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1492 debug_print("reply mode All\n");
1493 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1494 break;
1495 case COMPOSE_REPLY_TO_LIST:
1496 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1497 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1498 debug_print("reply mode List\n");
1499 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1500 break;
1501 case COMPOSE_REPLY_TO_ADDRESS:
1502 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1503 break;
1504 default:
1505 break;
1507 return compose;
1510 static Compose *compose_reply(MsgInfo *msginfo,
1511 ComposeQuoteMode quote_mode,
1512 gboolean to_all,
1513 gboolean to_ml,
1514 gboolean to_sender,
1515 const gchar *body)
1517 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1518 to_sender, FALSE, body);
1521 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1522 ComposeQuoteMode quote_mode,
1523 gboolean to_all,
1524 gboolean to_sender,
1525 const gchar *body)
1527 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1528 to_sender, TRUE, body);
1531 static void compose_extract_original_charset(Compose *compose)
1533 MsgInfo *info = NULL;
1534 if (compose->replyinfo) {
1535 info = compose->replyinfo;
1536 } else if (compose->fwdinfo) {
1537 info = compose->fwdinfo;
1538 } else if (compose->targetinfo) {
1539 info = compose->targetinfo;
1541 if (info) {
1542 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1543 MimeInfo *partinfo = mimeinfo;
1544 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1545 partinfo = procmime_mimeinfo_next(partinfo);
1546 if (partinfo) {
1547 compose->orig_charset =
1548 g_strdup(procmime_mimeinfo_get_parameter(
1549 partinfo, "charset"));
1551 procmime_mimeinfo_free_all(&mimeinfo);
1555 #define SIGNAL_BLOCK(buffer) { \
1556 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1557 G_CALLBACK(compose_changed_cb), \
1558 compose); \
1559 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1560 G_CALLBACK(text_inserted), \
1561 compose); \
1564 #define SIGNAL_UNBLOCK(buffer) { \
1565 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1566 G_CALLBACK(compose_changed_cb), \
1567 compose); \
1568 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1569 G_CALLBACK(text_inserted), \
1570 compose); \
1573 static Compose *compose_generic_reply(MsgInfo *msginfo,
1574 ComposeQuoteMode quote_mode,
1575 gboolean to_all, gboolean to_ml,
1576 gboolean to_sender,
1577 gboolean followup_and_reply_to,
1578 const gchar *body)
1580 Compose *compose;
1581 PrefsAccount *account = NULL;
1582 GtkTextView *textview;
1583 GtkTextBuffer *textbuf;
1584 gboolean quote = FALSE;
1585 const gchar *qmark = NULL;
1586 const gchar *body_fmt = NULL;
1587 gchar *s_system = NULL;
1588 START_TIMING("");
1589 cm_return_val_if_fail(msginfo != NULL, NULL);
1590 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1592 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1594 cm_return_val_if_fail(account != NULL, NULL);
1596 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1598 compose->updating = TRUE;
1600 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1601 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1603 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1604 if (!compose->replyinfo)
1605 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1607 compose_extract_original_charset(compose);
1609 if (msginfo->folder && msginfo->folder->ret_rcpt)
1610 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1612 /* Set save folder */
1613 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1614 gchar *folderidentifier;
1616 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1617 folderidentifier = folder_item_get_identifier(msginfo->folder);
1618 compose_set_save_to(compose, folderidentifier);
1619 g_free(folderidentifier);
1622 if (compose_parse_header(compose, msginfo) < 0) {
1623 compose->updating = FALSE;
1624 compose_destroy(compose);
1625 return NULL;
1628 /* override from name according to folder properties */
1629 if (msginfo->folder && msginfo->folder->prefs &&
1630 msginfo->folder->prefs->reply_with_format &&
1631 msginfo->folder->prefs->reply_override_from_format &&
1632 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1634 gchar *tmp = NULL;
1635 gchar *buf = NULL;
1637 /* decode \-escape sequences in the internal representation of the quote format */
1638 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1639 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1641 #ifdef USE_ENCHANT
1642 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1643 compose->gtkaspell);
1644 #else
1645 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1646 #endif
1647 quote_fmt_scan_string(tmp);
1648 quote_fmt_parse();
1650 buf = quote_fmt_get_buffer();
1651 if (buf == NULL)
1652 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1653 else
1654 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1655 quote_fmt_reset_vartable();
1657 g_free(tmp);
1660 textview = (GTK_TEXT_VIEW(compose->text));
1661 textbuf = gtk_text_view_get_buffer(textview);
1662 compose_create_tags(textview, compose);
1664 undo_block(compose->undostruct);
1665 #ifdef USE_ENCHANT
1666 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1667 gtkaspell_block_check(compose->gtkaspell);
1668 #endif
1670 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1671 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1672 /* use the reply format of folder (if enabled), or the account's one
1673 (if enabled) or fallback to the global reply format, which is always
1674 enabled (even if empty), and use the relevant quotemark */
1675 quote = TRUE;
1676 if (msginfo->folder && msginfo->folder->prefs &&
1677 msginfo->folder->prefs->reply_with_format) {
1678 qmark = msginfo->folder->prefs->reply_quotemark;
1679 body_fmt = msginfo->folder->prefs->reply_body_format;
1681 } else if (account->reply_with_format) {
1682 qmark = account->reply_quotemark;
1683 body_fmt = account->reply_body_format;
1685 } else {
1686 qmark = prefs_common.quotemark;
1687 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1688 body_fmt = gettext(prefs_common.quotefmt);
1689 else
1690 body_fmt = "";
1694 if (quote) {
1695 /* empty quotemark is not allowed */
1696 if (qmark == NULL || *qmark == '\0')
1697 qmark = "> ";
1698 compose_quote_fmt(compose, compose->replyinfo,
1699 body_fmt, qmark, body, FALSE, TRUE,
1700 _("The body of the \"Reply\" template has an error at line %d."));
1701 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1702 quote_fmt_reset_vartable();
1705 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1706 compose_force_encryption(compose, account, FALSE, s_system);
1709 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1710 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1711 compose_force_signing(compose, account, s_system);
1713 g_free(s_system);
1715 SIGNAL_BLOCK(textbuf);
1717 if (account->auto_sig)
1718 compose_insert_sig(compose, FALSE);
1720 compose_wrap_all(compose);
1722 #ifdef USE_ENCHANT
1723 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1724 gtkaspell_highlight_all(compose->gtkaspell);
1725 gtkaspell_unblock_check(compose->gtkaspell);
1726 #endif
1727 SIGNAL_UNBLOCK(textbuf);
1729 gtk_widget_grab_focus(compose->text);
1731 undo_unblock(compose->undostruct);
1733 if (prefs_common.auto_exteditor)
1734 compose_exec_ext_editor(compose);
1736 compose->modified = FALSE;
1737 compose_set_title(compose);
1739 compose->updating = FALSE;
1740 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1741 SCROLL_TO_CURSOR(compose);
1743 if (compose->deferred_destroy) {
1744 compose_destroy(compose);
1745 return NULL;
1747 END_TIMING();
1749 return compose;
1752 #define INSERT_FW_HEADER(var, hdr) \
1753 if (msginfo->var && *msginfo->var) { \
1754 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1755 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1756 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1759 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1760 gboolean as_attach, const gchar *body,
1761 gboolean no_extedit,
1762 gboolean batch)
1764 Compose *compose;
1765 GtkTextView *textview;
1766 GtkTextBuffer *textbuf;
1767 gint cursor_pos = -1;
1768 ComposeMode mode;
1770 cm_return_val_if_fail(msginfo != NULL, NULL);
1771 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1773 if (!account &&
1774 !(account = compose_guess_forward_account_from_msginfo
1775 (msginfo)))
1776 account = cur_account;
1778 if (!prefs_common.forward_as_attachment)
1779 mode = COMPOSE_FORWARD_INLINE;
1780 else
1781 mode = COMPOSE_FORWARD;
1782 compose = compose_create(account, msginfo->folder, mode, batch);
1784 compose->updating = TRUE;
1785 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1786 if (!compose->fwdinfo)
1787 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1789 compose_extract_original_charset(compose);
1791 if (msginfo->subject && *msginfo->subject) {
1792 gchar *buf, *buf2, *p;
1794 buf = p = g_strdup(msginfo->subject);
1795 p += subject_get_prefix_length(p);
1796 memmove(buf, p, strlen(p) + 1);
1798 buf2 = g_strdup_printf("Fw: %s", buf);
1799 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1801 g_free(buf);
1802 g_free(buf2);
1805 /* override from name according to folder properties */
1806 if (msginfo->folder && msginfo->folder->prefs &&
1807 msginfo->folder->prefs->forward_with_format &&
1808 msginfo->folder->prefs->forward_override_from_format &&
1809 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1811 gchar *tmp = NULL;
1812 gchar *buf = NULL;
1813 MsgInfo *full_msginfo = NULL;
1815 if (!as_attach)
1816 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1817 if (!full_msginfo)
1818 full_msginfo = procmsg_msginfo_copy(msginfo);
1820 /* decode \-escape sequences in the internal representation of the quote format */
1821 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1822 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1824 #ifdef USE_ENCHANT
1825 gtkaspell_block_check(compose->gtkaspell);
1826 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1827 compose->gtkaspell);
1828 #else
1829 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1830 #endif
1831 quote_fmt_scan_string(tmp);
1832 quote_fmt_parse();
1834 buf = quote_fmt_get_buffer();
1835 if (buf == NULL)
1836 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1837 else
1838 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1839 quote_fmt_reset_vartable();
1841 g_free(tmp);
1842 procmsg_msginfo_free(&full_msginfo);
1845 textview = GTK_TEXT_VIEW(compose->text);
1846 textbuf = gtk_text_view_get_buffer(textview);
1847 compose_create_tags(textview, compose);
1849 undo_block(compose->undostruct);
1850 if (as_attach) {
1851 gchar *msgfile;
1853 msgfile = procmsg_get_message_file(msginfo);
1854 if (!is_file_exist(msgfile))
1855 g_warning("%s: file does not exist", msgfile);
1856 else
1857 compose_attach_append(compose, msgfile, msgfile,
1858 "message/rfc822", NULL);
1860 g_free(msgfile);
1861 } else {
1862 const gchar *qmark = NULL;
1863 const gchar *body_fmt = NULL;
1864 MsgInfo *full_msginfo;
1866 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1867 if (!full_msginfo)
1868 full_msginfo = procmsg_msginfo_copy(msginfo);
1870 /* use the forward format of folder (if enabled), or the account's one
1871 (if enabled) or fallback to the global forward format, which is always
1872 enabled (even if empty), and use the relevant quotemark */
1873 if (msginfo->folder && msginfo->folder->prefs &&
1874 msginfo->folder->prefs->forward_with_format) {
1875 qmark = msginfo->folder->prefs->forward_quotemark;
1876 body_fmt = msginfo->folder->prefs->forward_body_format;
1878 } else if (account->forward_with_format) {
1879 qmark = account->forward_quotemark;
1880 body_fmt = account->forward_body_format;
1882 } else {
1883 qmark = prefs_common.fw_quotemark;
1884 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1885 body_fmt = gettext(prefs_common.fw_quotefmt);
1886 else
1887 body_fmt = "";
1890 /* empty quotemark is not allowed */
1891 if (qmark == NULL || *qmark == '\0')
1892 qmark = "> ";
1894 compose_quote_fmt(compose, full_msginfo,
1895 body_fmt, qmark, body, FALSE, TRUE,
1896 _("The body of the \"Forward\" template has an error at line %d."));
1897 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1898 quote_fmt_reset_vartable();
1899 compose_attach_parts(compose, msginfo);
1901 procmsg_msginfo_free(&full_msginfo);
1904 SIGNAL_BLOCK(textbuf);
1906 if (account->auto_sig)
1907 compose_insert_sig(compose, FALSE);
1909 compose_wrap_all(compose);
1911 #ifdef USE_ENCHANT
1912 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1913 gtkaspell_highlight_all(compose->gtkaspell);
1914 gtkaspell_unblock_check(compose->gtkaspell);
1915 #endif
1916 SIGNAL_UNBLOCK(textbuf);
1918 cursor_pos = quote_fmt_get_cursor_pos();
1919 if (cursor_pos == -1)
1920 gtk_widget_grab_focus(compose->header_last->entry);
1921 else
1922 gtk_widget_grab_focus(compose->text);
1924 if (!no_extedit && prefs_common.auto_exteditor)
1925 compose_exec_ext_editor(compose);
1927 /*save folder*/
1928 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1929 gchar *folderidentifier;
1931 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1932 folderidentifier = folder_item_get_identifier(msginfo->folder);
1933 compose_set_save_to(compose, folderidentifier);
1934 g_free(folderidentifier);
1937 undo_unblock(compose->undostruct);
1939 compose->modified = FALSE;
1940 compose_set_title(compose);
1942 compose->updating = FALSE;
1943 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1944 SCROLL_TO_CURSOR(compose);
1946 if (compose->deferred_destroy) {
1947 compose_destroy(compose);
1948 return NULL;
1951 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1953 return compose;
1956 #undef INSERT_FW_HEADER
1958 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1960 Compose *compose;
1961 GtkTextView *textview;
1962 GtkTextBuffer *textbuf;
1963 GtkTextIter iter;
1964 GSList *msginfo;
1965 gchar *msgfile;
1966 gboolean single_mail = TRUE;
1968 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1970 if (g_slist_length(msginfo_list) > 1)
1971 single_mail = FALSE;
1973 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1974 if (((MsgInfo *)msginfo->data)->folder == NULL)
1975 return NULL;
1977 /* guess account from first selected message */
1978 if (!account &&
1979 !(account = compose_guess_forward_account_from_msginfo
1980 (msginfo_list->data)))
1981 account = cur_account;
1983 cm_return_val_if_fail(account != NULL, NULL);
1985 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1986 if (msginfo->data) {
1987 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1988 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1992 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1993 g_warning("no msginfo_list");
1994 return NULL;
1997 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1999 compose->updating = TRUE;
2001 /* override from name according to folder properties */
2002 if (msginfo_list->data) {
2003 MsgInfo *msginfo = msginfo_list->data;
2005 if (msginfo->folder && msginfo->folder->prefs &&
2006 msginfo->folder->prefs->forward_with_format &&
2007 msginfo->folder->prefs->forward_override_from_format &&
2008 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2010 gchar *tmp = NULL;
2011 gchar *buf = NULL;
2013 /* decode \-escape sequences in the internal representation of the quote format */
2014 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2015 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2017 #ifdef USE_ENCHANT
2018 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2019 compose->gtkaspell);
2020 #else
2021 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2022 #endif
2023 quote_fmt_scan_string(tmp);
2024 quote_fmt_parse();
2026 buf = quote_fmt_get_buffer();
2027 if (buf == NULL)
2028 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2029 else
2030 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2031 quote_fmt_reset_vartable();
2033 g_free(tmp);
2037 textview = GTK_TEXT_VIEW(compose->text);
2038 textbuf = gtk_text_view_get_buffer(textview);
2039 compose_create_tags(textview, compose);
2041 undo_block(compose->undostruct);
2042 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2043 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2045 if (!is_file_exist(msgfile))
2046 g_warning("%s: file does not exist", msgfile);
2047 else
2048 compose_attach_append(compose, msgfile, msgfile,
2049 "message/rfc822", NULL);
2050 g_free(msgfile);
2053 if (single_mail) {
2054 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2055 if (info->subject && *info->subject) {
2056 gchar *buf, *buf2, *p;
2058 buf = p = g_strdup(info->subject);
2059 p += subject_get_prefix_length(p);
2060 memmove(buf, p, strlen(p) + 1);
2062 buf2 = g_strdup_printf("Fw: %s", buf);
2063 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2065 g_free(buf);
2066 g_free(buf2);
2068 } else {
2069 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2070 _("Fw: multiple emails"));
2073 SIGNAL_BLOCK(textbuf);
2075 if (account->auto_sig)
2076 compose_insert_sig(compose, FALSE);
2078 compose_wrap_all(compose);
2080 SIGNAL_UNBLOCK(textbuf);
2082 gtk_text_buffer_get_start_iter(textbuf, &iter);
2083 gtk_text_buffer_place_cursor(textbuf, &iter);
2085 if (prefs_common.auto_exteditor)
2086 compose_exec_ext_editor(compose);
2088 gtk_widget_grab_focus(compose->header_last->entry);
2089 undo_unblock(compose->undostruct);
2090 compose->modified = FALSE;
2091 compose_set_title(compose);
2093 compose->updating = FALSE;
2094 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2095 SCROLL_TO_CURSOR(compose);
2097 if (compose->deferred_destroy) {
2098 compose_destroy(compose);
2099 return NULL;
2102 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2104 return compose;
2107 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2109 GtkTextIter start = *iter;
2110 GtkTextIter end_iter;
2111 int start_pos = gtk_text_iter_get_offset(&start);
2112 gchar *str = NULL;
2113 if (!compose->account->sig_sep)
2114 return FALSE;
2116 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2117 start_pos+strlen(compose->account->sig_sep));
2119 /* check sig separator */
2120 str = gtk_text_iter_get_text(&start, &end_iter);
2121 if (!strcmp(str, compose->account->sig_sep)) {
2122 gchar *tmp = NULL;
2123 /* check end of line (\n) */
2124 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2125 start_pos+strlen(compose->account->sig_sep));
2126 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2127 start_pos+strlen(compose->account->sig_sep)+1);
2128 tmp = gtk_text_iter_get_text(&start, &end_iter);
2129 if (!strcmp(tmp,"\n")) {
2130 g_free(str);
2131 g_free(tmp);
2132 return TRUE;
2134 g_free(tmp);
2136 g_free(str);
2138 return FALSE;
2141 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2143 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2144 Compose *compose = (Compose *)data;
2145 FolderItem *old_item = NULL;
2146 FolderItem *new_item = NULL;
2147 gchar *old_id, *new_id;
2149 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2150 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2151 return FALSE;
2153 old_item = hookdata->item;
2154 new_item = hookdata->item2;
2156 old_id = folder_item_get_identifier(old_item);
2157 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2159 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2160 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2161 compose->targetinfo->folder = new_item;
2164 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2165 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2166 compose->replyinfo->folder = new_item;
2169 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2170 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2171 compose->fwdinfo->folder = new_item;
2174 g_free(old_id);
2175 g_free(new_id);
2176 return FALSE;
2179 static void compose_colorize_signature(Compose *compose)
2181 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2182 GtkTextIter iter;
2183 GtkTextIter end_iter;
2184 gtk_text_buffer_get_start_iter(buffer, &iter);
2185 while (gtk_text_iter_forward_line(&iter))
2186 if (compose_is_sig_separator(compose, buffer, &iter)) {
2187 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2188 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2192 #define BLOCK_WRAP() { \
2193 prev_autowrap = compose->autowrap; \
2194 buffer = gtk_text_view_get_buffer( \
2195 GTK_TEXT_VIEW(compose->text)); \
2196 compose->autowrap = FALSE; \
2198 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2199 G_CALLBACK(compose_changed_cb), \
2200 compose); \
2201 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2202 G_CALLBACK(text_inserted), \
2203 compose); \
2205 #define UNBLOCK_WRAP() { \
2206 compose->autowrap = prev_autowrap; \
2207 if (compose->autowrap) { \
2208 gint old = compose->draft_timeout_tag; \
2209 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2210 compose_wrap_all(compose); \
2211 compose->draft_timeout_tag = old; \
2214 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2215 G_CALLBACK(compose_changed_cb), \
2216 compose); \
2217 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2218 G_CALLBACK(text_inserted), \
2219 compose); \
2222 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2224 Compose *compose = NULL;
2225 PrefsAccount *account = NULL;
2226 GtkTextView *textview;
2227 GtkTextBuffer *textbuf;
2228 GtkTextMark *mark;
2229 GtkTextIter iter;
2230 FILE *fp;
2231 gchar buf[BUFFSIZE];
2232 gboolean use_signing = FALSE;
2233 gboolean use_encryption = FALSE;
2234 gchar *privacy_system = NULL;
2235 int priority = PRIORITY_NORMAL;
2236 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2237 gboolean autowrap = prefs_common.autowrap;
2238 gboolean autoindent = prefs_common.auto_indent;
2239 HeaderEntry *manual_headers = NULL;
2241 cm_return_val_if_fail(msginfo != NULL, NULL);
2242 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2244 if (compose_put_existing_to_front(msginfo)) {
2245 return NULL;
2248 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2249 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2250 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2251 gchar queueheader_buf[BUFFSIZE];
2252 gint id, param;
2254 /* Select Account from queue headers */
2255 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2256 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2257 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2258 account = account_find_from_id(id);
2260 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2261 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2262 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2263 account = account_find_from_id(id);
2265 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2266 sizeof(queueheader_buf), "NAID:")) {
2267 id = atoi(&queueheader_buf[strlen("NAID:")]);
2268 account = account_find_from_id(id);
2270 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2271 sizeof(queueheader_buf), "MAID:")) {
2272 id = atoi(&queueheader_buf[strlen("MAID:")]);
2273 account = account_find_from_id(id);
2275 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2276 sizeof(queueheader_buf), "S:")) {
2277 account = account_find_from_address(queueheader_buf, FALSE);
2279 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2280 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2281 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2282 use_signing = param;
2285 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2286 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2287 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2288 use_signing = param;
2291 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2292 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2293 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2294 use_encryption = param;
2296 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2297 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2298 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2299 use_encryption = param;
2301 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2302 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2303 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2304 autowrap = param;
2306 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2307 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2308 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2309 autoindent = param;
2311 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2312 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2313 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2315 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2316 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2317 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2319 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2320 sizeof(queueheader_buf), "X-Priority: ")) {
2321 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2322 priority = param;
2324 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2325 sizeof(queueheader_buf), "RMID:")) {
2326 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2327 if (tokens[0] && tokens[1] && tokens[2]) {
2328 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2329 if (orig_item != NULL) {
2330 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2333 g_strfreev(tokens);
2335 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2336 sizeof(queueheader_buf), "FMID:")) {
2337 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2338 if (tokens[0] && tokens[1] && tokens[2]) {
2339 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2340 if (orig_item != NULL) {
2341 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2344 g_strfreev(tokens);
2346 /* Get manual headers */
2347 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2348 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2349 if (*listmh != '\0') {
2350 debug_print("Got manual headers: %s\n", listmh);
2351 manual_headers = procheader_entries_from_str(listmh);
2353 g_free(listmh);
2355 } else {
2356 account = msginfo->folder->folder->account;
2359 if (!account && prefs_common.reedit_account_autosel) {
2360 gchar from[BUFFSIZE];
2361 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2362 extract_address(from);
2363 account = account_find_from_address(from, FALSE);
2366 if (!account) {
2367 account = cur_account;
2369 cm_return_val_if_fail(account != NULL, NULL);
2371 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2373 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2374 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2375 compose->autowrap = autowrap;
2376 compose->replyinfo = replyinfo;
2377 compose->fwdinfo = fwdinfo;
2379 compose->updating = TRUE;
2380 compose->priority = priority;
2382 if (privacy_system != NULL) {
2383 compose->privacy_system = privacy_system;
2384 compose_use_signing(compose, use_signing);
2385 compose_use_encryption(compose, use_encryption);
2386 compose_update_privacy_system_menu_item(compose, FALSE);
2387 } else {
2388 activate_privacy_system(compose, account, FALSE);
2391 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2393 compose_extract_original_charset(compose);
2395 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2396 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2397 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2398 gchar queueheader_buf[BUFFSIZE];
2400 /* Set message save folder */
2401 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2402 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2403 compose_set_save_to(compose, &queueheader_buf[4]);
2405 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2406 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2407 if (active) {
2408 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2413 if (compose_parse_header(compose, msginfo) < 0) {
2414 compose->updating = FALSE;
2415 compose_destroy(compose);
2416 return NULL;
2418 compose_reedit_set_entry(compose, msginfo);
2420 textview = GTK_TEXT_VIEW(compose->text);
2421 textbuf = gtk_text_view_get_buffer(textview);
2422 compose_create_tags(textview, compose);
2424 mark = gtk_text_buffer_get_insert(textbuf);
2425 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2427 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2428 G_CALLBACK(compose_changed_cb),
2429 compose);
2431 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2432 fp = procmime_get_first_encrypted_text_content(msginfo);
2433 if (fp) {
2434 compose_force_encryption(compose, account, TRUE, NULL);
2436 } else {
2437 fp = procmime_get_first_text_content(msginfo);
2439 if (fp == NULL) {
2440 g_warning("Can't get text part");
2443 if (fp != NULL) {
2444 gboolean prev_autowrap;
2445 GtkTextBuffer *buffer;
2446 BLOCK_WRAP();
2447 while (fgets(buf, sizeof(buf), fp) != NULL) {
2448 strcrchomp(buf);
2449 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2451 UNBLOCK_WRAP();
2452 fclose(fp);
2455 compose_attach_parts(compose, msginfo);
2457 compose_colorize_signature(compose);
2459 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2460 G_CALLBACK(compose_changed_cb),
2461 compose);
2463 if (manual_headers != NULL) {
2464 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2465 procheader_entries_free(manual_headers);
2466 compose->updating = FALSE;
2467 compose_destroy(compose);
2468 return NULL;
2470 procheader_entries_free(manual_headers);
2473 gtk_widget_grab_focus(compose->text);
2475 if (prefs_common.auto_exteditor) {
2476 compose_exec_ext_editor(compose);
2478 compose->modified = FALSE;
2479 compose_set_title(compose);
2481 compose->updating = FALSE;
2482 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2483 SCROLL_TO_CURSOR(compose);
2485 if (compose->deferred_destroy) {
2486 compose_destroy(compose);
2487 return NULL;
2490 compose->sig_str = account_get_signature_str(compose->account);
2492 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2494 return compose;
2497 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2498 gboolean batch)
2500 Compose *compose;
2501 gchar *filename;
2502 FolderItem *item;
2504 cm_return_val_if_fail(msginfo != NULL, NULL);
2506 if (!account)
2507 account = account_get_reply_account(msginfo,
2508 prefs_common.reply_account_autosel);
2509 cm_return_val_if_fail(account != NULL, NULL);
2511 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2513 compose->updating = TRUE;
2515 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2516 compose->replyinfo = NULL;
2517 compose->fwdinfo = NULL;
2519 compose_show_first_last_header(compose, TRUE);
2521 gtk_widget_grab_focus(compose->header_last->entry);
2523 filename = procmsg_get_message_file(msginfo);
2525 if (filename == NULL) {
2526 compose->updating = FALSE;
2527 compose_destroy(compose);
2529 return NULL;
2532 compose->redirect_filename = filename;
2534 /* Set save folder */
2535 item = msginfo->folder;
2536 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2537 gchar *folderidentifier;
2539 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2540 folderidentifier = folder_item_get_identifier(item);
2541 compose_set_save_to(compose, folderidentifier);
2542 g_free(folderidentifier);
2545 compose_attach_parts(compose, msginfo);
2547 if (msginfo->subject)
2548 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2549 msginfo->subject);
2550 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2552 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2553 _("The body of the \"Redirect\" template has an error at line %d."));
2554 quote_fmt_reset_vartable();
2555 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2557 compose_colorize_signature(compose);
2560 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2561 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2562 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2564 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2565 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2566 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2567 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2568 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2569 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2570 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2571 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2572 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2574 if (compose->toolbar->draft_btn)
2575 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2576 if (compose->toolbar->insert_btn)
2577 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2578 if (compose->toolbar->attach_btn)
2579 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2580 if (compose->toolbar->sig_btn)
2581 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2582 if (compose->toolbar->exteditor_btn)
2583 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2584 if (compose->toolbar->linewrap_current_btn)
2585 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2586 if (compose->toolbar->linewrap_all_btn)
2587 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2589 compose->modified = FALSE;
2590 compose_set_title(compose);
2591 compose->updating = FALSE;
2592 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2593 SCROLL_TO_CURSOR(compose);
2595 if (compose->deferred_destroy) {
2596 compose_destroy(compose);
2597 return NULL;
2600 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2602 return compose;
2605 const GList *compose_get_compose_list(void)
2607 return compose_list;
2610 void compose_entry_append(Compose *compose, const gchar *address,
2611 ComposeEntryType type, ComposePrefType pref_type)
2613 const gchar *header;
2614 gchar *cur, *begin;
2615 gboolean in_quote = FALSE;
2616 if (!address || *address == '\0') return;
2618 switch (type) {
2619 case COMPOSE_CC:
2620 header = N_("Cc:");
2621 break;
2622 case COMPOSE_BCC:
2623 header = N_("Bcc:");
2624 break;
2625 case COMPOSE_REPLYTO:
2626 header = N_("Reply-To:");
2627 break;
2628 case COMPOSE_NEWSGROUPS:
2629 header = N_("Newsgroups:");
2630 break;
2631 case COMPOSE_FOLLOWUPTO:
2632 header = N_( "Followup-To:");
2633 break;
2634 case COMPOSE_INREPLYTO:
2635 header = N_( "In-Reply-To:");
2636 break;
2637 case COMPOSE_TO:
2638 default:
2639 header = N_("To:");
2640 break;
2642 header = prefs_common_translated_header_name(header);
2644 cur = begin = (gchar *)address;
2646 /* we separate the line by commas, but not if we're inside a quoted
2647 * string */
2648 while (*cur != '\0') {
2649 if (*cur == '"')
2650 in_quote = !in_quote;
2651 if (*cur == ',' && !in_quote) {
2652 gchar *tmp = g_strdup(begin);
2653 gchar *o_tmp = tmp;
2654 tmp[cur-begin]='\0';
2655 cur++;
2656 begin = cur;
2657 while (*tmp == ' ' || *tmp == '\t')
2658 tmp++;
2659 compose_add_header_entry(compose, header, tmp, pref_type);
2660 g_free(o_tmp);
2661 continue;
2663 cur++;
2665 if (begin < cur) {
2666 gchar *tmp = g_strdup(begin);
2667 gchar *o_tmp = tmp;
2668 tmp[cur-begin]='\0';
2669 while (*tmp == ' ' || *tmp == '\t')
2670 tmp++;
2671 compose_add_header_entry(compose, header, tmp, pref_type);
2672 g_free(o_tmp);
2676 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2678 GSList *h_list;
2679 GtkEntry *entry;
2681 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2682 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2683 if (gtk_entry_get_text(entry) &&
2684 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2685 gtk_widget_modify_base(
2686 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2687 GTK_STATE_NORMAL, &default_to_bgcolor);
2688 gtk_widget_modify_text(
2689 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2690 GTK_STATE_NORMAL, &default_to_color);
2695 void compose_toolbar_cb(gint action, gpointer data)
2697 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2698 Compose *compose = (Compose*)toolbar_item->parent;
2700 cm_return_if_fail(compose != NULL);
2702 switch(action) {
2703 case A_SEND:
2704 compose_send_cb(NULL, compose);
2705 break;
2706 case A_SENDL:
2707 compose_send_later_cb(NULL, compose);
2708 break;
2709 case A_DRAFT:
2710 compose_draft(compose, COMPOSE_QUIT_EDITING);
2711 break;
2712 case A_INSERT:
2713 compose_insert_file_cb(NULL, compose);
2714 break;
2715 case A_ATTACH:
2716 compose_attach_cb(NULL, compose);
2717 break;
2718 case A_SIG:
2719 compose_insert_sig(compose, FALSE);
2720 break;
2721 case A_REP_SIG:
2722 compose_insert_sig(compose, TRUE);
2723 break;
2724 case A_EXTEDITOR:
2725 compose_ext_editor_cb(NULL, compose);
2726 break;
2727 case A_LINEWRAP_CURRENT:
2728 compose_beautify_paragraph(compose, NULL, TRUE);
2729 break;
2730 case A_LINEWRAP_ALL:
2731 compose_wrap_all_full(compose, TRUE);
2732 break;
2733 case A_ADDRBOOK:
2734 compose_address_cb(NULL, compose);
2735 break;
2736 #ifdef USE_ENCHANT
2737 case A_CHECK_SPELLING:
2738 compose_check_all(NULL, compose);
2739 break;
2740 #endif
2741 default:
2742 break;
2746 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2748 gchar *to = NULL;
2749 gchar *cc = NULL;
2750 gchar *bcc = NULL;
2751 gchar *subject = NULL;
2752 gchar *body = NULL;
2753 gchar *temp = NULL;
2754 gsize len = 0;
2755 gchar **attach = NULL;
2756 gchar *inreplyto = NULL;
2757 MailField mfield = NO_FIELD_PRESENT;
2759 /* get mailto parts but skip from */
2760 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2762 if (to) {
2763 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2764 mfield = TO_FIELD_PRESENT;
2766 if (cc)
2767 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2768 if (bcc)
2769 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2770 if (subject) {
2771 if (!g_utf8_validate (subject, -1, NULL)) {
2772 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2773 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2774 g_free(temp);
2775 } else {
2776 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2778 mfield = SUBJECT_FIELD_PRESENT;
2780 if (body) {
2781 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2782 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2783 GtkTextMark *mark;
2784 GtkTextIter iter;
2785 gboolean prev_autowrap = compose->autowrap;
2787 compose->autowrap = FALSE;
2789 mark = gtk_text_buffer_get_insert(buffer);
2790 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2792 if (!g_utf8_validate (body, -1, NULL)) {
2793 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2794 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2795 g_free(temp);
2796 } else {
2797 gtk_text_buffer_insert(buffer, &iter, body, -1);
2799 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2801 compose->autowrap = prev_autowrap;
2802 if (compose->autowrap)
2803 compose_wrap_all(compose);
2804 mfield = BODY_FIELD_PRESENT;
2807 if (attach) {
2808 gint i = 0, att = 0;
2809 gchar *warn_files = NULL;
2810 while (attach[i] != NULL) {
2811 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2812 if (utf8_filename) {
2813 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2814 gchar *tmp = g_strdup_printf("%s%s\n",
2815 warn_files?warn_files:"",
2816 utf8_filename);
2817 g_free(warn_files);
2818 warn_files = tmp;
2819 att++;
2821 g_free(utf8_filename);
2822 } else {
2823 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2825 i++;
2827 if (warn_files) {
2828 alertpanel_notice(ngettext(
2829 "The following file has been attached: \n%s",
2830 "The following files have been attached: \n%s", att), warn_files);
2831 g_free(warn_files);
2834 if (inreplyto)
2835 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2837 g_free(to);
2838 g_free(cc);
2839 g_free(bcc);
2840 g_free(subject);
2841 g_free(body);
2842 g_strfreev(attach);
2843 g_free(inreplyto);
2845 return mfield;
2848 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2850 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2851 {"Cc:", NULL, TRUE},
2852 {"References:", NULL, FALSE},
2853 {"Bcc:", NULL, TRUE},
2854 {"Newsgroups:", NULL, TRUE},
2855 {"Followup-To:", NULL, TRUE},
2856 {"List-Post:", NULL, FALSE},
2857 {"X-Priority:", NULL, FALSE},
2858 {NULL, NULL, FALSE}};
2860 enum
2862 H_REPLY_TO = 0,
2863 H_CC = 1,
2864 H_REFERENCES = 2,
2865 H_BCC = 3,
2866 H_NEWSGROUPS = 4,
2867 H_FOLLOWUP_TO = 5,
2868 H_LIST_POST = 6,
2869 H_X_PRIORITY = 7
2872 FILE *fp;
2874 cm_return_val_if_fail(msginfo != NULL, -1);
2876 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2877 procheader_get_header_fields(fp, hentry);
2878 fclose(fp);
2880 if (hentry[H_REPLY_TO].body != NULL) {
2881 if (hentry[H_REPLY_TO].body[0] != '\0') {
2882 compose->replyto =
2883 conv_unmime_header(hentry[H_REPLY_TO].body,
2884 NULL, TRUE);
2886 g_free(hentry[H_REPLY_TO].body);
2887 hentry[H_REPLY_TO].body = NULL;
2889 if (hentry[H_CC].body != NULL) {
2890 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2891 g_free(hentry[H_CC].body);
2892 hentry[H_CC].body = NULL;
2894 if (hentry[H_REFERENCES].body != NULL) {
2895 if (compose->mode == COMPOSE_REEDIT)
2896 compose->references = hentry[H_REFERENCES].body;
2897 else {
2898 compose->references = compose_parse_references
2899 (hentry[H_REFERENCES].body, msginfo->msgid);
2900 g_free(hentry[H_REFERENCES].body);
2902 hentry[H_REFERENCES].body = NULL;
2904 if (hentry[H_BCC].body != NULL) {
2905 if (compose->mode == COMPOSE_REEDIT)
2906 compose->bcc =
2907 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2908 g_free(hentry[H_BCC].body);
2909 hentry[H_BCC].body = NULL;
2911 if (hentry[H_NEWSGROUPS].body != NULL) {
2912 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2913 hentry[H_NEWSGROUPS].body = NULL;
2915 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2916 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2917 compose->followup_to =
2918 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2919 NULL, TRUE);
2921 g_free(hentry[H_FOLLOWUP_TO].body);
2922 hentry[H_FOLLOWUP_TO].body = NULL;
2924 if (hentry[H_LIST_POST].body != NULL) {
2925 gchar *to = NULL, *start = NULL;
2927 extract_address(hentry[H_LIST_POST].body);
2928 if (hentry[H_LIST_POST].body[0] != '\0') {
2929 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2931 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2932 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2934 if (to) {
2935 g_free(compose->ml_post);
2936 compose->ml_post = to;
2939 g_free(hentry[H_LIST_POST].body);
2940 hentry[H_LIST_POST].body = NULL;
2943 /* CLAWS - X-Priority */
2944 if (compose->mode == COMPOSE_REEDIT)
2945 if (hentry[H_X_PRIORITY].body != NULL) {
2946 gint priority;
2948 priority = atoi(hentry[H_X_PRIORITY].body);
2949 g_free(hentry[H_X_PRIORITY].body);
2951 hentry[H_X_PRIORITY].body = NULL;
2953 if (priority < PRIORITY_HIGHEST ||
2954 priority > PRIORITY_LOWEST)
2955 priority = PRIORITY_NORMAL;
2957 compose->priority = priority;
2960 if (compose->mode == COMPOSE_REEDIT) {
2961 if (msginfo->inreplyto && *msginfo->inreplyto)
2962 compose->inreplyto = g_strdup(msginfo->inreplyto);
2964 if (msginfo->msgid && *msginfo->msgid)
2965 compose->msgid = g_strdup(msginfo->msgid);
2966 } else {
2967 if (msginfo->msgid && *msginfo->msgid)
2968 compose->inreplyto = g_strdup(msginfo->msgid);
2970 if (!compose->references) {
2971 if (msginfo->msgid && *msginfo->msgid) {
2972 if (msginfo->inreplyto && *msginfo->inreplyto)
2973 compose->references =
2974 g_strdup_printf("<%s>\n\t<%s>",
2975 msginfo->inreplyto,
2976 msginfo->msgid);
2977 else
2978 compose->references =
2979 g_strconcat("<", msginfo->msgid, ">",
2980 NULL);
2981 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2982 compose->references =
2983 g_strconcat("<", msginfo->inreplyto, ">",
2984 NULL);
2989 return 0;
2992 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2994 FILE *fp;
2995 HeaderEntry *he;
2997 cm_return_val_if_fail(msginfo != NULL, -1);
2999 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3000 procheader_get_header_fields(fp, entries);
3001 fclose(fp);
3003 he = entries;
3004 while (he != NULL && he->name != NULL) {
3005 GtkTreeIter iter;
3006 GtkListStore *model = NULL;
3008 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3009 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3010 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3011 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3012 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3013 ++he;
3016 return 0;
3019 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3021 GSList *ref_id_list, *cur;
3022 GString *new_ref;
3023 gchar *new_ref_str;
3025 ref_id_list = references_list_append(NULL, ref);
3026 if (!ref_id_list) return NULL;
3027 if (msgid && *msgid)
3028 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3030 for (;;) {
3031 gint len = 0;
3033 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3034 /* "<" + Message-ID + ">" + CR+LF+TAB */
3035 len += strlen((gchar *)cur->data) + 5;
3037 if (len > MAX_REFERENCES_LEN) {
3038 /* remove second message-ID */
3039 if (ref_id_list && ref_id_list->next &&
3040 ref_id_list->next->next) {
3041 g_free(ref_id_list->next->data);
3042 ref_id_list = g_slist_remove
3043 (ref_id_list, ref_id_list->next->data);
3044 } else {
3045 slist_free_strings_full(ref_id_list);
3046 return NULL;
3048 } else
3049 break;
3052 new_ref = g_string_new("");
3053 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3054 if (new_ref->len > 0)
3055 g_string_append(new_ref, "\n\t");
3056 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3059 slist_free_strings_full(ref_id_list);
3061 new_ref_str = new_ref->str;
3062 g_string_free(new_ref, FALSE);
3064 return new_ref_str;
3067 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3068 const gchar *fmt, const gchar *qmark,
3069 const gchar *body, gboolean rewrap,
3070 gboolean need_unescape,
3071 const gchar *err_msg)
3073 MsgInfo* dummyinfo = NULL;
3074 gchar *quote_str = NULL;
3075 gchar *buf;
3076 gboolean prev_autowrap;
3077 const gchar *trimmed_body = body;
3078 gint cursor_pos = -1;
3079 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3080 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3081 GtkTextIter iter;
3082 GtkTextMark *mark;
3085 SIGNAL_BLOCK(buffer);
3087 if (!msginfo) {
3088 dummyinfo = compose_msginfo_new_from_compose(compose);
3089 msginfo = dummyinfo;
3092 if (qmark != NULL) {
3093 #ifdef USE_ENCHANT
3094 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3095 compose->gtkaspell);
3096 #else
3097 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3098 #endif
3099 quote_fmt_scan_string(qmark);
3100 quote_fmt_parse();
3102 buf = quote_fmt_get_buffer();
3103 if (buf == NULL)
3104 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3105 else
3106 Xstrdup_a(quote_str, buf, goto error)
3109 if (fmt && *fmt != '\0') {
3111 if (trimmed_body)
3112 while (*trimmed_body == '\n')
3113 trimmed_body++;
3115 #ifdef USE_ENCHANT
3116 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3117 compose->gtkaspell);
3118 #else
3119 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3120 #endif
3121 if (need_unescape) {
3122 gchar *tmp = NULL;
3124 /* decode \-escape sequences in the internal representation of the quote format */
3125 tmp = g_malloc(strlen(fmt)+1);
3126 pref_get_unescaped_pref(tmp, fmt);
3127 quote_fmt_scan_string(tmp);
3128 quote_fmt_parse();
3129 g_free(tmp);
3130 } else {
3131 quote_fmt_scan_string(fmt);
3132 quote_fmt_parse();
3135 buf = quote_fmt_get_buffer();
3136 if (buf == NULL) {
3137 gint line = quote_fmt_get_line();
3138 alertpanel_error(err_msg, line);
3139 goto error;
3141 } else
3142 buf = "";
3144 prev_autowrap = compose->autowrap;
3145 compose->autowrap = FALSE;
3147 mark = gtk_text_buffer_get_insert(buffer);
3148 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3149 if (g_utf8_validate(buf, -1, NULL)) {
3150 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3151 } else {
3152 gchar *tmpout = NULL;
3153 tmpout = conv_codeset_strdup
3154 (buf, conv_get_locale_charset_str_no_utf8(),
3155 CS_INTERNAL);
3156 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3157 g_free(tmpout);
3158 tmpout = g_malloc(strlen(buf)*2+1);
3159 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3161 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3162 g_free(tmpout);
3165 cursor_pos = quote_fmt_get_cursor_pos();
3166 if (cursor_pos == -1)
3167 cursor_pos = gtk_text_iter_get_offset(&iter);
3168 compose->set_cursor_pos = cursor_pos;
3170 gtk_text_buffer_get_start_iter(buffer, &iter);
3171 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3172 gtk_text_buffer_place_cursor(buffer, &iter);
3174 compose->autowrap = prev_autowrap;
3175 if (compose->autowrap && rewrap)
3176 compose_wrap_all(compose);
3178 goto ok;
3180 error:
3181 buf = NULL;
3183 SIGNAL_UNBLOCK(buffer);
3185 procmsg_msginfo_free( &dummyinfo );
3187 return buf;
3190 /* if ml_post is of type addr@host and from is of type
3191 * addr-anything@host, return TRUE
3193 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3195 gchar *left_ml = NULL;
3196 gchar *right_ml = NULL;
3197 gchar *left_from = NULL;
3198 gchar *right_from = NULL;
3199 gboolean result = FALSE;
3201 if (!ml_post || !from)
3202 return FALSE;
3204 left_ml = g_strdup(ml_post);
3205 if (strstr(left_ml, "@")) {
3206 right_ml = strstr(left_ml, "@")+1;
3207 *(strstr(left_ml, "@")) = '\0';
3210 left_from = g_strdup(from);
3211 if (strstr(left_from, "@")) {
3212 right_from = strstr(left_from, "@")+1;
3213 *(strstr(left_from, "@")) = '\0';
3216 if (right_ml && right_from
3217 && !strncmp(left_from, left_ml, strlen(left_ml))
3218 && !strcmp(right_from, right_ml)) {
3219 result = TRUE;
3221 g_free(left_ml);
3222 g_free(left_from);
3224 return result;
3227 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3228 gboolean respect_default_to)
3230 if (!compose)
3231 return;
3232 if (!folder || !folder->prefs)
3233 return;
3235 if (respect_default_to && folder->prefs->enable_default_to) {
3236 compose_entry_append(compose, folder->prefs->default_to,
3237 COMPOSE_TO, PREF_FOLDER);
3238 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3240 if (folder->prefs->enable_default_cc)
3241 compose_entry_append(compose, folder->prefs->default_cc,
3242 COMPOSE_CC, PREF_FOLDER);
3243 if (folder->prefs->enable_default_bcc)
3244 compose_entry_append(compose, folder->prefs->default_bcc,
3245 COMPOSE_BCC, PREF_FOLDER);
3246 if (folder->prefs->enable_default_replyto)
3247 compose_entry_append(compose, folder->prefs->default_replyto,
3248 COMPOSE_REPLYTO, PREF_FOLDER);
3251 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3253 gchar *buf, *buf2;
3254 gchar *p;
3256 if (!compose || !msginfo)
3257 return;
3259 if (msginfo->subject && *msginfo->subject) {
3260 buf = p = g_strdup(msginfo->subject);
3261 p += subject_get_prefix_length(p);
3262 memmove(buf, p, strlen(p) + 1);
3264 buf2 = g_strdup_printf("Re: %s", buf);
3265 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3267 g_free(buf2);
3268 g_free(buf);
3269 } else
3270 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3273 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3274 gboolean to_all, gboolean to_ml,
3275 gboolean to_sender,
3276 gboolean followup_and_reply_to)
3278 GSList *cc_list = NULL;
3279 GSList *cur;
3280 gchar *from = NULL;
3281 gchar *replyto = NULL;
3282 gchar *ac_email = NULL;
3284 gboolean reply_to_ml = FALSE;
3285 gboolean default_reply_to = FALSE;
3287 cm_return_if_fail(compose->account != NULL);
3288 cm_return_if_fail(msginfo != NULL);
3290 reply_to_ml = to_ml && compose->ml_post;
3292 default_reply_to = msginfo->folder &&
3293 msginfo->folder->prefs->enable_default_reply_to;
3295 if (compose->account->protocol != A_NNTP) {
3296 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3298 if (reply_to_ml && !default_reply_to) {
3300 gboolean is_subscr = is_subscription(compose->ml_post,
3301 msginfo->from);
3302 if (!is_subscr) {
3303 /* normal answer to ml post with a reply-to */
3304 compose_entry_append(compose,
3305 compose->ml_post,
3306 COMPOSE_TO, PREF_ML);
3307 if (compose->replyto)
3308 compose_entry_append(compose,
3309 compose->replyto,
3310 COMPOSE_CC, PREF_ML);
3311 } else {
3312 /* answer to subscription confirmation */
3313 if (compose->replyto)
3314 compose_entry_append(compose,
3315 compose->replyto,
3316 COMPOSE_TO, PREF_ML);
3317 else if (msginfo->from)
3318 compose_entry_append(compose,
3319 msginfo->from,
3320 COMPOSE_TO, PREF_ML);
3323 else if (!(to_all || to_sender) && default_reply_to) {
3324 compose_entry_append(compose,
3325 msginfo->folder->prefs->default_reply_to,
3326 COMPOSE_TO, PREF_FOLDER);
3327 compose_entry_mark_default_to(compose,
3328 msginfo->folder->prefs->default_reply_to);
3329 } else {
3330 gchar *tmp1 = NULL;
3331 if (!msginfo->from)
3332 return;
3333 if (to_sender)
3334 compose_entry_append(compose, msginfo->from,
3335 COMPOSE_TO, PREF_NONE);
3336 else if (to_all) {
3337 Xstrdup_a(tmp1, msginfo->from, return);
3338 extract_address(tmp1);
3339 compose_entry_append(compose,
3340 (!account_find_from_address(tmp1, FALSE))
3341 ? msginfo->from :
3342 msginfo->to,
3343 COMPOSE_TO, PREF_NONE);
3344 if (compose->replyto)
3345 compose_entry_append(compose,
3346 compose->replyto,
3347 COMPOSE_CC, PREF_NONE);
3348 } else {
3349 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3350 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3351 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3352 if (compose->replyto) {
3353 compose_entry_append(compose,
3354 compose->replyto,
3355 COMPOSE_TO, PREF_NONE);
3356 } else {
3357 compose_entry_append(compose,
3358 msginfo->from ? msginfo->from : "",
3359 COMPOSE_TO, PREF_NONE);
3361 } else {
3362 /* replying to own mail, use original recp */
3363 compose_entry_append(compose,
3364 msginfo->to ? msginfo->to : "",
3365 COMPOSE_TO, PREF_NONE);
3366 compose_entry_append(compose,
3367 msginfo->cc ? msginfo->cc : "",
3368 COMPOSE_CC, PREF_NONE);
3372 } else {
3373 if (to_sender || (compose->followup_to &&
3374 !strncmp(compose->followup_to, "poster", 6)))
3375 compose_entry_append
3376 (compose,
3377 (compose->replyto ? compose->replyto :
3378 msginfo->from ? msginfo->from : ""),
3379 COMPOSE_TO, PREF_NONE);
3381 else if (followup_and_reply_to || to_all) {
3382 compose_entry_append
3383 (compose,
3384 (compose->replyto ? compose->replyto :
3385 msginfo->from ? msginfo->from : ""),
3386 COMPOSE_TO, PREF_NONE);
3388 compose_entry_append
3389 (compose,
3390 compose->followup_to ? compose->followup_to :
3391 compose->newsgroups ? compose->newsgroups : "",
3392 COMPOSE_NEWSGROUPS, PREF_NONE);
3394 compose_entry_append
3395 (compose,
3396 msginfo->cc ? msginfo->cc : "",
3397 COMPOSE_CC, PREF_NONE);
3399 else
3400 compose_entry_append
3401 (compose,
3402 compose->followup_to ? compose->followup_to :
3403 compose->newsgroups ? compose->newsgroups : "",
3404 COMPOSE_NEWSGROUPS, PREF_NONE);
3406 compose_reply_set_subject(compose, msginfo);
3408 if (to_ml && compose->ml_post) return;
3409 if (!to_all || compose->account->protocol == A_NNTP) return;
3411 if (compose->replyto) {
3412 Xstrdup_a(replyto, compose->replyto, return);
3413 extract_address(replyto);
3415 if (msginfo->from) {
3416 Xstrdup_a(from, msginfo->from, return);
3417 extract_address(from);
3420 if (replyto && from)
3421 cc_list = address_list_append_with_comments(cc_list, from);
3422 if (to_all && msginfo->folder &&
3423 msginfo->folder->prefs->enable_default_reply_to)
3424 cc_list = address_list_append_with_comments(cc_list,
3425 msginfo->folder->prefs->default_reply_to);
3426 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3427 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3429 ac_email = g_utf8_strdown(compose->account->address, -1);
3431 if (cc_list) {
3432 for (cur = cc_list; cur != NULL; cur = cur->next) {
3433 gchar *addr = g_utf8_strdown(cur->data, -1);
3434 extract_address(addr);
3436 if (strcmp(ac_email, addr))
3437 compose_entry_append(compose, (gchar *)cur->data,
3438 COMPOSE_CC, PREF_NONE);
3439 else
3440 debug_print("Cc address same as compose account's, ignoring\n");
3442 g_free(addr);
3445 slist_free_strings_full(cc_list);
3448 g_free(ac_email);
3451 #define SET_ENTRY(entry, str) \
3453 if (str && *str) \
3454 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3457 #define SET_ADDRESS(type, str) \
3459 if (str && *str) \
3460 compose_entry_append(compose, str, type, PREF_NONE); \
3463 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3465 cm_return_if_fail(msginfo != NULL);
3467 SET_ENTRY(subject_entry, msginfo->subject);
3468 SET_ENTRY(from_name, msginfo->from);
3469 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3470 SET_ADDRESS(COMPOSE_CC, compose->cc);
3471 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3472 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3473 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3474 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3476 compose_update_priority_menu_item(compose);
3477 compose_update_privacy_system_menu_item(compose, FALSE);
3478 compose_show_first_last_header(compose, TRUE);
3481 #undef SET_ENTRY
3482 #undef SET_ADDRESS
3484 static void compose_insert_sig(Compose *compose, gboolean replace)
3486 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3487 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3488 GtkTextMark *mark;
3489 GtkTextIter iter, iter_end;
3490 gint cur_pos, ins_pos;
3491 gboolean prev_autowrap;
3492 gboolean found = FALSE;
3493 gboolean exists = FALSE;
3495 cm_return_if_fail(compose->account != NULL);
3497 BLOCK_WRAP();
3499 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3500 G_CALLBACK(compose_changed_cb),
3501 compose);
3503 mark = gtk_text_buffer_get_insert(buffer);
3504 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3505 cur_pos = gtk_text_iter_get_offset (&iter);
3506 ins_pos = cur_pos;
3508 gtk_text_buffer_get_end_iter(buffer, &iter);
3510 exists = (compose->sig_str != NULL);
3512 if (replace) {
3513 GtkTextIter first_iter, start_iter, end_iter;
3515 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3517 if (!exists || compose->sig_str[0] == '\0')
3518 found = FALSE;
3519 else
3520 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3521 compose->signature_tag);
3523 if (found) {
3524 /* include previous \n\n */
3525 gtk_text_iter_backward_chars(&first_iter, 1);
3526 start_iter = first_iter;
3527 end_iter = first_iter;
3528 /* skip re-start */
3529 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3530 compose->signature_tag);
3531 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3532 compose->signature_tag);
3533 if (found) {
3534 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3535 iter = start_iter;
3540 g_free(compose->sig_str);
3541 compose->sig_str = account_get_signature_str(compose->account);
3543 cur_pos = gtk_text_iter_get_offset(&iter);
3545 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3546 g_free(compose->sig_str);
3547 compose->sig_str = NULL;
3548 } else {
3549 if (compose->sig_inserted == FALSE)
3550 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3551 compose->sig_inserted = TRUE;
3553 cur_pos = gtk_text_iter_get_offset(&iter);
3554 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3555 /* remove \n\n */
3556 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3557 gtk_text_iter_forward_chars(&iter, 1);
3558 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3559 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3561 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3562 cur_pos = gtk_text_buffer_get_char_count (buffer);
3565 /* put the cursor where it should be
3566 * either where the quote_fmt says, either where it was */
3567 if (compose->set_cursor_pos < 0)
3568 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3569 else
3570 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3571 compose->set_cursor_pos);
3573 compose->set_cursor_pos = -1;
3574 gtk_text_buffer_place_cursor(buffer, &iter);
3575 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3576 G_CALLBACK(compose_changed_cb),
3577 compose);
3579 UNBLOCK_WRAP();
3582 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3584 GtkTextView *text;
3585 GtkTextBuffer *buffer;
3586 GtkTextMark *mark;
3587 GtkTextIter iter;
3588 const gchar *cur_encoding;
3589 gchar buf[BUFFSIZE];
3590 gint len;
3591 FILE *fp;
3592 gboolean prev_autowrap;
3593 GStatBuf file_stat;
3594 int ret;
3595 GString *file_contents = NULL;
3596 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3598 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3600 /* get the size of the file we are about to insert */
3601 ret = g_stat(file, &file_stat);
3602 if (ret != 0) {
3603 gchar *shortfile = g_path_get_basename(file);
3604 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3605 g_free(shortfile);
3606 return COMPOSE_INSERT_NO_FILE;
3607 } else if (prefs_common.warn_large_insert == TRUE) {
3609 /* ask user for confirmation if the file is large */
3610 if (prefs_common.warn_large_insert_size < 0 ||
3611 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3612 AlertValue aval;
3613 gchar *msg;
3615 msg = g_strdup_printf(_("You are about to insert a file of %s "
3616 "in the message body. Are you sure you want to do that?"),
3617 to_human_readable(file_stat.st_size));
3618 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3619 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3620 g_free(msg);
3622 /* do we ask for confirmation next time? */
3623 if (aval & G_ALERTDISABLE) {
3624 /* no confirmation next time, disable feature in preferences */
3625 aval &= ~G_ALERTDISABLE;
3626 prefs_common.warn_large_insert = FALSE;
3629 /* abort file insertion if user canceled action */
3630 if (aval != G_ALERTALTERNATE) {
3631 return COMPOSE_INSERT_NO_FILE;
3637 if ((fp = g_fopen(file, "rb")) == NULL) {
3638 FILE_OP_ERROR(file, "fopen");
3639 return COMPOSE_INSERT_READ_ERROR;
3642 prev_autowrap = compose->autowrap;
3643 compose->autowrap = FALSE;
3645 text = GTK_TEXT_VIEW(compose->text);
3646 buffer = gtk_text_view_get_buffer(text);
3647 mark = gtk_text_buffer_get_insert(buffer);
3648 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3650 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3651 G_CALLBACK(text_inserted),
3652 compose);
3654 cur_encoding = conv_get_locale_charset_str_no_utf8();
3656 file_contents = g_string_new("");
3657 while (fgets(buf, sizeof(buf), fp) != NULL) {
3658 gchar *str;
3660 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3661 str = g_strdup(buf);
3662 else {
3663 codeconv_set_strict(TRUE);
3664 str = conv_codeset_strdup
3665 (buf, cur_encoding, CS_INTERNAL);
3666 codeconv_set_strict(FALSE);
3668 if (!str) {
3669 result = COMPOSE_INSERT_INVALID_CHARACTER;
3670 break;
3673 if (!str) continue;
3675 /* strip <CR> if DOS/Windows file,
3676 replace <CR> with <LF> if Macintosh file. */
3677 strcrchomp(str);
3678 len = strlen(str);
3679 if (len > 0 && str[len - 1] != '\n') {
3680 while (--len >= 0)
3681 if (str[len] == '\r') str[len] = '\n';
3684 file_contents = g_string_append(file_contents, str);
3685 g_free(str);
3688 if (result == COMPOSE_INSERT_SUCCESS) {
3689 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3691 compose_changed_cb(NULL, compose);
3692 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3693 G_CALLBACK(text_inserted),
3694 compose);
3695 compose->autowrap = prev_autowrap;
3696 if (compose->autowrap)
3697 compose_wrap_all(compose);
3700 g_string_free(file_contents, TRUE);
3701 fclose(fp);
3703 return result;
3706 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3707 const gchar *filename,
3708 const gchar *content_type,
3709 const gchar *charset)
3711 AttachInfo *ainfo;
3712 GtkTreeIter iter;
3713 FILE *fp;
3714 off_t size;
3715 GAuto *auto_ainfo;
3716 gchar *size_text;
3717 GtkListStore *store;
3718 gchar *name;
3719 gboolean has_binary = FALSE;
3721 if (!is_file_exist(file)) {
3722 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3723 gboolean result = FALSE;
3724 if (file_from_uri && is_file_exist(file_from_uri)) {
3725 result = compose_attach_append(
3726 compose, file_from_uri,
3727 filename, content_type,
3728 charset);
3730 g_free(file_from_uri);
3731 if (result)
3732 return TRUE;
3733 alertpanel_error("File %s doesn't exist\n", filename);
3734 return FALSE;
3736 if ((size = get_file_size(file)) < 0) {
3737 alertpanel_error("Can't get file size of %s\n", filename);
3738 return FALSE;
3741 /* In batch mode, we allow 0-length files to be attached no questions asked */
3742 if (size == 0 && !compose->batch) {
3743 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3744 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3745 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3746 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3747 g_free(msg);
3749 if (aval != G_ALERTALTERNATE) {
3750 return FALSE;
3753 if ((fp = g_fopen(file, "rb")) == NULL) {
3754 alertpanel_error(_("Can't read %s."), filename);
3755 return FALSE;
3757 fclose(fp);
3759 ainfo = g_new0(AttachInfo, 1);
3760 auto_ainfo = g_auto_pointer_new_with_free
3761 (ainfo, (GFreeFunc) compose_attach_info_free);
3762 ainfo->file = g_strdup(file);
3764 if (content_type) {
3765 ainfo->content_type = g_strdup(content_type);
3766 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3767 MsgInfo *msginfo;
3768 MsgFlags flags = {0, 0};
3770 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3771 ainfo->encoding = ENC_7BIT;
3772 else
3773 ainfo->encoding = ENC_8BIT;
3775 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3776 if (msginfo && msginfo->subject)
3777 name = g_strdup(msginfo->subject);
3778 else
3779 name = g_path_get_basename(filename ? filename : file);
3781 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3783 procmsg_msginfo_free(&msginfo);
3784 } else {
3785 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3786 ainfo->charset = g_strdup(charset);
3787 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3788 } else {
3789 ainfo->encoding = ENC_BASE64;
3791 name = g_path_get_basename(filename ? filename : file);
3792 ainfo->name = g_strdup(name);
3794 g_free(name);
3795 } else {
3796 ainfo->content_type = procmime_get_mime_type(file);
3797 if (!ainfo->content_type) {
3798 ainfo->content_type =
3799 g_strdup("application/octet-stream");
3800 ainfo->encoding = ENC_BASE64;
3801 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3802 ainfo->encoding =
3803 procmime_get_encoding_for_text_file(file, &has_binary);
3804 else
3805 ainfo->encoding = ENC_BASE64;
3806 name = g_path_get_basename(filename ? filename : file);
3807 ainfo->name = g_strdup(name);
3808 g_free(name);
3811 if (ainfo->name != NULL
3812 && !strcmp(ainfo->name, ".")) {
3813 g_free(ainfo->name);
3814 ainfo->name = NULL;
3817 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3818 g_free(ainfo->content_type);
3819 ainfo->content_type = g_strdup("application/octet-stream");
3820 g_free(ainfo->charset);
3821 ainfo->charset = NULL;
3824 ainfo->size = (goffset)size;
3825 size_text = to_human_readable((goffset)size);
3827 store = GTK_LIST_STORE(gtk_tree_view_get_model
3828 (GTK_TREE_VIEW(compose->attach_clist)));
3830 gtk_list_store_append(store, &iter);
3831 gtk_list_store_set(store, &iter,
3832 COL_MIMETYPE, ainfo->content_type,
3833 COL_SIZE, size_text,
3834 COL_NAME, ainfo->name,
3835 COL_CHARSET, ainfo->charset,
3836 COL_DATA, ainfo,
3837 COL_AUTODATA, auto_ainfo,
3838 -1);
3840 g_auto_pointer_free(auto_ainfo);
3841 compose_attach_update_label(compose);
3842 return TRUE;
3845 static void compose_use_signing(Compose *compose, gboolean use_signing)
3847 compose->use_signing = use_signing;
3848 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3851 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3853 compose->use_encryption = use_encryption;
3854 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3857 #define NEXT_PART_NOT_CHILD(info) \
3859 node = info->node; \
3860 while (node->children) \
3861 node = g_node_last_child(node); \
3862 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3865 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3867 MimeInfo *mimeinfo;
3868 MimeInfo *child;
3869 MimeInfo *firsttext = NULL;
3870 MimeInfo *encrypted = NULL;
3871 GNode *node;
3872 gchar *outfile;
3873 const gchar *partname = NULL;
3875 mimeinfo = procmime_scan_message(msginfo);
3876 if (!mimeinfo) return;
3878 if (mimeinfo->node->children == NULL) {
3879 procmime_mimeinfo_free_all(&mimeinfo);
3880 return;
3883 /* find first content part */
3884 child = (MimeInfo *) mimeinfo->node->children->data;
3885 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3886 child = (MimeInfo *)child->node->children->data;
3888 if (child) {
3889 if (child->type == MIMETYPE_TEXT) {
3890 firsttext = child;
3891 debug_print("First text part found\n");
3892 } else if (compose->mode == COMPOSE_REEDIT &&
3893 child->type == MIMETYPE_APPLICATION &&
3894 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3895 encrypted = (MimeInfo *)child->node->parent->data;
3898 child = (MimeInfo *) mimeinfo->node->children->data;
3899 while (child != NULL) {
3900 gint err;
3902 if (child == encrypted) {
3903 /* skip this part of tree */
3904 NEXT_PART_NOT_CHILD(child);
3905 continue;
3908 if (child->type == MIMETYPE_MULTIPART) {
3909 /* get the actual content */
3910 child = procmime_mimeinfo_next(child);
3911 continue;
3914 if (child == firsttext) {
3915 child = procmime_mimeinfo_next(child);
3916 continue;
3919 outfile = procmime_get_tmp_file_name(child);
3920 if ((err = procmime_get_part(outfile, child)) < 0)
3921 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3922 else {
3923 gchar *content_type;
3925 content_type = procmime_get_content_type_str(child->type, child->subtype);
3927 /* if we meet a pgp signature, we don't attach it, but
3928 * we force signing. */
3929 if ((strcmp(content_type, "application/pgp-signature") &&
3930 strcmp(content_type, "application/pkcs7-signature") &&
3931 strcmp(content_type, "application/x-pkcs7-signature"))
3932 || compose->mode == COMPOSE_REDIRECT) {
3933 partname = procmime_mimeinfo_get_parameter(child, "filename");
3934 if (partname == NULL)
3935 partname = procmime_mimeinfo_get_parameter(child, "name");
3936 if (partname == NULL)
3937 partname = "";
3938 compose_attach_append(compose, outfile,
3939 partname, content_type,
3940 procmime_mimeinfo_get_parameter(child, "charset"));
3941 } else {
3942 compose_force_signing(compose, compose->account, NULL);
3944 g_free(content_type);
3946 g_free(outfile);
3947 NEXT_PART_NOT_CHILD(child);
3949 procmime_mimeinfo_free_all(&mimeinfo);
3952 #undef NEXT_PART_NOT_CHILD
3956 typedef enum {
3957 WAIT_FOR_INDENT_CHAR,
3958 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3959 } IndentState;
3961 /* return indent length, we allow:
3962 indent characters followed by indent characters or spaces/tabs,
3963 alphabets and numbers immediately followed by indent characters,
3964 and the repeating sequences of the above
3965 If quote ends with multiple spaces, only the first one is included. */
3966 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3967 const GtkTextIter *start, gint *len)
3969 GtkTextIter iter = *start;
3970 gunichar wc;
3971 gchar ch[6];
3972 gint clen;
3973 IndentState state = WAIT_FOR_INDENT_CHAR;
3974 gboolean is_space;
3975 gboolean is_indent;
3976 gint alnum_count = 0;
3977 gint space_count = 0;
3978 gint quote_len = 0;
3980 if (prefs_common.quote_chars == NULL) {
3981 return 0 ;
3984 while (!gtk_text_iter_ends_line(&iter)) {
3985 wc = gtk_text_iter_get_char(&iter);
3986 if (g_unichar_iswide(wc))
3987 break;
3988 clen = g_unichar_to_utf8(wc, ch);
3989 if (clen != 1)
3990 break;
3992 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3993 is_space = g_unichar_isspace(wc);
3995 if (state == WAIT_FOR_INDENT_CHAR) {
3996 if (!is_indent && !g_unichar_isalnum(wc))
3997 break;
3998 if (is_indent) {
3999 quote_len += alnum_count + space_count + 1;
4000 alnum_count = space_count = 0;
4001 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4002 } else
4003 alnum_count++;
4004 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4005 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4006 break;
4007 if (is_space)
4008 space_count++;
4009 else if (is_indent) {
4010 quote_len += alnum_count + space_count + 1;
4011 alnum_count = space_count = 0;
4012 } else {
4013 alnum_count++;
4014 state = WAIT_FOR_INDENT_CHAR;
4018 gtk_text_iter_forward_char(&iter);
4021 if (quote_len > 0 && space_count > 0)
4022 quote_len++;
4024 if (len)
4025 *len = quote_len;
4027 if (quote_len > 0) {
4028 iter = *start;
4029 gtk_text_iter_forward_chars(&iter, quote_len);
4030 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4033 return NULL;
4036 /* return >0 if the line is itemized */
4037 static int compose_itemized_length(GtkTextBuffer *buffer,
4038 const GtkTextIter *start)
4040 GtkTextIter iter = *start;
4041 gunichar wc;
4042 gchar ch[6];
4043 gint clen;
4044 gint len = 0;
4045 if (gtk_text_iter_ends_line(&iter))
4046 return 0;
4048 while (1) {
4049 len++;
4050 wc = gtk_text_iter_get_char(&iter);
4051 if (!g_unichar_isspace(wc))
4052 break;
4053 gtk_text_iter_forward_char(&iter);
4054 if (gtk_text_iter_ends_line(&iter))
4055 return 0;
4058 clen = g_unichar_to_utf8(wc, ch);
4059 if (clen != 1)
4060 return 0;
4062 if (!strchr("*-+", ch[0]))
4063 return 0;
4065 gtk_text_iter_forward_char(&iter);
4066 if (gtk_text_iter_ends_line(&iter))
4067 return 0;
4068 wc = gtk_text_iter_get_char(&iter);
4069 if (g_unichar_isspace(wc)) {
4070 return len+1;
4072 return 0;
4075 /* return the string at the start of the itemization */
4076 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4077 const GtkTextIter *start)
4079 GtkTextIter iter = *start;
4080 gunichar wc;
4081 gint len = 0;
4082 GString *item_chars = g_string_new("");
4083 gchar *str = NULL;
4085 if (gtk_text_iter_ends_line(&iter))
4086 return NULL;
4088 while (1) {
4089 len++;
4090 wc = gtk_text_iter_get_char(&iter);
4091 if (!g_unichar_isspace(wc))
4092 break;
4093 gtk_text_iter_forward_char(&iter);
4094 if (gtk_text_iter_ends_line(&iter))
4095 break;
4096 g_string_append_unichar(item_chars, wc);
4099 str = item_chars->str;
4100 g_string_free(item_chars, FALSE);
4101 return str;
4104 /* return the number of spaces at a line's start */
4105 static int compose_left_offset_length(GtkTextBuffer *buffer,
4106 const GtkTextIter *start)
4108 GtkTextIter iter = *start;
4109 gunichar wc;
4110 gint len = 0;
4111 if (gtk_text_iter_ends_line(&iter))
4112 return 0;
4114 while (1) {
4115 wc = gtk_text_iter_get_char(&iter);
4116 if (!g_unichar_isspace(wc))
4117 break;
4118 len++;
4119 gtk_text_iter_forward_char(&iter);
4120 if (gtk_text_iter_ends_line(&iter))
4121 return 0;
4124 gtk_text_iter_forward_char(&iter);
4125 if (gtk_text_iter_ends_line(&iter))
4126 return 0;
4127 return len;
4130 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4131 const GtkTextIter *start,
4132 GtkTextIter *break_pos,
4133 gint max_col,
4134 gint quote_len)
4136 GtkTextIter iter = *start, line_end = *start;
4137 PangoLogAttr *attrs;
4138 gchar *str;
4139 gchar *p;
4140 gint len;
4141 gint i;
4142 gint col = 0;
4143 gint pos = 0;
4144 gboolean can_break = FALSE;
4145 gboolean do_break = FALSE;
4146 gboolean was_white = FALSE;
4147 gboolean prev_dont_break = FALSE;
4149 gtk_text_iter_forward_to_line_end(&line_end);
4150 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4151 len = g_utf8_strlen(str, -1);
4153 if (len == 0) {
4154 g_free(str);
4155 g_warning("compose_get_line_break_pos: len = 0!");
4156 return FALSE;
4159 /* g_print("breaking line: %d: %s (len = %d)\n",
4160 gtk_text_iter_get_line(&iter), str, len); */
4162 attrs = g_new(PangoLogAttr, len + 1);
4164 pango_default_break(str, -1, NULL, attrs, len + 1);
4166 p = str;
4168 /* skip quote and leading spaces */
4169 for (i = 0; *p != '\0' && i < len; i++) {
4170 gunichar wc;
4172 wc = g_utf8_get_char(p);
4173 if (i >= quote_len && !g_unichar_isspace(wc))
4174 break;
4175 if (g_unichar_iswide(wc))
4176 col += 2;
4177 else if (*p == '\t')
4178 col += 8;
4179 else
4180 col++;
4181 p = g_utf8_next_char(p);
4184 for (; *p != '\0' && i < len; i++) {
4185 PangoLogAttr *attr = attrs + i;
4186 gunichar wc;
4187 gint uri_len;
4189 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4190 pos = i;
4192 was_white = attr->is_white;
4194 /* don't wrap URI */
4195 if ((uri_len = get_uri_len(p)) > 0) {
4196 col += uri_len;
4197 if (pos > 0 && col > max_col) {
4198 do_break = TRUE;
4199 break;
4201 i += uri_len - 1;
4202 p += uri_len;
4203 can_break = TRUE;
4204 continue;
4207 wc = g_utf8_get_char(p);
4208 if (g_unichar_iswide(wc)) {
4209 col += 2;
4210 if (prev_dont_break && can_break && attr->is_line_break)
4211 pos = i;
4212 } else if (*p == '\t')
4213 col += 8;
4214 else
4215 col++;
4216 if (pos > 0 && col > max_col) {
4217 do_break = TRUE;
4218 break;
4221 if (*p == '-' || *p == '/')
4222 prev_dont_break = TRUE;
4223 else
4224 prev_dont_break = FALSE;
4226 p = g_utf8_next_char(p);
4227 can_break = TRUE;
4230 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4232 g_free(attrs);
4233 g_free(str);
4235 *break_pos = *start;
4236 gtk_text_iter_set_line_offset(break_pos, pos);
4238 return do_break;
4241 static gboolean compose_join_next_line(Compose *compose,
4242 GtkTextBuffer *buffer,
4243 GtkTextIter *iter,
4244 const gchar *quote_str)
4246 GtkTextIter iter_ = *iter, cur, prev, next, end;
4247 PangoLogAttr attrs[3];
4248 gchar *str;
4249 gchar *next_quote_str;
4250 gunichar wc1, wc2;
4251 gint quote_len;
4252 gboolean keep_cursor = FALSE;
4254 if (!gtk_text_iter_forward_line(&iter_) ||
4255 gtk_text_iter_ends_line(&iter_)) {
4256 return FALSE;
4258 next_quote_str = compose_get_quote_str(buffer, &iter_, &quote_len);
4260 if ((quote_str || next_quote_str) &&
4261 strcmp2(quote_str, next_quote_str) != 0) {
4262 g_free(next_quote_str);
4263 return FALSE;
4265 g_free(next_quote_str);
4267 end = iter_;
4268 if (quote_len > 0) {
4269 gtk_text_iter_forward_chars(&end, quote_len);
4270 if (gtk_text_iter_ends_line(&end)) {
4271 return FALSE;
4275 /* don't join itemized lines */
4276 if (compose_itemized_length(buffer, &end) > 0) {
4277 return FALSE;
4280 /* don't join signature separator */
4281 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4282 return FALSE;
4284 /* delete quote str */
4285 if (quote_len > 0)
4286 gtk_text_buffer_delete(buffer, &iter_, &end);
4288 /* don't join line breaks put by the user */
4289 prev = cur = iter_;
4290 gtk_text_iter_backward_char(&cur);
4291 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4292 gtk_text_iter_forward_char(&cur);
4293 *iter = cur;
4294 return FALSE;
4296 gtk_text_iter_forward_char(&cur);
4297 /* delete linebreak and extra spaces */
4298 while (gtk_text_iter_backward_char(&cur)) {
4299 wc1 = gtk_text_iter_get_char(&cur);
4300 if (!g_unichar_isspace(wc1))
4301 break;
4302 prev = cur;
4304 next = cur = iter_;
4305 while (!gtk_text_iter_ends_line(&cur)) {
4306 wc1 = gtk_text_iter_get_char(&cur);
4307 if (!g_unichar_isspace(wc1))
4308 break;
4309 gtk_text_iter_forward_char(&cur);
4310 next = cur;
4312 if (!gtk_text_iter_equal(&prev, &next)) {
4313 GtkTextMark *mark;
4315 mark = gtk_text_buffer_get_insert(buffer);
4316 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4317 if (gtk_text_iter_equal(&prev, &cur))
4318 keep_cursor = TRUE;
4319 gtk_text_buffer_delete(buffer, &prev, &next);
4321 iter_ = prev;
4323 /* insert space if required */
4324 gtk_text_iter_backward_char(&prev);
4325 wc1 = gtk_text_iter_get_char(&prev);
4326 wc2 = gtk_text_iter_get_char(&next);
4327 gtk_text_iter_forward_char(&next);
4328 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4329 pango_default_break(str, -1, NULL, attrs, 3);
4330 if (!attrs[1].is_line_break ||
4331 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4332 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4333 if (keep_cursor) {
4334 gtk_text_iter_backward_char(&iter_);
4335 gtk_text_buffer_place_cursor(buffer, &iter_);
4338 g_free(str);
4340 *iter = iter_;
4341 return TRUE;
4344 #define ADD_TXT_POS(bp_, ep_, pti_) \
4345 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4346 last = last->next; \
4347 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4348 last->next = NULL; \
4349 } else { \
4350 g_warning("alloc error scanning URIs"); \
4353 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4355 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4356 GtkTextBuffer *buffer;
4357 GtkTextIter iter, break_pos, end_of_line;
4358 gchar *quote_str = NULL;
4359 gint quote_len;
4360 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4361 gboolean prev_autowrap = compose->autowrap;
4362 gint startq_offset = -1, noq_offset = -1;
4363 gint uri_start = -1, uri_stop = -1;
4364 gint nouri_start = -1, nouri_stop = -1;
4365 gint num_blocks = 0;
4366 gint quotelevel = -1;
4367 gboolean modified = force;
4368 gboolean removed = FALSE;
4369 gboolean modified_before_remove = FALSE;
4370 gint lines = 0;
4371 gboolean start = TRUE;
4372 gint itemized_len = 0, rem_item_len = 0;
4373 gchar *itemized_chars = NULL;
4374 gboolean item_continuation = FALSE;
4376 if (force) {
4377 modified = TRUE;
4379 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4380 modified = TRUE;
4383 compose->autowrap = FALSE;
4385 buffer = gtk_text_view_get_buffer(text);
4386 undo_wrapping(compose->undostruct, TRUE);
4387 if (par_iter) {
4388 iter = *par_iter;
4389 } else {
4390 GtkTextMark *mark;
4391 mark = gtk_text_buffer_get_insert(buffer);
4392 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4396 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4397 if (gtk_text_iter_ends_line(&iter)) {
4398 while (gtk_text_iter_ends_line(&iter) &&
4399 gtk_text_iter_forward_line(&iter))
4401 } else {
4402 while (gtk_text_iter_backward_line(&iter)) {
4403 if (gtk_text_iter_ends_line(&iter)) {
4404 gtk_text_iter_forward_line(&iter);
4405 break;
4409 } else {
4410 /* move to line start */
4411 gtk_text_iter_set_line_offset(&iter, 0);
4414 itemized_len = compose_itemized_length(buffer, &iter);
4416 if (!itemized_len) {
4417 itemized_len = compose_left_offset_length(buffer, &iter);
4418 item_continuation = TRUE;
4421 if (itemized_len)
4422 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4424 /* go until paragraph end (empty line) */
4425 while (start || !gtk_text_iter_ends_line(&iter)) {
4426 gchar *scanpos = NULL;
4427 /* parse table - in order of priority */
4428 struct table {
4429 const gchar *needle; /* token */
4431 /* token search function */
4432 gchar *(*search) (const gchar *haystack,
4433 const gchar *needle);
4434 /* part parsing function */
4435 gboolean (*parse) (const gchar *start,
4436 const gchar *scanpos,
4437 const gchar **bp_,
4438 const gchar **ep_,
4439 gboolean hdr);
4440 /* part to URI function */
4441 gchar *(*build_uri) (const gchar *bp,
4442 const gchar *ep);
4445 static struct table parser[] = {
4446 {"http://", strcasestr, get_uri_part, make_uri_string},
4447 {"https://", strcasestr, get_uri_part, make_uri_string},
4448 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4449 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4450 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4451 {"www.", strcasestr, get_uri_part, make_http_string},
4452 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4453 {"@", strcasestr, get_email_part, make_email_string}
4455 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4456 gint last_index = PARSE_ELEMS;
4457 gint n;
4458 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4459 gint walk_pos;
4461 start = FALSE;
4462 if (!prev_autowrap && num_blocks == 0) {
4463 num_blocks++;
4464 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4465 G_CALLBACK(text_inserted),
4466 compose);
4468 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4469 goto colorize;
4471 uri_start = uri_stop = -1;
4472 quote_len = 0;
4473 quote_str = compose_get_quote_str(buffer, &iter, &quote_len);
4475 if (quote_str) {
4476 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4477 if (startq_offset == -1)
4478 startq_offset = gtk_text_iter_get_offset(&iter);
4479 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4480 if (quotelevel > 2) {
4481 /* recycle colors */
4482 if (prefs_common.recycle_quote_colors)
4483 quotelevel %= 3;
4484 else
4485 quotelevel = 2;
4487 if (!wrap_quote) {
4488 goto colorize;
4490 } else {
4491 if (startq_offset == -1)
4492 noq_offset = gtk_text_iter_get_offset(&iter);
4493 quotelevel = -1;
4496 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4497 goto colorize;
4499 if (gtk_text_iter_ends_line(&iter)) {
4500 goto colorize;
4501 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4502 prefs_common.linewrap_len,
4503 quote_len)) {
4504 GtkTextIter prev, next, cur;
4505 if (prev_autowrap != FALSE || force) {
4506 compose->automatic_break = TRUE;
4507 modified = TRUE;
4508 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4509 compose->automatic_break = FALSE;
4510 if (itemized_len && compose->autoindent) {
4511 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4512 if (!item_continuation)
4513 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4515 } else if (quote_str && wrap_quote) {
4516 compose->automatic_break = TRUE;
4517 modified = TRUE;
4518 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4519 compose->automatic_break = FALSE;
4520 if (itemized_len && compose->autoindent) {
4521 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4522 if (!item_continuation)
4523 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4525 } else
4526 goto colorize;
4527 /* remove trailing spaces */
4528 cur = break_pos;
4529 rem_item_len = itemized_len;
4530 while (compose->autoindent && rem_item_len-- > 0)
4531 gtk_text_iter_backward_char(&cur);
4532 gtk_text_iter_backward_char(&cur);
4534 prev = next = cur;
4535 while (!gtk_text_iter_starts_line(&cur)) {
4536 gunichar wc;
4538 gtk_text_iter_backward_char(&cur);
4539 wc = gtk_text_iter_get_char(&cur);
4540 if (!g_unichar_isspace(wc))
4541 break;
4542 prev = cur;
4544 if (!gtk_text_iter_equal(&prev, &next)) {
4545 gtk_text_buffer_delete(buffer, &prev, &next);
4546 break_pos = next;
4547 gtk_text_iter_forward_char(&break_pos);
4550 if (quote_str)
4551 gtk_text_buffer_insert(buffer, &break_pos,
4552 quote_str, -1);
4554 iter = break_pos;
4555 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4557 /* move iter to current line start */
4558 gtk_text_iter_set_line_offset(&iter, 0);
4559 if (quote_str) {
4560 g_free(quote_str);
4561 quote_str = NULL;
4563 continue;
4564 } else {
4565 /* move iter to next line start */
4566 iter = break_pos;
4567 lines++;
4570 colorize:
4571 if (!prev_autowrap && num_blocks > 0) {
4572 num_blocks--;
4573 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4574 G_CALLBACK(text_inserted),
4575 compose);
4577 end_of_line = iter;
4578 while (!gtk_text_iter_ends_line(&end_of_line)) {
4579 gtk_text_iter_forward_char(&end_of_line);
4581 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4583 nouri_start = gtk_text_iter_get_offset(&iter);
4584 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4586 walk_pos = gtk_text_iter_get_offset(&iter);
4587 /* FIXME: this looks phony. scanning for anything in the parse table */
4588 for (n = 0; n < PARSE_ELEMS; n++) {
4589 gchar *tmp;
4591 tmp = parser[n].search(walk, parser[n].needle);
4592 if (tmp) {
4593 if (scanpos == NULL || tmp < scanpos) {
4594 scanpos = tmp;
4595 last_index = n;
4600 bp = ep = 0;
4601 if (scanpos) {
4602 /* check if URI can be parsed */
4603 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4604 (const gchar **)&ep, FALSE)
4605 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4606 walk = ep;
4607 } else
4608 walk = scanpos +
4609 strlen(parser[last_index].needle);
4611 if (bp && ep) {
4612 uri_start = walk_pos + (bp - o_walk);
4613 uri_stop = walk_pos + (ep - o_walk);
4615 g_free(o_walk);
4616 o_walk = NULL;
4617 gtk_text_iter_forward_line(&iter);
4618 g_free(quote_str);
4619 quote_str = NULL;
4620 if (startq_offset != -1) {
4621 GtkTextIter startquote, endquote;
4622 gtk_text_buffer_get_iter_at_offset(
4623 buffer, &startquote, startq_offset);
4624 endquote = iter;
4626 switch (quotelevel) {
4627 case 0:
4628 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4629 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4630 gtk_text_buffer_apply_tag_by_name(
4631 buffer, "quote0", &startquote, &endquote);
4632 gtk_text_buffer_remove_tag_by_name(
4633 buffer, "quote1", &startquote, &endquote);
4634 gtk_text_buffer_remove_tag_by_name(
4635 buffer, "quote2", &startquote, &endquote);
4636 modified = TRUE;
4638 break;
4639 case 1:
4640 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4641 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4642 gtk_text_buffer_apply_tag_by_name(
4643 buffer, "quote1", &startquote, &endquote);
4644 gtk_text_buffer_remove_tag_by_name(
4645 buffer, "quote0", &startquote, &endquote);
4646 gtk_text_buffer_remove_tag_by_name(
4647 buffer, "quote2", &startquote, &endquote);
4648 modified = TRUE;
4650 break;
4651 case 2:
4652 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4653 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4654 gtk_text_buffer_apply_tag_by_name(
4655 buffer, "quote2", &startquote, &endquote);
4656 gtk_text_buffer_remove_tag_by_name(
4657 buffer, "quote0", &startquote, &endquote);
4658 gtk_text_buffer_remove_tag_by_name(
4659 buffer, "quote1", &startquote, &endquote);
4660 modified = TRUE;
4662 break;
4664 startq_offset = -1;
4665 } else if (noq_offset != -1) {
4666 GtkTextIter startnoquote, endnoquote;
4667 gtk_text_buffer_get_iter_at_offset(
4668 buffer, &startnoquote, noq_offset);
4669 endnoquote = iter;
4671 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4672 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4673 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4674 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4675 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4676 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4677 gtk_text_buffer_remove_tag_by_name(
4678 buffer, "quote0", &startnoquote, &endnoquote);
4679 gtk_text_buffer_remove_tag_by_name(
4680 buffer, "quote1", &startnoquote, &endnoquote);
4681 gtk_text_buffer_remove_tag_by_name(
4682 buffer, "quote2", &startnoquote, &endnoquote);
4683 modified = TRUE;
4685 noq_offset = -1;
4688 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4689 GtkTextIter nouri_start_iter, nouri_end_iter;
4690 gtk_text_buffer_get_iter_at_offset(
4691 buffer, &nouri_start_iter, nouri_start);
4692 gtk_text_buffer_get_iter_at_offset(
4693 buffer, &nouri_end_iter, nouri_stop);
4694 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4695 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4696 gtk_text_buffer_remove_tag_by_name(
4697 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4698 modified_before_remove = modified;
4699 modified = TRUE;
4700 removed = TRUE;
4703 if (uri_start >= 0 && uri_stop > 0) {
4704 GtkTextIter uri_start_iter, uri_end_iter, back;
4705 gtk_text_buffer_get_iter_at_offset(
4706 buffer, &uri_start_iter, uri_start);
4707 gtk_text_buffer_get_iter_at_offset(
4708 buffer, &uri_end_iter, uri_stop);
4709 back = uri_end_iter;
4710 gtk_text_iter_backward_char(&back);
4711 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4712 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4713 gtk_text_buffer_apply_tag_by_name(
4714 buffer, "link", &uri_start_iter, &uri_end_iter);
4715 modified = TRUE;
4716 if (removed && !modified_before_remove) {
4717 modified = FALSE;
4721 if (!modified) {
4722 // debug_print("not modified, out after %d lines\n", lines);
4723 goto end;
4726 // debug_print("modified, out after %d lines\n", lines);
4727 end:
4728 g_free(itemized_chars);
4729 if (par_iter)
4730 *par_iter = iter;
4731 undo_wrapping(compose->undostruct, FALSE);
4732 compose->autowrap = prev_autowrap;
4734 return modified;
4737 void compose_action_cb(void *data)
4739 Compose *compose = (Compose *)data;
4740 compose_wrap_all(compose);
4743 static void compose_wrap_all(Compose *compose)
4745 compose_wrap_all_full(compose, FALSE);
4748 static void compose_wrap_all_full(Compose *compose, gboolean force)
4750 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4751 GtkTextBuffer *buffer;
4752 GtkTextIter iter;
4753 gboolean modified = TRUE;
4755 buffer = gtk_text_view_get_buffer(text);
4757 gtk_text_buffer_get_start_iter(buffer, &iter);
4759 undo_wrapping(compose->undostruct, TRUE);
4761 while (!gtk_text_iter_is_end(&iter) && modified)
4762 modified = compose_beautify_paragraph(compose, &iter, force);
4764 undo_wrapping(compose->undostruct, FALSE);
4768 static void compose_set_title(Compose *compose)
4770 gchar *str;
4771 gchar *edited;
4772 gchar *subject;
4774 edited = compose->modified ? _(" [Edited]") : "";
4776 subject = gtk_editable_get_chars(
4777 GTK_EDITABLE(compose->subject_entry), 0, -1);
4779 #ifndef GENERIC_UMPC
4780 if (subject && strlen(subject))
4781 str = g_strdup_printf(_("%s - Compose message%s"),
4782 subject, edited);
4783 else
4784 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4785 #else
4786 str = g_strdup(_("Compose message"));
4787 #endif
4789 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4790 g_free(str);
4791 g_free(subject);
4795 * compose_current_mail_account:
4797 * Find a current mail account (the currently selected account, or the
4798 * default account, if a news account is currently selected). If a
4799 * mail account cannot be found, display an error message.
4801 * Return value: Mail account, or NULL if not found.
4803 static PrefsAccount *
4804 compose_current_mail_account(void)
4806 PrefsAccount *ac;
4808 if (cur_account && cur_account->protocol != A_NNTP)
4809 ac = cur_account;
4810 else {
4811 ac = account_get_default();
4812 if (!ac || ac->protocol == A_NNTP) {
4813 alertpanel_error(_("Account for sending mail is not specified.\n"
4814 "Please select a mail account before sending."));
4815 return NULL;
4818 return ac;
4821 #define QUOTE_IF_REQUIRED(out, str) \
4823 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4824 gchar *__tmp; \
4825 gint len; \
4827 len = strlen(str) + 3; \
4828 if ((__tmp = alloca(len)) == NULL) { \
4829 g_warning("can't allocate memory"); \
4830 g_string_free(header, TRUE); \
4831 return NULL; \
4833 g_snprintf(__tmp, len, "\"%s\"", str); \
4834 out = __tmp; \
4835 } else { \
4836 gchar *__tmp; \
4838 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4839 g_warning("can't allocate memory"); \
4840 g_string_free(header, TRUE); \
4841 return NULL; \
4842 } else \
4843 strcpy(__tmp, str); \
4845 out = __tmp; \
4849 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4851 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4852 gchar *__tmp; \
4853 gint len; \
4855 len = strlen(str) + 3; \
4856 if ((__tmp = alloca(len)) == NULL) { \
4857 g_warning("can't allocate memory"); \
4858 errret; \
4860 g_snprintf(__tmp, len, "\"%s\"", str); \
4861 out = __tmp; \
4862 } else { \
4863 gchar *__tmp; \
4865 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4866 g_warning("can't allocate memory"); \
4867 errret; \
4868 } else \
4869 strcpy(__tmp, str); \
4871 out = __tmp; \
4875 static void compose_select_account(Compose *compose, PrefsAccount *account,
4876 gboolean init)
4878 gchar *from = NULL, *header = NULL;
4879 ComposeHeaderEntry *header_entry;
4880 #if GTK_CHECK_VERSION(2, 24, 0)
4881 GtkTreeIter iter;
4882 #endif
4884 cm_return_if_fail(account != NULL);
4886 compose->account = account;
4887 if (account->name && *account->name) {
4888 gchar *buf, *qbuf;
4889 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4890 qbuf = escape_internal_quotes(buf, '"');
4891 from = g_strdup_printf("%s <%s>",
4892 qbuf, account->address);
4893 if (qbuf != buf)
4894 g_free(qbuf);
4895 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4896 } else {
4897 from = g_strdup_printf("<%s>",
4898 account->address);
4899 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4902 g_free(from);
4904 compose_set_title(compose);
4906 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4907 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4908 else
4909 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4910 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4911 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4912 else
4913 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4915 activate_privacy_system(compose, account, FALSE);
4917 if (!init && compose->mode != COMPOSE_REDIRECT) {
4918 undo_block(compose->undostruct);
4919 compose_insert_sig(compose, TRUE);
4920 undo_unblock(compose->undostruct);
4923 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4924 #if !GTK_CHECK_VERSION(2, 24, 0)
4925 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4926 #else
4927 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4928 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4929 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4930 #endif
4932 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4933 if (account->protocol == A_NNTP) {
4934 if (!strcmp(header, _("To:")))
4935 combobox_select_by_text(
4936 GTK_COMBO_BOX(header_entry->combo),
4937 _("Newsgroups:"));
4938 } else {
4939 if (!strcmp(header, _("Newsgroups:")))
4940 combobox_select_by_text(
4941 GTK_COMBO_BOX(header_entry->combo),
4942 _("To:"));
4946 g_free(header);
4948 #ifdef USE_ENCHANT
4949 /* use account's dict info if set */
4950 if (compose->gtkaspell) {
4951 if (account->enable_default_dictionary)
4952 gtkaspell_change_dict(compose->gtkaspell,
4953 account->default_dictionary, FALSE);
4954 if (account->enable_default_alt_dictionary)
4955 gtkaspell_change_alt_dict(compose->gtkaspell,
4956 account->default_alt_dictionary);
4957 if (account->enable_default_dictionary
4958 || account->enable_default_alt_dictionary)
4959 compose_spell_menu_changed(compose);
4961 #endif
4964 gboolean compose_check_for_valid_recipient(Compose *compose) {
4965 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4966 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4967 gboolean recipient_found = FALSE;
4968 GSList *list;
4969 gchar **strptr;
4971 /* free to and newsgroup list */
4972 slist_free_strings_full(compose->to_list);
4973 compose->to_list = NULL;
4975 slist_free_strings_full(compose->newsgroup_list);
4976 compose->newsgroup_list = NULL;
4978 /* search header entries for to and newsgroup entries */
4979 for (list = compose->header_list; list; list = list->next) {
4980 gchar *header;
4981 gchar *entry;
4982 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4983 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4984 g_strstrip(entry);
4985 g_strstrip(header);
4986 if (entry[0] != '\0') {
4987 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4988 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4989 compose->to_list = address_list_append(compose->to_list, entry);
4990 recipient_found = TRUE;
4993 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4994 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4995 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4996 recipient_found = TRUE;
5000 g_free(header);
5001 g_free(entry);
5003 return recipient_found;
5006 static gboolean compose_check_for_set_recipients(Compose *compose)
5008 if (compose->account->set_autocc && compose->account->auto_cc) {
5009 gboolean found_other = FALSE;
5010 GSList *list;
5011 /* search header entries for to and newsgroup entries */
5012 for (list = compose->header_list; list; list = list->next) {
5013 gchar *entry;
5014 gchar *header;
5015 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5016 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5017 g_strstrip(entry);
5018 g_strstrip(header);
5019 if (strcmp(entry, compose->account->auto_cc)
5020 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5021 found_other = TRUE;
5022 g_free(entry);
5023 break;
5025 g_free(entry);
5026 g_free(header);
5028 if (!found_other) {
5029 AlertValue aval;
5030 if (compose->batch) {
5031 gtk_widget_show_all(compose->window);
5033 aval = alertpanel(_("Send"),
5034 _("The only recipient is the default CC address. Send anyway?"),
5035 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5036 if (aval != G_ALERTALTERNATE)
5037 return FALSE;
5040 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5041 gboolean found_other = FALSE;
5042 GSList *list;
5043 /* search header entries for to and newsgroup entries */
5044 for (list = compose->header_list; list; list = list->next) {
5045 gchar *entry;
5046 gchar *header;
5047 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5048 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5049 g_strstrip(entry);
5050 g_strstrip(header);
5051 if (strcmp(entry, compose->account->auto_bcc)
5052 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5053 found_other = TRUE;
5054 g_free(entry);
5055 break;
5057 g_free(entry);
5058 g_free(header);
5060 if (!found_other) {
5061 AlertValue aval;
5062 if (compose->batch) {
5063 gtk_widget_show_all(compose->window);
5065 aval = alertpanel(_("Send"),
5066 _("The only recipient is the default BCC address. Send anyway?"),
5067 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5068 if (aval != G_ALERTALTERNATE)
5069 return FALSE;
5072 return TRUE;
5075 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5077 const gchar *str;
5079 if (compose_check_for_valid_recipient(compose) == FALSE) {
5080 if (compose->batch) {
5081 gtk_widget_show_all(compose->window);
5083 alertpanel_error(_("Recipient is not specified."));
5084 return FALSE;
5087 if (compose_check_for_set_recipients(compose) == FALSE) {
5088 return FALSE;
5091 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5092 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5093 if (*str == '\0' && check_everything == TRUE &&
5094 compose->mode != COMPOSE_REDIRECT) {
5095 AlertValue aval;
5096 gchar *button_label;
5097 gchar *message;
5099 if (compose->sending)
5100 button_label = g_strconcat("+", _("_Send"), NULL);
5101 else
5102 button_label = g_strconcat("+", _("_Queue"), NULL);
5103 message = g_strdup_printf(_("Subject is empty. %s"),
5104 compose->sending?_("Send it anyway?"):
5105 _("Queue it anyway?"));
5107 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5108 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5109 ALERT_QUESTION, G_ALERTDEFAULT);
5110 g_free(message);
5111 if (aval & G_ALERTDISABLE) {
5112 aval &= ~G_ALERTDISABLE;
5113 prefs_common.warn_empty_subj = FALSE;
5115 if (aval != G_ALERTALTERNATE)
5116 return FALSE;
5120 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5121 return FALSE;
5123 return TRUE;
5126 gint compose_send(Compose *compose)
5128 gint msgnum;
5129 FolderItem *folder = NULL;
5130 gint val = -1;
5131 gchar *msgpath = NULL;
5132 gboolean discard_window = FALSE;
5133 gchar *errstr = NULL;
5134 gchar *tmsgid = NULL;
5135 MainWindow *mainwin = mainwindow_get_mainwindow();
5136 gboolean queued_removed = FALSE;
5138 if (prefs_common.send_dialog_invisible
5139 || compose->batch == TRUE)
5140 discard_window = TRUE;
5142 compose_allow_user_actions (compose, FALSE);
5143 compose->sending = TRUE;
5145 if (compose_check_entries(compose, TRUE) == FALSE) {
5146 if (compose->batch) {
5147 gtk_widget_show_all(compose->window);
5149 goto bail;
5152 inc_lock();
5153 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5155 if (val) {
5156 if (compose->batch) {
5157 gtk_widget_show_all(compose->window);
5159 if (val == -4) {
5160 alertpanel_error(_("Could not queue message for sending:\n\n"
5161 "Charset conversion failed."));
5162 } else if (val == -5) {
5163 alertpanel_error(_("Could not queue message for sending:\n\n"
5164 "Couldn't get recipient encryption key."));
5165 } else if (val == -6) {
5166 /* silent error */
5167 } else if (val == -3) {
5168 if (privacy_peek_error())
5169 alertpanel_error(_("Could not queue message for sending:\n\n"
5170 "Signature failed: %s"), privacy_get_error());
5171 } else if (val == -2 && errno != 0) {
5172 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5173 } else {
5174 alertpanel_error(_("Could not queue message for sending."));
5176 goto bail;
5179 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5180 if (discard_window) {
5181 compose->sending = FALSE;
5182 compose_close(compose);
5183 /* No more compose access in the normal codepath
5184 * after this point! */
5185 compose = NULL;
5188 if (msgnum == 0) {
5189 alertpanel_error(_("The message was queued but could not be "
5190 "sent.\nUse \"Send queued messages\" from "
5191 "the main window to retry."));
5192 if (!discard_window) {
5193 goto bail;
5195 inc_unlock();
5196 g_free(tmsgid);
5197 return -1;
5199 if (msgpath == NULL) {
5200 msgpath = folder_item_fetch_msg(folder, msgnum);
5201 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5202 g_free(msgpath);
5203 } else {
5204 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5205 claws_unlink(msgpath);
5206 g_free(msgpath);
5208 if (!discard_window) {
5209 if (val != 0) {
5210 if (!queued_removed)
5211 folder_item_remove_msg(folder, msgnum);
5212 folder_item_scan(folder);
5213 if (tmsgid) {
5214 /* make sure we delete that */
5215 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5216 if (tmp) {
5217 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5218 folder_item_remove_msg(folder, tmp->msgnum);
5219 procmsg_msginfo_free(&tmp);
5225 if (val == 0) {
5226 if (!queued_removed)
5227 folder_item_remove_msg(folder, msgnum);
5228 folder_item_scan(folder);
5229 if (tmsgid) {
5230 /* make sure we delete that */
5231 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5232 if (tmp) {
5233 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5234 folder_item_remove_msg(folder, tmp->msgnum);
5235 procmsg_msginfo_free(&tmp);
5238 if (!discard_window) {
5239 compose->sending = FALSE;
5240 compose_allow_user_actions (compose, TRUE);
5241 compose_close(compose);
5243 } else {
5244 if (errstr) {
5245 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5246 "the main window to retry."), errstr);
5247 g_free(errstr);
5248 } else {
5249 alertpanel_error_log(_("The message was queued but could not be "
5250 "sent.\nUse \"Send queued messages\" from "
5251 "the main window to retry."));
5253 if (!discard_window) {
5254 goto bail;
5256 inc_unlock();
5257 g_free(tmsgid);
5258 return -1;
5260 g_free(tmsgid);
5261 inc_unlock();
5262 toolbar_main_set_sensitive(mainwin);
5263 main_window_set_menu_sensitive(mainwin);
5264 return 0;
5266 bail:
5267 inc_unlock();
5268 g_free(tmsgid);
5269 compose_allow_user_actions (compose, TRUE);
5270 compose->sending = FALSE;
5271 compose->modified = TRUE;
5272 toolbar_main_set_sensitive(mainwin);
5273 main_window_set_menu_sensitive(mainwin);
5275 return -1;
5278 static gboolean compose_use_attach(Compose *compose)
5280 GtkTreeModel *model = gtk_tree_view_get_model
5281 (GTK_TREE_VIEW(compose->attach_clist));
5282 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5285 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5286 FILE *fp)
5288 gchar buf[BUFFSIZE];
5289 gchar *str;
5290 gboolean first_to_address;
5291 gboolean first_cc_address;
5292 GSList *list;
5293 ComposeHeaderEntry *headerentry;
5294 const gchar *headerentryname;
5295 const gchar *cc_hdr;
5296 const gchar *to_hdr;
5297 gboolean err = FALSE;
5299 debug_print("Writing redirect header\n");
5301 cc_hdr = prefs_common_translated_header_name("Cc:");
5302 to_hdr = prefs_common_translated_header_name("To:");
5304 first_to_address = TRUE;
5305 for (list = compose->header_list; list; list = list->next) {
5306 headerentry = ((ComposeHeaderEntry *)list->data);
5307 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5309 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5310 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5311 Xstrdup_a(str, entstr, return -1);
5312 g_strstrip(str);
5313 if (str[0] != '\0') {
5314 compose_convert_header
5315 (compose, buf, sizeof(buf), str,
5316 strlen("Resent-To") + 2, TRUE);
5318 if (first_to_address) {
5319 err |= (fprintf(fp, "Resent-To: ") < 0);
5320 first_to_address = FALSE;
5321 } else {
5322 err |= (fprintf(fp, ",") < 0);
5324 err |= (fprintf(fp, "%s", buf) < 0);
5328 if (!first_to_address) {
5329 err |= (fprintf(fp, "\n") < 0);
5332 first_cc_address = TRUE;
5333 for (list = compose->header_list; list; list = list->next) {
5334 headerentry = ((ComposeHeaderEntry *)list->data);
5335 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5337 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5338 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5339 Xstrdup_a(str, strg, return -1);
5340 g_strstrip(str);
5341 if (str[0] != '\0') {
5342 compose_convert_header
5343 (compose, buf, sizeof(buf), str,
5344 strlen("Resent-Cc") + 2, TRUE);
5346 if (first_cc_address) {
5347 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5348 first_cc_address = FALSE;
5349 } else {
5350 err |= (fprintf(fp, ",") < 0);
5352 err |= (fprintf(fp, "%s", buf) < 0);
5356 if (!first_cc_address) {
5357 err |= (fprintf(fp, "\n") < 0);
5360 return (err ? -1:0);
5363 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5365 gchar buf[BUFFSIZE];
5366 gchar *str;
5367 const gchar *entstr;
5368 /* struct utsname utsbuf; */
5369 gboolean err = FALSE;
5371 cm_return_val_if_fail(fp != NULL, -1);
5372 cm_return_val_if_fail(compose->account != NULL, -1);
5373 cm_return_val_if_fail(compose->account->address != NULL, -1);
5375 /* Resent-Date */
5376 if (prefs_common.hide_timezone)
5377 get_rfc822_date_hide_tz(buf, sizeof(buf));
5378 else
5379 get_rfc822_date(buf, sizeof(buf));
5380 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5382 /* Resent-From */
5383 if (compose->account->name && *compose->account->name) {
5384 compose_convert_header
5385 (compose, buf, sizeof(buf), compose->account->name,
5386 strlen("From: "), TRUE);
5387 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5388 buf, compose->account->address) < 0);
5389 } else
5390 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5392 /* Subject */
5393 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5394 if (*entstr != '\0') {
5395 Xstrdup_a(str, entstr, return -1);
5396 g_strstrip(str);
5397 if (*str != '\0') {
5398 compose_convert_header(compose, buf, sizeof(buf), str,
5399 strlen("Subject: "), FALSE);
5400 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5404 /* Resent-Message-ID */
5405 if (compose->account->gen_msgid) {
5406 gchar *addr = prefs_account_generate_msgid(compose->account);
5407 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5408 if (compose->msgid)
5409 g_free(compose->msgid);
5410 compose->msgid = addr;
5411 } else {
5412 compose->msgid = NULL;
5415 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5416 return -1;
5418 /* separator between header and body */
5419 err |= (fputs("\n", fp) == EOF);
5421 return (err ? -1:0);
5424 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5426 FILE *fp;
5427 size_t len;
5428 gchar buf[BUFFSIZE];
5429 int i = 0;
5430 gboolean skip = FALSE;
5431 gboolean err = FALSE;
5432 gchar *not_included[]={
5433 "Return-Path:", "Delivered-To:", "Received:",
5434 "Subject:", "X-UIDL:", "AF:",
5435 "NF:", "PS:", "SRH:",
5436 "SFN:", "DSR:", "MID:",
5437 "CFG:", "PT:", "S:",
5438 "RQ:", "SSV:", "NSV:",
5439 "SSH:", "R:", "MAID:",
5440 "NAID:", "RMID:", "FMID:",
5441 "SCF:", "RRCPT:", "NG:",
5442 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5443 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5444 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5445 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5446 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5447 NULL
5449 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5450 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5451 return -1;
5454 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5455 skip = FALSE;
5456 for (i = 0; not_included[i] != NULL; i++) {
5457 if (g_ascii_strncasecmp(buf, not_included[i],
5458 strlen(not_included[i])) == 0) {
5459 skip = TRUE;
5460 break;
5463 if (skip)
5464 continue;
5465 if (fputs(buf, fdest) == -1)
5466 goto error;
5468 if (!prefs_common.redirect_keep_from) {
5469 if (g_ascii_strncasecmp(buf, "From:",
5470 strlen("From:")) == 0) {
5471 err |= (fputs(" (by way of ", fdest) == EOF);
5472 if (compose->account->name
5473 && *compose->account->name) {
5474 compose_convert_header
5475 (compose, buf, sizeof(buf),
5476 compose->account->name,
5477 strlen("From: "),
5478 FALSE);
5479 err |= (fprintf(fdest, "%s <%s>",
5480 buf,
5481 compose->account->address) < 0);
5482 } else
5483 err |= (fprintf(fdest, "%s",
5484 compose->account->address) < 0);
5485 err |= (fputs(")", fdest) == EOF);
5489 if (fputs("\n", fdest) == -1)
5490 goto error;
5493 if (err)
5494 goto error;
5496 if (compose_redirect_write_headers(compose, fdest))
5497 goto error;
5499 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5500 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5501 goto error;
5504 fclose(fp);
5506 return 0;
5507 error:
5508 fclose(fp);
5510 return -1;
5513 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5515 GtkTextBuffer *buffer;
5516 GtkTextIter start, end;
5517 gchar *chars, *tmp_enc_file, *content;
5518 gchar *buf, *msg;
5519 const gchar *out_codeset;
5520 EncodingType encoding = ENC_UNKNOWN;
5521 MimeInfo *mimemsg, *mimetext;
5522 gint line;
5523 const gchar *src_codeset = CS_INTERNAL;
5524 gchar *from_addr = NULL;
5525 gchar *from_name = NULL;
5526 FolderItem *outbox;
5528 if (action == COMPOSE_WRITE_FOR_SEND) {
5529 attach_parts = TRUE;
5531 /* We're sending the message, generate a Message-ID
5532 * if necessary. */
5533 if (compose->msgid == NULL &&
5534 compose->account->gen_msgid) {
5535 compose->msgid = prefs_account_generate_msgid(compose->account);
5539 /* create message MimeInfo */
5540 mimemsg = procmime_mimeinfo_new();
5541 mimemsg->type = MIMETYPE_MESSAGE;
5542 mimemsg->subtype = g_strdup("rfc822");
5543 mimemsg->content = MIMECONTENT_MEM;
5544 mimemsg->tmp = TRUE; /* must free content later */
5545 mimemsg->data.mem = compose_get_header(compose);
5547 /* Create text part MimeInfo */
5548 /* get all composed text */
5549 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5550 gtk_text_buffer_get_start_iter(buffer, &start);
5551 gtk_text_buffer_get_end_iter(buffer, &end);
5552 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5554 out_codeset = conv_get_charset_str(compose->out_encoding);
5556 if (!out_codeset && is_ascii_str(chars)) {
5557 out_codeset = CS_US_ASCII;
5558 } else if (prefs_common.outgoing_fallback_to_ascii &&
5559 is_ascii_str(chars)) {
5560 out_codeset = CS_US_ASCII;
5561 encoding = ENC_7BIT;
5564 if (!out_codeset) {
5565 gchar *test_conv_global_out = NULL;
5566 gchar *test_conv_reply = NULL;
5568 /* automatic mode. be automatic. */
5569 codeconv_set_strict(TRUE);
5571 out_codeset = conv_get_outgoing_charset_str();
5572 if (out_codeset) {
5573 debug_print("trying to convert to %s\n", out_codeset);
5574 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5577 if (!test_conv_global_out && compose->orig_charset
5578 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5579 out_codeset = compose->orig_charset;
5580 debug_print("failure; trying to convert to %s\n", out_codeset);
5581 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5584 if (!test_conv_global_out && !test_conv_reply) {
5585 /* we're lost */
5586 out_codeset = CS_INTERNAL;
5587 debug_print("failure; finally using %s\n", out_codeset);
5589 g_free(test_conv_global_out);
5590 g_free(test_conv_reply);
5591 codeconv_set_strict(FALSE);
5594 if (encoding == ENC_UNKNOWN) {
5595 if (prefs_common.encoding_method == CTE_BASE64)
5596 encoding = ENC_BASE64;
5597 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5598 encoding = ENC_QUOTED_PRINTABLE;
5599 else if (prefs_common.encoding_method == CTE_8BIT)
5600 encoding = ENC_8BIT;
5601 else
5602 encoding = procmime_get_encoding_for_charset(out_codeset);
5605 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5606 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5608 if (action == COMPOSE_WRITE_FOR_SEND) {
5609 codeconv_set_strict(TRUE);
5610 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5611 codeconv_set_strict(FALSE);
5613 if (!buf) {
5614 AlertValue aval;
5616 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5617 "to the specified %s charset.\n"
5618 "Send it as %s?"), out_codeset, src_codeset);
5619 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5620 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5621 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5622 g_free(msg);
5624 if (aval != G_ALERTALTERNATE) {
5625 g_free(chars);
5626 return -3;
5627 } else {
5628 buf = chars;
5629 out_codeset = src_codeset;
5630 chars = NULL;
5633 } else {
5634 buf = chars;
5635 out_codeset = src_codeset;
5636 chars = NULL;
5638 g_free(chars);
5640 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5641 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5642 strstr(buf, "\nFrom ") != NULL) {
5643 encoding = ENC_QUOTED_PRINTABLE;
5647 mimetext = procmime_mimeinfo_new();
5648 mimetext->content = MIMECONTENT_MEM;
5649 mimetext->tmp = TRUE; /* must free content later */
5650 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5651 * and free the data, which we need later. */
5652 mimetext->data.mem = g_strdup(buf);
5653 mimetext->type = MIMETYPE_TEXT;
5654 mimetext->subtype = g_strdup("plain");
5655 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5656 g_strdup(out_codeset));
5658 /* protect trailing spaces when signing message */
5659 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5660 privacy_system_can_sign(compose->privacy_system)) {
5661 encoding = ENC_QUOTED_PRINTABLE;
5664 #ifdef G_OS_WIN32
5665 debug_print("main text: %Id bytes encoded as %s in %d\n",
5666 #else
5667 debug_print("main text: %zd bytes encoded as %s in %d\n",
5668 #endif
5669 strlen(buf), out_codeset, encoding);
5671 /* check for line length limit */
5672 if (action == COMPOSE_WRITE_FOR_SEND &&
5673 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5674 check_line_length(buf, 1000, &line) < 0) {
5675 AlertValue aval;
5677 msg = g_strdup_printf
5678 (_("Line %d exceeds the line length limit (998 bytes).\n"
5679 "The contents of the message might be broken on the way to the delivery.\n"
5680 "\n"
5681 "Send it anyway?"), line + 1);
5682 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5683 g_free(msg);
5684 if (aval != G_ALERTALTERNATE) {
5685 g_free(buf);
5686 return -1;
5690 if (encoding != ENC_UNKNOWN)
5691 procmime_encode_content(mimetext, encoding);
5693 /* append attachment parts */
5694 if (compose_use_attach(compose) && attach_parts) {
5695 MimeInfo *mimempart;
5696 gchar *boundary = NULL;
5697 mimempart = procmime_mimeinfo_new();
5698 mimempart->content = MIMECONTENT_EMPTY;
5699 mimempart->type = MIMETYPE_MULTIPART;
5700 mimempart->subtype = g_strdup("mixed");
5702 do {
5703 g_free(boundary);
5704 boundary = generate_mime_boundary(NULL);
5705 } while (strstr(buf, boundary) != NULL);
5707 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5708 boundary);
5710 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5712 g_node_append(mimempart->node, mimetext->node);
5713 g_node_append(mimemsg->node, mimempart->node);
5715 if (compose_add_attachments(compose, mimempart) < 0)
5716 return -1;
5717 } else
5718 g_node_append(mimemsg->node, mimetext->node);
5720 g_free(buf);
5722 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5723 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5724 /* extract name and address */
5725 if (strstr(spec, " <") && strstr(spec, ">")) {
5726 from_addr = g_strdup(strrchr(spec, '<')+1);
5727 *(strrchr(from_addr, '>')) = '\0';
5728 from_name = g_strdup(spec);
5729 *(strrchr(from_name, '<')) = '\0';
5730 } else {
5731 from_name = NULL;
5732 from_addr = NULL;
5734 g_free(spec);
5736 /* sign message if sending */
5737 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5738 privacy_system_can_sign(compose->privacy_system))
5739 if (!privacy_sign(compose->privacy_system, mimemsg,
5740 compose->account, from_addr)) {
5741 g_free(from_name);
5742 g_free(from_addr);
5743 return -2;
5745 g_free(from_name);
5746 g_free(from_addr);
5748 if (compose->use_encryption) {
5749 if (compose->encdata != NULL &&
5750 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5752 /* First, write an unencrypted copy and save it to outbox, if
5753 * user wants that. */
5754 if (compose->account->save_encrypted_as_clear_text) {
5755 debug_print("saving sent message unencrypted...\n");
5756 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5757 if (tmpfp) {
5758 fclose(tmpfp);
5760 /* fp now points to a file with headers written,
5761 * let's make a copy. */
5762 rewind(fp);
5763 content = file_read_stream_to_str(fp);
5765 str_write_to_file(content, tmp_enc_file);
5766 g_free(content);
5768 /* Now write the unencrypted body. */
5769 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5770 procmime_write_mimeinfo(mimemsg, tmpfp);
5771 fclose(tmpfp);
5773 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5774 if (!outbox)
5775 outbox = folder_get_default_outbox();
5777 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5778 claws_unlink(tmp_enc_file);
5779 } else {
5780 g_warning("Can't open file '%s'", tmp_enc_file);
5782 } else {
5783 g_warning("couldn't get tempfile");
5786 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5787 debug_print("Couldn't encrypt mime structure: %s.\n",
5788 privacy_get_error());
5789 alertpanel_error(_("Couldn't encrypt the email: %s"),
5790 privacy_get_error());
5795 procmime_write_mimeinfo(mimemsg, fp);
5797 procmime_mimeinfo_free_all(&mimemsg);
5799 return 0;
5802 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5804 GtkTextBuffer *buffer;
5805 GtkTextIter start, end;
5806 FILE *fp;
5807 size_t len;
5808 gchar *chars, *tmp;
5810 if ((fp = g_fopen(file, "wb")) == NULL) {
5811 FILE_OP_ERROR(file, "fopen");
5812 return -1;
5815 /* chmod for security */
5816 if (change_file_mode_rw(fp, file) < 0) {
5817 FILE_OP_ERROR(file, "chmod");
5818 g_warning("can't change file mode");
5821 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5822 gtk_text_buffer_get_start_iter(buffer, &start);
5823 gtk_text_buffer_get_end_iter(buffer, &end);
5824 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5826 chars = conv_codeset_strdup
5827 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5829 g_free(tmp);
5830 if (!chars) {
5831 fclose(fp);
5832 claws_unlink(file);
5833 return -1;
5835 /* write body */
5836 len = strlen(chars);
5837 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5838 FILE_OP_ERROR(file, "fwrite");
5839 g_free(chars);
5840 fclose(fp);
5841 claws_unlink(file);
5842 return -1;
5845 g_free(chars);
5847 if (fclose(fp) == EOF) {
5848 FILE_OP_ERROR(file, "fclose");
5849 claws_unlink(file);
5850 return -1;
5852 return 0;
5855 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5857 FolderItem *item;
5858 MsgInfo *msginfo = compose->targetinfo;
5860 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5861 if (!msginfo) return -1;
5863 if (!force && MSG_IS_LOCKED(msginfo->flags))
5864 return 0;
5866 item = msginfo->folder;
5867 cm_return_val_if_fail(item != NULL, -1);
5869 if (procmsg_msg_exist(msginfo) &&
5870 (folder_has_parent_of_type(item, F_QUEUE) ||
5871 folder_has_parent_of_type(item, F_DRAFT)
5872 || msginfo == compose->autosaved_draft)) {
5873 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5874 g_warning("can't remove the old message");
5875 return -1;
5876 } else {
5877 debug_print("removed reedit target %d\n", msginfo->msgnum);
5881 return 0;
5884 static void compose_remove_draft(Compose *compose)
5886 FolderItem *drafts;
5887 MsgInfo *msginfo = compose->targetinfo;
5888 drafts = account_get_special_folder(compose->account, F_DRAFT);
5890 if (procmsg_msg_exist(msginfo)) {
5891 folder_item_remove_msg(drafts, msginfo->msgnum);
5896 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5897 gboolean remove_reedit_target)
5899 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5902 static gboolean compose_warn_encryption(Compose *compose)
5904 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5905 AlertValue val = G_ALERTALTERNATE;
5907 if (warning == NULL)
5908 return TRUE;
5910 val = alertpanel_full(_("Encryption warning"), warning,
5911 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
5912 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5913 if (val & G_ALERTDISABLE) {
5914 val &= ~G_ALERTDISABLE;
5915 if (val == G_ALERTALTERNATE)
5916 privacy_inhibit_encrypt_warning(compose->privacy_system,
5917 TRUE);
5920 if (val == G_ALERTALTERNATE) {
5921 return TRUE;
5922 } else {
5923 return FALSE;
5927 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5928 gchar **msgpath, gboolean check_subject,
5929 gboolean remove_reedit_target)
5931 FolderItem *queue;
5932 gchar *tmp;
5933 FILE *fp;
5934 GSList *cur;
5935 gint num;
5936 PrefsAccount *mailac = NULL, *newsac = NULL;
5937 gboolean err = FALSE;
5939 debug_print("queueing message...\n");
5940 cm_return_val_if_fail(compose->account != NULL, -1);
5942 if (compose_check_entries(compose, check_subject) == FALSE) {
5943 if (compose->batch) {
5944 gtk_widget_show_all(compose->window);
5946 return -1;
5949 if (!compose->to_list && !compose->newsgroup_list) {
5950 g_warning("can't get recipient list.");
5951 return -1;
5954 if (compose->to_list) {
5955 if (compose->account->protocol != A_NNTP)
5956 mailac = compose->account;
5957 else if (cur_account && cur_account->protocol != A_NNTP)
5958 mailac = cur_account;
5959 else if (!(mailac = compose_current_mail_account())) {
5960 alertpanel_error(_("No account for sending mails available!"));
5961 return -1;
5965 if (compose->newsgroup_list) {
5966 if (compose->account->protocol == A_NNTP)
5967 newsac = compose->account;
5968 else {
5969 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5970 return -1;
5974 /* write queue header */
5975 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5976 G_DIR_SEPARATOR, compose, (guint) rand());
5977 debug_print("queuing to %s\n", tmp);
5978 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
5979 FILE_OP_ERROR(tmp, "fopen");
5980 g_free(tmp);
5981 return -2;
5984 if (change_file_mode_rw(fp, tmp) < 0) {
5985 FILE_OP_ERROR(tmp, "chmod");
5986 g_warning("can't change file mode");
5989 /* queueing variables */
5990 err |= (fprintf(fp, "AF:\n") < 0);
5991 err |= (fprintf(fp, "NF:0\n") < 0);
5992 err |= (fprintf(fp, "PS:10\n") < 0);
5993 err |= (fprintf(fp, "SRH:1\n") < 0);
5994 err |= (fprintf(fp, "SFN:\n") < 0);
5995 err |= (fprintf(fp, "DSR:\n") < 0);
5996 if (compose->msgid)
5997 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5998 else
5999 err |= (fprintf(fp, "MID:\n") < 0);
6000 err |= (fprintf(fp, "CFG:\n") < 0);
6001 err |= (fprintf(fp, "PT:0\n") < 0);
6002 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6003 err |= (fprintf(fp, "RQ:\n") < 0);
6004 if (mailac)
6005 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6006 else
6007 err |= (fprintf(fp, "SSV:\n") < 0);
6008 if (newsac)
6009 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6010 else
6011 err |= (fprintf(fp, "NSV:\n") < 0);
6012 err |= (fprintf(fp, "SSH:\n") < 0);
6013 /* write recepient list */
6014 if (compose->to_list) {
6015 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6016 for (cur = compose->to_list->next; cur != NULL;
6017 cur = cur->next)
6018 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6019 err |= (fprintf(fp, "\n") < 0);
6021 /* write newsgroup list */
6022 if (compose->newsgroup_list) {
6023 err |= (fprintf(fp, "NG:") < 0);
6024 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6025 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6026 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6027 err |= (fprintf(fp, "\n") < 0);
6029 /* Sylpheed account IDs */
6030 if (mailac)
6031 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6032 if (newsac)
6033 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6036 if (compose->privacy_system != NULL) {
6037 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6038 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6039 if (compose->use_encryption) {
6040 if (!compose_warn_encryption(compose)) {
6041 fclose(fp);
6042 claws_unlink(tmp);
6043 g_free(tmp);
6044 return -6;
6046 if (mailac && mailac->encrypt_to_self) {
6047 GSList *tmp_list = g_slist_copy(compose->to_list);
6048 tmp_list = g_slist_append(tmp_list, compose->account->address);
6049 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6050 g_slist_free(tmp_list);
6051 } else {
6052 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6054 if (compose->encdata != NULL) {
6055 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6056 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6057 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6058 compose->encdata) < 0);
6059 } /* else we finally dont want to encrypt */
6060 } else {
6061 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6062 /* and if encdata was null, it means there's been a problem in
6063 * key selection */
6064 if (err == TRUE)
6065 g_warning("failed to write queue message");
6066 fclose(fp);
6067 claws_unlink(tmp);
6068 g_free(tmp);
6069 return -5;
6074 /* Save copy folder */
6075 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6076 gchar *savefolderid;
6078 savefolderid = compose_get_save_to(compose);
6079 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6080 g_free(savefolderid);
6082 /* Save copy folder */
6083 if (compose->return_receipt) {
6084 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6086 /* Message-ID of message replying to */
6087 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6088 gchar *folderid = NULL;
6090 if (compose->replyinfo->folder)
6091 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6092 if (folderid == NULL)
6093 folderid = g_strdup("NULL");
6095 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6096 g_free(folderid);
6098 /* Message-ID of message forwarding to */
6099 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6100 gchar *folderid = NULL;
6102 if (compose->fwdinfo->folder)
6103 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6104 if (folderid == NULL)
6105 folderid = g_strdup("NULL");
6107 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6108 g_free(folderid);
6111 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6112 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6114 /* end of headers */
6115 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6117 if (compose->redirect_filename != NULL) {
6118 if (compose_redirect_write_to_file(compose, fp) < 0) {
6119 fclose(fp);
6120 claws_unlink(tmp);
6121 g_free(tmp);
6122 return -2;
6124 } else {
6125 gint result = 0;
6126 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6127 fclose(fp);
6128 claws_unlink(tmp);
6129 g_free(tmp);
6130 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6133 if (err == TRUE) {
6134 g_warning("failed to write queue message");
6135 fclose(fp);
6136 claws_unlink(tmp);
6137 g_free(tmp);
6138 return -2;
6140 if (fclose(fp) == EOF) {
6141 FILE_OP_ERROR(tmp, "fclose");
6142 claws_unlink(tmp);
6143 g_free(tmp);
6144 return -2;
6147 if (item && *item) {
6148 queue = *item;
6149 } else {
6150 queue = account_get_special_folder(compose->account, F_QUEUE);
6152 if (!queue) {
6153 g_warning("can't find queue folder");
6154 claws_unlink(tmp);
6155 g_free(tmp);
6156 return -1;
6158 folder_item_scan(queue);
6159 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6160 g_warning("can't queue the message");
6161 claws_unlink(tmp);
6162 g_free(tmp);
6163 return -1;
6166 if (msgpath == NULL) {
6167 claws_unlink(tmp);
6168 g_free(tmp);
6169 } else
6170 *msgpath = tmp;
6172 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6173 compose_remove_reedit_target(compose, FALSE);
6176 if ((msgnum != NULL) && (item != NULL)) {
6177 *msgnum = num;
6178 *item = queue;
6181 return 0;
6184 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6186 AttachInfo *ainfo;
6187 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6188 MimeInfo *mimepart;
6189 GStatBuf statbuf;
6190 gchar *type, *subtype;
6191 GtkTreeModel *model;
6192 GtkTreeIter iter;
6194 model = gtk_tree_view_get_model(tree_view);
6196 if (!gtk_tree_model_get_iter_first(model, &iter))
6197 return 0;
6198 do {
6199 gtk_tree_model_get(model, &iter,
6200 COL_DATA, &ainfo,
6201 -1);
6203 if (!is_file_exist(ainfo->file)) {
6204 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6205 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6206 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6207 g_free(msg);
6208 if (val == G_ALERTDEFAULT) {
6209 return -1;
6211 continue;
6213 if (g_stat(ainfo->file, &statbuf) < 0)
6214 return -1;
6216 mimepart = procmime_mimeinfo_new();
6217 mimepart->content = MIMECONTENT_FILE;
6218 mimepart->data.filename = g_strdup(ainfo->file);
6219 mimepart->tmp = FALSE; /* or we destroy our attachment */
6220 mimepart->offset = 0;
6221 mimepart->length = statbuf.st_size;
6223 type = g_strdup(ainfo->content_type);
6225 if (!strchr(type, '/')) {
6226 g_free(type);
6227 type = g_strdup("application/octet-stream");
6230 subtype = strchr(type, '/') + 1;
6231 *(subtype - 1) = '\0';
6232 mimepart->type = procmime_get_media_type(type);
6233 mimepart->subtype = g_strdup(subtype);
6234 g_free(type);
6236 if (mimepart->type == MIMETYPE_MESSAGE &&
6237 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6238 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6239 } else if (mimepart->type == MIMETYPE_TEXT) {
6240 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6241 /* Text parts with no name come from multipart/alternative
6242 * forwards. Make sure the recipient won't look at the
6243 * original HTML part by mistake. */
6244 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6245 ainfo->name = g_strdup_printf(_("Original %s part"),
6246 mimepart->subtype);
6248 if (ainfo->charset)
6249 g_hash_table_insert(mimepart->typeparameters,
6250 g_strdup("charset"), g_strdup(ainfo->charset));
6252 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6253 if (mimepart->type == MIMETYPE_APPLICATION &&
6254 !strcmp2(mimepart->subtype, "octet-stream"))
6255 g_hash_table_insert(mimepart->typeparameters,
6256 g_strdup("name"), g_strdup(ainfo->name));
6257 g_hash_table_insert(mimepart->dispositionparameters,
6258 g_strdup("filename"), g_strdup(ainfo->name));
6259 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6262 if (mimepart->type == MIMETYPE_MESSAGE
6263 || mimepart->type == MIMETYPE_MULTIPART)
6264 ainfo->encoding = ENC_BINARY;
6265 else if (compose->use_signing) {
6266 if (ainfo->encoding == ENC_7BIT)
6267 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6268 else if (ainfo->encoding == ENC_8BIT)
6269 ainfo->encoding = ENC_BASE64;
6274 procmime_encode_content(mimepart, ainfo->encoding);
6276 g_node_append(parent->node, mimepart->node);
6277 } while (gtk_tree_model_iter_next(model, &iter));
6279 return 0;
6282 static gchar *compose_quote_list_of_addresses(gchar *str)
6284 GSList *list = NULL, *item = NULL;
6285 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6287 list = address_list_append_with_comments(list, str);
6288 for (item = list; item != NULL; item = item->next) {
6289 gchar *spec = item->data;
6290 gchar *endofname = strstr(spec, " <");
6291 if (endofname != NULL) {
6292 gchar * qqname;
6293 *endofname = '\0';
6294 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6295 qqname = escape_internal_quotes(qname, '"');
6296 *endofname = ' ';
6297 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6298 gchar *addr = g_strdup(endofname);
6299 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6300 faddr = g_strconcat(name, addr, NULL);
6301 g_free(name);
6302 g_free(addr);
6303 debug_print("new auto-quoted address: '%s'\n", faddr);
6306 if (result == NULL)
6307 result = g_strdup((faddr != NULL)? faddr: spec);
6308 else {
6309 result = g_strconcat(result,
6310 ", ",
6311 (faddr != NULL)? faddr: spec,
6312 NULL);
6314 if (faddr != NULL) {
6315 g_free(faddr);
6316 faddr = NULL;
6319 slist_free_strings_full(list);
6321 return result;
6324 #define IS_IN_CUSTOM_HEADER(header) \
6325 (compose->account->add_customhdr && \
6326 custom_header_find(compose->account->customhdr_list, header) != NULL)
6328 static void compose_add_headerfield_from_headerlist(Compose *compose,
6329 GString *header,
6330 const gchar *fieldname,
6331 const gchar *seperator)
6333 gchar *str, *fieldname_w_colon;
6334 gboolean add_field = FALSE;
6335 GSList *list;
6336 ComposeHeaderEntry *headerentry;
6337 const gchar *headerentryname;
6338 const gchar *trans_fieldname;
6339 GString *fieldstr;
6341 if (IS_IN_CUSTOM_HEADER(fieldname))
6342 return;
6344 debug_print("Adding %s-fields\n", fieldname);
6346 fieldstr = g_string_sized_new(64);
6348 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6349 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6351 for (list = compose->header_list; list; list = list->next) {
6352 headerentry = ((ComposeHeaderEntry *)list->data);
6353 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6355 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6356 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6357 g_strstrip(ustr);
6358 str = compose_quote_list_of_addresses(ustr);
6359 g_free(ustr);
6360 if (str != NULL && str[0] != '\0') {
6361 if (add_field)
6362 g_string_append(fieldstr, seperator);
6363 g_string_append(fieldstr, str);
6364 add_field = TRUE;
6366 g_free(str);
6369 if (add_field) {
6370 gchar *buf;
6372 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6373 compose_convert_header
6374 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6375 strlen(fieldname) + 2, TRUE);
6376 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6377 g_free(buf);
6380 g_free(fieldname_w_colon);
6381 g_string_free(fieldstr, TRUE);
6383 return;
6386 static gchar *compose_get_manual_headers_info(Compose *compose)
6388 GString *sh_header = g_string_new(" ");
6389 GSList *list;
6390 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6392 for (list = compose->header_list; list; list = list->next) {
6393 ComposeHeaderEntry *headerentry;
6394 gchar *tmp;
6395 gchar *headername;
6396 gchar *headername_wcolon;
6397 const gchar *headername_trans;
6398 gchar **string;
6399 gboolean standard_header = FALSE;
6401 headerentry = ((ComposeHeaderEntry *)list->data);
6403 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6404 g_strstrip(tmp);
6405 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6406 g_free(tmp);
6407 continue;
6410 if (!strstr(tmp, ":")) {
6411 headername_wcolon = g_strconcat(tmp, ":", NULL);
6412 headername = g_strdup(tmp);
6413 } else {
6414 headername_wcolon = g_strdup(tmp);
6415 headername = g_strdup(strtok(tmp, ":"));
6417 g_free(tmp);
6419 string = std_headers;
6420 while (*string != NULL) {
6421 headername_trans = prefs_common_translated_header_name(*string);
6422 if (!strcmp(headername_trans, headername_wcolon))
6423 standard_header = TRUE;
6424 string++;
6426 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6427 g_string_append_printf(sh_header, "%s ", headername);
6428 g_free(headername);
6429 g_free(headername_wcolon);
6431 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6432 return g_string_free(sh_header, FALSE);
6435 static gchar *compose_get_header(Compose *compose)
6437 gchar buf[BUFFSIZE];
6438 const gchar *entry_str;
6439 gchar *str;
6440 gchar *name;
6441 GSList *list;
6442 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6443 GString *header;
6444 gchar *from_name = NULL, *from_address = NULL;
6445 gchar *tmp;
6447 cm_return_val_if_fail(compose->account != NULL, NULL);
6448 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6450 header = g_string_sized_new(64);
6452 /* Date */
6453 if (prefs_common.hide_timezone)
6454 get_rfc822_date_hide_tz(buf, sizeof(buf));
6455 else
6456 get_rfc822_date(buf, sizeof(buf));
6457 g_string_append_printf(header, "Date: %s\n", buf);
6459 /* From */
6461 if (compose->account->name && *compose->account->name) {
6462 gchar *buf;
6463 QUOTE_IF_REQUIRED(buf, compose->account->name);
6464 tmp = g_strdup_printf("%s <%s>",
6465 buf, compose->account->address);
6466 } else {
6467 tmp = g_strdup_printf("%s",
6468 compose->account->address);
6470 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6471 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6472 /* use default */
6473 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6474 from_address = g_strdup(compose->account->address);
6475 } else {
6476 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6477 /* extract name and address */
6478 if (strstr(spec, " <") && strstr(spec, ">")) {
6479 from_address = g_strdup(strrchr(spec, '<')+1);
6480 *(strrchr(from_address, '>')) = '\0';
6481 from_name = g_strdup(spec);
6482 *(strrchr(from_name, '<')) = '\0';
6483 } else {
6484 from_name = NULL;
6485 from_address = g_strdup(spec);
6487 g_free(spec);
6489 g_free(tmp);
6492 if (from_name && *from_name) {
6493 gchar *qname;
6494 compose_convert_header
6495 (compose, buf, sizeof(buf), from_name,
6496 strlen("From: "), TRUE);
6497 QUOTE_IF_REQUIRED(name, buf);
6498 qname = escape_internal_quotes(name, '"');
6500 g_string_append_printf(header, "From: %s <%s>\n",
6501 qname, from_address);
6502 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6503 compose->return_receipt) {
6504 compose_convert_header(compose, buf, sizeof(buf), from_name,
6505 strlen("Disposition-Notification-To: "),
6506 TRUE);
6507 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6509 if (qname != name)
6510 g_free(qname);
6511 } else {
6512 g_string_append_printf(header, "From: %s\n", from_address);
6513 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6514 compose->return_receipt)
6515 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6518 g_free(from_name);
6519 g_free(from_address);
6521 /* To */
6522 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6524 /* Newsgroups */
6525 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6527 /* Cc */
6528 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6530 /* Bcc */
6532 * If this account is a NNTP account remove Bcc header from
6533 * message body since it otherwise will be publicly shown
6535 if (compose->account->protocol != A_NNTP)
6536 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6538 /* Subject */
6539 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6541 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6542 g_strstrip(str);
6543 if (*str != '\0') {
6544 compose_convert_header(compose, buf, sizeof(buf), str,
6545 strlen("Subject: "), FALSE);
6546 g_string_append_printf(header, "Subject: %s\n", buf);
6549 g_free(str);
6551 /* Message-ID */
6552 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6553 g_string_append_printf(header, "Message-ID: <%s>\n",
6554 compose->msgid);
6557 if (compose->remove_references == FALSE) {
6558 /* In-Reply-To */
6559 if (compose->inreplyto && compose->to_list)
6560 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6562 /* References */
6563 if (compose->references)
6564 g_string_append_printf(header, "References: %s\n", compose->references);
6567 /* Followup-To */
6568 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6570 /* Reply-To */
6571 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6573 /* Organization */
6574 if (compose->account->organization &&
6575 strlen(compose->account->organization) &&
6576 !IS_IN_CUSTOM_HEADER("Organization")) {
6577 compose_convert_header(compose, buf, sizeof(buf),
6578 compose->account->organization,
6579 strlen("Organization: "), FALSE);
6580 g_string_append_printf(header, "Organization: %s\n", buf);
6583 /* Program version and system info */
6584 if (compose->account->gen_xmailer &&
6585 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6586 !compose->newsgroup_list) {
6587 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6588 prog_version,
6589 gtk_major_version, gtk_minor_version, gtk_micro_version,
6590 TARGET_ALIAS);
6592 if (compose->account->gen_xmailer &&
6593 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6594 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6595 prog_version,
6596 gtk_major_version, gtk_minor_version, gtk_micro_version,
6597 TARGET_ALIAS);
6600 /* custom headers */
6601 if (compose->account->add_customhdr) {
6602 GSList *cur;
6604 for (cur = compose->account->customhdr_list; cur != NULL;
6605 cur = cur->next) {
6606 CustomHeader *chdr = (CustomHeader *)cur->data;
6608 if (custom_header_is_allowed(chdr->name)
6609 && chdr->value != NULL
6610 && *(chdr->value) != '\0') {
6611 compose_convert_header
6612 (compose, buf, sizeof(buf),
6613 chdr->value,
6614 strlen(chdr->name) + 2, FALSE);
6615 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6620 /* Automatic Faces and X-Faces */
6621 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6622 g_string_append_printf(header, "X-Face: %s\n", buf);
6624 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6625 g_string_append_printf(header, "X-Face: %s\n", buf);
6627 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6628 g_string_append_printf(header, "Face: %s\n", buf);
6630 else if (get_default_face (buf, sizeof(buf)) == 0) {
6631 g_string_append_printf(header, "Face: %s\n", buf);
6634 /* PRIORITY */
6635 switch (compose->priority) {
6636 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6637 "X-Priority: 1 (Highest)\n");
6638 break;
6639 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6640 "X-Priority: 2 (High)\n");
6641 break;
6642 case PRIORITY_NORMAL: break;
6643 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6644 "X-Priority: 4 (Low)\n");
6645 break;
6646 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6647 "X-Priority: 5 (Lowest)\n");
6648 break;
6649 default: debug_print("compose: priority unknown : %d\n",
6650 compose->priority);
6653 /* get special headers */
6654 for (list = compose->header_list; list; list = list->next) {
6655 ComposeHeaderEntry *headerentry;
6656 gchar *tmp;
6657 gchar *headername;
6658 gchar *headername_wcolon;
6659 const gchar *headername_trans;
6660 gchar *headervalue;
6661 gchar **string;
6662 gboolean standard_header = FALSE;
6664 headerentry = ((ComposeHeaderEntry *)list->data);
6666 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6667 g_strstrip(tmp);
6668 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6669 g_free(tmp);
6670 continue;
6673 if (!strstr(tmp, ":")) {
6674 headername_wcolon = g_strconcat(tmp, ":", NULL);
6675 headername = g_strdup(tmp);
6676 } else {
6677 headername_wcolon = g_strdup(tmp);
6678 headername = g_strdup(strtok(tmp, ":"));
6680 g_free(tmp);
6682 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6683 Xstrdup_a(headervalue, entry_str, return NULL);
6684 subst_char(headervalue, '\r', ' ');
6685 subst_char(headervalue, '\n', ' ');
6686 string = std_headers;
6687 while (*string != NULL) {
6688 headername_trans = prefs_common_translated_header_name(*string);
6689 if (!strcmp(headername_trans, headername_wcolon))
6690 standard_header = TRUE;
6691 string++;
6693 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6694 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6696 g_free(headername);
6697 g_free(headername_wcolon);
6700 str = header->str;
6701 g_string_free(header, FALSE);
6703 return str;
6706 #undef IS_IN_CUSTOM_HEADER
6708 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6709 gint header_len, gboolean addr_field)
6711 gchar *tmpstr = NULL;
6712 const gchar *out_codeset = NULL;
6714 cm_return_if_fail(src != NULL);
6715 cm_return_if_fail(dest != NULL);
6717 if (len < 1) return;
6719 tmpstr = g_strdup(src);
6721 subst_char(tmpstr, '\n', ' ');
6722 subst_char(tmpstr, '\r', ' ');
6723 g_strchomp(tmpstr);
6725 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6726 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6727 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6728 g_free(tmpstr);
6729 tmpstr = mybuf;
6732 codeconv_set_strict(TRUE);
6733 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6734 conv_get_charset_str(compose->out_encoding));
6735 codeconv_set_strict(FALSE);
6737 if (!dest || *dest == '\0') {
6738 gchar *test_conv_global_out = NULL;
6739 gchar *test_conv_reply = NULL;
6741 /* automatic mode. be automatic. */
6742 codeconv_set_strict(TRUE);
6744 out_codeset = conv_get_outgoing_charset_str();
6745 if (out_codeset) {
6746 debug_print("trying to convert to %s\n", out_codeset);
6747 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6750 if (!test_conv_global_out && compose->orig_charset
6751 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6752 out_codeset = compose->orig_charset;
6753 debug_print("failure; trying to convert to %s\n", out_codeset);
6754 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6757 if (!test_conv_global_out && !test_conv_reply) {
6758 /* we're lost */
6759 out_codeset = CS_INTERNAL;
6760 debug_print("finally using %s\n", out_codeset);
6762 g_free(test_conv_global_out);
6763 g_free(test_conv_reply);
6764 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6765 out_codeset);
6766 codeconv_set_strict(FALSE);
6768 g_free(tmpstr);
6771 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6773 gchar *address;
6775 cm_return_if_fail(user_data != NULL);
6777 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6778 g_strstrip(address);
6779 if (*address != '\0') {
6780 gchar *name = procheader_get_fromname(address);
6781 extract_address(address);
6782 #ifndef USE_ALT_ADDRBOOK
6783 addressbook_add_contact(name, address, NULL, NULL);
6784 #else
6785 debug_print("%s: %s\n", name, address);
6786 if (addressadd_selection(name, address, NULL, NULL)) {
6787 debug_print( "addressbook_add_contact - added\n" );
6789 #endif
6791 g_free(address);
6794 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6796 GtkWidget *menuitem;
6797 gchar *address;
6799 cm_return_if_fail(menu != NULL);
6800 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6802 menuitem = gtk_separator_menu_item_new();
6803 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6804 gtk_widget_show(menuitem);
6806 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6807 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6809 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6810 g_strstrip(address);
6811 if (*address == '\0') {
6812 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6815 g_signal_connect(G_OBJECT(menuitem), "activate",
6816 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6817 gtk_widget_show(menuitem);
6820 void compose_add_extra_header(gchar *header, GtkListStore *model)
6822 GtkTreeIter iter;
6823 if (strcmp(header, "")) {
6824 COMBOBOX_ADD(model, header, COMPOSE_TO);
6828 void compose_add_extra_header_entries(GtkListStore *model)
6830 FILE *exh;
6831 gchar *exhrc;
6832 gchar buf[BUFFSIZE];
6833 gint lastc;
6835 if (extra_headers == NULL) {
6836 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6837 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6838 debug_print("extra headers file not found\n");
6839 goto extra_headers_done;
6841 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6842 lastc = strlen(buf) - 1; /* remove trailing control chars */
6843 while (lastc >= 0 && buf[lastc] != ':')
6844 buf[lastc--] = '\0';
6845 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6846 buf[lastc] = '\0'; /* remove trailing : for comparison */
6847 if (custom_header_is_allowed(buf)) {
6848 buf[lastc] = ':';
6849 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6851 else
6852 g_message("disallowed extra header line: %s\n", buf);
6854 else {
6855 if (buf[0] != '#')
6856 g_message("invalid extra header line: %s\n", buf);
6859 fclose(exh);
6860 extra_headers_done:
6861 g_free(exhrc);
6862 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6863 extra_headers = g_slist_reverse(extra_headers);
6865 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6868 static void compose_create_header_entry(Compose *compose)
6870 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6872 GtkWidget *combo;
6873 GtkWidget *entry;
6874 GtkWidget *button;
6875 GtkWidget *hbox;
6876 gchar **string;
6877 const gchar *header = NULL;
6878 ComposeHeaderEntry *headerentry;
6879 gboolean standard_header = FALSE;
6880 GtkListStore *model;
6881 GtkTreeIter iter;
6883 headerentry = g_new0(ComposeHeaderEntry, 1);
6885 /* Combo box model */
6886 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6887 #if !GTK_CHECK_VERSION(2, 24, 0)
6888 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6889 #endif
6890 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6891 COMPOSE_TO);
6892 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6893 COMPOSE_CC);
6894 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6895 COMPOSE_BCC);
6896 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6897 COMPOSE_NEWSGROUPS);
6898 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6899 COMPOSE_REPLYTO);
6900 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6901 COMPOSE_FOLLOWUPTO);
6902 compose_add_extra_header_entries(model);
6904 /* Combo box */
6905 #if GTK_CHECK_VERSION(2, 24, 0)
6906 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6907 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6908 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6909 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6910 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6911 #endif
6912 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6913 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6914 G_CALLBACK(compose_grab_focus_cb), compose);
6915 gtk_widget_show(combo);
6917 /* Putting only the combobox child into focus chain of its parent causes
6918 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6919 * This eliminates need to pres Tab twice in order to really get from the
6920 * combobox to next widget. */
6921 GList *l = NULL;
6922 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6923 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6924 g_list_free(l);
6926 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6927 compose->header_nextrow, compose->header_nextrow+1,
6928 GTK_SHRINK, GTK_FILL, 0, 0);
6929 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6930 const gchar *last_header_entry = gtk_entry_get_text(
6931 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6932 string = headers;
6933 while (*string != NULL) {
6934 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6935 standard_header = TRUE;
6936 string++;
6938 if (standard_header)
6939 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6941 if (!compose->header_last || !standard_header) {
6942 switch(compose->account->protocol) {
6943 case A_NNTP:
6944 header = prefs_common_translated_header_name("Newsgroups:");
6945 break;
6946 default:
6947 header = prefs_common_translated_header_name("To:");
6948 break;
6951 if (header)
6952 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6954 gtk_editable_set_editable(
6955 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
6956 prefs_common.type_any_header);
6958 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6959 G_CALLBACK(compose_grab_focus_cb), compose);
6961 /* Entry field with cleanup button */
6962 button = gtk_button_new();
6963 gtk_button_set_image(GTK_BUTTON(button),
6964 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6965 gtk_widget_show(button);
6966 CLAWS_SET_TIP(button,
6967 _("Delete entry contents"));
6968 entry = gtk_entry_new();
6969 gtk_widget_show(entry);
6970 CLAWS_SET_TIP(entry,
6971 _("Use <tab> to autocomplete from addressbook"));
6972 hbox = gtk_hbox_new (FALSE, 0);
6973 gtk_widget_show(hbox);
6974 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6975 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6976 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6977 compose->header_nextrow, compose->header_nextrow+1,
6978 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6980 g_signal_connect(G_OBJECT(entry), "key-press-event",
6981 G_CALLBACK(compose_headerentry_key_press_event_cb),
6982 headerentry);
6983 g_signal_connect(G_OBJECT(entry), "changed",
6984 G_CALLBACK(compose_headerentry_changed_cb),
6985 headerentry);
6986 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6987 G_CALLBACK(compose_grab_focus_cb), compose);
6989 g_signal_connect(G_OBJECT(button), "clicked",
6990 G_CALLBACK(compose_headerentry_button_clicked_cb),
6991 headerentry);
6993 /* email dnd */
6994 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6995 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6996 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6997 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6998 G_CALLBACK(compose_header_drag_received_cb),
6999 entry);
7000 g_signal_connect(G_OBJECT(entry), "drag-drop",
7001 G_CALLBACK(compose_drag_drop),
7002 compose);
7003 g_signal_connect(G_OBJECT(entry), "populate-popup",
7004 G_CALLBACK(compose_entry_popup_extend),
7005 NULL);
7007 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7009 headerentry->compose = compose;
7010 headerentry->combo = combo;
7011 headerentry->entry = entry;
7012 headerentry->button = button;
7013 headerentry->hbox = hbox;
7014 headerentry->headernum = compose->header_nextrow;
7015 headerentry->type = PREF_NONE;
7017 compose->header_nextrow++;
7018 compose->header_last = headerentry;
7019 compose->header_list =
7020 g_slist_append(compose->header_list,
7021 headerentry);
7024 static void compose_add_header_entry(Compose *compose, const gchar *header,
7025 gchar *text, ComposePrefType pref_type)
7027 ComposeHeaderEntry *last_header = compose->header_last;
7028 gchar *tmp = g_strdup(text), *email;
7029 gboolean replyto_hdr;
7031 replyto_hdr = (!strcasecmp(header,
7032 prefs_common_translated_header_name("Reply-To:")) ||
7033 !strcasecmp(header,
7034 prefs_common_translated_header_name("Followup-To:")) ||
7035 !strcasecmp(header,
7036 prefs_common_translated_header_name("In-Reply-To:")));
7038 extract_address(tmp);
7039 email = g_utf8_strdown(tmp, -1);
7041 if (replyto_hdr == FALSE &&
7042 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7044 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7045 header, text, (gint) pref_type);
7046 g_free(email);
7047 g_free(tmp);
7048 return;
7051 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7052 gtk_entry_set_text(GTK_ENTRY(
7053 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7054 else
7055 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7056 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7057 last_header->type = pref_type;
7059 if (replyto_hdr == FALSE)
7060 g_hash_table_insert(compose->email_hashtable, email,
7061 GUINT_TO_POINTER(1));
7062 else
7063 g_free(email);
7065 g_free(tmp);
7068 static void compose_destroy_headerentry(Compose *compose,
7069 ComposeHeaderEntry *headerentry)
7071 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7072 gchar *email;
7074 extract_address(text);
7075 email = g_utf8_strdown(text, -1);
7076 g_hash_table_remove(compose->email_hashtable, email);
7077 g_free(text);
7078 g_free(email);
7080 gtk_widget_destroy(headerentry->combo);
7081 gtk_widget_destroy(headerentry->entry);
7082 gtk_widget_destroy(headerentry->button);
7083 gtk_widget_destroy(headerentry->hbox);
7084 g_free(headerentry);
7087 static void compose_remove_header_entries(Compose *compose)
7089 GSList *list;
7090 for (list = compose->header_list; list; list = list->next)
7091 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7093 compose->header_last = NULL;
7094 g_slist_free(compose->header_list);
7095 compose->header_list = NULL;
7096 compose->header_nextrow = 1;
7097 compose_create_header_entry(compose);
7100 static GtkWidget *compose_create_header(Compose *compose)
7102 GtkWidget *from_optmenu_hbox;
7103 GtkWidget *header_table_main;
7104 GtkWidget *header_scrolledwin;
7105 GtkWidget *header_table;
7107 /* parent with account selection and from header */
7108 header_table_main = gtk_table_new(2, 2, FALSE);
7109 gtk_widget_show(header_table_main);
7110 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7112 from_optmenu_hbox = compose_account_option_menu_create(compose);
7113 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7114 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7116 /* child with header labels and entries */
7117 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7118 gtk_widget_show(header_scrolledwin);
7119 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7121 header_table = gtk_table_new(2, 2, FALSE);
7122 gtk_widget_show(header_table);
7123 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7124 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7125 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7126 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7127 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7129 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7130 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7132 compose->header_table = header_table;
7133 compose->header_list = NULL;
7134 compose->header_nextrow = 0;
7136 compose_create_header_entry(compose);
7138 compose->table = NULL;
7140 return header_table_main;
7143 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7145 Compose *compose = (Compose *)data;
7146 GdkEventButton event;
7148 event.button = 3;
7149 event.time = gtk_get_current_event_time();
7151 return attach_button_pressed(compose->attach_clist, &event, compose);
7154 static GtkWidget *compose_create_attach(Compose *compose)
7156 GtkWidget *attach_scrwin;
7157 GtkWidget *attach_clist;
7159 GtkListStore *store;
7160 GtkCellRenderer *renderer;
7161 GtkTreeViewColumn *column;
7162 GtkTreeSelection *selection;
7164 /* attachment list */
7165 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7166 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7167 GTK_POLICY_AUTOMATIC,
7168 GTK_POLICY_AUTOMATIC);
7169 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7171 store = gtk_list_store_new(N_ATTACH_COLS,
7172 G_TYPE_STRING,
7173 G_TYPE_STRING,
7174 G_TYPE_STRING,
7175 G_TYPE_STRING,
7176 G_TYPE_POINTER,
7177 G_TYPE_AUTO_POINTER,
7178 -1);
7179 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7180 (GTK_TREE_MODEL(store)));
7181 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7182 g_object_unref(store);
7184 renderer = gtk_cell_renderer_text_new();
7185 column = gtk_tree_view_column_new_with_attributes
7186 (_("Mime type"), renderer, "text",
7187 COL_MIMETYPE, NULL);
7188 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7190 renderer = gtk_cell_renderer_text_new();
7191 column = gtk_tree_view_column_new_with_attributes
7192 (_("Size"), renderer, "text",
7193 COL_SIZE, NULL);
7194 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7196 renderer = gtk_cell_renderer_text_new();
7197 column = gtk_tree_view_column_new_with_attributes
7198 (_("Name"), renderer, "text",
7199 COL_NAME, NULL);
7200 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7202 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7203 prefs_common.use_stripes_everywhere);
7204 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7205 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7207 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7208 G_CALLBACK(attach_selected), compose);
7209 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7210 G_CALLBACK(attach_button_pressed), compose);
7211 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7212 G_CALLBACK(popup_attach_button_pressed), compose);
7213 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7214 G_CALLBACK(attach_key_pressed), compose);
7216 /* drag and drop */
7217 gtk_drag_dest_set(attach_clist,
7218 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7219 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7220 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7221 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7222 G_CALLBACK(compose_attach_drag_received_cb),
7223 compose);
7224 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7225 G_CALLBACK(compose_drag_drop),
7226 compose);
7228 compose->attach_scrwin = attach_scrwin;
7229 compose->attach_clist = attach_clist;
7231 return attach_scrwin;
7234 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7235 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7237 static GtkWidget *compose_create_others(Compose *compose)
7239 GtkWidget *table;
7240 GtkWidget *savemsg_checkbtn;
7241 GtkWidget *savemsg_combo;
7242 GtkWidget *savemsg_select;
7244 guint rowcount = 0;
7245 gchar *folderidentifier;
7247 /* Table for settings */
7248 table = gtk_table_new(3, 1, FALSE);
7249 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7250 gtk_widget_show(table);
7251 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7252 rowcount = 0;
7254 /* Save Message to folder */
7255 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7256 gtk_widget_show(savemsg_checkbtn);
7257 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7258 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7259 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7261 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7262 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7264 #if !GTK_CHECK_VERSION(2, 24, 0)
7265 savemsg_combo = gtk_combo_box_entry_new_text();
7266 #else
7267 savemsg_combo = gtk_combo_box_text_new_with_entry();
7268 #endif
7269 compose->savemsg_checkbtn = savemsg_checkbtn;
7270 compose->savemsg_combo = savemsg_combo;
7271 gtk_widget_show(savemsg_combo);
7273 if (prefs_common.compose_save_to_history)
7274 #if !GTK_CHECK_VERSION(2, 24, 0)
7275 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7276 prefs_common.compose_save_to_history);
7277 #else
7278 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7279 prefs_common.compose_save_to_history);
7280 #endif
7281 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7282 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7283 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7284 G_CALLBACK(compose_grab_focus_cb), compose);
7285 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7286 folderidentifier = folder_item_get_identifier(account_get_special_folder
7287 (compose->account, F_OUTBOX));
7288 compose_set_save_to(compose, folderidentifier);
7289 g_free(folderidentifier);
7292 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7293 gtk_widget_show(savemsg_select);
7294 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7295 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7296 G_CALLBACK(compose_savemsg_select_cb),
7297 compose);
7299 return table;
7302 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7304 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7305 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7308 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7310 FolderItem *dest;
7311 gchar * path;
7313 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7314 if (!dest) return;
7316 path = folder_item_get_identifier(dest);
7318 compose_set_save_to(compose, path);
7319 g_free(path);
7322 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7323 GdkAtom clip, GtkTextIter *insert_place);
7326 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7327 Compose *compose)
7329 gint prev_autowrap;
7330 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7331 #if USE_ENCHANT
7332 if (event->button == 3) {
7333 GtkTextIter iter;
7334 GtkTextIter sel_start, sel_end;
7335 gboolean stuff_selected;
7336 gint x, y;
7337 /* move the cursor to allow GtkAspell to check the word
7338 * under the mouse */
7339 if (event->x && event->y) {
7340 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7341 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7342 &x, &y);
7343 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7344 &iter, x, y);
7345 } else {
7346 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7347 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7349 /* get selection */
7350 stuff_selected = gtk_text_buffer_get_selection_bounds(
7351 buffer,
7352 &sel_start, &sel_end);
7354 gtk_text_buffer_place_cursor (buffer, &iter);
7355 /* reselect stuff */
7356 if (stuff_selected
7357 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7358 gtk_text_buffer_select_range(buffer,
7359 &sel_start, &sel_end);
7361 return FALSE; /* pass the event so that the right-click goes through */
7363 #endif
7364 if (event->button == 2) {
7365 GtkTextIter iter;
7366 gint x, y;
7367 BLOCK_WRAP();
7369 /* get the middle-click position to paste at the correct place */
7370 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7371 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7372 &x, &y);
7373 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7374 &iter, x, y);
7376 entry_paste_clipboard(compose, text,
7377 prefs_common.linewrap_pastes,
7378 GDK_SELECTION_PRIMARY, &iter);
7379 UNBLOCK_WRAP();
7380 return TRUE;
7382 return FALSE;
7385 #if USE_ENCHANT
7386 static void compose_spell_menu_changed(void *data)
7388 Compose *compose = (Compose *)data;
7389 GSList *items;
7390 GtkWidget *menuitem;
7391 GtkWidget *parent_item;
7392 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7393 GSList *spell_menu;
7395 if (compose->gtkaspell == NULL)
7396 return;
7398 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7399 "/Menu/Spelling/Options");
7401 /* setting the submenu removes /Spelling/Options from the factory
7402 * so we need to save it */
7404 if (parent_item == NULL) {
7405 parent_item = compose->aspell_options_menu;
7406 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7407 } else
7408 compose->aspell_options_menu = parent_item;
7410 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7412 spell_menu = g_slist_reverse(spell_menu);
7413 for (items = spell_menu;
7414 items; items = items->next) {
7415 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7416 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7417 gtk_widget_show(GTK_WIDGET(menuitem));
7419 g_slist_free(spell_menu);
7421 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7422 gtk_widget_show(parent_item);
7425 static void compose_dict_changed(void *data)
7427 Compose *compose = (Compose *) data;
7429 if(!compose->gtkaspell)
7430 return;
7431 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7432 return;
7434 gtkaspell_highlight_all(compose->gtkaspell);
7435 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7437 #endif
7439 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7441 Compose *compose = (Compose *)data;
7442 GdkEventButton event;
7444 event.button = 3;
7445 event.time = gtk_get_current_event_time();
7446 event.x = 0;
7447 event.y = 0;
7449 return text_clicked(compose->text, &event, compose);
7452 static gboolean compose_force_window_origin = TRUE;
7453 static Compose *compose_create(PrefsAccount *account,
7454 FolderItem *folder,
7455 ComposeMode mode,
7456 gboolean batch)
7458 Compose *compose;
7459 GtkWidget *window;
7460 GtkWidget *vbox;
7461 GtkWidget *menubar;
7462 GtkWidget *handlebox;
7464 GtkWidget *notebook;
7466 GtkWidget *attach_hbox;
7467 GtkWidget *attach_lab1;
7468 GtkWidget *attach_lab2;
7470 GtkWidget *vbox2;
7472 GtkWidget *label;
7473 GtkWidget *subject_hbox;
7474 GtkWidget *subject_frame;
7475 GtkWidget *subject_entry;
7476 GtkWidget *subject;
7477 GtkWidget *paned;
7479 GtkWidget *edit_vbox;
7480 GtkWidget *ruler_hbox;
7481 GtkWidget *ruler;
7482 GtkWidget *scrolledwin;
7483 GtkWidget *text;
7484 GtkTextBuffer *buffer;
7485 GtkClipboard *clipboard;
7487 UndoMain *undostruct;
7489 GtkWidget *popupmenu;
7490 GtkWidget *tmpl_menu;
7491 GtkActionGroup *action_group = NULL;
7493 #if USE_ENCHANT
7494 GtkAspell * gtkaspell = NULL;
7495 #endif
7497 static GdkGeometry geometry;
7499 cm_return_val_if_fail(account != NULL, NULL);
7501 debug_print("Creating compose window...\n");
7502 compose = g_new0(Compose, 1);
7504 compose->batch = batch;
7505 compose->account = account;
7506 compose->folder = folder;
7508 compose->mutex = cm_mutex_new();
7509 compose->set_cursor_pos = -1;
7511 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7513 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7514 gtk_widget_set_size_request(window, prefs_common.compose_width,
7515 prefs_common.compose_height);
7517 if (!geometry.max_width) {
7518 geometry.max_width = gdk_screen_width();
7519 geometry.max_height = gdk_screen_height();
7522 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7523 &geometry, GDK_HINT_MAX_SIZE);
7524 if (!geometry.min_width) {
7525 geometry.min_width = 600;
7526 geometry.min_height = 440;
7528 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7529 &geometry, GDK_HINT_MIN_SIZE);
7531 #ifndef GENERIC_UMPC
7532 if (compose_force_window_origin)
7533 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7534 prefs_common.compose_y);
7535 #endif
7536 g_signal_connect(G_OBJECT(window), "delete_event",
7537 G_CALLBACK(compose_delete_cb), compose);
7538 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7539 gtk_widget_realize(window);
7541 gtkut_widget_set_composer_icon(window);
7543 vbox = gtk_vbox_new(FALSE, 0);
7544 gtk_container_add(GTK_CONTAINER(window), vbox);
7546 compose->ui_manager = gtk_ui_manager_new();
7547 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7548 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7549 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7550 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7551 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7552 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7553 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7554 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7555 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7556 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7558 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7560 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7561 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7562 #ifdef USE_ENCHANT
7563 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7564 #endif
7565 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7566 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7567 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7569 /* Compose menu */
7570 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7571 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7572 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7573 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7574 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7575 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7576 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7577 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7578 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7579 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7580 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7581 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7584 /* Edit menu */
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7586 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7587 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7601 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7602 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7628 #if USE_ENCHANT
7629 /* Spelling menu */
7630 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7632 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7633 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7634 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7635 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7636 #endif
7638 /* Options menu */
7639 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7640 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7641 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7642 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7643 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7645 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7646 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7647 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7648 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7649 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7654 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7655 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7656 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7657 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7658 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7660 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7662 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7668 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7669 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7670 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7675 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)
7676 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)
7677 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7682 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)
7683 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)
7685 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7687 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7688 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)
7689 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7691 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7692 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)
7693 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7695 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7697 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7698 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)
7699 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7700 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7701 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7702 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7704 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7705 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)
7706 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)
7707 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7708 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7710 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7711 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7712 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7713 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7714 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7715 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7717 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7718 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7719 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)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7722 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7724 /* phew. */
7726 /* Tools menu */
7727 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7728 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7729 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7730 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7731 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7732 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7734 /* Help menu */
7735 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7737 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7738 gtk_widget_show_all(menubar);
7740 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7741 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7743 if (prefs_common.toolbar_detachable) {
7744 handlebox = gtk_handle_box_new();
7745 } else {
7746 handlebox = gtk_hbox_new(FALSE, 0);
7748 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7750 gtk_widget_realize(handlebox);
7751 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7752 (gpointer)compose);
7754 vbox2 = gtk_vbox_new(FALSE, 2);
7755 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7756 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7758 /* Notebook */
7759 notebook = gtk_notebook_new();
7760 gtk_widget_show(notebook);
7762 /* header labels and entries */
7763 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7764 compose_create_header(compose),
7765 gtk_label_new_with_mnemonic(_("Hea_der")));
7766 /* attachment list */
7767 attach_hbox = gtk_hbox_new(FALSE, 0);
7768 gtk_widget_show(attach_hbox);
7770 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7771 gtk_widget_show(attach_lab1);
7772 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7774 attach_lab2 = gtk_label_new("");
7775 gtk_widget_show(attach_lab2);
7776 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7778 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7779 compose_create_attach(compose),
7780 attach_hbox);
7781 /* Others Tab */
7782 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7783 compose_create_others(compose),
7784 gtk_label_new_with_mnemonic(_("Othe_rs")));
7786 /* Subject */
7787 subject_hbox = gtk_hbox_new(FALSE, 0);
7788 gtk_widget_show(subject_hbox);
7790 subject_frame = gtk_frame_new(NULL);
7791 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7792 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7793 gtk_widget_show(subject_frame);
7795 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7796 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7797 gtk_widget_show(subject);
7799 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7800 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7801 gtk_widget_show(label);
7803 #ifdef USE_ENCHANT
7804 subject_entry = claws_spell_entry_new();
7805 #else
7806 subject_entry = gtk_entry_new();
7807 #endif
7808 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7809 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7810 G_CALLBACK(compose_grab_focus_cb), compose);
7811 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7812 gtk_widget_show(subject_entry);
7813 compose->subject_entry = subject_entry;
7814 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7816 edit_vbox = gtk_vbox_new(FALSE, 0);
7818 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7820 /* ruler */
7821 ruler_hbox = gtk_hbox_new(FALSE, 0);
7822 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7824 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7825 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7826 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7827 BORDER_WIDTH);
7829 /* text widget */
7830 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7831 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7832 GTK_POLICY_AUTOMATIC,
7833 GTK_POLICY_AUTOMATIC);
7834 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7835 GTK_SHADOW_IN);
7836 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7838 text = gtk_text_view_new();
7839 if (prefs_common.show_compose_margin) {
7840 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7841 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7843 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7844 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7845 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7846 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7847 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7849 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7850 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7851 G_CALLBACK(compose_edit_size_alloc),
7852 ruler);
7853 g_signal_connect(G_OBJECT(buffer), "changed",
7854 G_CALLBACK(compose_changed_cb), compose);
7855 g_signal_connect(G_OBJECT(text), "grab_focus",
7856 G_CALLBACK(compose_grab_focus_cb), compose);
7857 g_signal_connect(G_OBJECT(buffer), "insert_text",
7858 G_CALLBACK(text_inserted), compose);
7859 g_signal_connect(G_OBJECT(text), "button_press_event",
7860 G_CALLBACK(text_clicked), compose);
7861 g_signal_connect(G_OBJECT(text), "popup-menu",
7862 G_CALLBACK(compose_popup_menu), compose);
7863 g_signal_connect(G_OBJECT(subject_entry), "changed",
7864 G_CALLBACK(compose_changed_cb), compose);
7865 g_signal_connect(G_OBJECT(subject_entry), "activate",
7866 G_CALLBACK(compose_subject_entry_activated), compose);
7868 /* drag and drop */
7869 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7870 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7871 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7872 g_signal_connect(G_OBJECT(text), "drag_data_received",
7873 G_CALLBACK(compose_insert_drag_received_cb),
7874 compose);
7875 g_signal_connect(G_OBJECT(text), "drag-drop",
7876 G_CALLBACK(compose_drag_drop),
7877 compose);
7878 g_signal_connect(G_OBJECT(text), "key-press-event",
7879 G_CALLBACK(completion_set_focus_to_subject),
7880 compose);
7881 gtk_widget_show_all(vbox);
7883 /* pane between attach clist and text */
7884 paned = gtk_vpaned_new();
7885 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7886 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7887 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7888 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7889 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7890 G_CALLBACK(compose_notebook_size_alloc), paned);
7892 gtk_widget_show_all(paned);
7895 if (prefs_common.textfont) {
7896 PangoFontDescription *font_desc;
7898 font_desc = pango_font_description_from_string
7899 (prefs_common.textfont);
7900 if (font_desc) {
7901 gtk_widget_modify_font(text, font_desc);
7902 pango_font_description_free(font_desc);
7906 gtk_action_group_add_actions(action_group, compose_popup_entries,
7907 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7908 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7915 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7917 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7918 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7919 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7921 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7923 undostruct = undo_init(text);
7924 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7925 compose);
7927 address_completion_start(window);
7929 compose->window = window;
7930 compose->vbox = vbox;
7931 compose->menubar = menubar;
7932 compose->handlebox = handlebox;
7934 compose->vbox2 = vbox2;
7936 compose->paned = paned;
7938 compose->attach_label = attach_lab2;
7940 compose->notebook = notebook;
7941 compose->edit_vbox = edit_vbox;
7942 compose->ruler_hbox = ruler_hbox;
7943 compose->ruler = ruler;
7944 compose->scrolledwin = scrolledwin;
7945 compose->text = text;
7947 compose->focused_editable = NULL;
7949 compose->popupmenu = popupmenu;
7951 compose->tmpl_menu = tmpl_menu;
7953 compose->mode = mode;
7954 compose->rmode = mode;
7956 compose->targetinfo = NULL;
7957 compose->replyinfo = NULL;
7958 compose->fwdinfo = NULL;
7960 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7961 g_str_equal, (GDestroyNotify) g_free, NULL);
7963 compose->replyto = NULL;
7964 compose->cc = NULL;
7965 compose->bcc = NULL;
7966 compose->followup_to = NULL;
7968 compose->ml_post = NULL;
7970 compose->inreplyto = NULL;
7971 compose->references = NULL;
7972 compose->msgid = NULL;
7973 compose->boundary = NULL;
7975 compose->autowrap = prefs_common.autowrap;
7976 compose->autoindent = prefs_common.auto_indent;
7977 compose->use_signing = FALSE;
7978 compose->use_encryption = FALSE;
7979 compose->privacy_system = NULL;
7980 compose->encdata = NULL;
7982 compose->modified = FALSE;
7984 compose->return_receipt = FALSE;
7986 compose->to_list = NULL;
7987 compose->newsgroup_list = NULL;
7989 compose->undostruct = undostruct;
7991 compose->sig_str = NULL;
7993 compose->exteditor_file = NULL;
7994 compose->exteditor_pid = -1;
7995 compose->exteditor_tag = -1;
7996 compose->exteditor_socket = NULL;
7997 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
7999 compose->folder_update_callback_id =
8000 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8001 compose_update_folder_hook,
8002 (gpointer) compose);
8004 #if USE_ENCHANT
8005 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8006 if (mode != COMPOSE_REDIRECT) {
8007 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8008 strcmp(prefs_common.dictionary, "")) {
8009 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8010 prefs_common.alt_dictionary,
8011 conv_get_locale_charset_str(),
8012 prefs_common.misspelled_col,
8013 prefs_common.check_while_typing,
8014 prefs_common.recheck_when_changing_dict,
8015 prefs_common.use_alternate,
8016 prefs_common.use_both_dicts,
8017 GTK_TEXT_VIEW(text),
8018 GTK_WINDOW(compose->window),
8019 compose_dict_changed,
8020 compose_spell_menu_changed,
8021 compose);
8022 if (!gtkaspell) {
8023 alertpanel_error(_("Spell checker could not "
8024 "be started.\n%s"),
8025 gtkaspell_checkers_strerror());
8026 gtkaspell_checkers_reset_error();
8027 } else {
8028 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8032 compose->gtkaspell = gtkaspell;
8033 compose_spell_menu_changed(compose);
8034 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8035 #endif
8037 compose_select_account(compose, account, TRUE);
8039 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8040 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8042 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8043 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8045 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8046 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8048 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8049 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8051 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8052 if (account->protocol != A_NNTP)
8053 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8054 prefs_common_translated_header_name("To:"));
8055 else
8056 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8057 prefs_common_translated_header_name("Newsgroups:"));
8059 #ifndef USE_ALT_ADDRBOOK
8060 addressbook_set_target_compose(compose);
8061 #endif
8062 if (mode != COMPOSE_REDIRECT)
8063 compose_set_template_menu(compose);
8064 else {
8065 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8068 compose_list = g_list_append(compose_list, compose);
8070 if (!prefs_common.show_ruler)
8071 gtk_widget_hide(ruler_hbox);
8073 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8075 /* Priority */
8076 compose->priority = PRIORITY_NORMAL;
8077 compose_update_priority_menu_item(compose);
8079 compose_set_out_encoding(compose);
8081 /* Actions menu */
8082 compose_update_actions_menu(compose);
8084 /* Privacy Systems menu */
8085 compose_update_privacy_systems_menu(compose);
8087 activate_privacy_system(compose, account, TRUE);
8088 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8089 if (batch) {
8090 gtk_widget_realize(window);
8091 } else {
8092 gtk_widget_show(window);
8095 gtkut_convert_int_to_gdk_color(prefs_common.default_to_bgcolor,
8096 &default_to_bgcolor);
8097 gtkut_convert_int_to_gdk_color(prefs_common.default_to_color,
8098 &default_to_color);
8100 return compose;
8103 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8105 GList *accounts;
8106 GtkWidget *hbox;
8107 GtkWidget *optmenu;
8108 GtkWidget *optmenubox;
8109 GtkWidget *fromlabel;
8110 GtkListStore *menu;
8111 GtkTreeIter iter;
8112 GtkWidget *from_name = NULL;
8114 gint num = 0, def_menu = 0;
8116 accounts = account_get_list();
8117 cm_return_val_if_fail(accounts != NULL, NULL);
8119 optmenubox = gtk_event_box_new();
8120 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8121 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8123 hbox = gtk_hbox_new(FALSE, 4);
8124 from_name = gtk_entry_new();
8126 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8127 G_CALLBACK(compose_grab_focus_cb), compose);
8128 g_signal_connect_after(G_OBJECT(from_name), "activate",
8129 G_CALLBACK(from_name_activate_cb), optmenu);
8131 for (; accounts != NULL; accounts = accounts->next, num++) {
8132 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8133 gchar *name, *from = NULL;
8135 if (ac == compose->account) def_menu = num;
8137 name = g_markup_printf_escaped("<i>%s</i>",
8138 ac->account_name);
8140 if (ac == compose->account) {
8141 if (ac->name && *ac->name) {
8142 gchar *buf;
8143 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8144 from = g_strdup_printf("%s <%s>",
8145 buf, ac->address);
8146 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8147 } else {
8148 from = g_strdup_printf("%s",
8149 ac->address);
8150 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8153 COMBOBOX_ADD(menu, name, ac->account_id);
8154 g_free(name);
8155 g_free(from);
8158 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8160 g_signal_connect(G_OBJECT(optmenu), "changed",
8161 G_CALLBACK(account_activated),
8162 compose);
8163 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8164 G_CALLBACK(compose_entry_popup_extend),
8165 NULL);
8167 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8168 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8170 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8171 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8172 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8174 /* Putting only the GtkEntry into focus chain of parent hbox causes
8175 * the account selector combobox next to it to be unreachable when
8176 * navigating widgets in GtkTable with up/down arrow keys.
8177 * Note: gtk_widget_set_can_focus() was not enough. */
8178 GList *l = NULL;
8179 l = g_list_prepend(l, from_name);
8180 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8181 g_list_free(l);
8183 CLAWS_SET_TIP(optmenubox,
8184 _("Account to use for this email"));
8185 CLAWS_SET_TIP(from_name,
8186 _("Sender address to be used"));
8188 compose->account_combo = optmenu;
8189 compose->from_name = from_name;
8191 return hbox;
8194 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8196 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8197 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8198 Compose *compose = (Compose *) data;
8199 if (active) {
8200 compose->priority = value;
8204 static void compose_reply_change_mode(Compose *compose,
8205 ComposeMode action)
8207 gboolean was_modified = compose->modified;
8209 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8211 cm_return_if_fail(compose->replyinfo != NULL);
8213 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8214 ml = TRUE;
8215 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8216 followup = TRUE;
8217 if (action == COMPOSE_REPLY_TO_ALL)
8218 all = TRUE;
8219 if (action == COMPOSE_REPLY_TO_SENDER)
8220 sender = TRUE;
8221 if (action == COMPOSE_REPLY_TO_LIST)
8222 ml = TRUE;
8224 compose_remove_header_entries(compose);
8225 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8226 if (compose->account->set_autocc && compose->account->auto_cc)
8227 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8229 if (compose->account->set_autobcc && compose->account->auto_bcc)
8230 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8232 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8233 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8234 compose_show_first_last_header(compose, TRUE);
8235 compose->modified = was_modified;
8236 compose_set_title(compose);
8239 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8241 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8242 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8243 Compose *compose = (Compose *) data;
8245 if (active)
8246 compose_reply_change_mode(compose, value);
8249 static void compose_update_priority_menu_item(Compose * compose)
8251 GtkWidget *menuitem = NULL;
8252 switch (compose->priority) {
8253 case PRIORITY_HIGHEST:
8254 menuitem = gtk_ui_manager_get_widget
8255 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8256 break;
8257 case PRIORITY_HIGH:
8258 menuitem = gtk_ui_manager_get_widget
8259 (compose->ui_manager, "/Menu/Options/Priority/High");
8260 break;
8261 case PRIORITY_NORMAL:
8262 menuitem = gtk_ui_manager_get_widget
8263 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8264 break;
8265 case PRIORITY_LOW:
8266 menuitem = gtk_ui_manager_get_widget
8267 (compose->ui_manager, "/Menu/Options/Priority/Low");
8268 break;
8269 case PRIORITY_LOWEST:
8270 menuitem = gtk_ui_manager_get_widget
8271 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8272 break;
8274 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8277 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8279 Compose *compose = (Compose *) data;
8280 gchar *systemid;
8281 gboolean can_sign = FALSE, can_encrypt = FALSE;
8283 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8285 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8286 return;
8288 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8289 g_free(compose->privacy_system);
8290 compose->privacy_system = NULL;
8291 g_free(compose->encdata);
8292 compose->encdata = NULL;
8293 if (systemid != NULL) {
8294 compose->privacy_system = g_strdup(systemid);
8296 can_sign = privacy_system_can_sign(systemid);
8297 can_encrypt = privacy_system_can_encrypt(systemid);
8300 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8302 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8303 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8306 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8308 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8309 GtkWidget *menuitem = NULL;
8310 GList *children, *amenu;
8311 gboolean can_sign = FALSE, can_encrypt = FALSE;
8312 gboolean found = FALSE;
8314 if (compose->privacy_system != NULL) {
8315 gchar *systemid;
8316 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8317 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8318 cm_return_if_fail(menuitem != NULL);
8320 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8321 amenu = children;
8322 menuitem = NULL;
8323 while (amenu != NULL) {
8324 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8325 if (systemid != NULL) {
8326 if (strcmp(systemid, compose->privacy_system) == 0 &&
8327 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8328 menuitem = GTK_WIDGET(amenu->data);
8330 can_sign = privacy_system_can_sign(systemid);
8331 can_encrypt = privacy_system_can_encrypt(systemid);
8332 found = TRUE;
8333 break;
8335 } else if (strlen(compose->privacy_system) == 0 &&
8336 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8337 menuitem = GTK_WIDGET(amenu->data);
8339 can_sign = FALSE;
8340 can_encrypt = FALSE;
8341 found = TRUE;
8342 break;
8345 amenu = amenu->next;
8347 g_list_free(children);
8348 if (menuitem != NULL)
8349 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8351 if (warn && !found && strlen(compose->privacy_system)) {
8352 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8353 "will not be able to sign or encrypt this message."),
8354 compose->privacy_system);
8358 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8359 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8362 static void compose_set_out_encoding(Compose *compose)
8364 CharSet out_encoding;
8365 const gchar *branch = NULL;
8366 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8368 switch(out_encoding) {
8369 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8370 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8371 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8372 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8373 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8374 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8375 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8376 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8377 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8378 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8379 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8380 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8381 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8382 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8383 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8384 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8385 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8386 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8387 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8388 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8389 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8390 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8391 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8392 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8393 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8394 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8395 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8396 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8397 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8398 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8399 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8400 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8401 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8402 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8404 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8407 static void compose_set_template_menu(Compose *compose)
8409 GSList *tmpl_list, *cur;
8410 GtkWidget *menu;
8411 GtkWidget *item;
8413 tmpl_list = template_get_config();
8415 menu = gtk_menu_new();
8417 gtk_menu_set_accel_group (GTK_MENU (menu),
8418 gtk_ui_manager_get_accel_group(compose->ui_manager));
8419 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8420 Template *tmpl = (Template *)cur->data;
8421 gchar *accel_path = NULL;
8422 item = gtk_menu_item_new_with_label(tmpl->name);
8423 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8424 g_signal_connect(G_OBJECT(item), "activate",
8425 G_CALLBACK(compose_template_activate_cb),
8426 compose);
8427 g_object_set_data(G_OBJECT(item), "template", tmpl);
8428 gtk_widget_show(item);
8429 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8430 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8431 g_free(accel_path);
8434 gtk_widget_show(menu);
8435 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8438 void compose_update_actions_menu(Compose *compose)
8440 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8443 static void compose_update_privacy_systems_menu(Compose *compose)
8445 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8446 GSList *systems, *cur;
8447 GtkWidget *widget;
8448 GtkWidget *system_none;
8449 GSList *group;
8450 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8451 GtkWidget *privacy_menu = gtk_menu_new();
8453 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8454 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8456 g_signal_connect(G_OBJECT(system_none), "activate",
8457 G_CALLBACK(compose_set_privacy_system_cb), compose);
8459 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8460 gtk_widget_show(system_none);
8462 systems = privacy_get_system_ids();
8463 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8464 gchar *systemid = cur->data;
8466 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8467 widget = gtk_radio_menu_item_new_with_label(group,
8468 privacy_system_get_name(systemid));
8469 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8470 g_strdup(systemid), g_free);
8471 g_signal_connect(G_OBJECT(widget), "activate",
8472 G_CALLBACK(compose_set_privacy_system_cb), compose);
8474 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8475 gtk_widget_show(widget);
8476 g_free(systemid);
8478 g_slist_free(systems);
8479 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8480 gtk_widget_show_all(privacy_menu);
8481 gtk_widget_show_all(privacy_menuitem);
8484 void compose_reflect_prefs_all(void)
8486 GList *cur;
8487 Compose *compose;
8489 for (cur = compose_list; cur != NULL; cur = cur->next) {
8490 compose = (Compose *)cur->data;
8491 compose_set_template_menu(compose);
8495 void compose_reflect_prefs_pixmap_theme(void)
8497 GList *cur;
8498 Compose *compose;
8500 for (cur = compose_list; cur != NULL; cur = cur->next) {
8501 compose = (Compose *)cur->data;
8502 toolbar_update(TOOLBAR_COMPOSE, compose);
8506 static const gchar *compose_quote_char_from_context(Compose *compose)
8508 const gchar *qmark = NULL;
8510 cm_return_val_if_fail(compose != NULL, NULL);
8512 switch (compose->mode) {
8513 /* use forward-specific quote char */
8514 case COMPOSE_FORWARD:
8515 case COMPOSE_FORWARD_AS_ATTACH:
8516 case COMPOSE_FORWARD_INLINE:
8517 if (compose->folder && compose->folder->prefs &&
8518 compose->folder->prefs->forward_with_format)
8519 qmark = compose->folder->prefs->forward_quotemark;
8520 else if (compose->account->forward_with_format)
8521 qmark = compose->account->forward_quotemark;
8522 else
8523 qmark = prefs_common.fw_quotemark;
8524 break;
8526 /* use reply-specific quote char in all other modes */
8527 default:
8528 if (compose->folder && compose->folder->prefs &&
8529 compose->folder->prefs->reply_with_format)
8530 qmark = compose->folder->prefs->reply_quotemark;
8531 else if (compose->account->reply_with_format)
8532 qmark = compose->account->reply_quotemark;
8533 else
8534 qmark = prefs_common.quotemark;
8535 break;
8538 if (qmark == NULL || *qmark == '\0')
8539 qmark = "> ";
8541 return qmark;
8544 static void compose_template_apply(Compose *compose, Template *tmpl,
8545 gboolean replace)
8547 GtkTextView *text;
8548 GtkTextBuffer *buffer;
8549 GtkTextMark *mark;
8550 GtkTextIter iter;
8551 const gchar *qmark;
8552 gchar *parsed_str = NULL;
8553 gint cursor_pos = 0;
8554 const gchar *err_msg = _("The body of the template has an error at line %d.");
8555 if (!tmpl) return;
8557 /* process the body */
8559 text = GTK_TEXT_VIEW(compose->text);
8560 buffer = gtk_text_view_get_buffer(text);
8562 if (tmpl->value) {
8563 qmark = compose_quote_char_from_context(compose);
8565 if (compose->replyinfo != NULL) {
8567 if (replace)
8568 gtk_text_buffer_set_text(buffer, "", -1);
8569 mark = gtk_text_buffer_get_insert(buffer);
8570 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8572 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8573 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8575 } else if (compose->fwdinfo != NULL) {
8577 if (replace)
8578 gtk_text_buffer_set_text(buffer, "", -1);
8579 mark = gtk_text_buffer_get_insert(buffer);
8580 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8582 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8583 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8585 } else {
8586 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8588 GtkTextIter start, end;
8589 gchar *tmp = NULL;
8591 gtk_text_buffer_get_start_iter(buffer, &start);
8592 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8593 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8595 /* clear the buffer now */
8596 if (replace)
8597 gtk_text_buffer_set_text(buffer, "", -1);
8599 parsed_str = compose_quote_fmt(compose, dummyinfo,
8600 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8601 procmsg_msginfo_free( &dummyinfo );
8603 g_free( tmp );
8605 } else {
8606 if (replace)
8607 gtk_text_buffer_set_text(buffer, "", -1);
8608 mark = gtk_text_buffer_get_insert(buffer);
8609 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8612 if (replace && parsed_str && compose->account->auto_sig)
8613 compose_insert_sig(compose, FALSE);
8615 if (replace && parsed_str) {
8616 gtk_text_buffer_get_start_iter(buffer, &iter);
8617 gtk_text_buffer_place_cursor(buffer, &iter);
8620 if (parsed_str) {
8621 cursor_pos = quote_fmt_get_cursor_pos();
8622 compose->set_cursor_pos = cursor_pos;
8623 if (cursor_pos == -1)
8624 cursor_pos = 0;
8625 gtk_text_buffer_get_start_iter(buffer, &iter);
8626 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8627 gtk_text_buffer_place_cursor(buffer, &iter);
8630 /* process the other fields */
8632 compose_template_apply_fields(compose, tmpl);
8633 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8634 quote_fmt_reset_vartable();
8635 compose_changed_cb(NULL, compose);
8637 #ifdef USE_ENCHANT
8638 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8639 gtkaspell_highlight_all(compose->gtkaspell);
8640 #endif
8643 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8645 MsgInfo* dummyinfo = NULL;
8646 MsgInfo *msginfo = NULL;
8647 gchar *buf = NULL;
8649 if (compose->replyinfo != NULL)
8650 msginfo = compose->replyinfo;
8651 else if (compose->fwdinfo != NULL)
8652 msginfo = compose->fwdinfo;
8653 else {
8654 dummyinfo = compose_msginfo_new_from_compose(compose);
8655 msginfo = dummyinfo;
8658 if (tmpl->from && *tmpl->from != '\0') {
8659 #ifdef USE_ENCHANT
8660 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8661 compose->gtkaspell);
8662 #else
8663 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8664 #endif
8665 quote_fmt_scan_string(tmpl->from);
8666 quote_fmt_parse();
8668 buf = quote_fmt_get_buffer();
8669 if (buf == NULL) {
8670 alertpanel_error(_("Template From format error."));
8671 } else {
8672 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8676 if (tmpl->to && *tmpl->to != '\0') {
8677 #ifdef USE_ENCHANT
8678 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8679 compose->gtkaspell);
8680 #else
8681 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8682 #endif
8683 quote_fmt_scan_string(tmpl->to);
8684 quote_fmt_parse();
8686 buf = quote_fmt_get_buffer();
8687 if (buf == NULL) {
8688 alertpanel_error(_("Template To format error."));
8689 } else {
8690 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8694 if (tmpl->cc && *tmpl->cc != '\0') {
8695 #ifdef USE_ENCHANT
8696 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8697 compose->gtkaspell);
8698 #else
8699 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8700 #endif
8701 quote_fmt_scan_string(tmpl->cc);
8702 quote_fmt_parse();
8704 buf = quote_fmt_get_buffer();
8705 if (buf == NULL) {
8706 alertpanel_error(_("Template Cc format error."));
8707 } else {
8708 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8712 if (tmpl->bcc && *tmpl->bcc != '\0') {
8713 #ifdef USE_ENCHANT
8714 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8715 compose->gtkaspell);
8716 #else
8717 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8718 #endif
8719 quote_fmt_scan_string(tmpl->bcc);
8720 quote_fmt_parse();
8722 buf = quote_fmt_get_buffer();
8723 if (buf == NULL) {
8724 alertpanel_error(_("Template Bcc format error."));
8725 } else {
8726 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8730 if (tmpl->replyto && *tmpl->replyto != '\0') {
8731 #ifdef USE_ENCHANT
8732 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8733 compose->gtkaspell);
8734 #else
8735 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8736 #endif
8737 quote_fmt_scan_string(tmpl->replyto);
8738 quote_fmt_parse();
8740 buf = quote_fmt_get_buffer();
8741 if (buf == NULL) {
8742 alertpanel_error(_("Template Reply-To format error."));
8743 } else {
8744 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8748 /* process the subject */
8749 if (tmpl->subject && *tmpl->subject != '\0') {
8750 #ifdef USE_ENCHANT
8751 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8752 compose->gtkaspell);
8753 #else
8754 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8755 #endif
8756 quote_fmt_scan_string(tmpl->subject);
8757 quote_fmt_parse();
8759 buf = quote_fmt_get_buffer();
8760 if (buf == NULL) {
8761 alertpanel_error(_("Template subject format error."));
8762 } else {
8763 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8767 procmsg_msginfo_free( &dummyinfo );
8770 static void compose_destroy(Compose *compose)
8772 GtkAllocation allocation;
8773 GtkTextBuffer *buffer;
8774 GtkClipboard *clipboard;
8776 compose_list = g_list_remove(compose_list, compose);
8778 if (compose->updating) {
8779 debug_print("danger, not destroying anything now\n");
8780 compose->deferred_destroy = TRUE;
8781 return;
8784 /* NOTE: address_completion_end() does nothing with the window
8785 * however this may change. */
8786 address_completion_end(compose->window);
8788 slist_free_strings_full(compose->to_list);
8789 slist_free_strings_full(compose->newsgroup_list);
8790 slist_free_strings_full(compose->header_list);
8792 slist_free_strings_full(extra_headers);
8793 extra_headers = NULL;
8795 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8797 g_hash_table_destroy(compose->email_hashtable);
8799 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8800 compose->folder_update_callback_id);
8802 procmsg_msginfo_free(&(compose->targetinfo));
8803 procmsg_msginfo_free(&(compose->replyinfo));
8804 procmsg_msginfo_free(&(compose->fwdinfo));
8806 g_free(compose->replyto);
8807 g_free(compose->cc);
8808 g_free(compose->bcc);
8809 g_free(compose->newsgroups);
8810 g_free(compose->followup_to);
8812 g_free(compose->ml_post);
8814 g_free(compose->inreplyto);
8815 g_free(compose->references);
8816 g_free(compose->msgid);
8817 g_free(compose->boundary);
8819 g_free(compose->redirect_filename);
8820 if (compose->undostruct)
8821 undo_destroy(compose->undostruct);
8823 g_free(compose->sig_str);
8825 g_free(compose->exteditor_file);
8827 g_free(compose->orig_charset);
8829 g_free(compose->privacy_system);
8830 g_free(compose->encdata);
8832 #ifndef USE_ALT_ADDRBOOK
8833 if (addressbook_get_target_compose() == compose)
8834 addressbook_set_target_compose(NULL);
8835 #endif
8836 #if USE_ENCHANT
8837 if (compose->gtkaspell) {
8838 gtkaspell_delete(compose->gtkaspell);
8839 compose->gtkaspell = NULL;
8841 #endif
8843 if (!compose->batch) {
8844 gtk_widget_get_allocation(compose->window, &allocation);
8845 prefs_common.compose_width = allocation.width;
8846 prefs_common.compose_height = allocation.height;
8849 if (!gtk_widget_get_parent(compose->paned))
8850 gtk_widget_destroy(compose->paned);
8851 gtk_widget_destroy(compose->popupmenu);
8853 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8854 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8855 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8857 gtk_widget_destroy(compose->window);
8858 toolbar_destroy(compose->toolbar);
8859 g_free(compose->toolbar);
8860 cm_mutex_free(compose->mutex);
8861 g_free(compose);
8864 static void compose_attach_info_free(AttachInfo *ainfo)
8866 g_free(ainfo->file);
8867 g_free(ainfo->content_type);
8868 g_free(ainfo->name);
8869 g_free(ainfo->charset);
8870 g_free(ainfo);
8873 static void compose_attach_update_label(Compose *compose)
8875 GtkTreeIter iter;
8876 gint i = 1;
8877 gchar *text;
8878 GtkTreeModel *model;
8880 if(compose == NULL)
8881 return;
8883 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8884 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8885 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8886 return;
8889 while(gtk_tree_model_iter_next(model, &iter))
8890 i++;
8892 text = g_strdup_printf("(%d)", i);
8893 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8894 g_free(text);
8897 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8899 Compose *compose = (Compose *)data;
8900 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8901 GtkTreeSelection *selection;
8902 GList *sel, *cur;
8903 GtkTreeModel *model;
8905 selection = gtk_tree_view_get_selection(tree_view);
8906 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8908 if (!sel)
8909 return;
8911 for (cur = sel; cur != NULL; cur = cur->next) {
8912 GtkTreePath *path = cur->data;
8913 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8914 (model, cur->data);
8915 cur->data = ref;
8916 gtk_tree_path_free(path);
8919 for (cur = sel; cur != NULL; cur = cur->next) {
8920 GtkTreeRowReference *ref = cur->data;
8921 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8922 GtkTreeIter iter;
8924 if (gtk_tree_model_get_iter(model, &iter, path))
8925 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8927 gtk_tree_path_free(path);
8928 gtk_tree_row_reference_free(ref);
8931 g_list_free(sel);
8932 compose_attach_update_label(compose);
8935 static struct _AttachProperty
8937 GtkWidget *window;
8938 GtkWidget *mimetype_entry;
8939 GtkWidget *encoding_optmenu;
8940 GtkWidget *path_entry;
8941 GtkWidget *filename_entry;
8942 GtkWidget *ok_btn;
8943 GtkWidget *cancel_btn;
8944 } attach_prop;
8946 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8948 gtk_tree_path_free((GtkTreePath *)ptr);
8951 static void compose_attach_property(GtkAction *action, gpointer data)
8953 Compose *compose = (Compose *)data;
8954 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8955 AttachInfo *ainfo;
8956 GtkComboBox *optmenu;
8957 GtkTreeSelection *selection;
8958 GList *sel;
8959 GtkTreeModel *model;
8960 GtkTreeIter iter;
8961 GtkTreePath *path;
8962 static gboolean cancelled;
8964 /* only if one selected */
8965 selection = gtk_tree_view_get_selection(tree_view);
8966 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8967 return;
8969 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8970 if (!sel)
8971 return;
8973 path = (GtkTreePath *) sel->data;
8974 gtk_tree_model_get_iter(model, &iter, path);
8975 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8977 if (!ainfo) {
8978 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8979 g_list_free(sel);
8980 return;
8982 g_list_free(sel);
8984 if (!attach_prop.window)
8985 compose_attach_property_create(&cancelled);
8986 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8987 gtk_widget_grab_focus(attach_prop.ok_btn);
8988 gtk_widget_show(attach_prop.window);
8989 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8990 GTK_WINDOW(compose->window));
8992 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8993 if (ainfo->encoding == ENC_UNKNOWN)
8994 combobox_select_by_data(optmenu, ENC_BASE64);
8995 else
8996 combobox_select_by_data(optmenu, ainfo->encoding);
8998 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8999 ainfo->content_type ? ainfo->content_type : "");
9000 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9001 ainfo->file ? ainfo->file : "");
9002 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9003 ainfo->name ? ainfo->name : "");
9005 for (;;) {
9006 const gchar *entry_text;
9007 gchar *text;
9008 gchar *cnttype = NULL;
9009 gchar *file = NULL;
9010 off_t size = 0;
9012 cancelled = FALSE;
9013 gtk_main();
9015 gtk_widget_hide(attach_prop.window);
9016 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9018 if (cancelled)
9019 break;
9021 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9022 if (*entry_text != '\0') {
9023 gchar *p;
9025 text = g_strstrip(g_strdup(entry_text));
9026 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9027 cnttype = g_strdup(text);
9028 g_free(text);
9029 } else {
9030 alertpanel_error(_("Invalid MIME type."));
9031 g_free(text);
9032 continue;
9036 ainfo->encoding = combobox_get_active_data(optmenu);
9038 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9039 if (*entry_text != '\0') {
9040 if (is_file_exist(entry_text) &&
9041 (size = get_file_size(entry_text)) > 0)
9042 file = g_strdup(entry_text);
9043 else {
9044 alertpanel_error
9045 (_("File doesn't exist or is empty."));
9046 g_free(cnttype);
9047 continue;
9051 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9052 if (*entry_text != '\0') {
9053 g_free(ainfo->name);
9054 ainfo->name = g_strdup(entry_text);
9057 if (cnttype) {
9058 g_free(ainfo->content_type);
9059 ainfo->content_type = cnttype;
9061 if (file) {
9062 g_free(ainfo->file);
9063 ainfo->file = file;
9065 if (size)
9066 ainfo->size = (goffset)size;
9068 /* update tree store */
9069 text = to_human_readable(ainfo->size);
9070 gtk_tree_model_get_iter(model, &iter, path);
9071 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9072 COL_MIMETYPE, ainfo->content_type,
9073 COL_SIZE, text,
9074 COL_NAME, ainfo->name,
9075 COL_CHARSET, ainfo->charset,
9076 -1);
9078 break;
9081 gtk_tree_path_free(path);
9084 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9086 label = gtk_label_new(str); \
9087 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9088 GTK_FILL, 0, 0, 0); \
9089 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9091 entry = gtk_entry_new(); \
9092 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9093 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9096 static void compose_attach_property_create(gboolean *cancelled)
9098 GtkWidget *window;
9099 GtkWidget *vbox;
9100 GtkWidget *table;
9101 GtkWidget *label;
9102 GtkWidget *mimetype_entry;
9103 GtkWidget *hbox;
9104 GtkWidget *optmenu;
9105 GtkListStore *optmenu_menu;
9106 GtkWidget *path_entry;
9107 GtkWidget *filename_entry;
9108 GtkWidget *hbbox;
9109 GtkWidget *ok_btn;
9110 GtkWidget *cancel_btn;
9111 GList *mime_type_list, *strlist;
9112 GtkTreeIter iter;
9114 debug_print("Creating attach_property window...\n");
9116 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9117 gtk_widget_set_size_request(window, 480, -1);
9118 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9119 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9120 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9121 g_signal_connect(G_OBJECT(window), "delete_event",
9122 G_CALLBACK(attach_property_delete_event),
9123 cancelled);
9124 g_signal_connect(G_OBJECT(window), "key_press_event",
9125 G_CALLBACK(attach_property_key_pressed),
9126 cancelled);
9128 vbox = gtk_vbox_new(FALSE, 8);
9129 gtk_container_add(GTK_CONTAINER(window), vbox);
9131 table = gtk_table_new(4, 2, FALSE);
9132 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9133 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9134 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9136 label = gtk_label_new(_("MIME type"));
9137 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9138 GTK_FILL, 0, 0, 0);
9139 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9140 #if !GTK_CHECK_VERSION(2, 24, 0)
9141 mimetype_entry = gtk_combo_box_entry_new_text();
9142 #else
9143 mimetype_entry = gtk_combo_box_text_new_with_entry();
9144 #endif
9145 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9146 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9148 /* stuff with list */
9149 mime_type_list = procmime_get_mime_type_list();
9150 strlist = NULL;
9151 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9152 MimeType *type = (MimeType *) mime_type_list->data;
9153 gchar *tmp;
9155 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9157 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9158 g_free(tmp);
9159 else
9160 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9161 (GCompareFunc)strcmp2);
9164 for (mime_type_list = strlist; mime_type_list != NULL;
9165 mime_type_list = mime_type_list->next) {
9166 #if !GTK_CHECK_VERSION(2, 24, 0)
9167 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9168 #else
9169 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9170 #endif
9171 g_free(mime_type_list->data);
9173 g_list_free(strlist);
9174 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9175 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9177 label = gtk_label_new(_("Encoding"));
9178 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9179 GTK_FILL, 0, 0, 0);
9180 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9182 hbox = gtk_hbox_new(FALSE, 0);
9183 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9184 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9186 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9187 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9189 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9190 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9191 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9192 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9193 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9195 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9197 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9198 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9200 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9201 &ok_btn, GTK_STOCK_OK,
9202 NULL, NULL);
9203 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9204 gtk_widget_grab_default(ok_btn);
9206 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9207 G_CALLBACK(attach_property_ok),
9208 cancelled);
9209 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9210 G_CALLBACK(attach_property_cancel),
9211 cancelled);
9213 gtk_widget_show_all(vbox);
9215 attach_prop.window = window;
9216 attach_prop.mimetype_entry = mimetype_entry;
9217 attach_prop.encoding_optmenu = optmenu;
9218 attach_prop.path_entry = path_entry;
9219 attach_prop.filename_entry = filename_entry;
9220 attach_prop.ok_btn = ok_btn;
9221 attach_prop.cancel_btn = cancel_btn;
9224 #undef SET_LABEL_AND_ENTRY
9226 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9228 *cancelled = FALSE;
9229 gtk_main_quit();
9232 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9234 *cancelled = TRUE;
9235 gtk_main_quit();
9238 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9239 gboolean *cancelled)
9241 *cancelled = TRUE;
9242 gtk_main_quit();
9244 return TRUE;
9247 static gboolean attach_property_key_pressed(GtkWidget *widget,
9248 GdkEventKey *event,
9249 gboolean *cancelled)
9251 if (event && event->keyval == GDK_KEY_Escape) {
9252 *cancelled = TRUE;
9253 gtk_main_quit();
9255 if (event && event->keyval == GDK_KEY_Return) {
9256 *cancelled = FALSE;
9257 gtk_main_quit();
9258 return TRUE;
9260 return FALSE;
9263 static void compose_exec_ext_editor(Compose *compose)
9265 #ifdef G_OS_UNIX
9266 gchar *tmp;
9267 GtkWidget *socket;
9268 GdkNativeWindow socket_wid = 0;
9269 pid_t pid;
9270 gint pipe_fds[2];
9272 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9273 G_DIR_SEPARATOR, compose);
9275 if (compose_get_ext_editor_uses_socket()) {
9276 /* Only allow one socket */
9277 if (compose->exteditor_socket != NULL) {
9278 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9279 /* Move the focus off of the socket */
9280 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9282 g_free(tmp);
9283 return;
9285 /* Create the receiving GtkSocket */
9286 socket = gtk_socket_new ();
9287 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9288 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9289 compose);
9290 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9291 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9292 /* Realize the socket so that we can use its ID */
9293 gtk_widget_realize(socket);
9294 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9295 compose->exteditor_socket = socket;
9298 if (pipe(pipe_fds) < 0) {
9299 perror("pipe");
9300 g_free(tmp);
9301 return;
9304 if ((pid = fork()) < 0) {
9305 perror("fork");
9306 g_free(tmp);
9307 return;
9310 if (pid != 0) {
9311 /* close the write side of the pipe */
9312 close(pipe_fds[1]);
9314 compose->exteditor_file = g_strdup(tmp);
9315 compose->exteditor_pid = pid;
9317 compose_set_ext_editor_sensitive(compose, FALSE);
9319 #ifndef G_OS_WIN32
9320 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9321 #else
9322 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9323 #endif
9324 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9325 G_IO_IN,
9326 compose_input_cb,
9327 compose);
9328 } else { /* process-monitoring process */
9329 pid_t pid_ed;
9331 if (setpgid(0, 0))
9332 perror("setpgid");
9334 /* close the read side of the pipe */
9335 close(pipe_fds[0]);
9337 if (compose_write_body_to_file(compose, tmp) < 0) {
9338 fd_write_all(pipe_fds[1], "2\n", 2);
9339 _exit(1);
9342 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9343 if (pid_ed < 0) {
9344 fd_write_all(pipe_fds[1], "1\n", 2);
9345 _exit(1);
9348 /* wait until editor is terminated */
9349 waitpid(pid_ed, NULL, 0);
9351 fd_write_all(pipe_fds[1], "0\n", 2);
9353 close(pipe_fds[1]);
9354 _exit(0);
9357 g_free(tmp);
9358 #endif /* G_OS_UNIX */
9361 #ifdef G_OS_UNIX
9362 static gboolean compose_get_ext_editor_cmd_valid()
9364 gboolean has_s = FALSE;
9365 gboolean has_w = FALSE;
9366 const gchar *p = prefs_common_get_ext_editor_cmd();
9367 if (!p)
9368 return FALSE;
9369 while ((p = strchr(p, '%'))) {
9370 p++;
9371 if (*p == 's') {
9372 if (has_s)
9373 return FALSE;
9374 has_s = TRUE;
9375 } else if (*p == 'w') {
9376 if (has_w)
9377 return FALSE;
9378 has_w = TRUE;
9379 } else {
9380 return FALSE;
9383 return TRUE;
9386 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9388 gchar buf[1024];
9389 gchar *p, *s;
9390 gchar **cmdline;
9391 pid_t pid;
9393 cm_return_val_if_fail(file != NULL, -1);
9395 if ((pid = fork()) < 0) {
9396 perror("fork");
9397 return -1;
9400 if (pid != 0) return pid;
9402 /* grandchild process */
9404 if (setpgid(0, getppid()))
9405 perror("setpgid");
9407 if (compose_get_ext_editor_cmd_valid()) {
9408 if (compose_get_ext_editor_uses_socket()) {
9409 p = g_strdup(prefs_common_get_ext_editor_cmd());
9410 s = strstr(p, "%w");
9411 s[1] = 'u';
9412 if (strstr(p, "%s") < s)
9413 g_snprintf(buf, sizeof(buf), p, file, socket_wid);
9414 else
9415 g_snprintf(buf, sizeof(buf), p, socket_wid, file);
9416 g_free(p);
9417 } else {
9418 g_snprintf(buf, sizeof(buf),
9419 prefs_common_get_ext_editor_cmd(), file);
9421 } else {
9422 if (prefs_common_get_ext_editor_cmd())
9423 g_warning("External editor command-line is invalid: '%s'",
9424 prefs_common_get_ext_editor_cmd());
9425 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9428 cmdline = strsplit_with_quote(buf, " ", 1024);
9429 execvp(cmdline[0], cmdline);
9431 perror("execvp");
9432 g_strfreev(cmdline);
9434 _exit(1);
9437 static gboolean compose_ext_editor_kill(Compose *compose)
9439 pid_t pgid = compose->exteditor_pid * -1;
9440 gint ret;
9442 ret = kill(pgid, 0);
9444 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9445 AlertValue val;
9446 gchar *msg;
9448 msg = g_strdup_printf
9449 (_("The external editor is still working.\n"
9450 "Force terminating the process?\n"
9451 "process group id: %d"), -pgid);
9452 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9453 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9455 g_free(msg);
9457 if (val == G_ALERTALTERNATE) {
9458 g_source_remove(compose->exteditor_tag);
9459 g_io_channel_shutdown(compose->exteditor_ch,
9460 FALSE, NULL);
9461 g_io_channel_unref(compose->exteditor_ch);
9463 if (kill(pgid, SIGTERM) < 0) perror("kill");
9464 waitpid(compose->exteditor_pid, NULL, 0);
9466 g_warning("Terminated process group id: %d. "
9467 "Temporary file: %s", -pgid, compose->exteditor_file);
9469 compose_set_ext_editor_sensitive(compose, TRUE);
9471 g_free(compose->exteditor_file);
9472 compose->exteditor_file = NULL;
9473 compose->exteditor_pid = -1;
9474 compose->exteditor_ch = NULL;
9475 compose->exteditor_tag = -1;
9476 } else
9477 return FALSE;
9480 return TRUE;
9483 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9484 gpointer data)
9486 gchar buf[3] = "3";
9487 Compose *compose = (Compose *)data;
9488 gsize bytes_read;
9490 debug_print("Compose: input from monitoring process\n");
9492 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9493 bytes_read = 0;
9494 buf[0] = '\0';
9497 g_io_channel_shutdown(source, FALSE, NULL);
9498 g_io_channel_unref(source);
9500 waitpid(compose->exteditor_pid, NULL, 0);
9502 if (buf[0] == '0') { /* success */
9503 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9504 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9505 GtkTextIter start, end;
9506 gchar *chars;
9508 gtk_text_buffer_set_text(buffer, "", -1);
9509 compose_insert_file(compose, compose->exteditor_file);
9510 compose_changed_cb(NULL, compose);
9511 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9513 if (claws_unlink(compose->exteditor_file) < 0)
9514 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9516 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9517 gtk_text_buffer_get_start_iter(buffer, &start);
9518 gtk_text_buffer_get_end_iter(buffer, &end);
9519 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9520 if (chars && strlen(chars) > 0)
9521 compose->modified = TRUE;
9522 g_free(chars);
9523 } else if (buf[0] == '1') { /* failed */
9524 g_warning("Couldn't exec external editor");
9525 if (claws_unlink(compose->exteditor_file) < 0)
9526 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9527 } else if (buf[0] == '2') {
9528 g_warning("Couldn't write to file");
9529 } else if (buf[0] == '3') {
9530 g_warning("Pipe read failed");
9533 compose_set_ext_editor_sensitive(compose, TRUE);
9535 g_free(compose->exteditor_file);
9536 compose->exteditor_file = NULL;
9537 compose->exteditor_pid = -1;
9538 compose->exteditor_ch = NULL;
9539 compose->exteditor_tag = -1;
9540 if (compose->exteditor_socket) {
9541 gtk_widget_destroy(compose->exteditor_socket);
9542 compose->exteditor_socket = NULL;
9546 return FALSE;
9549 static char *ext_editor_menu_entries[] = {
9550 "Menu/Message/Send",
9551 "Menu/Message/SendLater",
9552 "Menu/Message/InsertFile",
9553 "Menu/Message/InsertSig",
9554 "Menu/Message/ReplaceSig",
9555 "Menu/Message/Save",
9556 "Menu/Message/Print",
9557 "Menu/Edit",
9558 #if USE_ENCHANT
9559 "Menu/Spelling",
9560 #endif
9561 "Menu/Tools/ShowRuler",
9562 "Menu/Tools/Actions",
9563 "Menu/Help",
9564 NULL
9567 static void compose_set_ext_editor_sensitive(Compose *compose,
9568 gboolean sensitive)
9570 int i;
9572 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9573 cm_menu_set_sensitive_full(compose->ui_manager,
9574 ext_editor_menu_entries[i], sensitive);
9577 if (compose_get_ext_editor_uses_socket()) {
9578 if (sensitive) {
9579 if (compose->exteditor_socket)
9580 gtk_widget_hide(compose->exteditor_socket);
9581 gtk_widget_show(compose->scrolledwin);
9582 if (prefs_common.show_ruler)
9583 gtk_widget_show(compose->ruler_hbox);
9584 /* Fix the focus, as it doesn't go anywhere when the
9585 * socket is hidden or destroyed */
9586 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9587 } else {
9588 g_assert (compose->exteditor_socket != NULL);
9589 /* Fix the focus, as it doesn't go anywhere when the
9590 * edit box is hidden */
9591 if (gtk_widget_is_focus(compose->text))
9592 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9593 gtk_widget_hide(compose->scrolledwin);
9594 gtk_widget_hide(compose->ruler_hbox);
9595 gtk_widget_show(compose->exteditor_socket);
9597 } else {
9598 gtk_widget_set_sensitive(compose->text, sensitive);
9600 if (compose->toolbar->send_btn)
9601 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9602 if (compose->toolbar->sendl_btn)
9603 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9604 if (compose->toolbar->draft_btn)
9605 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9606 if (compose->toolbar->insert_btn)
9607 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9608 if (compose->toolbar->sig_btn)
9609 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9610 if (compose->toolbar->exteditor_btn)
9611 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9612 if (compose->toolbar->linewrap_current_btn)
9613 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9614 if (compose->toolbar->linewrap_all_btn)
9615 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9618 static gboolean compose_get_ext_editor_uses_socket()
9620 return (prefs_common_get_ext_editor_cmd() &&
9621 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9624 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9626 compose->exteditor_socket = NULL;
9627 /* returning FALSE allows destruction of the socket */
9628 return FALSE;
9630 #endif /* G_OS_UNIX */
9633 * compose_undo_state_changed:
9635 * Change the sensivity of the menuentries undo and redo
9637 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9638 gint redo_state, gpointer data)
9640 Compose *compose = (Compose *)data;
9642 switch (undo_state) {
9643 case UNDO_STATE_TRUE:
9644 if (!undostruct->undo_state) {
9645 undostruct->undo_state = TRUE;
9646 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9648 break;
9649 case UNDO_STATE_FALSE:
9650 if (undostruct->undo_state) {
9651 undostruct->undo_state = FALSE;
9652 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9654 break;
9655 case UNDO_STATE_UNCHANGED:
9656 break;
9657 case UNDO_STATE_REFRESH:
9658 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9659 break;
9660 default:
9661 g_warning("Undo state not recognized");
9662 break;
9665 switch (redo_state) {
9666 case UNDO_STATE_TRUE:
9667 if (!undostruct->redo_state) {
9668 undostruct->redo_state = TRUE;
9669 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9671 break;
9672 case UNDO_STATE_FALSE:
9673 if (undostruct->redo_state) {
9674 undostruct->redo_state = FALSE;
9675 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9677 break;
9678 case UNDO_STATE_UNCHANGED:
9679 break;
9680 case UNDO_STATE_REFRESH:
9681 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9682 break;
9683 default:
9684 g_warning("Redo state not recognized");
9685 break;
9689 /* callback functions */
9691 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9692 GtkAllocation *allocation,
9693 GtkPaned *paned)
9695 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9698 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9699 * includes "non-client" (windows-izm) in calculation, so this calculation
9700 * may not be accurate.
9702 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9703 GtkAllocation *allocation,
9704 GtkSHRuler *shruler)
9706 if (prefs_common.show_ruler) {
9707 gint char_width = 0, char_height = 0;
9708 gint line_width_in_chars;
9710 gtkut_get_font_size(GTK_WIDGET(widget),
9711 &char_width, &char_height);
9712 line_width_in_chars =
9713 (allocation->width - allocation->x) / char_width;
9715 /* got the maximum */
9716 gtk_shruler_set_range(GTK_SHRULER(shruler),
9717 0.0, line_width_in_chars, 0);
9720 return TRUE;
9723 typedef struct {
9724 gchar *header;
9725 gchar *entry;
9726 ComposePrefType type;
9727 gboolean entry_marked;
9728 } HeaderEntryState;
9730 static void account_activated(GtkComboBox *optmenu, gpointer data)
9732 Compose *compose = (Compose *)data;
9734 PrefsAccount *ac;
9735 gchar *folderidentifier;
9736 gint account_id = 0;
9737 GtkTreeModel *menu;
9738 GtkTreeIter iter;
9739 GSList *list, *saved_list = NULL;
9740 HeaderEntryState *state;
9741 GtkRcStyle *style = NULL;
9742 #if !GTK_CHECK_VERSION(3, 0, 0)
9743 static GdkColor yellow;
9744 static gboolean color_set = FALSE;
9745 #else
9746 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9747 #endif
9749 /* Get ID of active account in the combo box */
9750 menu = gtk_combo_box_get_model(optmenu);
9751 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9752 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9754 ac = account_find_from_id(account_id);
9755 cm_return_if_fail(ac != NULL);
9757 if (ac != compose->account) {
9758 compose_select_account(compose, ac, FALSE);
9760 for (list = compose->header_list; list; list = list->next) {
9761 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9763 if (hentry->type == PREF_ACCOUNT || !list->next) {
9764 compose_destroy_headerentry(compose, hentry);
9765 continue;
9768 state = g_malloc0(sizeof(HeaderEntryState));
9769 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9770 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9771 state->entry = gtk_editable_get_chars(
9772 GTK_EDITABLE(hentry->entry), 0, -1);
9773 state->type = hentry->type;
9775 #if !GTK_CHECK_VERSION(3, 0, 0)
9776 if (!color_set) {
9777 gdk_color_parse("#f5f6be", &yellow);
9778 color_set = gdk_colormap_alloc_color(
9779 gdk_colormap_get_system(),
9780 &yellow, FALSE, TRUE);
9782 #endif
9784 style = gtk_widget_get_modifier_style(hentry->entry);
9785 state->entry_marked = gdk_color_equal(&yellow,
9786 &style->base[GTK_STATE_NORMAL]);
9788 saved_list = g_slist_append(saved_list, state);
9789 compose_destroy_headerentry(compose, hentry);
9792 compose->header_last = NULL;
9793 g_slist_free(compose->header_list);
9794 compose->header_list = NULL;
9795 compose->header_nextrow = 1;
9796 compose_create_header_entry(compose);
9798 if (ac->set_autocc && ac->auto_cc)
9799 compose_entry_append(compose, ac->auto_cc,
9800 COMPOSE_CC, PREF_ACCOUNT);
9802 if (ac->set_autobcc && ac->auto_bcc)
9803 compose_entry_append(compose, ac->auto_bcc,
9804 COMPOSE_BCC, PREF_ACCOUNT);
9806 if (ac->set_autoreplyto && ac->auto_replyto)
9807 compose_entry_append(compose, ac->auto_replyto,
9808 COMPOSE_REPLYTO, PREF_ACCOUNT);
9810 for (list = saved_list; list; list = list->next) {
9811 state = (HeaderEntryState *) list->data;
9813 compose_add_header_entry(compose, state->header,
9814 state->entry, state->type);
9815 if (state->entry_marked)
9816 compose_entry_mark_default_to(compose, state->entry);
9818 g_free(state->header);
9819 g_free(state->entry);
9820 g_free(state);
9822 g_slist_free(saved_list);
9824 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9825 (ac->protocol == A_NNTP) ?
9826 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9829 /* Set message save folder */
9830 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9831 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9833 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9834 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9836 compose_set_save_to(compose, NULL);
9837 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9838 folderidentifier = folder_item_get_identifier(account_get_special_folder
9839 (compose->account, F_OUTBOX));
9840 compose_set_save_to(compose, folderidentifier);
9841 g_free(folderidentifier);
9845 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9846 GtkTreeViewColumn *column, Compose *compose)
9848 compose_attach_property(NULL, compose);
9851 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9852 gpointer data)
9854 Compose *compose = (Compose *)data;
9855 GtkTreeSelection *attach_selection;
9856 gint attach_nr_selected;
9857 GtkTreePath *path;
9859 if (!event) return FALSE;
9861 if (event->button == 3) {
9862 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9863 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9865 /* If no rows, or just one row is selected, right-click should
9866 * open menu relevant to the row being right-clicked on. We
9867 * achieve that by selecting the clicked row first. If more
9868 * than one row is selected, we shouldn't modify the selection,
9869 * as user may want to remove selected rows (attachments). */
9870 if (attach_nr_selected < 2) {
9871 gtk_tree_selection_unselect_all(attach_selection);
9872 attach_nr_selected = 0;
9873 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
9874 event->x, event->y, &path, NULL, NULL, NULL);
9875 if (path != NULL) {
9876 gtk_tree_selection_select_path(attach_selection, path);
9877 gtk_tree_path_free(path);
9878 attach_nr_selected++;
9882 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9883 /* Properties menu item makes no sense with more than one row
9884 * selected, the properties dialog can only edit one attachment. */
9885 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
9887 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9888 NULL, NULL, event->button, event->time);
9889 return TRUE;
9892 return FALSE;
9895 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9896 gpointer data)
9898 Compose *compose = (Compose *)data;
9900 if (!event) return FALSE;
9902 switch (event->keyval) {
9903 case GDK_KEY_Delete:
9904 compose_attach_remove_selected(NULL, compose);
9905 break;
9907 return FALSE;
9910 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9912 toolbar_comp_set_sensitive(compose, allow);
9913 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9914 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9915 #if USE_ENCHANT
9916 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9917 #endif
9918 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9919 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9920 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9922 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9926 static void compose_send_cb(GtkAction *action, gpointer data)
9928 Compose *compose = (Compose *)data;
9930 #ifdef G_OS_UNIX
9931 if (compose->exteditor_tag != -1) {
9932 debug_print("ignoring send: external editor still open\n");
9933 return;
9935 #endif
9936 if (prefs_common.work_offline &&
9937 !inc_offline_should_override(TRUE,
9938 _("Claws Mail needs network access in order "
9939 "to send this email.")))
9940 return;
9942 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9943 g_source_remove(compose->draft_timeout_tag);
9944 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9947 compose_send(compose);
9950 static void compose_send_later_cb(GtkAction *action, gpointer data)
9952 Compose *compose = (Compose *)data;
9953 gint val;
9955 inc_lock();
9956 compose_allow_user_actions(compose, FALSE);
9957 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9958 compose_allow_user_actions(compose, TRUE);
9959 inc_unlock();
9961 if (!val) {
9962 compose_close(compose);
9963 } else if (val == -1) {
9964 alertpanel_error(_("Could not queue message."));
9965 } else if (val == -2) {
9966 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
9967 } else if (val == -3) {
9968 if (privacy_peek_error())
9969 alertpanel_error(_("Could not queue message for sending:\n\n"
9970 "Signature failed: %s"), privacy_get_error());
9971 } else if (val == -4) {
9972 alertpanel_error(_("Could not queue message for sending:\n\n"
9973 "Charset conversion failed."));
9974 } else if (val == -5) {
9975 alertpanel_error(_("Could not queue message for sending:\n\n"
9976 "Couldn't get recipient encryption key."));
9977 } else if (val == -6) {
9978 /* silent error */
9980 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9983 #define DRAFTED_AT_EXIT "drafted_at_exit"
9984 static void compose_register_draft(MsgInfo *info)
9986 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9987 DRAFTED_AT_EXIT, NULL);
9988 FILE *fp = g_fopen(filepath, "ab");
9990 if (fp) {
9991 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9992 info->msgnum);
9993 fclose(fp);
9996 g_free(filepath);
9999 gboolean compose_draft (gpointer data, guint action)
10001 Compose *compose = (Compose *)data;
10002 FolderItem *draft;
10003 gchar *tmp;
10004 gchar *sheaders;
10005 gint msgnum;
10006 MsgFlags flag = {0, 0};
10007 static gboolean lock = FALSE;
10008 MsgInfo *newmsginfo;
10009 FILE *fp;
10010 gboolean target_locked = FALSE;
10011 gboolean err = FALSE;
10013 if (lock) return FALSE;
10015 if (compose->sending)
10016 return TRUE;
10018 draft = account_get_special_folder(compose->account, F_DRAFT);
10019 cm_return_val_if_fail(draft != NULL, FALSE);
10021 if (!g_mutex_trylock(compose->mutex)) {
10022 /* we don't want to lock the mutex once it's available,
10023 * because as the only other part of compose.c locking
10024 * it is compose_close - which means once unlocked,
10025 * the compose struct will be freed */
10026 debug_print("couldn't lock mutex, probably sending\n");
10027 return FALSE;
10030 lock = TRUE;
10032 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10033 G_DIR_SEPARATOR, compose);
10034 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10035 FILE_OP_ERROR(tmp, "fopen");
10036 goto warn_err;
10039 /* chmod for security */
10040 if (change_file_mode_rw(fp, tmp) < 0) {
10041 FILE_OP_ERROR(tmp, "chmod");
10042 g_warning("can't change file mode");
10045 /* Save draft infos */
10046 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10047 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10049 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10050 gchar *savefolderid;
10052 savefolderid = compose_get_save_to(compose);
10053 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10054 g_free(savefolderid);
10056 if (compose->return_receipt) {
10057 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10059 if (compose->privacy_system) {
10060 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10061 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10062 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10065 /* Message-ID of message replying to */
10066 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10067 gchar *folderid = NULL;
10069 if (compose->replyinfo->folder)
10070 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10071 if (folderid == NULL)
10072 folderid = g_strdup("NULL");
10074 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10075 g_free(folderid);
10077 /* Message-ID of message forwarding to */
10078 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10079 gchar *folderid = NULL;
10081 if (compose->fwdinfo->folder)
10082 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10083 if (folderid == NULL)
10084 folderid = g_strdup("NULL");
10086 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10087 g_free(folderid);
10090 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10091 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10093 sheaders = compose_get_manual_headers_info(compose);
10094 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10095 g_free(sheaders);
10097 /* end of headers */
10098 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10100 if (err) {
10101 fclose(fp);
10102 goto warn_err;
10105 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10106 fclose(fp);
10107 goto warn_err;
10109 if (fclose(fp) == EOF) {
10110 goto warn_err;
10113 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10114 if (compose->targetinfo) {
10115 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10116 if (target_locked)
10117 flag.perm_flags |= MSG_LOCKED;
10119 flag.tmp_flags = MSG_DRAFT;
10121 folder_item_scan(draft);
10122 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10123 MsgInfo *tmpinfo = NULL;
10124 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10125 if (compose->msgid) {
10126 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10128 if (tmpinfo) {
10129 msgnum = tmpinfo->msgnum;
10130 procmsg_msginfo_free(&tmpinfo);
10131 debug_print("got draft msgnum %d from scanning\n", msgnum);
10132 } else {
10133 debug_print("didn't get draft msgnum after scanning\n");
10135 } else {
10136 debug_print("got draft msgnum %d from adding\n", msgnum);
10138 if (msgnum < 0) {
10139 warn_err:
10140 claws_unlink(tmp);
10141 g_free(tmp);
10142 if (action != COMPOSE_AUTO_SAVE) {
10143 if (action != COMPOSE_DRAFT_FOR_EXIT)
10144 alertpanel_error(_("Could not save draft."));
10145 else {
10146 AlertValue val;
10147 gtkut_window_popup(compose->window);
10148 val = alertpanel_full(_("Could not save draft"),
10149 _("Could not save draft.\n"
10150 "Do you want to cancel exit or discard this email?"),
10151 _("_Cancel exit"), _("_Discard email"), NULL,
10152 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10153 if (val == G_ALERTALTERNATE) {
10154 lock = FALSE;
10155 g_mutex_unlock(compose->mutex); /* must be done before closing */
10156 compose_close(compose);
10157 return TRUE;
10158 } else {
10159 lock = FALSE;
10160 g_mutex_unlock(compose->mutex); /* must be done before closing */
10161 return FALSE;
10165 goto unlock;
10167 g_free(tmp);
10169 if (compose->mode == COMPOSE_REEDIT) {
10170 compose_remove_reedit_target(compose, TRUE);
10173 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10175 if (newmsginfo) {
10176 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10177 if (target_locked)
10178 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10179 else
10180 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10181 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10182 procmsg_msginfo_set_flags(newmsginfo, 0,
10183 MSG_HAS_ATTACHMENT);
10185 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10186 compose_register_draft(newmsginfo);
10188 procmsg_msginfo_free(&newmsginfo);
10191 folder_item_scan(draft);
10193 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10194 lock = FALSE;
10195 g_mutex_unlock(compose->mutex); /* must be done before closing */
10196 compose_close(compose);
10197 return TRUE;
10198 } else {
10199 GStatBuf s;
10200 gchar *path;
10202 path = folder_item_fetch_msg(draft, msgnum);
10203 if (path == NULL) {
10204 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10205 goto unlock;
10207 if (g_stat(path, &s) < 0) {
10208 FILE_OP_ERROR(path, "stat");
10209 g_free(path);
10210 goto unlock;
10212 g_free(path);
10214 procmsg_msginfo_free(&(compose->targetinfo));
10215 compose->targetinfo = procmsg_msginfo_new();
10216 compose->targetinfo->msgnum = msgnum;
10217 compose->targetinfo->size = (goffset)s.st_size;
10218 compose->targetinfo->mtime = s.st_mtime;
10219 compose->targetinfo->folder = draft;
10220 if (target_locked)
10221 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10222 compose->mode = COMPOSE_REEDIT;
10224 if (action == COMPOSE_AUTO_SAVE) {
10225 compose->autosaved_draft = compose->targetinfo;
10227 compose->modified = FALSE;
10228 compose_set_title(compose);
10230 unlock:
10231 lock = FALSE;
10232 g_mutex_unlock(compose->mutex);
10233 return TRUE;
10236 void compose_clear_exit_drafts(void)
10238 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10239 DRAFTED_AT_EXIT, NULL);
10240 if (is_file_exist(filepath))
10241 claws_unlink(filepath);
10243 g_free(filepath);
10246 void compose_reopen_exit_drafts(void)
10248 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10249 DRAFTED_AT_EXIT, NULL);
10250 FILE *fp = g_fopen(filepath, "rb");
10251 gchar buf[1024];
10253 if (fp) {
10254 while (fgets(buf, sizeof(buf), fp)) {
10255 gchar **parts = g_strsplit(buf, "\t", 2);
10256 const gchar *folder = parts[0];
10257 int msgnum = parts[1] ? atoi(parts[1]):-1;
10259 if (folder && *folder && msgnum > -1) {
10260 FolderItem *item = folder_find_item_from_identifier(folder);
10261 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10262 if (info)
10263 compose_reedit(info, FALSE);
10265 g_strfreev(parts);
10267 fclose(fp);
10269 g_free(filepath);
10270 compose_clear_exit_drafts();
10273 static void compose_save_cb(GtkAction *action, gpointer data)
10275 Compose *compose = (Compose *)data;
10276 compose_draft(compose, COMPOSE_KEEP_EDITING);
10277 compose->rmode = COMPOSE_REEDIT;
10280 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10282 if (compose && file_list) {
10283 GList *tmp;
10285 for ( tmp = file_list; tmp; tmp = tmp->next) {
10286 gchar *file = (gchar *) tmp->data;
10287 gchar *utf8_filename = conv_filename_to_utf8(file);
10288 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10289 compose_changed_cb(NULL, compose);
10290 if (free_data) {
10291 g_free(file);
10292 tmp->data = NULL;
10294 g_free(utf8_filename);
10299 static void compose_attach_cb(GtkAction *action, gpointer data)
10301 Compose *compose = (Compose *)data;
10302 GList *file_list;
10304 if (compose->redirect_filename != NULL)
10305 return;
10307 /* Set focus_window properly, in case we were called via popup menu,
10308 * which unsets it (via focus_out_event callback on compose window). */
10309 manage_window_focus_in(compose->window, NULL, NULL);
10311 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10313 if (file_list) {
10314 compose_attach_from_list(compose, file_list, TRUE);
10315 g_list_free(file_list);
10319 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10321 Compose *compose = (Compose *)data;
10322 GList *file_list;
10323 gint files_inserted = 0;
10325 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10327 if (file_list) {
10328 GList *tmp;
10330 for ( tmp = file_list; tmp; tmp = tmp->next) {
10331 gchar *file = (gchar *) tmp->data;
10332 gchar *filedup = g_strdup(file);
10333 gchar *shortfile = g_path_get_basename(filedup);
10334 ComposeInsertResult res;
10335 /* insert the file if the file is short or if the user confirmed that
10336 he/she wants to insert the large file */
10337 res = compose_insert_file(compose, file);
10338 if (res == COMPOSE_INSERT_READ_ERROR) {
10339 alertpanel_error(_("File '%s' could not be read."), shortfile);
10340 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10341 alertpanel_error(_("File '%s' contained invalid characters\n"
10342 "for the current encoding, insertion may be incorrect."),
10343 shortfile);
10344 } else if (res == COMPOSE_INSERT_SUCCESS)
10345 files_inserted++;
10347 g_free(shortfile);
10348 g_free(filedup);
10349 g_free(file);
10351 g_list_free(file_list);
10354 #ifdef USE_ENCHANT
10355 if (files_inserted > 0 && compose->gtkaspell &&
10356 compose->gtkaspell->check_while_typing)
10357 gtkaspell_highlight_all(compose->gtkaspell);
10358 #endif
10361 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10363 Compose *compose = (Compose *)data;
10365 compose_insert_sig(compose, FALSE);
10368 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10370 Compose *compose = (Compose *)data;
10372 compose_insert_sig(compose, TRUE);
10375 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10376 gpointer data)
10378 gint x, y;
10379 Compose *compose = (Compose *)data;
10381 gtkut_widget_get_uposition(widget, &x, &y);
10382 if (!compose->batch) {
10383 prefs_common.compose_x = x;
10384 prefs_common.compose_y = y;
10386 if (compose->sending || compose->updating)
10387 return TRUE;
10388 compose_close_cb(NULL, compose);
10389 return TRUE;
10392 void compose_close_toolbar(Compose *compose)
10394 compose_close_cb(NULL, compose);
10397 static gboolean compose_can_autosave(Compose *compose)
10399 if (compose->privacy_system && compose->use_encryption)
10400 return prefs_common.autosave && prefs_common.autosave_encrypted;
10401 else
10402 return prefs_common.autosave;
10405 static void compose_close_cb(GtkAction *action, gpointer data)
10407 Compose *compose = (Compose *)data;
10408 AlertValue val;
10410 #ifdef G_OS_UNIX
10411 if (compose->exteditor_tag != -1) {
10412 if (!compose_ext_editor_kill(compose))
10413 return;
10415 #endif
10417 if (compose->modified) {
10418 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10419 if (!g_mutex_trylock(compose->mutex)) {
10420 /* we don't want to lock the mutex once it's available,
10421 * because as the only other part of compose.c locking
10422 * it is compose_close - which means once unlocked,
10423 * the compose struct will be freed */
10424 debug_print("couldn't lock mutex, probably sending\n");
10425 return;
10427 if (!reedit) {
10428 val = alertpanel(_("Discard message"),
10429 _("This message has been modified. Discard it?"),
10430 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10431 } else {
10432 val = alertpanel(_("Save changes"),
10433 _("This message has been modified. Save the latest changes?"),
10434 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10435 GTK_STOCK_CANCEL);
10437 g_mutex_unlock(compose->mutex);
10438 switch (val) {
10439 case G_ALERTDEFAULT:
10440 if (compose_can_autosave(compose) && !reedit)
10441 compose_remove_draft(compose);
10442 break;
10443 case G_ALERTALTERNATE:
10444 compose_draft(data, COMPOSE_QUIT_EDITING);
10445 return;
10446 default:
10447 return;
10451 compose_close(compose);
10454 static void compose_print_cb(GtkAction *action, gpointer data)
10456 Compose *compose = (Compose *) data;
10458 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10459 if (compose->targetinfo)
10460 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10463 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10465 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10466 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10467 Compose *compose = (Compose *) data;
10469 if (active)
10470 compose->out_encoding = (CharSet)value;
10473 static void compose_address_cb(GtkAction *action, gpointer data)
10475 Compose *compose = (Compose *)data;
10477 #ifndef USE_ALT_ADDRBOOK
10478 addressbook_open(compose);
10479 #else
10480 GError* error = NULL;
10481 addressbook_connect_signals(compose);
10482 addressbook_dbus_open(TRUE, &error);
10483 if (error) {
10484 g_warning("%s", error->message);
10485 g_error_free(error);
10487 #endif
10490 static void about_show_cb(GtkAction *action, gpointer data)
10492 about_show();
10495 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10497 Compose *compose = (Compose *)data;
10498 Template *tmpl;
10499 gchar *msg;
10500 AlertValue val;
10502 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10503 cm_return_if_fail(tmpl != NULL);
10505 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10506 tmpl->name);
10507 val = alertpanel(_("Apply template"), msg,
10508 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10509 g_free(msg);
10511 if (val == G_ALERTDEFAULT)
10512 compose_template_apply(compose, tmpl, TRUE);
10513 else if (val == G_ALERTALTERNATE)
10514 compose_template_apply(compose, tmpl, FALSE);
10517 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10519 Compose *compose = (Compose *)data;
10521 #ifdef G_OS_UNIX
10522 if (compose->exteditor_tag != -1) {
10523 debug_print("ignoring open external editor: external editor still open\n");
10524 return;
10526 #endif
10527 compose_exec_ext_editor(compose);
10530 static void compose_undo_cb(GtkAction *action, gpointer data)
10532 Compose *compose = (Compose *)data;
10533 gboolean prev_autowrap = compose->autowrap;
10535 compose->autowrap = FALSE;
10536 undo_undo(compose->undostruct);
10537 compose->autowrap = prev_autowrap;
10540 static void compose_redo_cb(GtkAction *action, gpointer data)
10542 Compose *compose = (Compose *)data;
10543 gboolean prev_autowrap = compose->autowrap;
10545 compose->autowrap = FALSE;
10546 undo_redo(compose->undostruct);
10547 compose->autowrap = prev_autowrap;
10550 static void entry_cut_clipboard(GtkWidget *entry)
10552 if (GTK_IS_EDITABLE(entry))
10553 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10554 else if (GTK_IS_TEXT_VIEW(entry))
10555 gtk_text_buffer_cut_clipboard(
10556 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10557 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10558 TRUE);
10561 static void entry_copy_clipboard(GtkWidget *entry)
10563 if (GTK_IS_EDITABLE(entry))
10564 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10565 else if (GTK_IS_TEXT_VIEW(entry))
10566 gtk_text_buffer_copy_clipboard(
10567 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10568 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10571 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10572 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10574 if (GTK_IS_TEXT_VIEW(entry)) {
10575 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10576 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10577 GtkTextIter start_iter, end_iter;
10578 gint start, end;
10579 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10581 if (contents == NULL)
10582 return;
10584 /* we shouldn't delete the selection when middle-click-pasting, or we
10585 * can't mid-click-paste our own selection */
10586 if (clip != GDK_SELECTION_PRIMARY) {
10587 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10588 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10591 if (insert_place == NULL) {
10592 /* if insert_place isn't specified, insert at the cursor.
10593 * used for Ctrl-V pasting */
10594 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10595 start = gtk_text_iter_get_offset(&start_iter);
10596 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10597 } else {
10598 /* if insert_place is specified, paste here.
10599 * used for mid-click-pasting */
10600 start = gtk_text_iter_get_offset(insert_place);
10601 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10602 if (prefs_common.primary_paste_unselects)
10603 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10606 if (!wrap) {
10607 /* paste unwrapped: mark the paste so it's not wrapped later */
10608 end = start + strlen(contents);
10609 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10610 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10611 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10612 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10613 /* rewrap paragraph now (after a mid-click-paste) */
10614 mark_start = gtk_text_buffer_get_insert(buffer);
10615 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10616 gtk_text_iter_backward_char(&start_iter);
10617 compose_beautify_paragraph(compose, &start_iter, TRUE);
10619 } else if (GTK_IS_EDITABLE(entry))
10620 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10622 compose->modified = TRUE;
10625 static void entry_allsel(GtkWidget *entry)
10627 if (GTK_IS_EDITABLE(entry))
10628 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10629 else if (GTK_IS_TEXT_VIEW(entry)) {
10630 GtkTextIter startiter, enditer;
10631 GtkTextBuffer *textbuf;
10633 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10634 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10635 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10637 gtk_text_buffer_move_mark_by_name(textbuf,
10638 "selection_bound", &startiter);
10639 gtk_text_buffer_move_mark_by_name(textbuf,
10640 "insert", &enditer);
10644 static void compose_cut_cb(GtkAction *action, gpointer data)
10646 Compose *compose = (Compose *)data;
10647 if (compose->focused_editable
10648 #ifndef GENERIC_UMPC
10649 && gtk_widget_has_focus(compose->focused_editable)
10650 #endif
10652 entry_cut_clipboard(compose->focused_editable);
10655 static void compose_copy_cb(GtkAction *action, gpointer data)
10657 Compose *compose = (Compose *)data;
10658 if (compose->focused_editable
10659 #ifndef GENERIC_UMPC
10660 && gtk_widget_has_focus(compose->focused_editable)
10661 #endif
10663 entry_copy_clipboard(compose->focused_editable);
10666 static void compose_paste_cb(GtkAction *action, gpointer data)
10668 Compose *compose = (Compose *)data;
10669 gint prev_autowrap;
10670 GtkTextBuffer *buffer;
10671 BLOCK_WRAP();
10672 if (compose->focused_editable &&
10673 #ifndef GENERIC_UMPC
10674 gtk_widget_has_focus(compose->focused_editable)
10675 #endif
10677 entry_paste_clipboard(compose, compose->focused_editable,
10678 prefs_common.linewrap_pastes,
10679 GDK_SELECTION_CLIPBOARD, NULL);
10680 UNBLOCK_WRAP();
10682 #ifdef USE_ENCHANT
10683 if (
10684 #ifndef GENERIC_UMPC
10685 gtk_widget_has_focus(compose->text) &&
10686 #endif
10687 compose->gtkaspell &&
10688 compose->gtkaspell->check_while_typing)
10689 gtkaspell_highlight_all(compose->gtkaspell);
10690 #endif
10693 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10695 Compose *compose = (Compose *)data;
10696 gint wrap_quote = prefs_common.linewrap_quote;
10697 if (compose->focused_editable
10698 #ifndef GENERIC_UMPC
10699 && gtk_widget_has_focus(compose->focused_editable)
10700 #endif
10702 /* let text_insert() (called directly or at a later time
10703 * after the gtk_editable_paste_clipboard) know that
10704 * text is to be inserted as a quotation. implemented
10705 * by using a simple refcount... */
10706 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10707 G_OBJECT(compose->focused_editable),
10708 "paste_as_quotation"));
10709 g_object_set_data(G_OBJECT(compose->focused_editable),
10710 "paste_as_quotation",
10711 GINT_TO_POINTER(paste_as_quotation + 1));
10712 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10713 entry_paste_clipboard(compose, compose->focused_editable,
10714 prefs_common.linewrap_pastes,
10715 GDK_SELECTION_CLIPBOARD, NULL);
10716 prefs_common.linewrap_quote = wrap_quote;
10720 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10722 Compose *compose = (Compose *)data;
10723 gint prev_autowrap;
10724 GtkTextBuffer *buffer;
10725 BLOCK_WRAP();
10726 if (compose->focused_editable
10727 #ifndef GENERIC_UMPC
10728 && gtk_widget_has_focus(compose->focused_editable)
10729 #endif
10731 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10732 GDK_SELECTION_CLIPBOARD, NULL);
10733 UNBLOCK_WRAP();
10735 #ifdef USE_ENCHANT
10736 if (
10737 #ifndef GENERIC_UMPC
10738 gtk_widget_has_focus(compose->text) &&
10739 #endif
10740 compose->gtkaspell &&
10741 compose->gtkaspell->check_while_typing)
10742 gtkaspell_highlight_all(compose->gtkaspell);
10743 #endif
10746 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10748 Compose *compose = (Compose *)data;
10749 gint prev_autowrap;
10750 GtkTextBuffer *buffer;
10751 BLOCK_WRAP();
10752 if (compose->focused_editable
10753 #ifndef GENERIC_UMPC
10754 && gtk_widget_has_focus(compose->focused_editable)
10755 #endif
10757 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10758 GDK_SELECTION_CLIPBOARD, NULL);
10759 UNBLOCK_WRAP();
10761 #ifdef USE_ENCHANT
10762 if (
10763 #ifndef GENERIC_UMPC
10764 gtk_widget_has_focus(compose->text) &&
10765 #endif
10766 compose->gtkaspell &&
10767 compose->gtkaspell->check_while_typing)
10768 gtkaspell_highlight_all(compose->gtkaspell);
10769 #endif
10772 static void compose_allsel_cb(GtkAction *action, gpointer data)
10774 Compose *compose = (Compose *)data;
10775 if (compose->focused_editable
10776 #ifndef GENERIC_UMPC
10777 && gtk_widget_has_focus(compose->focused_editable)
10778 #endif
10780 entry_allsel(compose->focused_editable);
10783 static void textview_move_beginning_of_line (GtkTextView *text)
10785 GtkTextBuffer *buffer;
10786 GtkTextMark *mark;
10787 GtkTextIter ins;
10789 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10791 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10792 mark = gtk_text_buffer_get_insert(buffer);
10793 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10794 gtk_text_iter_set_line_offset(&ins, 0);
10795 gtk_text_buffer_place_cursor(buffer, &ins);
10798 static void textview_move_forward_character (GtkTextView *text)
10800 GtkTextBuffer *buffer;
10801 GtkTextMark *mark;
10802 GtkTextIter ins;
10804 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10806 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10807 mark = gtk_text_buffer_get_insert(buffer);
10808 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10809 if (gtk_text_iter_forward_cursor_position(&ins))
10810 gtk_text_buffer_place_cursor(buffer, &ins);
10813 static void textview_move_backward_character (GtkTextView *text)
10815 GtkTextBuffer *buffer;
10816 GtkTextMark *mark;
10817 GtkTextIter ins;
10819 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10821 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10822 mark = gtk_text_buffer_get_insert(buffer);
10823 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10824 if (gtk_text_iter_backward_cursor_position(&ins))
10825 gtk_text_buffer_place_cursor(buffer, &ins);
10828 static void textview_move_forward_word (GtkTextView *text)
10830 GtkTextBuffer *buffer;
10831 GtkTextMark *mark;
10832 GtkTextIter ins;
10833 gint count;
10835 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10837 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10838 mark = gtk_text_buffer_get_insert(buffer);
10839 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10840 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10841 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10842 gtk_text_iter_backward_word_start(&ins);
10843 gtk_text_buffer_place_cursor(buffer, &ins);
10847 static void textview_move_backward_word (GtkTextView *text)
10849 GtkTextBuffer *buffer;
10850 GtkTextMark *mark;
10851 GtkTextIter ins;
10853 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10855 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10856 mark = gtk_text_buffer_get_insert(buffer);
10857 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10858 if (gtk_text_iter_backward_word_starts(&ins, 1))
10859 gtk_text_buffer_place_cursor(buffer, &ins);
10862 static void textview_move_end_of_line (GtkTextView *text)
10864 GtkTextBuffer *buffer;
10865 GtkTextMark *mark;
10866 GtkTextIter ins;
10868 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10870 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10871 mark = gtk_text_buffer_get_insert(buffer);
10872 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10873 if (gtk_text_iter_forward_to_line_end(&ins))
10874 gtk_text_buffer_place_cursor(buffer, &ins);
10877 static void textview_move_next_line (GtkTextView *text)
10879 GtkTextBuffer *buffer;
10880 GtkTextMark *mark;
10881 GtkTextIter ins;
10882 gint offset;
10884 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10886 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10887 mark = gtk_text_buffer_get_insert(buffer);
10888 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10889 offset = gtk_text_iter_get_line_offset(&ins);
10890 if (gtk_text_iter_forward_line(&ins)) {
10891 gtk_text_iter_set_line_offset(&ins, offset);
10892 gtk_text_buffer_place_cursor(buffer, &ins);
10896 static void textview_move_previous_line (GtkTextView *text)
10898 GtkTextBuffer *buffer;
10899 GtkTextMark *mark;
10900 GtkTextIter ins;
10901 gint offset;
10903 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10905 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10906 mark = gtk_text_buffer_get_insert(buffer);
10907 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10908 offset = gtk_text_iter_get_line_offset(&ins);
10909 if (gtk_text_iter_backward_line(&ins)) {
10910 gtk_text_iter_set_line_offset(&ins, offset);
10911 gtk_text_buffer_place_cursor(buffer, &ins);
10915 static void textview_delete_forward_character (GtkTextView *text)
10917 GtkTextBuffer *buffer;
10918 GtkTextMark *mark;
10919 GtkTextIter ins, end_iter;
10921 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10923 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10924 mark = gtk_text_buffer_get_insert(buffer);
10925 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10926 end_iter = ins;
10927 if (gtk_text_iter_forward_char(&end_iter)) {
10928 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10932 static void textview_delete_backward_character (GtkTextView *text)
10934 GtkTextBuffer *buffer;
10935 GtkTextMark *mark;
10936 GtkTextIter ins, end_iter;
10938 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10940 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10941 mark = gtk_text_buffer_get_insert(buffer);
10942 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10943 end_iter = ins;
10944 if (gtk_text_iter_backward_char(&end_iter)) {
10945 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10949 static void textview_delete_forward_word (GtkTextView *text)
10951 GtkTextBuffer *buffer;
10952 GtkTextMark *mark;
10953 GtkTextIter ins, end_iter;
10955 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10957 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10958 mark = gtk_text_buffer_get_insert(buffer);
10959 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10960 end_iter = ins;
10961 if (gtk_text_iter_forward_word_end(&end_iter)) {
10962 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10966 static void textview_delete_backward_word (GtkTextView *text)
10968 GtkTextBuffer *buffer;
10969 GtkTextMark *mark;
10970 GtkTextIter ins, end_iter;
10972 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10974 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10975 mark = gtk_text_buffer_get_insert(buffer);
10976 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10977 end_iter = ins;
10978 if (gtk_text_iter_backward_word_start(&end_iter)) {
10979 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10983 static void textview_delete_line (GtkTextView *text)
10985 GtkTextBuffer *buffer;
10986 GtkTextMark *mark;
10987 GtkTextIter ins, start_iter, end_iter;
10989 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10991 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10992 mark = gtk_text_buffer_get_insert(buffer);
10993 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10995 start_iter = ins;
10996 gtk_text_iter_set_line_offset(&start_iter, 0);
10998 end_iter = ins;
10999 if (gtk_text_iter_ends_line(&end_iter)){
11000 if (!gtk_text_iter_forward_char(&end_iter))
11001 gtk_text_iter_backward_char(&start_iter);
11003 else
11004 gtk_text_iter_forward_to_line_end(&end_iter);
11005 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11008 static void textview_delete_to_line_end (GtkTextView *text)
11010 GtkTextBuffer *buffer;
11011 GtkTextMark *mark;
11012 GtkTextIter ins, end_iter;
11014 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11016 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11017 mark = gtk_text_buffer_get_insert(buffer);
11018 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11019 end_iter = ins;
11020 if (gtk_text_iter_ends_line(&end_iter))
11021 gtk_text_iter_forward_char(&end_iter);
11022 else
11023 gtk_text_iter_forward_to_line_end(&end_iter);
11024 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11027 #define DO_ACTION(name, act) { \
11028 if(!strcmp(name, a_name)) { \
11029 return act; \
11032 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11034 const gchar *a_name = gtk_action_get_name(action);
11035 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11036 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11037 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11038 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11039 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11040 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11041 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11042 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11043 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11044 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11045 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11046 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11047 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11048 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11049 return -1;
11052 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11054 Compose *compose = (Compose *)data;
11055 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11056 ComposeCallAdvancedAction action = -1;
11058 action = compose_call_advanced_action_from_path(gaction);
11060 static struct {
11061 void (*do_action) (GtkTextView *text);
11062 } action_table[] = {
11063 {textview_move_beginning_of_line},
11064 {textview_move_forward_character},
11065 {textview_move_backward_character},
11066 {textview_move_forward_word},
11067 {textview_move_backward_word},
11068 {textview_move_end_of_line},
11069 {textview_move_next_line},
11070 {textview_move_previous_line},
11071 {textview_delete_forward_character},
11072 {textview_delete_backward_character},
11073 {textview_delete_forward_word},
11074 {textview_delete_backward_word},
11075 {textview_delete_line},
11076 {textview_delete_to_line_end}
11079 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11081 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11082 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11083 if (action_table[action].do_action)
11084 action_table[action].do_action(text);
11085 else
11086 g_warning("Not implemented yet.");
11090 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11092 GtkAllocation allocation;
11093 GtkWidget *parent;
11094 gchar *str = NULL;
11096 if (GTK_IS_EDITABLE(widget)) {
11097 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11098 gtk_editable_set_position(GTK_EDITABLE(widget),
11099 strlen(str));
11100 g_free(str);
11101 if ((parent = gtk_widget_get_parent(widget))
11102 && (parent = gtk_widget_get_parent(parent))
11103 && (parent = gtk_widget_get_parent(parent))) {
11104 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11105 gtk_widget_get_allocation(widget, &allocation);
11106 gint y = allocation.y;
11107 gint height = allocation.height;
11108 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11109 (GTK_SCROLLED_WINDOW(parent));
11111 gfloat value = gtk_adjustment_get_value(shown);
11112 gfloat upper = gtk_adjustment_get_upper(shown);
11113 gfloat page_size = gtk_adjustment_get_page_size(shown);
11114 if (y < (int)value) {
11115 gtk_adjustment_set_value(shown, y - 1);
11117 if ((y + height) > ((int)value + (int)page_size)) {
11118 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11119 gtk_adjustment_set_value(shown,
11120 y + height - (int)page_size - 1);
11121 } else {
11122 gtk_adjustment_set_value(shown,
11123 (int)upper - (int)page_size - 1);
11130 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11131 compose->focused_editable = widget;
11133 #ifdef GENERIC_UMPC
11134 if (GTK_IS_TEXT_VIEW(widget)
11135 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11136 g_object_ref(compose->notebook);
11137 g_object_ref(compose->edit_vbox);
11138 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11139 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11140 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11141 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11142 g_object_unref(compose->notebook);
11143 g_object_unref(compose->edit_vbox);
11144 g_signal_handlers_block_by_func(G_OBJECT(widget),
11145 G_CALLBACK(compose_grab_focus_cb),
11146 compose);
11147 gtk_widget_grab_focus(widget);
11148 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11149 G_CALLBACK(compose_grab_focus_cb),
11150 compose);
11151 } else if (!GTK_IS_TEXT_VIEW(widget)
11152 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11153 g_object_ref(compose->notebook);
11154 g_object_ref(compose->edit_vbox);
11155 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11156 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11157 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11158 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11159 g_object_unref(compose->notebook);
11160 g_object_unref(compose->edit_vbox);
11161 g_signal_handlers_block_by_func(G_OBJECT(widget),
11162 G_CALLBACK(compose_grab_focus_cb),
11163 compose);
11164 gtk_widget_grab_focus(widget);
11165 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11166 G_CALLBACK(compose_grab_focus_cb),
11167 compose);
11169 #endif
11172 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11174 compose->modified = TRUE;
11175 // compose_beautify_paragraph(compose, NULL, TRUE);
11176 #ifndef GENERIC_UMPC
11177 compose_set_title(compose);
11178 #endif
11181 static void compose_wrap_cb(GtkAction *action, gpointer data)
11183 Compose *compose = (Compose *)data;
11184 compose_beautify_paragraph(compose, NULL, TRUE);
11187 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11189 Compose *compose = (Compose *)data;
11190 compose_wrap_all_full(compose, TRUE);
11193 static void compose_find_cb(GtkAction *action, gpointer data)
11195 Compose *compose = (Compose *)data;
11197 message_search_compose(compose);
11200 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11201 gpointer data)
11203 Compose *compose = (Compose *)data;
11204 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11205 if (compose->autowrap)
11206 compose_wrap_all_full(compose, TRUE);
11207 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11210 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11211 gpointer data)
11213 Compose *compose = (Compose *)data;
11214 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11217 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11219 Compose *compose = (Compose *)data;
11221 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11224 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11226 Compose *compose = (Compose *)data;
11228 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11231 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11233 g_free(compose->privacy_system);
11234 g_free(compose->encdata);
11236 compose->privacy_system = g_strdup(account->default_privacy_system);
11237 compose_update_privacy_system_menu_item(compose, warn);
11240 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11242 Compose *compose = (Compose *)data;
11244 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11245 gtk_widget_show(compose->ruler_hbox);
11246 prefs_common.show_ruler = TRUE;
11247 } else {
11248 gtk_widget_hide(compose->ruler_hbox);
11249 gtk_widget_queue_resize(compose->edit_vbox);
11250 prefs_common.show_ruler = FALSE;
11254 static void compose_attach_drag_received_cb (GtkWidget *widget,
11255 GdkDragContext *context,
11256 gint x,
11257 gint y,
11258 GtkSelectionData *data,
11259 guint info,
11260 guint time,
11261 gpointer user_data)
11263 Compose *compose = (Compose *)user_data;
11264 GList *list, *tmp;
11265 GdkAtom type;
11267 type = gtk_selection_data_get_data_type(data);
11268 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11269 && gtk_drag_get_source_widget(context) !=
11270 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11271 list = uri_list_extract_filenames(
11272 (const gchar *)gtk_selection_data_get_data(data));
11273 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11274 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11275 compose_attach_append
11276 (compose, (const gchar *)tmp->data,
11277 utf8_filename, NULL, NULL);
11278 g_free(utf8_filename);
11280 if (list) compose_changed_cb(NULL, compose);
11281 list_free_strings(list);
11282 g_list_free(list);
11283 } else if (gtk_drag_get_source_widget(context)
11284 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11285 /* comes from our summaryview */
11286 SummaryView * summaryview = NULL;
11287 GSList * list = NULL, *cur = NULL;
11289 if (mainwindow_get_mainwindow())
11290 summaryview = mainwindow_get_mainwindow()->summaryview;
11292 if (summaryview)
11293 list = summary_get_selected_msg_list(summaryview);
11295 for (cur = list; cur; cur = cur->next) {
11296 MsgInfo *msginfo = (MsgInfo *)cur->data;
11297 gchar *file = NULL;
11298 if (msginfo)
11299 file = procmsg_get_message_file_full(msginfo,
11300 TRUE, TRUE);
11301 if (file) {
11302 compose_attach_append(compose, (const gchar *)file,
11303 (const gchar *)file, "message/rfc822", NULL);
11304 g_free(file);
11307 g_slist_free(list);
11311 static gboolean compose_drag_drop(GtkWidget *widget,
11312 GdkDragContext *drag_context,
11313 gint x, gint y,
11314 guint time, gpointer user_data)
11316 /* not handling this signal makes compose_insert_drag_received_cb
11317 * called twice */
11318 return TRUE;
11321 static gboolean completion_set_focus_to_subject
11322 (GtkWidget *widget,
11323 GdkEventKey *event,
11324 Compose *compose)
11326 cm_return_val_if_fail(compose != NULL, FALSE);
11328 /* make backtab move to subject field */
11329 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11330 gtk_widget_grab_focus(compose->subject_entry);
11331 return TRUE;
11333 return FALSE;
11336 static void compose_insert_drag_received_cb (GtkWidget *widget,
11337 GdkDragContext *drag_context,
11338 gint x,
11339 gint y,
11340 GtkSelectionData *data,
11341 guint info,
11342 guint time,
11343 gpointer user_data)
11345 Compose *compose = (Compose *)user_data;
11346 GList *list, *tmp;
11347 GdkAtom type;
11348 guint num_files;
11349 gchar *msg;
11351 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11352 * does not work */
11353 type = gtk_selection_data_get_data_type(data);
11354 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11355 AlertValue val = G_ALERTDEFAULT;
11356 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11358 list = uri_list_extract_filenames(ddata);
11359 num_files = g_list_length(list);
11360 if (list == NULL && strstr(ddata, "://")) {
11361 /* Assume a list of no files, and data has ://, is a remote link */
11362 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11363 gchar *tmpfile = get_tmp_file();
11364 str_write_to_file(tmpdata, tmpfile);
11365 g_free(tmpdata);
11366 compose_insert_file(compose, tmpfile);
11367 claws_unlink(tmpfile);
11368 g_free(tmpfile);
11369 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11370 compose_beautify_paragraph(compose, NULL, TRUE);
11371 return;
11373 switch (prefs_common.compose_dnd_mode) {
11374 case COMPOSE_DND_ASK:
11375 msg = g_strdup_printf(
11376 ngettext(
11377 "Do you want to insert the contents of the file "
11378 "into the message body, or attach it to the email?",
11379 "Do you want to insert the contents of the %d files "
11380 "into the message body, or attach them to the email?",
11381 num_files),
11382 num_files);
11383 val = alertpanel_full(_("Insert or attach?"), msg,
11384 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11385 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11386 g_free(msg);
11387 break;
11388 case COMPOSE_DND_INSERT:
11389 val = G_ALERTALTERNATE;
11390 break;
11391 case COMPOSE_DND_ATTACH:
11392 val = G_ALERTOTHER;
11393 break;
11394 default:
11395 /* unexpected case */
11396 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11399 if (val & G_ALERTDISABLE) {
11400 val &= ~G_ALERTDISABLE;
11401 /* remember what action to perform by default, only if we don't click Cancel */
11402 if (val == G_ALERTALTERNATE)
11403 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11404 else if (val == G_ALERTOTHER)
11405 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11408 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11409 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11410 list_free_strings(list);
11411 g_list_free(list);
11412 return;
11413 } else if (val == G_ALERTOTHER) {
11414 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11415 list_free_strings(list);
11416 g_list_free(list);
11417 return;
11420 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11421 compose_insert_file(compose, (const gchar *)tmp->data);
11423 list_free_strings(list);
11424 g_list_free(list);
11425 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11426 return;
11430 static void compose_header_drag_received_cb (GtkWidget *widget,
11431 GdkDragContext *drag_context,
11432 gint x,
11433 gint y,
11434 GtkSelectionData *data,
11435 guint info,
11436 guint time,
11437 gpointer user_data)
11439 GtkEditable *entry = (GtkEditable *)user_data;
11440 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11442 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11443 * does not work */
11445 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11446 gchar *decoded=g_new(gchar, strlen(email));
11447 int start = 0;
11449 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11450 gtk_editable_delete_text(entry, 0, -1);
11451 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11452 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11453 g_free(decoded);
11454 return;
11456 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11459 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11461 Compose *compose = (Compose *)data;
11463 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11464 compose->return_receipt = TRUE;
11465 else
11466 compose->return_receipt = FALSE;
11469 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11471 Compose *compose = (Compose *)data;
11473 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11474 compose->remove_references = TRUE;
11475 else
11476 compose->remove_references = FALSE;
11479 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11480 ComposeHeaderEntry *headerentry)
11482 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11483 return FALSE;
11486 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11487 GdkEventKey *event,
11488 ComposeHeaderEntry *headerentry)
11490 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11491 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11492 !(event->state & GDK_MODIFIER_MASK) &&
11493 (event->keyval == GDK_KEY_BackSpace) &&
11494 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11495 gtk_container_remove
11496 (GTK_CONTAINER(headerentry->compose->header_table),
11497 headerentry->combo);
11498 gtk_container_remove
11499 (GTK_CONTAINER(headerentry->compose->header_table),
11500 headerentry->entry);
11501 headerentry->compose->header_list =
11502 g_slist_remove(headerentry->compose->header_list,
11503 headerentry);
11504 g_free(headerentry);
11505 } else if (event->keyval == GDK_KEY_Tab) {
11506 if (headerentry->compose->header_last == headerentry) {
11507 /* Override default next focus, and give it to subject_entry
11508 * instead of notebook tabs
11510 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11511 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11512 return TRUE;
11515 return FALSE;
11518 static gboolean scroll_postpone(gpointer data)
11520 Compose *compose = (Compose *)data;
11522 if (compose->batch)
11523 return FALSE;
11525 GTK_EVENTS_FLUSH();
11526 compose_show_first_last_header(compose, FALSE);
11527 return FALSE;
11530 static void compose_headerentry_changed_cb(GtkWidget *entry,
11531 ComposeHeaderEntry *headerentry)
11533 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11534 compose_create_header_entry(headerentry->compose);
11535 g_signal_handlers_disconnect_matched
11536 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11537 0, 0, NULL, NULL, headerentry);
11539 if (!headerentry->compose->batch)
11540 g_timeout_add(0, scroll_postpone, headerentry->compose);
11544 static gboolean compose_defer_auto_save_draft(Compose *compose)
11546 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11547 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11548 return FALSE;
11551 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11553 GtkAdjustment *vadj;
11555 cm_return_if_fail(compose);
11557 if(compose->batch)
11558 return;
11560 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11561 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11562 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11563 gtk_widget_get_parent(compose->header_table)));
11564 gtk_adjustment_set_value(vadj, (show_first ?
11565 gtk_adjustment_get_lower(vadj) :
11566 (gtk_adjustment_get_upper(vadj) -
11567 gtk_adjustment_get_page_size(vadj))));
11568 gtk_adjustment_changed(vadj);
11571 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11572 const gchar *text, gint len, Compose *compose)
11574 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11575 (G_OBJECT(compose->text), "paste_as_quotation"));
11576 GtkTextMark *mark;
11578 cm_return_if_fail(text != NULL);
11580 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11581 G_CALLBACK(text_inserted),
11582 compose);
11583 if (paste_as_quotation) {
11584 gchar *new_text;
11585 const gchar *qmark;
11586 guint pos = 0;
11587 GtkTextIter start_iter;
11589 if (len < 0)
11590 len = strlen(text);
11592 new_text = g_strndup(text, len);
11594 qmark = compose_quote_char_from_context(compose);
11596 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11597 gtk_text_buffer_place_cursor(buffer, iter);
11599 pos = gtk_text_iter_get_offset(iter);
11601 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11602 _("Quote format error at line %d."));
11603 quote_fmt_reset_vartable();
11604 g_free(new_text);
11605 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11606 GINT_TO_POINTER(paste_as_quotation - 1));
11608 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11609 gtk_text_buffer_place_cursor(buffer, iter);
11610 gtk_text_buffer_delete_mark(buffer, mark);
11612 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11613 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11614 compose_beautify_paragraph(compose, &start_iter, FALSE);
11615 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11616 gtk_text_buffer_delete_mark(buffer, mark);
11617 } else {
11618 if (strcmp(text, "\n") || compose->automatic_break
11619 || gtk_text_iter_starts_line(iter)) {
11620 GtkTextIter before_ins;
11621 gtk_text_buffer_insert(buffer, iter, text, len);
11622 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11623 before_ins = *iter;
11624 gtk_text_iter_backward_chars(&before_ins, len);
11625 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11627 } else {
11628 /* check if the preceding is just whitespace or quote */
11629 GtkTextIter start_line;
11630 gchar *tmp = NULL, *quote = NULL;
11631 gint quote_len = 0, is_normal = 0;
11632 start_line = *iter;
11633 gtk_text_iter_set_line_offset(&start_line, 0);
11634 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11635 g_strstrip(tmp);
11637 if (*tmp == '\0') {
11638 is_normal = 1;
11639 } else {
11640 quote = compose_get_quote_str(buffer, &start_line, &quote_len);
11641 if (quote)
11642 is_normal = 1;
11643 g_free(quote);
11645 g_free(tmp);
11647 if (is_normal) {
11648 gtk_text_buffer_insert(buffer, iter, text, len);
11649 } else {
11650 gtk_text_buffer_insert_with_tags_by_name(buffer,
11651 iter, text, len, "no_join", NULL);
11656 if (!paste_as_quotation) {
11657 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11658 compose_beautify_paragraph(compose, iter, FALSE);
11659 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11660 gtk_text_buffer_delete_mark(buffer, mark);
11663 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11664 G_CALLBACK(text_inserted),
11665 compose);
11666 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11668 if (compose_can_autosave(compose) &&
11669 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11670 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11671 compose->draft_timeout_tag = g_timeout_add
11672 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11675 #if USE_ENCHANT
11676 static void compose_check_all(GtkAction *action, gpointer data)
11678 Compose *compose = (Compose *)data;
11679 if (!compose->gtkaspell)
11680 return;
11682 if (gtk_widget_has_focus(compose->subject_entry))
11683 claws_spell_entry_check_all(
11684 CLAWS_SPELL_ENTRY(compose->subject_entry));
11685 else
11686 gtkaspell_check_all(compose->gtkaspell);
11689 static void compose_highlight_all(GtkAction *action, gpointer data)
11691 Compose *compose = (Compose *)data;
11692 if (compose->gtkaspell) {
11693 claws_spell_entry_recheck_all(
11694 CLAWS_SPELL_ENTRY(compose->subject_entry));
11695 gtkaspell_highlight_all(compose->gtkaspell);
11699 static void compose_check_backwards(GtkAction *action, gpointer data)
11701 Compose *compose = (Compose *)data;
11702 if (!compose->gtkaspell) {
11703 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11704 return;
11707 if (gtk_widget_has_focus(compose->subject_entry))
11708 claws_spell_entry_check_backwards(
11709 CLAWS_SPELL_ENTRY(compose->subject_entry));
11710 else
11711 gtkaspell_check_backwards(compose->gtkaspell);
11714 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11716 Compose *compose = (Compose *)data;
11717 if (!compose->gtkaspell) {
11718 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11719 return;
11722 if (gtk_widget_has_focus(compose->subject_entry))
11723 claws_spell_entry_check_forwards_go(
11724 CLAWS_SPELL_ENTRY(compose->subject_entry));
11725 else
11726 gtkaspell_check_forwards_go(compose->gtkaspell);
11728 #endif
11731 *\brief Guess originating forward account from MsgInfo and several
11732 * "common preference" settings. Return NULL if no guess.
11734 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11736 PrefsAccount *account = NULL;
11738 cm_return_val_if_fail(msginfo, NULL);
11739 cm_return_val_if_fail(msginfo->folder, NULL);
11740 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11742 if (msginfo->folder->prefs->enable_default_account)
11743 account = account_find_from_id(msginfo->folder->prefs->default_account);
11745 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11746 gchar *to;
11747 Xstrdup_a(to, msginfo->to, return NULL);
11748 extract_address(to);
11749 account = account_find_from_address(to, FALSE);
11752 if (!account && prefs_common.forward_account_autosel) {
11753 gchar cc[BUFFSIZE];
11754 if (!procheader_get_header_from_msginfo
11755 (msginfo, cc,sizeof cc , "Cc:")) {
11756 gchar *buf = cc + strlen("Cc:");
11757 extract_address(buf);
11758 account = account_find_from_address(buf, FALSE);
11762 if (!account && prefs_common.forward_account_autosel) {
11763 gchar deliveredto[BUFFSIZE];
11764 if (!procheader_get_header_from_msginfo
11765 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11766 gchar *buf = deliveredto + strlen("Delivered-To:");
11767 extract_address(buf);
11768 account = account_find_from_address(buf, FALSE);
11772 if (!account)
11773 account = msginfo->folder->folder->account;
11775 return account;
11778 gboolean compose_close(Compose *compose)
11780 gint x, y;
11782 cm_return_val_if_fail(compose, FALSE);
11784 if (!g_mutex_trylock(compose->mutex)) {
11785 /* we have to wait for the (possibly deferred by auto-save)
11786 * drafting to be done, before destroying the compose under
11787 * it. */
11788 debug_print("waiting for drafting to finish...\n");
11789 compose_allow_user_actions(compose, FALSE);
11790 if (compose->close_timeout_tag == 0) {
11791 compose->close_timeout_tag =
11792 g_timeout_add (500, (GSourceFunc) compose_close,
11793 compose);
11795 return TRUE;
11798 if (compose->draft_timeout_tag >= 0) {
11799 g_source_remove(compose->draft_timeout_tag);
11800 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11803 gtkut_widget_get_uposition(compose->window, &x, &y);
11804 if (!compose->batch) {
11805 prefs_common.compose_x = x;
11806 prefs_common.compose_y = y;
11808 g_mutex_unlock(compose->mutex);
11809 compose_destroy(compose);
11810 return FALSE;
11814 * Add entry field for each address in list.
11815 * \param compose E-Mail composition object.
11816 * \param listAddress List of (formatted) E-Mail addresses.
11818 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11819 GList *node;
11820 gchar *addr;
11821 node = listAddress;
11822 while( node ) {
11823 addr = ( gchar * ) node->data;
11824 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11825 node = g_list_next( node );
11829 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11830 guint action, gboolean opening_multiple)
11832 gchar *body = NULL;
11833 GSList *new_msglist = NULL;
11834 MsgInfo *tmp_msginfo = NULL;
11835 gboolean originally_enc = FALSE;
11836 gboolean originally_sig = FALSE;
11837 Compose *compose = NULL;
11838 gchar *s_system = NULL;
11840 cm_return_if_fail(msgview != NULL);
11842 cm_return_if_fail(msginfo_list != NULL);
11844 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11845 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11846 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11848 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11849 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11850 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11851 orig_msginfo, mimeinfo);
11852 if (tmp_msginfo != NULL) {
11853 new_msglist = g_slist_append(NULL, tmp_msginfo);
11855 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11856 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11857 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11859 tmp_msginfo->folder = orig_msginfo->folder;
11860 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11861 if (orig_msginfo->tags) {
11862 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11863 tmp_msginfo->folder->tags_dirty = TRUE;
11869 if (!opening_multiple)
11870 body = messageview_get_selection(msgview);
11872 if (new_msglist) {
11873 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11874 procmsg_msginfo_free(&tmp_msginfo);
11875 g_slist_free(new_msglist);
11876 } else
11877 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11879 if (compose && originally_enc) {
11880 compose_force_encryption(compose, compose->account, FALSE, s_system);
11883 if (compose && originally_sig && compose->account->default_sign_reply) {
11884 compose_force_signing(compose, compose->account, s_system);
11886 g_free(s_system);
11887 g_free(body);
11888 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11891 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11892 guint action)
11894 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11895 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11896 GSList *cur = msginfo_list;
11897 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11898 "messages. Opening the windows "
11899 "could take some time. Do you "
11900 "want to continue?"),
11901 g_slist_length(msginfo_list));
11902 if (g_slist_length(msginfo_list) > 9
11903 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11904 != G_ALERTALTERNATE) {
11905 g_free(msg);
11906 return;
11908 g_free(msg);
11909 /* We'll open multiple compose windows */
11910 /* let the WM place the next windows */
11911 compose_force_window_origin = FALSE;
11912 for (; cur; cur = cur->next) {
11913 GSList tmplist;
11914 tmplist.data = cur->data;
11915 tmplist.next = NULL;
11916 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11918 compose_force_window_origin = TRUE;
11919 } else {
11920 /* forwarding multiple mails as attachments is done via a
11921 * single compose window */
11922 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11926 void compose_check_for_email_account(Compose *compose)
11928 PrefsAccount *ac = NULL, *curr = NULL;
11929 GList *list;
11931 if (!compose)
11932 return;
11934 if (compose->account && compose->account->protocol == A_NNTP) {
11935 ac = account_get_cur_account();
11936 if (ac->protocol == A_NNTP) {
11937 list = account_get_list();
11939 for( ; list != NULL ; list = g_list_next(list)) {
11940 curr = (PrefsAccount *) list->data;
11941 if (curr->protocol != A_NNTP) {
11942 ac = curr;
11943 break;
11947 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11948 ac->account_id);
11952 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11953 const gchar *address)
11955 GSList *msginfo_list = NULL;
11956 gchar *body = messageview_get_selection(msgview);
11957 Compose *compose;
11959 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11961 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11962 compose_check_for_email_account(compose);
11963 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11964 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11965 compose_reply_set_subject(compose, msginfo);
11967 g_free(body);
11968 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11971 void compose_set_position(Compose *compose, gint pos)
11973 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11975 gtkut_text_view_set_position(text, pos);
11978 gboolean compose_search_string(Compose *compose,
11979 const gchar *str, gboolean case_sens)
11981 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11983 return gtkut_text_view_search_string(text, str, case_sens);
11986 gboolean compose_search_string_backward(Compose *compose,
11987 const gchar *str, gboolean case_sens)
11989 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11991 return gtkut_text_view_search_string_backward(text, str, case_sens);
11994 /* allocate a msginfo structure and populate its data from a compose data structure */
11995 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11997 MsgInfo *newmsginfo;
11998 GSList *list;
11999 gchar buf[BUFFSIZE];
12001 cm_return_val_if_fail( compose != NULL, NULL );
12003 newmsginfo = procmsg_msginfo_new();
12005 /* date is now */
12006 get_rfc822_date(buf, sizeof(buf));
12007 newmsginfo->date = g_strdup(buf);
12009 /* from */
12010 if (compose->from_name) {
12011 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12012 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12015 /* subject */
12016 if (compose->subject_entry)
12017 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12019 /* to, cc, reply-to, newsgroups */
12020 for (list = compose->header_list; list; list = list->next) {
12021 gchar *header = gtk_editable_get_chars(
12022 GTK_EDITABLE(
12023 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12024 gchar *entry = gtk_editable_get_chars(
12025 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12027 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12028 if ( newmsginfo->to == NULL ) {
12029 newmsginfo->to = g_strdup(entry);
12030 } else if (entry && *entry) {
12031 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12032 g_free(newmsginfo->to);
12033 newmsginfo->to = tmp;
12035 } else
12036 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12037 if ( newmsginfo->cc == NULL ) {
12038 newmsginfo->cc = g_strdup(entry);
12039 } else if (entry && *entry) {
12040 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12041 g_free(newmsginfo->cc);
12042 newmsginfo->cc = tmp;
12044 } else
12045 if ( strcasecmp(header,
12046 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12047 if ( newmsginfo->newsgroups == NULL ) {
12048 newmsginfo->newsgroups = g_strdup(entry);
12049 } else if (entry && *entry) {
12050 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12051 g_free(newmsginfo->newsgroups);
12052 newmsginfo->newsgroups = tmp;
12056 g_free(header);
12057 g_free(entry);
12060 /* other data is unset */
12062 return newmsginfo;
12065 #ifdef USE_ENCHANT
12066 /* update compose's dictionaries from folder dict settings */
12067 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12068 FolderItem *folder_item)
12070 cm_return_if_fail(compose != NULL);
12072 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12073 FolderItemPrefs *prefs = folder_item->prefs;
12075 if (prefs->enable_default_dictionary)
12076 gtkaspell_change_dict(compose->gtkaspell,
12077 prefs->default_dictionary, FALSE);
12078 if (folder_item->prefs->enable_default_alt_dictionary)
12079 gtkaspell_change_alt_dict(compose->gtkaspell,
12080 prefs->default_alt_dictionary);
12081 if (prefs->enable_default_dictionary
12082 || prefs->enable_default_alt_dictionary)
12083 compose_spell_menu_changed(compose);
12086 #endif
12088 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12090 Compose *compose = (Compose *)data;
12092 cm_return_if_fail(compose != NULL);
12094 gtk_widget_grab_focus(compose->text);
12097 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12099 gtk_combo_box_popup(GTK_COMBO_BOX(data));
12104 * End of Source.