2011-10-15 [paul] 3.7.10cvs28
[claws.git] / src / compose.c
blob15d2ac94df5e15519db6c38819388d18f3dfa4d6
1 /*
2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2011 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/>.
20 #ifdef HAVE_CONFIG_H
21 # include "config.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 #include "addressbook.h"
64 #include "folderview.h"
65 #include "procmsg.h"
66 #include "menu.h"
67 #include "stock_pixmap.h"
68 #include "send_message.h"
69 #include "imap.h"
70 #include "news.h"
71 #include "customheader.h"
72 #include "prefs_common.h"
73 #include "prefs_account.h"
74 #include "action.h"
75 #include "account.h"
76 #include "filesel.h"
77 #include "procheader.h"
78 #include "procmime.h"
79 #include "statusbar.h"
80 #include "about.h"
81 #include "base64.h"
82 #include "quoted-printable.h"
83 #include "codeconv.h"
84 #include "utils.h"
85 #include "gtkutils.h"
86 #include "socket.h"
87 #include "alertpanel.h"
88 #include "manage_window.h"
89 #if !GTK_CHECK_VERSION(2, 24, 0)
90 #include "gtkshruler.h"
91 #endif
92 #include "folder.h"
93 #include "addr_compl.h"
94 #include "quote_fmt.h"
95 #include "undo.h"
96 #include "foldersel.h"
97 #include "toolbar.h"
98 #include "inc.h"
99 #include "message_search.h"
100 #include "combobox.h"
101 #include "hooks.h"
102 #include "privacy.h"
103 #include "timing.h"
104 #include "autofaces.h"
105 #include "spell_entry.h"
107 enum
109 COL_MIMETYPE = 0,
110 COL_SIZE = 1,
111 COL_NAME = 2,
112 COL_CHARSET = 3,
113 COL_DATA = 4,
114 COL_AUTODATA = 5,
115 N_COL_COLUMNS
118 #define N_ATTACH_COLS (N_COL_COLUMNS)
120 typedef enum
122 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
123 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
124 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
125 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
126 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
130 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
131 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
132 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
133 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
134 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
136 } ComposeCallAdvancedAction;
138 typedef enum
140 PRIORITY_HIGHEST = 1,
141 PRIORITY_HIGH,
142 PRIORITY_NORMAL,
143 PRIORITY_LOW,
144 PRIORITY_LOWEST
145 } PriorityLevel;
147 typedef enum
149 COMPOSE_INSERT_SUCCESS,
150 COMPOSE_INSERT_READ_ERROR,
151 COMPOSE_INSERT_INVALID_CHARACTER,
152 COMPOSE_INSERT_NO_FILE
153 } ComposeInsertResult;
155 typedef enum
157 COMPOSE_WRITE_FOR_SEND,
158 COMPOSE_WRITE_FOR_STORE
159 } ComposeWriteType;
161 typedef enum
163 COMPOSE_QUOTE_FORCED,
164 COMPOSE_QUOTE_CHECK,
165 COMPOSE_QUOTE_SKIP
166 } ComposeQuoteMode;
168 typedef enum {
169 TO_FIELD_PRESENT,
170 SUBJECT_FIELD_PRESENT,
171 BODY_FIELD_PRESENT,
172 NO_FIELD_PRESENT
173 } MailField;
175 #define B64_LINE_SIZE 57
176 #define B64_BUFFSIZE 77
178 #define MAX_REFERENCES_LEN 999
180 static GList *compose_list = NULL;
182 static Compose *compose_generic_new (PrefsAccount *account,
183 const gchar *to,
184 FolderItem *item,
185 GPtrArray *attach_files,
186 GList *listAddress );
188 static Compose *compose_create (PrefsAccount *account,
189 FolderItem *item,
190 ComposeMode mode,
191 gboolean batch);
193 static void compose_entry_mark_default_to (Compose *compose,
194 const gchar *address);
195 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
196 ComposeQuoteMode quote_mode,
197 gboolean to_all,
198 gboolean to_sender,
199 const gchar *body);
200 static Compose *compose_forward_multiple (PrefsAccount *account,
201 GSList *msginfo_list);
202 static Compose *compose_reply (MsgInfo *msginfo,
203 ComposeQuoteMode quote_mode,
204 gboolean to_all,
205 gboolean to_ml,
206 gboolean to_sender,
207 const gchar *body);
208 static Compose *compose_reply_mode (ComposeMode mode,
209 GSList *msginfo_list,
210 gchar *body);
211 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
212 static void compose_update_privacy_systems_menu(Compose *compose);
214 static GtkWidget *compose_account_option_menu_create
215 (Compose *compose);
216 static void compose_set_out_encoding (Compose *compose);
217 static void compose_set_template_menu (Compose *compose);
218 static void compose_destroy (Compose *compose);
220 static MailField compose_entries_set (Compose *compose,
221 const gchar *mailto,
222 ComposeEntryType to_type);
223 static gint compose_parse_header (Compose *compose,
224 MsgInfo *msginfo);
225 static gchar *compose_parse_references (const gchar *ref,
226 const gchar *msgid);
228 static gchar *compose_quote_fmt (Compose *compose,
229 MsgInfo *msginfo,
230 const gchar *fmt,
231 const gchar *qmark,
232 const gchar *body,
233 gboolean rewrap,
234 gboolean need_unescape,
235 const gchar *err_msg);
237 static void compose_reply_set_entry (Compose *compose,
238 MsgInfo *msginfo,
239 gboolean to_all,
240 gboolean to_ml,
241 gboolean to_sender,
242 gboolean
243 followup_and_reply_to);
244 static void compose_reedit_set_entry (Compose *compose,
245 MsgInfo *msginfo);
247 static void compose_insert_sig (Compose *compose,
248 gboolean replace);
249 static ComposeInsertResult compose_insert_file (Compose *compose,
250 const gchar *file);
252 static gboolean compose_attach_append (Compose *compose,
253 const gchar *file,
254 const gchar *type,
255 const gchar *content_type,
256 const gchar *charset);
257 static void compose_attach_parts (Compose *compose,
258 MsgInfo *msginfo);
260 static gboolean compose_beautify_paragraph (Compose *compose,
261 GtkTextIter *par_iter,
262 gboolean force);
263 static void compose_wrap_all (Compose *compose);
264 static void compose_wrap_all_full (Compose *compose,
265 gboolean autowrap);
267 static void compose_set_title (Compose *compose);
268 static void compose_select_account (Compose *compose,
269 PrefsAccount *account,
270 gboolean init);
272 static PrefsAccount *compose_current_mail_account(void);
273 /* static gint compose_send (Compose *compose); */
274 static gboolean compose_check_for_valid_recipient
275 (Compose *compose);
276 static gboolean compose_check_entries (Compose *compose,
277 gboolean check_everything);
278 static gint compose_write_to_file (Compose *compose,
279 FILE *fp,
280 gint action,
281 gboolean attach_parts);
282 static gint compose_write_body_to_file (Compose *compose,
283 const gchar *file);
284 static gint compose_remove_reedit_target (Compose *compose,
285 gboolean force);
286 static void compose_remove_draft (Compose *compose);
287 static gint compose_queue_sub (Compose *compose,
288 gint *msgnum,
289 FolderItem **item,
290 gchar **msgpath,
291 gboolean check_subject,
292 gboolean remove_reedit_target);
293 static int compose_add_attachments (Compose *compose,
294 MimeInfo *parent);
295 static gchar *compose_get_header (Compose *compose);
297 static void compose_convert_header (Compose *compose,
298 gchar *dest,
299 gint len,
300 gchar *src,
301 gint header_len,
302 gboolean addr_field);
304 static void compose_attach_info_free (AttachInfo *ainfo);
305 static void compose_attach_remove_selected (GtkAction *action,
306 gpointer data);
308 static void compose_template_apply (Compose *compose,
309 Template *tmpl,
310 gboolean replace);
311 static void compose_attach_property (GtkAction *action,
312 gpointer data);
313 static void compose_attach_property_create (gboolean *cancelled);
314 static void attach_property_ok (GtkWidget *widget,
315 gboolean *cancelled);
316 static void attach_property_cancel (GtkWidget *widget,
317 gboolean *cancelled);
318 static gint attach_property_delete_event (GtkWidget *widget,
319 GdkEventAny *event,
320 gboolean *cancelled);
321 static gboolean attach_property_key_pressed (GtkWidget *widget,
322 GdkEventKey *event,
323 gboolean *cancelled);
325 static void compose_exec_ext_editor (Compose *compose);
326 #ifdef G_OS_UNIX
327 static gint compose_exec_ext_editor_real (const gchar *file);
328 static gboolean compose_ext_editor_kill (Compose *compose);
329 static gboolean compose_input_cb (GIOChannel *source,
330 GIOCondition condition,
331 gpointer data);
332 static void compose_set_ext_editor_sensitive (Compose *compose,
333 gboolean sensitive);
334 #endif /* G_OS_UNIX */
336 static void compose_undo_state_changed (UndoMain *undostruct,
337 gint undo_state,
338 gint redo_state,
339 gpointer data);
341 static void compose_create_header_entry (Compose *compose);
342 static void compose_add_header_entry (Compose *compose, const gchar *header,
343 gchar *text, ComposePrefType pref_type);
344 static void compose_remove_header_entries(Compose *compose);
346 static void compose_update_priority_menu_item(Compose * compose);
347 #if USE_ENCHANT
348 static void compose_spell_menu_changed (void *data);
349 static void compose_dict_changed (void *data);
350 #endif
351 static void compose_add_field_list ( Compose *compose,
352 GList *listAddress );
354 /* callback functions */
356 #if !GTK_CHECK_VERSION(2, 24, 0)
357 static gboolean compose_edit_size_alloc (GtkEditable *widget,
358 GtkAllocation *allocation,
359 GtkSHRuler *shruler);
360 #endif
361 static void account_activated (GtkComboBox *optmenu,
362 gpointer data);
363 static void attach_selected (GtkTreeView *tree_view,
364 GtkTreePath *tree_path,
365 GtkTreeViewColumn *column,
366 Compose *compose);
367 static gboolean attach_button_pressed (GtkWidget *widget,
368 GdkEventButton *event,
369 gpointer data);
370 static gboolean attach_key_pressed (GtkWidget *widget,
371 GdkEventKey *event,
372 gpointer data);
373 static void compose_send_cb (GtkAction *action, gpointer data);
374 static void compose_send_later_cb (GtkAction *action, gpointer data);
376 static void compose_save_cb (GtkAction *action,
377 gpointer data);
379 static void compose_attach_cb (GtkAction *action,
380 gpointer data);
381 static void compose_insert_file_cb (GtkAction *action,
382 gpointer data);
383 static void compose_insert_sig_cb (GtkAction *action,
384 gpointer data);
386 static void compose_close_cb (GtkAction *action,
387 gpointer data);
389 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
391 static void compose_address_cb (GtkAction *action,
392 gpointer data);
393 static void about_show_cb (GtkAction *action,
394 gpointer data);
395 static void compose_template_activate_cb(GtkWidget *widget,
396 gpointer data);
398 static void compose_ext_editor_cb (GtkAction *action,
399 gpointer data);
401 static gint compose_delete_cb (GtkWidget *widget,
402 GdkEventAny *event,
403 gpointer data);
405 static void compose_undo_cb (GtkAction *action,
406 gpointer data);
407 static void compose_redo_cb (GtkAction *action,
408 gpointer data);
409 static void compose_cut_cb (GtkAction *action,
410 gpointer data);
411 static void compose_copy_cb (GtkAction *action,
412 gpointer data);
413 static void compose_paste_cb (GtkAction *action,
414 gpointer data);
415 static void compose_paste_as_quote_cb (GtkAction *action,
416 gpointer data);
417 static void compose_paste_no_wrap_cb (GtkAction *action,
418 gpointer data);
419 static void compose_paste_wrap_cb (GtkAction *action,
420 gpointer data);
421 static void compose_allsel_cb (GtkAction *action,
422 gpointer data);
424 static void compose_advanced_action_cb (GtkAction *action,
425 gpointer data);
427 static void compose_grab_focus_cb (GtkWidget *widget,
428 Compose *compose);
430 static void compose_changed_cb (GtkTextBuffer *textbuf,
431 Compose *compose);
433 static void compose_wrap_cb (GtkAction *action,
434 gpointer data);
435 static void compose_wrap_all_cb (GtkAction *action,
436 gpointer data);
437 static void compose_find_cb (GtkAction *action,
438 gpointer data);
439 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
440 gpointer data);
441 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
442 gpointer data);
444 #if !GTK_CHECK_VERSION(2, 24, 0)
445 static void compose_toggle_ruler_cb (GtkToggleAction *action,
446 gpointer data);
447 #endif
448 static void compose_toggle_sign_cb (GtkToggleAction *action,
449 gpointer data);
450 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
451 gpointer data);
452 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
453 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
454 static void activate_privacy_system (Compose *compose,
455 PrefsAccount *account,
456 gboolean warn);
457 static void compose_use_signing(Compose *compose, gboolean use_signing);
458 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
459 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
460 gpointer data);
461 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
462 gpointer data);
463 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
464 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
465 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
467 static void compose_attach_drag_received_cb (GtkWidget *widget,
468 GdkDragContext *drag_context,
469 gint x,
470 gint y,
471 GtkSelectionData *data,
472 guint info,
473 guint time,
474 gpointer user_data);
475 static void compose_insert_drag_received_cb (GtkWidget *widget,
476 GdkDragContext *drag_context,
477 gint x,
478 gint y,
479 GtkSelectionData *data,
480 guint info,
481 guint time,
482 gpointer user_data);
483 static void compose_header_drag_received_cb (GtkWidget *widget,
484 GdkDragContext *drag_context,
485 gint x,
486 gint y,
487 GtkSelectionData *data,
488 guint info,
489 guint time,
490 gpointer user_data);
492 static gboolean compose_drag_drop (GtkWidget *widget,
493 GdkDragContext *drag_context,
494 gint x, gint y,
495 guint time, gpointer user_data);
497 static void text_inserted (GtkTextBuffer *buffer,
498 GtkTextIter *iter,
499 const gchar *text,
500 gint len,
501 Compose *compose);
502 static Compose *compose_generic_reply(MsgInfo *msginfo,
503 ComposeQuoteMode quote_mode,
504 gboolean to_all,
505 gboolean to_ml,
506 gboolean to_sender,
507 gboolean followup_and_reply_to,
508 const gchar *body);
510 static void compose_headerentry_changed_cb (GtkWidget *entry,
511 ComposeHeaderEntry *headerentry);
512 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
513 GdkEventKey *event,
514 ComposeHeaderEntry *headerentry);
515 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
516 ComposeHeaderEntry *headerentry);
518 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
520 static void compose_allow_user_actions (Compose *compose, gboolean allow);
522 static void compose_nothing_cb (GtkAction *action, gpointer data)
527 #if USE_ENCHANT
528 static void compose_check_all (GtkAction *action, gpointer data);
529 static void compose_highlight_all (GtkAction *action, gpointer data);
530 static void compose_check_backwards (GtkAction *action, gpointer data);
531 static void compose_check_forwards_go (GtkAction *action, gpointer data);
532 #endif
534 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
536 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
538 #ifdef USE_ENCHANT
539 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
540 FolderItem *folder_item);
541 #endif
542 static void compose_attach_update_label(Compose *compose);
543 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
544 gboolean respect_default_to);
546 static GtkActionEntry compose_popup_entries[] =
548 {"Compose", NULL, "Compose" },
549 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
550 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
551 {"Compose/---", NULL, "---", NULL, NULL, NULL },
552 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
555 static GtkActionEntry compose_entries[] =
557 {"Menu", NULL, "Menu" },
558 /* menus */
559 {"Message", NULL, N_("_Message") },
560 {"Edit", NULL, N_("_Edit") },
561 #if USE_ENCHANT
562 {"Spelling", NULL, N_("_Spelling") },
563 #endif
564 {"Options", NULL, N_("_Options") },
565 {"Tools", NULL, N_("_Tools") },
566 {"Help", NULL, N_("_Help") },
567 /* Message menu */
568 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
569 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
570 {"Message/---", NULL, "---" },
572 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
573 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
574 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
575 /* {"Message/---", NULL, "---" }, */
576 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
577 /* {"Message/---", NULL, "---" }, */
578 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
580 /* Edit menu */
581 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
582 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
583 {"Edit/---", NULL, "---" },
585 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
586 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
587 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
589 {"Edit/SpecialPaste", NULL, N_("Special paste") },
590 {"Edit/SpecialPaste/AsQuotation", NULL, N_("as _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
591 {"Edit/SpecialPaste/Wrapped", NULL, N_("_wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
592 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
594 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
596 {"Edit/Advanced", NULL, N_("A_dvanced") },
597 {"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*/
598 {"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*/
599 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
600 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
601 {"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*/
602 {"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*/
603 {"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*/
604 {"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*/
605 {"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*/
606 {"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*/
607 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
608 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
609 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
610 {"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*/
612 /* {"Edit/---", NULL, "---" }, */
613 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
615 /* {"Edit/---", NULL, "---" }, */
616 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
617 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
618 /* {"Edit/---", NULL, "---" }, */
619 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
620 #if USE_ENCHANT
621 /* Spelling menu */
622 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
623 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
624 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
625 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
627 {"Spelling/---", NULL, "---" },
628 {"Spelling/Options", NULL, N_("_Options") },
629 #endif
631 /* Options menu */
633 {"Options/ReplyMode", NULL, N_("Reply _mode") },
634 {"Options/---", NULL, "---" },
635 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
636 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
638 /* {"Options/---", NULL, "---" }, */
640 {"Options/Priority", NULL, N_("_Priority") },
642 {"Options/Encoding", NULL, N_("Character _encoding") },
643 {"Options/Encoding/---", NULL, "---" },
644 #define ENC_ACTION(cs_char,c_char,string) \
645 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
647 {"Options/Encoding/Western", NULL, N_("Western European") },
648 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
649 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
650 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
651 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
652 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
653 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
654 {"Options/Encoding/Korean", NULL, N_("Korean") },
655 {"Options/Encoding/Thai", NULL, N_("Thai") },
657 /* Tools menu */
658 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
660 {"Tools/Template", NULL, N_("_Template") },
661 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
662 {"Tools/Actions", NULL, N_("Actio_ns") },
663 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
665 /* Help menu */
666 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
669 static GtkToggleActionEntry compose_toggle_entries[] =
671 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
672 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
673 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
674 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
675 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
676 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
677 #if !GTK_CHECK_VERSION(2, 24, 0)
678 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
679 #endif
682 static GtkRadioActionEntry compose_radio_rm_entries[] =
684 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
685 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
686 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
687 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
690 static GtkRadioActionEntry compose_radio_prio_entries[] =
692 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
693 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
694 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
695 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
696 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
699 static GtkRadioActionEntry compose_radio_enc_entries[] =
701 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
702 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
703 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
704 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
705 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
706 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
707 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
708 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
709 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
710 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
711 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
712 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
713 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
714 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
715 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
716 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
717 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
718 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
719 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
720 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
721 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
722 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
723 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
724 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
725 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
726 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
727 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
728 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
729 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
735 static GtkTargetEntry compose_mime_types[] =
737 {"text/uri-list", 0, 0},
738 {"UTF8_STRING", 0, 0},
739 {"text/plain", 0, 0}
742 static gboolean compose_put_existing_to_front(MsgInfo *info)
744 GList *compose_list = compose_get_compose_list();
745 GList *elem = NULL;
747 if (compose_list) {
748 for (elem = compose_list; elem != NULL && elem->data != NULL;
749 elem = elem->next) {
750 Compose *c = (Compose*)elem->data;
752 if (!c->targetinfo || !c->targetinfo->msgid ||
753 !info->msgid)
754 continue;
756 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
757 gtkut_window_popup(c->window);
758 return TRUE;
762 return FALSE;
765 static GdkColor quote_color1 =
766 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
767 static GdkColor quote_color2 =
768 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
769 static GdkColor quote_color3 =
770 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
772 static GdkColor quote_bgcolor1 =
773 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
774 static GdkColor quote_bgcolor2 =
775 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
776 static GdkColor quote_bgcolor3 =
777 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
779 static GdkColor signature_color = {
780 (gulong)0,
781 (gushort)0x7fff,
782 (gushort)0x7fff,
783 (gushort)0x7fff
786 static GdkColor uri_color = {
787 (gulong)0,
788 (gushort)0,
789 (gushort)0,
790 (gushort)0
793 static void compose_create_tags(GtkTextView *text, Compose *compose)
795 GtkTextBuffer *buffer;
796 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
797 #if !GTK_CHECK_VERSION(2, 24, 0)
798 GdkColormap *cmap;
799 gboolean success[8];
800 int i;
801 #endif
802 GdkColor color[8];
804 buffer = gtk_text_view_get_buffer(text);
806 if (prefs_common.enable_color) {
807 /* grab the quote colors, converting from an int to a GdkColor */
808 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
809 &quote_color1);
810 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
811 &quote_color2);
812 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
813 &quote_color3);
814 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
815 &quote_bgcolor1);
816 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
817 &quote_bgcolor2);
818 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
819 &quote_bgcolor3);
820 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
821 &signature_color);
822 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
823 &uri_color);
824 } else {
825 signature_color = quote_color1 = quote_color2 = quote_color3 =
826 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
829 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
830 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
831 "foreground-gdk", &quote_color1,
832 "paragraph-background-gdk", &quote_bgcolor1,
833 NULL);
834 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
835 "foreground-gdk", &quote_color2,
836 "paragraph-background-gdk", &quote_bgcolor2,
837 NULL);
838 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
839 "foreground-gdk", &quote_color3,
840 "paragraph-background-gdk", &quote_bgcolor3,
841 NULL);
842 } else {
843 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
844 "foreground-gdk", &quote_color1,
845 NULL);
846 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
847 "foreground-gdk", &quote_color2,
848 NULL);
849 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
850 "foreground-gdk", &quote_color3,
851 NULL);
854 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
855 "foreground-gdk", &signature_color,
856 NULL);
858 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
859 "foreground-gdk", &uri_color,
860 NULL);
861 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
862 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
864 color[0] = quote_color1;
865 color[1] = quote_color2;
866 color[2] = quote_color3;
867 color[3] = quote_bgcolor1;
868 color[4] = quote_bgcolor2;
869 color[5] = quote_bgcolor3;
870 color[6] = signature_color;
871 color[7] = uri_color;
872 #if !GTK_CHECK_VERSION(2, 24, 0)
873 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
874 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
876 for (i = 0; i < 8; i++) {
877 if (success[i] == FALSE) {
878 GtkStyle *style;
880 g_warning("Compose: color allocation failed.\n");
881 style = gtk_widget_get_style(GTK_WIDGET(text));
882 quote_color1 = quote_color2 = quote_color3 =
883 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
884 signature_color = uri_color = black;
887 #endif
890 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
891 GPtrArray *attach_files)
893 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
896 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
898 return compose_generic_new(account, mailto, item, NULL, NULL);
901 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
903 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
906 #define SCROLL_TO_CURSOR(compose) { \
907 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
908 gtk_text_view_get_buffer( \
909 GTK_TEXT_VIEW(compose->text))); \
910 gtk_text_view_scroll_mark_onscreen( \
911 GTK_TEXT_VIEW(compose->text), \
912 cmark); \
915 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
917 GtkEditable *entry;
918 if (folderidentifier) {
919 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
920 prefs_common.compose_save_to_history = add_history(
921 prefs_common.compose_save_to_history, folderidentifier);
922 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
923 prefs_common.compose_save_to_history);
926 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
927 if (folderidentifier)
928 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
929 else
930 gtk_entry_set_text(GTK_ENTRY(entry), "");
933 static gchar *compose_get_save_to(Compose *compose)
935 GtkEditable *entry;
936 gchar *result = NULL;
937 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
938 result = gtk_editable_get_chars(entry, 0, -1);
940 if (result) {
941 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
942 prefs_common.compose_save_to_history = add_history(
943 prefs_common.compose_save_to_history, result);
944 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
945 prefs_common.compose_save_to_history);
947 return result;
950 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
951 GPtrArray *attach_files, GList *listAddress )
953 Compose *compose;
954 GtkTextView *textview;
955 GtkTextBuffer *textbuf;
956 GtkTextIter iter;
957 const gchar *subject_format = NULL;
958 const gchar *body_format = NULL;
959 gchar *mailto_from = NULL;
960 PrefsAccount *mailto_account = NULL;
961 MsgInfo* dummyinfo = NULL;
962 gint cursor_pos = -1;
963 MailField mfield = NO_FIELD_PRESENT;
964 gchar* buf;
965 GtkTextMark *mark;
967 /* check if mailto defines a from */
968 if (mailto && *mailto != '\0') {
969 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
970 /* mailto defines a from, check if we can get account prefs from it,
971 if not, the account prefs will be guessed using other ways, but we'll keep
972 the from anyway */
973 if (mailto_from)
974 mailto_account = account_find_from_address(mailto_from, TRUE);
975 if (mailto_account)
976 account = mailto_account;
979 /* if no account prefs set from mailto, set if from folder prefs (if any) */
980 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
981 account = account_find_from_id(item->prefs->default_account);
983 /* if no account prefs set, fallback to the current one */
984 if (!account) account = cur_account;
985 cm_return_val_if_fail(account != NULL, NULL);
987 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
989 /* override from name if mailto asked for it */
990 if (mailto_from) {
991 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
992 g_free(mailto_from);
993 } else
994 /* override from name according to folder properties */
995 if (item && item->prefs &&
996 item->prefs->compose_with_format &&
997 item->prefs->compose_override_from_format &&
998 *item->prefs->compose_override_from_format != '\0') {
1000 gchar *tmp = NULL;
1001 gchar *buf = NULL;
1003 dummyinfo = compose_msginfo_new_from_compose(compose);
1005 /* decode \-escape sequences in the internal representation of the quote format */
1006 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1007 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1009 #ifdef USE_ENCHANT
1010 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1011 compose->gtkaspell);
1012 #else
1013 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1014 #endif
1015 quote_fmt_scan_string(tmp);
1016 quote_fmt_parse();
1018 buf = quote_fmt_get_buffer();
1019 if (buf == NULL)
1020 alertpanel_error(_("New message From format error."));
1021 else
1022 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1023 quote_fmt_reset_vartable();
1025 g_free(tmp);
1028 compose->replyinfo = NULL;
1029 compose->fwdinfo = NULL;
1031 textview = GTK_TEXT_VIEW(compose->text);
1032 textbuf = gtk_text_view_get_buffer(textview);
1033 compose_create_tags(textview, compose);
1035 undo_block(compose->undostruct);
1036 #ifdef USE_ENCHANT
1037 compose_set_dictionaries_from_folder_prefs(compose, item);
1038 #endif
1040 if (account->auto_sig)
1041 compose_insert_sig(compose, FALSE);
1042 gtk_text_buffer_get_start_iter(textbuf, &iter);
1043 gtk_text_buffer_place_cursor(textbuf, &iter);
1045 if (account->protocol != A_NNTP) {
1046 if (mailto && *mailto != '\0') {
1047 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1049 } else {
1050 compose_set_folder_prefs(compose, item, TRUE);
1052 if (item && item->ret_rcpt) {
1053 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1055 } else {
1056 if (mailto && *mailto != '\0') {
1057 if (!strchr(mailto, '@'))
1058 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1059 else
1060 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1061 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1062 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1063 mfield = TO_FIELD_PRESENT;
1066 * CLAWS: just don't allow return receipt request, even if the user
1067 * may want to send an email. simple but foolproof.
1069 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1071 compose_add_field_list( compose, listAddress );
1073 if (item && item->prefs && item->prefs->compose_with_format) {
1074 subject_format = item->prefs->compose_subject_format;
1075 body_format = item->prefs->compose_body_format;
1076 } else if (account->compose_with_format) {
1077 subject_format = account->compose_subject_format;
1078 body_format = account->compose_body_format;
1079 } else if (prefs_common.compose_with_format) {
1080 subject_format = prefs_common.compose_subject_format;
1081 body_format = prefs_common.compose_body_format;
1084 if (subject_format || body_format) {
1086 if ( subject_format
1087 && *subject_format != '\0' )
1089 gchar *subject = NULL;
1090 gchar *tmp = NULL;
1091 gchar *buf = NULL;
1093 if (!dummyinfo)
1094 dummyinfo = compose_msginfo_new_from_compose(compose);
1096 /* decode \-escape sequences in the internal representation of the quote format */
1097 tmp = g_malloc(strlen(subject_format)+1);
1098 pref_get_unescaped_pref(tmp, subject_format);
1100 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1101 #ifdef USE_ENCHANT
1102 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1103 compose->gtkaspell);
1104 #else
1105 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1106 #endif
1107 quote_fmt_scan_string(tmp);
1108 quote_fmt_parse();
1110 buf = quote_fmt_get_buffer();
1111 if (buf == NULL)
1112 alertpanel_error(_("New message subject format error."));
1113 else
1114 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1115 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1116 quote_fmt_reset_vartable();
1118 g_free(subject);
1119 g_free(tmp);
1120 mfield = SUBJECT_FIELD_PRESENT;
1123 if ( body_format
1124 && *body_format != '\0' )
1126 GtkTextView *text;
1127 GtkTextBuffer *buffer;
1128 GtkTextIter start, end;
1129 gchar *tmp = NULL;
1131 if (!dummyinfo)
1132 dummyinfo = compose_msginfo_new_from_compose(compose);
1134 text = GTK_TEXT_VIEW(compose->text);
1135 buffer = gtk_text_view_get_buffer(text);
1136 gtk_text_buffer_get_start_iter(buffer, &start);
1137 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1138 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1140 compose_quote_fmt(compose, dummyinfo,
1141 body_format,
1142 NULL, tmp, FALSE, TRUE,
1143 _("The body of the \"New message\" template has an error at line %d."));
1144 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1145 quote_fmt_reset_vartable();
1147 g_free(tmp);
1148 #ifdef USE_ENCHANT
1149 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1150 gtkaspell_highlight_all(compose->gtkaspell);
1151 #endif
1152 mfield = BODY_FIELD_PRESENT;
1156 procmsg_msginfo_free( dummyinfo );
1158 if (attach_files) {
1159 gint i;
1160 gchar *file;
1162 for (i = 0; i < attach_files->len; i++) {
1163 file = g_ptr_array_index(attach_files, i);
1164 compose_attach_append(compose, file, file, NULL, NULL);
1168 compose_show_first_last_header(compose, TRUE);
1170 /* Set save folder */
1171 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1172 gchar *folderidentifier;
1174 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1175 folderidentifier = folder_item_get_identifier(item);
1176 compose_set_save_to(compose, folderidentifier);
1177 g_free(folderidentifier);
1180 /* Place cursor according to provided input (mfield) */
1181 switch (mfield) {
1182 case NO_FIELD_PRESENT:
1183 if (compose->header_last)
1184 gtk_widget_grab_focus(compose->header_last->entry);
1185 break;
1186 case TO_FIELD_PRESENT:
1187 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1188 if (buf) {
1189 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1190 g_free(buf);
1192 gtk_widget_grab_focus(compose->subject_entry);
1193 break;
1194 case SUBJECT_FIELD_PRESENT:
1195 textview = GTK_TEXT_VIEW(compose->text);
1196 if (!textview)
1197 break;
1198 textbuf = gtk_text_view_get_buffer(textview);
1199 if (!textbuf)
1200 break;
1201 mark = gtk_text_buffer_get_insert(textbuf);
1202 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1203 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1205 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1206 * only defers where it comes to the variable body
1207 * is not null. If no body is present compose->text
1208 * will be null in which case you cannot place the
1209 * cursor inside the component so. An empty component
1210 * is therefore created before placing the cursor
1212 case BODY_FIELD_PRESENT:
1213 cursor_pos = quote_fmt_get_cursor_pos();
1214 if (cursor_pos == -1)
1215 gtk_widget_grab_focus(compose->header_last->entry);
1216 else
1217 gtk_widget_grab_focus(compose->text);
1218 break;
1221 undo_unblock(compose->undostruct);
1223 if (prefs_common.auto_exteditor)
1224 compose_exec_ext_editor(compose);
1226 compose->draft_timeout_tag = -1;
1227 SCROLL_TO_CURSOR(compose);
1229 compose->modified = FALSE;
1230 compose_set_title(compose);
1232 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1234 return compose;
1237 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1238 gboolean override_pref, const gchar *system)
1240 const gchar *privacy = NULL;
1242 cm_return_if_fail(compose != NULL);
1243 cm_return_if_fail(account != NULL);
1245 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1246 return;
1248 if (system)
1249 privacy = system;
1250 else if (account->default_privacy_system
1251 && strlen(account->default_privacy_system)) {
1252 privacy = account->default_privacy_system;
1253 } else {
1254 GSList *privacy_avail = privacy_get_system_ids();
1255 if (privacy_avail && g_slist_length(privacy_avail)) {
1256 privacy = (gchar *)(privacy_avail->data);
1259 if (privacy != NULL) {
1260 if (system) {
1261 g_free(compose->privacy_system);
1262 compose->privacy_system = NULL;
1264 if (compose->privacy_system == NULL)
1265 compose->privacy_system = g_strdup(privacy);
1266 else if (*(compose->privacy_system) == '\0') {
1267 g_free(compose->privacy_system);
1268 compose->privacy_system = g_strdup(privacy);
1270 compose_update_privacy_system_menu_item(compose, FALSE);
1271 compose_use_encryption(compose, TRUE);
1275 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1277 const gchar *privacy = NULL;
1279 if (system)
1280 privacy = system;
1281 else if (account->default_privacy_system
1282 && strlen(account->default_privacy_system)) {
1283 privacy = account->default_privacy_system;
1284 } else {
1285 GSList *privacy_avail = privacy_get_system_ids();
1286 if (privacy_avail && g_slist_length(privacy_avail)) {
1287 privacy = (gchar *)(privacy_avail->data);
1291 if (privacy != NULL) {
1292 if (system) {
1293 g_free(compose->privacy_system);
1294 compose->privacy_system = NULL;
1296 if (compose->privacy_system == NULL)
1297 compose->privacy_system = g_strdup(privacy);
1298 compose_update_privacy_system_menu_item(compose, FALSE);
1299 compose_use_signing(compose, TRUE);
1303 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1305 MsgInfo *msginfo;
1306 guint list_len;
1307 Compose *compose = NULL;
1309 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1311 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1312 cm_return_val_if_fail(msginfo != NULL, NULL);
1314 list_len = g_slist_length(msginfo_list);
1316 switch (mode) {
1317 case COMPOSE_REPLY:
1318 case COMPOSE_REPLY_TO_ADDRESS:
1319 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1320 FALSE, prefs_common.default_reply_list, FALSE, body);
1321 break;
1322 case COMPOSE_REPLY_WITH_QUOTE:
1323 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1324 FALSE, prefs_common.default_reply_list, FALSE, body);
1325 break;
1326 case COMPOSE_REPLY_WITHOUT_QUOTE:
1327 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1328 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1329 break;
1330 case COMPOSE_REPLY_TO_SENDER:
1331 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1332 FALSE, FALSE, TRUE, body);
1333 break;
1334 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1335 compose = compose_followup_and_reply_to(msginfo,
1336 COMPOSE_QUOTE_CHECK,
1337 FALSE, FALSE, body);
1338 break;
1339 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1340 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1341 FALSE, FALSE, TRUE, body);
1342 break;
1343 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1344 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1345 FALSE, FALSE, TRUE, NULL);
1346 break;
1347 case COMPOSE_REPLY_TO_ALL:
1348 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1349 TRUE, FALSE, FALSE, body);
1350 break;
1351 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1352 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1353 TRUE, FALSE, FALSE, body);
1354 break;
1355 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1356 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1357 TRUE, FALSE, FALSE, NULL);
1358 break;
1359 case COMPOSE_REPLY_TO_LIST:
1360 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1361 FALSE, TRUE, FALSE, body);
1362 break;
1363 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1364 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1365 FALSE, TRUE, FALSE, body);
1366 break;
1367 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1368 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1369 FALSE, TRUE, FALSE, NULL);
1370 break;
1371 case COMPOSE_FORWARD:
1372 if (prefs_common.forward_as_attachment) {
1373 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1374 return compose;
1375 } else {
1376 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1377 return compose;
1379 break;
1380 case COMPOSE_FORWARD_INLINE:
1381 /* check if we reply to more than one Message */
1382 if (list_len == 1) {
1383 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1384 break;
1386 /* more messages FALL THROUGH */
1387 case COMPOSE_FORWARD_AS_ATTACH:
1388 compose = compose_forward_multiple(NULL, msginfo_list);
1389 break;
1390 case COMPOSE_REDIRECT:
1391 compose = compose_redirect(NULL, msginfo, FALSE);
1392 break;
1393 default:
1394 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1397 if (compose == NULL) {
1398 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1399 return NULL;
1402 compose->rmode = mode;
1403 switch (compose->rmode) {
1404 case COMPOSE_REPLY:
1405 case COMPOSE_REPLY_WITH_QUOTE:
1406 case COMPOSE_REPLY_WITHOUT_QUOTE:
1407 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1408 debug_print("reply mode Normal\n");
1409 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1410 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1411 break;
1412 case COMPOSE_REPLY_TO_SENDER:
1413 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1414 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1415 debug_print("reply mode Sender\n");
1416 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1417 break;
1418 case COMPOSE_REPLY_TO_ALL:
1419 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1420 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1421 debug_print("reply mode All\n");
1422 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1423 break;
1424 case COMPOSE_REPLY_TO_LIST:
1425 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1426 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1427 debug_print("reply mode List\n");
1428 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1429 break;
1430 case COMPOSE_REPLY_TO_ADDRESS:
1431 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1432 break;
1433 default:
1434 break;
1436 return compose;
1439 static Compose *compose_reply(MsgInfo *msginfo,
1440 ComposeQuoteMode quote_mode,
1441 gboolean to_all,
1442 gboolean to_ml,
1443 gboolean to_sender,
1444 const gchar *body)
1446 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1447 to_sender, FALSE, body);
1450 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1451 ComposeQuoteMode quote_mode,
1452 gboolean to_all,
1453 gboolean to_sender,
1454 const gchar *body)
1456 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1457 to_sender, TRUE, body);
1460 static void compose_extract_original_charset(Compose *compose)
1462 MsgInfo *info = NULL;
1463 if (compose->replyinfo) {
1464 info = compose->replyinfo;
1465 } else if (compose->fwdinfo) {
1466 info = compose->fwdinfo;
1467 } else if (compose->targetinfo) {
1468 info = compose->targetinfo;
1470 if (info) {
1471 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1472 MimeInfo *partinfo = mimeinfo;
1473 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1474 partinfo = procmime_mimeinfo_next(partinfo);
1475 if (partinfo) {
1476 compose->orig_charset =
1477 g_strdup(procmime_mimeinfo_get_parameter(
1478 partinfo, "charset"));
1480 procmime_mimeinfo_free_all(mimeinfo);
1484 #define SIGNAL_BLOCK(buffer) { \
1485 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1486 G_CALLBACK(compose_changed_cb), \
1487 compose); \
1488 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1489 G_CALLBACK(text_inserted), \
1490 compose); \
1493 #define SIGNAL_UNBLOCK(buffer) { \
1494 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1495 G_CALLBACK(compose_changed_cb), \
1496 compose); \
1497 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1498 G_CALLBACK(text_inserted), \
1499 compose); \
1502 static Compose *compose_generic_reply(MsgInfo *msginfo,
1503 ComposeQuoteMode quote_mode,
1504 gboolean to_all, gboolean to_ml,
1505 gboolean to_sender,
1506 gboolean followup_and_reply_to,
1507 const gchar *body)
1509 Compose *compose;
1510 PrefsAccount *account = NULL;
1511 GtkTextView *textview;
1512 GtkTextBuffer *textbuf;
1513 gboolean quote = FALSE;
1514 const gchar *qmark = NULL;
1515 const gchar *body_fmt = NULL;
1516 gchar *s_system = NULL;
1517 START_TIMING("");
1518 cm_return_val_if_fail(msginfo != NULL, NULL);
1519 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1521 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1523 cm_return_val_if_fail(account != NULL, NULL);
1525 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1527 compose->updating = TRUE;
1529 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1530 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1532 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1533 if (!compose->replyinfo)
1534 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1536 compose_extract_original_charset(compose);
1538 if (msginfo->folder && msginfo->folder->ret_rcpt)
1539 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1541 /* Set save folder */
1542 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1543 gchar *folderidentifier;
1545 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1546 folderidentifier = folder_item_get_identifier(msginfo->folder);
1547 compose_set_save_to(compose, folderidentifier);
1548 g_free(folderidentifier);
1551 if (compose_parse_header(compose, msginfo) < 0) {
1552 compose->updating = FALSE;
1553 compose_destroy(compose);
1554 return NULL;
1557 /* override from name according to folder properties */
1558 if (msginfo->folder && msginfo->folder->prefs &&
1559 msginfo->folder->prefs->reply_with_format &&
1560 msginfo->folder->prefs->reply_override_from_format &&
1561 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1563 gchar *tmp = NULL;
1564 gchar *buf = NULL;
1566 /* decode \-escape sequences in the internal representation of the quote format */
1567 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1568 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1570 #ifdef USE_ENCHANT
1571 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1572 compose->gtkaspell);
1573 #else
1574 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1575 #endif
1576 quote_fmt_scan_string(tmp);
1577 quote_fmt_parse();
1579 buf = quote_fmt_get_buffer();
1580 if (buf == NULL)
1581 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1582 else
1583 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1584 quote_fmt_reset_vartable();
1586 g_free(tmp);
1589 textview = (GTK_TEXT_VIEW(compose->text));
1590 textbuf = gtk_text_view_get_buffer(textview);
1591 compose_create_tags(textview, compose);
1593 undo_block(compose->undostruct);
1594 #ifdef USE_ENCHANT
1595 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1596 #endif
1598 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1599 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1600 /* use the reply format of folder (if enabled), or the account's one
1601 (if enabled) or fallback to the global reply format, which is always
1602 enabled (even if empty), and use the relevant quotemark */
1603 quote = TRUE;
1604 if (msginfo->folder && msginfo->folder->prefs &&
1605 msginfo->folder->prefs->reply_with_format) {
1606 qmark = msginfo->folder->prefs->reply_quotemark;
1607 body_fmt = msginfo->folder->prefs->reply_body_format;
1609 } else if (account->reply_with_format) {
1610 qmark = account->reply_quotemark;
1611 body_fmt = account->reply_body_format;
1613 } else {
1614 qmark = prefs_common.quotemark;
1615 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1616 body_fmt = gettext(prefs_common.quotefmt);
1617 else
1618 body_fmt = "";
1622 if (quote) {
1623 /* empty quotemark is not allowed */
1624 if (qmark == NULL || *qmark == '\0')
1625 qmark = "> ";
1626 compose_quote_fmt(compose, compose->replyinfo,
1627 body_fmt, qmark, body, FALSE, TRUE,
1628 _("The body of the \"Reply\" template has an error at line %d."));
1629 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1630 quote_fmt_reset_vartable();
1631 #ifdef USE_ENCHANT
1632 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1633 gtkaspell_highlight_all(compose->gtkaspell);
1634 #endif
1637 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1638 compose_force_encryption(compose, account, FALSE, s_system);
1641 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1642 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1643 compose_force_signing(compose, account, s_system);
1645 g_free(s_system);
1647 SIGNAL_BLOCK(textbuf);
1649 if (account->auto_sig)
1650 compose_insert_sig(compose, FALSE);
1652 compose_wrap_all(compose);
1654 SIGNAL_UNBLOCK(textbuf);
1656 gtk_widget_grab_focus(compose->text);
1658 undo_unblock(compose->undostruct);
1660 if (prefs_common.auto_exteditor)
1661 compose_exec_ext_editor(compose);
1663 compose->modified = FALSE;
1664 compose_set_title(compose);
1666 compose->updating = FALSE;
1667 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1668 SCROLL_TO_CURSOR(compose);
1670 if (compose->deferred_destroy) {
1671 compose_destroy(compose);
1672 return NULL;
1674 END_TIMING();
1676 return compose;
1679 #define INSERT_FW_HEADER(var, hdr) \
1680 if (msginfo->var && *msginfo->var) { \
1681 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1682 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1683 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1686 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1687 gboolean as_attach, const gchar *body,
1688 gboolean no_extedit,
1689 gboolean batch)
1691 Compose *compose;
1692 GtkTextView *textview;
1693 GtkTextBuffer *textbuf;
1694 gint cursor_pos = -1;
1695 ComposeMode mode;
1697 cm_return_val_if_fail(msginfo != NULL, NULL);
1698 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1700 if (!account &&
1701 !(account = compose_guess_forward_account_from_msginfo
1702 (msginfo)))
1703 account = cur_account;
1705 if (!prefs_common.forward_as_attachment)
1706 mode = COMPOSE_FORWARD_INLINE;
1707 else
1708 mode = COMPOSE_FORWARD;
1709 compose = compose_create(account, msginfo->folder, mode, batch);
1711 compose->updating = TRUE;
1712 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1713 if (!compose->fwdinfo)
1714 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1716 compose_extract_original_charset(compose);
1718 if (msginfo->subject && *msginfo->subject) {
1719 gchar *buf, *buf2, *p;
1721 buf = p = g_strdup(msginfo->subject);
1722 p += subject_get_prefix_length(p);
1723 memmove(buf, p, strlen(p) + 1);
1725 buf2 = g_strdup_printf("Fw: %s", buf);
1726 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1728 g_free(buf);
1729 g_free(buf2);
1732 /* override from name according to folder properties */
1733 if (msginfo->folder && msginfo->folder->prefs &&
1734 msginfo->folder->prefs->forward_with_format &&
1735 msginfo->folder->prefs->forward_override_from_format &&
1736 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1738 gchar *tmp = NULL;
1739 gchar *buf = NULL;
1740 MsgInfo *full_msginfo = NULL;
1742 if (!as_attach)
1743 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1744 if (!full_msginfo)
1745 full_msginfo = procmsg_msginfo_copy(msginfo);
1747 /* decode \-escape sequences in the internal representation of the quote format */
1748 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1749 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1751 #ifdef USE_ENCHANT
1752 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1753 compose->gtkaspell);
1754 #else
1755 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1756 #endif
1757 quote_fmt_scan_string(tmp);
1758 quote_fmt_parse();
1760 buf = quote_fmt_get_buffer();
1761 if (buf == NULL)
1762 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1763 else
1764 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1765 quote_fmt_reset_vartable();
1767 g_free(tmp);
1768 procmsg_msginfo_free(full_msginfo);
1771 textview = GTK_TEXT_VIEW(compose->text);
1772 textbuf = gtk_text_view_get_buffer(textview);
1773 compose_create_tags(textview, compose);
1775 undo_block(compose->undostruct);
1776 if (as_attach) {
1777 gchar *msgfile;
1779 msgfile = procmsg_get_message_file(msginfo);
1780 if (!is_file_exist(msgfile))
1781 g_warning("%s: file not exist\n", msgfile);
1782 else
1783 compose_attach_append(compose, msgfile, msgfile,
1784 "message/rfc822", NULL);
1786 g_free(msgfile);
1787 } else {
1788 const gchar *qmark = NULL;
1789 const gchar *body_fmt = NULL;
1790 MsgInfo *full_msginfo;
1792 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1793 body_fmt = gettext(prefs_common.fw_quotefmt);
1794 else
1795 body_fmt = "";
1797 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1798 if (!full_msginfo)
1799 full_msginfo = procmsg_msginfo_copy(msginfo);
1801 /* use the forward format of folder (if enabled), or the account's one
1802 (if enabled) or fallback to the global forward format, which is always
1803 enabled (even if empty), and use the relevant quotemark */
1804 if (msginfo->folder && msginfo->folder->prefs &&
1805 msginfo->folder->prefs->forward_with_format) {
1806 qmark = msginfo->folder->prefs->forward_quotemark;
1807 body_fmt = msginfo->folder->prefs->forward_body_format;
1809 } else if (account->forward_with_format) {
1810 qmark = account->forward_quotemark;
1811 body_fmt = account->forward_body_format;
1813 } else {
1814 qmark = prefs_common.fw_quotemark;
1815 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1816 body_fmt = gettext(prefs_common.fw_quotefmt);
1817 else
1818 body_fmt = "";
1821 /* empty quotemark is not allowed */
1822 if (qmark == NULL || *qmark == '\0')
1823 qmark = "> ";
1825 compose_quote_fmt(compose, full_msginfo,
1826 body_fmt, qmark, body, FALSE, TRUE,
1827 _("The body of the \"Forward\" template has an error at line %d."));
1828 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1829 quote_fmt_reset_vartable();
1830 compose_attach_parts(compose, msginfo);
1832 procmsg_msginfo_free(full_msginfo);
1833 #ifdef USE_ENCHANT
1834 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1835 gtkaspell_highlight_all(compose->gtkaspell);
1836 #endif
1839 SIGNAL_BLOCK(textbuf);
1841 if (account->auto_sig)
1842 compose_insert_sig(compose, FALSE);
1844 compose_wrap_all(compose);
1846 SIGNAL_UNBLOCK(textbuf);
1848 cursor_pos = quote_fmt_get_cursor_pos();
1849 if (cursor_pos == -1)
1850 gtk_widget_grab_focus(compose->header_last->entry);
1851 else
1852 gtk_widget_grab_focus(compose->text);
1854 if (!no_extedit && prefs_common.auto_exteditor)
1855 compose_exec_ext_editor(compose);
1857 /*save folder*/
1858 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1859 gchar *folderidentifier;
1861 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1862 folderidentifier = folder_item_get_identifier(msginfo->folder);
1863 compose_set_save_to(compose, folderidentifier);
1864 g_free(folderidentifier);
1867 undo_unblock(compose->undostruct);
1869 compose->modified = FALSE;
1870 compose_set_title(compose);
1872 compose->updating = FALSE;
1873 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1874 SCROLL_TO_CURSOR(compose);
1876 if (compose->deferred_destroy) {
1877 compose_destroy(compose);
1878 return NULL;
1881 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1883 return compose;
1886 #undef INSERT_FW_HEADER
1888 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1890 Compose *compose;
1891 GtkTextView *textview;
1892 GtkTextBuffer *textbuf;
1893 GtkTextIter iter;
1894 GSList *msginfo;
1895 gchar *msgfile;
1896 gboolean single_mail = TRUE;
1898 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1900 if (g_slist_length(msginfo_list) > 1)
1901 single_mail = FALSE;
1903 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1904 if (((MsgInfo *)msginfo->data)->folder == NULL)
1905 return NULL;
1907 /* guess account from first selected message */
1908 if (!account &&
1909 !(account = compose_guess_forward_account_from_msginfo
1910 (msginfo_list->data)))
1911 account = cur_account;
1913 cm_return_val_if_fail(account != NULL, NULL);
1915 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1916 if (msginfo->data) {
1917 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1918 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1922 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1923 g_warning("no msginfo_list");
1924 return NULL;
1927 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1929 compose->updating = TRUE;
1931 /* override from name according to folder properties */
1932 if (msginfo_list->data) {
1933 MsgInfo *msginfo = msginfo_list->data;
1935 if (msginfo->folder && msginfo->folder->prefs &&
1936 msginfo->folder->prefs->forward_with_format &&
1937 msginfo->folder->prefs->forward_override_from_format &&
1938 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1940 gchar *tmp = NULL;
1941 gchar *buf = NULL;
1943 /* decode \-escape sequences in the internal representation of the quote format */
1944 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1945 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1947 #ifdef USE_ENCHANT
1948 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1949 compose->gtkaspell);
1950 #else
1951 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1952 #endif
1953 quote_fmt_scan_string(tmp);
1954 quote_fmt_parse();
1956 buf = quote_fmt_get_buffer();
1957 if (buf == NULL)
1958 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1959 else
1960 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1961 quote_fmt_reset_vartable();
1963 g_free(tmp);
1967 textview = GTK_TEXT_VIEW(compose->text);
1968 textbuf = gtk_text_view_get_buffer(textview);
1969 compose_create_tags(textview, compose);
1971 undo_block(compose->undostruct);
1972 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1973 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1975 if (!is_file_exist(msgfile))
1976 g_warning("%s: file not exist\n", msgfile);
1977 else
1978 compose_attach_append(compose, msgfile, msgfile,
1979 "message/rfc822", NULL);
1980 g_free(msgfile);
1983 if (single_mail) {
1984 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1985 if (info->subject && *info->subject) {
1986 gchar *buf, *buf2, *p;
1988 buf = p = g_strdup(info->subject);
1989 p += subject_get_prefix_length(p);
1990 memmove(buf, p, strlen(p) + 1);
1992 buf2 = g_strdup_printf("Fw: %s", buf);
1993 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1995 g_free(buf);
1996 g_free(buf2);
1998 } else {
1999 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2000 _("Fw: multiple emails"));
2003 SIGNAL_BLOCK(textbuf);
2005 if (account->auto_sig)
2006 compose_insert_sig(compose, FALSE);
2008 compose_wrap_all(compose);
2010 SIGNAL_UNBLOCK(textbuf);
2012 gtk_text_buffer_get_start_iter(textbuf, &iter);
2013 gtk_text_buffer_place_cursor(textbuf, &iter);
2015 gtk_widget_grab_focus(compose->header_last->entry);
2016 undo_unblock(compose->undostruct);
2017 compose->modified = FALSE;
2018 compose_set_title(compose);
2020 compose->updating = FALSE;
2021 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2022 SCROLL_TO_CURSOR(compose);
2024 if (compose->deferred_destroy) {
2025 compose_destroy(compose);
2026 return NULL;
2029 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2031 return compose;
2034 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2036 GtkTextIter start = *iter;
2037 GtkTextIter end_iter;
2038 int start_pos = gtk_text_iter_get_offset(&start);
2039 gchar *str = NULL;
2040 if (!compose->account->sig_sep)
2041 return FALSE;
2043 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2044 start_pos+strlen(compose->account->sig_sep));
2046 /* check sig separator */
2047 str = gtk_text_iter_get_text(&start, &end_iter);
2048 if (!strcmp(str, compose->account->sig_sep)) {
2049 gchar *tmp = NULL;
2050 /* check end of line (\n) */
2051 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2052 start_pos+strlen(compose->account->sig_sep));
2053 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2054 start_pos+strlen(compose->account->sig_sep)+1);
2055 tmp = gtk_text_iter_get_text(&start, &end_iter);
2056 if (!strcmp(tmp,"\n")) {
2057 g_free(str);
2058 g_free(tmp);
2059 return TRUE;
2061 g_free(tmp);
2063 g_free(str);
2065 return FALSE;
2068 static void compose_colorize_signature(Compose *compose)
2070 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2071 GtkTextIter iter;
2072 GtkTextIter end_iter;
2073 gtk_text_buffer_get_start_iter(buffer, &iter);
2074 while (gtk_text_iter_forward_line(&iter))
2075 if (compose_is_sig_separator(compose, buffer, &iter)) {
2076 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2077 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2081 #define BLOCK_WRAP() { \
2082 prev_autowrap = compose->autowrap; \
2083 buffer = gtk_text_view_get_buffer( \
2084 GTK_TEXT_VIEW(compose->text)); \
2085 compose->autowrap = FALSE; \
2087 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2088 G_CALLBACK(compose_changed_cb), \
2089 compose); \
2090 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2091 G_CALLBACK(text_inserted), \
2092 compose); \
2094 #define UNBLOCK_WRAP() { \
2095 compose->autowrap = prev_autowrap; \
2096 if (compose->autowrap) { \
2097 gint old = compose->draft_timeout_tag; \
2098 compose->draft_timeout_tag = -2; \
2099 compose_wrap_all(compose); \
2100 compose->draft_timeout_tag = old; \
2103 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2104 G_CALLBACK(compose_changed_cb), \
2105 compose); \
2106 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2107 G_CALLBACK(text_inserted), \
2108 compose); \
2111 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2113 Compose *compose = NULL;
2114 PrefsAccount *account = NULL;
2115 GtkTextView *textview;
2116 GtkTextBuffer *textbuf;
2117 GtkTextMark *mark;
2118 GtkTextIter iter;
2119 FILE *fp;
2120 gchar buf[BUFFSIZE];
2121 gboolean use_signing = FALSE;
2122 gboolean use_encryption = FALSE;
2123 gchar *privacy_system = NULL;
2124 int priority = PRIORITY_NORMAL;
2125 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2126 gboolean autowrap = prefs_common.autowrap;
2127 gboolean autoindent = prefs_common.auto_indent;
2129 cm_return_val_if_fail(msginfo != NULL, NULL);
2130 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2132 if (compose_put_existing_to_front(msginfo)) {
2133 return NULL;
2136 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2137 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2138 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2139 gchar queueheader_buf[BUFFSIZE];
2140 gint id, param;
2142 /* Select Account from queue headers */
2143 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2144 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2145 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2146 account = account_find_from_id(id);
2148 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2149 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2150 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2151 account = account_find_from_id(id);
2153 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2154 sizeof(queueheader_buf), "NAID:")) {
2155 id = atoi(&queueheader_buf[strlen("NAID:")]);
2156 account = account_find_from_id(id);
2158 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2159 sizeof(queueheader_buf), "MAID:")) {
2160 id = atoi(&queueheader_buf[strlen("MAID:")]);
2161 account = account_find_from_id(id);
2163 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2164 sizeof(queueheader_buf), "S:")) {
2165 account = account_find_from_address(queueheader_buf, FALSE);
2167 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2168 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2169 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2170 use_signing = param;
2173 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2174 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2175 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2176 use_signing = param;
2179 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2180 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2181 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2182 use_encryption = param;
2184 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2185 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2186 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2187 use_encryption = param;
2189 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2190 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2191 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2192 autowrap = param;
2194 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2195 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2196 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2197 autoindent = param;
2199 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2200 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2201 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2203 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2204 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2205 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2207 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2208 sizeof(queueheader_buf), "X-Priority: ")) {
2209 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2210 priority = param;
2212 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2213 sizeof(queueheader_buf), "RMID:")) {
2214 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2215 if (tokens[0] && tokens[1] && tokens[2]) {
2216 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2217 if (orig_item != NULL) {
2218 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2221 g_strfreev(tokens);
2223 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2224 sizeof(queueheader_buf), "FMID:")) {
2225 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2226 if (tokens[0] && tokens[1] && tokens[2]) {
2227 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2228 if (orig_item != NULL) {
2229 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2232 g_strfreev(tokens);
2234 } else {
2235 account = msginfo->folder->folder->account;
2238 if (!account && prefs_common.reedit_account_autosel) {
2239 gchar from[BUFFSIZE];
2240 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2241 extract_address(from);
2242 account = account_find_from_address(from, FALSE);
2245 if (!account) {
2246 account = cur_account;
2248 cm_return_val_if_fail(account != NULL, NULL);
2250 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2252 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2253 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2254 compose->autowrap = autowrap;
2255 compose->replyinfo = replyinfo;
2256 compose->fwdinfo = fwdinfo;
2258 compose->updating = TRUE;
2259 compose->priority = priority;
2261 if (privacy_system != NULL) {
2262 compose->privacy_system = privacy_system;
2263 compose_use_signing(compose, use_signing);
2264 compose_use_encryption(compose, use_encryption);
2265 compose_update_privacy_system_menu_item(compose, FALSE);
2266 } else {
2267 activate_privacy_system(compose, account, FALSE);
2270 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2272 compose_extract_original_charset(compose);
2274 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2275 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2276 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2277 gchar queueheader_buf[BUFFSIZE];
2279 /* Set message save folder */
2280 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2281 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2282 compose_set_save_to(compose, &queueheader_buf[4]);
2284 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2285 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2286 if (active) {
2287 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2292 if (compose_parse_header(compose, msginfo) < 0) {
2293 compose->updating = FALSE;
2294 compose_destroy(compose);
2295 return NULL;
2297 compose_reedit_set_entry(compose, msginfo);
2299 textview = GTK_TEXT_VIEW(compose->text);
2300 textbuf = gtk_text_view_get_buffer(textview);
2301 compose_create_tags(textview, compose);
2303 mark = gtk_text_buffer_get_insert(textbuf);
2304 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2306 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2307 G_CALLBACK(compose_changed_cb),
2308 compose);
2310 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2311 fp = procmime_get_first_encrypted_text_content(msginfo);
2312 if (fp) {
2313 compose_force_encryption(compose, account, TRUE, NULL);
2315 } else {
2316 fp = procmime_get_first_text_content(msginfo);
2318 if (fp == NULL) {
2319 g_warning("Can't get text part\n");
2322 if (fp != NULL) {
2323 gboolean prev_autowrap = compose->autowrap;
2324 GtkTextBuffer *buffer = textbuf;
2325 BLOCK_WRAP();
2326 while (fgets(buf, sizeof(buf), fp) != NULL) {
2327 strcrchomp(buf);
2328 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2330 UNBLOCK_WRAP();
2331 fclose(fp);
2334 compose_attach_parts(compose, msginfo);
2336 compose_colorize_signature(compose);
2338 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2339 G_CALLBACK(compose_changed_cb),
2340 compose);
2342 gtk_widget_grab_focus(compose->text);
2344 if (prefs_common.auto_exteditor) {
2345 compose_exec_ext_editor(compose);
2347 compose->modified = FALSE;
2348 compose_set_title(compose);
2350 compose->updating = FALSE;
2351 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2352 SCROLL_TO_CURSOR(compose);
2354 if (compose->deferred_destroy) {
2355 compose_destroy(compose);
2356 return NULL;
2359 compose->sig_str = account_get_signature_str(compose->account);
2361 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2363 return compose;
2366 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2367 gboolean batch)
2369 Compose *compose;
2370 gchar *filename;
2371 FolderItem *item;
2373 cm_return_val_if_fail(msginfo != NULL, NULL);
2375 if (!account)
2376 account = account_get_reply_account(msginfo,
2377 prefs_common.reply_account_autosel);
2378 cm_return_val_if_fail(account != NULL, NULL);
2380 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2382 compose->updating = TRUE;
2384 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2385 compose->replyinfo = NULL;
2386 compose->fwdinfo = NULL;
2388 compose_show_first_last_header(compose, TRUE);
2390 gtk_widget_grab_focus(compose->header_last->entry);
2392 filename = procmsg_get_message_file(msginfo);
2394 if (filename == NULL) {
2395 compose->updating = FALSE;
2396 compose_destroy(compose);
2398 return NULL;
2401 compose->redirect_filename = filename;
2403 /* Set save folder */
2404 item = msginfo->folder;
2405 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2406 gchar *folderidentifier;
2408 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2409 folderidentifier = folder_item_get_identifier(item);
2410 compose_set_save_to(compose, folderidentifier);
2411 g_free(folderidentifier);
2414 compose_attach_parts(compose, msginfo);
2416 if (msginfo->subject)
2417 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2418 msginfo->subject);
2419 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2421 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2422 _("The body of the \"Redirect\" template has an error at line %d."));
2423 quote_fmt_reset_vartable();
2424 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2426 compose_colorize_signature(compose);
2429 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2430 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2431 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2433 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2434 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2435 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2436 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2437 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2438 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2439 #if !GTK_CHECK_VERSION(2, 24, 0)
2440 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2441 #endif
2442 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2444 if (compose->toolbar->draft_btn)
2445 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2446 if (compose->toolbar->insert_btn)
2447 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2448 if (compose->toolbar->attach_btn)
2449 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2450 if (compose->toolbar->sig_btn)
2451 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2452 if (compose->toolbar->exteditor_btn)
2453 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2454 if (compose->toolbar->linewrap_current_btn)
2455 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2456 if (compose->toolbar->linewrap_all_btn)
2457 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2459 compose->modified = FALSE;
2460 compose_set_title(compose);
2461 compose->updating = FALSE;
2462 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2463 SCROLL_TO_CURSOR(compose);
2465 if (compose->deferred_destroy) {
2466 compose_destroy(compose);
2467 return NULL;
2470 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2472 return compose;
2475 GList *compose_get_compose_list(void)
2477 return compose_list;
2480 void compose_entry_append(Compose *compose, const gchar *address,
2481 ComposeEntryType type, ComposePrefType pref_type)
2483 const gchar *header;
2484 gchar *cur, *begin;
2485 gboolean in_quote = FALSE;
2486 if (!address || *address == '\0') return;
2488 switch (type) {
2489 case COMPOSE_CC:
2490 header = N_("Cc:");
2491 break;
2492 case COMPOSE_BCC:
2493 header = N_("Bcc:");
2494 break;
2495 case COMPOSE_REPLYTO:
2496 header = N_("Reply-To:");
2497 break;
2498 case COMPOSE_NEWSGROUPS:
2499 header = N_("Newsgroups:");
2500 break;
2501 case COMPOSE_FOLLOWUPTO:
2502 header = N_( "Followup-To:");
2503 break;
2504 case COMPOSE_INREPLYTO:
2505 header = N_( "In-Reply-To:");
2506 break;
2507 case COMPOSE_TO:
2508 default:
2509 header = N_("To:");
2510 break;
2512 header = prefs_common_translated_header_name(header);
2514 cur = begin = (gchar *)address;
2516 /* we separate the line by commas, but not if we're inside a quoted
2517 * string */
2518 while (*cur != '\0') {
2519 if (*cur == '"')
2520 in_quote = !in_quote;
2521 if (*cur == ',' && !in_quote) {
2522 gchar *tmp = g_strdup(begin);
2523 gchar *o_tmp = tmp;
2524 tmp[cur-begin]='\0';
2525 cur++;
2526 begin = cur;
2527 while (*tmp == ' ' || *tmp == '\t')
2528 tmp++;
2529 compose_add_header_entry(compose, header, tmp, pref_type);
2530 g_free(o_tmp);
2531 continue;
2533 cur++;
2535 if (begin < cur) {
2536 gchar *tmp = g_strdup(begin);
2537 gchar *o_tmp = tmp;
2538 tmp[cur-begin]='\0';
2539 cur++;
2540 begin = cur;
2541 while (*tmp == ' ' || *tmp == '\t')
2542 tmp++;
2543 compose_add_header_entry(compose, header, tmp, pref_type);
2544 g_free(o_tmp);
2548 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2550 #if !GTK_CHECK_VERSION(3, 0, 0)
2551 static GdkColor yellow;
2552 static GdkColor black;
2553 static gboolean yellow_initialised = FALSE;
2554 #else
2555 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2556 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2557 #endif
2558 GSList *h_list;
2559 GtkEntry *entry;
2561 #if !GTK_CHECK_VERSION(3, 0, 0)
2562 if (!yellow_initialised) {
2563 gdk_color_parse("#f5f6be", &yellow);
2564 gdk_color_parse("#000000", &black);
2565 yellow_initialised = gdk_colormap_alloc_color(
2566 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2567 yellow_initialised &= gdk_colormap_alloc_color(
2568 gdk_colormap_get_system(), &black, FALSE, TRUE);
2570 #endif
2572 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2573 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2574 if (gtk_entry_get_text(entry) &&
2575 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2576 #if !GTK_CHECK_VERSION(3, 0, 0)
2577 if (yellow_initialised) {
2578 #endif
2579 gtk_widget_modify_base(
2580 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2581 GTK_STATE_NORMAL, &yellow);
2582 gtk_widget_modify_text(
2583 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2584 GTK_STATE_NORMAL, &black);
2585 #if !GTK_CHECK_VERSION(3, 0, 0)
2587 #endif
2592 void compose_toolbar_cb(gint action, gpointer data)
2594 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2595 Compose *compose = (Compose*)toolbar_item->parent;
2597 cm_return_if_fail(compose != NULL);
2599 switch(action) {
2600 case A_SEND:
2601 compose_send_cb(NULL, compose);
2602 break;
2603 case A_SENDL:
2604 compose_send_later_cb(NULL, compose);
2605 break;
2606 case A_DRAFT:
2607 compose_draft(compose, COMPOSE_QUIT_EDITING);
2608 break;
2609 case A_INSERT:
2610 compose_insert_file_cb(NULL, compose);
2611 break;
2612 case A_ATTACH:
2613 compose_attach_cb(NULL, compose);
2614 break;
2615 case A_SIG:
2616 compose_insert_sig(compose, FALSE);
2617 break;
2618 case A_EXTEDITOR:
2619 compose_ext_editor_cb(NULL, compose);
2620 break;
2621 case A_LINEWRAP_CURRENT:
2622 compose_beautify_paragraph(compose, NULL, TRUE);
2623 break;
2624 case A_LINEWRAP_ALL:
2625 compose_wrap_all_full(compose, TRUE);
2626 break;
2627 case A_ADDRBOOK:
2628 compose_address_cb(NULL, compose);
2629 break;
2630 #ifdef USE_ENCHANT
2631 case A_CHECK_SPELLING:
2632 compose_check_all(NULL, compose);
2633 break;
2634 #endif
2635 default:
2636 break;
2640 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2642 gchar *to = NULL;
2643 gchar *cc = NULL;
2644 gchar *bcc = NULL;
2645 gchar *subject = NULL;
2646 gchar *body = NULL;
2647 gchar *temp = NULL;
2648 gsize len = 0;
2649 gchar **attach = NULL;
2650 gchar *inreplyto = NULL;
2651 MailField mfield = NO_FIELD_PRESENT;
2653 /* get mailto parts but skip from */
2654 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2656 if (to) {
2657 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2658 mfield = TO_FIELD_PRESENT;
2660 if (cc)
2661 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2662 if (bcc)
2663 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2664 if (subject) {
2665 if (!g_utf8_validate (subject, -1, NULL)) {
2666 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2667 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2668 g_free(temp);
2669 } else {
2670 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2672 mfield = SUBJECT_FIELD_PRESENT;
2674 if (body) {
2675 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2676 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2677 GtkTextMark *mark;
2678 GtkTextIter iter;
2679 gboolean prev_autowrap = compose->autowrap;
2681 compose->autowrap = FALSE;
2683 mark = gtk_text_buffer_get_insert(buffer);
2684 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2686 if (!g_utf8_validate (body, -1, NULL)) {
2687 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2688 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2689 g_free(temp);
2690 } else {
2691 gtk_text_buffer_insert(buffer, &iter, body, -1);
2693 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2695 compose->autowrap = prev_autowrap;
2696 if (compose->autowrap)
2697 compose_wrap_all(compose);
2698 mfield = BODY_FIELD_PRESENT;
2701 if (attach) {
2702 gint i = 0, att = 0;
2703 gchar *warn_files = NULL;
2704 while (attach[i] != NULL) {
2705 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2706 if (utf8_filename) {
2707 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2708 gchar *tmp = g_strdup_printf("%s%s\n",
2709 warn_files?warn_files:"",
2710 utf8_filename);
2711 g_free(warn_files);
2712 warn_files = tmp;
2713 att++;
2715 g_free(utf8_filename);
2716 } else {
2717 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2719 i++;
2721 if (warn_files) {
2722 alertpanel_notice(ngettext(
2723 "The following file has been attached: \n%s",
2724 "The following files have been attached: \n%s", att), warn_files);
2725 g_free(warn_files);
2728 if (inreplyto)
2729 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2731 g_free(to);
2732 g_free(cc);
2733 g_free(bcc);
2734 g_free(subject);
2735 g_free(body);
2736 g_strfreev(attach);
2737 g_free(inreplyto);
2739 return mfield;
2742 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2744 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2745 {"Cc:", NULL, TRUE},
2746 {"References:", NULL, FALSE},
2747 {"Bcc:", NULL, TRUE},
2748 {"Newsgroups:", NULL, TRUE},
2749 {"Followup-To:", NULL, TRUE},
2750 {"List-Post:", NULL, FALSE},
2751 {"X-Priority:", NULL, FALSE},
2752 {NULL, NULL, FALSE}};
2754 enum
2756 H_REPLY_TO = 0,
2757 H_CC = 1,
2758 H_REFERENCES = 2,
2759 H_BCC = 3,
2760 H_NEWSGROUPS = 4,
2761 H_FOLLOWUP_TO = 5,
2762 H_LIST_POST = 6,
2763 H_X_PRIORITY = 7
2766 FILE *fp;
2768 cm_return_val_if_fail(msginfo != NULL, -1);
2770 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2771 procheader_get_header_fields(fp, hentry);
2772 fclose(fp);
2774 if (hentry[H_REPLY_TO].body != NULL) {
2775 if (hentry[H_REPLY_TO].body[0] != '\0') {
2776 compose->replyto =
2777 conv_unmime_header(hentry[H_REPLY_TO].body,
2778 NULL, TRUE);
2780 g_free(hentry[H_REPLY_TO].body);
2781 hentry[H_REPLY_TO].body = NULL;
2783 if (hentry[H_CC].body != NULL) {
2784 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2785 g_free(hentry[H_CC].body);
2786 hentry[H_CC].body = NULL;
2788 if (hentry[H_REFERENCES].body != NULL) {
2789 if (compose->mode == COMPOSE_REEDIT)
2790 compose->references = hentry[H_REFERENCES].body;
2791 else {
2792 compose->references = compose_parse_references
2793 (hentry[H_REFERENCES].body, msginfo->msgid);
2794 g_free(hentry[H_REFERENCES].body);
2796 hentry[H_REFERENCES].body = NULL;
2798 if (hentry[H_BCC].body != NULL) {
2799 if (compose->mode == COMPOSE_REEDIT)
2800 compose->bcc =
2801 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2802 g_free(hentry[H_BCC].body);
2803 hentry[H_BCC].body = NULL;
2805 if (hentry[H_NEWSGROUPS].body != NULL) {
2806 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2807 hentry[H_NEWSGROUPS].body = NULL;
2809 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2810 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2811 compose->followup_to =
2812 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2813 NULL, TRUE);
2815 g_free(hentry[H_FOLLOWUP_TO].body);
2816 hentry[H_FOLLOWUP_TO].body = NULL;
2818 if (hentry[H_LIST_POST].body != NULL) {
2819 gchar *to = NULL, *start = NULL;
2821 extract_address(hentry[H_LIST_POST].body);
2822 if (hentry[H_LIST_POST].body[0] != '\0') {
2823 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2825 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2826 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2828 if (to) {
2829 g_free(compose->ml_post);
2830 compose->ml_post = to;
2833 g_free(hentry[H_LIST_POST].body);
2834 hentry[H_LIST_POST].body = NULL;
2837 /* CLAWS - X-Priority */
2838 if (compose->mode == COMPOSE_REEDIT)
2839 if (hentry[H_X_PRIORITY].body != NULL) {
2840 gint priority;
2842 priority = atoi(hentry[H_X_PRIORITY].body);
2843 g_free(hentry[H_X_PRIORITY].body);
2845 hentry[H_X_PRIORITY].body = NULL;
2847 if (priority < PRIORITY_HIGHEST ||
2848 priority > PRIORITY_LOWEST)
2849 priority = PRIORITY_NORMAL;
2851 compose->priority = priority;
2854 if (compose->mode == COMPOSE_REEDIT) {
2855 if (msginfo->inreplyto && *msginfo->inreplyto)
2856 compose->inreplyto = g_strdup(msginfo->inreplyto);
2857 return 0;
2860 if (msginfo->msgid && *msginfo->msgid)
2861 compose->inreplyto = g_strdup(msginfo->msgid);
2863 if (!compose->references) {
2864 if (msginfo->msgid && *msginfo->msgid) {
2865 if (msginfo->inreplyto && *msginfo->inreplyto)
2866 compose->references =
2867 g_strdup_printf("<%s>\n\t<%s>",
2868 msginfo->inreplyto,
2869 msginfo->msgid);
2870 else
2871 compose->references =
2872 g_strconcat("<", msginfo->msgid, ">",
2873 NULL);
2874 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2875 compose->references =
2876 g_strconcat("<", msginfo->inreplyto, ">",
2877 NULL);
2881 return 0;
2884 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2886 GSList *ref_id_list, *cur;
2887 GString *new_ref;
2888 gchar *new_ref_str;
2890 ref_id_list = references_list_append(NULL, ref);
2891 if (!ref_id_list) return NULL;
2892 if (msgid && *msgid)
2893 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2895 for (;;) {
2896 gint len = 0;
2898 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2899 /* "<" + Message-ID + ">" + CR+LF+TAB */
2900 len += strlen((gchar *)cur->data) + 5;
2902 if (len > MAX_REFERENCES_LEN) {
2903 /* remove second message-ID */
2904 if (ref_id_list && ref_id_list->next &&
2905 ref_id_list->next->next) {
2906 g_free(ref_id_list->next->data);
2907 ref_id_list = g_slist_remove
2908 (ref_id_list, ref_id_list->next->data);
2909 } else {
2910 slist_free_strings(ref_id_list);
2911 g_slist_free(ref_id_list);
2912 return NULL;
2914 } else
2915 break;
2918 new_ref = g_string_new("");
2919 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2920 if (new_ref->len > 0)
2921 g_string_append(new_ref, "\n\t");
2922 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2925 slist_free_strings(ref_id_list);
2926 g_slist_free(ref_id_list);
2928 new_ref_str = new_ref->str;
2929 g_string_free(new_ref, FALSE);
2931 return new_ref_str;
2934 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2935 const gchar *fmt, const gchar *qmark,
2936 const gchar *body, gboolean rewrap,
2937 gboolean need_unescape,
2938 const gchar *err_msg)
2940 MsgInfo* dummyinfo = NULL;
2941 gchar *quote_str = NULL;
2942 gchar *buf;
2943 gboolean prev_autowrap;
2944 const gchar *trimmed_body = body;
2945 gint cursor_pos = -1;
2946 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2947 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2948 GtkTextIter iter;
2949 GtkTextMark *mark;
2952 SIGNAL_BLOCK(buffer);
2954 if (!msginfo) {
2955 dummyinfo = compose_msginfo_new_from_compose(compose);
2956 msginfo = dummyinfo;
2959 if (qmark != NULL) {
2960 #ifdef USE_ENCHANT
2961 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2962 compose->gtkaspell);
2963 #else
2964 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2965 #endif
2966 quote_fmt_scan_string(qmark);
2967 quote_fmt_parse();
2969 buf = quote_fmt_get_buffer();
2970 if (buf == NULL)
2971 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
2972 else
2973 Xstrdup_a(quote_str, buf, goto error)
2976 if (fmt && *fmt != '\0') {
2978 if (trimmed_body)
2979 while (*trimmed_body == '\n')
2980 trimmed_body++;
2982 #ifdef USE_ENCHANT
2983 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
2984 compose->gtkaspell);
2985 #else
2986 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
2987 #endif
2988 if (need_unescape) {
2989 gchar *tmp = NULL;
2991 /* decode \-escape sequences in the internal representation of the quote format */
2992 tmp = g_malloc(strlen(fmt)+1);
2993 pref_get_unescaped_pref(tmp, fmt);
2994 quote_fmt_scan_string(tmp);
2995 quote_fmt_parse();
2996 g_free(tmp);
2997 } else {
2998 quote_fmt_scan_string(fmt);
2999 quote_fmt_parse();
3002 buf = quote_fmt_get_buffer();
3003 if (buf == NULL) {
3004 gint line = quote_fmt_get_line();
3005 alertpanel_error(err_msg, line);
3006 goto error;
3008 } else
3009 buf = "";
3011 prev_autowrap = compose->autowrap;
3012 compose->autowrap = FALSE;
3014 mark = gtk_text_buffer_get_insert(buffer);
3015 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3016 if (g_utf8_validate(buf, -1, NULL)) {
3017 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3018 } else {
3019 gchar *tmpout = NULL;
3020 tmpout = conv_codeset_strdup
3021 (buf, conv_get_locale_charset_str_no_utf8(),
3022 CS_INTERNAL);
3023 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3024 g_free(tmpout);
3025 tmpout = g_malloc(strlen(buf)*2+1);
3026 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3028 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3029 g_free(tmpout);
3032 cursor_pos = quote_fmt_get_cursor_pos();
3033 if (cursor_pos == -1)
3034 cursor_pos = gtk_text_iter_get_offset(&iter);
3035 compose->set_cursor_pos = cursor_pos;
3037 gtk_text_buffer_get_start_iter(buffer, &iter);
3038 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3039 gtk_text_buffer_place_cursor(buffer, &iter);
3041 compose->autowrap = prev_autowrap;
3042 if (compose->autowrap && rewrap)
3043 compose_wrap_all(compose);
3045 goto ok;
3047 error:
3048 buf = NULL;
3050 SIGNAL_UNBLOCK(buffer);
3052 procmsg_msginfo_free( dummyinfo );
3054 return buf;
3057 /* if ml_post is of type addr@host and from is of type
3058 * addr-anything@host, return TRUE
3060 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3062 gchar *left_ml = NULL;
3063 gchar *right_ml = NULL;
3064 gchar *left_from = NULL;
3065 gchar *right_from = NULL;
3066 gboolean result = FALSE;
3068 if (!ml_post || !from)
3069 return FALSE;
3071 left_ml = g_strdup(ml_post);
3072 if (strstr(left_ml, "@")) {
3073 right_ml = strstr(left_ml, "@")+1;
3074 *(strstr(left_ml, "@")) = '\0';
3077 left_from = g_strdup(from);
3078 if (strstr(left_from, "@")) {
3079 right_from = strstr(left_from, "@")+1;
3080 *(strstr(left_from, "@")) = '\0';
3083 if (left_ml && left_from && right_ml && right_from
3084 && !strncmp(left_from, left_ml, strlen(left_ml))
3085 && !strcmp(right_from, right_ml)) {
3086 result = TRUE;
3088 g_free(left_ml);
3089 g_free(left_from);
3091 return result;
3094 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3095 gboolean respect_default_to)
3097 if (!compose)
3098 return;
3099 if (!folder || !folder->prefs)
3100 return;
3102 if (respect_default_to && folder->prefs->enable_default_to) {
3103 compose_entry_append(compose, folder->prefs->default_to,
3104 COMPOSE_TO, PREF_FOLDER);
3105 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3107 if (folder->prefs->enable_default_cc)
3108 compose_entry_append(compose, folder->prefs->default_cc,
3109 COMPOSE_CC, PREF_FOLDER);
3110 if (folder->prefs->enable_default_bcc)
3111 compose_entry_append(compose, folder->prefs->default_bcc,
3112 COMPOSE_BCC, PREF_FOLDER);
3113 if (folder->prefs->enable_default_replyto)
3114 compose_entry_append(compose, folder->prefs->default_replyto,
3115 COMPOSE_REPLYTO, PREF_FOLDER);
3118 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3120 gchar *buf, *buf2;
3121 gchar *p;
3123 if (!compose || !msginfo)
3124 return;
3126 if (msginfo->subject && *msginfo->subject) {
3127 buf = p = g_strdup(msginfo->subject);
3128 p += subject_get_prefix_length(p);
3129 memmove(buf, p, strlen(p) + 1);
3131 buf2 = g_strdup_printf("Re: %s", buf);
3132 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3134 g_free(buf2);
3135 g_free(buf);
3136 } else
3137 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3140 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3141 gboolean to_all, gboolean to_ml,
3142 gboolean to_sender,
3143 gboolean followup_and_reply_to)
3145 GSList *cc_list = NULL;
3146 GSList *cur;
3147 gchar *from = NULL;
3148 gchar *replyto = NULL;
3149 gchar *ac_email = NULL;
3151 gboolean reply_to_ml = FALSE;
3152 gboolean default_reply_to = FALSE;
3154 cm_return_if_fail(compose->account != NULL);
3155 cm_return_if_fail(msginfo != NULL);
3157 reply_to_ml = to_ml && compose->ml_post;
3159 default_reply_to = msginfo->folder &&
3160 msginfo->folder->prefs->enable_default_reply_to;
3162 if (compose->account->protocol != A_NNTP) {
3163 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3165 if (reply_to_ml && !default_reply_to) {
3167 gboolean is_subscr = is_subscription(compose->ml_post,
3168 msginfo->from);
3169 if (!is_subscr) {
3170 /* normal answer to ml post with a reply-to */
3171 compose_entry_append(compose,
3172 compose->ml_post,
3173 COMPOSE_TO, PREF_ML);
3174 if (compose->replyto)
3175 compose_entry_append(compose,
3176 compose->replyto,
3177 COMPOSE_CC, PREF_ML);
3178 } else {
3179 /* answer to subscription confirmation */
3180 if (compose->replyto)
3181 compose_entry_append(compose,
3182 compose->replyto,
3183 COMPOSE_TO, PREF_ML);
3184 else if (msginfo->from)
3185 compose_entry_append(compose,
3186 msginfo->from,
3187 COMPOSE_TO, PREF_ML);
3190 else if (!(to_all || to_sender) && default_reply_to) {
3191 compose_entry_append(compose,
3192 msginfo->folder->prefs->default_reply_to,
3193 COMPOSE_TO, PREF_FOLDER);
3194 compose_entry_mark_default_to(compose,
3195 msginfo->folder->prefs->default_reply_to);
3196 } else {
3197 gchar *tmp1 = NULL;
3198 if (!msginfo->from)
3199 return;
3200 Xstrdup_a(tmp1, msginfo->from, return);
3201 extract_address(tmp1);
3202 if (to_all || to_sender ||
3203 !account_find_from_address(tmp1, FALSE))
3204 compose_entry_append(compose,
3205 (compose->replyto && !to_sender)
3206 ? compose->replyto :
3207 msginfo->from ? msginfo->from : "",
3208 COMPOSE_TO, PREF_NONE);
3209 else if (!to_all && !to_sender) {
3210 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3211 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3212 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3213 if (compose->replyto) {
3214 compose_entry_append(compose,
3215 compose->replyto,
3216 COMPOSE_TO, PREF_NONE);
3217 } else {
3218 compose_entry_append(compose,
3219 msginfo->from ? msginfo->from : "",
3220 COMPOSE_TO, PREF_NONE);
3222 } else {
3223 /* replying to own mail, use original recp */
3224 compose_entry_append(compose,
3225 msginfo->to ? msginfo->to : "",
3226 COMPOSE_TO, PREF_NONE);
3227 compose_entry_append(compose,
3228 msginfo->cc ? msginfo->cc : "",
3229 COMPOSE_CC, PREF_NONE);
3233 } else {
3234 if (to_sender || (compose->followup_to &&
3235 !strncmp(compose->followup_to, "poster", 6)))
3236 compose_entry_append
3237 (compose,
3238 (compose->replyto ? compose->replyto :
3239 msginfo->from ? msginfo->from : ""),
3240 COMPOSE_TO, PREF_NONE);
3242 else if (followup_and_reply_to || to_all) {
3243 compose_entry_append
3244 (compose,
3245 (compose->replyto ? compose->replyto :
3246 msginfo->from ? msginfo->from : ""),
3247 COMPOSE_TO, PREF_NONE);
3249 compose_entry_append
3250 (compose,
3251 compose->followup_to ? compose->followup_to :
3252 compose->newsgroups ? compose->newsgroups : "",
3253 COMPOSE_NEWSGROUPS, PREF_NONE);
3255 else
3256 compose_entry_append
3257 (compose,
3258 compose->followup_to ? compose->followup_to :
3259 compose->newsgroups ? compose->newsgroups : "",
3260 COMPOSE_NEWSGROUPS, PREF_NONE);
3262 compose_reply_set_subject(compose, msginfo);
3264 if (to_ml && compose->ml_post) return;
3265 if (!to_all || compose->account->protocol == A_NNTP) return;
3267 if (compose->replyto) {
3268 Xstrdup_a(replyto, compose->replyto, return);
3269 extract_address(replyto);
3271 if (msginfo->from) {
3272 Xstrdup_a(from, msginfo->from, return);
3273 extract_address(from);
3276 if (replyto && from)
3277 cc_list = address_list_append_with_comments(cc_list, from);
3278 if (to_all && msginfo->folder &&
3279 msginfo->folder->prefs->enable_default_reply_to)
3280 cc_list = address_list_append_with_comments(cc_list,
3281 msginfo->folder->prefs->default_reply_to);
3282 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3283 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3285 ac_email = g_utf8_strdown(compose->account->address, -1);
3287 if (cc_list) {
3288 for (cur = cc_list; cur != NULL; cur = cur->next) {
3289 gchar *addr = g_utf8_strdown(cur->data, -1);
3290 extract_address(addr);
3292 if (strcmp(ac_email, addr))
3293 compose_entry_append(compose, (gchar *)cur->data,
3294 COMPOSE_CC, PREF_NONE);
3295 else
3296 debug_print("Cc address same as compose account's, ignoring\n");
3298 g_free(addr);
3301 slist_free_strings(cc_list);
3302 g_slist_free(cc_list);
3305 g_free(ac_email);
3308 #define SET_ENTRY(entry, str) \
3310 if (str && *str) \
3311 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3314 #define SET_ADDRESS(type, str) \
3316 if (str && *str) \
3317 compose_entry_append(compose, str, type, PREF_NONE); \
3320 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3322 cm_return_if_fail(msginfo != NULL);
3324 SET_ENTRY(subject_entry, msginfo->subject);
3325 SET_ENTRY(from_name, msginfo->from);
3326 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3327 SET_ADDRESS(COMPOSE_CC, compose->cc);
3328 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3329 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3330 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3331 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3333 compose_update_priority_menu_item(compose);
3334 compose_update_privacy_system_menu_item(compose, FALSE);
3335 compose_show_first_last_header(compose, TRUE);
3338 #undef SET_ENTRY
3339 #undef SET_ADDRESS
3341 static void compose_insert_sig(Compose *compose, gboolean replace)
3343 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3344 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3345 GtkTextMark *mark;
3346 GtkTextIter iter, iter_end;
3347 gint cur_pos, ins_pos;
3348 gboolean prev_autowrap;
3349 gboolean found = FALSE;
3350 gboolean exists = FALSE;
3352 cm_return_if_fail(compose->account != NULL);
3354 BLOCK_WRAP();
3356 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3357 G_CALLBACK(compose_changed_cb),
3358 compose);
3360 mark = gtk_text_buffer_get_insert(buffer);
3361 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3362 cur_pos = gtk_text_iter_get_offset (&iter);
3363 ins_pos = cur_pos;
3365 gtk_text_buffer_get_end_iter(buffer, &iter);
3367 exists = (compose->sig_str != NULL);
3369 if (replace) {
3370 GtkTextIter first_iter, start_iter, end_iter;
3372 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3374 if (!exists || compose->sig_str[0] == '\0')
3375 found = FALSE;
3376 else
3377 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3378 compose->signature_tag);
3380 if (found) {
3381 /* include previous \n\n */
3382 gtk_text_iter_backward_chars(&first_iter, 1);
3383 start_iter = first_iter;
3384 end_iter = first_iter;
3385 /* skip re-start */
3386 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3387 compose->signature_tag);
3388 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3389 compose->signature_tag);
3390 if (found) {
3391 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3392 iter = start_iter;
3397 g_free(compose->sig_str);
3398 compose->sig_str = account_get_signature_str(compose->account);
3400 cur_pos = gtk_text_iter_get_offset(&iter);
3402 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3403 g_free(compose->sig_str);
3404 compose->sig_str = NULL;
3405 } else {
3406 if (compose->sig_inserted == FALSE)
3407 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3408 compose->sig_inserted = TRUE;
3410 cur_pos = gtk_text_iter_get_offset(&iter);
3411 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3412 /* remove \n\n */
3413 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3414 gtk_text_iter_forward_chars(&iter, 1);
3415 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3416 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3418 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3419 cur_pos = gtk_text_buffer_get_char_count (buffer);
3422 /* put the cursor where it should be
3423 * either where the quote_fmt says, either where it was */
3424 if (compose->set_cursor_pos < 0)
3425 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3426 else
3427 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3428 compose->set_cursor_pos);
3430 compose->set_cursor_pos = -1;
3431 gtk_text_buffer_place_cursor(buffer, &iter);
3432 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3433 G_CALLBACK(compose_changed_cb),
3434 compose);
3436 UNBLOCK_WRAP();
3439 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3441 GtkTextView *text;
3442 GtkTextBuffer *buffer;
3443 GtkTextMark *mark;
3444 GtkTextIter iter;
3445 const gchar *cur_encoding;
3446 gchar buf[BUFFSIZE];
3447 gint len;
3448 FILE *fp;
3449 gboolean prev_autowrap;
3450 gboolean badtxt = FALSE;
3451 struct stat file_stat;
3452 int ret;
3454 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3456 /* get the size of the file we are about to insert */
3457 ret = g_stat(file, &file_stat);
3458 if (ret != 0) {
3459 gchar *shortfile = g_path_get_basename(file);
3460 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3461 g_free(shortfile);
3462 return COMPOSE_INSERT_NO_FILE;
3463 } else if (prefs_common.warn_large_insert == TRUE) {
3465 /* ask user for confirmation if the file is large */
3466 if (prefs_common.warn_large_insert_size < 0 ||
3467 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3468 AlertValue aval;
3469 gchar *msg;
3471 msg = g_strdup_printf(_("You are about to insert a file of %s "
3472 "in the message body. Are you sure you want to do that?"),
3473 to_human_readable(file_stat.st_size));
3474 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3475 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3476 g_free(msg);
3478 /* do we ask for confirmation next time? */
3479 if (aval & G_ALERTDISABLE) {
3480 /* no confirmation next time, disable feature in preferences */
3481 aval &= ~G_ALERTDISABLE;
3482 prefs_common.warn_large_insert = FALSE;
3485 /* abort file insertion if user canceled action */
3486 if (aval != G_ALERTALTERNATE) {
3487 return COMPOSE_INSERT_NO_FILE;
3493 if ((fp = g_fopen(file, "rb")) == NULL) {
3494 FILE_OP_ERROR(file, "fopen");
3495 return COMPOSE_INSERT_READ_ERROR;
3498 prev_autowrap = compose->autowrap;
3499 compose->autowrap = FALSE;
3501 text = GTK_TEXT_VIEW(compose->text);
3502 buffer = gtk_text_view_get_buffer(text);
3503 mark = gtk_text_buffer_get_insert(buffer);
3504 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3506 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3507 G_CALLBACK(text_inserted),
3508 compose);
3510 cur_encoding = conv_get_locale_charset_str_no_utf8();
3512 while (fgets(buf, sizeof(buf), fp) != NULL) {
3513 gchar *str;
3515 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3516 str = g_strdup(buf);
3517 else
3518 str = conv_codeset_strdup
3519 (buf, cur_encoding, CS_INTERNAL);
3520 if (!str) continue;
3522 /* strip <CR> if DOS/Windows file,
3523 replace <CR> with <LF> if Macintosh file. */
3524 strcrchomp(str);
3525 len = strlen(str);
3526 if (len > 0 && str[len - 1] != '\n') {
3527 while (--len >= 0)
3528 if (str[len] == '\r') str[len] = '\n';
3531 gtk_text_buffer_insert(buffer, &iter, str, -1);
3532 g_free(str);
3535 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3536 G_CALLBACK(text_inserted),
3537 compose);
3538 compose->autowrap = prev_autowrap;
3539 if (compose->autowrap)
3540 compose_wrap_all(compose);
3542 fclose(fp);
3544 if (badtxt)
3545 return COMPOSE_INSERT_INVALID_CHARACTER;
3546 else
3547 return COMPOSE_INSERT_SUCCESS;
3550 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3551 const gchar *filename,
3552 const gchar *content_type,
3553 const gchar *charset)
3555 AttachInfo *ainfo;
3556 GtkTreeIter iter;
3557 FILE *fp;
3558 off_t size;
3559 GAuto *auto_ainfo;
3560 gchar *size_text;
3561 GtkListStore *store;
3562 gchar *name;
3563 gboolean has_binary = FALSE;
3565 if (!is_file_exist(file)) {
3566 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3567 gboolean result = FALSE;
3568 if (file_from_uri && is_file_exist(file_from_uri)) {
3569 result = compose_attach_append(
3570 compose, file_from_uri,
3571 filename, content_type,
3572 charset);
3574 g_free(file_from_uri);
3575 if (result)
3576 return TRUE;
3577 alertpanel_error("File %s doesn't exist\n", filename);
3578 return FALSE;
3580 if ((size = get_file_size(file)) < 0) {
3581 alertpanel_error("Can't get file size of %s\n", filename);
3582 return FALSE;
3584 if (size == 0) {
3585 alertpanel_error(_("File %s is empty."), filename);
3586 return FALSE;
3588 if ((fp = g_fopen(file, "rb")) == NULL) {
3589 alertpanel_error(_("Can't read %s."), filename);
3590 return FALSE;
3592 fclose(fp);
3594 ainfo = g_new0(AttachInfo, 1);
3595 auto_ainfo = g_auto_pointer_new_with_free
3596 (ainfo, (GFreeFunc) compose_attach_info_free);
3597 ainfo->file = g_strdup(file);
3599 if (content_type) {
3600 ainfo->content_type = g_strdup(content_type);
3601 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3602 MsgInfo *msginfo;
3603 MsgFlags flags = {0, 0};
3605 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3606 ainfo->encoding = ENC_7BIT;
3607 else
3608 ainfo->encoding = ENC_8BIT;
3610 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3611 if (msginfo && msginfo->subject)
3612 name = g_strdup(msginfo->subject);
3613 else
3614 name = g_path_get_basename(filename ? filename : file);
3616 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3618 procmsg_msginfo_free(msginfo);
3619 } else {
3620 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3621 ainfo->charset = g_strdup(charset);
3622 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3623 } else {
3624 ainfo->encoding = ENC_BASE64;
3626 name = g_path_get_basename(filename ? filename : file);
3627 ainfo->name = g_strdup(name);
3629 g_free(name);
3630 } else {
3631 ainfo->content_type = procmime_get_mime_type(file);
3632 if (!ainfo->content_type) {
3633 ainfo->content_type =
3634 g_strdup("application/octet-stream");
3635 ainfo->encoding = ENC_BASE64;
3636 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3637 ainfo->encoding =
3638 procmime_get_encoding_for_text_file(file, &has_binary);
3639 else
3640 ainfo->encoding = ENC_BASE64;
3641 name = g_path_get_basename(filename ? filename : file);
3642 ainfo->name = g_strdup(name);
3643 g_free(name);
3646 if (ainfo->name != NULL
3647 && !strcmp(ainfo->name, ".")) {
3648 g_free(ainfo->name);
3649 ainfo->name = NULL;
3652 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3653 g_free(ainfo->content_type);
3654 ainfo->content_type = g_strdup("application/octet-stream");
3655 g_free(ainfo->charset);
3656 ainfo->charset = NULL;
3659 ainfo->size = (goffset)size;
3660 size_text = to_human_readable((goffset)size);
3662 store = GTK_LIST_STORE(gtk_tree_view_get_model
3663 (GTK_TREE_VIEW(compose->attach_clist)));
3665 gtk_list_store_append(store, &iter);
3666 gtk_list_store_set(store, &iter,
3667 COL_MIMETYPE, ainfo->content_type,
3668 COL_SIZE, size_text,
3669 COL_NAME, ainfo->name,
3670 COL_CHARSET, ainfo->charset,
3671 COL_DATA, ainfo,
3672 COL_AUTODATA, auto_ainfo,
3673 -1);
3675 g_auto_pointer_free(auto_ainfo);
3676 compose_attach_update_label(compose);
3677 return TRUE;
3680 static void compose_use_signing(Compose *compose, gboolean use_signing)
3682 compose->use_signing = use_signing;
3683 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3686 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3688 compose->use_encryption = use_encryption;
3689 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3692 #define NEXT_PART_NOT_CHILD(info) \
3694 node = info->node; \
3695 while (node->children) \
3696 node = g_node_last_child(node); \
3697 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3700 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3702 MimeInfo *mimeinfo;
3703 MimeInfo *child;
3704 MimeInfo *firsttext = NULL;
3705 MimeInfo *encrypted = NULL;
3706 GNode *node;
3707 gchar *outfile;
3708 const gchar *partname = NULL;
3710 mimeinfo = procmime_scan_message(msginfo);
3711 if (!mimeinfo) return;
3713 if (mimeinfo->node->children == NULL) {
3714 procmime_mimeinfo_free_all(mimeinfo);
3715 return;
3718 /* find first content part */
3719 child = (MimeInfo *) mimeinfo->node->children->data;
3720 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3721 child = (MimeInfo *)child->node->children->data;
3723 if (child) {
3724 if (child->type == MIMETYPE_TEXT) {
3725 firsttext = child;
3726 debug_print("First text part found\n");
3727 } else if (compose->mode == COMPOSE_REEDIT &&
3728 child->type == MIMETYPE_APPLICATION &&
3729 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3730 encrypted = (MimeInfo *)child->node->parent->data;
3733 child = (MimeInfo *) mimeinfo->node->children->data;
3734 while (child != NULL) {
3735 gint err;
3737 if (child == encrypted) {
3738 /* skip this part of tree */
3739 NEXT_PART_NOT_CHILD(child);
3740 continue;
3743 if (child->type == MIMETYPE_MULTIPART) {
3744 /* get the actual content */
3745 child = procmime_mimeinfo_next(child);
3746 continue;
3749 if (child == firsttext) {
3750 child = procmime_mimeinfo_next(child);
3751 continue;
3754 outfile = procmime_get_tmp_file_name(child);
3755 if ((err = procmime_get_part(outfile, child)) < 0)
3756 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3757 else {
3758 gchar *content_type;
3760 content_type = procmime_get_content_type_str(child->type, child->subtype);
3762 /* if we meet a pgp signature, we don't attach it, but
3763 * we force signing. */
3764 if ((strcmp(content_type, "application/pgp-signature") &&
3765 strcmp(content_type, "application/pkcs7-signature") &&
3766 strcmp(content_type, "application/x-pkcs7-signature"))
3767 || compose->mode == COMPOSE_REDIRECT) {
3768 partname = procmime_mimeinfo_get_parameter(child, "filename");
3769 if (partname == NULL)
3770 partname = procmime_mimeinfo_get_parameter(child, "name");
3771 if (partname == NULL)
3772 partname = "";
3773 compose_attach_append(compose, outfile,
3774 partname, content_type,
3775 procmime_mimeinfo_get_parameter(child, "charset"));
3776 } else {
3777 compose_force_signing(compose, compose->account, NULL);
3779 g_free(content_type);
3781 g_free(outfile);
3782 NEXT_PART_NOT_CHILD(child);
3784 procmime_mimeinfo_free_all(mimeinfo);
3787 #undef NEXT_PART_NOT_CHILD
3791 typedef enum {
3792 WAIT_FOR_INDENT_CHAR,
3793 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3794 } IndentState;
3796 /* return indent length, we allow:
3797 indent characters followed by indent characters or spaces/tabs,
3798 alphabets and numbers immediately followed by indent characters,
3799 and the repeating sequences of the above
3800 If quote ends with multiple spaces, only the first one is included. */
3801 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3802 const GtkTextIter *start, gint *len)
3804 GtkTextIter iter = *start;
3805 gunichar wc;
3806 gchar ch[6];
3807 gint clen;
3808 IndentState state = WAIT_FOR_INDENT_CHAR;
3809 gboolean is_space;
3810 gboolean is_indent;
3811 gint alnum_count = 0;
3812 gint space_count = 0;
3813 gint quote_len = 0;
3815 if (prefs_common.quote_chars == NULL) {
3816 return 0 ;
3819 while (!gtk_text_iter_ends_line(&iter)) {
3820 wc = gtk_text_iter_get_char(&iter);
3821 if (g_unichar_iswide(wc))
3822 break;
3823 clen = g_unichar_to_utf8(wc, ch);
3824 if (clen != 1)
3825 break;
3827 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3828 is_space = g_unichar_isspace(wc);
3830 if (state == WAIT_FOR_INDENT_CHAR) {
3831 if (!is_indent && !g_unichar_isalnum(wc))
3832 break;
3833 if (is_indent) {
3834 quote_len += alnum_count + space_count + 1;
3835 alnum_count = space_count = 0;
3836 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3837 } else
3838 alnum_count++;
3839 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3840 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3841 break;
3842 if (is_space)
3843 space_count++;
3844 else if (is_indent) {
3845 quote_len += alnum_count + space_count + 1;
3846 alnum_count = space_count = 0;
3847 } else {
3848 alnum_count++;
3849 state = WAIT_FOR_INDENT_CHAR;
3853 gtk_text_iter_forward_char(&iter);
3856 if (quote_len > 0 && space_count > 0)
3857 quote_len++;
3859 if (len)
3860 *len = quote_len;
3862 if (quote_len > 0) {
3863 iter = *start;
3864 gtk_text_iter_forward_chars(&iter, quote_len);
3865 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3868 return NULL;
3871 /* return >0 if the line is itemized */
3872 static int compose_itemized_length(GtkTextBuffer *buffer,
3873 const GtkTextIter *start)
3875 GtkTextIter iter = *start;
3876 gunichar wc;
3877 gchar ch[6];
3878 gint clen;
3879 gint len = 0;
3880 if (gtk_text_iter_ends_line(&iter))
3881 return 0;
3883 while (1) {
3884 len++;
3885 wc = gtk_text_iter_get_char(&iter);
3886 if (!g_unichar_isspace(wc))
3887 break;
3888 gtk_text_iter_forward_char(&iter);
3889 if (gtk_text_iter_ends_line(&iter))
3890 return 0;
3893 clen = g_unichar_to_utf8(wc, ch);
3894 if (clen != 1)
3895 return 0;
3897 if (!strchr("*-+", ch[0]))
3898 return 0;
3900 gtk_text_iter_forward_char(&iter);
3901 if (gtk_text_iter_ends_line(&iter))
3902 return 0;
3903 wc = gtk_text_iter_get_char(&iter);
3904 if (g_unichar_isspace(wc)) {
3905 return len+1;
3907 return 0;
3910 /* return the string at the start of the itemization */
3911 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
3912 const GtkTextIter *start)
3914 GtkTextIter iter = *start;
3915 gunichar wc;
3916 gint len = 0;
3917 GString *item_chars = g_string_new("");
3918 gchar *str = NULL;
3920 if (gtk_text_iter_ends_line(&iter))
3921 return NULL;
3923 while (1) {
3924 len++;
3925 wc = gtk_text_iter_get_char(&iter);
3926 if (!g_unichar_isspace(wc))
3927 break;
3928 gtk_text_iter_forward_char(&iter);
3929 if (gtk_text_iter_ends_line(&iter))
3930 break;
3931 g_string_append_unichar(item_chars, wc);
3934 str = item_chars->str;
3935 g_string_free(item_chars, FALSE);
3936 return str;
3939 /* return the number of spaces at a line's start */
3940 static int compose_left_offset_length(GtkTextBuffer *buffer,
3941 const GtkTextIter *start)
3943 GtkTextIter iter = *start;
3944 gunichar wc;
3945 gint len = 0;
3946 if (gtk_text_iter_ends_line(&iter))
3947 return 0;
3949 while (1) {
3950 wc = gtk_text_iter_get_char(&iter);
3951 if (!g_unichar_isspace(wc))
3952 break;
3953 len++;
3954 gtk_text_iter_forward_char(&iter);
3955 if (gtk_text_iter_ends_line(&iter))
3956 return 0;
3959 gtk_text_iter_forward_char(&iter);
3960 if (gtk_text_iter_ends_line(&iter))
3961 return 0;
3962 return len;
3965 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3966 const GtkTextIter *start,
3967 GtkTextIter *break_pos,
3968 gint max_col,
3969 gint quote_len)
3971 GtkTextIter iter = *start, line_end = *start;
3972 PangoLogAttr *attrs;
3973 gchar *str;
3974 gchar *p;
3975 gint len;
3976 gint i;
3977 gint col = 0;
3978 gint pos = 0;
3979 gboolean can_break = FALSE;
3980 gboolean do_break = FALSE;
3981 gboolean was_white = FALSE;
3982 gboolean prev_dont_break = FALSE;
3984 gtk_text_iter_forward_to_line_end(&line_end);
3985 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3986 len = g_utf8_strlen(str, -1);
3988 if (len == 0) {
3989 g_free(str);
3990 g_warning("compose_get_line_break_pos: len = 0!\n");
3991 return FALSE;
3994 /* g_print("breaking line: %d: %s (len = %d)\n",
3995 gtk_text_iter_get_line(&iter), str, len); */
3997 attrs = g_new(PangoLogAttr, len + 1);
3999 pango_default_break(str, -1, NULL, attrs, len + 1);
4001 p = str;
4003 /* skip quote and leading spaces */
4004 for (i = 0; *p != '\0' && i < len; i++) {
4005 gunichar wc;
4007 wc = g_utf8_get_char(p);
4008 if (i >= quote_len && !g_unichar_isspace(wc))
4009 break;
4010 if (g_unichar_iswide(wc))
4011 col += 2;
4012 else if (*p == '\t')
4013 col += 8;
4014 else
4015 col++;
4016 p = g_utf8_next_char(p);
4019 for (; *p != '\0' && i < len; i++) {
4020 PangoLogAttr *attr = attrs + i;
4021 gunichar wc;
4022 gint uri_len;
4024 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4025 pos = i;
4027 was_white = attr->is_white;
4029 /* don't wrap URI */
4030 if ((uri_len = get_uri_len(p)) > 0) {
4031 col += uri_len;
4032 if (pos > 0 && col > max_col) {
4033 do_break = TRUE;
4034 break;
4036 i += uri_len - 1;
4037 p += uri_len;
4038 can_break = TRUE;
4039 continue;
4042 wc = g_utf8_get_char(p);
4043 if (g_unichar_iswide(wc)) {
4044 col += 2;
4045 if (prev_dont_break && can_break && attr->is_line_break)
4046 pos = i;
4047 } else if (*p == '\t')
4048 col += 8;
4049 else
4050 col++;
4051 if (pos > 0 && col > max_col) {
4052 do_break = TRUE;
4053 break;
4056 if (*p == '-' || *p == '/')
4057 prev_dont_break = TRUE;
4058 else
4059 prev_dont_break = FALSE;
4061 p = g_utf8_next_char(p);
4062 can_break = TRUE;
4065 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4067 g_free(attrs);
4068 g_free(str);
4070 *break_pos = *start;
4071 gtk_text_iter_set_line_offset(break_pos, pos);
4073 return do_break;
4076 static gboolean compose_join_next_line(Compose *compose,
4077 GtkTextBuffer *buffer,
4078 GtkTextIter *iter,
4079 const gchar *quote_str)
4081 GtkTextIter iter_ = *iter, cur, prev, next, end;
4082 PangoLogAttr attrs[3];
4083 gchar *str;
4084 gchar *next_quote_str;
4085 gunichar wc1, wc2;
4086 gint quote_len;
4087 gboolean keep_cursor = FALSE;
4089 if (!gtk_text_iter_forward_line(&iter_) ||
4090 gtk_text_iter_ends_line(&iter_)) {
4091 return FALSE;
4093 next_quote_str = compose_get_quote_str(buffer, &iter_, &quote_len);
4095 if ((quote_str || next_quote_str) &&
4096 strcmp2(quote_str, next_quote_str) != 0) {
4097 g_free(next_quote_str);
4098 return FALSE;
4100 g_free(next_quote_str);
4102 end = iter_;
4103 if (quote_len > 0) {
4104 gtk_text_iter_forward_chars(&end, quote_len);
4105 if (gtk_text_iter_ends_line(&end)) {
4106 return FALSE;
4110 /* don't join itemized lines */
4111 if (compose_itemized_length(buffer, &end) > 0) {
4112 return FALSE;
4115 /* don't join signature separator */
4116 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4117 return FALSE;
4119 /* delete quote str */
4120 if (quote_len > 0)
4121 gtk_text_buffer_delete(buffer, &iter_, &end);
4123 /* don't join line breaks put by the user */
4124 prev = cur = iter_;
4125 gtk_text_iter_backward_char(&cur);
4126 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4127 gtk_text_iter_forward_char(&cur);
4128 *iter = cur;
4129 return FALSE;
4131 gtk_text_iter_forward_char(&cur);
4132 /* delete linebreak and extra spaces */
4133 while (gtk_text_iter_backward_char(&cur)) {
4134 wc1 = gtk_text_iter_get_char(&cur);
4135 if (!g_unichar_isspace(wc1))
4136 break;
4137 prev = cur;
4139 next = cur = iter_;
4140 while (!gtk_text_iter_ends_line(&cur)) {
4141 wc1 = gtk_text_iter_get_char(&cur);
4142 if (!g_unichar_isspace(wc1))
4143 break;
4144 gtk_text_iter_forward_char(&cur);
4145 next = cur;
4147 if (!gtk_text_iter_equal(&prev, &next)) {
4148 GtkTextMark *mark;
4150 mark = gtk_text_buffer_get_insert(buffer);
4151 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4152 if (gtk_text_iter_equal(&prev, &cur))
4153 keep_cursor = TRUE;
4154 gtk_text_buffer_delete(buffer, &prev, &next);
4156 iter_ = prev;
4158 /* insert space if required */
4159 gtk_text_iter_backward_char(&prev);
4160 wc1 = gtk_text_iter_get_char(&prev);
4161 wc2 = gtk_text_iter_get_char(&next);
4162 gtk_text_iter_forward_char(&next);
4163 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4164 pango_default_break(str, -1, NULL, attrs, 3);
4165 if (!attrs[1].is_line_break ||
4166 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4167 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4168 if (keep_cursor) {
4169 gtk_text_iter_backward_char(&iter_);
4170 gtk_text_buffer_place_cursor(buffer, &iter_);
4173 g_free(str);
4175 *iter = iter_;
4176 return TRUE;
4179 #define ADD_TXT_POS(bp_, ep_, pti_) \
4180 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4181 last = last->next; \
4182 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4183 last->next = NULL; \
4184 } else { \
4185 g_warning("alloc error scanning URIs\n"); \
4188 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4190 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4191 GtkTextBuffer *buffer;
4192 GtkTextIter iter, break_pos, end_of_line;
4193 gchar *quote_str = NULL;
4194 gint quote_len;
4195 gboolean wrap_quote = prefs_common.linewrap_quote;
4196 gboolean prev_autowrap = compose->autowrap;
4197 gint startq_offset = -1, noq_offset = -1;
4198 gint uri_start = -1, uri_stop = -1;
4199 gint nouri_start = -1, nouri_stop = -1;
4200 gint num_blocks = 0;
4201 gint quotelevel = -1;
4202 gboolean modified = force;
4203 gboolean removed = FALSE;
4204 gboolean modified_before_remove = FALSE;
4205 gint lines = 0;
4206 gboolean start = TRUE;
4207 gint itemized_len = 0, rem_item_len = 0;
4208 gchar *itemized_chars = NULL;
4209 gboolean item_continuation = FALSE;
4211 if (force) {
4212 modified = TRUE;
4214 if (compose->draft_timeout_tag == -2) {
4215 modified = TRUE;
4218 compose->autowrap = FALSE;
4220 buffer = gtk_text_view_get_buffer(text);
4221 undo_wrapping(compose->undostruct, TRUE);
4222 if (par_iter) {
4223 iter = *par_iter;
4224 } else {
4225 GtkTextMark *mark;
4226 mark = gtk_text_buffer_get_insert(buffer);
4227 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4231 if (compose->draft_timeout_tag == -2) {
4232 if (gtk_text_iter_ends_line(&iter)) {
4233 while (gtk_text_iter_ends_line(&iter) &&
4234 gtk_text_iter_forward_line(&iter))
4236 } else {
4237 while (gtk_text_iter_backward_line(&iter)) {
4238 if (gtk_text_iter_ends_line(&iter)) {
4239 gtk_text_iter_forward_line(&iter);
4240 break;
4244 } else {
4245 /* move to line start */
4246 gtk_text_iter_set_line_offset(&iter, 0);
4249 itemized_len = compose_itemized_length(buffer, &iter);
4251 if (!itemized_len) {
4252 itemized_len = compose_left_offset_length(buffer, &iter);
4253 item_continuation = TRUE;
4256 if (itemized_len)
4257 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4259 /* go until paragraph end (empty line) */
4260 while (start || !gtk_text_iter_ends_line(&iter)) {
4261 gchar *scanpos = NULL;
4262 /* parse table - in order of priority */
4263 struct table {
4264 const gchar *needle; /* token */
4266 /* token search function */
4267 gchar *(*search) (const gchar *haystack,
4268 const gchar *needle);
4269 /* part parsing function */
4270 gboolean (*parse) (const gchar *start,
4271 const gchar *scanpos,
4272 const gchar **bp_,
4273 const gchar **ep_,
4274 gboolean hdr);
4275 /* part to URI function */
4276 gchar *(*build_uri) (const gchar *bp,
4277 const gchar *ep);
4280 static struct table parser[] = {
4281 {"http://", strcasestr, get_uri_part, make_uri_string},
4282 {"https://", strcasestr, get_uri_part, make_uri_string},
4283 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4284 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4285 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4286 {"www.", strcasestr, get_uri_part, make_http_string},
4287 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4288 {"@", strcasestr, get_email_part, make_email_string}
4290 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4291 gint last_index = PARSE_ELEMS;
4292 gint n;
4293 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4294 gint walk_pos;
4296 start = FALSE;
4297 if (!prev_autowrap && num_blocks == 0) {
4298 num_blocks++;
4299 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4300 G_CALLBACK(text_inserted),
4301 compose);
4303 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4304 goto colorize;
4306 uri_start = uri_stop = -1;
4307 quote_len = 0;
4308 quote_str = compose_get_quote_str(buffer, &iter, &quote_len);
4310 if (quote_str) {
4311 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4312 if (startq_offset == -1)
4313 startq_offset = gtk_text_iter_get_offset(&iter);
4314 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4315 if (quotelevel > 2) {
4316 /* recycle colors */
4317 if (prefs_common.recycle_quote_colors)
4318 quotelevel %= 3;
4319 else
4320 quotelevel = 2;
4322 if (!wrap_quote) {
4323 goto colorize;
4325 } else {
4326 if (startq_offset == -1)
4327 noq_offset = gtk_text_iter_get_offset(&iter);
4328 quotelevel = -1;
4331 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4332 goto colorize;
4334 if (gtk_text_iter_ends_line(&iter)) {
4335 goto colorize;
4336 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4337 prefs_common.linewrap_len,
4338 quote_len)) {
4339 GtkTextIter prev, next, cur;
4340 if (prev_autowrap != FALSE || force) {
4341 compose->automatic_break = TRUE;
4342 modified = TRUE;
4343 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4344 compose->automatic_break = FALSE;
4345 if (itemized_len && compose->autoindent) {
4346 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4347 if (!item_continuation)
4348 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4350 } else if (quote_str && wrap_quote) {
4351 compose->automatic_break = TRUE;
4352 modified = TRUE;
4353 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4354 compose->automatic_break = FALSE;
4355 if (itemized_len && compose->autoindent) {
4356 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4357 if (!item_continuation)
4358 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4360 } else
4361 goto colorize;
4362 /* remove trailing spaces */
4363 cur = break_pos;
4364 rem_item_len = itemized_len;
4365 while (compose->autoindent && rem_item_len-- > 0)
4366 gtk_text_iter_backward_char(&cur);
4367 gtk_text_iter_backward_char(&cur);
4369 prev = next = cur;
4370 while (!gtk_text_iter_starts_line(&cur)) {
4371 gunichar wc;
4373 gtk_text_iter_backward_char(&cur);
4374 wc = gtk_text_iter_get_char(&cur);
4375 if (!g_unichar_isspace(wc))
4376 break;
4377 prev = cur;
4379 if (!gtk_text_iter_equal(&prev, &next)) {
4380 gtk_text_buffer_delete(buffer, &prev, &next);
4381 break_pos = next;
4382 gtk_text_iter_forward_char(&break_pos);
4385 if (quote_str)
4386 gtk_text_buffer_insert(buffer, &break_pos,
4387 quote_str, -1);
4389 iter = break_pos;
4390 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4392 /* move iter to current line start */
4393 gtk_text_iter_set_line_offset(&iter, 0);
4394 if (quote_str) {
4395 g_free(quote_str);
4396 quote_str = NULL;
4398 continue;
4399 } else {
4400 /* move iter to next line start */
4401 iter = break_pos;
4402 lines++;
4405 colorize:
4406 if (!prev_autowrap && num_blocks > 0) {
4407 num_blocks--;
4408 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4409 G_CALLBACK(text_inserted),
4410 compose);
4412 end_of_line = iter;
4413 while (!gtk_text_iter_ends_line(&end_of_line)) {
4414 gtk_text_iter_forward_char(&end_of_line);
4416 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4418 nouri_start = gtk_text_iter_get_offset(&iter);
4419 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4421 walk_pos = gtk_text_iter_get_offset(&iter);
4422 /* FIXME: this looks phony. scanning for anything in the parse table */
4423 for (n = 0; n < PARSE_ELEMS; n++) {
4424 gchar *tmp;
4426 tmp = parser[n].search(walk, parser[n].needle);
4427 if (tmp) {
4428 if (scanpos == NULL || tmp < scanpos) {
4429 scanpos = tmp;
4430 last_index = n;
4435 bp = ep = 0;
4436 if (scanpos) {
4437 /* check if URI can be parsed */
4438 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4439 (const gchar **)&ep, FALSE)
4440 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4441 walk = ep;
4442 } else
4443 walk = scanpos +
4444 strlen(parser[last_index].needle);
4446 if (bp && ep) {
4447 uri_start = walk_pos + (bp - o_walk);
4448 uri_stop = walk_pos + (ep - o_walk);
4450 g_free(o_walk);
4451 o_walk = NULL;
4452 gtk_text_iter_forward_line(&iter);
4453 g_free(quote_str);
4454 quote_str = NULL;
4455 if (startq_offset != -1) {
4456 GtkTextIter startquote, endquote;
4457 gtk_text_buffer_get_iter_at_offset(
4458 buffer, &startquote, startq_offset);
4459 endquote = iter;
4461 switch (quotelevel) {
4462 case 0:
4463 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4464 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4465 gtk_text_buffer_apply_tag_by_name(
4466 buffer, "quote0", &startquote, &endquote);
4467 gtk_text_buffer_remove_tag_by_name(
4468 buffer, "quote1", &startquote, &endquote);
4469 gtk_text_buffer_remove_tag_by_name(
4470 buffer, "quote2", &startquote, &endquote);
4471 modified = TRUE;
4473 break;
4474 case 1:
4475 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4476 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4477 gtk_text_buffer_apply_tag_by_name(
4478 buffer, "quote1", &startquote, &endquote);
4479 gtk_text_buffer_remove_tag_by_name(
4480 buffer, "quote0", &startquote, &endquote);
4481 gtk_text_buffer_remove_tag_by_name(
4482 buffer, "quote2", &startquote, &endquote);
4483 modified = TRUE;
4485 break;
4486 case 2:
4487 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4488 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4489 gtk_text_buffer_apply_tag_by_name(
4490 buffer, "quote2", &startquote, &endquote);
4491 gtk_text_buffer_remove_tag_by_name(
4492 buffer, "quote0", &startquote, &endquote);
4493 gtk_text_buffer_remove_tag_by_name(
4494 buffer, "quote1", &startquote, &endquote);
4495 modified = TRUE;
4497 break;
4499 startq_offset = -1;
4500 } else if (noq_offset != -1) {
4501 GtkTextIter startnoquote, endnoquote;
4502 gtk_text_buffer_get_iter_at_offset(
4503 buffer, &startnoquote, noq_offset);
4504 endnoquote = iter;
4506 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4507 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4508 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4509 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4510 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4511 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4512 gtk_text_buffer_remove_tag_by_name(
4513 buffer, "quote0", &startnoquote, &endnoquote);
4514 gtk_text_buffer_remove_tag_by_name(
4515 buffer, "quote1", &startnoquote, &endnoquote);
4516 gtk_text_buffer_remove_tag_by_name(
4517 buffer, "quote2", &startnoquote, &endnoquote);
4518 modified = TRUE;
4520 noq_offset = -1;
4523 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4524 GtkTextIter nouri_start_iter, nouri_end_iter;
4525 gtk_text_buffer_get_iter_at_offset(
4526 buffer, &nouri_start_iter, nouri_start);
4527 gtk_text_buffer_get_iter_at_offset(
4528 buffer, &nouri_end_iter, nouri_stop);
4529 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4530 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4531 gtk_text_buffer_remove_tag_by_name(
4532 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4533 modified_before_remove = modified;
4534 modified = TRUE;
4535 removed = TRUE;
4538 if (uri_start >= 0 && uri_stop > 0) {
4539 GtkTextIter uri_start_iter, uri_end_iter, back;
4540 gtk_text_buffer_get_iter_at_offset(
4541 buffer, &uri_start_iter, uri_start);
4542 gtk_text_buffer_get_iter_at_offset(
4543 buffer, &uri_end_iter, uri_stop);
4544 back = uri_end_iter;
4545 gtk_text_iter_backward_char(&back);
4546 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4547 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4548 gtk_text_buffer_apply_tag_by_name(
4549 buffer, "link", &uri_start_iter, &uri_end_iter);
4550 modified = TRUE;
4551 if (removed && !modified_before_remove) {
4552 modified = FALSE;
4556 if (!modified) {
4557 // debug_print("not modified, out after %d lines\n", lines);
4558 goto end;
4561 // debug_print("modified, out after %d lines\n", lines);
4562 end:
4563 g_free(itemized_chars);
4564 if (par_iter)
4565 *par_iter = iter;
4566 undo_wrapping(compose->undostruct, FALSE);
4567 compose->autowrap = prev_autowrap;
4569 return modified;
4572 void compose_action_cb(void *data)
4574 Compose *compose = (Compose *)data;
4575 compose_wrap_all(compose);
4578 static void compose_wrap_all(Compose *compose)
4580 compose_wrap_all_full(compose, FALSE);
4583 static void compose_wrap_all_full(Compose *compose, gboolean force)
4585 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4586 GtkTextBuffer *buffer;
4587 GtkTextIter iter;
4588 gboolean modified = TRUE;
4590 buffer = gtk_text_view_get_buffer(text);
4592 gtk_text_buffer_get_start_iter(buffer, &iter);
4593 while (!gtk_text_iter_is_end(&iter) && modified)
4594 modified = compose_beautify_paragraph(compose, &iter, force);
4598 static void compose_set_title(Compose *compose)
4600 gchar *str;
4601 gchar *edited;
4602 gchar *subject;
4604 edited = compose->modified ? _(" [Edited]") : "";
4606 subject = gtk_editable_get_chars(
4607 GTK_EDITABLE(compose->subject_entry), 0, -1);
4609 #ifndef GENERIC_UMPC
4610 if (subject && strlen(subject))
4611 str = g_strdup_printf(_("%s - Compose message%s"),
4612 subject, edited);
4613 else
4614 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4615 #else
4616 str = g_strdup(_("Compose message"));
4617 #endif
4619 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4620 g_free(str);
4621 g_free(subject);
4625 * compose_current_mail_account:
4627 * Find a current mail account (the currently selected account, or the
4628 * default account, if a news account is currently selected). If a
4629 * mail account cannot be found, display an error message.
4631 * Return value: Mail account, or NULL if not found.
4633 static PrefsAccount *
4634 compose_current_mail_account(void)
4636 PrefsAccount *ac;
4638 if (cur_account && cur_account->protocol != A_NNTP)
4639 ac = cur_account;
4640 else {
4641 ac = account_get_default();
4642 if (!ac || ac->protocol == A_NNTP) {
4643 alertpanel_error(_("Account for sending mail is not specified.\n"
4644 "Please select a mail account before sending."));
4645 return NULL;
4648 return ac;
4651 #define QUOTE_IF_REQUIRED(out, str) \
4653 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4654 gchar *__tmp; \
4655 gint len; \
4657 len = strlen(str) + 3; \
4658 if ((__tmp = alloca(len)) == NULL) { \
4659 g_warning("can't allocate memory\n"); \
4660 g_string_free(header, TRUE); \
4661 return NULL; \
4663 g_snprintf(__tmp, len, "\"%s\"", str); \
4664 out = __tmp; \
4665 } else { \
4666 gchar *__tmp; \
4668 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4669 g_warning("can't allocate memory\n"); \
4670 g_string_free(header, TRUE); \
4671 return NULL; \
4672 } else \
4673 strcpy(__tmp, str); \
4675 out = __tmp; \
4679 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4681 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4682 gchar *__tmp; \
4683 gint len; \
4685 len = strlen(str) + 3; \
4686 if ((__tmp = alloca(len)) == NULL) { \
4687 g_warning("can't allocate memory\n"); \
4688 errret; \
4690 g_snprintf(__tmp, len, "\"%s\"", str); \
4691 out = __tmp; \
4692 } else { \
4693 gchar *__tmp; \
4695 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4696 g_warning("can't allocate memory\n"); \
4697 errret; \
4698 } else \
4699 strcpy(__tmp, str); \
4701 out = __tmp; \
4705 static void compose_select_account(Compose *compose, PrefsAccount *account,
4706 gboolean init)
4708 gchar *from = NULL, *header;
4709 ComposeHeaderEntry *header_entry;
4711 cm_return_if_fail(account != NULL);
4713 compose->account = account;
4714 if (account->name && *account->name) {
4715 gchar *buf;
4716 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4717 from = g_strdup_printf("%s <%s>",
4718 buf, account->address);
4719 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4720 } else {
4721 from = g_strdup_printf("<%s>",
4722 account->address);
4723 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4726 g_free(from);
4728 compose_set_title(compose);
4730 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4731 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4732 else
4733 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4734 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4735 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4736 else
4737 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4739 activate_privacy_system(compose, account, FALSE);
4741 if (!init && compose->mode != COMPOSE_REDIRECT) {
4742 undo_block(compose->undostruct);
4743 compose_insert_sig(compose, TRUE);
4744 undo_unblock(compose->undostruct);
4747 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4748 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4750 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4751 if (account->protocol == A_NNTP) {
4752 if (!strcmp(header, _("To:")))
4753 combobox_select_by_text(
4754 GTK_COMBO_BOX(header_entry->combo),
4755 _("Newsgroups:"));
4756 } else {
4757 if (!strcmp(header, _("Newsgroups:")))
4758 combobox_select_by_text(
4759 GTK_COMBO_BOX(header_entry->combo),
4760 _("To:"));
4764 g_free(header);
4766 #ifdef USE_ENCHANT
4767 /* use account's dict info if set */
4768 if (compose->gtkaspell) {
4769 if (account->enable_default_dictionary)
4770 gtkaspell_change_dict(compose->gtkaspell,
4771 account->default_dictionary, FALSE);
4772 if (account->enable_default_alt_dictionary)
4773 gtkaspell_change_alt_dict(compose->gtkaspell,
4774 account->default_alt_dictionary);
4775 if (account->enable_default_dictionary
4776 || account->enable_default_alt_dictionary)
4777 compose_spell_menu_changed(compose);
4779 #endif
4782 gboolean compose_check_for_valid_recipient(Compose *compose) {
4783 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4784 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4785 gboolean recipient_found = FALSE;
4786 GSList *list;
4787 gchar **strptr;
4789 /* free to and newsgroup list */
4790 slist_free_strings(compose->to_list);
4791 g_slist_free(compose->to_list);
4792 compose->to_list = NULL;
4794 slist_free_strings(compose->newsgroup_list);
4795 g_slist_free(compose->newsgroup_list);
4796 compose->newsgroup_list = NULL;
4798 /* search header entries for to and newsgroup entries */
4799 for (list = compose->header_list; list; list = list->next) {
4800 gchar *header;
4801 gchar *entry;
4802 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4803 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4804 g_strstrip(entry);
4805 g_strstrip(header);
4806 if (entry[0] != '\0') {
4807 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4808 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4809 compose->to_list = address_list_append(compose->to_list, entry);
4810 recipient_found = TRUE;
4813 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4814 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4815 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4816 recipient_found = TRUE;
4820 g_free(header);
4821 g_free(entry);
4823 return recipient_found;
4826 static gboolean compose_check_for_set_recipients(Compose *compose)
4828 if (compose->account->set_autocc && compose->account->auto_cc) {
4829 gboolean found_other = FALSE;
4830 GSList *list;
4831 /* search header entries for to and newsgroup entries */
4832 for (list = compose->header_list; list; list = list->next) {
4833 gchar *entry;
4834 gchar *header;
4835 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4836 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4837 g_strstrip(entry);
4838 g_strstrip(header);
4839 if (strcmp(entry, compose->account->auto_cc)
4840 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4841 found_other = TRUE;
4842 g_free(entry);
4843 break;
4845 g_free(entry);
4846 g_free(header);
4848 if (!found_other) {
4849 AlertValue aval;
4850 if (compose->batch) {
4851 gtk_widget_show_all(compose->window);
4853 aval = alertpanel(_("Send"),
4854 _("The only recipient is the default CC address. Send anyway?"),
4855 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4856 if (aval != G_ALERTALTERNATE)
4857 return FALSE;
4860 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4861 gboolean found_other = FALSE;
4862 GSList *list;
4863 /* search header entries for to and newsgroup entries */
4864 for (list = compose->header_list; list; list = list->next) {
4865 gchar *entry;
4866 gchar *header;
4867 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4868 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4869 g_strstrip(entry);
4870 g_strstrip(header);
4871 if (strcmp(entry, compose->account->auto_bcc)
4872 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4873 found_other = TRUE;
4874 g_free(entry);
4875 break;
4877 g_free(entry);
4878 g_free(header);
4880 if (!found_other) {
4881 AlertValue aval;
4882 if (compose->batch) {
4883 gtk_widget_show_all(compose->window);
4885 aval = alertpanel(_("Send"),
4886 _("The only recipient is the default BCC address. Send anyway?"),
4887 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4888 if (aval != G_ALERTALTERNATE)
4889 return FALSE;
4892 return TRUE;
4895 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4897 const gchar *str;
4899 if (compose_check_for_valid_recipient(compose) == FALSE) {
4900 if (compose->batch) {
4901 gtk_widget_show_all(compose->window);
4903 alertpanel_error(_("Recipient is not specified."));
4904 return FALSE;
4907 if (compose_check_for_set_recipients(compose) == FALSE) {
4908 return FALSE;
4911 if (!compose->batch) {
4912 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4913 if (*str == '\0' && check_everything == TRUE &&
4914 compose->mode != COMPOSE_REDIRECT) {
4915 AlertValue aval;
4916 gchar *button_label;
4917 gchar *message;
4919 if (compose->sending)
4920 button_label = _("+_Send");
4921 else
4922 button_label = _("+_Queue");
4923 message = g_strdup_printf(_("Subject is empty. %s"),
4924 compose->sending?_("Send it anyway?"):
4925 _("Queue it anyway?"));
4927 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4928 GTK_STOCK_CANCEL, button_label, NULL);
4929 g_free(message);
4930 if (aval != G_ALERTALTERNATE)
4931 return FALSE;
4935 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4936 return FALSE;
4938 return TRUE;
4941 gint compose_send(Compose *compose)
4943 gint msgnum;
4944 FolderItem *folder = NULL;
4945 gint val = -1;
4946 gchar *msgpath = NULL;
4947 gboolean discard_window = FALSE;
4948 gchar *errstr = NULL;
4949 gchar *tmsgid = NULL;
4950 MainWindow *mainwin = mainwindow_get_mainwindow();
4951 gboolean queued_removed = FALSE;
4953 if (prefs_common.send_dialog_invisible
4954 || compose->batch == TRUE)
4955 discard_window = TRUE;
4957 compose_allow_user_actions (compose, FALSE);
4958 compose->sending = TRUE;
4960 if (compose_check_entries(compose, TRUE) == FALSE) {
4961 if (compose->batch) {
4962 gtk_widget_show_all(compose->window);
4964 goto bail;
4967 inc_lock();
4968 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4970 if (val) {
4971 if (compose->batch) {
4972 gtk_widget_show_all(compose->window);
4974 if (val == -4) {
4975 alertpanel_error(_("Could not queue message for sending:\n\n"
4976 "Charset conversion failed."));
4977 } else if (val == -5) {
4978 alertpanel_error(_("Could not queue message for sending:\n\n"
4979 "Couldn't get recipient encryption key."));
4980 } else if (val == -6) {
4981 /* silent error */
4982 } else if (val == -3) {
4983 if (privacy_peek_error())
4984 alertpanel_error(_("Could not queue message for sending:\n\n"
4985 "Signature failed: %s"), privacy_get_error());
4986 } else if (val == -2 && errno != 0) {
4987 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4988 } else {
4989 alertpanel_error(_("Could not queue message for sending."));
4991 goto bail;
4994 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4995 if (discard_window) {
4996 compose->sending = FALSE;
4997 compose_close(compose);
4998 /* No more compose access in the normal codepath
4999 * after this point! */
5000 compose = NULL;
5003 if (msgnum == 0) {
5004 alertpanel_error(_("The message was queued but could not be "
5005 "sent.\nUse \"Send queued messages\" from "
5006 "the main window to retry."));
5007 if (!discard_window) {
5008 goto bail;
5010 inc_unlock();
5011 g_free(tmsgid);
5012 return -1;
5014 if (msgpath == NULL) {
5015 msgpath = folder_item_fetch_msg(folder, msgnum);
5016 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
5017 g_free(msgpath);
5018 } else {
5019 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
5020 claws_unlink(msgpath);
5021 g_free(msgpath);
5023 if (!discard_window) {
5024 if (val != 0) {
5025 if (!queued_removed)
5026 folder_item_remove_msg(folder, msgnum);
5027 folder_item_scan(folder);
5028 if (tmsgid) {
5029 /* make sure we delete that */
5030 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5031 if (tmp) {
5032 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5033 folder_item_remove_msg(folder, tmp->msgnum);
5034 procmsg_msginfo_free(tmp);
5040 if (val == 0) {
5041 if (!queued_removed)
5042 folder_item_remove_msg(folder, msgnum);
5043 folder_item_scan(folder);
5044 if (tmsgid) {
5045 /* make sure we delete that */
5046 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5047 if (tmp) {
5048 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5049 folder_item_remove_msg(folder, tmp->msgnum);
5050 procmsg_msginfo_free(tmp);
5053 if (!discard_window) {
5054 compose->sending = FALSE;
5055 compose_allow_user_actions (compose, TRUE);
5056 compose_close(compose);
5058 } else {
5059 if (errstr) {
5060 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5061 "the main window to retry."), errstr);
5062 g_free(errstr);
5063 } else {
5064 alertpanel_error_log(_("The message was queued but could not be "
5065 "sent.\nUse \"Send queued messages\" from "
5066 "the main window to retry."));
5068 if (!discard_window) {
5069 goto bail;
5071 inc_unlock();
5072 g_free(tmsgid);
5073 return -1;
5075 g_free(tmsgid);
5076 inc_unlock();
5077 toolbar_main_set_sensitive(mainwin);
5078 main_window_set_menu_sensitive(mainwin);
5079 return 0;
5081 bail:
5082 inc_unlock();
5083 g_free(tmsgid);
5084 compose_allow_user_actions (compose, TRUE);
5085 compose->sending = FALSE;
5086 compose->modified = TRUE;
5087 toolbar_main_set_sensitive(mainwin);
5088 main_window_set_menu_sensitive(mainwin);
5090 return -1;
5093 static gboolean compose_use_attach(Compose *compose)
5095 GtkTreeModel *model = gtk_tree_view_get_model
5096 (GTK_TREE_VIEW(compose->attach_clist));
5097 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5100 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5101 FILE *fp)
5103 gchar buf[BUFFSIZE];
5104 gchar *str;
5105 gboolean first_to_address;
5106 gboolean first_cc_address;
5107 GSList *list;
5108 ComposeHeaderEntry *headerentry;
5109 const gchar *headerentryname;
5110 const gchar *cc_hdr;
5111 const gchar *to_hdr;
5112 gboolean err = FALSE;
5114 debug_print("Writing redirect header\n");
5116 cc_hdr = prefs_common_translated_header_name("Cc:");
5117 to_hdr = prefs_common_translated_header_name("To:");
5119 first_to_address = TRUE;
5120 for (list = compose->header_list; list; list = list->next) {
5121 headerentry = ((ComposeHeaderEntry *)list->data);
5122 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5124 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5125 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5126 Xstrdup_a(str, entstr, return -1);
5127 g_strstrip(str);
5128 if (str[0] != '\0') {
5129 compose_convert_header
5130 (compose, buf, sizeof(buf), str,
5131 strlen("Resent-To") + 2, TRUE);
5133 if (first_to_address) {
5134 err |= (fprintf(fp, "Resent-To: ") < 0);
5135 first_to_address = FALSE;
5136 } else {
5137 err |= (fprintf(fp, ",") < 0);
5139 err |= (fprintf(fp, "%s", buf) < 0);
5143 if (!first_to_address) {
5144 err |= (fprintf(fp, "\n") < 0);
5147 first_cc_address = TRUE;
5148 for (list = compose->header_list; list; list = list->next) {
5149 headerentry = ((ComposeHeaderEntry *)list->data);
5150 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5152 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5153 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5154 Xstrdup_a(str, strg, return -1);
5155 g_strstrip(str);
5156 if (str[0] != '\0') {
5157 compose_convert_header
5158 (compose, buf, sizeof(buf), str,
5159 strlen("Resent-Cc") + 2, TRUE);
5161 if (first_cc_address) {
5162 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5163 first_cc_address = FALSE;
5164 } else {
5165 err |= (fprintf(fp, ",") < 0);
5167 err |= (fprintf(fp, "%s", buf) < 0);
5171 if (!first_cc_address) {
5172 err |= (fprintf(fp, "\n") < 0);
5175 return (err ? -1:0);
5178 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5180 gchar buf[BUFFSIZE];
5181 gchar *str;
5182 const gchar *entstr;
5183 /* struct utsname utsbuf; */
5184 gboolean err = FALSE;
5186 cm_return_val_if_fail(fp != NULL, -1);
5187 cm_return_val_if_fail(compose->account != NULL, -1);
5188 cm_return_val_if_fail(compose->account->address != NULL, -1);
5190 /* Resent-Date */
5191 get_rfc822_date(buf, sizeof(buf));
5192 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5194 /* Resent-From */
5195 if (compose->account->name && *compose->account->name) {
5196 compose_convert_header
5197 (compose, buf, sizeof(buf), compose->account->name,
5198 strlen("From: "), TRUE);
5199 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5200 buf, compose->account->address) < 0);
5201 } else
5202 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5204 /* Subject */
5205 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5206 if (*entstr != '\0') {
5207 Xstrdup_a(str, entstr, return -1);
5208 g_strstrip(str);
5209 if (*str != '\0') {
5210 compose_convert_header(compose, buf, sizeof(buf), str,
5211 strlen("Subject: "), FALSE);
5212 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5216 /* Resent-Message-ID */
5217 if (compose->account->set_domain && compose->account->domain) {
5218 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5219 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5220 g_snprintf(buf, sizeof(buf), "%s",
5221 strchr(compose->account->address, '@') ?
5222 strchr(compose->account->address, '@')+1 :
5223 compose->account->address);
5224 } else {
5225 g_snprintf(buf, sizeof(buf), "%s", "");
5228 if (compose->account->gen_msgid) {
5229 gchar *addr = NULL;
5230 if (compose->account->msgid_with_addr) {
5231 addr = compose->account->address;
5233 generate_msgid(buf, sizeof(buf), addr);
5234 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5235 compose->msgid = g_strdup(buf);
5236 } else {
5237 compose->msgid = NULL;
5240 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5241 return -1;
5243 /* separator between header and body */
5244 err |= (fputs("\n", fp) == EOF);
5246 return (err ? -1:0);
5249 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5251 FILE *fp;
5252 size_t len;
5253 gchar buf[BUFFSIZE];
5254 int i = 0;
5255 gboolean skip = FALSE;
5256 gboolean err = FALSE;
5257 gchar *not_included[]={
5258 "Return-Path:", "Delivered-To:", "Received:",
5259 "Subject:", "X-UIDL:", "AF:",
5260 "NF:", "PS:", "SRH:",
5261 "SFN:", "DSR:", "MID:",
5262 "CFG:", "PT:", "S:",
5263 "RQ:", "SSV:", "NSV:",
5264 "SSH:", "R:", "MAID:",
5265 "NAID:", "RMID:", "FMID:",
5266 "SCF:", "RRCPT:", "NG:",
5267 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5268 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5269 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5270 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5271 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5272 NULL
5274 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5275 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5276 return -1;
5279 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5280 skip = FALSE;
5281 for (i = 0; not_included[i] != NULL; i++) {
5282 if (g_ascii_strncasecmp(buf, not_included[i],
5283 strlen(not_included[i])) == 0) {
5284 skip = TRUE;
5285 break;
5288 if (skip)
5289 continue;
5290 if (fputs(buf, fdest) == -1)
5291 goto error;
5293 if (!prefs_common.redirect_keep_from) {
5294 if (g_ascii_strncasecmp(buf, "From:",
5295 strlen("From:")) == 0) {
5296 err |= (fputs(" (by way of ", fdest) == EOF);
5297 if (compose->account->name
5298 && *compose->account->name) {
5299 compose_convert_header
5300 (compose, buf, sizeof(buf),
5301 compose->account->name,
5302 strlen("From: "),
5303 FALSE);
5304 err |= (fprintf(fdest, "%s <%s>",
5305 buf,
5306 compose->account->address) < 0);
5307 } else
5308 err |= (fprintf(fdest, "%s",
5309 compose->account->address) < 0);
5310 err |= (fputs(")", fdest) == EOF);
5314 if (fputs("\n", fdest) == -1)
5315 goto error;
5318 if (err)
5319 goto error;
5321 if (compose_redirect_write_headers(compose, fdest))
5322 goto error;
5324 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5325 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5326 goto error;
5329 fclose(fp);
5331 return 0;
5332 error:
5333 fclose(fp);
5335 return -1;
5338 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5340 GtkTextBuffer *buffer;
5341 GtkTextIter start, end;
5342 gchar *chars;
5343 gchar *buf;
5344 const gchar *out_codeset;
5345 EncodingType encoding = ENC_UNKNOWN;
5346 MimeInfo *mimemsg, *mimetext;
5347 gint line;
5348 const gchar *src_codeset = CS_INTERNAL;
5349 gchar *from_addr = NULL;
5350 gchar *from_name = NULL;
5352 if (action == COMPOSE_WRITE_FOR_SEND)
5353 attach_parts = TRUE;
5355 /* create message MimeInfo */
5356 mimemsg = procmime_mimeinfo_new();
5357 mimemsg->type = MIMETYPE_MESSAGE;
5358 mimemsg->subtype = g_strdup("rfc822");
5359 mimemsg->content = MIMECONTENT_MEM;
5360 mimemsg->tmp = TRUE; /* must free content later */
5361 mimemsg->data.mem = compose_get_header(compose);
5363 /* Create text part MimeInfo */
5364 /* get all composed text */
5365 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5366 gtk_text_buffer_get_start_iter(buffer, &start);
5367 gtk_text_buffer_get_end_iter(buffer, &end);
5368 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5370 out_codeset = conv_get_charset_str(compose->out_encoding);
5372 if (!out_codeset && is_ascii_str(chars)) {
5373 out_codeset = CS_US_ASCII;
5374 } else if (prefs_common.outgoing_fallback_to_ascii &&
5375 is_ascii_str(chars)) {
5376 out_codeset = CS_US_ASCII;
5377 encoding = ENC_7BIT;
5380 if (!out_codeset) {
5381 gchar *test_conv_global_out = NULL;
5382 gchar *test_conv_reply = NULL;
5384 /* automatic mode. be automatic. */
5385 codeconv_set_strict(TRUE);
5387 out_codeset = conv_get_outgoing_charset_str();
5388 if (out_codeset) {
5389 debug_print("trying to convert to %s\n", out_codeset);
5390 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5393 if (!test_conv_global_out && compose->orig_charset
5394 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5395 out_codeset = compose->orig_charset;
5396 debug_print("failure; trying to convert to %s\n", out_codeset);
5397 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5400 if (!test_conv_global_out && !test_conv_reply) {
5401 /* we're lost */
5402 out_codeset = CS_INTERNAL;
5403 debug_print("failure; finally using %s\n", out_codeset);
5405 g_free(test_conv_global_out);
5406 g_free(test_conv_reply);
5407 codeconv_set_strict(FALSE);
5410 if (encoding == ENC_UNKNOWN) {
5411 if (prefs_common.encoding_method == CTE_BASE64)
5412 encoding = ENC_BASE64;
5413 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5414 encoding = ENC_QUOTED_PRINTABLE;
5415 else if (prefs_common.encoding_method == CTE_8BIT)
5416 encoding = ENC_8BIT;
5417 else
5418 encoding = procmime_get_encoding_for_charset(out_codeset);
5421 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5422 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5424 if (action == COMPOSE_WRITE_FOR_SEND) {
5425 codeconv_set_strict(TRUE);
5426 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5427 codeconv_set_strict(FALSE);
5429 if (!buf) {
5430 AlertValue aval;
5431 gchar *msg;
5433 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5434 "to the specified %s charset.\n"
5435 "Send it as %s?"), out_codeset, src_codeset);
5436 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5437 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5438 g_free(msg);
5440 if (aval != G_ALERTALTERNATE) {
5441 g_free(chars);
5442 return -3;
5443 } else {
5444 buf = chars;
5445 out_codeset = src_codeset;
5446 chars = NULL;
5449 } else {
5450 buf = chars;
5451 out_codeset = src_codeset;
5452 chars = NULL;
5454 g_free(chars);
5456 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5457 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5458 strstr(buf, "\nFrom ") != NULL) {
5459 encoding = ENC_QUOTED_PRINTABLE;
5463 mimetext = procmime_mimeinfo_new();
5464 mimetext->content = MIMECONTENT_MEM;
5465 mimetext->tmp = TRUE; /* must free content later */
5466 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5467 * and free the data, which we need later. */
5468 mimetext->data.mem = g_strdup(buf);
5469 mimetext->type = MIMETYPE_TEXT;
5470 mimetext->subtype = g_strdup("plain");
5471 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5472 g_strdup(out_codeset));
5474 /* protect trailing spaces when signing message */
5475 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5476 privacy_system_can_sign(compose->privacy_system)) {
5477 encoding = ENC_QUOTED_PRINTABLE;
5480 debug_print("main text: %zd bytes encoded as %s in %d\n",
5481 strlen(buf), out_codeset, encoding);
5483 /* check for line length limit */
5484 if (action == COMPOSE_WRITE_FOR_SEND &&
5485 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5486 check_line_length(buf, 1000, &line) < 0) {
5487 AlertValue aval;
5488 gchar *msg;
5490 msg = g_strdup_printf
5491 (_("Line %d exceeds the line length limit (998 bytes).\n"
5492 "The contents of the message might be broken on the way to the delivery.\n"
5493 "\n"
5494 "Send it anyway?"), line + 1);
5495 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5496 g_free(msg);
5497 if (aval != G_ALERTALTERNATE) {
5498 g_free(buf);
5499 return -1;
5503 if (encoding != ENC_UNKNOWN)
5504 procmime_encode_content(mimetext, encoding);
5506 /* append attachment parts */
5507 if (compose_use_attach(compose) && attach_parts) {
5508 MimeInfo *mimempart;
5509 gchar *boundary = NULL;
5510 mimempart = procmime_mimeinfo_new();
5511 mimempart->content = MIMECONTENT_EMPTY;
5512 mimempart->type = MIMETYPE_MULTIPART;
5513 mimempart->subtype = g_strdup("mixed");
5515 do {
5516 g_free(boundary);
5517 boundary = generate_mime_boundary(NULL);
5518 } while (strstr(buf, boundary) != NULL);
5520 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5521 boundary);
5523 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5525 g_node_append(mimempart->node, mimetext->node);
5526 g_node_append(mimemsg->node, mimempart->node);
5528 if (compose_add_attachments(compose, mimempart) < 0)
5529 return -1;
5530 } else
5531 g_node_append(mimemsg->node, mimetext->node);
5533 g_free(buf);
5535 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5536 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5537 /* extract name and address */
5538 if (strstr(spec, " <") && strstr(spec, ">")) {
5539 from_addr = g_strdup(strrchr(spec, '<')+1);
5540 *(strrchr(from_addr, '>')) = '\0';
5541 from_name = g_strdup(spec);
5542 *(strrchr(from_name, '<')) = '\0';
5543 } else {
5544 from_name = NULL;
5545 from_addr = NULL;
5547 g_free(spec);
5549 /* sign message if sending */
5550 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5551 privacy_system_can_sign(compose->privacy_system))
5552 if (!privacy_sign(compose->privacy_system, mimemsg,
5553 compose->account, from_addr)) {
5554 g_free(from_name);
5555 g_free(from_addr);
5556 return -2;
5558 g_free(from_name);
5559 g_free(from_addr);
5560 procmime_write_mimeinfo(mimemsg, fp);
5562 procmime_mimeinfo_free_all(mimemsg);
5564 return 0;
5567 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5569 GtkTextBuffer *buffer;
5570 GtkTextIter start, end;
5571 FILE *fp;
5572 size_t len;
5573 gchar *chars, *tmp;
5575 if ((fp = g_fopen(file, "wb")) == NULL) {
5576 FILE_OP_ERROR(file, "fopen");
5577 return -1;
5580 /* chmod for security */
5581 if (change_file_mode_rw(fp, file) < 0) {
5582 FILE_OP_ERROR(file, "chmod");
5583 g_warning("can't change file mode\n");
5586 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5587 gtk_text_buffer_get_start_iter(buffer, &start);
5588 gtk_text_buffer_get_end_iter(buffer, &end);
5589 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5591 chars = conv_codeset_strdup
5592 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5594 g_free(tmp);
5595 if (!chars) return -1;
5597 /* write body */
5598 len = strlen(chars);
5599 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5600 FILE_OP_ERROR(file, "fwrite");
5601 g_free(chars);
5602 fclose(fp);
5603 claws_unlink(file);
5604 return -1;
5607 g_free(chars);
5609 if (fclose(fp) == EOF) {
5610 FILE_OP_ERROR(file, "fclose");
5611 claws_unlink(file);
5612 return -1;
5614 return 0;
5617 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5619 FolderItem *item;
5620 MsgInfo *msginfo = compose->targetinfo;
5622 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5623 if (!msginfo) return -1;
5625 if (!force && MSG_IS_LOCKED(msginfo->flags))
5626 return 0;
5628 item = msginfo->folder;
5629 cm_return_val_if_fail(item != NULL, -1);
5631 if (procmsg_msg_exist(msginfo) &&
5632 (folder_has_parent_of_type(item, F_QUEUE) ||
5633 folder_has_parent_of_type(item, F_DRAFT)
5634 || msginfo == compose->autosaved_draft)) {
5635 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5636 g_warning("can't remove the old message\n");
5637 return -1;
5638 } else {
5639 debug_print("removed reedit target %d\n", msginfo->msgnum);
5643 return 0;
5646 static void compose_remove_draft(Compose *compose)
5648 FolderItem *drafts;
5649 MsgInfo *msginfo = compose->targetinfo;
5650 drafts = account_get_special_folder(compose->account, F_DRAFT);
5652 if (procmsg_msg_exist(msginfo)) {
5653 folder_item_remove_msg(drafts, msginfo->msgnum);
5658 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5659 gboolean remove_reedit_target)
5661 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5664 static gboolean compose_warn_encryption(Compose *compose)
5666 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5667 AlertValue val = G_ALERTALTERNATE;
5669 if (warning == NULL)
5670 return TRUE;
5672 val = alertpanel_full(_("Encryption warning"), warning,
5673 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5674 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5675 if (val & G_ALERTDISABLE) {
5676 val &= ~G_ALERTDISABLE;
5677 if (val == G_ALERTALTERNATE)
5678 privacy_inhibit_encrypt_warning(compose->privacy_system,
5679 TRUE);
5682 if (val == G_ALERTALTERNATE) {
5683 return TRUE;
5684 } else {
5685 return FALSE;
5689 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5690 gchar **msgpath, gboolean check_subject,
5691 gboolean remove_reedit_target)
5693 FolderItem *queue;
5694 gchar *tmp;
5695 FILE *fp;
5696 GSList *cur;
5697 gint num;
5698 static gboolean lock = FALSE;
5699 PrefsAccount *mailac = NULL, *newsac = NULL;
5700 gboolean err = FALSE;
5702 debug_print("queueing message...\n");
5703 cm_return_val_if_fail(compose->account != NULL, -1);
5705 lock = TRUE;
5707 if (compose_check_entries(compose, check_subject) == FALSE) {
5708 lock = FALSE;
5709 if (compose->batch) {
5710 gtk_widget_show_all(compose->window);
5712 return -1;
5715 if (!compose->to_list && !compose->newsgroup_list) {
5716 g_warning("can't get recipient list.");
5717 lock = FALSE;
5718 return -1;
5721 if (compose->to_list) {
5722 if (compose->account->protocol != A_NNTP)
5723 mailac = compose->account;
5724 else if (cur_account && cur_account->protocol != A_NNTP)
5725 mailac = cur_account;
5726 else if (!(mailac = compose_current_mail_account())) {
5727 lock = FALSE;
5728 alertpanel_error(_("No account for sending mails available!"));
5729 return -1;
5733 if (compose->newsgroup_list) {
5734 if (compose->account->protocol == A_NNTP)
5735 newsac = compose->account;
5736 else {
5737 lock = FALSE;
5738 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5739 return -1;
5743 /* write queue header */
5744 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5745 G_DIR_SEPARATOR, compose, (guint) rand());
5746 debug_print("queuing to %s\n", tmp);
5747 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5748 FILE_OP_ERROR(tmp, "fopen");
5749 g_free(tmp);
5750 lock = FALSE;
5751 return -2;
5754 if (change_file_mode_rw(fp, tmp) < 0) {
5755 FILE_OP_ERROR(tmp, "chmod");
5756 g_warning("can't change file mode\n");
5759 /* queueing variables */
5760 err |= (fprintf(fp, "AF:\n") < 0);
5761 err |= (fprintf(fp, "NF:0\n") < 0);
5762 err |= (fprintf(fp, "PS:10\n") < 0);
5763 err |= (fprintf(fp, "SRH:1\n") < 0);
5764 err |= (fprintf(fp, "SFN:\n") < 0);
5765 err |= (fprintf(fp, "DSR:\n") < 0);
5766 if (compose->msgid)
5767 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5768 else
5769 err |= (fprintf(fp, "MID:\n") < 0);
5770 err |= (fprintf(fp, "CFG:\n") < 0);
5771 err |= (fprintf(fp, "PT:0\n") < 0);
5772 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5773 err |= (fprintf(fp, "RQ:\n") < 0);
5774 if (mailac)
5775 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5776 else
5777 err |= (fprintf(fp, "SSV:\n") < 0);
5778 if (newsac)
5779 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5780 else
5781 err |= (fprintf(fp, "NSV:\n") < 0);
5782 err |= (fprintf(fp, "SSH:\n") < 0);
5783 /* write recepient list */
5784 if (compose->to_list) {
5785 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5786 for (cur = compose->to_list->next; cur != NULL;
5787 cur = cur->next)
5788 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5789 err |= (fprintf(fp, "\n") < 0);
5791 /* write newsgroup list */
5792 if (compose->newsgroup_list) {
5793 err |= (fprintf(fp, "NG:") < 0);
5794 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5795 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5796 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5797 err |= (fprintf(fp, "\n") < 0);
5799 /* Sylpheed account IDs */
5800 if (mailac)
5801 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5802 if (newsac)
5803 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5806 if (compose->privacy_system != NULL) {
5807 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5808 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5809 if (compose->use_encryption) {
5810 gchar *encdata;
5811 if (!compose_warn_encryption(compose)) {
5812 lock = FALSE;
5813 fclose(fp);
5814 claws_unlink(tmp);
5815 g_free(tmp);
5816 return -6;
5818 if (mailac && mailac->encrypt_to_self) {
5819 GSList *tmp_list = g_slist_copy(compose->to_list);
5820 tmp_list = g_slist_append(tmp_list, compose->account->address);
5821 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5822 g_slist_free(tmp_list);
5823 } else {
5824 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5826 if (encdata != NULL) {
5827 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5828 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5829 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5830 encdata) < 0);
5831 } /* else we finally dont want to encrypt */
5832 } else {
5833 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5834 /* and if encdata was null, it means there's been a problem in
5835 * key selection */
5836 lock = FALSE;
5837 fclose(fp);
5838 claws_unlink(tmp);
5839 g_free(tmp);
5840 return -5;
5842 g_free(encdata);
5846 /* Save copy folder */
5847 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5848 gchar *savefolderid;
5850 savefolderid = compose_get_save_to(compose);
5851 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5852 g_free(savefolderid);
5854 /* Save copy folder */
5855 if (compose->return_receipt) {
5856 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5858 /* Message-ID of message replying to */
5859 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5860 gchar *folderid;
5862 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5863 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5864 g_free(folderid);
5866 /* Message-ID of message forwarding to */
5867 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5868 gchar *folderid;
5870 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5871 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5872 g_free(folderid);
5875 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5876 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5878 /* end of headers */
5879 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5881 if (compose->redirect_filename != NULL) {
5882 if (compose_redirect_write_to_file(compose, fp) < 0) {
5883 lock = FALSE;
5884 fclose(fp);
5885 claws_unlink(tmp);
5886 g_free(tmp);
5887 return -2;
5889 } else {
5890 gint result = 0;
5891 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5892 lock = FALSE;
5893 fclose(fp);
5894 claws_unlink(tmp);
5895 g_free(tmp);
5896 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5899 if (err == TRUE) {
5900 g_warning("failed to write queue message\n");
5901 fclose(fp);
5902 claws_unlink(tmp);
5903 g_free(tmp);
5904 lock = FALSE;
5905 return -2;
5907 if (fclose(fp) == EOF) {
5908 FILE_OP_ERROR(tmp, "fclose");
5909 claws_unlink(tmp);
5910 g_free(tmp);
5911 lock = FALSE;
5912 return -2;
5915 if (item && *item) {
5916 queue = *item;
5917 } else {
5918 queue = account_get_special_folder(compose->account, F_QUEUE);
5920 if (!queue) {
5921 g_warning("can't find queue folder\n");
5922 claws_unlink(tmp);
5923 g_free(tmp);
5924 lock = FALSE;
5925 return -1;
5927 folder_item_scan(queue);
5928 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5929 g_warning("can't queue the message\n");
5930 claws_unlink(tmp);
5931 g_free(tmp);
5932 lock = FALSE;
5933 return -1;
5936 if (msgpath == NULL) {
5937 claws_unlink(tmp);
5938 g_free(tmp);
5939 } else
5940 *msgpath = tmp;
5942 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5943 compose_remove_reedit_target(compose, FALSE);
5946 if ((msgnum != NULL) && (item != NULL)) {
5947 *msgnum = num;
5948 *item = queue;
5951 return 0;
5954 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
5956 AttachInfo *ainfo;
5957 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5958 MimeInfo *mimepart;
5959 struct stat statbuf;
5960 gchar *type, *subtype;
5961 GtkTreeModel *model;
5962 GtkTreeIter iter;
5964 model = gtk_tree_view_get_model(tree_view);
5966 if (!gtk_tree_model_get_iter_first(model, &iter))
5967 return 0;
5968 do {
5969 gtk_tree_model_get(model, &iter,
5970 COL_DATA, &ainfo,
5971 -1);
5973 if (!is_file_exist(ainfo->file)) {
5974 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
5975 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
5976 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
5977 g_free(msg);
5978 if (val == G_ALERTDEFAULT) {
5979 return -1;
5981 continue;
5983 mimepart = procmime_mimeinfo_new();
5984 mimepart->content = MIMECONTENT_FILE;
5985 mimepart->data.filename = g_strdup(ainfo->file);
5986 mimepart->tmp = FALSE; /* or we destroy our attachment */
5987 mimepart->offset = 0;
5989 g_stat(ainfo->file, &statbuf);
5990 mimepart->length = statbuf.st_size;
5992 type = g_strdup(ainfo->content_type);
5994 if (!strchr(type, '/')) {
5995 g_free(type);
5996 type = g_strdup("application/octet-stream");
5999 subtype = strchr(type, '/') + 1;
6000 *(subtype - 1) = '\0';
6001 mimepart->type = procmime_get_media_type(type);
6002 mimepart->subtype = g_strdup(subtype);
6003 g_free(type);
6005 if (mimepart->type == MIMETYPE_MESSAGE &&
6006 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6007 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6008 } else if (mimepart->type == MIMETYPE_TEXT) {
6009 if (!ainfo->name && compose->mode == COMPOSE_FORWARD_INLINE) {
6010 /* Text parts with no name come from multipart/alternative
6011 * forwards. Make sure the recipient won't look at the
6012 * original HTML part by mistake. */
6013 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6014 ainfo->name = g_strdup_printf(_("Original %s part"),
6015 mimepart->subtype);
6017 if (ainfo->charset)
6018 g_hash_table_insert(mimepart->typeparameters,
6019 g_strdup("charset"), g_strdup(ainfo->charset));
6021 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6022 if (mimepart->type == MIMETYPE_APPLICATION &&
6023 !strcmp2(mimepart->subtype, "octet-stream"))
6024 g_hash_table_insert(mimepart->typeparameters,
6025 g_strdup("name"), g_strdup(ainfo->name));
6026 g_hash_table_insert(mimepart->dispositionparameters,
6027 g_strdup("filename"), g_strdup(ainfo->name));
6028 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6031 if (mimepart->type == MIMETYPE_MESSAGE
6032 || mimepart->type == MIMETYPE_MULTIPART)
6033 ainfo->encoding = ENC_BINARY;
6034 else if (compose->use_signing) {
6035 if (ainfo->encoding == ENC_7BIT)
6036 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6037 else if (ainfo->encoding == ENC_8BIT)
6038 ainfo->encoding = ENC_BASE64;
6043 procmime_encode_content(mimepart, ainfo->encoding);
6045 g_node_append(parent->node, mimepart->node);
6046 } while (gtk_tree_model_iter_next(model, &iter));
6048 return 0;
6051 #define IS_IN_CUSTOM_HEADER(header) \
6052 (compose->account->add_customhdr && \
6053 custom_header_find(compose->account->customhdr_list, header) != NULL)
6055 static void compose_add_headerfield_from_headerlist(Compose *compose,
6056 GString *header,
6057 const gchar *fieldname,
6058 const gchar *seperator)
6060 gchar *str, *fieldname_w_colon;
6061 gboolean add_field = FALSE;
6062 GSList *list;
6063 ComposeHeaderEntry *headerentry;
6064 const gchar *headerentryname;
6065 const gchar *trans_fieldname;
6066 GString *fieldstr;
6068 if (IS_IN_CUSTOM_HEADER(fieldname))
6069 return;
6071 debug_print("Adding %s-fields\n", fieldname);
6073 fieldstr = g_string_sized_new(64);
6075 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6076 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6078 for (list = compose->header_list; list; list = list->next) {
6079 headerentry = ((ComposeHeaderEntry *)list->data);
6080 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6082 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6083 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6084 g_strstrip(str);
6085 if (str[0] != '\0') {
6086 if (add_field)
6087 g_string_append(fieldstr, seperator);
6088 g_string_append(fieldstr, str);
6089 add_field = TRUE;
6091 g_free(str);
6094 if (add_field) {
6095 gchar *buf;
6097 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6098 compose_convert_header
6099 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6100 strlen(fieldname) + 2, TRUE);
6101 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6102 g_free(buf);
6105 g_free(fieldname_w_colon);
6106 g_string_free(fieldstr, TRUE);
6108 return;
6111 static gchar *compose_get_header(Compose *compose)
6113 gchar buf[BUFFSIZE];
6114 const gchar *entry_str;
6115 gchar *str;
6116 gchar *name;
6117 GSList *list;
6118 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6119 GString *header;
6120 gchar *from_name = NULL, *from_address = NULL;
6121 gchar *tmp;
6123 cm_return_val_if_fail(compose->account != NULL, NULL);
6124 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6126 header = g_string_sized_new(64);
6128 /* Date */
6129 get_rfc822_date(buf, sizeof(buf));
6130 g_string_append_printf(header, "Date: %s\n", buf);
6132 /* From */
6134 if (compose->account->name && *compose->account->name) {
6135 gchar *buf;
6136 QUOTE_IF_REQUIRED(buf, compose->account->name);
6137 tmp = g_strdup_printf("%s <%s>",
6138 buf, compose->account->address);
6139 } else {
6140 tmp = g_strdup_printf("%s",
6141 compose->account->address);
6143 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6144 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6145 /* use default */
6146 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6147 from_address = g_strdup(compose->account->address);
6148 } else {
6149 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6150 /* extract name and address */
6151 if (strstr(spec, " <") && strstr(spec, ">")) {
6152 from_address = g_strdup(strrchr(spec, '<')+1);
6153 *(strrchr(from_address, '>')) = '\0';
6154 from_name = g_strdup(spec);
6155 *(strrchr(from_name, '<')) = '\0';
6156 } else {
6157 from_name = NULL;
6158 from_address = g_strdup(spec);
6160 g_free(spec);
6162 g_free(tmp);
6165 if (from_name && *from_name) {
6166 compose_convert_header
6167 (compose, buf, sizeof(buf), from_name,
6168 strlen("From: "), TRUE);
6169 QUOTE_IF_REQUIRED(name, buf);
6171 g_string_append_printf(header, "From: %s <%s>\n",
6172 name, from_address);
6173 } else
6174 g_string_append_printf(header, "From: %s\n", from_address);
6176 g_free(from_name);
6177 g_free(from_address);
6179 /* To */
6180 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6182 /* Newsgroups */
6183 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6185 /* Cc */
6186 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6188 /* Bcc */
6190 * If this account is a NNTP account remove Bcc header from
6191 * message body since it otherwise will be publicly shown
6193 if (compose->account->protocol != A_NNTP)
6194 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6196 /* Subject */
6197 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6199 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6200 g_strstrip(str);
6201 if (*str != '\0') {
6202 compose_convert_header(compose, buf, sizeof(buf), str,
6203 strlen("Subject: "), FALSE);
6204 g_string_append_printf(header, "Subject: %s\n", buf);
6207 g_free(str);
6209 /* Message-ID */
6210 if (compose->account->set_domain && compose->account->domain) {
6211 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6212 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6213 g_snprintf(buf, sizeof(buf), "%s",
6214 strchr(compose->account->address, '@') ?
6215 strchr(compose->account->address, '@')+1 :
6216 compose->account->address);
6217 } else {
6218 g_snprintf(buf, sizeof(buf), "%s", "");
6221 if (compose->account->gen_msgid) {
6222 gchar *addr = NULL;
6223 if (compose->account->msgid_with_addr) {
6224 addr = compose->account->address;
6226 generate_msgid(buf, sizeof(buf), addr);
6227 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6228 compose->msgid = g_strdup(buf);
6229 } else {
6230 compose->msgid = NULL;
6233 if (compose->remove_references == FALSE) {
6234 /* In-Reply-To */
6235 if (compose->inreplyto && compose->to_list)
6236 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6238 /* References */
6239 if (compose->references)
6240 g_string_append_printf(header, "References: %s\n", compose->references);
6243 /* Followup-To */
6244 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6246 /* Reply-To */
6247 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6249 /* Organization */
6250 if (compose->account->organization &&
6251 strlen(compose->account->organization) &&
6252 !IS_IN_CUSTOM_HEADER("Organization")) {
6253 compose_convert_header(compose, buf, sizeof(buf),
6254 compose->account->organization,
6255 strlen("Organization: "), FALSE);
6256 g_string_append_printf(header, "Organization: %s\n", buf);
6259 /* Program version and system info */
6260 if (compose->account->gen_xmailer &&
6261 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6262 !compose->newsgroup_list) {
6263 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6264 prog_version,
6265 gtk_major_version, gtk_minor_version, gtk_micro_version,
6266 TARGET_ALIAS);
6268 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6269 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6270 prog_version,
6271 gtk_major_version, gtk_minor_version, gtk_micro_version,
6272 TARGET_ALIAS);
6275 /* custom headers */
6276 if (compose->account->add_customhdr) {
6277 GSList *cur;
6279 for (cur = compose->account->customhdr_list; cur != NULL;
6280 cur = cur->next) {
6281 CustomHeader *chdr = (CustomHeader *)cur->data;
6283 if (custom_header_is_allowed(chdr->name)
6284 && chdr->value != NULL
6285 && *(chdr->value) != '\0') {
6286 compose_convert_header
6287 (compose, buf, sizeof(buf),
6288 chdr->value,
6289 strlen(chdr->name) + 2, FALSE);
6290 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6295 /* Automatic Faces and X-Faces */
6296 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6297 g_string_append_printf(header, "X-Face: %s\n", buf);
6299 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6300 g_string_append_printf(header, "X-Face: %s\n", buf);
6302 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6303 g_string_append_printf(header, "Face: %s\n", buf);
6305 else if (get_default_face (buf, sizeof(buf)) == 0) {
6306 g_string_append_printf(header, "Face: %s\n", buf);
6309 /* PRIORITY */
6310 switch (compose->priority) {
6311 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6312 "X-Priority: 1 (Highest)\n");
6313 break;
6314 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6315 "X-Priority: 2 (High)\n");
6316 break;
6317 case PRIORITY_NORMAL: break;
6318 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6319 "X-Priority: 4 (Low)\n");
6320 break;
6321 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6322 "X-Priority: 5 (Lowest)\n");
6323 break;
6324 default: debug_print("compose: priority unknown : %d\n",
6325 compose->priority);
6328 /* Request Return Receipt */
6329 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6330 if (compose->return_receipt) {
6331 if (compose->account->name
6332 && *compose->account->name) {
6333 compose_convert_header(compose, buf, sizeof(buf),
6334 compose->account->name,
6335 strlen("Disposition-Notification-To: "),
6336 TRUE);
6337 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6338 } else
6339 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6343 /* get special headers */
6344 for (list = compose->header_list; list; list = list->next) {
6345 ComposeHeaderEntry *headerentry;
6346 gchar *tmp;
6347 gchar *headername;
6348 gchar *headername_wcolon;
6349 const gchar *headername_trans;
6350 gchar *headervalue;
6351 gchar **string;
6352 gboolean standard_header = FALSE;
6354 headerentry = ((ComposeHeaderEntry *)list->data);
6356 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6357 g_strstrip(tmp);
6358 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6359 g_free(tmp);
6360 continue;
6363 if (!strstr(tmp, ":")) {
6364 headername_wcolon = g_strconcat(tmp, ":", NULL);
6365 headername = g_strdup(tmp);
6366 } else {
6367 headername_wcolon = g_strdup(tmp);
6368 headername = g_strdup(strtok(tmp, ":"));
6370 g_free(tmp);
6372 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6373 Xstrdup_a(headervalue, entry_str, return NULL);
6374 subst_char(headervalue, '\r', ' ');
6375 subst_char(headervalue, '\n', ' ');
6376 string = std_headers;
6377 while (*string != NULL) {
6378 headername_trans = prefs_common_translated_header_name(*string);
6379 if (!strcmp(headername_trans, headername_wcolon))
6380 standard_header = TRUE;
6381 string++;
6383 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6384 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6386 g_free(headername);
6387 g_free(headername_wcolon);
6390 str = header->str;
6391 g_string_free(header, FALSE);
6393 return str;
6396 #undef IS_IN_CUSTOM_HEADER
6398 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6399 gint header_len, gboolean addr_field)
6401 gchar *tmpstr = NULL;
6402 const gchar *out_codeset = NULL;
6404 cm_return_if_fail(src != NULL);
6405 cm_return_if_fail(dest != NULL);
6407 if (len < 1) return;
6409 tmpstr = g_strdup(src);
6411 subst_char(tmpstr, '\n', ' ');
6412 subst_char(tmpstr, '\r', ' ');
6413 g_strchomp(tmpstr);
6415 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6416 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6417 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6418 g_free(tmpstr);
6419 tmpstr = mybuf;
6422 codeconv_set_strict(TRUE);
6423 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6424 conv_get_charset_str(compose->out_encoding));
6425 codeconv_set_strict(FALSE);
6427 if (!dest || *dest == '\0') {
6428 gchar *test_conv_global_out = NULL;
6429 gchar *test_conv_reply = NULL;
6431 /* automatic mode. be automatic. */
6432 codeconv_set_strict(TRUE);
6434 out_codeset = conv_get_outgoing_charset_str();
6435 if (out_codeset) {
6436 debug_print("trying to convert to %s\n", out_codeset);
6437 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6440 if (!test_conv_global_out && compose->orig_charset
6441 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6442 out_codeset = compose->orig_charset;
6443 debug_print("failure; trying to convert to %s\n", out_codeset);
6444 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6447 if (!test_conv_global_out && !test_conv_reply) {
6448 /* we're lost */
6449 out_codeset = CS_INTERNAL;
6450 debug_print("finally using %s\n", out_codeset);
6452 g_free(test_conv_global_out);
6453 g_free(test_conv_reply);
6454 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6455 out_codeset);
6456 codeconv_set_strict(FALSE);
6458 g_free(tmpstr);
6461 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6463 gchar *address;
6465 cm_return_if_fail(user_data != NULL);
6467 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6468 g_strstrip(address);
6469 if (*address != '\0') {
6470 gchar *name = procheader_get_fromname(address);
6471 extract_address(address);
6472 addressbook_add_contact(name, address, NULL, NULL);
6474 g_free(address);
6477 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6479 GtkWidget *menuitem;
6480 gchar *address;
6482 cm_return_if_fail(menu != NULL);
6483 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6485 menuitem = gtk_separator_menu_item_new();
6486 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6487 gtk_widget_show(menuitem);
6489 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6490 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6492 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6493 g_strstrip(address);
6494 if (*address == '\0') {
6495 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6498 g_signal_connect(G_OBJECT(menuitem), "activate",
6499 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6500 gtk_widget_show(menuitem);
6503 static void compose_create_header_entry(Compose *compose)
6505 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6507 GtkWidget *combo;
6508 GtkWidget *entry;
6509 GtkWidget *button;
6510 GtkWidget *hbox;
6511 gchar **string;
6512 const gchar *header = NULL;
6513 ComposeHeaderEntry *headerentry;
6514 gboolean standard_header = FALSE;
6515 GtkListStore *model;
6516 GtkTreeIter iter;
6517 #if !(GTK_CHECK_VERSION(2,12,0))
6518 GtkTooltips *tips = compose->tooltips;
6519 #endif
6521 headerentry = g_new0(ComposeHeaderEntry, 1);
6523 /* Combo box */
6524 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6525 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6526 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6527 COMPOSE_TO);
6528 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6529 COMPOSE_CC);
6530 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6531 COMPOSE_BCC);
6532 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6533 COMPOSE_NEWSGROUPS);
6534 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6535 COMPOSE_REPLYTO);
6536 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6537 COMPOSE_FOLLOWUPTO);
6539 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6540 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6541 G_CALLBACK(compose_grab_focus_cb), compose);
6542 gtk_widget_show(combo);
6543 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6544 compose->header_nextrow, compose->header_nextrow+1,
6545 GTK_SHRINK, GTK_FILL, 0, 0);
6546 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6547 const gchar *last_header_entry = gtk_entry_get_text(
6548 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6549 string = headers;
6550 while (*string != NULL) {
6551 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6552 standard_header = TRUE;
6553 string++;
6555 if (standard_header)
6556 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6558 if (!compose->header_last || !standard_header) {
6559 switch(compose->account->protocol) {
6560 case A_NNTP:
6561 header = prefs_common_translated_header_name("Newsgroups:");
6562 break;
6563 default:
6564 header = prefs_common_translated_header_name("To:");
6565 break;
6568 if (header)
6569 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6571 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6572 G_CALLBACK(compose_grab_focus_cb), compose);
6574 /* Entry field with cleanup button */
6575 button = gtk_button_new();
6576 gtk_button_set_image(GTK_BUTTON(button),
6577 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6578 gtk_widget_show(button);
6579 CLAWS_SET_TIP(button,
6580 _("Delete entry contents"));
6581 entry = gtk_entry_new();
6582 gtk_widget_show(entry);
6583 CLAWS_SET_TIP(entry,
6584 _("Use <tab> to autocomplete from addressbook"));
6585 hbox = gtk_hbox_new (FALSE, 0);
6586 gtk_widget_show(hbox);
6587 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6588 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6589 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6590 compose->header_nextrow, compose->header_nextrow+1,
6591 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6593 g_signal_connect(G_OBJECT(entry), "key-press-event",
6594 G_CALLBACK(compose_headerentry_key_press_event_cb),
6595 headerentry);
6596 g_signal_connect(G_OBJECT(entry), "changed",
6597 G_CALLBACK(compose_headerentry_changed_cb),
6598 headerentry);
6599 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6600 G_CALLBACK(compose_grab_focus_cb), compose);
6602 g_signal_connect(G_OBJECT(button), "clicked",
6603 G_CALLBACK(compose_headerentry_button_clicked_cb),
6604 headerentry);
6606 /* email dnd */
6607 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6608 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6609 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6610 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6611 G_CALLBACK(compose_header_drag_received_cb),
6612 entry);
6613 g_signal_connect(G_OBJECT(entry), "drag-drop",
6614 G_CALLBACK(compose_drag_drop),
6615 compose);
6616 g_signal_connect(G_OBJECT(entry), "populate-popup",
6617 G_CALLBACK(compose_entry_popup_extend),
6618 NULL);
6620 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6622 headerentry->compose = compose;
6623 headerentry->combo = combo;
6624 headerentry->entry = entry;
6625 headerentry->button = button;
6626 headerentry->hbox = hbox;
6627 headerentry->headernum = compose->header_nextrow;
6628 headerentry->type = PREF_NONE;
6630 compose->header_nextrow++;
6631 compose->header_last = headerentry;
6632 compose->header_list =
6633 g_slist_append(compose->header_list,
6634 headerentry);
6637 static void compose_add_header_entry(Compose *compose, const gchar *header,
6638 gchar *text, ComposePrefType pref_type)
6640 ComposeHeaderEntry *last_header = compose->header_last;
6641 gchar *tmp = g_strdup(text), *email;
6642 gboolean replyto_hdr;
6644 replyto_hdr = (!strcasecmp(header,
6645 prefs_common_translated_header_name("Reply-To:")) ||
6646 !strcasecmp(header,
6647 prefs_common_translated_header_name("Followup-To:")) ||
6648 !strcasecmp(header,
6649 prefs_common_translated_header_name("In-Reply-To:")));
6651 extract_address(tmp);
6652 email = g_utf8_strdown(tmp, -1);
6654 if (replyto_hdr == FALSE &&
6655 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6657 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6658 header, text, (gint) pref_type);
6659 g_free(email);
6660 g_free(tmp);
6661 return;
6664 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6665 gtk_entry_set_text(GTK_ENTRY(
6666 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6667 else
6668 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6669 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6670 last_header->type = pref_type;
6672 if (replyto_hdr == FALSE)
6673 g_hash_table_insert(compose->email_hashtable, email,
6674 GUINT_TO_POINTER(1));
6675 else
6676 g_free(email);
6678 g_free(tmp);
6681 static void compose_destroy_headerentry(Compose *compose,
6682 ComposeHeaderEntry *headerentry)
6684 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6685 gchar *email;
6687 extract_address(text);
6688 email = g_utf8_strdown(text, -1);
6689 g_hash_table_remove(compose->email_hashtable, email);
6690 g_free(text);
6691 g_free(email);
6693 gtk_widget_destroy(headerentry->combo);
6694 gtk_widget_destroy(headerentry->entry);
6695 gtk_widget_destroy(headerentry->button);
6696 gtk_widget_destroy(headerentry->hbox);
6697 g_free(headerentry);
6700 static void compose_remove_header_entries(Compose *compose)
6702 GSList *list;
6703 for (list = compose->header_list; list; list = list->next)
6704 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6706 compose->header_last = NULL;
6707 g_slist_free(compose->header_list);
6708 compose->header_list = NULL;
6709 compose->header_nextrow = 1;
6710 compose_create_header_entry(compose);
6713 static GtkWidget *compose_create_header(Compose *compose)
6715 GtkWidget *from_optmenu_hbox;
6716 GtkWidget *header_scrolledwin;
6717 GtkWidget *header_table;
6719 gint count = 0;
6721 /* header labels and entries */
6722 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6723 gtk_widget_show(header_scrolledwin);
6724 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6726 header_table = gtk_table_new(2, 2, FALSE);
6727 gtk_widget_show(header_table);
6728 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6729 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6730 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6731 count = 0;
6733 /* option menu for selecting accounts */
6734 from_optmenu_hbox = compose_account_option_menu_create(compose);
6735 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6736 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6737 count++;
6739 compose->header_table = header_table;
6740 compose->header_list = NULL;
6741 compose->header_nextrow = count;
6743 compose_create_header_entry(compose);
6745 compose->table = NULL;
6747 return header_scrolledwin ;
6750 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6752 Compose *compose = (Compose *)data;
6753 GdkEventButton event;
6755 event.button = 3;
6756 event.time = gtk_get_current_event_time();
6758 return attach_button_pressed(compose->attach_clist, &event, compose);
6761 static GtkWidget *compose_create_attach(Compose *compose)
6763 GtkWidget *attach_scrwin;
6764 GtkWidget *attach_clist;
6766 GtkListStore *store;
6767 GtkCellRenderer *renderer;
6768 GtkTreeViewColumn *column;
6769 GtkTreeSelection *selection;
6771 /* attachment list */
6772 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6773 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6774 GTK_POLICY_AUTOMATIC,
6775 GTK_POLICY_AUTOMATIC);
6776 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6778 store = gtk_list_store_new(N_ATTACH_COLS,
6779 G_TYPE_STRING,
6780 G_TYPE_STRING,
6781 G_TYPE_STRING,
6782 G_TYPE_STRING,
6783 G_TYPE_POINTER,
6784 G_TYPE_AUTO_POINTER,
6785 -1);
6786 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6787 (GTK_TREE_MODEL(store)));
6788 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6789 g_object_unref(store);
6791 renderer = gtk_cell_renderer_text_new();
6792 column = gtk_tree_view_column_new_with_attributes
6793 (_("Mime type"), renderer, "text",
6794 COL_MIMETYPE, NULL);
6795 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6797 renderer = gtk_cell_renderer_text_new();
6798 column = gtk_tree_view_column_new_with_attributes
6799 (_("Size"), renderer, "text",
6800 COL_SIZE, NULL);
6801 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6803 renderer = gtk_cell_renderer_text_new();
6804 column = gtk_tree_view_column_new_with_attributes
6805 (_("Name"), renderer, "text",
6806 COL_NAME, NULL);
6807 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6809 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6810 prefs_common.use_stripes_everywhere);
6811 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6812 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6814 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6815 G_CALLBACK(attach_selected), compose);
6816 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6817 G_CALLBACK(attach_button_pressed), compose);
6818 #ifndef MAEMO
6819 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6820 G_CALLBACK(popup_attach_button_pressed), compose);
6821 #else
6822 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6823 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6824 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6825 G_CALLBACK(popup_attach_button_pressed), compose);
6826 #endif
6827 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6828 G_CALLBACK(attach_key_pressed), compose);
6830 /* drag and drop */
6831 gtk_drag_dest_set(attach_clist,
6832 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6833 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6834 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6835 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6836 G_CALLBACK(compose_attach_drag_received_cb),
6837 compose);
6838 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6839 G_CALLBACK(compose_drag_drop),
6840 compose);
6842 compose->attach_scrwin = attach_scrwin;
6843 compose->attach_clist = attach_clist;
6845 return attach_scrwin;
6848 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6849 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6851 static GtkWidget *compose_create_others(Compose *compose)
6853 GtkWidget *table;
6854 GtkWidget *savemsg_checkbtn;
6855 GtkWidget *savemsg_combo;
6856 GtkWidget *savemsg_select;
6858 guint rowcount = 0;
6859 gchar *folderidentifier;
6861 /* Table for settings */
6862 table = gtk_table_new(3, 1, FALSE);
6863 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6864 gtk_widget_show(table);
6865 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6866 rowcount = 0;
6868 /* Save Message to folder */
6869 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6870 gtk_widget_show(savemsg_checkbtn);
6871 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6872 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6873 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6875 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6876 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6878 savemsg_combo = gtk_combo_box_entry_new_text();
6879 compose->savemsg_checkbtn = savemsg_checkbtn;
6880 compose->savemsg_combo = savemsg_combo;
6881 gtk_widget_show(savemsg_combo);
6883 if (prefs_common.compose_save_to_history)
6884 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
6885 prefs_common.compose_save_to_history);
6887 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
6888 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
6889 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
6890 G_CALLBACK(compose_grab_focus_cb), compose);
6891 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6892 folderidentifier = folder_item_get_identifier(account_get_special_folder
6893 (compose->account, F_OUTBOX));
6894 compose_set_save_to(compose, folderidentifier);
6895 g_free(folderidentifier);
6898 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6899 gtk_widget_show(savemsg_select);
6900 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6901 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6902 G_CALLBACK(compose_savemsg_select_cb),
6903 compose);
6905 rowcount++;
6907 return table;
6910 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6912 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
6913 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6916 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6918 FolderItem *dest;
6919 gchar * path;
6921 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
6922 if (!dest) return;
6924 path = folder_item_get_identifier(dest);
6926 compose_set_save_to(compose, path);
6927 g_free(path);
6930 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6931 GdkAtom clip, GtkTextIter *insert_place);
6934 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6935 Compose *compose)
6937 gint prev_autowrap;
6938 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6939 #if USE_ENCHANT
6940 if (event->button == 3) {
6941 GtkTextIter iter;
6942 GtkTextIter sel_start, sel_end;
6943 gboolean stuff_selected;
6944 gint x, y;
6945 /* move the cursor to allow GtkAspell to check the word
6946 * under the mouse */
6947 if (event->x && event->y) {
6948 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6949 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6950 &x, &y);
6951 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6952 &iter, x, y);
6953 } else {
6954 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6955 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6957 /* get selection */
6958 stuff_selected = gtk_text_buffer_get_selection_bounds(
6959 buffer,
6960 &sel_start, &sel_end);
6962 gtk_text_buffer_place_cursor (buffer, &iter);
6963 /* reselect stuff */
6964 if (stuff_selected
6965 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6966 gtk_text_buffer_select_range(buffer,
6967 &sel_start, &sel_end);
6969 return FALSE; /* pass the event so that the right-click goes through */
6971 #endif
6972 if (event->button == 2) {
6973 GtkTextIter iter;
6974 gint x, y;
6975 BLOCK_WRAP();
6977 /* get the middle-click position to paste at the correct place */
6978 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6979 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6980 &x, &y);
6981 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6982 &iter, x, y);
6984 entry_paste_clipboard(compose, text,
6985 prefs_common.linewrap_pastes,
6986 GDK_SELECTION_PRIMARY, &iter);
6987 UNBLOCK_WRAP();
6988 return TRUE;
6990 return FALSE;
6993 #if USE_ENCHANT
6994 static void compose_spell_menu_changed(void *data)
6996 Compose *compose = (Compose *)data;
6997 GSList *items;
6998 GtkWidget *menuitem;
6999 GtkWidget *parent_item;
7000 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7001 GSList *spell_menu;
7003 if (compose->gtkaspell == NULL)
7004 return;
7006 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7007 "/Menu/Spelling/Options");
7009 /* setting the submenu removes /Spelling/Options from the factory
7010 * so we need to save it */
7012 if (parent_item == NULL) {
7013 parent_item = compose->aspell_options_menu;
7014 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7015 } else
7016 compose->aspell_options_menu = parent_item;
7018 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7020 spell_menu = g_slist_reverse(spell_menu);
7021 for (items = spell_menu;
7022 items; items = items->next) {
7023 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7024 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7025 gtk_widget_show(GTK_WIDGET(menuitem));
7027 g_slist_free(spell_menu);
7029 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7030 gtk_widget_show(parent_item);
7033 static void compose_dict_changed(void *data)
7035 Compose *compose = (Compose *) data;
7037 if(compose->gtkaspell &&
7038 compose->gtkaspell->recheck_when_changing_dict == FALSE)
7039 return;
7041 gtkaspell_highlight_all(compose->gtkaspell);
7042 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7044 #endif
7046 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7048 Compose *compose = (Compose *)data;
7049 GdkEventButton event;
7051 event.button = 3;
7052 event.time = gtk_get_current_event_time();
7053 event.x = 0;
7054 event.y = 0;
7056 return text_clicked(compose->text, &event, compose);
7059 static gboolean compose_force_window_origin = TRUE;
7060 static Compose *compose_create(PrefsAccount *account,
7061 FolderItem *folder,
7062 ComposeMode mode,
7063 gboolean batch)
7065 Compose *compose;
7066 GtkWidget *window;
7067 GtkWidget *vbox;
7068 GtkWidget *menubar;
7069 GtkWidget *handlebox;
7071 GtkWidget *notebook;
7073 GtkWidget *attach_hbox;
7074 GtkWidget *attach_lab1;
7075 GtkWidget *attach_lab2;
7077 GtkWidget *vbox2;
7079 GtkWidget *label;
7080 GtkWidget *subject_hbox;
7081 GtkWidget *subject_frame;
7082 GtkWidget *subject_entry;
7083 GtkWidget *subject;
7084 GtkWidget *paned;
7086 GtkWidget *edit_vbox;
7087 #if !GTK_CHECK_VERSION(2, 24, 0)
7088 GtkWidget *ruler_hbox;
7089 GtkWidget *ruler;
7090 #endif
7091 GtkWidget *scrolledwin;
7092 GtkWidget *text;
7093 GtkTextBuffer *buffer;
7094 GtkClipboard *clipboard;
7095 CLAWS_TIP_DECL();
7097 UndoMain *undostruct;
7099 gchar *titles[N_ATTACH_COLS];
7100 GtkWidget *popupmenu;
7101 GtkWidget *tmpl_menu;
7102 GtkActionGroup *action_group = NULL;
7104 #if USE_ENCHANT
7105 GtkAspell * gtkaspell = NULL;
7106 #endif
7108 static GdkGeometry geometry;
7110 cm_return_val_if_fail(account != NULL, NULL);
7112 debug_print("Creating compose window...\n");
7113 compose = g_new0(Compose, 1);
7115 titles[COL_MIMETYPE] = _("MIME type");
7116 titles[COL_SIZE] = _("Size");
7117 titles[COL_NAME] = _("Name");
7118 titles[COL_CHARSET] = _("Charset");
7120 compose->batch = batch;
7121 compose->account = account;
7122 compose->folder = folder;
7124 compose->mutex = g_mutex_new();
7125 compose->set_cursor_pos = -1;
7127 #if !(GTK_CHECK_VERSION(2,12,0))
7128 compose->tooltips = tips;
7129 #endif
7131 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7133 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7134 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
7136 if (!geometry.max_width) {
7137 geometry.max_width = gdk_screen_width();
7138 geometry.max_height = gdk_screen_height();
7141 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7142 &geometry, GDK_HINT_MAX_SIZE);
7143 if (!geometry.min_width) {
7144 geometry.min_width = 600;
7145 geometry.min_height = 440;
7147 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7148 &geometry, GDK_HINT_MIN_SIZE);
7150 #ifndef GENERIC_UMPC
7151 if (compose_force_window_origin)
7152 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7153 prefs_common.compose_y);
7154 #endif
7155 g_signal_connect(G_OBJECT(window), "delete_event",
7156 G_CALLBACK(compose_delete_cb), compose);
7157 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7158 gtk_widget_realize(window);
7160 gtkut_widget_set_composer_icon(window);
7162 vbox = gtk_vbox_new(FALSE, 0);
7163 gtk_container_add(GTK_CONTAINER(window), vbox);
7165 compose->ui_manager = gtk_ui_manager_new();
7166 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7167 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7168 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7169 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7170 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7171 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7172 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7173 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7174 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7175 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7177 #ifndef MAEMO
7178 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7179 #else
7180 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_POPUP)
7181 #endif
7183 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7184 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7185 #ifdef USE_ENCHANT
7186 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7187 #endif
7188 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7189 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7190 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7192 /* Compose menu */
7193 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7194 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7195 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7196 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7197 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7198 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7199 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7200 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7201 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7202 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7204 /* Edit menu */
7205 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7206 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7207 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7209 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7210 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7211 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7213 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7214 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7215 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7216 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7218 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7220 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7221 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7222 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7223 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7224 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7225 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7226 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7227 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7228 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7229 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7230 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7231 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7232 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7233 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7234 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7236 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7238 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7239 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7240 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7241 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7242 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7244 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7246 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7248 #if USE_ENCHANT
7249 /* Spelling menu */
7250 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7251 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7252 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7253 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7254 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7255 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7256 #endif
7258 /* Options menu */
7259 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7260 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7261 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7262 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7263 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7265 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7266 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7267 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7268 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7269 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7272 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7273 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7274 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7275 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7276 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7277 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7278 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7280 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7281 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7282 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7283 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7284 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7286 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7288 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7289 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7290 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7291 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7292 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7294 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7295 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)
7296 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)
7297 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7299 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7301 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7302 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)
7303 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)
7305 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7307 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7308 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)
7309 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7311 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7312 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)
7313 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7315 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7317 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7318 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)
7319 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7320 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7321 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7323 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7324 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)
7325 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)
7326 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7327 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7329 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7330 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7331 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7332 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7333 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7334 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7336 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7337 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7338 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)
7340 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7341 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7342 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7343 /* phew. */
7345 /* Tools menu */
7346 #if !GTK_CHECK_VERSION(2, 24, 0)
7347 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7348 #endif
7349 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7350 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7351 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7352 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7353 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7355 /* Help menu */
7356 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7358 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7359 gtk_widget_show_all(menubar);
7361 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7362 #ifndef MAEMO
7363 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7364 #else
7365 hildon_window_set_menu(HILDON_WINDOW(window), GTK_MENU(menubar));
7366 #endif
7368 if (prefs_common.toolbar_detachable) {
7369 handlebox = gtk_handle_box_new();
7370 } else {
7371 handlebox = gtk_hbox_new(FALSE, 0);
7373 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7375 gtk_widget_realize(handlebox);
7376 #ifdef MAEMO
7377 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
7378 (gpointer)compose);
7379 #else
7380 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7381 (gpointer)compose);
7382 #endif
7384 vbox2 = gtk_vbox_new(FALSE, 2);
7385 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7386 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7388 /* Notebook */
7389 notebook = gtk_notebook_new();
7390 gtk_widget_set_size_request(notebook, -1, 130);
7391 gtk_widget_show(notebook);
7393 /* header labels and entries */
7394 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7395 compose_create_header(compose),
7396 gtk_label_new_with_mnemonic(_("Hea_der")));
7397 /* attachment list */
7398 attach_hbox = gtk_hbox_new(FALSE, 0);
7399 gtk_widget_show(attach_hbox);
7401 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7402 gtk_widget_show(attach_lab1);
7403 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7405 attach_lab2 = gtk_label_new("");
7406 gtk_widget_show(attach_lab2);
7407 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7409 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7410 compose_create_attach(compose),
7411 attach_hbox);
7412 /* Others Tab */
7413 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7414 compose_create_others(compose),
7415 gtk_label_new_with_mnemonic(_("Othe_rs")));
7417 /* Subject */
7418 subject_hbox = gtk_hbox_new(FALSE, 0);
7419 gtk_widget_show(subject_hbox);
7421 subject_frame = gtk_frame_new(NULL);
7422 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7423 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7424 gtk_widget_show(subject_frame);
7426 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7427 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7428 gtk_widget_show(subject);
7430 label = gtk_label_new(_("Subject:"));
7431 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7432 gtk_widget_show(label);
7434 #ifdef USE_ENCHANT
7435 subject_entry = claws_spell_entry_new();
7436 #else
7437 subject_entry = gtk_entry_new();
7438 #endif
7439 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7440 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7441 G_CALLBACK(compose_grab_focus_cb), compose);
7442 gtk_widget_show(subject_entry);
7443 compose->subject_entry = subject_entry;
7444 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7446 edit_vbox = gtk_vbox_new(FALSE, 0);
7448 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7450 #if !GTK_CHECK_VERSION(2, 24, 0)
7451 /* ruler */
7452 ruler_hbox = gtk_hbox_new(FALSE, 0);
7453 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7455 ruler = gtk_shruler_new();
7456 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
7457 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7458 BORDER_WIDTH);
7459 #endif
7461 /* text widget */
7462 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7463 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7464 GTK_POLICY_AUTOMATIC,
7465 GTK_POLICY_AUTOMATIC);
7466 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7467 GTK_SHADOW_IN);
7468 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7469 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
7471 text = gtk_text_view_new();
7472 if (prefs_common.show_compose_margin) {
7473 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7474 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7476 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7477 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7478 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7479 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7480 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7482 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7483 #if !GTK_CHECK_VERSION(2, 24, 0)
7484 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7485 G_CALLBACK(compose_edit_size_alloc),
7486 ruler);
7487 #endif
7488 g_signal_connect(G_OBJECT(buffer), "changed",
7489 G_CALLBACK(compose_changed_cb), compose);
7490 g_signal_connect(G_OBJECT(text), "grab_focus",
7491 G_CALLBACK(compose_grab_focus_cb), compose);
7492 g_signal_connect(G_OBJECT(buffer), "insert_text",
7493 G_CALLBACK(text_inserted), compose);
7494 g_signal_connect(G_OBJECT(text), "button_press_event",
7495 G_CALLBACK(text_clicked), compose);
7496 #ifndef MAEMO
7497 g_signal_connect(G_OBJECT(text), "popup-menu",
7498 G_CALLBACK(compose_popup_menu), compose);
7499 #else
7500 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
7501 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7502 g_signal_connect(G_OBJECT(text), "tap-and-hold",
7503 G_CALLBACK(compose_popup_menu), compose);
7504 #endif
7505 g_signal_connect(G_OBJECT(subject_entry), "changed",
7506 G_CALLBACK(compose_changed_cb), compose);
7508 /* drag and drop */
7509 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7510 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7511 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7512 g_signal_connect(G_OBJECT(text), "drag_data_received",
7513 G_CALLBACK(compose_insert_drag_received_cb),
7514 compose);
7515 g_signal_connect(G_OBJECT(text), "drag-drop",
7516 G_CALLBACK(compose_drag_drop),
7517 compose);
7518 gtk_widget_show_all(vbox);
7520 /* pane between attach clist and text */
7521 paned = gtk_vpaned_new();
7522 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7523 #ifdef MAEMO
7524 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
7525 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
7526 else
7527 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
7528 #endif
7529 gtk_paned_add1(GTK_PANED(paned), notebook);
7530 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7531 gtk_widget_show_all(paned);
7534 if (prefs_common.textfont) {
7535 PangoFontDescription *font_desc;
7537 font_desc = pango_font_description_from_string
7538 (prefs_common.textfont);
7539 if (font_desc) {
7540 gtk_widget_modify_font(text, font_desc);
7541 pango_font_description_free(font_desc);
7545 gtk_action_group_add_actions(action_group, compose_popup_entries,
7546 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7547 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7548 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7549 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7550 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7551 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7552 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7554 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7556 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7557 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7558 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7560 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7562 undostruct = undo_init(text);
7563 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7564 compose);
7566 address_completion_start(window);
7568 compose->window = window;
7569 compose->vbox = vbox;
7570 compose->menubar = menubar;
7571 compose->handlebox = handlebox;
7573 compose->vbox2 = vbox2;
7575 compose->paned = paned;
7577 compose->attach_label = attach_lab2;
7579 compose->notebook = notebook;
7580 compose->edit_vbox = edit_vbox;
7581 #if !GTK_CHECK_VERSION(2, 24, 0)
7582 compose->ruler_hbox = ruler_hbox;
7583 compose->ruler = ruler;
7584 #endif
7585 compose->scrolledwin = scrolledwin;
7586 compose->text = text;
7588 compose->focused_editable = NULL;
7590 compose->popupmenu = popupmenu;
7592 compose->tmpl_menu = tmpl_menu;
7594 compose->mode = mode;
7595 compose->rmode = mode;
7597 compose->targetinfo = NULL;
7598 compose->replyinfo = NULL;
7599 compose->fwdinfo = NULL;
7601 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7602 g_str_equal, (GDestroyNotify) g_free, NULL);
7604 compose->replyto = NULL;
7605 compose->cc = NULL;
7606 compose->bcc = NULL;
7607 compose->followup_to = NULL;
7609 compose->ml_post = NULL;
7611 compose->inreplyto = NULL;
7612 compose->references = NULL;
7613 compose->msgid = NULL;
7614 compose->boundary = NULL;
7616 compose->autowrap = prefs_common.autowrap;
7617 compose->autoindent = prefs_common.auto_indent;
7618 compose->use_signing = FALSE;
7619 compose->use_encryption = FALSE;
7620 compose->privacy_system = NULL;
7622 compose->modified = FALSE;
7624 compose->return_receipt = FALSE;
7626 compose->to_list = NULL;
7627 compose->newsgroup_list = NULL;
7629 compose->undostruct = undostruct;
7631 compose->sig_str = NULL;
7633 compose->exteditor_file = NULL;
7634 compose->exteditor_pid = -1;
7635 compose->exteditor_tag = -1;
7636 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7638 #if USE_ENCHANT
7639 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7640 if (mode != COMPOSE_REDIRECT) {
7641 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7642 strcmp(prefs_common.dictionary, "")) {
7643 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7644 prefs_common.alt_dictionary,
7645 conv_get_locale_charset_str(),
7646 prefs_common.misspelled_col,
7647 prefs_common.check_while_typing,
7648 prefs_common.recheck_when_changing_dict,
7649 prefs_common.use_alternate,
7650 prefs_common.use_both_dicts,
7651 GTK_TEXT_VIEW(text),
7652 GTK_WINDOW(compose->window),
7653 compose_dict_changed,
7654 compose_spell_menu_changed,
7655 compose);
7656 if (!gtkaspell) {
7657 alertpanel_error(_("Spell checker could not "
7658 "be started.\n%s"),
7659 gtkaspell_checkers_strerror());
7660 gtkaspell_checkers_reset_error();
7661 } else {
7662 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7666 compose->gtkaspell = gtkaspell;
7667 compose_spell_menu_changed(compose);
7668 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7669 #endif
7671 compose_select_account(compose, account, TRUE);
7673 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7674 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7676 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7677 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7679 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7680 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7682 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7683 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7685 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7686 if (account->protocol != A_NNTP)
7687 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7688 prefs_common_translated_header_name("To:"));
7689 else
7690 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7691 prefs_common_translated_header_name("Newsgroups:"));
7693 addressbook_set_target_compose(compose);
7695 if (mode != COMPOSE_REDIRECT)
7696 compose_set_template_menu(compose);
7697 else {
7698 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7701 compose_list = g_list_append(compose_list, compose);
7703 #if !GTK_CHECK_VERSION(2, 24, 0)
7704 if (!prefs_common.show_ruler)
7705 gtk_widget_hide(ruler_hbox);
7707 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7708 #endif
7710 /* Priority */
7711 compose->priority = PRIORITY_NORMAL;
7712 compose_update_priority_menu_item(compose);
7714 compose_set_out_encoding(compose);
7716 /* Actions menu */
7717 compose_update_actions_menu(compose);
7719 /* Privacy Systems menu */
7720 compose_update_privacy_systems_menu(compose);
7722 activate_privacy_system(compose, account, TRUE);
7723 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7724 if (batch) {
7725 gtk_widget_realize(window);
7726 } else {
7727 gtk_widget_show(window);
7728 #ifdef MAEMO
7729 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7730 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7731 #endif
7734 return compose;
7737 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7739 GList *accounts;
7740 GtkWidget *hbox;
7741 GtkWidget *optmenu;
7742 GtkWidget *optmenubox;
7743 GtkListStore *menu;
7744 GtkTreeIter iter;
7745 GtkWidget *from_name = NULL;
7746 #if !(GTK_CHECK_VERSION(2,12,0))
7747 GtkTooltips *tips = compose->tooltips;
7748 #endif
7750 gint num = 0, def_menu = 0;
7752 accounts = account_get_list();
7753 cm_return_val_if_fail(accounts != NULL, NULL);
7755 optmenubox = gtk_event_box_new();
7756 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7757 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7759 hbox = gtk_hbox_new(FALSE, 6);
7760 from_name = gtk_entry_new();
7762 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7763 G_CALLBACK(compose_grab_focus_cb), compose);
7765 for (; accounts != NULL; accounts = accounts->next, num++) {
7766 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7767 gchar *name, *from = NULL;
7769 if (ac == compose->account) def_menu = num;
7771 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7772 ac->account_name);
7774 if (ac == compose->account) {
7775 if (ac->name && *ac->name) {
7776 gchar *buf;
7777 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7778 from = g_strdup_printf("%s <%s>",
7779 buf, ac->address);
7780 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7781 } else {
7782 from = g_strdup_printf("%s",
7783 ac->address);
7784 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7787 COMBOBOX_ADD(menu, name, ac->account_id);
7788 g_free(name);
7789 g_free(from);
7792 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7794 g_signal_connect(G_OBJECT(optmenu), "changed",
7795 G_CALLBACK(account_activated),
7796 compose);
7797 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7798 G_CALLBACK(compose_entry_popup_extend),
7799 NULL);
7801 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7802 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7804 CLAWS_SET_TIP(optmenubox,
7805 _("Account to use for this email"));
7806 CLAWS_SET_TIP(from_name,
7807 _("Sender address to be used"));
7809 compose->account_combo = optmenu;
7810 compose->from_name = from_name;
7812 return hbox;
7815 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7817 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7818 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7819 Compose *compose = (Compose *) data;
7820 if (active) {
7821 compose->priority = value;
7825 static void compose_reply_change_mode(Compose *compose,
7826 ComposeMode action)
7828 gboolean was_modified = compose->modified;
7830 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7832 cm_return_if_fail(compose->replyinfo != NULL);
7834 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7835 ml = TRUE;
7836 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7837 followup = TRUE;
7838 if (action == COMPOSE_REPLY_TO_ALL)
7839 all = TRUE;
7840 if (action == COMPOSE_REPLY_TO_SENDER)
7841 sender = TRUE;
7842 if (action == COMPOSE_REPLY_TO_LIST)
7843 ml = TRUE;
7845 compose_remove_header_entries(compose);
7846 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7847 if (compose->account->set_autocc && compose->account->auto_cc)
7848 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7850 if (compose->account->set_autobcc && compose->account->auto_bcc)
7851 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7853 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7854 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7855 compose_show_first_last_header(compose, TRUE);
7856 compose->modified = was_modified;
7857 compose_set_title(compose);
7860 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7862 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7863 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7864 Compose *compose = (Compose *) data;
7866 if (active)
7867 compose_reply_change_mode(compose, value);
7870 static void compose_update_priority_menu_item(Compose * compose)
7872 GtkWidget *menuitem = NULL;
7873 switch (compose->priority) {
7874 case PRIORITY_HIGHEST:
7875 menuitem = gtk_ui_manager_get_widget
7876 (compose->ui_manager, "/Menu/Options/Priority/Highest");
7877 break;
7878 case PRIORITY_HIGH:
7879 menuitem = gtk_ui_manager_get_widget
7880 (compose->ui_manager, "/Menu/Options/Priority/High");
7881 break;
7882 case PRIORITY_NORMAL:
7883 menuitem = gtk_ui_manager_get_widget
7884 (compose->ui_manager, "/Menu/Options/Priority/Normal");
7885 break;
7886 case PRIORITY_LOW:
7887 menuitem = gtk_ui_manager_get_widget
7888 (compose->ui_manager, "/Menu/Options/Priority/Low");
7889 break;
7890 case PRIORITY_LOWEST:
7891 menuitem = gtk_ui_manager_get_widget
7892 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
7893 break;
7895 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7898 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7900 Compose *compose = (Compose *) data;
7901 gchar *systemid;
7902 gboolean can_sign = FALSE, can_encrypt = FALSE;
7904 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7906 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
7907 return;
7909 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7910 g_free(compose->privacy_system);
7911 compose->privacy_system = NULL;
7912 if (systemid != NULL) {
7913 compose->privacy_system = g_strdup(systemid);
7915 can_sign = privacy_system_can_sign(systemid);
7916 can_encrypt = privacy_system_can_encrypt(systemid);
7919 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7921 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7922 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7925 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7927 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7928 GtkWidget *menuitem = NULL;
7929 GList *children, *amenu;
7930 gboolean can_sign = FALSE, can_encrypt = FALSE;
7931 gboolean found = FALSE;
7933 if (compose->privacy_system != NULL) {
7934 gchar *systemid;
7935 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
7936 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
7937 cm_return_if_fail(menuitem != NULL);
7939 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
7940 amenu = children;
7941 menuitem = NULL;
7942 while (amenu != NULL) {
7943 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7944 if (systemid != NULL) {
7945 if (strcmp(systemid, compose->privacy_system) == 0 &&
7946 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7947 menuitem = GTK_WIDGET(amenu->data);
7949 can_sign = privacy_system_can_sign(systemid);
7950 can_encrypt = privacy_system_can_encrypt(systemid);
7951 found = TRUE;
7952 break;
7954 } else if (strlen(compose->privacy_system) == 0 &&
7955 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7956 menuitem = GTK_WIDGET(amenu->data);
7958 can_sign = FALSE;
7959 can_encrypt = FALSE;
7960 found = TRUE;
7961 break;
7964 amenu = amenu->next;
7966 g_list_free(children);
7967 if (menuitem != NULL)
7968 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7970 if (warn && !found && strlen(compose->privacy_system)) {
7971 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7972 "will not be able to sign or encrypt this message."),
7973 compose->privacy_system);
7977 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7978 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7981 static void compose_set_out_encoding(Compose *compose)
7983 CharSet out_encoding;
7984 const gchar *branch = NULL;
7985 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7987 switch(out_encoding) {
7988 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7989 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
7990 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
7991 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
7992 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
7993 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
7994 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
7995 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
7996 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
7997 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
7998 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
7999 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8000 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8001 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8002 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8003 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8004 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8005 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8006 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8007 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8008 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8009 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8010 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8011 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8012 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8013 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8014 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8015 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8016 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8017 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8018 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8019 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8020 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8022 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8025 static void compose_set_template_menu(Compose *compose)
8027 GSList *tmpl_list, *cur;
8028 GtkWidget *menu;
8029 GtkWidget *item;
8031 tmpl_list = template_get_config();
8033 menu = gtk_menu_new();
8035 gtk_menu_set_accel_group (GTK_MENU (menu),
8036 gtk_ui_manager_get_accel_group(compose->ui_manager));
8037 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8038 Template *tmpl = (Template *)cur->data;
8039 gchar *accel_path = NULL;
8040 item = gtk_menu_item_new_with_label(tmpl->name);
8041 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8042 g_signal_connect(G_OBJECT(item), "activate",
8043 G_CALLBACK(compose_template_activate_cb),
8044 compose);
8045 g_object_set_data(G_OBJECT(item), "template", tmpl);
8046 gtk_widget_show(item);
8047 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8048 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8049 g_free(accel_path);
8052 gtk_widget_show(menu);
8053 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8056 void compose_update_actions_menu(Compose *compose)
8058 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8061 static void compose_update_privacy_systems_menu(Compose *compose)
8063 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8064 GSList *systems, *cur;
8065 GtkWidget *widget;
8066 GtkWidget *system_none;
8067 GSList *group;
8068 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8069 GtkWidget *privacy_menu = gtk_menu_new();
8071 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8072 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8074 g_signal_connect(G_OBJECT(system_none), "activate",
8075 G_CALLBACK(compose_set_privacy_system_cb), compose);
8077 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8078 gtk_widget_show(system_none);
8080 systems = privacy_get_system_ids();
8081 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8082 gchar *systemid = cur->data;
8084 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8085 widget = gtk_radio_menu_item_new_with_label(group,
8086 privacy_system_get_name(systemid));
8087 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8088 g_strdup(systemid), g_free);
8089 g_signal_connect(G_OBJECT(widget), "activate",
8090 G_CALLBACK(compose_set_privacy_system_cb), compose);
8092 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8093 gtk_widget_show(widget);
8094 g_free(systemid);
8096 g_slist_free(systems);
8097 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8098 gtk_widget_show_all(privacy_menu);
8099 gtk_widget_show_all(privacy_menuitem);
8102 void compose_reflect_prefs_all(void)
8104 GList *cur;
8105 Compose *compose;
8107 for (cur = compose_list; cur != NULL; cur = cur->next) {
8108 compose = (Compose *)cur->data;
8109 compose_set_template_menu(compose);
8113 void compose_reflect_prefs_pixmap_theme(void)
8115 GList *cur;
8116 Compose *compose;
8118 for (cur = compose_list; cur != NULL; cur = cur->next) {
8119 compose = (Compose *)cur->data;
8120 toolbar_update(TOOLBAR_COMPOSE, compose);
8124 static const gchar *compose_quote_char_from_context(Compose *compose)
8126 const gchar *qmark = NULL;
8128 cm_return_val_if_fail(compose != NULL, NULL);
8130 switch (compose->mode) {
8131 /* use forward-specific quote char */
8132 case COMPOSE_FORWARD:
8133 case COMPOSE_FORWARD_AS_ATTACH:
8134 case COMPOSE_FORWARD_INLINE:
8135 if (compose->folder && compose->folder->prefs &&
8136 compose->folder->prefs->forward_with_format)
8137 qmark = compose->folder->prefs->forward_quotemark;
8138 else if (compose->account->forward_with_format)
8139 qmark = compose->account->forward_quotemark;
8140 else
8141 qmark = prefs_common.fw_quotemark;
8142 break;
8144 /* use reply-specific quote char in all other modes */
8145 default:
8146 if (compose->folder && compose->folder->prefs &&
8147 compose->folder->prefs->reply_with_format)
8148 qmark = compose->folder->prefs->reply_quotemark;
8149 else if (compose->account->reply_with_format)
8150 qmark = compose->account->reply_quotemark;
8151 else
8152 qmark = prefs_common.quotemark;
8153 break;
8156 if (qmark == NULL || *qmark == '\0')
8157 qmark = "> ";
8159 return qmark;
8162 static void compose_template_apply(Compose *compose, Template *tmpl,
8163 gboolean replace)
8165 GtkTextView *text;
8166 GtkTextBuffer *buffer;
8167 GtkTextMark *mark;
8168 GtkTextIter iter;
8169 const gchar *qmark;
8170 gchar *parsed_str = NULL;
8171 gint cursor_pos = 0;
8172 const gchar *err_msg = _("The body of the template has an error at line %d.");
8173 if (!tmpl) return;
8175 /* process the body */
8177 text = GTK_TEXT_VIEW(compose->text);
8178 buffer = gtk_text_view_get_buffer(text);
8180 if (tmpl->value) {
8181 qmark = compose_quote_char_from_context(compose);
8183 if (compose->replyinfo != NULL) {
8185 if (replace)
8186 gtk_text_buffer_set_text(buffer, "", -1);
8187 mark = gtk_text_buffer_get_insert(buffer);
8188 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8190 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8191 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8193 } else if (compose->fwdinfo != NULL) {
8195 if (replace)
8196 gtk_text_buffer_set_text(buffer, "", -1);
8197 mark = gtk_text_buffer_get_insert(buffer);
8198 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8200 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8201 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8203 } else {
8204 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8206 GtkTextIter start, end;
8207 gchar *tmp = NULL;
8209 gtk_text_buffer_get_start_iter(buffer, &start);
8210 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8211 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8213 /* clear the buffer now */
8214 if (replace)
8215 gtk_text_buffer_set_text(buffer, "", -1);
8217 parsed_str = compose_quote_fmt(compose, dummyinfo,
8218 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8219 procmsg_msginfo_free( dummyinfo );
8221 g_free( tmp );
8223 } else {
8224 if (replace)
8225 gtk_text_buffer_set_text(buffer, "", -1);
8226 mark = gtk_text_buffer_get_insert(buffer);
8227 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8230 if (replace && parsed_str && compose->account->auto_sig)
8231 compose_insert_sig(compose, FALSE);
8233 if (replace && parsed_str) {
8234 gtk_text_buffer_get_start_iter(buffer, &iter);
8235 gtk_text_buffer_place_cursor(buffer, &iter);
8238 if (parsed_str) {
8239 cursor_pos = quote_fmt_get_cursor_pos();
8240 compose->set_cursor_pos = cursor_pos;
8241 if (cursor_pos == -1)
8242 cursor_pos = 0;
8243 gtk_text_buffer_get_start_iter(buffer, &iter);
8244 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8245 gtk_text_buffer_place_cursor(buffer, &iter);
8248 /* process the other fields */
8250 compose_template_apply_fields(compose, tmpl);
8251 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8252 quote_fmt_reset_vartable();
8253 compose_changed_cb(NULL, compose);
8255 #ifdef USE_ENCHANT
8256 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8257 gtkaspell_highlight_all(compose->gtkaspell);
8258 #endif
8261 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8263 MsgInfo* dummyinfo = NULL;
8264 MsgInfo *msginfo = NULL;
8265 gchar *buf = NULL;
8267 if (compose->replyinfo != NULL)
8268 msginfo = compose->replyinfo;
8269 else if (compose->fwdinfo != NULL)
8270 msginfo = compose->fwdinfo;
8271 else {
8272 dummyinfo = compose_msginfo_new_from_compose(compose);
8273 msginfo = dummyinfo;
8276 if (tmpl->from && *tmpl->from != '\0') {
8277 #ifdef USE_ENCHANT
8278 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8279 compose->gtkaspell);
8280 #else
8281 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8282 #endif
8283 quote_fmt_scan_string(tmpl->from);
8284 quote_fmt_parse();
8286 buf = quote_fmt_get_buffer();
8287 if (buf == NULL) {
8288 alertpanel_error(_("Template From format error."));
8289 } else {
8290 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8294 if (tmpl->to && *tmpl->to != '\0') {
8295 #ifdef USE_ENCHANT
8296 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8297 compose->gtkaspell);
8298 #else
8299 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8300 #endif
8301 quote_fmt_scan_string(tmpl->to);
8302 quote_fmt_parse();
8304 buf = quote_fmt_get_buffer();
8305 if (buf == NULL) {
8306 alertpanel_error(_("Template To format error."));
8307 } else {
8308 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8312 if (tmpl->cc && *tmpl->cc != '\0') {
8313 #ifdef USE_ENCHANT
8314 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8315 compose->gtkaspell);
8316 #else
8317 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8318 #endif
8319 quote_fmt_scan_string(tmpl->cc);
8320 quote_fmt_parse();
8322 buf = quote_fmt_get_buffer();
8323 if (buf == NULL) {
8324 alertpanel_error(_("Template Cc format error."));
8325 } else {
8326 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8330 if (tmpl->bcc && *tmpl->bcc != '\0') {
8331 #ifdef USE_ENCHANT
8332 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8333 compose->gtkaspell);
8334 #else
8335 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8336 #endif
8337 quote_fmt_scan_string(tmpl->bcc);
8338 quote_fmt_parse();
8340 buf = quote_fmt_get_buffer();
8341 if (buf == NULL) {
8342 alertpanel_error(_("Template Bcc format error."));
8343 } else {
8344 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8348 /* process the subject */
8349 if (tmpl->subject && *tmpl->subject != '\0') {
8350 #ifdef USE_ENCHANT
8351 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8352 compose->gtkaspell);
8353 #else
8354 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8355 #endif
8356 quote_fmt_scan_string(tmpl->subject);
8357 quote_fmt_parse();
8359 buf = quote_fmt_get_buffer();
8360 if (buf == NULL) {
8361 alertpanel_error(_("Template subject format error."));
8362 } else {
8363 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8367 procmsg_msginfo_free( dummyinfo );
8370 static void compose_destroy(Compose *compose)
8372 GtkAllocation allocation;
8373 GtkTextBuffer *buffer;
8374 GtkClipboard *clipboard;
8376 compose_list = g_list_remove(compose_list, compose);
8378 if (compose->updating) {
8379 debug_print("danger, not destroying anything now\n");
8380 compose->deferred_destroy = TRUE;
8381 return;
8383 /* NOTE: address_completion_end() does nothing with the window
8384 * however this may change. */
8385 address_completion_end(compose->window);
8387 slist_free_strings(compose->to_list);
8388 g_slist_free(compose->to_list);
8389 slist_free_strings(compose->newsgroup_list);
8390 g_slist_free(compose->newsgroup_list);
8391 slist_free_strings(compose->header_list);
8392 g_slist_free(compose->header_list);
8394 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8396 g_hash_table_destroy(compose->email_hashtable);
8398 procmsg_msginfo_free(compose->targetinfo);
8399 procmsg_msginfo_free(compose->replyinfo);
8400 procmsg_msginfo_free(compose->fwdinfo);
8402 g_free(compose->replyto);
8403 g_free(compose->cc);
8404 g_free(compose->bcc);
8405 g_free(compose->newsgroups);
8406 g_free(compose->followup_to);
8408 g_free(compose->ml_post);
8410 g_free(compose->inreplyto);
8411 g_free(compose->references);
8412 g_free(compose->msgid);
8413 g_free(compose->boundary);
8415 g_free(compose->redirect_filename);
8416 if (compose->undostruct)
8417 undo_destroy(compose->undostruct);
8419 g_free(compose->sig_str);
8421 g_free(compose->exteditor_file);
8423 g_free(compose->orig_charset);
8425 g_free(compose->privacy_system);
8427 if (addressbook_get_target_compose() == compose)
8428 addressbook_set_target_compose(NULL);
8430 #if USE_ENCHANT
8431 if (compose->gtkaspell) {
8432 gtkaspell_delete(compose->gtkaspell);
8433 compose->gtkaspell = NULL;
8435 #endif
8437 if (!compose->batch) {
8438 gtk_widget_get_allocation(compose->scrolledwin, &allocation);
8439 prefs_common.compose_width = allocation.width;
8440 prefs_common.compose_height = allocation.height;
8443 if (!gtk_widget_get_parent(compose->paned))
8444 gtk_widget_destroy(compose->paned);
8445 gtk_widget_destroy(compose->popupmenu);
8447 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8448 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8449 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8451 gtk_widget_destroy(compose->window);
8452 toolbar_destroy(compose->toolbar);
8453 g_free(compose->toolbar);
8454 g_mutex_free(compose->mutex);
8455 g_free(compose);
8458 static void compose_attach_info_free(AttachInfo *ainfo)
8460 g_free(ainfo->file);
8461 g_free(ainfo->content_type);
8462 g_free(ainfo->name);
8463 g_free(ainfo->charset);
8464 g_free(ainfo);
8467 static void compose_attach_update_label(Compose *compose)
8469 GtkTreeIter iter;
8470 gint i = 1;
8471 gchar *text;
8472 GtkTreeModel *model;
8474 if(compose == NULL)
8475 return;
8477 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8478 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8479 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8480 return;
8483 while(gtk_tree_model_iter_next(model, &iter))
8484 i++;
8486 text = g_strdup_printf("(%d)", i);
8487 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8488 g_free(text);
8491 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8493 Compose *compose = (Compose *)data;
8494 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8495 GtkTreeSelection *selection;
8496 GList *sel, *cur;
8497 GtkTreeModel *model;
8499 selection = gtk_tree_view_get_selection(tree_view);
8500 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8502 if (!sel)
8503 return;
8505 for (cur = sel; cur != NULL; cur = cur->next) {
8506 GtkTreePath *path = cur->data;
8507 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8508 (model, cur->data);
8509 cur->data = ref;
8510 gtk_tree_path_free(path);
8513 for (cur = sel; cur != NULL; cur = cur->next) {
8514 GtkTreeRowReference *ref = cur->data;
8515 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8516 GtkTreeIter iter;
8518 if (gtk_tree_model_get_iter(model, &iter, path))
8519 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8521 gtk_tree_path_free(path);
8522 gtk_tree_row_reference_free(ref);
8525 g_list_free(sel);
8526 compose_attach_update_label(compose);
8529 static struct _AttachProperty
8531 GtkWidget *window;
8532 GtkWidget *mimetype_entry;
8533 GtkWidget *encoding_optmenu;
8534 GtkWidget *path_entry;
8535 GtkWidget *filename_entry;
8536 GtkWidget *ok_btn;
8537 GtkWidget *cancel_btn;
8538 } attach_prop;
8540 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8542 gtk_tree_path_free((GtkTreePath *)ptr);
8545 static void compose_attach_property(GtkAction *action, gpointer data)
8547 Compose *compose = (Compose *)data;
8548 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8549 AttachInfo *ainfo;
8550 GtkComboBox *optmenu;
8551 GtkTreeSelection *selection;
8552 GList *sel;
8553 GtkTreeModel *model;
8554 GtkTreeIter iter;
8555 GtkTreePath *path;
8556 static gboolean cancelled;
8558 /* only if one selected */
8559 selection = gtk_tree_view_get_selection(tree_view);
8560 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8561 return;
8563 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8564 if (!sel)
8565 return;
8567 path = (GtkTreePath *) sel->data;
8568 gtk_tree_model_get_iter(model, &iter, path);
8569 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8571 if (!ainfo) {
8572 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8573 g_list_free(sel);
8574 return;
8576 g_list_free(sel);
8578 if (!attach_prop.window)
8579 compose_attach_property_create(&cancelled);
8580 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8581 gtk_widget_grab_focus(attach_prop.ok_btn);
8582 gtk_widget_show(attach_prop.window);
8583 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
8585 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8586 if (ainfo->encoding == ENC_UNKNOWN)
8587 combobox_select_by_data(optmenu, ENC_BASE64);
8588 else
8589 combobox_select_by_data(optmenu, ainfo->encoding);
8591 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8592 ainfo->content_type ? ainfo->content_type : "");
8593 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8594 ainfo->file ? ainfo->file : "");
8595 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8596 ainfo->name ? ainfo->name : "");
8598 for (;;) {
8599 const gchar *entry_text;
8600 gchar *text;
8601 gchar *cnttype = NULL;
8602 gchar *file = NULL;
8603 off_t size = 0;
8605 cancelled = FALSE;
8606 gtk_main();
8608 gtk_widget_hide(attach_prop.window);
8609 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8611 if (cancelled)
8612 break;
8614 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8615 if (*entry_text != '\0') {
8616 gchar *p;
8618 text = g_strstrip(g_strdup(entry_text));
8619 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8620 cnttype = g_strdup(text);
8621 g_free(text);
8622 } else {
8623 alertpanel_error(_("Invalid MIME type."));
8624 g_free(text);
8625 continue;
8629 ainfo->encoding = combobox_get_active_data(optmenu);
8631 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8632 if (*entry_text != '\0') {
8633 if (is_file_exist(entry_text) &&
8634 (size = get_file_size(entry_text)) > 0)
8635 file = g_strdup(entry_text);
8636 else {
8637 alertpanel_error
8638 (_("File doesn't exist or is empty."));
8639 g_free(cnttype);
8640 continue;
8644 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8645 if (*entry_text != '\0') {
8646 g_free(ainfo->name);
8647 ainfo->name = g_strdup(entry_text);
8650 if (cnttype) {
8651 g_free(ainfo->content_type);
8652 ainfo->content_type = cnttype;
8654 if (file) {
8655 g_free(ainfo->file);
8656 ainfo->file = file;
8658 if (size)
8659 ainfo->size = (goffset)size;
8661 /* update tree store */
8662 text = to_human_readable(ainfo->size);
8663 gtk_tree_model_get_iter(model, &iter, path);
8664 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8665 COL_MIMETYPE, ainfo->content_type,
8666 COL_SIZE, text,
8667 COL_NAME, ainfo->name,
8668 COL_CHARSET, ainfo->charset,
8669 -1);
8671 break;
8674 gtk_tree_path_free(path);
8677 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8679 label = gtk_label_new(str); \
8680 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8681 GTK_FILL, 0, 0, 0); \
8682 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8684 entry = gtk_entry_new(); \
8685 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8686 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8689 static void compose_attach_property_create(gboolean *cancelled)
8691 GtkWidget *window;
8692 GtkWidget *vbox;
8693 GtkWidget *table;
8694 GtkWidget *label;
8695 GtkWidget *mimetype_entry;
8696 GtkWidget *hbox;
8697 GtkWidget *optmenu;
8698 GtkListStore *optmenu_menu;
8699 GtkWidget *path_entry;
8700 GtkWidget *filename_entry;
8701 GtkWidget *hbbox;
8702 GtkWidget *ok_btn;
8703 GtkWidget *cancel_btn;
8704 GList *mime_type_list, *strlist;
8705 GtkTreeIter iter;
8707 debug_print("Creating attach_property window...\n");
8709 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8710 gtk_widget_set_size_request(window, 480, -1);
8711 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8712 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8713 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8714 g_signal_connect(G_OBJECT(window), "delete_event",
8715 G_CALLBACK(attach_property_delete_event),
8716 cancelled);
8717 g_signal_connect(G_OBJECT(window), "key_press_event",
8718 G_CALLBACK(attach_property_key_pressed),
8719 cancelled);
8721 vbox = gtk_vbox_new(FALSE, 8);
8722 gtk_container_add(GTK_CONTAINER(window), vbox);
8724 table = gtk_table_new(4, 2, FALSE);
8725 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8726 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8727 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8729 label = gtk_label_new(_("MIME type"));
8730 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8731 GTK_FILL, 0, 0, 0);
8732 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8733 mimetype_entry = gtk_combo_box_entry_new_text();
8734 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8735 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8737 /* stuff with list */
8738 mime_type_list = procmime_get_mime_type_list();
8739 strlist = NULL;
8740 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8741 MimeType *type = (MimeType *) mime_type_list->data;
8742 gchar *tmp;
8744 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8746 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8747 g_free(tmp);
8748 else
8749 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8750 (GCompareFunc)strcmp2);
8753 for (mime_type_list = strlist; mime_type_list != NULL;
8754 mime_type_list = mime_type_list->next) {
8755 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8756 g_free(mime_type_list->data);
8758 g_list_free(strlist);
8759 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8760 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8762 label = gtk_label_new(_("Encoding"));
8763 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8764 GTK_FILL, 0, 0, 0);
8765 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8767 hbox = gtk_hbox_new(FALSE, 0);
8768 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8769 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8771 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8772 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8774 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8775 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8776 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8777 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8778 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8780 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8782 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8783 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8785 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8786 &ok_btn, GTK_STOCK_OK,
8787 NULL, NULL);
8788 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8789 gtk_widget_grab_default(ok_btn);
8791 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8792 G_CALLBACK(attach_property_ok),
8793 cancelled);
8794 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8795 G_CALLBACK(attach_property_cancel),
8796 cancelled);
8798 gtk_widget_show_all(vbox);
8800 attach_prop.window = window;
8801 attach_prop.mimetype_entry = mimetype_entry;
8802 attach_prop.encoding_optmenu = optmenu;
8803 attach_prop.path_entry = path_entry;
8804 attach_prop.filename_entry = filename_entry;
8805 attach_prop.ok_btn = ok_btn;
8806 attach_prop.cancel_btn = cancel_btn;
8809 #undef SET_LABEL_AND_ENTRY
8811 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8813 *cancelled = FALSE;
8814 gtk_main_quit();
8817 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8819 *cancelled = TRUE;
8820 gtk_main_quit();
8823 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8824 gboolean *cancelled)
8826 *cancelled = TRUE;
8827 gtk_main_quit();
8829 return TRUE;
8832 static gboolean attach_property_key_pressed(GtkWidget *widget,
8833 GdkEventKey *event,
8834 gboolean *cancelled)
8836 if (event && event->keyval == GDK_KEY_Escape) {
8837 *cancelled = TRUE;
8838 gtk_main_quit();
8840 if (event && event->keyval == GDK_KEY_Return) {
8841 *cancelled = FALSE;
8842 gtk_main_quit();
8843 return TRUE;
8845 return FALSE;
8848 static void compose_exec_ext_editor(Compose *compose)
8850 #ifdef G_OS_UNIX
8851 gchar *tmp;
8852 pid_t pid;
8853 gint pipe_fds[2];
8855 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8856 G_DIR_SEPARATOR, compose);
8858 if (pipe(pipe_fds) < 0) {
8859 perror("pipe");
8860 g_free(tmp);
8861 return;
8864 if ((pid = fork()) < 0) {
8865 perror("fork");
8866 g_free(tmp);
8867 return;
8870 if (pid != 0) {
8871 /* close the write side of the pipe */
8872 close(pipe_fds[1]);
8874 compose->exteditor_file = g_strdup(tmp);
8875 compose->exteditor_pid = pid;
8877 compose_set_ext_editor_sensitive(compose, FALSE);
8879 #ifndef G_OS_WIN32
8880 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8881 #else
8882 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
8883 #endif
8884 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8885 G_IO_IN,
8886 compose_input_cb,
8887 compose);
8888 } else { /* process-monitoring process */
8889 pid_t pid_ed;
8891 if (setpgid(0, 0))
8892 perror("setpgid");
8894 /* close the read side of the pipe */
8895 close(pipe_fds[0]);
8897 if (compose_write_body_to_file(compose, tmp) < 0) {
8898 fd_write_all(pipe_fds[1], "2\n", 2);
8899 _exit(1);
8902 pid_ed = compose_exec_ext_editor_real(tmp);
8903 if (pid_ed < 0) {
8904 fd_write_all(pipe_fds[1], "1\n", 2);
8905 _exit(1);
8908 /* wait until editor is terminated */
8909 waitpid(pid_ed, NULL, 0);
8911 fd_write_all(pipe_fds[1], "0\n", 2);
8913 close(pipe_fds[1]);
8914 _exit(0);
8917 g_free(tmp);
8918 #endif /* G_OS_UNIX */
8921 #ifdef G_OS_UNIX
8922 static gint compose_exec_ext_editor_real(const gchar *file)
8924 gchar buf[1024];
8925 gchar *p;
8926 gchar **cmdline;
8927 pid_t pid;
8929 cm_return_val_if_fail(file != NULL, -1);
8931 if ((pid = fork()) < 0) {
8932 perror("fork");
8933 return -1;
8936 if (pid != 0) return pid;
8938 /* grandchild process */
8940 if (setpgid(0, getppid()))
8941 perror("setpgid");
8943 if (prefs_common_get_ext_editor_cmd() &&
8944 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8945 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8946 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8947 } else {
8948 if (prefs_common_get_ext_editor_cmd())
8949 g_warning("External editor command-line is invalid: '%s'\n",
8950 prefs_common_get_ext_editor_cmd());
8951 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8954 cmdline = strsplit_with_quote(buf, " ", 1024);
8955 execvp(cmdline[0], cmdline);
8957 perror("execvp");
8958 g_strfreev(cmdline);
8960 _exit(1);
8963 static gboolean compose_ext_editor_kill(Compose *compose)
8965 pid_t pgid = compose->exteditor_pid * -1;
8966 gint ret;
8968 ret = kill(pgid, 0);
8970 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8971 AlertValue val;
8972 gchar *msg;
8974 msg = g_strdup_printf
8975 (_("The external editor is still working.\n"
8976 "Force terminating the process?\n"
8977 "process group id: %d"), -pgid);
8978 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8979 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8981 g_free(msg);
8983 if (val == G_ALERTALTERNATE) {
8984 g_source_remove(compose->exteditor_tag);
8985 g_io_channel_shutdown(compose->exteditor_ch,
8986 FALSE, NULL);
8987 g_io_channel_unref(compose->exteditor_ch);
8989 if (kill(pgid, SIGTERM) < 0) perror("kill");
8990 waitpid(compose->exteditor_pid, NULL, 0);
8992 g_warning("Terminated process group id: %d", -pgid);
8993 g_warning("Temporary file: %s",
8994 compose->exteditor_file);
8996 compose_set_ext_editor_sensitive(compose, TRUE);
8998 g_free(compose->exteditor_file);
8999 compose->exteditor_file = NULL;
9000 compose->exteditor_pid = -1;
9001 compose->exteditor_ch = NULL;
9002 compose->exteditor_tag = -1;
9003 } else
9004 return FALSE;
9007 return TRUE;
9010 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9011 gpointer data)
9013 gchar buf[3] = "3";
9014 Compose *compose = (Compose *)data;
9015 gsize bytes_read;
9017 debug_print("Compose: input from monitoring process\n");
9019 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9021 g_io_channel_shutdown(source, FALSE, NULL);
9022 g_io_channel_unref(source);
9024 waitpid(compose->exteditor_pid, NULL, 0);
9026 if (buf[0] == '0') { /* success */
9027 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9028 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9030 gtk_text_buffer_set_text(buffer, "", -1);
9031 compose_insert_file(compose, compose->exteditor_file);
9032 compose_changed_cb(NULL, compose);
9033 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9035 if (claws_unlink(compose->exteditor_file) < 0)
9036 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9037 } else if (buf[0] == '1') { /* failed */
9038 g_warning("Couldn't exec external editor\n");
9039 if (claws_unlink(compose->exteditor_file) < 0)
9040 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9041 } else if (buf[0] == '2') {
9042 g_warning("Couldn't write to file\n");
9043 } else if (buf[0] == '3') {
9044 g_warning("Pipe read failed\n");
9047 compose_set_ext_editor_sensitive(compose, TRUE);
9049 g_free(compose->exteditor_file);
9050 compose->exteditor_file = NULL;
9051 compose->exteditor_pid = -1;
9052 compose->exteditor_ch = NULL;
9053 compose->exteditor_tag = -1;
9055 return FALSE;
9058 static void compose_set_ext_editor_sensitive(Compose *compose,
9059 gboolean sensitive)
9061 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9062 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9063 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9064 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9065 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9066 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9067 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9069 gtk_widget_set_sensitive(compose->text, sensitive);
9070 if (compose->toolbar->send_btn)
9071 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9072 if (compose->toolbar->sendl_btn)
9073 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9074 if (compose->toolbar->draft_btn)
9075 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9076 if (compose->toolbar->insert_btn)
9077 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9078 if (compose->toolbar->sig_btn)
9079 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9080 if (compose->toolbar->exteditor_btn)
9081 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9082 if (compose->toolbar->linewrap_current_btn)
9083 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9084 if (compose->toolbar->linewrap_all_btn)
9085 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9087 #endif /* G_OS_UNIX */
9090 * compose_undo_state_changed:
9092 * Change the sensivity of the menuentries undo and redo
9094 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9095 gint redo_state, gpointer data)
9097 Compose *compose = (Compose *)data;
9099 switch (undo_state) {
9100 case UNDO_STATE_TRUE:
9101 if (!undostruct->undo_state) {
9102 undostruct->undo_state = TRUE;
9103 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9105 break;
9106 case UNDO_STATE_FALSE:
9107 if (undostruct->undo_state) {
9108 undostruct->undo_state = FALSE;
9109 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9111 break;
9112 case UNDO_STATE_UNCHANGED:
9113 break;
9114 case UNDO_STATE_REFRESH:
9115 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9116 break;
9117 default:
9118 g_warning("Undo state not recognized");
9119 break;
9122 switch (redo_state) {
9123 case UNDO_STATE_TRUE:
9124 if (!undostruct->redo_state) {
9125 undostruct->redo_state = TRUE;
9126 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9128 break;
9129 case UNDO_STATE_FALSE:
9130 if (undostruct->redo_state) {
9131 undostruct->redo_state = FALSE;
9132 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9134 break;
9135 case UNDO_STATE_UNCHANGED:
9136 break;
9137 case UNDO_STATE_REFRESH:
9138 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9139 break;
9140 default:
9141 g_warning("Redo state not recognized");
9142 break;
9146 /* callback functions */
9148 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9149 * includes "non-client" (windows-izm) in calculation, so this calculation
9150 * may not be accurate.
9152 #if !GTK_CHECK_VERSION(2, 24, 0)
9153 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9154 GtkAllocation *allocation,
9155 GtkSHRuler *shruler)
9157 if (prefs_common.show_ruler) {
9158 gint char_width = 0, char_height = 0;
9159 gint line_width_in_chars;
9161 gtkut_get_font_size(GTK_WIDGET(widget),
9162 &char_width, &char_height);
9163 line_width_in_chars =
9164 (allocation->width - allocation->x) / char_width;
9166 /* got the maximum */
9167 gtk_ruler_set_range(GTK_RULER(shruler),
9168 0.0, line_width_in_chars, 0,
9169 /*line_width_in_chars*/ char_width);
9172 return TRUE;
9174 #endif
9176 typedef struct {
9177 gchar *header;
9178 gchar *entry;
9179 ComposePrefType type;
9180 gboolean entry_marked;
9181 } HeaderEntryState;
9183 static void account_activated(GtkComboBox *optmenu, gpointer data)
9185 Compose *compose = (Compose *)data;
9187 PrefsAccount *ac;
9188 gchar *folderidentifier;
9189 gint account_id = 0;
9190 GtkTreeModel *menu;
9191 GtkTreeIter iter;
9192 GSList *list, *saved_list = NULL;
9193 HeaderEntryState *state;
9194 GtkRcStyle *style = NULL;
9195 static GdkColor yellow;
9196 static gboolean color_set = FALSE;
9198 /* Get ID of active account in the combo box */
9199 menu = gtk_combo_box_get_model(optmenu);
9200 gtk_combo_box_get_active_iter(optmenu, &iter);
9201 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9203 ac = account_find_from_id(account_id);
9204 cm_return_if_fail(ac != NULL);
9206 if (ac != compose->account) {
9207 compose_select_account(compose, ac, FALSE);
9209 for (list = compose->header_list; list; list = list->next) {
9210 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9212 if (hentry->type == PREF_ACCOUNT || !list->next) {
9213 compose_destroy_headerentry(compose, hentry);
9214 continue;
9217 state = g_malloc0(sizeof(HeaderEntryState));
9218 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9219 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9220 state->entry = gtk_editable_get_chars(
9221 GTK_EDITABLE(hentry->entry), 0, -1);
9222 state->type = hentry->type;
9224 if (!color_set) {
9225 gdk_color_parse("#f5f6be", &yellow);
9226 color_set = gdk_colormap_alloc_color(
9227 gdk_colormap_get_system(),
9228 &yellow, FALSE, TRUE);
9231 style = gtk_widget_get_modifier_style(hentry->entry);
9232 state->entry_marked = gdk_color_equal(&yellow,
9233 &style->base[GTK_STATE_NORMAL]);
9235 saved_list = g_slist_append(saved_list, state);
9236 compose_destroy_headerentry(compose, hentry);
9239 compose->header_last = NULL;
9240 g_slist_free(compose->header_list);
9241 compose->header_list = NULL;
9242 compose->header_nextrow = 1;
9243 compose_create_header_entry(compose);
9245 if (ac->set_autocc && ac->auto_cc)
9246 compose_entry_append(compose, ac->auto_cc,
9247 COMPOSE_CC, PREF_ACCOUNT);
9249 if (ac->set_autobcc && ac->auto_bcc)
9250 compose_entry_append(compose, ac->auto_bcc,
9251 COMPOSE_BCC, PREF_ACCOUNT);
9253 if (ac->set_autoreplyto && ac->auto_replyto)
9254 compose_entry_append(compose, ac->auto_replyto,
9255 COMPOSE_REPLYTO, PREF_ACCOUNT);
9257 for (list = saved_list; list; list = list->next) {
9258 state = (HeaderEntryState *) list->data;
9260 compose_add_header_entry(compose, state->header,
9261 state->entry, state->type);
9262 if (state->entry_marked)
9263 compose_entry_mark_default_to(compose, state->entry);
9265 g_free(state->header);
9266 g_free(state->entry);
9267 g_free(state);
9269 g_slist_free(saved_list);
9271 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9272 (ac->protocol == A_NNTP) ?
9273 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9276 /* Set message save folder */
9277 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9278 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9280 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9281 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9283 compose_set_save_to(compose, NULL);
9284 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9285 folderidentifier = folder_item_get_identifier(account_get_special_folder
9286 (compose->account, F_OUTBOX));
9287 compose_set_save_to(compose, folderidentifier);
9288 g_free(folderidentifier);
9292 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9293 GtkTreeViewColumn *column, Compose *compose)
9295 compose_attach_property(NULL, compose);
9298 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9299 gpointer data)
9301 Compose *compose = (Compose *)data;
9302 GtkTreeSelection *attach_selection;
9303 gint attach_nr_selected;
9305 if (!event) return FALSE;
9307 if (event->button == 3) {
9308 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9309 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9311 if (attach_nr_selected > 0)
9313 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", TRUE);
9314 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", TRUE);
9315 } else {
9316 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
9317 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
9320 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9321 NULL, NULL, event->button, event->time);
9322 return TRUE;
9325 return FALSE;
9328 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9329 gpointer data)
9331 Compose *compose = (Compose *)data;
9333 if (!event) return FALSE;
9335 switch (event->keyval) {
9336 case GDK_KEY_Delete:
9337 compose_attach_remove_selected(NULL, compose);
9338 break;
9340 return FALSE;
9343 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9345 toolbar_comp_set_sensitive(compose, allow);
9346 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9347 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9348 #if USE_ENCHANT
9349 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9350 #endif
9351 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9352 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9353 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9355 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9359 static void compose_send_cb(GtkAction *action, gpointer data)
9361 Compose *compose = (Compose *)data;
9363 if (prefs_common.work_offline &&
9364 !inc_offline_should_override(TRUE,
9365 _("Claws Mail needs network access in order "
9366 "to send this email.")))
9367 return;
9369 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9370 g_source_remove(compose->draft_timeout_tag);
9371 compose->draft_timeout_tag = -1;
9374 compose_send(compose);
9377 static void compose_send_later_cb(GtkAction *action, gpointer data)
9379 Compose *compose = (Compose *)data;
9380 gint val;
9382 inc_lock();
9383 compose_allow_user_actions(compose, FALSE);
9384 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9385 compose_allow_user_actions(compose, TRUE);
9386 inc_unlock();
9388 if (!val) {
9389 compose_close(compose);
9390 } else if (val == -1) {
9391 alertpanel_error(_("Could not queue message."));
9392 } else if (val == -2) {
9393 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9394 } else if (val == -3) {
9395 if (privacy_peek_error())
9396 alertpanel_error(_("Could not queue message for sending:\n\n"
9397 "Signature failed: %s"), privacy_get_error());
9398 } else if (val == -4) {
9399 alertpanel_error(_("Could not queue message for sending:\n\n"
9400 "Charset conversion failed."));
9401 } else if (val == -5) {
9402 alertpanel_error(_("Could not queue message for sending:\n\n"
9403 "Couldn't get recipient encryption key."));
9404 } else if (val == -6) {
9405 /* silent error */
9407 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9410 #define DRAFTED_AT_EXIT "drafted_at_exit"
9411 static void compose_register_draft(MsgInfo *info)
9413 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9414 DRAFTED_AT_EXIT, NULL);
9415 FILE *fp = g_fopen(filepath, "ab");
9417 if (fp) {
9418 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9419 info->msgnum);
9420 fclose(fp);
9423 g_free(filepath);
9426 gboolean compose_draft (gpointer data, guint action)
9428 Compose *compose = (Compose *)data;
9429 FolderItem *draft;
9430 gchar *tmp;
9431 gint msgnum;
9432 MsgFlags flag = {0, 0};
9433 static gboolean lock = FALSE;
9434 MsgInfo *newmsginfo;
9435 FILE *fp;
9436 gboolean target_locked = FALSE;
9437 gboolean err = FALSE;
9439 if (lock) return FALSE;
9441 if (compose->sending)
9442 return TRUE;
9444 draft = account_get_special_folder(compose->account, F_DRAFT);
9445 cm_return_val_if_fail(draft != NULL, FALSE);
9447 if (!g_mutex_trylock(compose->mutex)) {
9448 /* we don't want to lock the mutex once it's available,
9449 * because as the only other part of compose.c locking
9450 * it is compose_close - which means once unlocked,
9451 * the compose struct will be freed */
9452 debug_print("couldn't lock mutex, probably sending\n");
9453 return FALSE;
9456 lock = TRUE;
9458 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9459 G_DIR_SEPARATOR, compose);
9460 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9461 FILE_OP_ERROR(tmp, "fopen");
9462 goto warn_err;
9465 /* chmod for security */
9466 if (change_file_mode_rw(fp, tmp) < 0) {
9467 FILE_OP_ERROR(tmp, "chmod");
9468 g_warning("can't change file mode\n");
9471 /* Save draft infos */
9472 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9473 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9475 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9476 gchar *savefolderid;
9478 savefolderid = compose_get_save_to(compose);
9479 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9480 g_free(savefolderid);
9482 if (compose->return_receipt) {
9483 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9485 if (compose->privacy_system) {
9486 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9487 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9488 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9491 /* Message-ID of message replying to */
9492 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9493 gchar *folderid;
9495 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9496 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9497 g_free(folderid);
9499 /* Message-ID of message forwarding to */
9500 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9501 gchar *folderid;
9503 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9504 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9505 g_free(folderid);
9508 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9509 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9511 /* end of headers */
9512 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9514 if (err) {
9515 fclose(fp);
9516 goto warn_err;
9519 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9520 fclose(fp);
9521 goto warn_err;
9523 if (fclose(fp) == EOF) {
9524 goto warn_err;
9527 if (compose->targetinfo) {
9528 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9529 flag.perm_flags = target_locked?MSG_LOCKED:0;
9531 flag.tmp_flags = MSG_DRAFT;
9533 folder_item_scan(draft);
9534 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9535 MsgInfo *tmpinfo = NULL;
9536 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9537 if (compose->msgid) {
9538 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9540 if (tmpinfo) {
9541 msgnum = tmpinfo->msgnum;
9542 procmsg_msginfo_free(tmpinfo);
9543 debug_print("got draft msgnum %d from scanning\n", msgnum);
9544 } else {
9545 debug_print("didn't get draft msgnum after scanning\n");
9547 } else {
9548 debug_print("got draft msgnum %d from adding\n", msgnum);
9550 if (msgnum < 0) {
9551 warn_err:
9552 claws_unlink(tmp);
9553 g_free(tmp);
9554 if (action != COMPOSE_AUTO_SAVE) {
9555 if (action != COMPOSE_DRAFT_FOR_EXIT)
9556 alertpanel_error(_("Could not save draft."));
9557 else {
9558 AlertValue val;
9559 gtkut_window_popup(compose->window);
9560 val = alertpanel_full(_("Could not save draft"),
9561 _("Could not save draft.\n"
9562 "Do you want to cancel exit or discard this email?"),
9563 _("_Cancel exit"), _("_Discard email"), NULL,
9564 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9565 if (val == G_ALERTALTERNATE) {
9566 lock = FALSE;
9567 g_mutex_unlock(compose->mutex); /* must be done before closing */
9568 compose_close(compose);
9569 return TRUE;
9570 } else {
9571 lock = FALSE;
9572 g_mutex_unlock(compose->mutex); /* must be done before closing */
9573 return FALSE;
9577 goto unlock;
9579 g_free(tmp);
9581 if (compose->mode == COMPOSE_REEDIT) {
9582 compose_remove_reedit_target(compose, TRUE);
9585 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9587 if (newmsginfo) {
9588 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9589 if (target_locked)
9590 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9591 else
9592 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9593 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9594 procmsg_msginfo_set_flags(newmsginfo, 0,
9595 MSG_HAS_ATTACHMENT);
9597 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9598 compose_register_draft(newmsginfo);
9600 procmsg_msginfo_free(newmsginfo);
9603 folder_item_scan(draft);
9605 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9606 lock = FALSE;
9607 g_mutex_unlock(compose->mutex); /* must be done before closing */
9608 compose_close(compose);
9609 return TRUE;
9610 } else {
9611 struct stat s;
9612 gchar *path;
9614 path = folder_item_fetch_msg(draft, msgnum);
9615 if (path == NULL) {
9616 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9617 goto unlock;
9619 if (g_stat(path, &s) < 0) {
9620 FILE_OP_ERROR(path, "stat");
9621 g_free(path);
9622 goto unlock;
9624 g_free(path);
9626 procmsg_msginfo_free(compose->targetinfo);
9627 compose->targetinfo = procmsg_msginfo_new();
9628 compose->targetinfo->msgnum = msgnum;
9629 compose->targetinfo->size = (goffset)s.st_size;
9630 compose->targetinfo->mtime = s.st_mtime;
9631 compose->targetinfo->folder = draft;
9632 if (target_locked)
9633 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9634 compose->mode = COMPOSE_REEDIT;
9636 if (action == COMPOSE_AUTO_SAVE) {
9637 compose->autosaved_draft = compose->targetinfo;
9639 compose->modified = FALSE;
9640 compose_set_title(compose);
9642 unlock:
9643 lock = FALSE;
9644 g_mutex_unlock(compose->mutex);
9645 return TRUE;
9648 void compose_clear_exit_drafts(void)
9650 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9651 DRAFTED_AT_EXIT, NULL);
9652 if (is_file_exist(filepath))
9653 claws_unlink(filepath);
9655 g_free(filepath);
9658 void compose_reopen_exit_drafts(void)
9660 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9661 DRAFTED_AT_EXIT, NULL);
9662 FILE *fp = g_fopen(filepath, "rb");
9663 gchar buf[1024];
9665 if (fp) {
9666 while (fgets(buf, sizeof(buf), fp)) {
9667 gchar **parts = g_strsplit(buf, "\t", 2);
9668 const gchar *folder = parts[0];
9669 int msgnum = parts[1] ? atoi(parts[1]):-1;
9671 if (folder && *folder && msgnum > -1) {
9672 FolderItem *item = folder_find_item_from_identifier(folder);
9673 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9674 if (info)
9675 compose_reedit(info, FALSE);
9677 g_strfreev(parts);
9679 fclose(fp);
9681 g_free(filepath);
9682 compose_clear_exit_drafts();
9685 static void compose_save_cb(GtkAction *action, gpointer data)
9687 Compose *compose = (Compose *)data;
9688 compose_draft(compose, COMPOSE_KEEP_EDITING);
9689 compose->rmode = COMPOSE_REEDIT;
9692 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9694 if (compose && file_list) {
9695 GList *tmp;
9697 for ( tmp = file_list; tmp; tmp = tmp->next) {
9698 gchar *file = (gchar *) tmp->data;
9699 gchar *utf8_filename = conv_filename_to_utf8(file);
9700 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
9701 compose_changed_cb(NULL, compose);
9702 if (free_data) {
9703 g_free(file);
9704 tmp->data = NULL;
9706 g_free(utf8_filename);
9711 static void compose_attach_cb(GtkAction *action, gpointer data)
9713 Compose *compose = (Compose *)data;
9714 GList *file_list;
9716 if (compose->redirect_filename != NULL)
9717 return;
9719 file_list = filesel_select_multiple_files_open(_("Select file"));
9721 if (file_list) {
9722 compose_attach_from_list(compose, file_list, TRUE);
9723 g_list_free(file_list);
9727 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9729 Compose *compose = (Compose *)data;
9730 GList *file_list;
9731 gint files_inserted = 0;
9733 file_list = filesel_select_multiple_files_open(_("Select file"));
9735 if (file_list) {
9736 GList *tmp;
9738 for ( tmp = file_list; tmp; tmp = tmp->next) {
9739 gchar *file = (gchar *) tmp->data;
9740 gchar *filedup = g_strdup(file);
9741 gchar *shortfile = g_path_get_basename(filedup);
9742 ComposeInsertResult res;
9743 /* insert the file if the file is short or if the user confirmed that
9744 he/she wants to insert the large file */
9745 res = compose_insert_file(compose, file);
9746 if (res == COMPOSE_INSERT_READ_ERROR) {
9747 alertpanel_error(_("File '%s' could not be read."), shortfile);
9748 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9749 alertpanel_error(_("File '%s' contained invalid characters\n"
9750 "for the current encoding, insertion may be incorrect."),
9751 shortfile);
9752 } else if (res == COMPOSE_INSERT_SUCCESS)
9753 files_inserted++;
9755 g_free(shortfile);
9756 g_free(filedup);
9757 g_free(file);
9759 g_list_free(file_list);
9762 #ifdef USE_ENCHANT
9763 if (files_inserted > 0 && compose->gtkaspell &&
9764 compose->gtkaspell->check_while_typing)
9765 gtkaspell_highlight_all(compose->gtkaspell);
9766 #endif
9769 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
9771 Compose *compose = (Compose *)data;
9773 compose_insert_sig(compose, FALSE);
9776 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9777 gpointer data)
9779 gint x, y;
9780 Compose *compose = (Compose *)data;
9782 gtkut_widget_get_uposition(widget, &x, &y);
9783 if (!compose->batch) {
9784 prefs_common.compose_x = x;
9785 prefs_common.compose_y = y;
9787 if (compose->sending || compose->updating)
9788 return TRUE;
9789 compose_close_cb(NULL, compose);
9790 return TRUE;
9793 void compose_close_toolbar(Compose *compose)
9795 compose_close_cb(NULL, compose);
9798 static void compose_close_cb(GtkAction *action, gpointer data)
9800 Compose *compose = (Compose *)data;
9801 AlertValue val;
9803 #ifdef G_OS_UNIX
9804 if (compose->exteditor_tag != -1) {
9805 if (!compose_ext_editor_kill(compose))
9806 return;
9808 #endif
9810 if (compose->modified) {
9811 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
9812 if (!g_mutex_trylock(compose->mutex)) {
9813 /* we don't want to lock the mutex once it's available,
9814 * because as the only other part of compose.c locking
9815 * it is compose_close - which means once unlocked,
9816 * the compose struct will be freed */
9817 debug_print("couldn't lock mutex, probably sending\n");
9818 return;
9820 if (!reedit) {
9821 val = alertpanel(_("Discard message"),
9822 _("This message has been modified. Discard it?"),
9823 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
9824 } else {
9825 val = alertpanel(_("Save changes"),
9826 _("This message has been modified. Save the latest changes?"),
9827 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
9829 g_mutex_unlock(compose->mutex);
9830 switch (val) {
9831 case G_ALERTDEFAULT:
9832 if (prefs_common.autosave && !reedit)
9833 compose_remove_draft(compose);
9834 break;
9835 case G_ALERTALTERNATE:
9836 compose_draft(data, COMPOSE_QUIT_EDITING);
9837 return;
9838 default:
9839 return;
9843 compose_close(compose);
9846 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
9848 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
9849 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
9850 Compose *compose = (Compose *) data;
9852 if (active)
9853 compose->out_encoding = (CharSet)value;
9856 static void compose_address_cb(GtkAction *action, gpointer data)
9858 Compose *compose = (Compose *)data;
9860 addressbook_open(compose);
9863 static void about_show_cb(GtkAction *action, gpointer data)
9865 about_show();
9868 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
9870 Compose *compose = (Compose *)data;
9871 Template *tmpl;
9872 gchar *msg;
9873 AlertValue val;
9875 tmpl = g_object_get_data(G_OBJECT(widget), "template");
9876 cm_return_if_fail(tmpl != NULL);
9878 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
9879 tmpl->name);
9880 val = alertpanel(_("Apply template"), msg,
9881 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
9882 g_free(msg);
9884 if (val == G_ALERTDEFAULT)
9885 compose_template_apply(compose, tmpl, TRUE);
9886 else if (val == G_ALERTALTERNATE)
9887 compose_template_apply(compose, tmpl, FALSE);
9890 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
9892 Compose *compose = (Compose *)data;
9894 compose_exec_ext_editor(compose);
9897 static void compose_undo_cb(GtkAction *action, gpointer data)
9899 Compose *compose = (Compose *)data;
9900 gboolean prev_autowrap = compose->autowrap;
9902 compose->autowrap = FALSE;
9903 undo_undo(compose->undostruct);
9904 compose->autowrap = prev_autowrap;
9907 static void compose_redo_cb(GtkAction *action, gpointer data)
9909 Compose *compose = (Compose *)data;
9910 gboolean prev_autowrap = compose->autowrap;
9912 compose->autowrap = FALSE;
9913 undo_redo(compose->undostruct);
9914 compose->autowrap = prev_autowrap;
9917 static void entry_cut_clipboard(GtkWidget *entry)
9919 if (GTK_IS_EDITABLE(entry))
9920 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
9921 else if (GTK_IS_TEXT_VIEW(entry))
9922 gtk_text_buffer_cut_clipboard(
9923 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9924 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9925 TRUE);
9928 static void entry_copy_clipboard(GtkWidget *entry)
9930 if (GTK_IS_EDITABLE(entry))
9931 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9932 else if (GTK_IS_TEXT_VIEW(entry))
9933 gtk_text_buffer_copy_clipboard(
9934 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9935 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9938 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9939 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9941 if (GTK_IS_TEXT_VIEW(entry)) {
9942 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9943 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9944 GtkTextIter start_iter, end_iter;
9945 gint start, end;
9946 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9948 if (contents == NULL)
9949 return;
9951 /* we shouldn't delete the selection when middle-click-pasting, or we
9952 * can't mid-click-paste our own selection */
9953 if (clip != GDK_SELECTION_PRIMARY) {
9954 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9955 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9958 if (insert_place == NULL) {
9959 /* if insert_place isn't specified, insert at the cursor.
9960 * used for Ctrl-V pasting */
9961 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9962 start = gtk_text_iter_get_offset(&start_iter);
9963 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9964 } else {
9965 /* if insert_place is specified, paste here.
9966 * used for mid-click-pasting */
9967 start = gtk_text_iter_get_offset(insert_place);
9968 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9969 if (prefs_common.primary_paste_unselects)
9970 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
9973 if (!wrap) {
9974 /* paste unwrapped: mark the paste so it's not wrapped later */
9975 end = start + strlen(contents);
9976 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9977 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9978 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9979 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9980 /* rewrap paragraph now (after a mid-click-paste) */
9981 mark_start = gtk_text_buffer_get_insert(buffer);
9982 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9983 gtk_text_iter_backward_char(&start_iter);
9984 compose_beautify_paragraph(compose, &start_iter, TRUE);
9986 } else if (GTK_IS_EDITABLE(entry))
9987 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9989 compose->modified = TRUE;
9992 static void entry_allsel(GtkWidget *entry)
9994 if (GTK_IS_EDITABLE(entry))
9995 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9996 else if (GTK_IS_TEXT_VIEW(entry)) {
9997 GtkTextIter startiter, enditer;
9998 GtkTextBuffer *textbuf;
10000 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10001 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10002 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10004 gtk_text_buffer_move_mark_by_name(textbuf,
10005 "selection_bound", &startiter);
10006 gtk_text_buffer_move_mark_by_name(textbuf,
10007 "insert", &enditer);
10011 static void compose_cut_cb(GtkAction *action, gpointer data)
10013 Compose *compose = (Compose *)data;
10014 if (compose->focused_editable
10015 #ifndef GENERIC_UMPC
10016 && gtkut_widget_has_focus(compose->focused_editable)
10017 #endif
10019 entry_cut_clipboard(compose->focused_editable);
10022 static void compose_copy_cb(GtkAction *action, gpointer data)
10024 Compose *compose = (Compose *)data;
10025 if (compose->focused_editable
10026 #ifndef GENERIC_UMPC
10027 && gtkut_widget_has_focus(compose->focused_editable)
10028 #endif
10030 entry_copy_clipboard(compose->focused_editable);
10033 static void compose_paste_cb(GtkAction *action, gpointer data)
10035 Compose *compose = (Compose *)data;
10036 gint prev_autowrap;
10037 GtkTextBuffer *buffer;
10038 BLOCK_WRAP();
10039 if (compose->focused_editable &&
10040 gtkut_widget_has_focus(compose->focused_editable))
10041 entry_paste_clipboard(compose, compose->focused_editable,
10042 prefs_common.linewrap_pastes,
10043 GDK_SELECTION_CLIPBOARD, NULL);
10044 UNBLOCK_WRAP();
10046 #ifdef USE_ENCHANT
10047 if (gtkut_widget_has_focus(compose->text) &&
10048 compose->gtkaspell &&
10049 compose->gtkaspell->check_while_typing)
10050 gtkaspell_highlight_all(compose->gtkaspell);
10051 #endif
10054 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10056 Compose *compose = (Compose *)data;
10057 gint wrap_quote = prefs_common.linewrap_quote;
10058 if (compose->focused_editable
10059 #ifndef GENERIC_UMPC
10060 && gtkut_widget_has_focus(compose->focused_editable)
10061 #endif
10063 /* let text_insert() (called directly or at a later time
10064 * after the gtk_editable_paste_clipboard) know that
10065 * text is to be inserted as a quotation. implemented
10066 * by using a simple refcount... */
10067 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10068 G_OBJECT(compose->focused_editable),
10069 "paste_as_quotation"));
10070 g_object_set_data(G_OBJECT(compose->focused_editable),
10071 "paste_as_quotation",
10072 GINT_TO_POINTER(paste_as_quotation + 1));
10073 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10074 entry_paste_clipboard(compose, compose->focused_editable,
10075 prefs_common.linewrap_pastes,
10076 GDK_SELECTION_CLIPBOARD, NULL);
10077 prefs_common.linewrap_quote = wrap_quote;
10081 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10083 Compose *compose = (Compose *)data;
10084 gint prev_autowrap;
10085 GtkTextBuffer *buffer;
10086 BLOCK_WRAP();
10087 if (compose->focused_editable
10088 #ifndef GENERIC_UMPC
10089 && gtkut_widget_has_focus(compose->focused_editable)
10090 #endif
10092 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10093 GDK_SELECTION_CLIPBOARD, NULL);
10094 UNBLOCK_WRAP();
10096 #ifdef USE_ENCHANT
10097 if (gtkut_widget_has_focus(compose->text) &&
10098 compose->gtkaspell &&
10099 compose->gtkaspell->check_while_typing)
10100 gtkaspell_highlight_all(compose->gtkaspell);
10101 #endif
10104 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10106 Compose *compose = (Compose *)data;
10107 gint prev_autowrap;
10108 GtkTextBuffer *buffer;
10109 BLOCK_WRAP();
10110 if (compose->focused_editable
10111 #ifndef GENERIC_UMPC
10112 && gtkut_widget_has_focus(compose->focused_editable)
10113 #endif
10115 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10116 GDK_SELECTION_CLIPBOARD, NULL);
10117 UNBLOCK_WRAP();
10119 #ifdef USE_ENCHANT
10120 if (gtkut_widget_has_focus(compose->text) &&
10121 compose->gtkaspell &&
10122 compose->gtkaspell->check_while_typing)
10123 gtkaspell_highlight_all(compose->gtkaspell);
10124 #endif
10127 static void compose_allsel_cb(GtkAction *action, gpointer data)
10129 Compose *compose = (Compose *)data;
10130 if (compose->focused_editable
10131 #ifndef GENERIC_UMPC
10132 && gtkut_widget_has_focus(compose->focused_editable)
10133 #endif
10135 entry_allsel(compose->focused_editable);
10138 static void textview_move_beginning_of_line (GtkTextView *text)
10140 GtkTextBuffer *buffer;
10141 GtkTextMark *mark;
10142 GtkTextIter ins;
10144 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10146 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10147 mark = gtk_text_buffer_get_insert(buffer);
10148 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10149 gtk_text_iter_set_line_offset(&ins, 0);
10150 gtk_text_buffer_place_cursor(buffer, &ins);
10153 static void textview_move_forward_character (GtkTextView *text)
10155 GtkTextBuffer *buffer;
10156 GtkTextMark *mark;
10157 GtkTextIter ins;
10159 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10161 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10162 mark = gtk_text_buffer_get_insert(buffer);
10163 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10164 if (gtk_text_iter_forward_cursor_position(&ins))
10165 gtk_text_buffer_place_cursor(buffer, &ins);
10168 static void textview_move_backward_character (GtkTextView *text)
10170 GtkTextBuffer *buffer;
10171 GtkTextMark *mark;
10172 GtkTextIter ins;
10174 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10176 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10177 mark = gtk_text_buffer_get_insert(buffer);
10178 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10179 if (gtk_text_iter_backward_cursor_position(&ins))
10180 gtk_text_buffer_place_cursor(buffer, &ins);
10183 static void textview_move_forward_word (GtkTextView *text)
10185 GtkTextBuffer *buffer;
10186 GtkTextMark *mark;
10187 GtkTextIter ins;
10188 gint count;
10190 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10192 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10193 mark = gtk_text_buffer_get_insert(buffer);
10194 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10195 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10196 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10197 gtk_text_iter_backward_word_start(&ins);
10198 gtk_text_buffer_place_cursor(buffer, &ins);
10202 static void textview_move_backward_word (GtkTextView *text)
10204 GtkTextBuffer *buffer;
10205 GtkTextMark *mark;
10206 GtkTextIter ins;
10207 gint count;
10209 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10211 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10212 mark = gtk_text_buffer_get_insert(buffer);
10213 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10214 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10215 if (gtk_text_iter_backward_word_starts(&ins, 1))
10216 gtk_text_buffer_place_cursor(buffer, &ins);
10219 static void textview_move_end_of_line (GtkTextView *text)
10221 GtkTextBuffer *buffer;
10222 GtkTextMark *mark;
10223 GtkTextIter ins;
10225 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10227 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10228 mark = gtk_text_buffer_get_insert(buffer);
10229 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10230 if (gtk_text_iter_forward_to_line_end(&ins))
10231 gtk_text_buffer_place_cursor(buffer, &ins);
10234 static void textview_move_next_line (GtkTextView *text)
10236 GtkTextBuffer *buffer;
10237 GtkTextMark *mark;
10238 GtkTextIter ins;
10239 gint offset;
10241 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10243 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10244 mark = gtk_text_buffer_get_insert(buffer);
10245 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10246 offset = gtk_text_iter_get_line_offset(&ins);
10247 if (gtk_text_iter_forward_line(&ins)) {
10248 gtk_text_iter_set_line_offset(&ins, offset);
10249 gtk_text_buffer_place_cursor(buffer, &ins);
10253 static void textview_move_previous_line (GtkTextView *text)
10255 GtkTextBuffer *buffer;
10256 GtkTextMark *mark;
10257 GtkTextIter ins;
10258 gint offset;
10260 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10262 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10263 mark = gtk_text_buffer_get_insert(buffer);
10264 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10265 offset = gtk_text_iter_get_line_offset(&ins);
10266 if (gtk_text_iter_backward_line(&ins)) {
10267 gtk_text_iter_set_line_offset(&ins, offset);
10268 gtk_text_buffer_place_cursor(buffer, &ins);
10272 static void textview_delete_forward_character (GtkTextView *text)
10274 GtkTextBuffer *buffer;
10275 GtkTextMark *mark;
10276 GtkTextIter ins, end_iter;
10278 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10280 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10281 mark = gtk_text_buffer_get_insert(buffer);
10282 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10283 end_iter = ins;
10284 if (gtk_text_iter_forward_char(&end_iter)) {
10285 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10289 static void textview_delete_backward_character (GtkTextView *text)
10291 GtkTextBuffer *buffer;
10292 GtkTextMark *mark;
10293 GtkTextIter ins, end_iter;
10295 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10297 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10298 mark = gtk_text_buffer_get_insert(buffer);
10299 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10300 end_iter = ins;
10301 if (gtk_text_iter_backward_char(&end_iter)) {
10302 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10306 static void textview_delete_forward_word (GtkTextView *text)
10308 GtkTextBuffer *buffer;
10309 GtkTextMark *mark;
10310 GtkTextIter ins, end_iter;
10312 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10314 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10315 mark = gtk_text_buffer_get_insert(buffer);
10316 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10317 end_iter = ins;
10318 if (gtk_text_iter_forward_word_end(&end_iter)) {
10319 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10323 static void textview_delete_backward_word (GtkTextView *text)
10325 GtkTextBuffer *buffer;
10326 GtkTextMark *mark;
10327 GtkTextIter ins, end_iter;
10329 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10331 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10332 mark = gtk_text_buffer_get_insert(buffer);
10333 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10334 end_iter = ins;
10335 if (gtk_text_iter_backward_word_start(&end_iter)) {
10336 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10340 static void textview_delete_line (GtkTextView *text)
10342 GtkTextBuffer *buffer;
10343 GtkTextMark *mark;
10344 GtkTextIter ins, start_iter, end_iter;
10346 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10348 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10349 mark = gtk_text_buffer_get_insert(buffer);
10350 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10352 start_iter = ins;
10353 gtk_text_iter_set_line_offset(&start_iter, 0);
10355 end_iter = ins;
10356 if (gtk_text_iter_ends_line(&end_iter)){
10357 if (!gtk_text_iter_forward_char(&end_iter))
10358 gtk_text_iter_backward_char(&start_iter);
10360 else
10361 gtk_text_iter_forward_to_line_end(&end_iter);
10362 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10365 static void textview_delete_to_line_end (GtkTextView *text)
10367 GtkTextBuffer *buffer;
10368 GtkTextMark *mark;
10369 GtkTextIter ins, end_iter;
10371 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10373 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10374 mark = gtk_text_buffer_get_insert(buffer);
10375 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10376 end_iter = ins;
10377 if (gtk_text_iter_ends_line(&end_iter))
10378 gtk_text_iter_forward_char(&end_iter);
10379 else
10380 gtk_text_iter_forward_to_line_end(&end_iter);
10381 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10384 #define DO_ACTION(name, act) { \
10385 if(!strcmp(name, a_name)) { \
10386 return act; \
10389 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10391 const gchar *a_name = gtk_action_get_name(action);
10392 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10393 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10394 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10395 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10396 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10397 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10398 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10399 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10400 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10401 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10402 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10403 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10404 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10405 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10406 return -1;
10409 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10411 Compose *compose = (Compose *)data;
10412 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10413 ComposeCallAdvancedAction action = -1;
10415 action = compose_call_advanced_action_from_path(gaction);
10417 static struct {
10418 void (*do_action) (GtkTextView *text);
10419 } action_table[] = {
10420 {textview_move_beginning_of_line},
10421 {textview_move_forward_character},
10422 {textview_move_backward_character},
10423 {textview_move_forward_word},
10424 {textview_move_backward_word},
10425 {textview_move_end_of_line},
10426 {textview_move_next_line},
10427 {textview_move_previous_line},
10428 {textview_delete_forward_character},
10429 {textview_delete_backward_character},
10430 {textview_delete_forward_word},
10431 {textview_delete_backward_word},
10432 {textview_delete_line},
10433 {textview_delete_to_line_end}
10436 if (!gtkut_widget_has_focus(GTK_WIDGET(text))) return;
10438 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10439 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10440 if (action_table[action].do_action)
10441 action_table[action].do_action(text);
10442 else
10443 g_warning("Not implemented yet.");
10447 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10449 GtkAllocation allocation;
10450 GtkWidget *parent;
10451 gchar *str = NULL;
10453 if (GTK_IS_EDITABLE(widget)) {
10454 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10455 gtk_editable_set_position(GTK_EDITABLE(widget),
10456 strlen(str));
10457 g_free(str);
10458 if ((parent = gtk_widget_get_parent(widget))
10459 && (parent = gtk_widget_get_parent(parent))
10460 && (parent = gtk_widget_get_parent(parent))) {
10461 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10462 gtk_widget_get_allocation(widget, &allocation);
10463 gint y = allocation.y;
10464 gint height = allocation.height;
10465 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10466 (GTK_SCROLLED_WINDOW(parent));
10468 gfloat value = gtk_adjustment_get_value(shown);
10469 gfloat upper = gtk_adjustment_get_upper(shown);
10470 gfloat page_size = gtk_adjustment_get_page_size(shown);
10471 if (y < (int)value) {
10472 gtk_adjustment_set_value(shown, y - 1);
10474 if ((y + height) > ((int)value + (int)page_size)) {
10475 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10476 gtk_adjustment_set_value(shown,
10477 y + height - (int)page_size - 1);
10478 } else {
10479 gtk_adjustment_set_value(shown,
10480 (int)upper - (int)page_size - 1);
10487 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10488 compose->focused_editable = widget;
10490 #ifdef GENERIC_UMPC
10491 if (GTK_IS_TEXT_VIEW(widget)
10492 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10493 g_object_ref(compose->notebook);
10494 g_object_ref(compose->edit_vbox);
10495 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10496 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10497 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10498 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10499 g_object_unref(compose->notebook);
10500 g_object_unref(compose->edit_vbox);
10501 g_signal_handlers_block_by_func(G_OBJECT(widget),
10502 G_CALLBACK(compose_grab_focus_cb),
10503 compose);
10504 gtk_widget_grab_focus(widget);
10505 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10506 G_CALLBACK(compose_grab_focus_cb),
10507 compose);
10508 } else if (!GTK_IS_TEXT_VIEW(widget)
10509 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10510 g_object_ref(compose->notebook);
10511 g_object_ref(compose->edit_vbox);
10512 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10513 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10514 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10515 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10516 g_object_unref(compose->notebook);
10517 g_object_unref(compose->edit_vbox);
10518 g_signal_handlers_block_by_func(G_OBJECT(widget),
10519 G_CALLBACK(compose_grab_focus_cb),
10520 compose);
10521 gtk_widget_grab_focus(widget);
10522 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10523 G_CALLBACK(compose_grab_focus_cb),
10524 compose);
10526 #endif
10529 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10531 compose->modified = TRUE;
10532 // compose_beautify_paragraph(compose, NULL, TRUE);
10533 #ifndef GENERIC_UMPC
10534 compose_set_title(compose);
10535 #endif
10538 static void compose_wrap_cb(GtkAction *action, gpointer data)
10540 Compose *compose = (Compose *)data;
10541 compose_beautify_paragraph(compose, NULL, TRUE);
10544 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10546 Compose *compose = (Compose *)data;
10547 compose_wrap_all_full(compose, TRUE);
10550 static void compose_find_cb(GtkAction *action, gpointer data)
10552 Compose *compose = (Compose *)data;
10554 message_search_compose(compose);
10557 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10558 gpointer data)
10560 Compose *compose = (Compose *)data;
10561 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10562 if (compose->autowrap)
10563 compose_wrap_all_full(compose, TRUE);
10564 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10567 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10568 gpointer data)
10570 Compose *compose = (Compose *)data;
10571 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10574 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10576 Compose *compose = (Compose *)data;
10578 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10581 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10583 Compose *compose = (Compose *)data;
10585 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10588 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10590 g_free(compose->privacy_system);
10592 compose->privacy_system = g_strdup(account->default_privacy_system);
10593 compose_update_privacy_system_menu_item(compose, warn);
10596 #if !GTK_CHECK_VERSION(2, 24, 0)
10597 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10599 Compose *compose = (Compose *)data;
10601 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10602 gtk_widget_show(compose->ruler_hbox);
10603 prefs_common.show_ruler = TRUE;
10604 } else {
10605 gtk_widget_hide(compose->ruler_hbox);
10606 gtk_widget_queue_resize(compose->edit_vbox);
10607 prefs_common.show_ruler = FALSE;
10610 #endif
10612 static void compose_attach_drag_received_cb (GtkWidget *widget,
10613 GdkDragContext *context,
10614 gint x,
10615 gint y,
10616 GtkSelectionData *data,
10617 guint info,
10618 guint time,
10619 gpointer user_data)
10621 Compose *compose = (Compose *)user_data;
10622 GList *list, *tmp;
10623 GdkAtom type;
10625 type = gtk_selection_data_get_data_type(data);
10626 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
10627 #ifdef G_OS_WIN32
10628 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
10629 #endif
10630 ) && gtk_drag_get_source_widget(context) !=
10631 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10632 list = uri_list_extract_filenames(
10633 (const gchar *)gtk_selection_data_get_data(data));
10634 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10635 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10636 compose_attach_append
10637 (compose, (const gchar *)tmp->data,
10638 utf8_filename, NULL, NULL);
10639 g_free(utf8_filename);
10641 if (list) compose_changed_cb(NULL, compose);
10642 list_free_strings(list);
10643 g_list_free(list);
10644 } else if (gtk_drag_get_source_widget(context)
10645 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10646 /* comes from our summaryview */
10647 SummaryView * summaryview = NULL;
10648 GSList * list = NULL, *cur = NULL;
10650 if (mainwindow_get_mainwindow())
10651 summaryview = mainwindow_get_mainwindow()->summaryview;
10653 if (summaryview)
10654 list = summary_get_selected_msg_list(summaryview);
10656 for (cur = list; cur; cur = cur->next) {
10657 MsgInfo *msginfo = (MsgInfo *)cur->data;
10658 gchar *file = NULL;
10659 if (msginfo)
10660 file = procmsg_get_message_file_full(msginfo,
10661 TRUE, TRUE);
10662 if (file) {
10663 compose_attach_append(compose, (const gchar *)file,
10664 (const gchar *)file, "message/rfc822", NULL);
10665 g_free(file);
10668 g_slist_free(list);
10672 static gboolean compose_drag_drop(GtkWidget *widget,
10673 GdkDragContext *drag_context,
10674 gint x, gint y,
10675 guint time, gpointer user_data)
10677 /* not handling this signal makes compose_insert_drag_received_cb
10678 * called twice */
10679 return TRUE;
10682 static void compose_insert_drag_received_cb (GtkWidget *widget,
10683 GdkDragContext *drag_context,
10684 gint x,
10685 gint y,
10686 GtkSelectionData *data,
10687 guint info,
10688 guint time,
10689 gpointer user_data)
10691 Compose *compose = (Compose *)user_data;
10692 GList *list, *tmp;
10693 GdkAtom type;
10695 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10696 * does not work */
10697 type = gtk_selection_data_get_data_type(data);
10698 #ifndef G_OS_WIN32
10699 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
10700 #else
10701 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
10702 #endif
10703 AlertValue val = G_ALERTDEFAULT;
10704 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
10706 list = uri_list_extract_filenames(ddata);
10707 if (list == NULL && strstr(ddata, "://")) {
10708 /* Assume a list of no files, and data has ://, is a remote link */
10709 gchar *tmpdata = g_strstrip(g_strdup(ddata));
10710 gchar *tmpfile = get_tmp_file();
10711 str_write_to_file(tmpdata, tmpfile);
10712 g_free(tmpdata);
10713 compose_insert_file(compose, tmpfile);
10714 claws_unlink(tmpfile);
10715 g_free(tmpfile);
10716 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10717 compose_beautify_paragraph(compose, NULL, TRUE);
10718 return;
10720 switch (prefs_common.compose_dnd_mode) {
10721 case COMPOSE_DND_ASK:
10722 val = alertpanel_full(_("Insert or attach?"),
10723 _("Do you want to insert the contents of the file(s) "
10724 "into the message body, or attach it to the email?"),
10725 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
10726 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
10727 break;
10728 case COMPOSE_DND_INSERT:
10729 val = G_ALERTALTERNATE;
10730 break;
10731 case COMPOSE_DND_ATTACH:
10732 val = G_ALERTOTHER;
10733 break;
10734 default:
10735 /* unexpected case */
10736 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
10739 if (val & G_ALERTDISABLE) {
10740 val &= ~G_ALERTDISABLE;
10741 /* remember what action to perform by default, only if we don't click Cancel */
10742 if (val == G_ALERTALTERNATE)
10743 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
10744 else if (val == G_ALERTOTHER)
10745 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
10748 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
10749 gtk_drag_finish(drag_context, FALSE, FALSE, time);
10750 list_free_strings(list);
10751 g_list_free(list);
10752 return;
10753 } else if (val == G_ALERTOTHER) {
10754 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
10755 list_free_strings(list);
10756 g_list_free(list);
10757 return;
10760 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10761 compose_insert_file(compose, (const gchar *)tmp->data);
10763 list_free_strings(list);
10764 g_list_free(list);
10765 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10766 return;
10767 } else {
10768 return;
10770 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10773 static void compose_header_drag_received_cb (GtkWidget *widget,
10774 GdkDragContext *drag_context,
10775 gint x,
10776 gint y,
10777 GtkSelectionData *data,
10778 guint info,
10779 guint time,
10780 gpointer user_data)
10782 GtkEditable *entry = (GtkEditable *)user_data;
10783 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
10785 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
10786 * does not work */
10788 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
10789 gchar *decoded=g_new(gchar, strlen(email));
10790 int start = 0;
10792 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
10793 gtk_editable_delete_text(entry, 0, -1);
10794 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
10795 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10796 g_free(decoded);
10797 return;
10799 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10802 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
10804 Compose *compose = (Compose *)data;
10806 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10807 compose->return_receipt = TRUE;
10808 else
10809 compose->return_receipt = FALSE;
10812 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
10814 Compose *compose = (Compose *)data;
10816 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10817 compose->remove_references = TRUE;
10818 else
10819 compose->remove_references = FALSE;
10822 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
10823 ComposeHeaderEntry *headerentry)
10825 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
10826 return FALSE;
10829 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
10830 GdkEventKey *event,
10831 ComposeHeaderEntry *headerentry)
10833 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
10834 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
10835 !(event->state & GDK_MODIFIER_MASK) &&
10836 (event->keyval == GDK_KEY_BackSpace) &&
10837 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
10838 gtk_container_remove
10839 (GTK_CONTAINER(headerentry->compose->header_table),
10840 headerentry->combo);
10841 gtk_container_remove
10842 (GTK_CONTAINER(headerentry->compose->header_table),
10843 headerentry->entry);
10844 headerentry->compose->header_list =
10845 g_slist_remove(headerentry->compose->header_list,
10846 headerentry);
10847 g_free(headerentry);
10848 } else if (event->keyval == GDK_KEY_Tab) {
10849 if (headerentry->compose->header_last == headerentry) {
10850 /* Override default next focus, and give it to subject_entry
10851 * instead of notebook tabs
10853 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
10854 gtk_widget_grab_focus(headerentry->compose->subject_entry);
10855 return TRUE;
10858 return FALSE;
10861 static gboolean scroll_postpone(gpointer data)
10863 Compose *compose = (Compose *)data;
10865 cm_return_val_if_fail(!compose->batch, FALSE);
10867 GTK_EVENTS_FLUSH();
10868 compose_show_first_last_header(compose, FALSE);
10869 return FALSE;
10872 static void compose_headerentry_changed_cb(GtkWidget *entry,
10873 ComposeHeaderEntry *headerentry)
10875 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
10876 compose_create_header_entry(headerentry->compose);
10877 g_signal_handlers_disconnect_matched
10878 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
10879 0, 0, NULL, NULL, headerentry);
10881 if (!headerentry->compose->batch)
10882 g_timeout_add(0, scroll_postpone, headerentry->compose);
10886 static gboolean compose_defer_auto_save_draft(Compose *compose)
10888 compose->draft_timeout_tag = -1;
10889 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10890 return FALSE;
10893 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
10895 GtkAdjustment *vadj;
10897 cm_return_if_fail(compose);
10898 cm_return_if_fail(!compose->batch);
10899 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
10900 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
10901 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
10902 gtk_widget_get_parent(compose->header_table)));
10903 gtk_adjustment_set_value(vadj, (show_first ?
10904 gtk_adjustment_get_lower(vadj) :
10905 (gtk_adjustment_get_upper(vadj) -
10906 gtk_adjustment_get_page_size(vadj))));
10907 gtk_adjustment_changed(vadj);
10910 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
10911 const gchar *text, gint len, Compose *compose)
10913 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
10914 (G_OBJECT(compose->text), "paste_as_quotation"));
10915 GtkTextMark *mark;
10917 cm_return_if_fail(text != NULL);
10919 g_signal_handlers_block_by_func(G_OBJECT(buffer),
10920 G_CALLBACK(text_inserted),
10921 compose);
10922 if (paste_as_quotation) {
10923 gchar *new_text;
10924 const gchar *qmark;
10925 guint pos = 0;
10926 GtkTextIter start_iter;
10928 if (len < 0)
10929 len = strlen(text);
10931 new_text = g_strndup(text, len);
10933 qmark = compose_quote_char_from_context(compose);
10935 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10936 gtk_text_buffer_place_cursor(buffer, iter);
10938 pos = gtk_text_iter_get_offset(iter);
10940 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
10941 _("Quote format error at line %d."));
10942 quote_fmt_reset_vartable();
10943 g_free(new_text);
10944 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
10945 GINT_TO_POINTER(paste_as_quotation - 1));
10947 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10948 gtk_text_buffer_place_cursor(buffer, iter);
10949 gtk_text_buffer_delete_mark(buffer, mark);
10951 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
10952 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
10953 compose_beautify_paragraph(compose, &start_iter, FALSE);
10954 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
10955 gtk_text_buffer_delete_mark(buffer, mark);
10956 } else {
10957 if (strcmp(text, "\n") || compose->automatic_break
10958 || gtk_text_iter_starts_line(iter)) {
10959 GtkTextIter before_ins;
10960 gtk_text_buffer_insert(buffer, iter, text, len);
10961 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
10962 before_ins = *iter;
10963 gtk_text_iter_backward_chars(&before_ins, len);
10964 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
10966 } else {
10967 /* check if the preceding is just whitespace or quote */
10968 GtkTextIter start_line;
10969 gchar *tmp = NULL, *quote = NULL;
10970 gint quote_len = 0, is_normal = 0;
10971 start_line = *iter;
10972 gtk_text_iter_set_line_offset(&start_line, 0);
10973 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
10974 g_strstrip(tmp);
10976 if (*tmp == '\0') {
10977 is_normal = 1;
10978 } else {
10979 quote = compose_get_quote_str(buffer, &start_line, &quote_len);
10980 if (quote)
10981 is_normal = 1;
10982 g_free(quote);
10984 g_free(tmp);
10986 if (is_normal) {
10987 gtk_text_buffer_insert(buffer, iter, text, len);
10988 } else {
10989 gtk_text_buffer_insert_with_tags_by_name(buffer,
10990 iter, text, len, "no_join", NULL);
10995 if (!paste_as_quotation) {
10996 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10997 compose_beautify_paragraph(compose, iter, FALSE);
10998 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10999 gtk_text_buffer_delete_mark(buffer, mark);
11002 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11003 G_CALLBACK(text_inserted),
11004 compose);
11005 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11007 if (prefs_common.autosave &&
11008 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11009 compose->draft_timeout_tag != -2 /* disabled while loading */)
11010 compose->draft_timeout_tag = g_timeout_add
11011 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11014 #if USE_ENCHANT
11015 static void compose_check_all(GtkAction *action, gpointer data)
11017 Compose *compose = (Compose *)data;
11018 if (!compose->gtkaspell)
11019 return;
11021 if (gtkut_widget_has_focus(compose->subject_entry))
11022 claws_spell_entry_check_all(
11023 CLAWS_SPELL_ENTRY(compose->subject_entry));
11024 else
11025 gtkaspell_check_all(compose->gtkaspell);
11028 static void compose_highlight_all(GtkAction *action, gpointer data)
11030 Compose *compose = (Compose *)data;
11031 if (compose->gtkaspell) {
11032 claws_spell_entry_recheck_all(
11033 CLAWS_SPELL_ENTRY(compose->subject_entry));
11034 gtkaspell_highlight_all(compose->gtkaspell);
11038 static void compose_check_backwards(GtkAction *action, gpointer data)
11040 Compose *compose = (Compose *)data;
11041 if (!compose->gtkaspell) {
11042 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11043 return;
11046 if (gtkut_widget_has_focus(compose->subject_entry))
11047 claws_spell_entry_check_backwards(
11048 CLAWS_SPELL_ENTRY(compose->subject_entry));
11049 else
11050 gtkaspell_check_backwards(compose->gtkaspell);
11053 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11055 Compose *compose = (Compose *)data;
11056 if (!compose->gtkaspell) {
11057 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11058 return;
11061 if (gtkut_widget_has_focus(compose->subject_entry))
11062 claws_spell_entry_check_forwards_go(
11063 CLAWS_SPELL_ENTRY(compose->subject_entry));
11064 else
11065 gtkaspell_check_forwards_go(compose->gtkaspell);
11067 #endif
11070 *\brief Guess originating forward account from MsgInfo and several
11071 * "common preference" settings. Return NULL if no guess.
11073 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11075 PrefsAccount *account = NULL;
11077 cm_return_val_if_fail(msginfo, NULL);
11078 cm_return_val_if_fail(msginfo->folder, NULL);
11079 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11081 if (msginfo->folder->prefs->enable_default_account)
11082 account = account_find_from_id(msginfo->folder->prefs->default_account);
11084 if (!account)
11085 account = msginfo->folder->folder->account;
11087 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11088 gchar *to;
11089 Xstrdup_a(to, msginfo->to, return NULL);
11090 extract_address(to);
11091 account = account_find_from_address(to, FALSE);
11094 if (!account && prefs_common.forward_account_autosel) {
11095 gchar cc[BUFFSIZE];
11096 if (!procheader_get_header_from_msginfo
11097 (msginfo, cc,sizeof cc , "Cc:")) {
11098 gchar *buf = cc + strlen("Cc:");
11099 extract_address(buf);
11100 account = account_find_from_address(buf, FALSE);
11104 if (!account && prefs_common.forward_account_autosel) {
11105 gchar deliveredto[BUFFSIZE];
11106 if (!procheader_get_header_from_msginfo
11107 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11108 gchar *buf = deliveredto + strlen("Delivered-To:");
11109 extract_address(buf);
11110 account = account_find_from_address(buf, FALSE);
11114 return account;
11117 gboolean compose_close(Compose *compose)
11119 gint x, y;
11121 if (!g_mutex_trylock(compose->mutex)) {
11122 /* we have to wait for the (possibly deferred by auto-save)
11123 * drafting to be done, before destroying the compose under
11124 * it. */
11125 debug_print("waiting for drafting to finish...\n");
11126 compose_allow_user_actions(compose, FALSE);
11127 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11128 return FALSE;
11130 cm_return_val_if_fail(compose, FALSE);
11131 gtkut_widget_get_uposition(compose->window, &x, &y);
11132 if (!compose->batch) {
11133 prefs_common.compose_x = x;
11134 prefs_common.compose_y = y;
11136 g_mutex_unlock(compose->mutex);
11137 compose_destroy(compose);
11138 return FALSE;
11142 * Add entry field for each address in list.
11143 * \param compose E-Mail composition object.
11144 * \param listAddress List of (formatted) E-Mail addresses.
11146 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11147 GList *node;
11148 gchar *addr;
11149 node = listAddress;
11150 while( node ) {
11151 addr = ( gchar * ) node->data;
11152 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11153 node = g_list_next( node );
11157 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11158 guint action, gboolean opening_multiple)
11160 gchar *body = NULL;
11161 GSList *new_msglist = NULL;
11162 MsgInfo *tmp_msginfo = NULL;
11163 gboolean originally_enc = FALSE;
11164 gboolean originally_sig = FALSE;
11165 Compose *compose = NULL;
11166 gchar *s_system = NULL;
11168 cm_return_if_fail(msgview != NULL);
11170 cm_return_if_fail(msginfo_list != NULL);
11172 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11173 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11174 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11176 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11177 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11178 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11179 orig_msginfo, mimeinfo);
11180 if (tmp_msginfo != NULL) {
11181 new_msglist = g_slist_append(NULL, tmp_msginfo);
11183 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11184 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11185 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11187 tmp_msginfo->folder = orig_msginfo->folder;
11188 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11189 if (orig_msginfo->tags) {
11190 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11191 tmp_msginfo->folder->tags_dirty = TRUE;
11197 if (!opening_multiple)
11198 body = messageview_get_selection(msgview);
11200 if (new_msglist) {
11201 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11202 procmsg_msginfo_free(tmp_msginfo);
11203 g_slist_free(new_msglist);
11204 } else
11205 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11207 if (compose && originally_enc) {
11208 compose_force_encryption(compose, compose->account, FALSE, s_system);
11211 if (compose && originally_sig && compose->account->default_sign_reply) {
11212 compose_force_signing(compose, compose->account, s_system);
11214 g_free(s_system);
11215 g_free(body);
11216 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11219 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11220 guint action)
11222 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11223 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11224 GSList *cur = msginfo_list;
11225 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11226 "messages. Opening the windows "
11227 "could take some time. Do you "
11228 "want to continue?"),
11229 g_slist_length(msginfo_list));
11230 if (g_slist_length(msginfo_list) > 9
11231 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11232 != G_ALERTALTERNATE) {
11233 g_free(msg);
11234 return;
11236 g_free(msg);
11237 /* We'll open multiple compose windows */
11238 /* let the WM place the next windows */
11239 compose_force_window_origin = FALSE;
11240 for (; cur; cur = cur->next) {
11241 GSList tmplist;
11242 tmplist.data = cur->data;
11243 tmplist.next = NULL;
11244 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11246 compose_force_window_origin = TRUE;
11247 } else {
11248 /* forwarding multiple mails as attachments is done via a
11249 * single compose window */
11250 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11254 void compose_check_for_email_account(Compose *compose)
11256 PrefsAccount *ac = NULL, *curr = NULL;
11257 GList *list;
11259 if (!compose)
11260 return;
11262 if (compose->account && compose->account->protocol == A_NNTP) {
11263 ac = account_get_cur_account();
11264 if (ac->protocol == A_NNTP) {
11265 list = account_get_list();
11267 for( ; list != NULL ; list = g_list_next(list)) {
11268 curr = (PrefsAccount *) list->data;
11269 if (curr->protocol != A_NNTP) {
11270 ac = curr;
11271 break;
11275 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11276 ac->account_id);
11280 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11281 const gchar *address)
11283 GSList *msginfo_list = NULL;
11284 gchar *body = messageview_get_selection(msgview);
11285 Compose *compose;
11287 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11289 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11290 compose_check_for_email_account(compose);
11291 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11292 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11293 compose_reply_set_subject(compose, msginfo);
11295 g_free(body);
11296 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11299 void compose_set_position(Compose *compose, gint pos)
11301 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11303 gtkut_text_view_set_position(text, pos);
11306 gboolean compose_search_string(Compose *compose,
11307 const gchar *str, gboolean case_sens)
11309 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11311 return gtkut_text_view_search_string(text, str, case_sens);
11314 gboolean compose_search_string_backward(Compose *compose,
11315 const gchar *str, gboolean case_sens)
11317 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11319 return gtkut_text_view_search_string_backward(text, str, case_sens);
11322 /* allocate a msginfo structure and populate its data from a compose data structure */
11323 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11325 MsgInfo *newmsginfo;
11326 GSList *list;
11327 gchar buf[BUFFSIZE];
11329 cm_return_val_if_fail( compose != NULL, NULL );
11331 newmsginfo = procmsg_msginfo_new();
11333 /* date is now */
11334 get_rfc822_date(buf, sizeof(buf));
11335 newmsginfo->date = g_strdup(buf);
11337 /* from */
11338 if (compose->from_name) {
11339 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11340 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11343 /* subject */
11344 if (compose->subject_entry)
11345 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11347 /* to, cc, reply-to, newsgroups */
11348 for (list = compose->header_list; list; list = list->next) {
11349 gchar *header = gtk_editable_get_chars(
11350 GTK_EDITABLE(
11351 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11352 gchar *entry = gtk_editable_get_chars(
11353 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11355 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11356 if ( newmsginfo->to == NULL ) {
11357 newmsginfo->to = g_strdup(entry);
11358 } else if (entry && *entry) {
11359 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11360 g_free(newmsginfo->to);
11361 newmsginfo->to = tmp;
11363 } else
11364 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11365 if ( newmsginfo->cc == NULL ) {
11366 newmsginfo->cc = g_strdup(entry);
11367 } else if (entry && *entry) {
11368 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11369 g_free(newmsginfo->cc);
11370 newmsginfo->cc = tmp;
11372 } else
11373 if ( strcasecmp(header,
11374 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11375 if ( newmsginfo->newsgroups == NULL ) {
11376 newmsginfo->newsgroups = g_strdup(entry);
11377 } else if (entry && *entry) {
11378 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11379 g_free(newmsginfo->newsgroups);
11380 newmsginfo->newsgroups = tmp;
11384 g_free(header);
11385 g_free(entry);
11388 /* other data is unset */
11390 return newmsginfo;
11393 #ifdef USE_ENCHANT
11394 /* update compose's dictionaries from folder dict settings */
11395 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11396 FolderItem *folder_item)
11398 cm_return_if_fail(compose != NULL);
11400 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11401 FolderItemPrefs *prefs = folder_item->prefs;
11403 if (prefs->enable_default_dictionary)
11404 gtkaspell_change_dict(compose->gtkaspell,
11405 prefs->default_dictionary, FALSE);
11406 if (folder_item->prefs->enable_default_alt_dictionary)
11407 gtkaspell_change_alt_dict(compose->gtkaspell,
11408 prefs->default_alt_dictionary);
11409 if (prefs->enable_default_dictionary
11410 || prefs->enable_default_alt_dictionary)
11411 compose_spell_menu_changed(compose);
11414 #endif
11417 * End of Source.