2.10.0 unleashed
[claws.git] / src / compose.c
blobb8b1d2a062eac997619e0f68990c2279205577c0
1 /*
2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2007 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 2 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, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
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/gtkmain.h>
34 #include <gtk/gtkmenu.h>
35 #include <gtk/gtkmenuitem.h>
36 #include <gtk/gtkitemfactory.h>
37 #include <gtk/gtkcheckmenuitem.h>
38 #include <gtk/gtkoptionmenu.h>
39 #include <gtk/gtkwidget.h>
40 #include <gtk/gtkvpaned.h>
41 #include <gtk/gtkentry.h>
42 #include <gtk/gtkeditable.h>
43 #include <gtk/gtkwindow.h>
44 #include <gtk/gtksignal.h>
45 #include <gtk/gtkvbox.h>
46 #include <gtk/gtkcontainer.h>
47 #include <gtk/gtkhandlebox.h>
48 #include <gtk/gtktoolbar.h>
49 #include <gtk/gtktable.h>
50 #include <gtk/gtkhbox.h>
51 #include <gtk/gtklabel.h>
52 #include <gtk/gtkscrolledwindow.h>
53 #include <gtk/gtktreeview.h>
54 #include <gtk/gtkliststore.h>
55 #include <gtk/gtktreeselection.h>
56 #include <gtk/gtktreemodel.h>
58 #include <gtk/gtkdnd.h>
59 #include <gtk/gtkclipboard.h>
60 #include <pango/pango-break.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <ctype.h>
65 #include <sys/types.h>
66 #include <sys/stat.h>
67 #include <unistd.h>
68 #include <time.h>
69 #include <stdlib.h>
70 #if HAVE_SYS_WAIT_H
71 # include <sys/wait.h>
72 #endif
73 #include <signal.h>
74 #include <errno.h>
75 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
76 #include <libgen.h>
77 #endif
79 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
80 # include <wchar.h>
81 # include <wctype.h>
82 #endif
84 #include "claws.h"
85 #include "main.h"
86 #include "mainwindow.h"
87 #include "compose.h"
88 #include "addressbook.h"
89 #include "folderview.h"
90 #include "procmsg.h"
91 #include "menu.h"
92 #include "stock_pixmap.h"
93 #include "send_message.h"
94 #include "imap.h"
95 #include "news.h"
96 #include "customheader.h"
97 #include "prefs_common.h"
98 #include "prefs_account.h"
99 #include "action.h"
100 #include "account.h"
101 #include "filesel.h"
102 #include "procheader.h"
103 #include "procmime.h"
104 #include "statusbar.h"
105 #include "about.h"
106 #include "base64.h"
107 #include "quoted-printable.h"
108 #include "codeconv.h"
109 #include "utils.h"
110 #include "gtkutils.h"
111 #include "socket.h"
112 #include "alertpanel.h"
113 #include "manage_window.h"
114 #include "gtkshruler.h"
115 #include "folder.h"
116 #include "addr_compl.h"
117 #include "quote_fmt.h"
118 #include "undo.h"
119 #include "foldersel.h"
120 #include "toolbar.h"
121 #include "inc.h"
122 #include "message_search.h"
123 #include "combobox.h"
124 #include "hooks.h"
125 #include "privacy.h"
127 enum
129 COL_MIMETYPE = 0,
130 COL_SIZE = 1,
131 COL_NAME = 2,
132 COL_DATA = 3,
133 COL_AUTODATA = 4,
134 N_COL_COLUMNS
137 #define N_ATTACH_COLS (N_COL_COLUMNS)
139 typedef enum
141 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
142 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
143 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
144 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
145 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
146 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
147 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
148 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
149 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
150 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
151 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
152 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
153 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
154 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
155 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
156 } ComposeCallAdvancedAction;
158 typedef enum
160 PRIORITY_HIGHEST = 1,
161 PRIORITY_HIGH,
162 PRIORITY_NORMAL,
163 PRIORITY_LOW,
164 PRIORITY_LOWEST
165 } PriorityLevel;
167 typedef enum
169 COMPOSE_INSERT_SUCCESS,
170 COMPOSE_INSERT_READ_ERROR,
171 COMPOSE_INSERT_INVALID_CHARACTER,
172 COMPOSE_INSERT_NO_FILE
173 } ComposeInsertResult;
175 typedef enum
177 COMPOSE_WRITE_FOR_SEND,
178 COMPOSE_WRITE_FOR_STORE
179 } ComposeWriteType;
181 typedef enum
183 COMPOSE_QUOTE_FORCED,
184 COMPOSE_QUOTE_CHECK,
185 COMPOSE_QUOTE_SKIP
186 } ComposeQuoteMode;
188 #define B64_LINE_SIZE 57
189 #define B64_BUFFSIZE 77
191 #define MAX_REFERENCES_LEN 999
193 static GList *compose_list = NULL;
195 static Compose *compose_generic_new (PrefsAccount *account,
196 const gchar *to,
197 FolderItem *item,
198 GPtrArray *attach_files,
199 GList *listAddress );
201 static Compose *compose_create (PrefsAccount *account,
202 FolderItem *item,
203 ComposeMode mode,
204 gboolean batch);
206 static void compose_entry_mark_default_to (Compose *compose,
207 const gchar *address);
208 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
209 ComposeQuoteMode quote_mode,
210 gboolean to_all,
211 gboolean to_sender,
212 const gchar *body);
213 static Compose *compose_forward_multiple (PrefsAccount *account,
214 GSList *msginfo_list);
215 static Compose *compose_reply (MsgInfo *msginfo,
216 ComposeQuoteMode quote_mode,
217 gboolean to_all,
218 gboolean to_ml,
219 gboolean to_sender,
220 const gchar *body);
221 static Compose *compose_reply_mode (ComposeMode mode,
222 GSList *msginfo_list,
223 gchar *body);
224 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
225 static void compose_update_privacy_systems_menu(Compose *compose);
227 static GtkWidget *compose_account_option_menu_create
228 (Compose *compose);
229 static void compose_set_out_encoding (Compose *compose);
230 static void compose_set_template_menu (Compose *compose);
231 static void compose_template_apply (Compose *compose,
232 Template *tmpl,
233 gboolean replace);
234 static void compose_destroy (Compose *compose);
236 static void compose_entries_set (Compose *compose,
237 const gchar *mailto);
238 static gint compose_parse_header (Compose *compose,
239 MsgInfo *msginfo);
240 static gchar *compose_parse_references (const gchar *ref,
241 const gchar *msgid);
243 static gchar *compose_quote_fmt (Compose *compose,
244 MsgInfo *msginfo,
245 const gchar *fmt,
246 const gchar *qmark,
247 const gchar *body,
248 gboolean rewrap,
249 gboolean need_unescape,
250 const gchar *err_msg);
252 static void compose_reply_set_entry (Compose *compose,
253 MsgInfo *msginfo,
254 gboolean to_all,
255 gboolean to_ml,
256 gboolean to_sender,
257 gboolean
258 followup_and_reply_to);
259 static void compose_reedit_set_entry (Compose *compose,
260 MsgInfo *msginfo);
262 static void compose_insert_sig (Compose *compose,
263 gboolean replace);
264 static gchar *compose_get_signature_str (Compose *compose);
265 static ComposeInsertResult compose_insert_file (Compose *compose,
266 const gchar *file);
268 static gboolean compose_attach_append (Compose *compose,
269 const gchar *file,
270 const gchar *type,
271 const gchar *content_type);
272 static void compose_attach_parts (Compose *compose,
273 MsgInfo *msginfo);
275 static void compose_beautify_paragraph (Compose *compose,
276 GtkTextIter *par_iter,
277 gboolean force);
278 static void compose_wrap_all (Compose *compose);
279 static void compose_wrap_all_full (Compose *compose,
280 gboolean autowrap);
282 static void compose_set_title (Compose *compose);
283 static void compose_select_account (Compose *compose,
284 PrefsAccount *account,
285 gboolean init);
287 static PrefsAccount *compose_current_mail_account(void);
288 /* static gint compose_send (Compose *compose); */
289 static gboolean compose_check_for_valid_recipient
290 (Compose *compose);
291 static gboolean compose_check_entries (Compose *compose,
292 gboolean check_everything);
293 static gint compose_write_to_file (Compose *compose,
294 FILE *fp,
295 gint action,
296 gboolean attach_parts);
297 static gint compose_write_body_to_file (Compose *compose,
298 const gchar *file);
299 static gint compose_remove_reedit_target (Compose *compose,
300 gboolean force);
301 static void compose_remove_draft (Compose *compose);
302 static gint compose_queue_sub (Compose *compose,
303 gint *msgnum,
304 FolderItem **item,
305 gchar **msgpath,
306 gboolean check_subject,
307 gboolean remove_reedit_target);
308 static void compose_add_attachments (Compose *compose,
309 MimeInfo *parent);
310 static gchar *compose_get_header (Compose *compose);
312 static void compose_convert_header (Compose *compose,
313 gchar *dest,
314 gint len,
315 gchar *src,
316 gint header_len,
317 gboolean addr_field);
319 static void compose_attach_info_free (AttachInfo *ainfo);
320 static void compose_attach_remove_selected (Compose *compose);
322 static void compose_attach_property (Compose *compose);
323 static void compose_attach_property_create (gboolean *cancelled);
324 static void attach_property_ok (GtkWidget *widget,
325 gboolean *cancelled);
326 static void attach_property_cancel (GtkWidget *widget,
327 gboolean *cancelled);
328 static gint attach_property_delete_event (GtkWidget *widget,
329 GdkEventAny *event,
330 gboolean *cancelled);
331 static gboolean attach_property_key_pressed (GtkWidget *widget,
332 GdkEventKey *event,
333 gboolean *cancelled);
335 static void compose_exec_ext_editor (Compose *compose);
336 #ifdef G_OS_UNIX
337 static gint compose_exec_ext_editor_real (const gchar *file);
338 static gboolean compose_ext_editor_kill (Compose *compose);
339 static gboolean compose_input_cb (GIOChannel *source,
340 GIOCondition condition,
341 gpointer data);
342 static void compose_set_ext_editor_sensitive (Compose *compose,
343 gboolean sensitive);
344 #endif /* G_OS_UNIX */
346 static void compose_undo_state_changed (UndoMain *undostruct,
347 gint undo_state,
348 gint redo_state,
349 gpointer data);
351 static void compose_create_header_entry (Compose *compose);
352 static void compose_add_header_entry (Compose *compose, const gchar *header, gchar *text);
353 static void compose_remove_header_entries(Compose *compose);
355 static void compose_update_priority_menu_item(Compose * compose);
356 #if USE_ASPELL
357 static void compose_spell_menu_changed (void *data);
358 #endif
359 static void compose_add_field_list ( Compose *compose,
360 GList *listAddress );
362 /* callback functions */
364 static gboolean compose_edit_size_alloc (GtkEditable *widget,
365 GtkAllocation *allocation,
366 GtkSHRuler *shruler);
367 static void account_activated (GtkComboBox *optmenu,
368 gpointer data);
369 static void attach_selected (GtkTreeView *tree_view,
370 GtkTreePath *tree_path,
371 GtkTreeViewColumn *column,
372 Compose *compose);
373 static gboolean attach_button_pressed (GtkWidget *widget,
374 GdkEventButton *event,
375 gpointer data);
376 static gboolean attach_key_pressed (GtkWidget *widget,
377 GdkEventKey *event,
378 gpointer data);
379 static void compose_send_cb (gpointer data,
380 guint action,
381 GtkWidget *widget);
382 static void compose_send_later_cb (gpointer data,
383 guint action,
384 GtkWidget *widget);
386 static void compose_draft_cb (gpointer data,
387 guint action,
388 GtkWidget *widget);
390 static void compose_attach_cb (gpointer data,
391 guint action,
392 GtkWidget *widget);
393 static void compose_insert_file_cb (gpointer data,
394 guint action,
395 GtkWidget *widget);
396 static void compose_insert_sig_cb (gpointer data,
397 guint action,
398 GtkWidget *widget);
400 static void compose_close_cb (gpointer data,
401 guint action,
402 GtkWidget *widget);
404 static void compose_set_encoding_cb (gpointer data,
405 guint action,
406 GtkWidget *widget);
408 static void compose_address_cb (gpointer data,
409 guint action,
410 GtkWidget *widget);
411 static void compose_template_activate_cb(GtkWidget *widget,
412 gpointer data);
414 static void compose_ext_editor_cb (gpointer data,
415 guint action,
416 GtkWidget *widget);
418 static gint compose_delete_cb (GtkWidget *widget,
419 GdkEventAny *event,
420 gpointer data);
422 static void compose_undo_cb (Compose *compose);
423 static void compose_redo_cb (Compose *compose);
424 static void compose_cut_cb (Compose *compose);
425 static void compose_copy_cb (Compose *compose);
426 static void compose_paste_cb (Compose *compose);
427 static void compose_paste_as_quote_cb (Compose *compose);
428 static void compose_paste_no_wrap_cb (Compose *compose);
429 static void compose_paste_wrap_cb (Compose *compose);
430 static void compose_allsel_cb (Compose *compose);
432 static void compose_advanced_action_cb (Compose *compose,
433 ComposeCallAdvancedAction action);
435 static void compose_grab_focus_cb (GtkWidget *widget,
436 Compose *compose);
438 static void compose_changed_cb (GtkTextBuffer *textbuf,
439 Compose *compose);
441 static void compose_wrap_cb (gpointer data,
442 guint action,
443 GtkWidget *widget);
444 static void compose_find_cb (gpointer data,
445 guint action,
446 GtkWidget *widget);
447 static void compose_toggle_autowrap_cb (gpointer data,
448 guint action,
449 GtkWidget *widget);
451 static void compose_toggle_ruler_cb (gpointer data,
452 guint action,
453 GtkWidget *widget);
454 static void compose_toggle_sign_cb (gpointer data,
455 guint action,
456 GtkWidget *widget);
457 static void compose_toggle_encrypt_cb (gpointer data,
458 guint action,
459 GtkWidget *widget);
460 static void compose_set_privacy_system_cb(GtkWidget *widget,
461 gpointer data);
462 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
463 static void activate_privacy_system (Compose *compose,
464 PrefsAccount *account,
465 gboolean warn);
466 static void compose_use_signing(Compose *compose, gboolean use_signing);
467 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
468 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
469 GtkWidget *widget);
470 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
471 GtkWidget *widget);
472 static void compose_set_priority_cb (gpointer data,
473 guint action,
474 GtkWidget *widget);
475 static void compose_reply_change_mode (gpointer data,
476 ComposeMode action,
477 GtkWidget *widget);
479 static void compose_attach_drag_received_cb (GtkWidget *widget,
480 GdkDragContext *drag_context,
481 gint x,
482 gint y,
483 GtkSelectionData *data,
484 guint info,
485 guint time,
486 gpointer user_data);
487 static void compose_insert_drag_received_cb (GtkWidget *widget,
488 GdkDragContext *drag_context,
489 gint x,
490 gint y,
491 GtkSelectionData *data,
492 guint info,
493 guint time,
494 gpointer user_data);
495 static void compose_header_drag_received_cb (GtkWidget *widget,
496 GdkDragContext *drag_context,
497 gint x,
498 gint y,
499 GtkSelectionData *data,
500 guint info,
501 guint time,
502 gpointer user_data);
504 static gboolean compose_drag_drop (GtkWidget *widget,
505 GdkDragContext *drag_context,
506 gint x, gint y,
507 guint time, gpointer user_data);
509 static void text_inserted (GtkTextBuffer *buffer,
510 GtkTextIter *iter,
511 const gchar *text,
512 gint len,
513 Compose *compose);
514 static Compose *compose_generic_reply(MsgInfo *msginfo,
515 ComposeQuoteMode quote_mode,
516 gboolean to_all,
517 gboolean to_ml,
518 gboolean to_sender,
519 gboolean followup_and_reply_to,
520 const gchar *body);
522 static gboolean compose_headerentry_changed_cb (GtkWidget *entry,
523 ComposeHeaderEntry *headerentry);
524 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
525 GdkEventKey *event,
526 ComposeHeaderEntry *headerentry);
528 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
530 static void compose_allow_user_actions (Compose *compose, gboolean allow);
532 #if USE_ASPELL
533 static void compose_check_all (Compose *compose);
534 static void compose_highlight_all (Compose *compose);
535 static void compose_check_backwards (Compose *compose);
536 static void compose_check_forwards_go (Compose *compose);
537 #endif
539 static gint compose_defer_auto_save_draft (Compose *compose);
540 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
542 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
544 #ifdef USE_ASPELL
545 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
546 FolderItem *folder_item);
547 #endif
549 static GtkItemFactoryEntry compose_popup_entries[] =
551 {N_("/_Add..."), NULL, compose_attach_cb, 0, NULL},
552 {N_("/_Remove"), NULL, compose_attach_remove_selected, 0, NULL},
553 {"/---", NULL, NULL, 0, "<Separator>"},
554 {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
557 static GtkItemFactoryEntry compose_entries[] =
559 {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
560 {N_("/_Message/S_end"), "<control>Return",
561 compose_send_cb, 0, NULL},
562 {N_("/_Message/Send _later"), "<shift><control>S",
563 compose_send_later_cb, 0, NULL},
564 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
565 {N_("/_Message/_Attach file"), "<control>M", compose_attach_cb, 0, NULL},
566 {N_("/_Message/_Insert file"), "<control>I", compose_insert_file_cb, 0, NULL},
567 {N_("/_Message/Insert si_gnature"), "<control>G", compose_insert_sig_cb, 0, NULL},
568 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
569 {N_("/_Message/_Save"),
570 "<control>S", compose_draft_cb, COMPOSE_KEEP_EDITING, NULL},
571 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
572 {N_("/_Message/_Close"), "<control>W", compose_close_cb, 0, NULL},
574 {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
575 {N_("/_Edit/_Undo"), "<control>Z", compose_undo_cb, 0, NULL},
576 {N_("/_Edit/_Redo"), "<control>Y", compose_redo_cb, 0, NULL},
577 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
578 {N_("/_Edit/Cu_t"), "<control>X", compose_cut_cb, 0, NULL},
579 {N_("/_Edit/_Copy"), "<control>C", compose_copy_cb, 0, NULL},
580 {N_("/_Edit/_Paste"), "<control>V", compose_paste_cb, 0, NULL},
581 {N_("/_Edit/Special paste"), NULL, NULL, 0, "<Branch>"},
582 {N_("/_Edit/Special paste/as _quotation"),
583 NULL, compose_paste_as_quote_cb, 0, NULL},
584 {N_("/_Edit/Special paste/_wrapped"),
585 NULL, compose_paste_wrap_cb, 0, NULL},
586 {N_("/_Edit/Special paste/_unwrapped"),
587 NULL, compose_paste_no_wrap_cb, 0, NULL},
588 {N_("/_Edit/Select _all"), "<control>A", compose_allsel_cb, 0, NULL},
589 {N_("/_Edit/A_dvanced"), NULL, NULL, 0, "<Branch>"},
590 {N_("/_Edit/A_dvanced/Move a character backward"),
591 "<shift><control>B",
592 compose_advanced_action_cb,
593 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
594 NULL},
595 {N_("/_Edit/A_dvanced/Move a character forward"),
596 "<shift><control>F",
597 compose_advanced_action_cb,
598 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
599 NULL},
600 {N_("/_Edit/A_dvanced/Move a word backward"),
601 NULL, /* "<alt>B" */
602 compose_advanced_action_cb,
603 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
604 NULL},
605 {N_("/_Edit/A_dvanced/Move a word forward"),
606 NULL, /* "<alt>F" */
607 compose_advanced_action_cb,
608 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
609 NULL},
610 {N_("/_Edit/A_dvanced/Move to beginning of line"),
611 NULL, /* "<control>A" */
612 compose_advanced_action_cb,
613 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
614 NULL},
615 {N_("/_Edit/A_dvanced/Move to end of line"),
616 "<control>E",
617 compose_advanced_action_cb,
618 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
619 NULL},
620 {N_("/_Edit/A_dvanced/Move to previous line"),
621 "<control>P",
622 compose_advanced_action_cb,
623 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
624 NULL},
625 {N_("/_Edit/A_dvanced/Move to next line"),
626 "<control>N",
627 compose_advanced_action_cb,
628 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
629 NULL},
630 {N_("/_Edit/A_dvanced/Delete a character backward"),
631 "<control>H",
632 compose_advanced_action_cb,
633 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
634 NULL},
635 {N_("/_Edit/A_dvanced/Delete a character forward"),
636 "<control>D",
637 compose_advanced_action_cb,
638 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
639 NULL},
640 {N_("/_Edit/A_dvanced/Delete a word backward"),
641 NULL, /* "<control>W" */
642 compose_advanced_action_cb,
643 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
644 NULL},
645 {N_("/_Edit/A_dvanced/Delete a word forward"),
646 NULL, /* "<alt>D", */
647 compose_advanced_action_cb,
648 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
649 NULL},
650 {N_("/_Edit/A_dvanced/Delete line"),
651 "<control>U",
652 compose_advanced_action_cb,
653 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
654 NULL},
655 {N_("/_Edit/A_dvanced/Delete entire line"),
656 NULL,
657 compose_advanced_action_cb,
658 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N,
659 NULL},
660 {N_("/_Edit/A_dvanced/Delete to end of line"),
661 "<control>K",
662 compose_advanced_action_cb,
663 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END,
664 NULL},
665 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
666 {N_("/_Edit/_Find"),
667 "<control>F", compose_find_cb, 0, NULL},
668 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
669 {N_("/_Edit/_Wrap current paragraph"),
670 "<control>L", compose_wrap_cb, 0, NULL},
671 {N_("/_Edit/Wrap all long _lines"),
672 "<control><alt>L", compose_wrap_cb, 1, NULL},
673 {N_("/_Edit/Aut_o wrapping"), "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
674 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
675 {N_("/_Edit/Edit with e_xternal editor"),
676 "<shift><control>X", compose_ext_editor_cb, 0, NULL},
677 #if USE_ASPELL
678 {N_("/_Spelling"), NULL, NULL, 0, "<Branch>"},
679 {N_("/_Spelling/_Check all or check selection"),
680 NULL, compose_check_all, 0, NULL},
681 {N_("/_Spelling/_Highlight all misspelled words"),
682 NULL, compose_highlight_all, 0, NULL},
683 {N_("/_Spelling/Check _backwards misspelled word"),
684 NULL, compose_check_backwards , 0, NULL},
685 {N_("/_Spelling/_Forward to next misspelled word"),
686 NULL, compose_check_forwards_go, 0, NULL},
687 {N_("/_Spelling/---"), NULL, NULL, 0, "<Separator>"},
688 {N_("/_Spelling/Options"),
689 NULL, NULL, 0, "<Branch>"},
690 #endif
691 {N_("/_Options"), NULL, NULL, 0, "<Branch>"},
692 {N_("/_Options/Reply _mode"), NULL, NULL, 0, "<Branch>"},
693 {N_("/_Options/Reply _mode/_Normal"), NULL, compose_reply_change_mode, COMPOSE_REPLY, "<RadioItem>"},
694 {N_("/_Options/Reply _mode/_All"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_ALL, "/Options/Reply mode/Normal"},
695 {N_("/_Options/Reply _mode/_Sender"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_SENDER, "/Options/Reply mode/Normal"},
696 {N_("/_Options/Reply _mode/_Mailing-list"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_LIST, "/Options/Reply mode/Normal"},
697 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
698 {N_("/_Options/Privacy _System"), NULL, NULL, 0, "<Branch>"},
699 {N_("/_Options/Privacy _System/None"), NULL, NULL, 0, "<RadioItem>"},
700 {N_("/_Options/Si_gn"), NULL, compose_toggle_sign_cb , 0, "<ToggleItem>"},
701 {N_("/_Options/_Encrypt"), NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
702 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
703 {N_("/_Options/_Priority"), NULL, NULL, 0, "<Branch>"},
704 {N_("/_Options/Priority/_Highest"), NULL, compose_set_priority_cb, PRIORITY_HIGHEST, "<RadioItem>"},
705 {N_("/_Options/Priority/Hi_gh"), NULL, compose_set_priority_cb, PRIORITY_HIGH, "/Options/Priority/Highest"},
706 {N_("/_Options/Priority/_Normal"), NULL, compose_set_priority_cb, PRIORITY_NORMAL, "/Options/Priority/Highest"},
707 {N_("/_Options/Priority/Lo_w"), NULL, compose_set_priority_cb, PRIORITY_LOW, "/Options/Priority/Highest"},
708 {N_("/_Options/Priority/_Lowest"), NULL, compose_set_priority_cb, PRIORITY_LOWEST, "/Options/Priority/Highest"},
709 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
710 {N_("/_Options/_Request Return Receipt"), NULL, compose_toggle_return_receipt_cb, 0, "<ToggleItem>"},
711 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
712 {N_("/_Options/Remo_ve references"), NULL, compose_toggle_remove_refs_cb, 0, "<ToggleItem>"},
713 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
715 #define ENC_ACTION(action) \
716 NULL, compose_set_encoding_cb, action, \
717 "/Options/Character encoding/Automatic"
719 {N_("/_Options/Character _encoding"), NULL, NULL, 0, "<Branch>"},
720 {N_("/_Options/Character _encoding/_Automatic"),
721 NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
722 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
724 {N_("/_Options/Character _encoding/7bit ascii (US-ASC_II)"),
725 ENC_ACTION(C_US_ASCII)},
726 {N_("/_Options/Character _encoding/Unicode (_UTF-8)"),
727 ENC_ACTION(C_UTF_8)},
728 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
730 {N_("/_Options/Character _encoding/Western European (ISO-8859-_1)"),
731 ENC_ACTION(C_ISO_8859_1)},
732 {N_("/_Options/Character _encoding/Western European (ISO-8859-15)"),
733 ENC_ACTION(C_ISO_8859_15)},
734 {N_("/_Options/Character _encoding/Western European (Windows-1252)"),
735 ENC_ACTION(C_WINDOWS_1252)},
736 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
738 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
739 ENC_ACTION(C_ISO_8859_2)},
740 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
742 {N_("/_Options/Character _encoding/_Baltic (ISO-8859-13)"),
743 ENC_ACTION(C_ISO_8859_13)},
744 {N_("/_Options/Character _encoding/Baltic (ISO-8859-_4)"),
745 ENC_ACTION(C_ISO_8859_4)},
746 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
748 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
749 ENC_ACTION(C_ISO_8859_7)},
750 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
752 {N_("/_Options/Character _encoding/Hebrew (ISO-8859-_8)"),
753 ENC_ACTION(C_ISO_8859_8)},
754 {N_("/_Options/Character _encoding/Hebrew (Windows-1255)"),
755 ENC_ACTION(C_WINDOWS_1255)},
756 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
758 {N_("/_Options/Character _encoding/Arabic (ISO-8859-_6)"),
759 ENC_ACTION(C_ISO_8859_6)},
760 {N_("/_Options/Character _encoding/Arabic (Windows-1256)"),
761 ENC_ACTION(C_CP1256)},
762 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
764 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
765 ENC_ACTION(C_ISO_8859_9)},
766 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
768 {N_("/_Options/Character _encoding/Cyrillic (ISO-8859-_5)"),
769 ENC_ACTION(C_ISO_8859_5)},
770 {N_("/_Options/Character _encoding/Cyrillic (KOI8-_R)"),
771 ENC_ACTION(C_KOI8_R)},
772 {N_("/_Options/Character _encoding/Cyrillic (KOI8-U)"),
773 ENC_ACTION(C_KOI8_U)},
774 {N_("/_Options/Character _encoding/Cyrillic (Windows-1251)"),
775 ENC_ACTION(C_WINDOWS_1251)},
776 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
778 {N_("/_Options/Character _encoding/Japanese (ISO-2022-_JP)"),
779 ENC_ACTION(C_ISO_2022_JP)},
780 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
782 {N_("/_Options/Character _encoding/Simplified Chinese (_GB2312)"),
783 ENC_ACTION(C_GB2312)},
784 {N_("/_Options/Character _encoding/Simplified Chinese (GBK)"),
785 ENC_ACTION(C_GBK)},
786 {N_("/_Options/Character _encoding/Traditional Chinese (_Big5)"),
787 ENC_ACTION(C_BIG5)},
788 {N_("/_Options/Character _encoding/Traditional Chinese (EUC-_TW)"),
789 ENC_ACTION(C_EUC_TW)},
790 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
792 {N_("/_Options/Character _encoding/Korean (EUC-_KR)"),
793 ENC_ACTION(C_EUC_KR)},
794 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
796 {N_("/_Options/Character _encoding/Thai (TIS-620)"),
797 ENC_ACTION(C_TIS_620)},
798 {N_("/_Options/Character _encoding/Thai (Windows-874)"),
799 ENC_ACTION(C_WINDOWS_874)},
801 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
802 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
803 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
804 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
805 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
806 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
807 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
810 static GtkTargetEntry compose_mime_types[] =
812 {"text/uri-list", 0, 0},
813 {"UTF8_STRING", 0, 0},
814 {"text/plain", 0, 0}
817 static gboolean compose_put_existing_to_front(MsgInfo *info)
819 GList *compose_list = compose_get_compose_list();
820 GList *elem = NULL;
822 if (compose_list) {
823 for (elem = compose_list; elem != NULL && elem->data != NULL;
824 elem = elem->next) {
825 Compose *c = (Compose*)elem->data;
827 if (!c->targetinfo || !c->targetinfo->msgid ||
828 !info->msgid)
829 continue;
831 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
832 gtkut_window_popup(c->window);
833 return TRUE;
837 return FALSE;
840 static GdkColor quote_color1 =
841 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
842 static GdkColor quote_color2 =
843 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
844 static GdkColor quote_color3 =
845 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
847 static GdkColor quote_bgcolor1 =
848 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
849 static GdkColor quote_bgcolor2 =
850 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
851 static GdkColor quote_bgcolor3 =
852 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
854 static GdkColor signature_color = {
855 (gulong)0,
856 (gushort)0x7fff,
857 (gushort)0x7fff,
858 (gushort)0x7fff
861 static GdkColor uri_color = {
862 (gulong)0,
863 (gushort)0,
864 (gushort)0,
865 (gushort)0
868 static void compose_create_tags(GtkTextView *text, Compose *compose)
870 GtkTextBuffer *buffer;
871 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
872 GdkColormap *cmap;
873 GdkColor color[8];
874 gboolean success[8];
875 int i;
877 buffer = gtk_text_view_get_buffer(text);
879 if (prefs_common.enable_color) {
880 /* grab the quote colors, converting from an int to a GdkColor */
881 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
882 &quote_color1);
883 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
884 &quote_color2);
885 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
886 &quote_color3);
887 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
888 &quote_bgcolor1);
889 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
890 &quote_bgcolor2);
891 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
892 &quote_bgcolor3);
893 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
894 &signature_color);
895 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
896 &uri_color);
897 } else {
898 signature_color = quote_color1 = quote_color2 = quote_color3 =
899 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
902 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
903 gtk_text_buffer_create_tag(buffer, "quote0",
904 "foreground-gdk", &quote_color1,
905 "paragraph-background-gdk", &quote_bgcolor1,
906 NULL);
907 gtk_text_buffer_create_tag(buffer, "quote1",
908 "foreground-gdk", &quote_color2,
909 "paragraph-background-gdk", &quote_bgcolor2,
910 NULL);
911 gtk_text_buffer_create_tag(buffer, "quote2",
912 "foreground-gdk", &quote_color3,
913 "paragraph-background-gdk", &quote_bgcolor3,
914 NULL);
915 } else {
916 gtk_text_buffer_create_tag(buffer, "quote0",
917 "foreground-gdk", &quote_color1,
918 NULL);
919 gtk_text_buffer_create_tag(buffer, "quote1",
920 "foreground-gdk", &quote_color2,
921 NULL);
922 gtk_text_buffer_create_tag(buffer, "quote2",
923 "foreground-gdk", &quote_color3,
924 NULL);
927 gtk_text_buffer_create_tag(buffer, "signature",
928 "foreground-gdk", &signature_color,
929 NULL);
930 gtk_text_buffer_create_tag(buffer, "link",
931 "foreground-gdk", &uri_color,
932 NULL);
933 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
934 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
936 color[0] = quote_color1;
937 color[1] = quote_color2;
938 color[2] = quote_color3;
939 color[3] = quote_bgcolor1;
940 color[4] = quote_bgcolor2;
941 color[5] = quote_bgcolor3;
942 color[6] = signature_color;
943 color[7] = uri_color;
944 cmap = gdk_drawable_get_colormap(compose->window->window);
945 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
947 for (i = 0; i < 8; i++) {
948 if (success[i] == FALSE) {
949 GtkStyle *style;
951 g_warning("Compose: color allocation failed.\n");
952 style = gtk_widget_get_style(GTK_WIDGET(text));
953 quote_color1 = quote_color2 = quote_color3 =
954 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
955 signature_color = uri_color = black;
960 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
961 GPtrArray *attach_files)
963 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
966 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
968 return compose_generic_new(account, mailto, item, NULL, NULL);
971 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
973 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
976 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
977 GPtrArray *attach_files, GList *listAddress )
979 Compose *compose;
980 GtkTextView *textview;
981 GtkTextBuffer *textbuf;
982 GtkTextIter iter;
983 GtkItemFactory *ifactory;
984 const gchar *subject_format = NULL;
985 const gchar *body_format = NULL;
987 if (item && item->prefs && item->prefs->enable_default_account)
988 account = account_find_from_id(item->prefs->default_account);
990 if (!account) account = cur_account;
991 g_return_val_if_fail(account != NULL, NULL);
993 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
995 ifactory = gtk_item_factory_from_widget(compose->menubar);
997 compose->replyinfo = NULL;
998 compose->fwdinfo = NULL;
1000 textview = GTK_TEXT_VIEW(compose->text);
1001 textbuf = gtk_text_view_get_buffer(textview);
1002 compose_create_tags(textview, compose);
1004 undo_block(compose->undostruct);
1005 #ifdef USE_ASPELL
1006 compose_set_dictionaries_from_folder_prefs(compose, item);
1007 #endif
1009 if (account->auto_sig)
1010 compose_insert_sig(compose, FALSE);
1011 gtk_text_buffer_get_start_iter(textbuf, &iter);
1012 gtk_text_buffer_place_cursor(textbuf, &iter);
1014 if (account->protocol != A_NNTP) {
1015 if (mailto && *mailto != '\0') {
1016 compose_entries_set(compose, mailto);
1018 } else if (item && item->prefs->enable_default_to) {
1019 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1020 compose_entry_mark_default_to(compose, item->prefs->default_to);
1022 if (item && item->ret_rcpt) {
1023 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1025 } else {
1026 if (mailto) {
1027 compose_entry_append(compose, mailto, COMPOSE_NEWSGROUPS);
1028 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1029 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1032 * CLAWS: just don't allow return receipt request, even if the user
1033 * may want to send an email. simple but foolproof.
1035 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1037 compose_add_field_list( compose, listAddress );
1039 if (item && item->prefs && item->prefs->compose_with_format) {
1040 subject_format = item->prefs->compose_subject_format;
1041 body_format = item->prefs->compose_body_format;
1042 } else if (account->compose_with_format) {
1043 subject_format = account->compose_subject_format;
1044 body_format = account->compose_body_format;
1045 } else if (prefs_common.compose_with_format) {
1046 subject_format = prefs_common.compose_subject_format;
1047 body_format = prefs_common.compose_body_format;
1050 if (subject_format || body_format) {
1051 MsgInfo* dummyinfo = NULL;
1053 if ( subject_format
1054 && *subject_format != '\0' )
1056 gchar *subject = NULL;
1057 gchar *tmp = NULL;
1058 gchar *buf = NULL;
1060 dummyinfo = compose_msginfo_new_from_compose(compose);
1062 /* decode \-escape sequences in the internal representation of the quote format */
1063 tmp = malloc(strlen(subject_format)+1);
1064 pref_get_unescaped_pref(tmp, subject_format);
1066 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1067 #ifdef USE_ASPELL
1068 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account,
1069 compose->gtkaspell);
1070 #else
1071 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account);
1072 #endif
1073 quote_fmt_scan_string(tmp);
1074 quote_fmt_parse();
1076 buf = quote_fmt_get_buffer();
1077 if (buf == NULL)
1078 alertpanel_error(_("New message subject format error."));
1079 else
1080 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1081 quote_fmt_reset_vartable();
1083 g_free(subject);
1084 g_free(tmp);
1087 if ( body_format
1088 && *body_format != '\0' )
1090 GtkTextView *text;
1091 GtkTextBuffer *buffer;
1092 GtkTextIter start, end;
1093 gchar *tmp = NULL;
1095 if ( dummyinfo == NULL )
1096 dummyinfo = compose_msginfo_new_from_compose(compose);
1098 text = GTK_TEXT_VIEW(compose->text);
1099 buffer = gtk_text_view_get_buffer(text);
1100 gtk_text_buffer_get_start_iter(buffer, &start);
1101 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1102 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1104 compose_quote_fmt(compose, dummyinfo,
1105 body_format,
1106 NULL, tmp, FALSE, TRUE,
1107 _("New message body format error at line %d."));
1108 quote_fmt_reset_vartable();
1110 g_free(tmp);
1113 procmsg_msginfo_free( dummyinfo );
1116 if (attach_files) {
1117 gint i;
1118 gchar *file;
1120 for (i = 0; i < attach_files->len; i++) {
1121 file = g_ptr_array_index(attach_files, i);
1122 compose_attach_append(compose, file, file, NULL);
1126 compose_show_first_last_header(compose, TRUE);
1128 /* Set save folder */
1129 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1130 gchar *folderidentifier;
1132 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1133 folderidentifier = folder_item_get_identifier(item);
1134 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1135 g_free(folderidentifier);
1138 gtk_widget_grab_focus(compose->header_last->entry);
1140 undo_unblock(compose->undostruct);
1142 if (prefs_common.auto_exteditor)
1143 compose_exec_ext_editor(compose);
1145 compose->modified = FALSE;
1146 compose_set_title(compose);
1147 return compose;
1150 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1151 gboolean override_pref)
1153 gchar *privacy = NULL;
1155 g_return_if_fail(compose != NULL);
1156 g_return_if_fail(account != NULL);
1158 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1159 return;
1161 if (account->default_privacy_system
1162 && strlen(account->default_privacy_system)) {
1163 privacy = account->default_privacy_system;
1164 } else {
1165 GSList *privacy_avail = privacy_get_system_ids();
1166 if (privacy_avail && g_slist_length(privacy_avail)) {
1167 privacy = (gchar *)(privacy_avail->data);
1170 if (privacy != NULL) {
1171 if (compose->privacy_system == NULL)
1172 compose->privacy_system = g_strdup(privacy);
1173 compose_update_privacy_system_menu_item(compose, FALSE);
1174 compose_use_encryption(compose, TRUE);
1178 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1180 gchar *privacy = NULL;
1182 if (account->default_privacy_system
1183 && strlen(account->default_privacy_system)) {
1184 privacy = account->default_privacy_system;
1185 } else {
1186 GSList *privacy_avail = privacy_get_system_ids();
1187 if (privacy_avail && g_slist_length(privacy_avail)) {
1188 privacy = (gchar *)(privacy_avail->data);
1191 if (privacy != NULL) {
1192 if (compose->privacy_system == NULL)
1193 compose->privacy_system = g_strdup(privacy);
1194 compose_update_privacy_system_menu_item(compose, FALSE);
1195 compose_use_signing(compose, TRUE);
1199 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1201 MsgInfo *msginfo;
1202 guint list_len;
1203 Compose *compose = NULL;
1204 GtkItemFactory *ifactory = NULL;
1206 g_return_val_if_fail(msginfo_list != NULL, NULL);
1208 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1209 g_return_val_if_fail(msginfo != NULL, NULL);
1211 list_len = g_slist_length(msginfo_list);
1213 switch (mode) {
1214 case COMPOSE_REPLY:
1215 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1216 FALSE, prefs_common.default_reply_list, FALSE, body);
1217 break;
1218 case COMPOSE_REPLY_WITH_QUOTE:
1219 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1220 FALSE, prefs_common.default_reply_list, FALSE, body);
1221 break;
1222 case COMPOSE_REPLY_WITHOUT_QUOTE:
1223 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1224 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1225 break;
1226 case COMPOSE_REPLY_TO_SENDER:
1227 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1228 FALSE, FALSE, TRUE, body);
1229 break;
1230 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1231 compose = compose_followup_and_reply_to(msginfo,
1232 COMPOSE_QUOTE_CHECK,
1233 FALSE, FALSE, body);
1234 break;
1235 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1236 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1237 FALSE, FALSE, TRUE, body);
1238 break;
1239 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1240 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1241 FALSE, FALSE, TRUE, NULL);
1242 break;
1243 case COMPOSE_REPLY_TO_ALL:
1244 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1245 TRUE, FALSE, FALSE, body);
1246 break;
1247 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1248 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1249 TRUE, FALSE, FALSE, body);
1250 break;
1251 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1252 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1253 TRUE, FALSE, FALSE, NULL);
1254 break;
1255 case COMPOSE_REPLY_TO_LIST:
1256 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1257 FALSE, TRUE, FALSE, body);
1258 break;
1259 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1260 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1261 FALSE, TRUE, FALSE, body);
1262 break;
1263 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1264 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1265 FALSE, TRUE, FALSE, NULL);
1266 break;
1267 case COMPOSE_FORWARD:
1268 if (prefs_common.forward_as_attachment) {
1269 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1270 return compose;
1271 } else {
1272 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1273 return compose;
1275 break;
1276 case COMPOSE_FORWARD_INLINE:
1277 /* check if we reply to more than one Message */
1278 if (list_len == 1) {
1279 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1280 break;
1282 /* more messages FALL THROUGH */
1283 case COMPOSE_FORWARD_AS_ATTACH:
1284 compose = compose_forward_multiple(NULL, msginfo_list);
1285 break;
1286 case COMPOSE_REDIRECT:
1287 compose = compose_redirect(NULL, msginfo, FALSE);
1288 break;
1289 default:
1290 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1293 ifactory = gtk_item_factory_from_widget(compose->menubar);
1295 compose->rmode = mode;
1296 switch (compose->rmode) {
1297 case COMPOSE_REPLY:
1298 case COMPOSE_REPLY_WITH_QUOTE:
1299 case COMPOSE_REPLY_WITHOUT_QUOTE:
1300 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1301 debug_print("reply mode Normal\n");
1302 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1303 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1304 break;
1305 case COMPOSE_REPLY_TO_SENDER:
1306 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1307 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1308 debug_print("reply mode Sender\n");
1309 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1310 break;
1311 case COMPOSE_REPLY_TO_ALL:
1312 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1313 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1314 debug_print("reply mode All\n");
1315 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1316 break;
1317 case COMPOSE_REPLY_TO_LIST:
1318 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1319 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1320 debug_print("reply mode List\n");
1321 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1322 break;
1323 default:
1324 break;
1326 return compose;
1329 static Compose *compose_reply(MsgInfo *msginfo,
1330 ComposeQuoteMode quote_mode,
1331 gboolean to_all,
1332 gboolean to_ml,
1333 gboolean to_sender,
1334 const gchar *body)
1336 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1337 to_sender, FALSE, body);
1340 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1341 ComposeQuoteMode quote_mode,
1342 gboolean to_all,
1343 gboolean to_sender,
1344 const gchar *body)
1346 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1347 to_sender, TRUE, body);
1350 static void compose_extract_original_charset(Compose *compose)
1352 MsgInfo *info = NULL;
1353 if (compose->replyinfo) {
1354 info = compose->replyinfo;
1355 } else if (compose->fwdinfo) {
1356 info = compose->fwdinfo;
1357 } else if (compose->targetinfo) {
1358 info = compose->targetinfo;
1360 if (info) {
1361 MimeInfo *mimeinfo = procmime_scan_message(info);
1362 MimeInfo *partinfo = mimeinfo;
1363 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1364 partinfo = procmime_mimeinfo_next(partinfo);
1365 if (partinfo) {
1366 compose->orig_charset =
1367 g_strdup(procmime_mimeinfo_get_parameter(
1368 partinfo, "charset"));
1370 procmime_mimeinfo_free_all(mimeinfo);
1374 #define SIGNAL_BLOCK(buffer) { \
1375 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1376 G_CALLBACK(compose_changed_cb), \
1377 compose); \
1378 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1379 G_CALLBACK(text_inserted), \
1380 compose); \
1383 #define SIGNAL_UNBLOCK(buffer) { \
1384 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1385 G_CALLBACK(compose_changed_cb), \
1386 compose); \
1387 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1388 G_CALLBACK(text_inserted), \
1389 compose); \
1392 static Compose *compose_generic_reply(MsgInfo *msginfo,
1393 ComposeQuoteMode quote_mode,
1394 gboolean to_all, gboolean to_ml,
1395 gboolean to_sender,
1396 gboolean followup_and_reply_to,
1397 const gchar *body)
1399 GtkItemFactory *ifactory;
1400 Compose *compose;
1401 PrefsAccount *account = NULL;
1402 GtkTextView *textview;
1403 GtkTextBuffer *textbuf;
1404 gboolean quote = FALSE;
1405 const gchar *qmark = NULL;
1406 const gchar *body_fmt = NULL;
1408 g_return_val_if_fail(msginfo != NULL, NULL);
1409 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1411 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1413 g_return_val_if_fail(account != NULL, NULL);
1415 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1417 compose->updating = TRUE;
1419 ifactory = gtk_item_factory_from_widget(compose->menubar);
1421 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1422 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1424 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1425 if (!compose->replyinfo)
1426 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1428 compose_extract_original_charset(compose);
1430 if (msginfo->folder && msginfo->folder->ret_rcpt)
1431 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1433 /* Set save folder */
1434 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1435 gchar *folderidentifier;
1437 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1438 folderidentifier = folder_item_get_identifier(msginfo->folder);
1439 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1440 g_free(folderidentifier);
1443 if (compose_parse_header(compose, msginfo) < 0) return NULL;
1445 textview = (GTK_TEXT_VIEW(compose->text));
1446 textbuf = gtk_text_view_get_buffer(textview);
1447 compose_create_tags(textview, compose);
1449 undo_block(compose->undostruct);
1450 #ifdef USE_ASPELL
1451 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1452 #endif
1454 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1455 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1456 /* use the reply format of folder (if enabled), or the account's one
1457 (if enabled) or fallback to the global reply format, which is always
1458 enabled (even if empty), and use the relevant quotemark */
1459 quote = TRUE;
1460 if (msginfo->folder && msginfo->folder->prefs &&
1461 msginfo->folder->prefs->reply_with_format) {
1462 qmark = msginfo->folder->prefs->reply_quotemark;
1463 body_fmt = msginfo->folder->prefs->reply_body_format;
1465 } else if (account->reply_with_format) {
1466 qmark = account->reply_quotemark;
1467 body_fmt = account->reply_body_format;
1469 } else {
1470 qmark = prefs_common.quotemark;
1471 body_fmt = prefs_common.quotefmt;
1475 if (quote) {
1476 /* empty quotemark is not allowed */
1477 if (qmark == NULL || *qmark == '\0')
1478 qmark = "> ";
1479 compose_quote_fmt(compose, compose->replyinfo,
1480 body_fmt, qmark, body, FALSE, TRUE,
1481 _("Message reply format error at line %d."));
1482 quote_fmt_reset_vartable();
1484 if (procmime_msginfo_is_encrypted(compose->replyinfo)) {
1485 compose_force_encryption(compose, account, FALSE);
1488 SIGNAL_BLOCK(textbuf);
1490 if (account->auto_sig)
1491 compose_insert_sig(compose, FALSE);
1493 compose_wrap_all(compose);
1495 SIGNAL_UNBLOCK(textbuf);
1497 gtk_widget_grab_focus(compose->text);
1499 undo_unblock(compose->undostruct);
1501 if (prefs_common.auto_exteditor)
1502 compose_exec_ext_editor(compose);
1504 compose->modified = FALSE;
1505 compose_set_title(compose);
1507 compose->updating = FALSE;
1508 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1510 if (compose->deferred_destroy) {
1511 compose_destroy(compose);
1512 return NULL;
1515 return compose;
1518 #define INSERT_FW_HEADER(var, hdr) \
1519 if (msginfo->var && *msginfo->var) { \
1520 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1521 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1522 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1525 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1526 gboolean as_attach, const gchar *body,
1527 gboolean no_extedit,
1528 gboolean batch)
1530 Compose *compose;
1531 GtkTextView *textview;
1532 GtkTextBuffer *textbuf;
1533 GtkTextIter iter;
1535 g_return_val_if_fail(msginfo != NULL, NULL);
1536 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1538 if (!account &&
1539 !(account = compose_guess_forward_account_from_msginfo
1540 (msginfo)))
1541 account = cur_account;
1543 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1545 compose->updating = TRUE;
1546 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1547 if (!compose->fwdinfo)
1548 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1550 compose_extract_original_charset(compose);
1552 if (msginfo->subject && *msginfo->subject) {
1553 gchar *buf, *buf2, *p;
1555 buf = p = g_strdup(msginfo->subject);
1556 p += subject_get_prefix_length(p);
1557 memmove(buf, p, strlen(p) + 1);
1559 buf2 = g_strdup_printf("Fw: %s", buf);
1560 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1562 g_free(buf);
1563 g_free(buf2);
1566 textview = GTK_TEXT_VIEW(compose->text);
1567 textbuf = gtk_text_view_get_buffer(textview);
1568 compose_create_tags(textview, compose);
1570 undo_block(compose->undostruct);
1571 if (as_attach) {
1572 gchar *msgfile;
1574 msgfile = procmsg_get_message_file(msginfo);
1575 if (!is_file_exist(msgfile))
1576 g_warning("%s: file not exist\n", msgfile);
1577 else
1578 compose_attach_append(compose, msgfile, msgfile,
1579 "message/rfc822");
1581 g_free(msgfile);
1582 } else {
1583 const gchar *qmark = NULL;
1584 const gchar *body_fmt = prefs_common.fw_quotefmt;
1585 MsgInfo *full_msginfo;
1587 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1588 if (!full_msginfo)
1589 full_msginfo = procmsg_msginfo_copy(msginfo);
1591 /* use the forward format of folder (if enabled), or the account's one
1592 (if enabled) or fallback to the global forward format, which is always
1593 enabled (even if empty), and use the relevant quotemark */
1594 if (msginfo->folder && msginfo->folder->prefs &&
1595 msginfo->folder->prefs->forward_with_format) {
1596 qmark = msginfo->folder->prefs->forward_quotemark;
1597 body_fmt = msginfo->folder->prefs->forward_body_format;
1599 } else if (account->forward_with_format) {
1600 qmark = account->forward_quotemark;
1601 body_fmt = account->forward_body_format;
1603 } else {
1604 qmark = prefs_common.fw_quotemark;
1605 body_fmt = prefs_common.fw_quotefmt;
1608 /* empty quotemark is not allowed */
1609 if (qmark == NULL || *qmark == '\0')
1610 qmark = "> ";
1612 compose_quote_fmt(compose, full_msginfo,
1613 body_fmt, qmark, body, FALSE, TRUE,
1614 _("Message forward format error at line %d."));
1615 quote_fmt_reset_vartable();
1616 compose_attach_parts(compose, msginfo);
1618 procmsg_msginfo_free(full_msginfo);
1621 SIGNAL_BLOCK(textbuf);
1623 if (account->auto_sig)
1624 compose_insert_sig(compose, FALSE);
1626 compose_wrap_all(compose);
1628 SIGNAL_UNBLOCK(textbuf);
1630 gtk_text_buffer_get_start_iter(textbuf, &iter);
1631 gtk_text_buffer_place_cursor(textbuf, &iter);
1633 gtk_widget_grab_focus(compose->header_last->entry);
1635 if (!no_extedit && prefs_common.auto_exteditor)
1636 compose_exec_ext_editor(compose);
1638 /*save folder*/
1639 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1640 gchar *folderidentifier;
1642 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1643 folderidentifier = folder_item_get_identifier(msginfo->folder);
1644 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1645 g_free(folderidentifier);
1648 undo_unblock(compose->undostruct);
1650 compose->modified = FALSE;
1651 compose_set_title(compose);
1653 compose->updating = FALSE;
1654 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1656 if (compose->deferred_destroy) {
1657 compose_destroy(compose);
1658 return NULL;
1661 return compose;
1664 #undef INSERT_FW_HEADER
1666 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1668 Compose *compose;
1669 GtkTextView *textview;
1670 GtkTextBuffer *textbuf;
1671 GtkTextIter iter;
1672 GSList *msginfo;
1673 gchar *msgfile;
1674 gboolean single_mail = TRUE;
1676 g_return_val_if_fail(msginfo_list != NULL, NULL);
1678 if (g_slist_length(msginfo_list) > 1)
1679 single_mail = FALSE;
1681 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1682 if (((MsgInfo *)msginfo->data)->folder == NULL)
1683 return NULL;
1685 /* guess account from first selected message */
1686 if (!account &&
1687 !(account = compose_guess_forward_account_from_msginfo
1688 (msginfo_list->data)))
1689 account = cur_account;
1691 g_return_val_if_fail(account != NULL, NULL);
1693 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1694 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1695 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1698 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1700 compose->updating = TRUE;
1702 textview = GTK_TEXT_VIEW(compose->text);
1703 textbuf = gtk_text_view_get_buffer(textview);
1704 compose_create_tags(textview, compose);
1706 undo_block(compose->undostruct);
1707 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1708 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1710 if (!is_file_exist(msgfile))
1711 g_warning("%s: file not exist\n", msgfile);
1712 else
1713 compose_attach_append(compose, msgfile, msgfile,
1714 "message/rfc822");
1715 g_free(msgfile);
1718 if (single_mail) {
1719 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1720 if (info->subject && *info->subject) {
1721 gchar *buf, *buf2, *p;
1723 buf = p = g_strdup(info->subject);
1724 p += subject_get_prefix_length(p);
1725 memmove(buf, p, strlen(p) + 1);
1727 buf2 = g_strdup_printf("Fw: %s", buf);
1728 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1730 g_free(buf);
1731 g_free(buf2);
1733 } else {
1734 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1735 _("Fw: multiple emails"));
1738 SIGNAL_BLOCK(textbuf);
1740 if (account->auto_sig)
1741 compose_insert_sig(compose, FALSE);
1743 compose_wrap_all(compose);
1745 SIGNAL_UNBLOCK(textbuf);
1747 gtk_text_buffer_get_start_iter(textbuf, &iter);
1748 gtk_text_buffer_place_cursor(textbuf, &iter);
1750 gtk_widget_grab_focus(compose->header_last->entry);
1751 undo_unblock(compose->undostruct);
1752 compose->modified = FALSE;
1753 compose_set_title(compose);
1755 compose->updating = FALSE;
1756 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1758 if (compose->deferred_destroy) {
1759 compose_destroy(compose);
1760 return NULL;
1763 return compose;
1766 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1768 GtkTextIter start = *iter;
1769 GtkTextIter end_iter;
1770 int start_pos = gtk_text_iter_get_offset(&start);
1771 gchar *str = NULL;
1772 if (!compose->account->sig_sep)
1773 return FALSE;
1775 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1776 start_pos+strlen(compose->account->sig_sep));
1778 /* check sig separator */
1779 str = gtk_text_iter_get_text(&start, &end_iter);
1780 if (!strcmp(str, compose->account->sig_sep)) {
1781 gchar *tmp = NULL;
1782 /* check end of line (\n) */
1783 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
1784 start_pos+strlen(compose->account->sig_sep));
1785 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1786 start_pos+strlen(compose->account->sig_sep)+1);
1787 tmp = gtk_text_iter_get_text(&start, &end_iter);
1788 if (!strcmp(tmp,"\n")) {
1789 g_free(str);
1790 g_free(tmp);
1791 return TRUE;
1793 g_free(tmp);
1795 g_free(str);
1797 return FALSE;
1800 static void compose_colorize_signature(Compose *compose)
1802 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
1803 GtkTextIter iter;
1804 GtkTextIter end_iter;
1805 gtk_text_buffer_get_start_iter(buffer, &iter);
1806 while (gtk_text_iter_forward_line(&iter))
1807 if (compose_is_sig_separator(compose, buffer, &iter)) {
1808 gtk_text_buffer_get_end_iter(buffer, &end_iter);
1809 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
1813 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
1815 Compose *compose = NULL;
1816 PrefsAccount *account = NULL;
1817 GtkTextView *textview;
1818 GtkTextBuffer *textbuf;
1819 GtkTextMark *mark;
1820 GtkTextIter iter;
1821 FILE *fp;
1822 gchar buf[BUFFSIZE];
1823 gboolean use_signing = FALSE;
1824 gboolean use_encryption = FALSE;
1825 gchar *privacy_system = NULL;
1826 int priority = PRIORITY_NORMAL;
1827 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
1829 g_return_val_if_fail(msginfo != NULL, NULL);
1830 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1832 if (compose_put_existing_to_front(msginfo)) {
1833 return NULL;
1836 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1837 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1838 gchar queueheader_buf[BUFFSIZE];
1839 gint id, param;
1841 /* Select Account from queue headers */
1842 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1843 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
1844 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
1845 account = account_find_from_id(id);
1847 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1848 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
1849 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
1850 account = account_find_from_id(id);
1852 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1853 sizeof(queueheader_buf), "NAID:")) {
1854 id = atoi(&queueheader_buf[strlen("NAID:")]);
1855 account = account_find_from_id(id);
1857 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1858 sizeof(queueheader_buf), "MAID:")) {
1859 id = atoi(&queueheader_buf[strlen("MAID:")]);
1860 account = account_find_from_id(id);
1862 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1863 sizeof(queueheader_buf), "S:")) {
1864 account = account_find_from_address(queueheader_buf);
1866 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1867 sizeof(queueheader_buf), "X-Claws-Sign:")) {
1868 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
1869 use_signing = param;
1872 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1873 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
1874 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
1875 use_signing = param;
1878 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1879 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
1880 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
1881 use_encryption = param;
1883 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1884 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
1885 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
1886 use_encryption = param;
1888 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1889 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
1890 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
1892 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1893 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
1894 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
1896 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1897 sizeof(queueheader_buf), "X-Priority: ")) {
1898 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
1899 priority = param;
1901 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1902 sizeof(queueheader_buf), "RMID:")) {
1903 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
1904 if (tokens[0] && tokens[1] && tokens[2]) {
1905 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1906 if (orig_item != NULL) {
1907 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1910 g_strfreev(tokens);
1912 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
1913 sizeof(queueheader_buf), "FMID:")) {
1914 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
1915 if (tokens[0] && tokens[1] && tokens[2]) {
1916 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
1917 if (orig_item != NULL) {
1918 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
1921 g_strfreev(tokens);
1923 } else {
1924 account = msginfo->folder->folder->account;
1927 if (!account && prefs_common.reedit_account_autosel) {
1928 gchar from[BUFFSIZE];
1929 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
1930 extract_address(from);
1931 account = account_find_from_address(from);
1934 if (!account) {
1935 account = cur_account;
1937 g_return_val_if_fail(account != NULL, NULL);
1939 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
1941 compose->replyinfo = replyinfo;
1942 compose->fwdinfo = fwdinfo;
1944 compose->updating = TRUE;
1945 compose->priority = priority;
1947 if (privacy_system != NULL) {
1948 compose->privacy_system = privacy_system;
1949 compose_use_signing(compose, use_signing);
1950 compose_use_encryption(compose, use_encryption);
1951 compose_update_privacy_system_menu_item(compose, FALSE);
1952 } else {
1953 activate_privacy_system(compose, account, FALSE);
1956 compose->targetinfo = procmsg_msginfo_copy(msginfo);
1958 compose_extract_original_charset(compose);
1960 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
1961 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
1962 gchar queueheader_buf[BUFFSIZE];
1964 /* Set message save folder */
1965 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
1966 gint startpos = 0;
1968 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1969 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
1970 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
1972 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
1973 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
1974 if (active) {
1975 GtkItemFactory *ifactory;
1976 ifactory = gtk_item_factory_from_widget(compose->menubar);
1977 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1982 if (compose_parse_header(compose, msginfo) < 0) {
1983 compose->updating = FALSE;
1984 compose_destroy(compose);
1985 return NULL;
1987 compose_reedit_set_entry(compose, msginfo);
1989 textview = GTK_TEXT_VIEW(compose->text);
1990 textbuf = gtk_text_view_get_buffer(textview);
1991 compose_create_tags(textview, compose);
1993 mark = gtk_text_buffer_get_insert(textbuf);
1994 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1996 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
1997 G_CALLBACK(compose_changed_cb),
1998 compose);
2000 if (procmime_msginfo_is_encrypted(msginfo)) {
2001 fp = procmime_get_first_encrypted_text_content(msginfo);
2002 if (fp) {
2003 compose_force_encryption(compose, account, TRUE);
2005 } else {
2006 fp = procmime_get_first_text_content(msginfo);
2008 if (fp == NULL) {
2009 g_warning("Can't get text part\n");
2012 if (fp != NULL) {
2013 gboolean prev_autowrap = compose->autowrap;
2015 compose->autowrap = FALSE;
2016 while (fgets(buf, sizeof(buf), fp) != NULL) {
2017 strcrchomp(buf);
2018 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2020 compose_wrap_all_full(compose, FALSE);
2021 compose->autowrap = prev_autowrap;
2022 fclose(fp);
2025 compose_attach_parts(compose, msginfo);
2027 compose_colorize_signature(compose);
2029 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2030 G_CALLBACK(compose_changed_cb),
2031 compose);
2033 gtk_widget_grab_focus(compose->text);
2035 if (prefs_common.auto_exteditor) {
2036 compose_exec_ext_editor(compose);
2038 compose->modified = FALSE;
2039 compose_set_title(compose);
2041 compose->updating = FALSE;
2042 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2044 if (compose->deferred_destroy) {
2045 compose_destroy(compose);
2046 return NULL;
2049 compose->sig_str = compose_get_signature_str(compose);
2051 return compose;
2054 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2055 gboolean batch)
2057 Compose *compose;
2058 gchar *filename;
2059 GtkItemFactory *ifactory;
2060 FolderItem *item;
2062 g_return_val_if_fail(msginfo != NULL, NULL);
2064 if (!account)
2065 account = account_get_reply_account(msginfo,
2066 prefs_common.reply_account_autosel);
2067 g_return_val_if_fail(account != NULL, NULL);
2069 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2071 compose->updating = TRUE;
2073 ifactory = gtk_item_factory_from_widget(compose->menubar);
2074 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2075 compose->replyinfo = NULL;
2076 compose->fwdinfo = NULL;
2078 compose_show_first_last_header(compose, TRUE);
2080 gtk_widget_grab_focus(compose->header_last->entry);
2082 filename = procmsg_get_message_file(msginfo);
2084 if (filename == NULL) {
2085 compose->updating = FALSE;
2086 compose_destroy(compose);
2088 return NULL;
2091 compose->redirect_filename = filename;
2093 /* Set save folder */
2094 item = msginfo->folder;
2095 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2096 gchar *folderidentifier;
2098 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2099 folderidentifier = folder_item_get_identifier(item);
2100 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2101 g_free(folderidentifier);
2104 compose_attach_parts(compose, msginfo);
2106 if (msginfo->subject)
2107 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2108 msginfo->subject);
2109 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2111 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2112 _("Message redirect format error at line %d."));
2113 quote_fmt_reset_vartable();
2114 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2116 compose_colorize_signature(compose);
2118 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2119 menu_set_sensitive(ifactory, "/Add...", FALSE);
2120 menu_set_sensitive(ifactory, "/Remove", FALSE);
2121 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2123 ifactory = gtk_item_factory_from_widget(compose->menubar);
2124 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2125 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2126 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2127 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2128 menu_set_sensitive(ifactory, "/Edit", FALSE);
2129 menu_set_sensitive(ifactory, "/Options", FALSE);
2130 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2131 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2133 if (compose->toolbar->draft_btn)
2134 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2135 if (compose->toolbar->insert_btn)
2136 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2137 if (compose->toolbar->attach_btn)
2138 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2139 if (compose->toolbar->sig_btn)
2140 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2141 if (compose->toolbar->exteditor_btn)
2142 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2143 if (compose->toolbar->linewrap_current_btn)
2144 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2145 if (compose->toolbar->linewrap_all_btn)
2146 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2148 compose->modified = FALSE;
2149 compose_set_title(compose);
2150 compose->updating = FALSE;
2151 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2153 if (compose->deferred_destroy) {
2154 compose_destroy(compose);
2155 return NULL;
2158 return compose;
2161 GList *compose_get_compose_list(void)
2163 return compose_list;
2166 void compose_entry_append(Compose *compose, const gchar *address,
2167 ComposeEntryType type)
2169 const gchar *header;
2170 gchar *cur, *begin;
2171 gboolean in_quote = FALSE;
2172 if (!address || *address == '\0') return;
2174 switch (type) {
2175 case COMPOSE_CC:
2176 header = N_("Cc:");
2177 break;
2178 case COMPOSE_BCC:
2179 header = N_("Bcc:");
2180 break;
2181 case COMPOSE_REPLYTO:
2182 header = N_("Reply-To:");
2183 break;
2184 case COMPOSE_NEWSGROUPS:
2185 header = N_("Newsgroups:");
2186 break;
2187 case COMPOSE_FOLLOWUPTO:
2188 header = N_( "Followup-To:");
2189 break;
2190 case COMPOSE_TO:
2191 default:
2192 header = N_("To:");
2193 break;
2195 header = prefs_common_translated_header_name(header);
2197 cur = begin = (gchar *)address;
2199 /* we separate the line by commas, but not if we're inside a quoted
2200 * string */
2201 while (*cur != '\0') {
2202 if (*cur == '"')
2203 in_quote = !in_quote;
2204 if (*cur == ',' && !in_quote) {
2205 gchar *tmp = g_strdup(begin);
2206 gchar *o_tmp = tmp;
2207 tmp[cur-begin]='\0';
2208 cur++;
2209 begin = cur;
2210 while (*tmp == ' ' || *tmp == '\t')
2211 tmp++;
2212 compose_add_header_entry(compose, header, tmp);
2213 g_free(o_tmp);
2214 continue;
2216 cur++;
2218 if (begin < cur) {
2219 gchar *tmp = g_strdup(begin);
2220 gchar *o_tmp = tmp;
2221 tmp[cur-begin]='\0';
2222 cur++;
2223 begin = cur;
2224 while (*tmp == ' ' || *tmp == '\t')
2225 tmp++;
2226 compose_add_header_entry(compose, header, tmp);
2227 g_free(o_tmp);
2231 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2233 static GdkColor yellow;
2234 static GdkColor black;
2235 static gboolean yellow_initialised = FALSE;
2236 GSList *h_list;
2237 GtkEntry *entry;
2239 if (!yellow_initialised) {
2240 gdk_color_parse("#f5f6be", &yellow);
2241 gdk_color_parse("#000000", &black);
2242 yellow_initialised = gdk_colormap_alloc_color(
2243 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2244 yellow_initialised &= gdk_colormap_alloc_color(
2245 gdk_colormap_get_system(), &black, FALSE, TRUE);
2248 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2249 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2250 if (gtk_entry_get_text(entry) &&
2251 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2252 if (yellow_initialised) {
2253 gtk_widget_modify_base(
2254 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2255 GTK_STATE_NORMAL, &yellow);
2256 gtk_widget_modify_text(
2257 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2258 GTK_STATE_NORMAL, &black);
2264 void compose_toolbar_cb(gint action, gpointer data)
2266 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2267 Compose *compose = (Compose*)toolbar_item->parent;
2269 g_return_if_fail(compose != NULL);
2271 switch(action) {
2272 case A_SEND:
2273 compose_send_cb(compose, 0, NULL);
2274 break;
2275 case A_SENDL:
2276 compose_send_later_cb(compose, 0, NULL);
2277 break;
2278 case A_DRAFT:
2279 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2280 break;
2281 case A_INSERT:
2282 compose_insert_file_cb(compose, 0, NULL);
2283 break;
2284 case A_ATTACH:
2285 compose_attach_cb(compose, 0, NULL);
2286 break;
2287 case A_SIG:
2288 compose_insert_sig(compose, FALSE);
2289 break;
2290 case A_EXTEDITOR:
2291 compose_ext_editor_cb(compose, 0, NULL);
2292 break;
2293 case A_LINEWRAP_CURRENT:
2294 compose_beautify_paragraph(compose, NULL, TRUE);
2295 break;
2296 case A_LINEWRAP_ALL:
2297 compose_wrap_all_full(compose, TRUE);
2298 break;
2299 case A_ADDRBOOK:
2300 compose_address_cb(compose, 0, NULL);
2301 break;
2302 #ifdef USE_ASPELL
2303 case A_CHECK_SPELLING:
2304 compose_check_all(compose);
2305 break;
2306 #endif
2307 default:
2308 break;
2312 static void compose_entries_set(Compose *compose, const gchar *mailto)
2314 gchar *to = NULL;
2315 gchar *cc = NULL;
2316 gchar *subject = NULL;
2317 gchar *body = NULL;
2318 gchar *temp = NULL;
2319 gsize len = 0;
2320 gchar *attach = NULL;
2322 scan_mailto_url(mailto, &to, &cc, NULL, &subject, &body, &attach);
2324 if (to)
2325 compose_entry_append(compose, to, COMPOSE_TO);
2326 if (cc)
2327 compose_entry_append(compose, cc, COMPOSE_CC);
2328 if (subject) {
2329 if (!g_utf8_validate (subject, -1, NULL)) {
2330 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2331 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2332 g_free(temp);
2333 } else {
2334 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2337 if (body) {
2338 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2339 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2340 GtkTextMark *mark;
2341 GtkTextIter iter;
2342 gboolean prev_autowrap = compose->autowrap;
2344 compose->autowrap = FALSE;
2346 mark = gtk_text_buffer_get_insert(buffer);
2347 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2349 if (!g_utf8_validate (body, -1, NULL)) {
2350 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2351 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2352 g_free(temp);
2353 } else {
2354 gtk_text_buffer_insert(buffer, &iter, body, -1);
2356 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2358 compose->autowrap = prev_autowrap;
2359 if (compose->autowrap)
2360 compose_wrap_all(compose);
2363 if (attach) {
2364 gchar *utf8_filename = conv_filename_to_utf8(attach);
2365 if (utf8_filename) {
2366 if (compose_attach_append(compose, attach, utf8_filename, NULL)) {
2367 alertpanel_notice(_("The file '%s' has been attached."), attach);
2369 g_free(utf8_filename);
2370 } else {
2371 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2374 g_free(to);
2375 g_free(cc);
2376 g_free(subject);
2377 g_free(body);
2378 g_free(attach);
2381 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2383 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2384 {"Cc:", NULL, TRUE},
2385 {"References:", NULL, FALSE},
2386 {"Bcc:", NULL, TRUE},
2387 {"Newsgroups:", NULL, TRUE},
2388 {"Followup-To:", NULL, TRUE},
2389 {"List-Post:", NULL, FALSE},
2390 {"X-Priority:", NULL, FALSE},
2391 {NULL, NULL, FALSE}};
2393 enum
2395 H_REPLY_TO = 0,
2396 H_CC = 1,
2397 H_REFERENCES = 2,
2398 H_BCC = 3,
2399 H_NEWSGROUPS = 4,
2400 H_FOLLOWUP_TO = 5,
2401 H_LIST_POST = 6,
2402 H_X_PRIORITY = 7
2405 FILE *fp;
2407 g_return_val_if_fail(msginfo != NULL, -1);
2409 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2410 procheader_get_header_fields(fp, hentry);
2411 fclose(fp);
2413 if (hentry[H_REPLY_TO].body != NULL) {
2414 if (hentry[H_REPLY_TO].body[0] != '\0') {
2415 compose->replyto =
2416 conv_unmime_header(hentry[H_REPLY_TO].body,
2417 NULL);
2419 g_free(hentry[H_REPLY_TO].body);
2420 hentry[H_REPLY_TO].body = NULL;
2422 if (hentry[H_CC].body != NULL) {
2423 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2424 g_free(hentry[H_CC].body);
2425 hentry[H_CC].body = NULL;
2427 if (hentry[H_REFERENCES].body != NULL) {
2428 if (compose->mode == COMPOSE_REEDIT)
2429 compose->references = hentry[H_REFERENCES].body;
2430 else {
2431 compose->references = compose_parse_references
2432 (hentry[H_REFERENCES].body, msginfo->msgid);
2433 g_free(hentry[H_REFERENCES].body);
2435 hentry[H_REFERENCES].body = NULL;
2437 if (hentry[H_BCC].body != NULL) {
2438 if (compose->mode == COMPOSE_REEDIT)
2439 compose->bcc =
2440 conv_unmime_header(hentry[H_BCC].body, NULL);
2441 g_free(hentry[H_BCC].body);
2442 hentry[H_BCC].body = NULL;
2444 if (hentry[H_NEWSGROUPS].body != NULL) {
2445 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2446 hentry[H_NEWSGROUPS].body = NULL;
2448 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2449 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2450 compose->followup_to =
2451 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2452 NULL);
2454 g_free(hentry[H_FOLLOWUP_TO].body);
2455 hentry[H_FOLLOWUP_TO].body = NULL;
2457 if (hentry[H_LIST_POST].body != NULL) {
2458 gchar *to = NULL;
2460 extract_address(hentry[H_LIST_POST].body);
2461 if (hentry[H_LIST_POST].body[0] != '\0') {
2462 scan_mailto_url(hentry[H_LIST_POST].body,
2463 &to, NULL, NULL, NULL, NULL, NULL);
2464 if (to) {
2465 g_free(compose->ml_post);
2466 compose->ml_post = to;
2469 g_free(hentry[H_LIST_POST].body);
2470 hentry[H_LIST_POST].body = NULL;
2473 /* CLAWS - X-Priority */
2474 if (compose->mode == COMPOSE_REEDIT)
2475 if (hentry[H_X_PRIORITY].body != NULL) {
2476 gint priority;
2478 priority = atoi(hentry[H_X_PRIORITY].body);
2479 g_free(hentry[H_X_PRIORITY].body);
2481 hentry[H_X_PRIORITY].body = NULL;
2483 if (priority < PRIORITY_HIGHEST ||
2484 priority > PRIORITY_LOWEST)
2485 priority = PRIORITY_NORMAL;
2487 compose->priority = priority;
2490 if (compose->mode == COMPOSE_REEDIT) {
2491 if (msginfo->inreplyto && *msginfo->inreplyto)
2492 compose->inreplyto = g_strdup(msginfo->inreplyto);
2493 return 0;
2496 if (msginfo->msgid && *msginfo->msgid)
2497 compose->inreplyto = g_strdup(msginfo->msgid);
2499 if (!compose->references) {
2500 if (msginfo->msgid && *msginfo->msgid) {
2501 if (msginfo->inreplyto && *msginfo->inreplyto)
2502 compose->references =
2503 g_strdup_printf("<%s>\n\t<%s>",
2504 msginfo->inreplyto,
2505 msginfo->msgid);
2506 else
2507 compose->references =
2508 g_strconcat("<", msginfo->msgid, ">",
2509 NULL);
2510 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2511 compose->references =
2512 g_strconcat("<", msginfo->inreplyto, ">",
2513 NULL);
2517 return 0;
2520 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2522 GSList *ref_id_list, *cur;
2523 GString *new_ref;
2524 gchar *new_ref_str;
2526 ref_id_list = references_list_append(NULL, ref);
2527 if (!ref_id_list) return NULL;
2528 if (msgid && *msgid)
2529 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2531 for (;;) {
2532 gint len = 0;
2534 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2535 /* "<" + Message-ID + ">" + CR+LF+TAB */
2536 len += strlen((gchar *)cur->data) + 5;
2538 if (len > MAX_REFERENCES_LEN) {
2539 /* remove second message-ID */
2540 if (ref_id_list && ref_id_list->next &&
2541 ref_id_list->next->next) {
2542 g_free(ref_id_list->next->data);
2543 ref_id_list = g_slist_remove
2544 (ref_id_list, ref_id_list->next->data);
2545 } else {
2546 slist_free_strings(ref_id_list);
2547 g_slist_free(ref_id_list);
2548 return NULL;
2550 } else
2551 break;
2554 new_ref = g_string_new("");
2555 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2556 if (new_ref->len > 0)
2557 g_string_append(new_ref, "\n\t");
2558 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2561 slist_free_strings(ref_id_list);
2562 g_slist_free(ref_id_list);
2564 new_ref_str = new_ref->str;
2565 g_string_free(new_ref, FALSE);
2567 return new_ref_str;
2570 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2571 const gchar *fmt, const gchar *qmark,
2572 const gchar *body, gboolean rewrap,
2573 gboolean need_unescape,
2574 const gchar *err_msg)
2576 MsgInfo* dummyinfo = NULL;
2577 gchar *quote_str = NULL;
2578 gchar *buf;
2579 gboolean prev_autowrap;
2580 const gchar *trimmed_body = body;
2581 gint cursor_pos = -1;
2582 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2583 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2584 GtkTextIter iter;
2585 GtkTextMark *mark;
2588 SIGNAL_BLOCK(buffer);
2590 if (!msginfo) {
2591 dummyinfo = compose_msginfo_new_from_compose(compose);
2592 msginfo = dummyinfo;
2595 if (qmark != NULL) {
2596 #ifdef USE_ASPELL
2597 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
2598 compose->gtkaspell);
2599 #else
2600 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
2601 #endif
2602 quote_fmt_scan_string(qmark);
2603 quote_fmt_parse();
2605 buf = quote_fmt_get_buffer();
2606 if (buf == NULL)
2607 alertpanel_error(_("Quote mark format error."));
2608 else
2609 Xstrdup_a(quote_str, buf, goto error)
2612 if (fmt && *fmt != '\0') {
2614 if (trimmed_body)
2615 while (*trimmed_body == '\n')
2616 trimmed_body++;
2618 #ifdef USE_ASPELL
2619 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account,
2620 compose->gtkaspell);
2621 #else
2622 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account);
2623 #endif
2624 if (need_unescape) {
2625 gchar *tmp = NULL;
2627 /* decode \-escape sequences in the internal representation of the quote format */
2628 tmp = malloc(strlen(fmt)+1);
2629 pref_get_unescaped_pref(tmp, fmt);
2630 quote_fmt_scan_string(tmp);
2631 quote_fmt_parse();
2632 g_free(tmp);
2633 } else {
2634 quote_fmt_scan_string(fmt);
2635 quote_fmt_parse();
2638 buf = quote_fmt_get_buffer();
2639 if (buf == NULL) {
2640 gint line = quote_fmt_get_line();
2641 gchar *msg = g_strdup_printf(err_msg, line);
2642 alertpanel_error(msg);
2643 g_free(msg);
2644 goto error;
2646 } else
2647 buf = "";
2649 prev_autowrap = compose->autowrap;
2650 compose->autowrap = FALSE;
2652 mark = gtk_text_buffer_get_insert(buffer);
2653 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2654 if (g_utf8_validate(buf, -1, NULL)) {
2655 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2656 } else {
2657 gchar *tmpout = NULL;
2658 tmpout = conv_codeset_strdup
2659 (buf, conv_get_locale_charset_str_no_utf8(),
2660 CS_INTERNAL);
2661 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2662 g_free(tmpout);
2663 tmpout = g_malloc(strlen(buf)*2+1);
2664 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2666 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2667 g_free(tmpout);
2670 cursor_pos = quote_fmt_get_cursor_pos();
2671 compose->set_cursor_pos = cursor_pos;
2672 if (cursor_pos == -1) {
2673 cursor_pos = 0;
2675 gtk_text_buffer_get_start_iter(buffer, &iter);
2676 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2677 gtk_text_buffer_place_cursor(buffer, &iter);
2679 compose->autowrap = prev_autowrap;
2680 if (compose->autowrap && rewrap)
2681 compose_wrap_all(compose);
2683 goto ok;
2685 error:
2686 buf = NULL;
2688 SIGNAL_UNBLOCK(buffer);
2690 procmsg_msginfo_free( dummyinfo );
2692 return buf;
2695 /* if ml_post is of type addr@host and from is of type
2696 * addr-anything@host, return TRUE
2698 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2700 gchar *left_ml = NULL;
2701 gchar *right_ml = NULL;
2702 gchar *left_from = NULL;
2703 gchar *right_from = NULL;
2704 gboolean result = FALSE;
2706 if (!ml_post || !from)
2707 return FALSE;
2709 left_ml = g_strdup(ml_post);
2710 if (strstr(left_ml, "@")) {
2711 right_ml = strstr(left_ml, "@")+1;
2712 *(strstr(left_ml, "@")) = '\0';
2715 left_from = g_strdup(from);
2716 if (strstr(left_from, "@")) {
2717 right_from = strstr(left_from, "@")+1;
2718 *(strstr(left_from, "@")) = '\0';
2721 if (left_ml && left_from && right_ml && right_from
2722 && !strncmp(left_from, left_ml, strlen(left_ml))
2723 && !strcmp(right_from, right_ml)) {
2724 result = TRUE;
2726 g_free(left_ml);
2727 g_free(left_from);
2729 return result;
2732 static gboolean same_address(const gchar *addr1, const gchar *addr2)
2734 gchar *my_addr1, *my_addr2;
2736 if (!addr1 || !addr2)
2737 return FALSE;
2739 Xstrdup_a(my_addr1, addr1, return FALSE);
2740 Xstrdup_a(my_addr2, addr2, return FALSE);
2742 extract_address(my_addr1);
2743 extract_address(my_addr2);
2745 return !strcasecmp(my_addr1, my_addr2);
2748 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
2749 gboolean to_all, gboolean to_ml,
2750 gboolean to_sender,
2751 gboolean followup_and_reply_to)
2753 GSList *cc_list = NULL;
2754 GSList *cur;
2755 gchar *from = NULL;
2756 gchar *replyto = NULL;
2757 GHashTable *to_table;
2759 gboolean reply_to_ml = FALSE;
2760 gboolean default_reply_to = FALSE;
2762 g_return_if_fail(compose->account != NULL);
2763 g_return_if_fail(msginfo != NULL);
2765 reply_to_ml = to_ml && compose->ml_post;
2767 default_reply_to = msginfo->folder &&
2768 msginfo->folder->prefs->enable_default_reply_to;
2770 if (compose->account->protocol != A_NNTP) {
2771 if (reply_to_ml && !default_reply_to) {
2773 gboolean is_subscr = is_subscription(compose->ml_post,
2774 msginfo->from);
2775 if (!is_subscr) {
2776 /* normal answer to ml post with a reply-to */
2777 compose_entry_append(compose,
2778 compose->ml_post,
2779 COMPOSE_TO);
2780 if (compose->replyto
2781 && !same_address(compose->ml_post, compose->replyto))
2782 compose_entry_append(compose,
2783 compose->replyto,
2784 COMPOSE_CC);
2785 } else {
2786 /* answer to subscription confirmation */
2787 if (compose->replyto)
2788 compose_entry_append(compose,
2789 compose->replyto,
2790 COMPOSE_TO);
2791 else if (msginfo->from)
2792 compose_entry_append(compose,
2793 msginfo->from,
2794 COMPOSE_TO);
2797 else if (!(to_all || to_sender) && default_reply_to) {
2798 compose_entry_append(compose,
2799 msginfo->folder->prefs->default_reply_to,
2800 COMPOSE_TO);
2801 compose_entry_mark_default_to(compose,
2802 msginfo->folder->prefs->default_reply_to);
2803 } else {
2804 gchar *tmp1 = NULL;
2805 if (!msginfo->from)
2806 return;
2807 Xstrdup_a(tmp1, msginfo->from, return);
2808 extract_address(tmp1);
2809 if (to_all || to_sender ||
2810 !account_find_from_address(tmp1))
2811 compose_entry_append(compose,
2812 (compose->replyto && !to_sender)
2813 ? compose->replyto :
2814 msginfo->from ? msginfo->from : "",
2815 COMPOSE_TO);
2816 else if (!to_all && !to_sender) {
2817 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2818 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
2819 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2820 compose_entry_append(compose,
2821 msginfo->from ? msginfo->from : "",
2822 COMPOSE_TO);
2823 } else {
2824 /* replying to own mail, use original recp */
2825 compose_entry_append(compose,
2826 msginfo->to ? msginfo->to : "",
2827 COMPOSE_TO);
2828 compose_entry_append(compose,
2829 msginfo->cc ? msginfo->cc : "",
2830 COMPOSE_CC);
2834 } else {
2835 if (to_sender || (compose->followup_to &&
2836 !strncmp(compose->followup_to, "poster", 6)))
2837 compose_entry_append
2838 (compose,
2839 (compose->replyto ? compose->replyto :
2840 msginfo->from ? msginfo->from : ""),
2841 COMPOSE_TO);
2843 else if (followup_and_reply_to || to_all) {
2844 compose_entry_append
2845 (compose,
2846 (compose->replyto ? compose->replyto :
2847 msginfo->from ? msginfo->from : ""),
2848 COMPOSE_TO);
2850 compose_entry_append
2851 (compose,
2852 compose->followup_to ? compose->followup_to :
2853 compose->newsgroups ? compose->newsgroups : "",
2854 COMPOSE_NEWSGROUPS);
2856 else
2857 compose_entry_append
2858 (compose,
2859 compose->followup_to ? compose->followup_to :
2860 compose->newsgroups ? compose->newsgroups : "",
2861 COMPOSE_NEWSGROUPS);
2864 if (msginfo->subject && *msginfo->subject) {
2865 gchar *buf, *buf2;
2866 gchar *p;
2868 buf = p = g_strdup(msginfo->subject);
2869 p += subject_get_prefix_length(p);
2870 memmove(buf, p, strlen(p) + 1);
2872 buf2 = g_strdup_printf("Re: %s", buf);
2873 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2875 g_free(buf2);
2876 g_free(buf);
2877 } else
2878 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
2880 if (to_ml && compose->ml_post) return;
2881 if (!to_all || compose->account->protocol == A_NNTP) return;
2883 if (compose->replyto) {
2884 Xstrdup_a(replyto, compose->replyto, return);
2885 extract_address(replyto);
2887 if (msginfo->from) {
2888 Xstrdup_a(from, msginfo->from, return);
2889 extract_address(from);
2892 if (replyto && from)
2893 cc_list = address_list_append_with_comments(cc_list, from);
2894 if (to_all && msginfo->folder &&
2895 msginfo->folder->prefs->enable_default_reply_to)
2896 cc_list = address_list_append_with_comments(cc_list,
2897 msginfo->folder->prefs->default_reply_to);
2898 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
2899 cc_list = address_list_append_with_comments(cc_list, compose->cc);
2901 to_table = g_hash_table_new(g_str_hash, g_str_equal);
2902 if (replyto)
2903 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
2904 if (compose->account) {
2905 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
2906 GINT_TO_POINTER(1));
2908 /* remove address on To: and that of current account */
2909 for (cur = cc_list; cur != NULL; ) {
2910 GSList *next = cur->next;
2911 gchar *addr;
2913 addr = g_utf8_strdown(cur->data, -1);
2914 extract_address(addr);
2916 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
2917 cc_list = g_slist_remove(cc_list, cur->data);
2918 else
2919 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
2921 cur = next;
2923 hash_free_strings(to_table);
2924 g_hash_table_destroy(to_table);
2926 if (cc_list) {
2927 for (cur = cc_list; cur != NULL; cur = cur->next)
2928 compose_entry_append(compose, (gchar *)cur->data,
2929 COMPOSE_CC);
2930 slist_free_strings(cc_list);
2931 g_slist_free(cc_list);
2936 #define SET_ENTRY(entry, str) \
2938 if (str && *str) \
2939 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
2942 #define SET_ADDRESS(type, str) \
2944 if (str && *str) \
2945 compose_entry_append(compose, str, type); \
2948 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
2950 g_return_if_fail(msginfo != NULL);
2952 SET_ENTRY(subject_entry, msginfo->subject);
2953 SET_ENTRY(from_name, msginfo->from);
2954 SET_ADDRESS(COMPOSE_TO, msginfo->to);
2955 SET_ADDRESS(COMPOSE_CC, compose->cc);
2956 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
2957 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
2958 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
2959 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
2961 compose_update_priority_menu_item(compose);
2962 compose_update_privacy_system_menu_item(compose, FALSE);
2963 compose_show_first_last_header(compose, TRUE);
2966 #undef SET_ENTRY
2967 #undef SET_ADDRESS
2969 static void compose_insert_sig(Compose *compose, gboolean replace)
2971 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2972 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2973 GtkTextMark *mark;
2974 GtkTextIter iter, iter_end;
2975 gint cur_pos;
2976 gchar *search = NULL;
2977 gboolean prev_autowrap;
2978 gboolean found = FALSE, shift = FALSE;
2981 g_return_if_fail(compose->account != NULL);
2983 prev_autowrap = compose->autowrap;
2984 compose->autowrap = FALSE;
2986 g_signal_handlers_block_by_func(G_OBJECT(buffer),
2987 G_CALLBACK(compose_changed_cb),
2988 compose);
2990 mark = gtk_text_buffer_get_insert(buffer);
2991 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2992 cur_pos = gtk_text_iter_get_offset (&iter);
2994 gtk_text_buffer_get_end_iter(buffer, &iter);
2996 search = compose->sig_str;
2997 again:
2998 if (replace && search) {
2999 GtkTextIter first_iter, start_iter, end_iter;
3001 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3003 if (compose->sig_str[0] == '\0')
3004 found = FALSE;
3005 else
3006 found = gtk_text_iter_forward_search(&first_iter,
3007 search,
3008 GTK_TEXT_SEARCH_TEXT_ONLY,
3009 &start_iter, &end_iter,
3010 NULL);
3012 if (found) {
3013 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3014 iter = start_iter;
3017 if (replace && !found && search && strlen(search) > 2
3018 && search[0] == '\n' && search[1] == '\n') {
3019 search ++;
3020 shift = TRUE;
3021 goto again;
3024 g_free(compose->sig_str);
3025 compose->sig_str = compose_get_signature_str(compose);
3026 if (!compose->sig_str || (replace && !compose->account->auto_sig))
3027 compose->sig_str = g_strdup("");
3029 cur_pos = gtk_text_iter_get_offset(&iter);
3030 if (shift && found)
3031 gtk_text_buffer_insert(buffer, &iter, compose->sig_str + 1, -1);
3032 else
3033 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3034 /* skip \n\n */
3035 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3036 gtk_text_iter_forward_char(&iter);
3037 gtk_text_iter_forward_char(&iter);
3038 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3039 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3041 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3042 cur_pos = gtk_text_buffer_get_char_count (buffer);
3044 /* put the cursor where it should be
3045 * either where the quote_fmt says, either before the signature */
3046 if (compose->set_cursor_pos < 0)
3047 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3048 else
3049 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3050 compose->set_cursor_pos);
3052 gtk_text_buffer_place_cursor(buffer, &iter);
3053 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3054 G_CALLBACK(compose_changed_cb),
3055 compose);
3057 compose->autowrap = prev_autowrap;
3058 if (compose->autowrap)
3059 compose_wrap_all(compose);
3062 static gchar *compose_get_signature_str(Compose *compose)
3064 gchar *sig_body = NULL;
3065 gchar *sig_str = NULL;
3066 gchar *utf8_sig_str = NULL;
3068 g_return_val_if_fail(compose->account != NULL, NULL);
3070 if (!compose->account->sig_path)
3071 return NULL;
3073 if (compose->account->sig_type == SIG_FILE) {
3074 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3075 g_warning("can't open signature file: %s\n",
3076 compose->account->sig_path);
3077 return NULL;
3081 if (compose->account->sig_type == SIG_COMMAND)
3082 sig_body = get_command_output(compose->account->sig_path);
3083 else {
3084 gchar *tmp;
3086 tmp = file_read_to_str(compose->account->sig_path);
3087 if (!tmp)
3088 return NULL;
3089 sig_body = normalize_newlines(tmp);
3090 g_free(tmp);
3093 if (compose->account->sig_sep) {
3094 sig_str = g_strconcat("\n\n", compose->account->sig_sep, "\n", sig_body,
3095 NULL);
3096 g_free(sig_body);
3097 } else
3098 sig_str = g_strconcat("\n\n", sig_body, NULL);
3100 if (sig_str) {
3101 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3102 utf8_sig_str = sig_str;
3103 else {
3104 utf8_sig_str = conv_codeset_strdup
3105 (sig_str, conv_get_locale_charset_str_no_utf8(),
3106 CS_INTERNAL);
3107 g_free(sig_str);
3111 return utf8_sig_str;
3114 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3116 GtkTextView *text;
3117 GtkTextBuffer *buffer;
3118 GtkTextMark *mark;
3119 GtkTextIter iter;
3120 const gchar *cur_encoding;
3121 gchar buf[BUFFSIZE];
3122 gint len;
3123 FILE *fp;
3124 gboolean prev_autowrap;
3125 gboolean badtxt = FALSE;
3127 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3129 if ((fp = g_fopen(file, "rb")) == NULL) {
3130 FILE_OP_ERROR(file, "fopen");
3131 return COMPOSE_INSERT_READ_ERROR;
3134 prev_autowrap = compose->autowrap;
3135 compose->autowrap = FALSE;
3137 text = GTK_TEXT_VIEW(compose->text);
3138 buffer = gtk_text_view_get_buffer(text);
3139 mark = gtk_text_buffer_get_insert(buffer);
3140 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3142 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3143 G_CALLBACK(text_inserted),
3144 compose);
3146 cur_encoding = conv_get_locale_charset_str_no_utf8();
3148 while (fgets(buf, sizeof(buf), fp) != NULL) {
3149 gchar *str;
3151 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3152 str = g_strdup(buf);
3153 else
3154 str = conv_codeset_strdup
3155 (buf, cur_encoding, CS_INTERNAL);
3156 if (!str) continue;
3158 /* strip <CR> if DOS/Windows file,
3159 replace <CR> with <LF> if Macintosh file. */
3160 strcrchomp(str);
3161 len = strlen(str);
3162 if (len > 0 && str[len - 1] != '\n') {
3163 while (--len >= 0)
3164 if (str[len] == '\r') str[len] = '\n';
3167 gtk_text_buffer_insert(buffer, &iter, str, -1);
3168 g_free(str);
3171 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3172 G_CALLBACK(text_inserted),
3173 compose);
3174 compose->autowrap = prev_autowrap;
3175 if (compose->autowrap)
3176 compose_wrap_all(compose);
3178 fclose(fp);
3180 if (badtxt)
3181 return COMPOSE_INSERT_INVALID_CHARACTER;
3182 else
3183 return COMPOSE_INSERT_SUCCESS;
3186 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3187 const gchar *filename,
3188 const gchar *content_type)
3190 AttachInfo *ainfo;
3191 GtkTreeIter iter;
3192 FILE *fp;
3193 off_t size;
3194 GAuto *auto_ainfo;
3195 gchar *size_text;
3196 GtkListStore *store;
3197 gchar *name;
3198 gboolean has_binary = FALSE;
3200 if (!is_file_exist(file)) {
3201 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3202 gboolean result = FALSE;
3203 if (file_from_uri && is_file_exist(file_from_uri)) {
3204 result = compose_attach_append(
3205 compose, file_from_uri,
3206 filename,
3207 content_type);
3209 g_free(file_from_uri);
3210 if (result)
3211 return TRUE;
3212 alertpanel_error("File %s doesn't exist\n", filename);
3213 return FALSE;
3215 if ((size = get_file_size(file)) < 0) {
3216 alertpanel_error("Can't get file size of %s\n", filename);
3217 return FALSE;
3219 if (size == 0) {
3220 alertpanel_error(_("File %s is empty."), filename);
3221 return FALSE;
3223 if ((fp = g_fopen(file, "rb")) == NULL) {
3224 alertpanel_error(_("Can't read %s."), filename);
3225 return FALSE;
3227 fclose(fp);
3229 ainfo = g_new0(AttachInfo, 1);
3230 auto_ainfo = g_auto_pointer_new_with_free
3231 (ainfo, (GFreeFunc) compose_attach_info_free);
3232 ainfo->file = g_strdup(file);
3234 if (content_type) {
3235 ainfo->content_type = g_strdup(content_type);
3236 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3237 MsgInfo *msginfo;
3238 MsgFlags flags = {0, 0};
3240 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3241 ainfo->encoding = ENC_7BIT;
3242 else
3243 ainfo->encoding = ENC_8BIT;
3245 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3246 if (msginfo && msginfo->subject)
3247 name = g_strdup(msginfo->subject);
3248 else
3249 name = g_path_get_basename(filename ? filename : file);
3251 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3253 procmsg_msginfo_free(msginfo);
3254 } else {
3255 if (!g_ascii_strncasecmp(content_type, "text", 4))
3256 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3257 else
3258 ainfo->encoding = ENC_BASE64;
3259 name = g_path_get_basename(filename ? filename : file);
3260 ainfo->name = g_strdup(name);
3262 g_free(name);
3263 } else {
3264 ainfo->content_type = procmime_get_mime_type(file);
3265 if (!ainfo->content_type) {
3266 ainfo->content_type =
3267 g_strdup("application/octet-stream");
3268 ainfo->encoding = ENC_BASE64;
3269 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3270 ainfo->encoding =
3271 procmime_get_encoding_for_text_file(file, &has_binary);
3272 else
3273 ainfo->encoding = ENC_BASE64;
3274 name = g_path_get_basename(filename ? filename : file);
3275 ainfo->name = g_strdup(name);
3276 g_free(name);
3279 if (ainfo->name != NULL
3280 && !strcmp(ainfo->name, ".")) {
3281 g_free(ainfo->name);
3282 ainfo->name = NULL;
3285 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3286 g_free(ainfo->content_type);
3287 ainfo->content_type = g_strdup("application/octet-stream");
3290 ainfo->size = size;
3291 size_text = to_human_readable(size);
3293 store = GTK_LIST_STORE(gtk_tree_view_get_model
3294 (GTK_TREE_VIEW(compose->attach_clist)));
3296 gtk_list_store_append(store, &iter);
3297 gtk_list_store_set(store, &iter,
3298 COL_MIMETYPE, ainfo->content_type,
3299 COL_SIZE, size_text,
3300 COL_NAME, ainfo->name,
3301 COL_DATA, ainfo,
3302 COL_AUTODATA, auto_ainfo,
3303 -1);
3305 g_auto_pointer_free(auto_ainfo);
3306 return TRUE;
3309 static void compose_use_signing(Compose *compose, gboolean use_signing)
3311 GtkItemFactory *ifactory;
3312 GtkWidget *menuitem = NULL;
3314 compose->use_signing = use_signing;
3315 ifactory = gtk_item_factory_from_widget(compose->menubar);
3316 menuitem = gtk_item_factory_get_item
3317 (ifactory, "/Options/Sign");
3318 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3319 use_signing);
3322 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3324 GtkItemFactory *ifactory;
3325 GtkWidget *menuitem = NULL;
3327 compose->use_encryption = use_encryption;
3328 ifactory = gtk_item_factory_from_widget(compose->menubar);
3329 menuitem = gtk_item_factory_get_item
3330 (ifactory, "/Options/Encrypt");
3332 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3333 use_encryption);
3336 #define NEXT_PART_NOT_CHILD(info) \
3338 node = info->node; \
3339 while (node->children) \
3340 node = g_node_last_child(node); \
3341 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3344 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3346 MimeInfo *mimeinfo;
3347 MimeInfo *child;
3348 MimeInfo *firsttext = NULL;
3349 MimeInfo *encrypted = NULL;
3350 GNode *node;
3351 gchar *outfile;
3352 const gchar *partname = NULL;
3354 mimeinfo = procmime_scan_message(msginfo);
3355 if (!mimeinfo) return;
3357 if (mimeinfo->node->children == NULL) {
3358 procmime_mimeinfo_free_all(mimeinfo);
3359 return;
3362 /* find first content part */
3363 child = (MimeInfo *) mimeinfo->node->children->data;
3364 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3365 child = (MimeInfo *)child->node->children->data;
3367 if (child->type == MIMETYPE_TEXT) {
3368 firsttext = child;
3369 debug_print("First text part found\n");
3370 } else if (compose->mode == COMPOSE_REEDIT &&
3371 child->type == MIMETYPE_APPLICATION &&
3372 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3373 encrypted = (MimeInfo *)child->node->parent->data;
3376 child = (MimeInfo *) mimeinfo->node->children->data;
3377 while (child != NULL) {
3378 gint err;
3380 if (child == encrypted) {
3381 /* skip this part of tree */
3382 NEXT_PART_NOT_CHILD(child);
3383 continue;
3386 if (child->type == MIMETYPE_MULTIPART) {
3387 /* get the actual content */
3388 child = procmime_mimeinfo_next(child);
3389 continue;
3392 if (child == firsttext) {
3393 child = procmime_mimeinfo_next(child);
3394 continue;
3397 outfile = procmime_get_tmp_file_name(child);
3398 if ((err = procmime_get_part(outfile, child)) < 0)
3399 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3400 else {
3401 gchar *content_type;
3403 content_type = procmime_get_content_type_str(child->type, child->subtype);
3405 /* if we meet a pgp signature, we don't attach it, but
3406 * we force signing. */
3407 if ((strcmp(content_type, "application/pgp-signature") &&
3408 strcmp(content_type, "application/pkcs7-signature") &&
3409 strcmp(content_type, "application/x-pkcs7-signature"))
3410 || compose->mode == COMPOSE_REDIRECT) {
3411 partname = procmime_mimeinfo_get_parameter(child, "filename");
3412 if (partname == NULL)
3413 partname = procmime_mimeinfo_get_parameter(child, "name");
3414 if (partname == NULL)
3415 partname = "";
3416 compose_attach_append(compose, outfile,
3417 partname, content_type);
3418 } else {
3419 compose_force_signing(compose, compose->account);
3421 g_free(content_type);
3423 g_free(outfile);
3424 NEXT_PART_NOT_CHILD(child);
3426 procmime_mimeinfo_free_all(mimeinfo);
3429 #undef NEXT_PART_NOT_CHILD
3433 typedef enum {
3434 WAIT_FOR_INDENT_CHAR,
3435 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3436 } IndentState;
3438 /* return indent length, we allow:
3439 indent characters followed by indent characters or spaces/tabs,
3440 alphabets and numbers immediately followed by indent characters,
3441 and the repeating sequences of the above
3442 If quote ends with multiple spaces, only the first one is included. */
3443 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3444 const GtkTextIter *start, gint *len)
3446 GtkTextIter iter = *start;
3447 gunichar wc;
3448 gchar ch[6];
3449 gint clen;
3450 IndentState state = WAIT_FOR_INDENT_CHAR;
3451 gboolean is_space;
3452 gboolean is_indent;
3453 gint alnum_count = 0;
3454 gint space_count = 0;
3455 gint quote_len = 0;
3457 if (prefs_common.quote_chars == NULL) {
3458 return 0 ;
3461 while (!gtk_text_iter_ends_line(&iter)) {
3462 wc = gtk_text_iter_get_char(&iter);
3463 if (g_unichar_iswide(wc))
3464 break;
3465 clen = g_unichar_to_utf8(wc, ch);
3466 if (clen != 1)
3467 break;
3469 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3470 is_space = g_unichar_isspace(wc);
3472 if (state == WAIT_FOR_INDENT_CHAR) {
3473 if (!is_indent && !g_unichar_isalnum(wc))
3474 break;
3475 if (is_indent) {
3476 quote_len += alnum_count + space_count + 1;
3477 alnum_count = space_count = 0;
3478 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3479 } else
3480 alnum_count++;
3481 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3482 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3483 break;
3484 if (is_space)
3485 space_count++;
3486 else if (is_indent) {
3487 quote_len += alnum_count + space_count + 1;
3488 alnum_count = space_count = 0;
3489 } else {
3490 alnum_count++;
3491 state = WAIT_FOR_INDENT_CHAR;
3495 gtk_text_iter_forward_char(&iter);
3498 if (quote_len > 0 && space_count > 0)
3499 quote_len++;
3501 if (len)
3502 *len = quote_len;
3504 if (quote_len > 0) {
3505 iter = *start;
3506 gtk_text_iter_forward_chars(&iter, quote_len);
3507 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3510 return NULL;
3513 /* return TRUE if the line is itemized */
3514 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3515 const GtkTextIter *start)
3517 GtkTextIter iter = *start;
3518 gunichar wc;
3519 gchar ch[6];
3520 gint clen;
3522 if (gtk_text_iter_ends_line(&iter))
3523 return FALSE;
3525 while (1) {
3526 wc = gtk_text_iter_get_char(&iter);
3527 if (!g_unichar_isspace(wc))
3528 break;
3529 gtk_text_iter_forward_char(&iter);
3530 if (gtk_text_iter_ends_line(&iter))
3531 return FALSE;
3534 clen = g_unichar_to_utf8(wc, ch);
3535 if (clen != 1)
3536 return FALSE;
3538 if (!strchr("*-+", ch[0]))
3539 return FALSE;
3541 gtk_text_iter_forward_char(&iter);
3542 if (gtk_text_iter_ends_line(&iter))
3543 return FALSE;
3544 wc = gtk_text_iter_get_char(&iter);
3545 if (g_unichar_isspace(wc))
3546 return TRUE;
3548 return FALSE;
3551 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3552 const GtkTextIter *start,
3553 GtkTextIter *break_pos,
3554 gint max_col,
3555 gint quote_len)
3557 GtkTextIter iter = *start, line_end = *start;
3558 PangoLogAttr *attrs;
3559 gchar *str;
3560 gchar *p;
3561 gint len;
3562 gint i;
3563 gint col = 0;
3564 gint pos = 0;
3565 gboolean can_break = FALSE;
3566 gboolean do_break = FALSE;
3567 gboolean was_white = FALSE;
3568 gboolean prev_dont_break = FALSE;
3570 gtk_text_iter_forward_to_line_end(&line_end);
3571 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3572 len = g_utf8_strlen(str, -1);
3573 /* g_print("breaking line: %d: %s (len = %d)\n",
3574 gtk_text_iter_get_line(&iter), str, len); */
3575 attrs = g_new(PangoLogAttr, len + 1);
3577 pango_default_break(str, -1, NULL, attrs, len + 1);
3579 p = str;
3581 /* skip quote and leading spaces */
3582 for (i = 0; *p != '\0' && i < len; i++) {
3583 gunichar wc;
3585 wc = g_utf8_get_char(p);
3586 if (i >= quote_len && !g_unichar_isspace(wc))
3587 break;
3588 if (g_unichar_iswide(wc))
3589 col += 2;
3590 else if (*p == '\t')
3591 col += 8;
3592 else
3593 col++;
3594 p = g_utf8_next_char(p);
3597 for (; *p != '\0' && i < len; i++) {
3598 PangoLogAttr *attr = attrs + i;
3599 gunichar wc;
3600 gint uri_len;
3602 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3603 pos = i;
3605 was_white = attr->is_white;
3607 /* don't wrap URI */
3608 if ((uri_len = get_uri_len(p)) > 0) {
3609 col += uri_len;
3610 if (pos > 0 && col > max_col) {
3611 do_break = TRUE;
3612 break;
3614 i += uri_len - 1;
3615 p += uri_len;
3616 can_break = TRUE;
3617 continue;
3620 wc = g_utf8_get_char(p);
3621 if (g_unichar_iswide(wc)) {
3622 col += 2;
3623 if (prev_dont_break && can_break && attr->is_line_break)
3624 pos = i;
3625 } else if (*p == '\t')
3626 col += 8;
3627 else
3628 col++;
3629 if (pos > 0 && col > max_col) {
3630 do_break = TRUE;
3631 break;
3634 if (*p == '-' || *p == '/')
3635 prev_dont_break = TRUE;
3636 else
3637 prev_dont_break = FALSE;
3639 p = g_utf8_next_char(p);
3640 can_break = TRUE;
3643 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3645 g_free(attrs);
3646 g_free(str);
3648 *break_pos = *start;
3649 gtk_text_iter_set_line_offset(break_pos, pos);
3651 return do_break;
3654 static gboolean compose_join_next_line(Compose *compose,
3655 GtkTextBuffer *buffer,
3656 GtkTextIter *iter,
3657 const gchar *quote_str)
3659 GtkTextIter iter_ = *iter, cur, prev, next, end;
3660 PangoLogAttr attrs[3];
3661 gchar *str;
3662 gchar *next_quote_str;
3663 gunichar wc1, wc2;
3664 gint quote_len;
3665 gboolean keep_cursor = FALSE;
3667 if (!gtk_text_iter_forward_line(&iter_) ||
3668 gtk_text_iter_ends_line(&iter_))
3669 return FALSE;
3671 next_quote_str = compose_get_quote_str(buffer, &iter_, &quote_len);
3673 if ((quote_str || next_quote_str) &&
3674 strcmp2(quote_str, next_quote_str) != 0) {
3675 g_free(next_quote_str);
3676 return FALSE;
3678 g_free(next_quote_str);
3680 end = iter_;
3681 if (quote_len > 0) {
3682 gtk_text_iter_forward_chars(&end, quote_len);
3683 if (gtk_text_iter_ends_line(&end))
3684 return FALSE;
3687 /* don't join itemized lines */
3688 if (compose_is_itemized(buffer, &end))
3689 return FALSE;
3691 /* don't join signature separator */
3692 if (compose_is_sig_separator(compose, buffer, &iter_))
3693 return FALSE;
3695 /* delete quote str */
3696 if (quote_len > 0)
3697 gtk_text_buffer_delete(buffer, &iter_, &end);
3699 /* don't join line breaks put by the user */
3700 prev = cur = iter_;
3701 gtk_text_iter_backward_char(&cur);
3702 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3703 gtk_text_iter_forward_char(&cur);
3704 *iter = cur;
3705 return FALSE;
3707 gtk_text_iter_forward_char(&cur);
3708 /* delete linebreak and extra spaces */
3709 while (gtk_text_iter_backward_char(&cur)) {
3710 wc1 = gtk_text_iter_get_char(&cur);
3711 if (!g_unichar_isspace(wc1))
3712 break;
3713 prev = cur;
3715 next = cur = iter_;
3716 while (!gtk_text_iter_ends_line(&cur)) {
3717 wc1 = gtk_text_iter_get_char(&cur);
3718 if (!g_unichar_isspace(wc1))
3719 break;
3720 gtk_text_iter_forward_char(&cur);
3721 next = cur;
3723 if (!gtk_text_iter_equal(&prev, &next)) {
3724 GtkTextMark *mark;
3726 mark = gtk_text_buffer_get_insert(buffer);
3727 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
3728 if (gtk_text_iter_equal(&prev, &cur))
3729 keep_cursor = TRUE;
3730 gtk_text_buffer_delete(buffer, &prev, &next);
3732 iter_ = prev;
3734 /* insert space if required */
3735 gtk_text_iter_backward_char(&prev);
3736 wc1 = gtk_text_iter_get_char(&prev);
3737 wc2 = gtk_text_iter_get_char(&next);
3738 gtk_text_iter_forward_char(&next);
3739 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
3740 pango_default_break(str, -1, NULL, attrs, 3);
3741 if (!attrs[1].is_line_break ||
3742 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
3743 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
3744 if (keep_cursor) {
3745 gtk_text_iter_backward_char(&iter_);
3746 gtk_text_buffer_place_cursor(buffer, &iter_);
3749 g_free(str);
3751 *iter = iter_;
3752 return TRUE;
3755 #define ADD_TXT_POS(bp_, ep_, pti_) \
3756 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
3757 last = last->next; \
3758 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
3759 last->next = NULL; \
3760 } else { \
3761 g_warning("alloc error scanning URIs\n"); \
3764 static gboolean automatic_break = FALSE;
3765 static void compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
3767 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3768 GtkTextBuffer *buffer;
3769 GtkTextIter iter, break_pos, end_of_line;
3770 gchar *quote_str = NULL;
3771 gint quote_len;
3772 gboolean wrap_quote = prefs_common.linewrap_quote;
3773 gboolean prev_autowrap = compose->autowrap;
3774 gint startq_offset = -1, noq_offset = -1;
3775 gint uri_start = -1, uri_stop = -1;
3776 gint nouri_start = -1, nouri_stop = -1;
3777 gint num_blocks = 0;
3778 gint quotelevel = -1;
3780 compose->autowrap = FALSE;
3782 buffer = gtk_text_view_get_buffer(text);
3783 undo_wrapping(compose->undostruct, TRUE);
3784 if (par_iter) {
3785 iter = *par_iter;
3786 } else {
3787 GtkTextMark *mark;
3788 mark = gtk_text_buffer_get_insert(buffer);
3789 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3792 /* move to paragraph start */
3793 gtk_text_iter_set_line_offset(&iter, 0);
3794 if (gtk_text_iter_ends_line(&iter)) {
3795 while (gtk_text_iter_ends_line(&iter) &&
3796 gtk_text_iter_forward_line(&iter))
3798 } else {
3799 while (gtk_text_iter_backward_line(&iter)) {
3800 if (gtk_text_iter_ends_line(&iter)) {
3801 gtk_text_iter_forward_line(&iter);
3802 break;
3807 /* go until paragraph end (empty line) */
3809 while (!gtk_text_iter_ends_line(&iter)) {
3810 gchar *scanpos = NULL;
3811 /* parse table - in order of priority */
3812 struct table {
3813 const gchar *needle; /* token */
3815 /* token search function */
3816 gchar *(*search) (const gchar *haystack,
3817 const gchar *needle);
3818 /* part parsing function */
3819 gboolean (*parse) (const gchar *start,
3820 const gchar *scanpos,
3821 const gchar **bp_,
3822 const gchar **ep_,
3823 gboolean hdr);
3824 /* part to URI function */
3825 gchar *(*build_uri) (const gchar *bp,
3826 const gchar *ep);
3829 static struct table parser[] = {
3830 {"http://", strcasestr, get_uri_part, make_uri_string},
3831 {"https://", strcasestr, get_uri_part, make_uri_string},
3832 {"ftp://", strcasestr, get_uri_part, make_uri_string},
3833 {"sftp://", strcasestr, get_uri_part, make_uri_string},
3834 {"www.", strcasestr, get_uri_part, make_http_string},
3835 {"mailto:", strcasestr, get_uri_part, make_uri_string},
3836 {"@", strcasestr, get_email_part, make_email_string}
3838 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
3839 gint last_index = PARSE_ELEMS;
3840 gint n;
3841 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
3842 gint walk_pos;
3844 if (!prev_autowrap && num_blocks == 0) {
3845 num_blocks++;
3846 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3847 G_CALLBACK(text_inserted),
3848 compose);
3850 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
3851 goto colorize;
3853 uri_start = uri_stop = -1;
3854 quote_len = 0;
3855 quote_str = compose_get_quote_str(buffer, &iter, &quote_len);
3857 if (quote_str) {
3858 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
3859 if (startq_offset == -1)
3860 startq_offset = gtk_text_iter_get_offset(&iter);
3861 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
3862 if (quotelevel > 2) {
3863 /* recycle colors */
3864 if (prefs_common.recycle_quote_colors)
3865 quotelevel %= 3;
3866 else
3867 quotelevel = 2;
3869 if (!wrap_quote) {
3870 goto colorize;
3872 } else {
3873 if (startq_offset == -1)
3874 noq_offset = gtk_text_iter_get_offset(&iter);
3875 quotelevel = -1;
3878 if (prev_autowrap == FALSE && !force && !wrap_quote) {
3879 goto colorize;
3881 if (compose_get_line_break_pos(buffer, &iter, &break_pos,
3882 prefs_common.linewrap_len,
3883 quote_len)) {
3884 GtkTextIter prev, next, cur;
3886 if (prev_autowrap != FALSE || force) {
3887 automatic_break = TRUE;
3888 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3889 automatic_break = FALSE;
3890 } else if (quote_str && wrap_quote) {
3891 automatic_break = TRUE;
3892 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
3893 automatic_break = FALSE;
3894 } else
3895 goto colorize;
3896 /* remove trailing spaces */
3897 cur = break_pos;
3898 gtk_text_iter_backward_char(&cur);
3899 prev = next = cur;
3900 while (!gtk_text_iter_starts_line(&cur)) {
3901 gunichar wc;
3903 gtk_text_iter_backward_char(&cur);
3904 wc = gtk_text_iter_get_char(&cur);
3905 if (!g_unichar_isspace(wc))
3906 break;
3907 prev = cur;
3909 if (!gtk_text_iter_equal(&prev, &next)) {
3910 gtk_text_buffer_delete(buffer, &prev, &next);
3911 break_pos = next;
3912 gtk_text_iter_forward_char(&break_pos);
3915 if (quote_str)
3916 gtk_text_buffer_insert(buffer, &break_pos,
3917 quote_str, -1);
3919 iter = break_pos;
3920 compose_join_next_line(compose, buffer, &iter, quote_str);
3922 /* move iter to current line start */
3923 gtk_text_iter_set_line_offset(&iter, 0);
3924 if (quote_str) {
3925 g_free(quote_str);
3926 quote_str = NULL;
3928 continue;
3929 } else {
3930 /* move iter to next line start */
3931 iter = break_pos;
3934 colorize:
3935 if (!prev_autowrap && num_blocks > 0) {
3936 num_blocks--;
3937 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3938 G_CALLBACK(text_inserted),
3939 compose);
3941 end_of_line = iter;
3942 while (!gtk_text_iter_ends_line(&end_of_line)) {
3943 gtk_text_iter_forward_char(&end_of_line);
3945 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
3947 nouri_start = gtk_text_iter_get_offset(&iter);
3948 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
3950 walk_pos = gtk_text_iter_get_offset(&iter);
3951 /* FIXME: this looks phony. scanning for anything in the parse table */
3952 for (n = 0; n < PARSE_ELEMS; n++) {
3953 gchar *tmp;
3955 tmp = parser[n].search(walk, parser[n].needle);
3956 if (tmp) {
3957 if (scanpos == NULL || tmp < scanpos) {
3958 scanpos = tmp;
3959 last_index = n;
3964 bp = ep = 0;
3965 if (scanpos) {
3966 /* check if URI can be parsed */
3967 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
3968 (const gchar **)&ep, FALSE)
3969 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
3970 walk = ep;
3971 } else
3972 walk = scanpos +
3973 strlen(parser[last_index].needle);
3975 if (bp && ep) {
3976 uri_start = walk_pos + (bp - o_walk);
3977 uri_stop = walk_pos + (ep - o_walk);
3979 g_free(o_walk);
3980 o_walk = NULL;
3981 gtk_text_iter_forward_line(&iter);
3982 g_free(quote_str);
3983 quote_str = NULL;
3984 if (startq_offset != -1) {
3985 GtkTextIter startquote, endquote;
3986 gtk_text_buffer_get_iter_at_offset(
3987 buffer, &startquote, startq_offset);
3988 endquote = iter;
3990 switch (quotelevel) {
3991 case 0: gtk_text_buffer_apply_tag_by_name(
3992 buffer, "quote0", &startquote, &endquote);
3993 break;
3994 case 1: gtk_text_buffer_apply_tag_by_name(
3995 buffer, "quote1", &startquote, &endquote);
3996 break;
3997 case 2: gtk_text_buffer_apply_tag_by_name(
3998 buffer, "quote2", &startquote, &endquote);
3999 break;
4001 startq_offset = -1;
4002 } else if (noq_offset != -1) {
4003 GtkTextIter startnoquote, endnoquote;
4004 gtk_text_buffer_get_iter_at_offset(
4005 buffer, &startnoquote, noq_offset);
4006 endnoquote = iter;
4007 gtk_text_buffer_remove_tag_by_name(
4008 buffer, "quote0", &startnoquote, &endnoquote);
4009 gtk_text_buffer_remove_tag_by_name(
4010 buffer, "quote1", &startnoquote, &endnoquote);
4011 gtk_text_buffer_remove_tag_by_name(
4012 buffer, "quote2", &startnoquote, &endnoquote);
4013 noq_offset = -1;
4016 /* always */ {
4017 GtkTextIter nouri_start_iter, nouri_end_iter;
4018 gtk_text_buffer_get_iter_at_offset(
4019 buffer, &nouri_start_iter, nouri_start);
4020 gtk_text_buffer_get_iter_at_offset(
4021 buffer, &nouri_end_iter, nouri_stop);
4022 gtk_text_buffer_remove_tag_by_name(
4023 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4025 if (uri_start > 0 && uri_stop > 0) {
4026 GtkTextIter uri_start_iter, uri_end_iter;
4027 gtk_text_buffer_get_iter_at_offset(
4028 buffer, &uri_start_iter, uri_start);
4029 gtk_text_buffer_get_iter_at_offset(
4030 buffer, &uri_end_iter, uri_stop);
4031 gtk_text_buffer_apply_tag_by_name(
4032 buffer, "link", &uri_start_iter, &uri_end_iter);
4036 if (par_iter)
4037 *par_iter = iter;
4038 undo_wrapping(compose->undostruct, FALSE);
4039 compose->autowrap = prev_autowrap;
4042 void compose_action_cb(void *data)
4044 Compose *compose = (Compose *)data;
4045 compose_wrap_all(compose);
4048 static void compose_wrap_all(Compose *compose)
4050 compose_wrap_all_full(compose, FALSE);
4053 static void compose_wrap_all_full(Compose *compose, gboolean force)
4055 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4056 GtkTextBuffer *buffer;
4057 GtkTextIter iter;
4059 buffer = gtk_text_view_get_buffer(text);
4061 gtk_text_buffer_get_start_iter(buffer, &iter);
4062 while (!gtk_text_iter_is_end(&iter))
4063 compose_beautify_paragraph(compose, &iter, force);
4067 static void compose_set_title(Compose *compose)
4069 gchar *str;
4070 gchar *edited;
4071 gchar *subject;
4073 edited = compose->modified ? _(" [Edited]") : "";
4075 subject = gtk_editable_get_chars(
4076 GTK_EDITABLE(compose->subject_entry), 0, -1);
4078 #ifndef MAEMO
4079 if (subject && strlen(subject))
4080 str = g_strdup_printf(_("%s - Compose message%s"),
4081 subject, edited);
4082 else
4083 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4084 #else
4085 str = g_strdup(_("Compose message"));
4086 #endif
4088 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4089 g_free(str);
4090 g_free(subject);
4094 * compose_current_mail_account:
4096 * Find a current mail account (the currently selected account, or the
4097 * default account, if a news account is currently selected). If a
4098 * mail account cannot be found, display an error message.
4100 * Return value: Mail account, or NULL if not found.
4102 static PrefsAccount *
4103 compose_current_mail_account(void)
4105 PrefsAccount *ac;
4107 if (cur_account && cur_account->protocol != A_NNTP)
4108 ac = cur_account;
4109 else {
4110 ac = account_get_default();
4111 if (!ac || ac->protocol == A_NNTP) {
4112 alertpanel_error(_("Account for sending mail is not specified.\n"
4113 "Please select a mail account before sending."));
4114 return NULL;
4117 return ac;
4120 #define QUOTE_IF_REQUIRED(out, str) \
4122 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4123 gchar *__tmp; \
4124 gint len; \
4126 len = strlen(str) + 3; \
4127 if ((__tmp = alloca(len)) == NULL) { \
4128 g_warning("can't allocate memory\n"); \
4129 g_string_free(header, TRUE); \
4130 return NULL; \
4132 g_snprintf(__tmp, len, "\"%s\"", str); \
4133 out = __tmp; \
4134 } else { \
4135 gchar *__tmp; \
4137 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4138 g_warning("can't allocate memory\n"); \
4139 g_string_free(header, TRUE); \
4140 return NULL; \
4141 } else \
4142 strcpy(__tmp, str); \
4144 out = __tmp; \
4148 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4150 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4151 gchar *__tmp; \
4152 gint len; \
4154 len = strlen(str) + 3; \
4155 if ((__tmp = alloca(len)) == NULL) { \
4156 g_warning("can't allocate memory\n"); \
4157 errret; \
4159 g_snprintf(__tmp, len, "\"%s\"", str); \
4160 out = __tmp; \
4161 } else { \
4162 gchar *__tmp; \
4164 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4165 g_warning("can't allocate memory\n"); \
4166 errret; \
4167 } else \
4168 strcpy(__tmp, str); \
4170 out = __tmp; \
4174 static void compose_select_account(Compose *compose, PrefsAccount *account,
4175 gboolean init)
4177 GtkItemFactory *ifactory;
4178 gchar *from = NULL;
4180 g_return_if_fail(account != NULL);
4182 compose->account = account;
4184 if (account->name && *account->name) {
4185 gchar *buf;
4186 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4187 from = g_strdup_printf("%s <%s>",
4188 buf, account->address);
4189 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4190 } else {
4191 from = g_strdup_printf("<%s>",
4192 account->address);
4193 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4196 g_free(from);
4198 compose_set_title(compose);
4200 ifactory = gtk_item_factory_from_widget(compose->menubar);
4202 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4203 menu_set_active(ifactory, "/Options/Sign", TRUE);
4204 else
4205 menu_set_active(ifactory, "/Options/Sign", FALSE);
4206 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4207 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4208 else
4209 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4211 activate_privacy_system(compose, account, FALSE);
4213 if (!init && compose->mode != COMPOSE_REDIRECT) {
4214 undo_block(compose->undostruct);
4215 compose_insert_sig(compose, TRUE);
4216 undo_unblock(compose->undostruct);
4219 #ifdef USE_ASPELL
4220 /* use account's dict info if set */
4221 if (compose->gtkaspell) {
4222 if (account->enable_default_dictionary)
4223 gtkaspell_change_dict(compose->gtkaspell,
4224 account->default_dictionary, FALSE);
4225 if (account->enable_default_alt_dictionary)
4226 gtkaspell_change_alt_dict(compose->gtkaspell,
4227 account->default_alt_dictionary);
4228 if (account->enable_default_dictionary
4229 || account->enable_default_alt_dictionary)
4230 compose_spell_menu_changed(compose);
4232 #endif
4235 gboolean compose_check_for_valid_recipient(Compose *compose) {
4236 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4237 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4238 gboolean recipient_found = FALSE;
4239 GSList *list;
4240 gchar **strptr;
4242 /* free to and newsgroup list */
4243 slist_free_strings(compose->to_list);
4244 g_slist_free(compose->to_list);
4245 compose->to_list = NULL;
4247 slist_free_strings(compose->newsgroup_list);
4248 g_slist_free(compose->newsgroup_list);
4249 compose->newsgroup_list = NULL;
4251 /* search header entries for to and newsgroup entries */
4252 for (list = compose->header_list; list; list = list->next) {
4253 gchar *header;
4254 gchar *entry;
4255 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4256 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4258 g_strstrip(entry);
4259 if (entry[0] != '\0') {
4260 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4261 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4262 compose->to_list = address_list_append(compose->to_list, entry);
4263 recipient_found = TRUE;
4266 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4267 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4268 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4269 recipient_found = TRUE;
4273 g_free(header);
4274 g_free(entry);
4276 return recipient_found;
4279 static gboolean compose_check_for_set_recipients(Compose *compose)
4281 if (compose->account->set_autocc && compose->account->auto_cc) {
4282 gboolean found_other = FALSE;
4283 GSList *list;
4284 /* search header entries for to and newsgroup entries */
4285 for (list = compose->header_list; list; list = list->next) {
4286 gchar *entry;
4287 gchar *header;
4288 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4289 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4290 g_strstrip(entry);
4291 if (strcmp(entry, compose->account->auto_cc)
4292 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4293 found_other = TRUE;
4294 g_free(entry);
4295 break;
4297 g_free(entry);
4298 g_free(header);
4300 if (!found_other) {
4301 AlertValue aval;
4302 if (compose->batch) {
4303 gtk_widget_show_all(compose->window);
4305 aval = alertpanel(_("Send"),
4306 _("The only recipient is the default CC address. Send anyway?"),
4307 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4308 if (aval != G_ALERTALTERNATE)
4309 return FALSE;
4312 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4313 gboolean found_other = FALSE;
4314 GSList *list;
4315 /* search header entries for to and newsgroup entries */
4316 for (list = compose->header_list; list; list = list->next) {
4317 gchar *entry;
4318 gchar *header;
4319 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4320 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
4321 g_strstrip(entry);
4322 if (strcmp(entry, compose->account->auto_bcc)
4323 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4324 found_other = TRUE;
4325 g_free(entry);
4326 break;
4328 g_free(entry);
4329 g_free(header);
4331 if (!found_other) {
4332 AlertValue aval;
4333 if (compose->batch) {
4334 gtk_widget_show_all(compose->window);
4336 aval = alertpanel(_("Send"),
4337 _("The only recipient is the default BCC address. Send anyway?"),
4338 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4339 if (aval != G_ALERTALTERNATE)
4340 return FALSE;
4343 return TRUE;
4346 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4348 const gchar *str;
4350 if (compose_check_for_valid_recipient(compose) == FALSE) {
4351 if (compose->batch) {
4352 gtk_widget_show_all(compose->window);
4354 alertpanel_error(_("Recipient is not specified."));
4355 return FALSE;
4358 if (compose_check_for_set_recipients(compose) == FALSE) {
4359 return FALSE;
4362 if (!compose->batch) {
4363 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4364 if (*str == '\0' && check_everything == TRUE &&
4365 compose->mode != COMPOSE_REDIRECT) {
4366 AlertValue aval;
4368 aval = alertpanel(_("Send"),
4369 _("Subject is empty. Send it anyway?"),
4370 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4371 if (aval != G_ALERTALTERNATE)
4372 return FALSE;
4376 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4377 return FALSE;
4379 return TRUE;
4382 gint compose_send(Compose *compose)
4384 gint msgnum;
4385 FolderItem *folder = NULL;
4386 gint val = -1;
4387 gchar *msgpath = NULL;
4388 gboolean discard_window = FALSE;
4389 gchar *errstr = NULL;
4390 gchar *tmsgid = NULL;
4391 MainWindow *mainwin = mainwindow_get_mainwindow();
4392 gboolean queued_removed = FALSE;
4394 if (prefs_common.send_dialog_mode != SEND_DIALOG_ALWAYS
4395 || compose->batch == TRUE)
4396 discard_window = TRUE;
4398 compose_allow_user_actions (compose, FALSE);
4399 compose->sending = TRUE;
4401 if (compose_check_entries(compose, TRUE) == FALSE) {
4402 if (compose->batch) {
4403 gtk_widget_show_all(compose->window);
4405 goto bail;
4408 inc_lock();
4409 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4411 if (val) {
4412 if (compose->batch) {
4413 gtk_widget_show_all(compose->window);
4415 if (val == -4) {
4416 alertpanel_error(_("Could not queue message for sending:\n\n"
4417 "Charset conversion failed."));
4418 } else if (val == -5) {
4419 alertpanel_error(_("Could not queue message for sending:\n\n"
4420 "Couldn't get recipient encryption key."));
4421 } else if (val == -6) {
4422 /* silent error */
4423 } else if (val == -3) {
4424 if (privacy_peek_error())
4425 alertpanel_error(_("Could not queue message for sending:\n\n"
4426 "Signature failed: %s"), privacy_get_error());
4427 } else if (val == -2 && errno != 0) {
4428 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4429 } else {
4430 alertpanel_error(_("Could not queue message for sending."));
4432 goto bail;
4435 tmsgid = g_strdup(compose->msgid);
4436 if (discard_window) {
4437 compose->sending = FALSE;
4438 compose_close(compose);
4439 /* No more compose access in the normal codepath
4440 * after this point! */
4441 compose = NULL;
4444 if (msgnum == 0) {
4445 alertpanel_error(_("The message was queued but could not be "
4446 "sent.\nUse \"Send queued messages\" from "
4447 "the main window to retry."));
4448 if (!discard_window) {
4449 goto bail;
4451 inc_unlock();
4452 g_free(tmsgid);
4453 return -1;
4455 if (msgpath == NULL) {
4456 msgpath = folder_item_fetch_msg(folder, msgnum);
4457 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4458 g_free(msgpath);
4459 } else {
4460 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4461 g_unlink(msgpath);
4462 g_free(msgpath);
4464 if (!discard_window) {
4465 if (val != 0) {
4466 if (!queued_removed)
4467 folder_item_remove_msg(folder, msgnum);
4468 folder_item_scan(folder);
4469 if (tmsgid) {
4470 /* make sure we delete that */
4471 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4472 if (tmp) {
4473 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4474 folder_item_remove_msg(folder, tmp->msgnum);
4475 procmsg_msginfo_free(tmp);
4481 if (val == 0) {
4482 if (!queued_removed)
4483 folder_item_remove_msg(folder, msgnum);
4484 folder_item_scan(folder);
4485 if (tmsgid) {
4486 /* make sure we delete that */
4487 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4488 if (tmp) {
4489 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4490 folder_item_remove_msg(folder, tmp->msgnum);
4491 procmsg_msginfo_free(tmp);
4494 if (!discard_window) {
4495 compose->sending = FALSE;
4496 compose_allow_user_actions (compose, TRUE);
4497 compose_close(compose);
4499 } else {
4500 if (errstr) {
4501 gchar *tmp = g_strdup_printf(_("%s\nUse \"Send queued messages\" from "
4502 "the main window to retry."), errstr);
4503 g_free(errstr);
4504 alertpanel_error_log(tmp);
4505 g_free(tmp);
4506 } else {
4507 alertpanel_error_log(_("The message was queued but could not be "
4508 "sent.\nUse \"Send queued messages\" from "
4509 "the main window to retry."));
4511 if (!discard_window) {
4512 goto bail;
4514 inc_unlock();
4515 g_free(tmsgid);
4516 return -1;
4518 g_free(tmsgid);
4519 inc_unlock();
4520 toolbar_main_set_sensitive(mainwin);
4521 main_window_set_menu_sensitive(mainwin);
4522 return 0;
4524 bail:
4525 inc_unlock();
4526 g_free(tmsgid);
4527 compose_allow_user_actions (compose, TRUE);
4528 compose->sending = FALSE;
4529 compose->modified = TRUE;
4530 toolbar_main_set_sensitive(mainwin);
4531 main_window_set_menu_sensitive(mainwin);
4533 return -1;
4536 static gboolean compose_use_attach(Compose *compose)
4538 GtkTreeModel *model = gtk_tree_view_get_model
4539 (GTK_TREE_VIEW(compose->attach_clist));
4540 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4543 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4544 FILE *fp)
4546 gchar buf[BUFFSIZE];
4547 gchar *str;
4548 gboolean first_to_address;
4549 gboolean first_cc_address;
4550 GSList *list;
4551 ComposeHeaderEntry *headerentry;
4552 const gchar *headerentryname;
4553 const gchar *cc_hdr;
4554 const gchar *to_hdr;
4556 debug_print("Writing redirect header\n");
4558 cc_hdr = prefs_common_translated_header_name("Cc:");
4559 to_hdr = prefs_common_translated_header_name("To:");
4561 first_to_address = TRUE;
4562 for (list = compose->header_list; list; list = list->next) {
4563 headerentry = ((ComposeHeaderEntry *)list->data);
4564 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4566 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4567 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4568 Xstrdup_a(str, entstr, return -1);
4569 g_strstrip(str);
4570 if (str[0] != '\0') {
4571 compose_convert_header
4572 (compose, buf, sizeof(buf), str,
4573 strlen("Resent-To") + 2, TRUE);
4575 if (first_to_address) {
4576 fprintf(fp, "Resent-To: ");
4577 first_to_address = FALSE;
4578 } else {
4579 fprintf(fp, ",");
4581 fprintf(fp, "%s", buf);
4585 if (!first_to_address) {
4586 fprintf(fp, "\n");
4589 first_cc_address = TRUE;
4590 for (list = compose->header_list; list; list = list->next) {
4591 headerentry = ((ComposeHeaderEntry *)list->data);
4592 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
4594 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4595 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4596 Xstrdup_a(str, strg, return -1);
4597 g_strstrip(str);
4598 if (str[0] != '\0') {
4599 compose_convert_header
4600 (compose, buf, sizeof(buf), str,
4601 strlen("Resent-Cc") + 2, TRUE);
4603 if (first_cc_address) {
4604 fprintf(fp, "Resent-Cc: ");
4605 first_cc_address = FALSE;
4606 } else {
4607 fprintf(fp, ",");
4609 fprintf(fp, "%s", buf);
4613 if (!first_cc_address) {
4614 fprintf(fp, "\n");
4617 return(0);
4620 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4622 gchar buf[BUFFSIZE];
4623 gchar *str;
4624 const gchar *entstr;
4625 /* struct utsname utsbuf; */
4627 g_return_val_if_fail(fp != NULL, -1);
4628 g_return_val_if_fail(compose->account != NULL, -1);
4629 g_return_val_if_fail(compose->account->address != NULL, -1);
4631 /* Resent-Date */
4632 get_rfc822_date(buf, sizeof(buf));
4633 fprintf(fp, "Resent-Date: %s\n", buf);
4635 /* Resent-From */
4636 if (compose->account->name && *compose->account->name) {
4637 compose_convert_header
4638 (compose, buf, sizeof(buf), compose->account->name,
4639 strlen("From: "), TRUE);
4640 fprintf(fp, "Resent-From: %s <%s>\n",
4641 buf, compose->account->address);
4642 } else
4643 fprintf(fp, "Resent-From: %s\n", compose->account->address);
4645 /* Subject */
4646 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4647 if (*entstr != '\0') {
4648 Xstrdup_a(str, entstr, return -1);
4649 g_strstrip(str);
4650 if (*str != '\0') {
4651 compose_convert_header(compose, buf, sizeof(buf), str,
4652 strlen("Subject: "), FALSE);
4653 fprintf(fp, "Subject: %s\n", buf);
4657 /* Resent-Message-ID */
4658 if (compose->account->gen_msgid) {
4659 generate_msgid(buf, sizeof(buf));
4660 fprintf(fp, "Resent-Message-ID: <%s>\n", buf);
4661 compose->msgid = g_strdup(buf);
4664 compose_redirect_write_headers_from_headerlist(compose, fp);
4666 /* separator between header and body */
4667 fputs("\n", fp);
4669 return 0;
4672 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
4674 FILE *fp;
4675 size_t len;
4676 gchar buf[BUFFSIZE];
4677 int i = 0;
4678 gboolean skip = FALSE;
4679 gchar *not_included[]={
4680 "Return-Path:", "Delivered-To:", "Received:",
4681 "Subject:", "X-UIDL:", "AF:",
4682 "NF:", "PS:", "SRH:",
4683 "SFN:", "DSR:", "MID:",
4684 "CFG:", "PT:", "S:",
4685 "RQ:", "SSV:", "NSV:",
4686 "SSH:", "R:", "MAID:",
4687 "NAID:", "RMID:", "FMID:",
4688 "SCF:", "RRCPT:", "NG:",
4689 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
4690 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
4691 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
4692 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
4693 NULL
4695 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
4696 FILE_OP_ERROR(compose->redirect_filename, "fopen");
4697 return -1;
4700 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
4701 skip = FALSE;
4702 for (i = 0; not_included[i] != NULL; i++) {
4703 if (g_ascii_strncasecmp(buf, not_included[i],
4704 strlen(not_included[i])) == 0) {
4705 skip = TRUE;
4706 break;
4709 if (skip)
4710 continue;
4711 if (fputs(buf, fdest) == -1)
4712 goto error;
4714 if (!prefs_common.redirect_keep_from) {
4715 if (g_ascii_strncasecmp(buf, "From:",
4716 strlen("From:")) == 0) {
4717 fputs(" (by way of ", fdest);
4718 if (compose->account->name
4719 && *compose->account->name) {
4720 compose_convert_header
4721 (compose, buf, sizeof(buf),
4722 compose->account->name,
4723 strlen("From: "),
4724 FALSE);
4725 fprintf(fdest, "%s <%s>",
4726 buf,
4727 compose->account->address);
4728 } else
4729 fprintf(fdest, "%s",
4730 compose->account->address);
4731 fputs(")", fdest);
4735 if (fputs("\n", fdest) == -1)
4736 goto error;
4739 compose_redirect_write_headers(compose, fdest);
4741 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4742 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
4743 goto error;
4746 fclose(fp);
4748 return 0;
4749 error:
4750 fclose(fp);
4752 return -1;
4755 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
4757 GtkTextBuffer *buffer;
4758 GtkTextIter start, end;
4759 gchar *chars;
4760 gchar *buf;
4761 const gchar *out_codeset;
4762 EncodingType encoding;
4763 MimeInfo *mimemsg, *mimetext;
4764 gint line;
4766 if (action == COMPOSE_WRITE_FOR_SEND)
4767 attach_parts = TRUE;
4769 /* create message MimeInfo */
4770 mimemsg = procmime_mimeinfo_new();
4771 mimemsg->type = MIMETYPE_MESSAGE;
4772 mimemsg->subtype = g_strdup("rfc822");
4773 mimemsg->content = MIMECONTENT_MEM;
4774 mimemsg->tmp = TRUE; /* must free content later */
4775 mimemsg->data.mem = compose_get_header(compose);
4777 /* Create text part MimeInfo */
4778 /* get all composed text */
4779 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4780 gtk_text_buffer_get_start_iter(buffer, &start);
4781 gtk_text_buffer_get_end_iter(buffer, &end);
4782 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4783 if (is_ascii_str(chars)) {
4784 buf = chars;
4785 chars = NULL;
4786 out_codeset = CS_US_ASCII;
4787 encoding = ENC_7BIT;
4788 } else {
4789 const gchar *src_codeset = CS_INTERNAL;
4791 out_codeset = conv_get_charset_str(compose->out_encoding);
4793 if (!out_codeset) {
4794 gchar *test_conv_global_out = NULL;
4795 gchar *test_conv_reply = NULL;
4797 /* automatic mode. be automatic. */
4798 codeconv_set_strict(TRUE);
4800 out_codeset = conv_get_outgoing_charset_str();
4801 if (out_codeset) {
4802 debug_print("trying to convert to %s\n", out_codeset);
4803 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
4806 if (!test_conv_global_out && compose->orig_charset
4807 && strcmp(compose->orig_charset, CS_US_ASCII)) {
4808 out_codeset = compose->orig_charset;
4809 debug_print("failure; trying to convert to %s\n", out_codeset);
4810 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
4813 if (!test_conv_global_out && !test_conv_reply) {
4814 /* we're lost */
4815 out_codeset = CS_INTERNAL;
4816 debug_print("failure; finally using %s\n", out_codeset);
4818 g_free(test_conv_global_out);
4819 g_free(test_conv_reply);
4820 codeconv_set_strict(FALSE);
4823 if (!g_ascii_strcasecmp(out_codeset, CS_US_ASCII))
4824 out_codeset = CS_ISO_8859_1;
4826 if (prefs_common.encoding_method == CTE_BASE64)
4827 encoding = ENC_BASE64;
4828 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
4829 encoding = ENC_QUOTED_PRINTABLE;
4830 else if (prefs_common.encoding_method == CTE_8BIT)
4831 encoding = ENC_8BIT;
4832 else
4833 encoding = procmime_get_encoding_for_charset(out_codeset);
4835 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
4836 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
4838 if (action == COMPOSE_WRITE_FOR_SEND) {
4839 codeconv_set_strict(TRUE);
4840 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
4841 codeconv_set_strict(FALSE);
4843 if (!buf) {
4844 AlertValue aval;
4845 gchar *msg;
4847 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
4848 "to the specified %s charset.\n"
4849 "Send it as %s?"), out_codeset, src_codeset);
4850 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
4851 NULL, ALERT_ERROR, G_ALERTDEFAULT);
4852 g_free(msg);
4854 if (aval != G_ALERTALTERNATE) {
4855 g_free(chars);
4856 return -3;
4857 } else {
4858 buf = chars;
4859 out_codeset = src_codeset;
4860 chars = NULL;
4863 } else {
4864 buf = chars;
4865 out_codeset = src_codeset;
4866 chars = NULL;
4869 g_free(chars);
4871 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
4872 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
4873 strstr(buf, "\nFrom ") != NULL) {
4874 encoding = ENC_QUOTED_PRINTABLE;
4878 mimetext = procmime_mimeinfo_new();
4879 mimetext->content = MIMECONTENT_MEM;
4880 mimetext->tmp = TRUE; /* must free content later */
4881 /* dup'ed because procmime_encode_content can turn it into a tmpfile
4882 * and free the data, which we need later. */
4883 mimetext->data.mem = g_strdup(buf);
4884 mimetext->type = MIMETYPE_TEXT;
4885 mimetext->subtype = g_strdup("plain");
4886 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
4887 g_strdup(out_codeset));
4889 /* protect trailing spaces when signing message */
4890 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
4891 privacy_system_can_sign(compose->privacy_system)) {
4892 encoding = ENC_QUOTED_PRINTABLE;
4895 debug_print("main text: %d bytes encoded as %s in %d\n",
4896 strlen(buf), out_codeset, encoding);
4898 /* check for line length limit */
4899 if (action == COMPOSE_WRITE_FOR_SEND &&
4900 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
4901 check_line_length(buf, 1000, &line) < 0) {
4902 AlertValue aval;
4903 gchar *msg;
4905 msg = g_strdup_printf
4906 (_("Line %d exceeds the line length limit (998 bytes).\n"
4907 "The contents of the message might be broken on the way to the delivery.\n"
4908 "\n"
4909 "Send it anyway?"), line + 1);
4910 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
4911 g_free(msg);
4912 if (aval != G_ALERTALTERNATE) {
4913 g_free(buf);
4914 return -1;
4918 if (encoding != ENC_UNKNOWN)
4919 procmime_encode_content(mimetext, encoding);
4921 /* append attachment parts */
4922 if (compose_use_attach(compose) && attach_parts) {
4923 MimeInfo *mimempart;
4924 gchar *boundary = NULL;
4925 mimempart = procmime_mimeinfo_new();
4926 mimempart->content = MIMECONTENT_EMPTY;
4927 mimempart->type = MIMETYPE_MULTIPART;
4928 mimempart->subtype = g_strdup("mixed");
4930 do {
4931 g_free(boundary);
4932 boundary = generate_mime_boundary(NULL);
4933 } while (strstr(buf, boundary) != NULL);
4935 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
4936 boundary);
4938 mimetext->disposition = DISPOSITIONTYPE_INLINE;
4940 g_node_append(mimempart->node, mimetext->node);
4941 g_node_append(mimemsg->node, mimempart->node);
4943 compose_add_attachments(compose, mimempart);
4944 } else
4945 g_node_append(mimemsg->node, mimetext->node);
4947 g_free(buf);
4949 /* sign message if sending */
4950 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
4951 privacy_system_can_sign(compose->privacy_system))
4952 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
4953 return -2;
4955 procmime_write_mimeinfo(mimemsg, fp);
4957 procmime_mimeinfo_free_all(mimemsg);
4959 return 0;
4962 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
4964 GtkTextBuffer *buffer;
4965 GtkTextIter start, end;
4966 FILE *fp;
4967 size_t len;
4968 gchar *chars, *tmp;
4970 if ((fp = g_fopen(file, "wb")) == NULL) {
4971 FILE_OP_ERROR(file, "fopen");
4972 return -1;
4975 /* chmod for security */
4976 if (change_file_mode_rw(fp, file) < 0) {
4977 FILE_OP_ERROR(file, "chmod");
4978 g_warning("can't change file mode\n");
4981 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4982 gtk_text_buffer_get_start_iter(buffer, &start);
4983 gtk_text_buffer_get_end_iter(buffer, &end);
4984 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4986 chars = conv_codeset_strdup
4987 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
4989 g_free(tmp);
4990 if (!chars) return -1;
4992 /* write body */
4993 len = strlen(chars);
4994 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
4995 FILE_OP_ERROR(file, "fwrite");
4996 g_free(chars);
4997 fclose(fp);
4998 g_unlink(file);
4999 return -1;
5002 g_free(chars);
5004 if (fclose(fp) == EOF) {
5005 FILE_OP_ERROR(file, "fclose");
5006 g_unlink(file);
5007 return -1;
5009 return 0;
5012 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5014 FolderItem *item;
5015 MsgInfo *msginfo = compose->targetinfo;
5017 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5018 if (!msginfo) return -1;
5020 if (!force && MSG_IS_LOCKED(msginfo->flags))
5021 return 0;
5023 item = msginfo->folder;
5024 g_return_val_if_fail(item != NULL, -1);
5026 if (procmsg_msg_exist(msginfo) &&
5027 (folder_has_parent_of_type(item, F_QUEUE) ||
5028 folder_has_parent_of_type(item, F_DRAFT)
5029 || msginfo == compose->autosaved_draft)) {
5030 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5031 g_warning("can't remove the old message\n");
5032 return -1;
5036 return 0;
5039 static void compose_remove_draft(Compose *compose)
5041 FolderItem *drafts;
5042 MsgInfo *msginfo = compose->targetinfo;
5043 drafts = account_get_special_folder(compose->account, F_DRAFT);
5045 if (procmsg_msg_exist(msginfo)) {
5046 folder_item_remove_msg(drafts, msginfo->msgnum);
5051 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5052 gboolean remove_reedit_target)
5054 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5057 static gboolean compose_warn_encryption(Compose *compose)
5059 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5060 AlertValue val = G_ALERTALTERNATE;
5062 if (warning == NULL)
5063 return TRUE;
5065 val = alertpanel_full(_("Encryption warning"), warning,
5066 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5067 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5068 if (val & G_ALERTDISABLE) {
5069 val &= ~G_ALERTDISABLE;
5070 if (val == G_ALERTALTERNATE)
5071 privacy_inhibit_encrypt_warning(compose->privacy_system,
5072 TRUE);
5075 if (val == G_ALERTALTERNATE) {
5076 return TRUE;
5077 } else {
5078 return FALSE;
5082 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5083 gchar **msgpath, gboolean check_subject,
5084 gboolean remove_reedit_target)
5086 FolderItem *queue;
5087 gchar *tmp;
5088 FILE *fp;
5089 GSList *cur;
5090 gint num;
5091 static gboolean lock = FALSE;
5092 PrefsAccount *mailac = NULL, *newsac = NULL;
5094 debug_print("queueing message...\n");
5095 g_return_val_if_fail(compose->account != NULL, -1);
5097 lock = TRUE;
5099 if (compose_check_entries(compose, check_subject) == FALSE) {
5100 lock = FALSE;
5101 if (compose->batch) {
5102 gtk_widget_show_all(compose->window);
5104 return -1;
5107 if (!compose->to_list && !compose->newsgroup_list) {
5108 g_warning("can't get recipient list.");
5109 lock = FALSE;
5110 return -1;
5113 if (compose->to_list) {
5114 if (compose->account->protocol != A_NNTP)
5115 mailac = compose->account;
5116 else if (cur_account && cur_account->protocol != A_NNTP)
5117 mailac = cur_account;
5118 else if (!(mailac = compose_current_mail_account())) {
5119 lock = FALSE;
5120 alertpanel_error(_("No account for sending mails available!"));
5121 return -1;
5125 if (compose->newsgroup_list) {
5126 if (compose->account->protocol == A_NNTP)
5127 newsac = compose->account;
5128 else if (!newsac->protocol != A_NNTP) {
5129 lock = FALSE;
5130 alertpanel_error(_("No account for posting news available!"));
5131 return -1;
5135 /* write queue header */
5136 tmp = g_strdup_printf("%s%cqueue.%p", get_tmp_dir(),
5137 G_DIR_SEPARATOR, compose);
5138 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5139 FILE_OP_ERROR(tmp, "fopen");
5140 g_free(tmp);
5141 lock = FALSE;
5142 return -2;
5145 if (change_file_mode_rw(fp, tmp) < 0) {
5146 FILE_OP_ERROR(tmp, "chmod");
5147 g_warning("can't change file mode\n");
5150 /* queueing variables */
5151 fprintf(fp, "AF:\n");
5152 fprintf(fp, "NF:0\n");
5153 fprintf(fp, "PS:10\n");
5154 fprintf(fp, "SRH:1\n");
5155 fprintf(fp, "SFN:\n");
5156 fprintf(fp, "DSR:\n");
5157 if (compose->msgid)
5158 fprintf(fp, "MID:<%s>\n", compose->msgid);
5159 else
5160 fprintf(fp, "MID:\n");
5161 fprintf(fp, "CFG:\n");
5162 fprintf(fp, "PT:0\n");
5163 fprintf(fp, "S:%s\n", compose->account->address);
5164 fprintf(fp, "RQ:\n");
5165 if (mailac)
5166 fprintf(fp, "SSV:%s\n", mailac->smtp_server);
5167 else
5168 fprintf(fp, "SSV:\n");
5169 if (newsac)
5170 fprintf(fp, "NSV:%s\n", newsac->nntp_server);
5171 else
5172 fprintf(fp, "NSV:\n");
5173 fprintf(fp, "SSH:\n");
5174 /* write recepient list */
5175 if (compose->to_list) {
5176 fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data);
5177 for (cur = compose->to_list->next; cur != NULL;
5178 cur = cur->next)
5179 fprintf(fp, ",<%s>", (gchar *)cur->data);
5180 fprintf(fp, "\n");
5182 /* write newsgroup list */
5183 if (compose->newsgroup_list) {
5184 fprintf(fp, "NG:");
5185 fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data);
5186 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5187 fprintf(fp, ",%s", (gchar *)cur->data);
5188 fprintf(fp, "\n");
5190 /* Sylpheed account IDs */
5191 if (mailac)
5192 fprintf(fp, "MAID:%d\n", mailac->account_id);
5193 if (newsac)
5194 fprintf(fp, "NAID:%d\n", newsac->account_id);
5197 if (compose->privacy_system != NULL) {
5198 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
5199 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
5200 if (compose->use_encryption) {
5201 gchar *encdata;
5202 if (!compose_warn_encryption(compose)) {
5203 lock = FALSE;
5204 fclose(fp);
5205 g_unlink(tmp);
5206 g_free(tmp);
5207 return -6;
5209 if (mailac && mailac->encrypt_to_self) {
5210 GSList *tmp_list = g_slist_copy(compose->to_list);
5211 tmp_list = g_slist_append(tmp_list, compose->account->address);
5212 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5213 g_slist_free(tmp_list);
5214 } else {
5215 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5217 if (encdata != NULL) {
5218 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5219 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5220 fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5221 encdata);
5222 } /* else we finally dont want to encrypt */
5223 } else {
5224 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
5225 /* and if encdata was null, it means there's been a problem in
5226 * key selection */
5227 lock = FALSE;
5228 fclose(fp);
5229 g_unlink(tmp);
5230 g_free(tmp);
5231 return -5;
5233 g_free(encdata);
5237 /* Save copy folder */
5238 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5239 gchar *savefolderid;
5241 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5242 fprintf(fp, "SCF:%s\n", savefolderid);
5243 g_free(savefolderid);
5245 /* Save copy folder */
5246 if (compose->return_receipt) {
5247 fprintf(fp, "RRCPT:1\n");
5249 /* Message-ID of message replying to */
5250 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5251 gchar *folderid;
5253 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5254 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
5255 g_free(folderid);
5257 /* Message-ID of message forwarding to */
5258 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5259 gchar *folderid;
5261 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5262 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
5263 g_free(folderid);
5266 /* end of headers */
5267 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
5269 if (compose->redirect_filename != NULL) {
5270 if (compose_redirect_write_to_file(compose, fp) < 0) {
5271 lock = FALSE;
5272 fclose(fp);
5273 g_unlink(tmp);
5274 g_free(tmp);
5275 return -2;
5277 } else {
5278 gint result = 0;
5279 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5280 lock = FALSE;
5281 fclose(fp);
5282 g_unlink(tmp);
5283 g_free(tmp);
5284 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5288 if (fclose(fp) == EOF) {
5289 FILE_OP_ERROR(tmp, "fclose");
5290 g_unlink(tmp);
5291 g_free(tmp);
5292 lock = FALSE;
5293 return -2;
5296 if (item && *item) {
5297 queue = *item;
5298 } else {
5299 queue = account_get_special_folder(compose->account, F_QUEUE);
5301 if (!queue) {
5302 g_warning("can't find queue folder\n");
5303 g_unlink(tmp);
5304 g_free(tmp);
5305 lock = FALSE;
5306 return -1;
5308 folder_item_scan(queue);
5309 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5310 g_warning("can't queue the message\n");
5311 g_unlink(tmp);
5312 g_free(tmp);
5313 lock = FALSE;
5314 return -1;
5317 if (msgpath == NULL) {
5318 g_unlink(tmp);
5319 g_free(tmp);
5320 } else
5321 *msgpath = tmp;
5323 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5324 compose_remove_reedit_target(compose, FALSE);
5327 if ((msgnum != NULL) && (item != NULL)) {
5328 *msgnum = num;
5329 *item = queue;
5332 return 0;
5335 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5337 AttachInfo *ainfo;
5338 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5339 MimeInfo *mimepart;
5340 struct stat statbuf;
5341 gchar *type, *subtype;
5342 GtkTreeModel *model;
5343 GtkTreeIter iter;
5345 model = gtk_tree_view_get_model(tree_view);
5347 if (!gtk_tree_model_get_iter_first(model, &iter))
5348 return;
5349 do {
5350 gtk_tree_model_get(model, &iter,
5351 COL_DATA, &ainfo,
5352 -1);
5354 mimepart = procmime_mimeinfo_new();
5355 mimepart->content = MIMECONTENT_FILE;
5356 mimepart->data.filename = g_strdup(ainfo->file);
5357 mimepart->tmp = FALSE; /* or we destroy our attachment */
5358 mimepart->offset = 0;
5360 stat(ainfo->file, &statbuf);
5361 mimepart->length = statbuf.st_size;
5363 type = g_strdup(ainfo->content_type);
5365 if (!strchr(type, '/')) {
5366 g_free(type);
5367 type = g_strdup("application/octet-stream");
5370 subtype = strchr(type, '/') + 1;
5371 *(subtype - 1) = '\0';
5372 mimepart->type = procmime_get_media_type(type);
5373 mimepart->subtype = g_strdup(subtype);
5374 g_free(type);
5376 if (mimepart->type == MIMETYPE_MESSAGE &&
5377 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5378 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5379 } else {
5380 if (ainfo->name) {
5381 g_hash_table_insert(mimepart->typeparameters,
5382 g_strdup("name"), g_strdup(ainfo->name));
5383 g_hash_table_insert(mimepart->dispositionparameters,
5384 g_strdup("filename"), g_strdup(ainfo->name));
5385 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5389 if (compose->use_signing) {
5390 if (ainfo->encoding == ENC_7BIT)
5391 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5392 else if (ainfo->encoding == ENC_8BIT)
5393 ainfo->encoding = ENC_BASE64;
5396 procmime_encode_content(mimepart, ainfo->encoding);
5398 g_node_append(parent->node, mimepart->node);
5399 } while (gtk_tree_model_iter_next(model, &iter));
5402 #define IS_IN_CUSTOM_HEADER(header) \
5403 (compose->account->add_customhdr && \
5404 custom_header_find(compose->account->customhdr_list, header) != NULL)
5406 static void compose_add_headerfield_from_headerlist(Compose *compose,
5407 GString *header,
5408 const gchar *fieldname,
5409 const gchar *seperator)
5411 gchar *str, *fieldname_w_colon;
5412 gboolean add_field = FALSE;
5413 GSList *list;
5414 ComposeHeaderEntry *headerentry;
5415 const gchar *headerentryname;
5416 const gchar *trans_fieldname;
5417 GString *fieldstr;
5419 if (IS_IN_CUSTOM_HEADER(fieldname))
5420 return;
5422 debug_print("Adding %s-fields\n", fieldname);
5424 fieldstr = g_string_sized_new(64);
5426 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5427 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5429 for (list = compose->header_list; list; list = list->next) {
5430 headerentry = ((ComposeHeaderEntry *)list->data);
5431 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry));
5433 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5434 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5435 g_strstrip(str);
5436 if (str[0] != '\0') {
5437 if (add_field)
5438 g_string_append(fieldstr, seperator);
5439 g_string_append(fieldstr, str);
5440 add_field = TRUE;
5442 g_free(str);
5445 if (add_field) {
5446 gchar *buf;
5448 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5449 compose_convert_header
5450 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5451 strlen(fieldname) + 2, TRUE);
5452 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5453 g_free(buf);
5456 g_free(fieldname_w_colon);
5457 g_string_free(fieldstr, TRUE);
5459 return;
5462 static gchar *compose_get_header(Compose *compose)
5464 gchar buf[BUFFSIZE];
5465 const gchar *entry_str;
5466 gchar *str;
5467 gchar *name;
5468 GSList *list;
5469 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5470 GString *header;
5471 gchar *from_name = NULL, *from_address = NULL;
5472 gchar *tmp;
5474 g_return_val_if_fail(compose->account != NULL, NULL);
5475 g_return_val_if_fail(compose->account->address != NULL, NULL);
5477 header = g_string_sized_new(64);
5479 /* Date */
5480 get_rfc822_date(buf, sizeof(buf));
5481 g_string_append_printf(header, "Date: %s\n", buf);
5483 /* From */
5485 if (compose->account->name && *compose->account->name) {
5486 gchar *buf;
5487 QUOTE_IF_REQUIRED(buf, compose->account->name);
5488 tmp = g_strdup_printf("%s <%s>",
5489 buf, compose->account->address);
5490 } else {
5491 tmp = g_strdup_printf("%s",
5492 compose->account->address);
5494 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5495 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5496 /* use default */
5497 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5498 from_address = g_strdup(compose->account->address);
5499 } else {
5500 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5501 /* extract name and address */
5502 if (strstr(spec, " <") && strstr(spec, ">")) {
5503 from_address = g_strdup(strrchr(spec, '<')+1);
5504 *(strrchr(from_address, '>')) = '\0';
5505 from_name = g_strdup(spec);
5506 *(strrchr(from_name, '<')) = '\0';
5507 } else {
5508 from_name = NULL;
5509 from_address = g_strdup(spec);
5511 g_free(spec);
5513 g_free(tmp);
5516 if (from_name && *from_name) {
5517 compose_convert_header
5518 (compose, buf, sizeof(buf), from_name,
5519 strlen("From: "), TRUE);
5520 QUOTE_IF_REQUIRED(name, buf);
5522 g_string_append_printf(header, "From: %s <%s>\n",
5523 name, from_address);
5524 } else
5525 g_string_append_printf(header, "From: %s\n", from_address);
5527 g_free(from_name);
5528 g_free(from_address);
5530 /* To */
5531 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5533 /* Newsgroups */
5534 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5536 /* Cc */
5537 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5539 /* Bcc */
5540 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5542 /* Subject */
5543 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5545 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5546 g_strstrip(str);
5547 if (*str != '\0') {
5548 compose_convert_header(compose, buf, sizeof(buf), str,
5549 strlen("Subject: "), FALSE);
5550 g_string_append_printf(header, "Subject: %s\n", buf);
5553 g_free(str);
5555 /* Message-ID */
5556 if (compose->account->gen_msgid) {
5557 generate_msgid(buf, sizeof(buf));
5558 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5559 compose->msgid = g_strdup(buf);
5562 if (compose->remove_references == FALSE) {
5563 /* In-Reply-To */
5564 if (compose->inreplyto && compose->to_list)
5565 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5567 /* References */
5568 if (compose->references)
5569 g_string_append_printf(header, "References: %s\n", compose->references);
5572 /* Followup-To */
5573 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5575 /* Reply-To */
5576 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
5578 /* Organization */
5579 if (compose->account->organization &&
5580 strlen(compose->account->organization) &&
5581 !IS_IN_CUSTOM_HEADER("Organization")) {
5582 compose_convert_header(compose, buf, sizeof(buf),
5583 compose->account->organization,
5584 strlen("Organization: "), FALSE);
5585 g_string_append_printf(header, "Organization: %s\n", buf);
5588 /* Program version and system info */
5589 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
5590 !compose->newsgroup_list) {
5591 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
5592 prog_version,
5593 gtk_major_version, gtk_minor_version, gtk_micro_version,
5594 TARGET_ALIAS);
5596 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
5597 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
5598 prog_version,
5599 gtk_major_version, gtk_minor_version, gtk_micro_version,
5600 TARGET_ALIAS);
5603 /* custom headers */
5604 if (compose->account->add_customhdr) {
5605 GSList *cur;
5607 for (cur = compose->account->customhdr_list; cur != NULL;
5608 cur = cur->next) {
5609 CustomHeader *chdr = (CustomHeader *)cur->data;
5611 if (custom_header_is_allowed(chdr->name)) {
5612 compose_convert_header
5613 (compose, buf, sizeof(buf),
5614 chdr->value ? chdr->value : "",
5615 strlen(chdr->name) + 2, FALSE);
5616 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
5621 /* PRIORITY */
5622 switch (compose->priority) {
5623 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
5624 "X-Priority: 1 (Highest)\n");
5625 break;
5626 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
5627 "X-Priority: 2 (High)\n");
5628 break;
5629 case PRIORITY_NORMAL: break;
5630 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
5631 "X-Priority: 4 (Low)\n");
5632 break;
5633 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
5634 "X-Priority: 5 (Lowest)\n");
5635 break;
5636 default: debug_print("compose: priority unknown : %d\n",
5637 compose->priority);
5640 /* Request Return Receipt */
5641 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
5642 if (compose->return_receipt) {
5643 if (compose->account->name
5644 && *compose->account->name) {
5645 compose_convert_header(compose, buf, sizeof(buf),
5646 compose->account->name,
5647 strlen("Disposition-Notification-To: "),
5648 TRUE);
5649 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
5650 } else
5651 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
5655 /* get special headers */
5656 for (list = compose->header_list; list; list = list->next) {
5657 ComposeHeaderEntry *headerentry;
5658 gchar *tmp;
5659 gchar *headername;
5660 gchar *headername_wcolon;
5661 const gchar *headername_trans;
5662 gchar *headervalue;
5663 gchar **string;
5664 gboolean standard_header = FALSE;
5666 headerentry = ((ComposeHeaderEntry *)list->data);
5668 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(headerentry->combo)->entry)));
5669 if (strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
5670 g_free(tmp);
5671 continue;
5674 if (!strstr(tmp, ":")) {
5675 headername_wcolon = g_strconcat(tmp, ":", NULL);
5676 headername = g_strdup(tmp);
5677 } else {
5678 headername_wcolon = g_strdup(tmp);
5679 headername = g_strdup(strtok(tmp, ":"));
5681 g_free(tmp);
5683 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5684 Xstrdup_a(headervalue, entry_str, return NULL);
5685 subst_char(headervalue, '\r', ' ');
5686 subst_char(headervalue, '\n', ' ');
5687 string = std_headers;
5688 while (*string != NULL) {
5689 headername_trans = prefs_common_translated_header_name(*string);
5690 if (!strcmp(headername_trans, headername_wcolon))
5691 standard_header = TRUE;
5692 string++;
5694 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
5695 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
5697 g_free(headername);
5698 g_free(headername_wcolon);
5701 str = header->str;
5702 g_string_free(header, FALSE);
5704 return str;
5707 #undef IS_IN_CUSTOM_HEADER
5709 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
5710 gint header_len, gboolean addr_field)
5712 gchar *tmpstr = NULL;
5713 const gchar *out_codeset = NULL;
5715 g_return_if_fail(src != NULL);
5716 g_return_if_fail(dest != NULL);
5718 if (len < 1) return;
5720 tmpstr = g_strdup(src);
5722 subst_char(tmpstr, '\n', ' ');
5723 subst_char(tmpstr, '\r', ' ');
5724 g_strchomp(tmpstr);
5726 if (!g_utf8_validate(tmpstr, -1, NULL)) {
5727 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
5728 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
5729 g_free(tmpstr);
5730 tmpstr = mybuf;
5733 codeconv_set_strict(TRUE);
5734 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5735 conv_get_charset_str(compose->out_encoding));
5736 codeconv_set_strict(FALSE);
5738 if (!dest || *dest == '\0') {
5739 gchar *test_conv_global_out = NULL;
5740 gchar *test_conv_reply = NULL;
5742 /* automatic mode. be automatic. */
5743 codeconv_set_strict(TRUE);
5745 out_codeset = conv_get_outgoing_charset_str();
5746 if (out_codeset) {
5747 debug_print("trying to convert to %s\n", out_codeset);
5748 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5751 if (!test_conv_global_out && compose->orig_charset
5752 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5753 out_codeset = compose->orig_charset;
5754 debug_print("failure; trying to convert to %s\n", out_codeset);
5755 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
5758 if (!test_conv_global_out && !test_conv_reply) {
5759 /* we're lost */
5760 out_codeset = CS_INTERNAL;
5761 debug_print("finally using %s\n", out_codeset);
5763 g_free(test_conv_global_out);
5764 g_free(test_conv_reply);
5765 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
5766 out_codeset);
5767 codeconv_set_strict(FALSE);
5769 g_free(tmpstr);
5772 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
5774 gchar *address;
5776 g_return_if_fail(user_data != NULL);
5778 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
5779 g_strstrip(address);
5780 if (*address != '\0') {
5781 gchar *name = procheader_get_fromname(address);
5782 extract_address(address);
5783 addressbook_add_contact(name, address, NULL);
5785 g_free(address);
5788 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
5790 GtkWidget *menuitem;
5791 gchar *address;
5793 g_return_if_fail(menu != NULL);
5794 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
5796 menuitem = gtk_separator_menu_item_new();
5797 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5798 gtk_widget_show(menuitem);
5800 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
5801 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
5803 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
5804 g_strstrip(address);
5805 if (*address == '\0') {
5806 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
5809 g_signal_connect(G_OBJECT(menuitem), "activate",
5810 G_CALLBACK(compose_add_to_addressbook_cb), entry);
5811 gtk_widget_show(menuitem);
5814 static void compose_create_header_entry(Compose *compose)
5816 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5818 GtkWidget *combo;
5819 GtkWidget *entry;
5820 GList *combo_list = NULL;
5821 gchar **string;
5822 const gchar *header = NULL;
5823 ComposeHeaderEntry *headerentry;
5824 gboolean standard_header = FALSE;
5826 headerentry = g_new0(ComposeHeaderEntry, 1);
5828 /* Combo box */
5829 combo = gtk_combo_new();
5830 string = headers;
5831 while(*string != NULL) {
5832 combo_list = g_list_append(combo_list, (gchar*)prefs_common_translated_header_name(*string));
5833 string++;
5835 gtk_combo_set_popdown_strings(GTK_COMBO(combo), combo_list);
5836 g_list_free(combo_list);
5837 gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), TRUE);
5838 g_signal_connect(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5839 G_CALLBACK(compose_grab_focus_cb), compose);
5840 gtk_widget_show(combo);
5841 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
5842 compose->header_nextrow, compose->header_nextrow+1,
5843 GTK_SHRINK, GTK_FILL, 0, 0);
5844 if (compose->header_last) {
5845 const gchar *last_header_entry = gtk_entry_get_text(
5846 GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5847 string = headers;
5848 while (*string != NULL) {
5849 if (!strcmp(*string, last_header_entry))
5850 standard_header = TRUE;
5851 string++;
5853 if (standard_header)
5854 header = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry));
5856 if (!compose->header_last || !standard_header) {
5857 switch(compose->account->protocol) {
5858 case A_NNTP:
5859 header = prefs_common_translated_header_name("Newsgroups:");
5860 break;
5861 default:
5862 header = prefs_common_translated_header_name("To:");
5863 break;
5866 if (header)
5867 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(combo)->entry), header);
5869 g_signal_connect_after(G_OBJECT(GTK_COMBO(combo)->entry), "grab_focus",
5870 G_CALLBACK(compose_grab_focus_cb), compose);
5872 /* Entry field */
5873 entry = gtk_entry_new();
5874 gtk_widget_show(entry);
5875 gtk_tooltips_set_tip(compose->tooltips, entry,
5876 _("Use <tab> to autocomplete from addressbook"), NULL);
5877 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
5878 compose->header_nextrow, compose->header_nextrow+1,
5879 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
5881 g_signal_connect(G_OBJECT(entry), "key-press-event",
5882 G_CALLBACK(compose_headerentry_key_press_event_cb),
5883 headerentry);
5884 g_signal_connect(G_OBJECT(entry), "changed",
5885 G_CALLBACK(compose_headerentry_changed_cb),
5886 headerentry);
5887 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
5888 G_CALLBACK(compose_grab_focus_cb), compose);
5890 /* email dnd */
5891 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
5892 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
5893 GDK_ACTION_COPY | GDK_ACTION_MOVE);
5894 g_signal_connect(G_OBJECT(entry), "drag_data_received",
5895 G_CALLBACK(compose_header_drag_received_cb),
5896 entry);
5897 g_signal_connect(G_OBJECT(entry), "drag-drop",
5898 G_CALLBACK(compose_drag_drop),
5899 compose);
5900 g_signal_connect(G_OBJECT(entry), "populate-popup",
5901 G_CALLBACK(compose_entry_popup_extend),
5902 NULL);
5904 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
5906 headerentry->compose = compose;
5907 headerentry->combo = combo;
5908 headerentry->entry = entry;
5909 headerentry->headernum = compose->header_nextrow;
5911 compose->header_nextrow++;
5912 compose->header_last = headerentry;
5913 compose->header_list =
5914 g_slist_append(compose->header_list,
5915 headerentry);
5918 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
5920 ComposeHeaderEntry *last_header;
5922 last_header = compose->header_last;
5924 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(last_header->combo)->entry), header);
5925 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
5928 static void compose_remove_header_entries(Compose *compose)
5930 GSList *list;
5931 for (list = compose->header_list; list; list = list->next) {
5932 ComposeHeaderEntry *headerentry =
5933 (ComposeHeaderEntry *)list->data;
5934 gtk_widget_destroy(headerentry->combo);
5935 gtk_widget_destroy(headerentry->entry);
5936 g_free(headerentry);
5938 compose->header_last = NULL;
5939 g_slist_free(compose->header_list);
5940 compose->header_list = NULL;
5941 compose->header_nextrow = 1;
5942 compose_create_header_entry(compose);
5945 static GtkWidget *compose_create_header(Compose *compose)
5947 GtkWidget *from_optmenu_hbox;
5948 GtkWidget *header_scrolledwin;
5949 GtkWidget *header_table;
5951 gint count = 0;
5953 /* header labels and entries */
5954 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
5955 gtk_widget_show(header_scrolledwin);
5956 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
5958 header_table = gtk_table_new(2, 2, FALSE);
5959 gtk_widget_show(header_table);
5960 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
5961 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
5962 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_ETCHED_IN);
5963 count = 0;
5965 /* option menu for selecting accounts */
5966 from_optmenu_hbox = compose_account_option_menu_create(compose);
5967 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
5968 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
5969 count++;
5971 compose->header_table = header_table;
5972 compose->header_list = NULL;
5973 compose->header_nextrow = count;
5975 compose_create_header_entry(compose);
5977 compose->table = NULL;
5979 return header_scrolledwin ;
5982 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
5984 Compose *compose = (Compose *)data;
5985 GdkEventButton event;
5987 event.button = 3;
5988 event.time = gtk_get_current_event_time();
5990 return attach_button_pressed(compose->attach_clist, &event, compose);
5993 static GtkWidget *compose_create_attach(Compose *compose)
5995 GtkWidget *attach_scrwin;
5996 GtkWidget *attach_clist;
5998 GtkListStore *store;
5999 GtkCellRenderer *renderer;
6000 GtkTreeViewColumn *column;
6001 GtkTreeSelection *selection;
6003 /* attachment list */
6004 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6005 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6006 GTK_POLICY_AUTOMATIC,
6007 GTK_POLICY_AUTOMATIC);
6008 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6010 store = gtk_list_store_new(N_ATTACH_COLS,
6011 G_TYPE_STRING,
6012 G_TYPE_STRING,
6013 G_TYPE_STRING,
6014 G_TYPE_POINTER,
6015 G_TYPE_AUTO_POINTER,
6016 -1);
6017 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6018 (GTK_TREE_MODEL(store)));
6019 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6020 g_object_unref(store);
6022 renderer = gtk_cell_renderer_text_new();
6023 column = gtk_tree_view_column_new_with_attributes
6024 (_("Mime type"), renderer, "text",
6025 COL_MIMETYPE, NULL);
6026 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6028 renderer = gtk_cell_renderer_text_new();
6029 column = gtk_tree_view_column_new_with_attributes
6030 (_("Size"), renderer, "text",
6031 COL_SIZE, NULL);
6032 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6034 renderer = gtk_cell_renderer_text_new();
6035 column = gtk_tree_view_column_new_with_attributes
6036 (_("Name"), renderer, "text",
6037 COL_NAME, NULL);
6038 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6040 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6041 prefs_common.use_stripes_everywhere);
6042 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6043 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6045 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6046 G_CALLBACK(attach_selected), compose);
6047 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6048 G_CALLBACK(attach_button_pressed), compose);
6049 #ifndef MAEMO
6050 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6051 G_CALLBACK(popup_attach_button_pressed), compose);
6052 #else
6053 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6054 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6055 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6056 G_CALLBACK(popup_attach_button_pressed), compose);
6057 #endif
6058 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6059 G_CALLBACK(attach_key_pressed), compose);
6061 /* drag and drop */
6062 gtk_drag_dest_set(attach_clist,
6063 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6064 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6065 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6066 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6067 G_CALLBACK(compose_attach_drag_received_cb),
6068 compose);
6069 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6070 G_CALLBACK(compose_drag_drop),
6071 compose);
6073 compose->attach_scrwin = attach_scrwin;
6074 compose->attach_clist = attach_clist;
6076 return attach_scrwin;
6079 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6080 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6082 static GtkWidget *compose_create_others(Compose *compose)
6084 GtkWidget *table;
6085 GtkWidget *savemsg_checkbtn;
6086 GtkWidget *savemsg_entry;
6087 GtkWidget *savemsg_select;
6089 guint rowcount = 0;
6090 gchar *folderidentifier;
6092 /* Table for settings */
6093 table = gtk_table_new(3, 1, FALSE);
6094 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6095 gtk_widget_show(table);
6096 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6097 rowcount = 0;
6099 /* Save Message to folder */
6100 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6101 gtk_widget_show(savemsg_checkbtn);
6102 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6103 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6104 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6106 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6107 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6109 savemsg_entry = gtk_entry_new();
6110 gtk_widget_show(savemsg_entry);
6111 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6112 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6113 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6114 G_CALLBACK(compose_grab_focus_cb), compose);
6115 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6116 folderidentifier = folder_item_get_identifier(account_get_special_folder
6117 (compose->account, F_OUTBOX));
6118 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6119 g_free(folderidentifier);
6122 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6123 gtk_widget_show(savemsg_select);
6124 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6125 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6126 G_CALLBACK(compose_savemsg_select_cb),
6127 compose);
6129 rowcount++;
6131 compose->savemsg_checkbtn = savemsg_checkbtn;
6132 compose->savemsg_entry = savemsg_entry;
6134 return table;
6137 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6139 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6140 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6143 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6145 FolderItem *dest;
6146 gchar * path;
6148 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL);
6149 if (!dest) return;
6151 path = folder_item_get_identifier(dest);
6153 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6154 g_free(path);
6157 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6158 GdkAtom clip, GtkTextIter *insert_place);
6160 #define BLOCK_WRAP() { \
6161 prev_autowrap = compose->autowrap; \
6162 buffer = gtk_text_view_get_buffer( \
6163 GTK_TEXT_VIEW(compose->text)); \
6164 compose->autowrap = FALSE; \
6166 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
6167 G_CALLBACK(compose_changed_cb), \
6168 compose); \
6169 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
6170 G_CALLBACK(text_inserted), \
6171 compose); \
6173 #define UNBLOCK_WRAP() { \
6174 compose->autowrap = prev_autowrap; \
6175 if (compose->autowrap) \
6176 compose_wrap_all(compose); \
6178 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
6179 G_CALLBACK(compose_changed_cb), \
6180 compose); \
6181 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
6182 G_CALLBACK(text_inserted), \
6183 compose); \
6187 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6188 Compose *compose)
6190 gint prev_autowrap;
6191 GtkTextBuffer *buffer;
6192 #if USE_ASPELL
6193 if (event->button == 3) {
6194 GtkTextIter iter;
6195 GtkTextIter sel_start, sel_end;
6196 gboolean stuff_selected;
6197 gint x, y;
6198 /* move the cursor to allow GtkAspell to check the word
6199 * under the mouse */
6200 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6201 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6202 &x, &y);
6203 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6204 &iter, x, y);
6205 /* get selection */
6206 stuff_selected = gtk_text_buffer_get_selection_bounds(
6207 GTK_TEXT_VIEW(text)->buffer,
6208 &sel_start, &sel_end);
6210 gtk_text_buffer_place_cursor (GTK_TEXT_VIEW(text)->buffer, &iter);
6211 /* reselect stuff */
6212 if (stuff_selected
6213 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6214 gtk_text_buffer_select_range(GTK_TEXT_VIEW(text)->buffer,
6215 &sel_start, &sel_end);
6217 return FALSE; /* pass the event so that the right-click goes through */
6219 #endif
6220 if (event->button == 2) {
6221 GtkTextIter iter;
6222 gint x, y;
6223 BLOCK_WRAP();
6225 /* get the middle-click position to paste at the correct place */
6226 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6227 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6228 &x, &y);
6229 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6230 &iter, x, y);
6232 entry_paste_clipboard(compose, text,
6233 prefs_common.linewrap_pastes,
6234 GDK_SELECTION_PRIMARY, &iter);
6235 UNBLOCK_WRAP();
6236 return TRUE;
6238 return FALSE;
6241 #if USE_ASPELL
6242 static void compose_spell_menu_changed(void *data)
6244 Compose *compose = (Compose *)data;
6245 GSList *items;
6246 GtkWidget *menuitem;
6247 GtkWidget *parent_item;
6248 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6249 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6250 GSList *spell_menu;
6252 if (compose->gtkaspell == NULL)
6253 return;
6255 parent_item = gtk_item_factory_get_item(ifactory,
6256 "/Spelling/Options");
6258 /* setting the submenu removes /Spelling/Options from the factory
6259 * so we need to save it */
6261 if (parent_item == NULL) {
6262 parent_item = compose->aspell_options_menu;
6263 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6264 } else
6265 compose->aspell_options_menu = parent_item;
6267 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6269 spell_menu = g_slist_reverse(spell_menu);
6270 for (items = spell_menu;
6271 items; items = items->next) {
6272 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6273 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6274 gtk_widget_show(GTK_WIDGET(menuitem));
6276 g_slist_free(spell_menu);
6278 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6281 #endif
6283 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6285 Compose *compose = (Compose *)data;
6286 GdkEventButton event;
6288 event.button = 3;
6289 event.time = gtk_get_current_event_time();
6291 return text_clicked(compose->text, &event, compose);
6294 static gboolean compose_force_window_origin = TRUE;
6295 static Compose *compose_create(PrefsAccount *account,
6296 FolderItem *folder,
6297 ComposeMode mode,
6298 gboolean batch)
6300 Compose *compose;
6301 GtkWidget *window;
6302 GtkWidget *vbox;
6303 GtkWidget *menubar;
6304 GtkWidget *handlebox;
6306 GtkWidget *notebook;
6308 GtkWidget *vbox2;
6310 GtkWidget *label;
6311 GtkWidget *subject_hbox;
6312 GtkWidget *subject_frame;
6313 GtkWidget *subject_entry;
6314 GtkWidget *subject;
6315 GtkWidget *paned;
6317 GtkWidget *edit_vbox;
6318 GtkWidget *ruler_hbox;
6319 GtkWidget *ruler;
6320 GtkWidget *scrolledwin;
6321 GtkWidget *text;
6322 GtkTextBuffer *buffer;
6323 GtkClipboard *clipboard;
6325 UndoMain *undostruct;
6327 gchar *titles[N_ATTACH_COLS];
6328 guint n_menu_entries;
6329 GtkWidget *popupmenu;
6330 GtkItemFactory *popupfactory;
6331 GtkItemFactory *ifactory;
6332 GtkWidget *tmpl_menu;
6333 gint n_entries;
6334 GtkWidget *menuitem;
6336 #if USE_ASPELL
6337 GtkAspell * gtkaspell = NULL;
6338 #endif
6340 static GdkGeometry geometry;
6342 g_return_val_if_fail(account != NULL, NULL);
6344 debug_print("Creating compose window...\n");
6345 compose = g_new0(Compose, 1);
6347 titles[COL_MIMETYPE] = _("MIME type");
6348 titles[COL_SIZE] = _("Size");
6349 titles[COL_NAME] = _("Name");
6351 compose->batch = batch;
6352 compose->account = account;
6353 compose->folder = folder;
6355 compose->mutex = g_mutex_new();
6356 compose->set_cursor_pos = -1;
6358 compose->tooltips = gtk_tooltips_new();
6360 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6362 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6363 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6365 if (!geometry.max_width) {
6366 geometry.max_width = gdk_screen_width();
6367 geometry.max_height = gdk_screen_height();
6370 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6371 &geometry, GDK_HINT_MAX_SIZE);
6372 if (!geometry.min_width) {
6373 geometry.min_width = 600;
6374 geometry.min_height = 480;
6376 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6377 &geometry, GDK_HINT_MIN_SIZE);
6379 #ifndef MAEMO
6380 if (compose_force_window_origin)
6381 gtk_widget_set_uposition(window, prefs_common.compose_x,
6382 prefs_common.compose_y);
6383 #endif
6384 g_signal_connect(G_OBJECT(window), "delete_event",
6385 G_CALLBACK(compose_delete_cb), compose);
6386 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6387 gtk_widget_realize(window);
6389 gtkut_widget_set_composer_icon(window);
6391 vbox = gtk_vbox_new(FALSE, 0);
6392 gtk_container_add(GTK_CONTAINER(window), vbox);
6394 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6395 menubar = menubar_create(window, compose_entries,
6396 n_menu_entries, "<Compose>", compose);
6397 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6399 if (prefs_common.toolbar_detachable) {
6400 handlebox = gtk_handle_box_new();
6401 } else {
6402 handlebox = gtk_hbox_new(FALSE, 0);
6404 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6406 gtk_widget_realize(handlebox);
6407 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6408 (gpointer)compose);
6410 vbox2 = gtk_vbox_new(FALSE, 2);
6411 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6412 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6414 /* Notebook */
6415 notebook = gtk_notebook_new();
6416 gtk_widget_set_size_request(notebook, -1, 130);
6417 gtk_widget_show(notebook);
6419 /* header labels and entries */
6420 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6421 compose_create_header(compose),
6422 gtk_label_new_with_mnemonic(_("Hea_der")));
6423 /* attachment list */
6424 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6425 compose_create_attach(compose),
6426 gtk_label_new_with_mnemonic(_("_Attachments")));
6427 /* Others Tab */
6428 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6429 compose_create_others(compose),
6430 gtk_label_new_with_mnemonic(_("Othe_rs")));
6432 /* Subject */
6433 subject_hbox = gtk_hbox_new(FALSE, 0);
6434 gtk_widget_show(subject_hbox);
6436 subject_frame = gtk_frame_new(NULL);
6437 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6438 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6439 gtk_widget_show(subject_frame);
6441 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6442 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6443 gtk_widget_show(subject);
6445 label = gtk_label_new(_("Subject:"));
6446 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6447 gtk_widget_show(label);
6449 subject_entry = gtk_entry_new();
6450 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6451 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6452 G_CALLBACK(compose_grab_focus_cb), compose);
6453 gtk_widget_show(subject_entry);
6454 compose->subject_entry = subject_entry;
6455 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6457 edit_vbox = gtk_vbox_new(FALSE, 0);
6459 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6461 /* ruler */
6462 ruler_hbox = gtk_hbox_new(FALSE, 0);
6463 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6465 ruler = gtk_shruler_new();
6466 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6467 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6468 BORDER_WIDTH);
6470 /* text widget */
6471 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6472 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6473 GTK_POLICY_AUTOMATIC,
6474 GTK_POLICY_AUTOMATIC);
6475 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6476 GTK_SHADOW_IN);
6477 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6478 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6480 text = gtk_text_view_new();
6481 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6482 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6483 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6484 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6485 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6487 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6489 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6490 G_CALLBACK(compose_edit_size_alloc),
6491 ruler);
6492 g_signal_connect(G_OBJECT(buffer), "changed",
6493 G_CALLBACK(compose_changed_cb), compose);
6494 g_signal_connect(G_OBJECT(text), "grab_focus",
6495 G_CALLBACK(compose_grab_focus_cb), compose);
6496 g_signal_connect(G_OBJECT(buffer), "insert_text",
6497 G_CALLBACK(text_inserted), compose);
6498 g_signal_connect(G_OBJECT(text), "button_press_event",
6499 G_CALLBACK(text_clicked), compose);
6500 #ifndef MAEMO
6501 g_signal_connect(G_OBJECT(text), "popup-menu",
6502 G_CALLBACK(compose_popup_menu), compose);
6503 #else
6504 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6505 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6506 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6507 G_CALLBACK(compose_popup_menu), compose);
6508 #endif
6509 g_signal_connect(G_OBJECT(subject_entry), "changed",
6510 G_CALLBACK(compose_changed_cb), compose);
6512 /* drag and drop */
6513 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6514 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6515 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6516 g_signal_connect(G_OBJECT(text), "drag_data_received",
6517 G_CALLBACK(compose_insert_drag_received_cb),
6518 compose);
6519 g_signal_connect(G_OBJECT(text), "drag-drop",
6520 G_CALLBACK(compose_drag_drop),
6521 compose);
6522 gtk_widget_show_all(vbox);
6524 /* pane between attach clist and text */
6525 paned = gtk_vpaned_new();
6526 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6527 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6528 #ifdef MAEMO
6529 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6530 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6531 else
6532 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6533 #endif
6534 gtk_paned_add1(GTK_PANED(paned), notebook);
6535 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6536 gtk_widget_show_all(paned);
6539 if (prefs_common.textfont) {
6540 PangoFontDescription *font_desc;
6542 font_desc = pango_font_description_from_string
6543 (prefs_common.textfont);
6544 if (font_desc) {
6545 gtk_widget_modify_font(text, font_desc);
6546 pango_font_description_free(font_desc);
6550 n_entries = sizeof(compose_popup_entries) /
6551 sizeof(compose_popup_entries[0]);
6552 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6553 "<Compose>", &popupfactory,
6554 compose);
6556 ifactory = gtk_item_factory_from_widget(menubar);
6557 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6558 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
6559 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
6561 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6563 undostruct = undo_init(text);
6564 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
6565 menubar);
6567 address_completion_start(window);
6569 compose->window = window;
6570 compose->vbox = vbox;
6571 compose->menubar = menubar;
6572 compose->handlebox = handlebox;
6574 compose->vbox2 = vbox2;
6576 compose->paned = paned;
6578 compose->notebook = notebook;
6579 compose->edit_vbox = edit_vbox;
6580 compose->ruler_hbox = ruler_hbox;
6581 compose->ruler = ruler;
6582 compose->scrolledwin = scrolledwin;
6583 compose->text = text;
6585 compose->focused_editable = NULL;
6587 compose->popupmenu = popupmenu;
6588 compose->popupfactory = popupfactory;
6590 compose->tmpl_menu = tmpl_menu;
6592 compose->mode = mode;
6593 compose->rmode = mode;
6595 compose->targetinfo = NULL;
6596 compose->replyinfo = NULL;
6597 compose->fwdinfo = NULL;
6599 compose->replyto = NULL;
6600 compose->cc = NULL;
6601 compose->bcc = NULL;
6602 compose->followup_to = NULL;
6604 compose->ml_post = NULL;
6606 compose->inreplyto = NULL;
6607 compose->references = NULL;
6608 compose->msgid = NULL;
6609 compose->boundary = NULL;
6611 compose->autowrap = prefs_common.autowrap;
6613 compose->use_signing = FALSE;
6614 compose->use_encryption = FALSE;
6615 compose->privacy_system = NULL;
6617 compose->modified = FALSE;
6619 compose->return_receipt = FALSE;
6621 compose->to_list = NULL;
6622 compose->newsgroup_list = NULL;
6624 compose->undostruct = undostruct;
6626 compose->sig_str = NULL;
6628 compose->exteditor_file = NULL;
6629 compose->exteditor_pid = -1;
6630 compose->exteditor_tag = -1;
6631 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
6633 #if USE_ASPELL
6634 menu_set_sensitive(ifactory, "/Spelling", FALSE);
6635 if (mode != COMPOSE_REDIRECT) {
6636 if (prefs_common.enable_aspell && prefs_common.dictionary &&
6637 strcmp(prefs_common.dictionary, "")) {
6638 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
6639 prefs_common.dictionary,
6640 prefs_common.alt_dictionary,
6641 conv_get_locale_charset_str(),
6642 prefs_common.misspelled_col,
6643 prefs_common.check_while_typing,
6644 prefs_common.recheck_when_changing_dict,
6645 prefs_common.use_alternate,
6646 prefs_common.use_both_dicts,
6647 GTK_TEXT_VIEW(text),
6648 GTK_WINDOW(compose->window),
6649 compose_spell_menu_changed,
6650 compose);
6651 if (!gtkaspell) {
6652 alertpanel_error(_("Spell checker could not "
6653 "be started.\n%s"),
6654 gtkaspell_checkers_strerror());
6655 gtkaspell_checkers_reset_error();
6656 } else {
6657 if (!gtkaspell_set_sug_mode(gtkaspell,
6658 prefs_common.aspell_sugmode)) {
6659 debug_print("Aspell: could not set "
6660 "suggestion mode %s\n",
6661 gtkaspell_checkers_strerror());
6662 gtkaspell_checkers_reset_error();
6665 menu_set_sensitive(ifactory, "/Spelling", TRUE);
6669 compose->gtkaspell = gtkaspell;
6670 compose_spell_menu_changed(compose);
6671 #endif
6673 compose_select_account(compose, account, TRUE);
6675 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
6676 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
6677 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
6679 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
6680 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
6682 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
6683 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
6685 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
6687 if (account->protocol != A_NNTP)
6688 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6689 prefs_common_translated_header_name("To:"));
6690 else
6691 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(compose->header_last->combo)->entry),
6692 prefs_common_translated_header_name("Newsgroups:"));
6694 addressbook_set_target_compose(compose);
6696 if (mode != COMPOSE_REDIRECT)
6697 compose_set_template_menu(compose);
6698 else {
6699 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
6700 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
6703 compose_list = g_list_append(compose_list, compose);
6705 if (!prefs_common.show_ruler)
6706 gtk_widget_hide(ruler_hbox);
6708 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
6709 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
6710 prefs_common.show_ruler);
6712 /* Priority */
6713 compose->priority = PRIORITY_NORMAL;
6714 compose_update_priority_menu_item(compose);
6716 compose_set_out_encoding(compose);
6718 /* Actions menu */
6719 compose_update_actions_menu(compose);
6721 /* Privacy Systems menu */
6722 compose_update_privacy_systems_menu(compose);
6724 activate_privacy_system(compose, account, TRUE);
6725 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
6726 if (batch) {
6727 gtk_widget_realize(window);
6728 } else {
6729 gtk_widget_show(window);
6730 #ifdef MAEMO
6731 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
6732 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
6733 #endif
6736 return compose;
6739 static GtkWidget *compose_account_option_menu_create(Compose *compose)
6741 GList *accounts;
6742 GtkWidget *hbox;
6743 GtkWidget *optmenu;
6744 GtkWidget *optmenubox;
6745 GtkListStore *menu;
6746 GtkTreeIter iter;
6747 GtkWidget *from_name = NULL;
6749 gint num = 0, def_menu = 0;
6751 accounts = account_get_list();
6752 g_return_val_if_fail(accounts != NULL, NULL);
6754 optmenubox = gtk_event_box_new();
6755 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
6756 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
6758 hbox = gtk_hbox_new(FALSE, 6);
6759 from_name = gtk_entry_new();
6761 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
6762 G_CALLBACK(compose_grab_focus_cb), compose);
6764 for (; accounts != NULL; accounts = accounts->next, num++) {
6765 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6766 gchar *name, *from = NULL;
6768 if (ac == compose->account) def_menu = num;
6770 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
6771 ac->account_name);
6773 if (ac == compose->account) {
6774 if (ac->name && *ac->name) {
6775 gchar *buf;
6776 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
6777 from = g_strdup_printf("%s <%s>",
6778 buf, ac->address);
6779 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6780 } else {
6781 from = g_strdup_printf("%s",
6782 ac->address);
6783 gtk_entry_set_text(GTK_ENTRY(from_name), from);
6786 COMBOBOX_ADD(menu, name, ac->account_id);
6787 g_free(name);
6788 g_free(from);
6791 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
6793 g_signal_connect(G_OBJECT(optmenu), "changed",
6794 G_CALLBACK(account_activated),
6795 compose);
6796 g_signal_connect(G_OBJECT(from_name), "populate-popup",
6797 G_CALLBACK(compose_entry_popup_extend),
6798 NULL);
6800 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
6801 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
6803 gtk_tooltips_set_tip(compose->tooltips, optmenubox,
6804 _("Account to use for this email"), NULL);
6805 gtk_tooltips_set_tip(compose->tooltips, from_name,
6806 _("Sender address to be used"), NULL);
6808 compose->from_name = from_name;
6810 return hbox;
6813 static void compose_set_priority_cb(gpointer data,
6814 guint action,
6815 GtkWidget *widget)
6817 Compose *compose = (Compose *) data;
6818 compose->priority = action;
6821 static void compose_reply_change_mode(gpointer data,
6822 ComposeMode action,
6823 GtkWidget *widget)
6825 Compose *compose = (Compose *) data;
6826 gboolean was_modified = compose->modified;
6828 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
6830 g_return_if_fail(compose->replyinfo != NULL);
6832 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
6833 ml = TRUE;
6834 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
6835 followup = TRUE;
6836 if (action == COMPOSE_REPLY_TO_ALL)
6837 all = TRUE;
6838 if (action == COMPOSE_REPLY_TO_SENDER)
6839 sender = TRUE;
6840 if (action == COMPOSE_REPLY_TO_LIST)
6841 ml = TRUE;
6843 compose_remove_header_entries(compose);
6844 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
6845 if (compose->account->set_autocc && compose->account->auto_cc)
6846 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
6848 if (compose->account->set_autobcc && compose->account->auto_bcc)
6849 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
6851 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
6852 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
6853 compose_show_first_last_header(compose, TRUE);
6854 compose->modified = was_modified;
6855 compose_set_title(compose);
6858 static void compose_update_priority_menu_item(Compose * compose)
6860 GtkItemFactory *ifactory;
6861 GtkWidget *menuitem = NULL;
6863 ifactory = gtk_item_factory_from_widget(compose->menubar);
6865 switch (compose->priority) {
6866 case PRIORITY_HIGHEST:
6867 menuitem = gtk_item_factory_get_item
6868 (ifactory, "/Options/Priority/Highest");
6869 break;
6870 case PRIORITY_HIGH:
6871 menuitem = gtk_item_factory_get_item
6872 (ifactory, "/Options/Priority/High");
6873 break;
6874 case PRIORITY_NORMAL:
6875 menuitem = gtk_item_factory_get_item
6876 (ifactory, "/Options/Priority/Normal");
6877 break;
6878 case PRIORITY_LOW:
6879 menuitem = gtk_item_factory_get_item
6880 (ifactory, "/Options/Priority/Low");
6881 break;
6882 case PRIORITY_LOWEST:
6883 menuitem = gtk_item_factory_get_item
6884 (ifactory, "/Options/Priority/Lowest");
6885 break;
6887 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
6890 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
6892 Compose *compose = (Compose *) data;
6893 gchar *systemid;
6894 GtkItemFactory *ifactory;
6895 gboolean can_sign = FALSE, can_encrypt = FALSE;
6897 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
6899 if (!GTK_CHECK_MENU_ITEM(widget)->active)
6900 return;
6902 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
6903 g_free(compose->privacy_system);
6904 compose->privacy_system = NULL;
6905 if (systemid != NULL) {
6906 compose->privacy_system = g_strdup(systemid);
6908 can_sign = privacy_system_can_sign(systemid);
6909 can_encrypt = privacy_system_can_encrypt(systemid);
6912 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
6914 ifactory = gtk_item_factory_from_widget(compose->menubar);
6915 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
6916 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
6919 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
6921 static gchar *branch_path = "/Options/Privacy System";
6922 GtkItemFactory *ifactory;
6923 GtkWidget *menuitem = NULL;
6924 GList *amenu;
6925 gboolean can_sign = FALSE, can_encrypt = FALSE;
6926 gboolean found = FALSE;
6928 ifactory = gtk_item_factory_from_widget(compose->menubar);
6930 if (compose->privacy_system != NULL) {
6931 gchar *systemid;
6933 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
6934 g_return_if_fail(menuitem != NULL);
6936 amenu = GTK_MENU_SHELL(menuitem)->children;
6937 menuitem = NULL;
6938 while (amenu != NULL) {
6939 GList *alist = amenu->next;
6941 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
6942 if (systemid != NULL) {
6943 if (strcmp(systemid, compose->privacy_system) == 0) {
6944 menuitem = GTK_WIDGET(amenu->data);
6946 can_sign = privacy_system_can_sign(systemid);
6947 can_encrypt = privacy_system_can_encrypt(systemid);
6948 found = TRUE;
6949 break;
6951 } else if (strlen(compose->privacy_system) == 0) {
6952 menuitem = GTK_WIDGET(amenu->data);
6954 can_sign = FALSE;
6955 can_encrypt = FALSE;
6956 found = TRUE;
6957 break;
6960 amenu = alist;
6962 if (menuitem != NULL)
6963 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
6965 if (warn && !found && strlen(compose->privacy_system)) {
6966 gchar *tmp = g_strdup_printf(
6967 _("The privacy system '%s' cannot be loaded. You "
6968 "will not be able to sign or encrypt this message."),
6969 compose->privacy_system);
6970 alertpanel_warning(tmp);
6971 g_free(tmp);
6975 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
6976 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
6979 static void compose_set_out_encoding(Compose *compose)
6981 GtkItemFactoryEntry *entry;
6982 GtkItemFactory *ifactory;
6983 CharSet out_encoding;
6984 gchar *path, *p, *q;
6985 GtkWidget *item;
6987 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
6988 ifactory = gtk_item_factory_from_widget(compose->menubar);
6990 for (entry = compose_entries; entry->callback != compose_address_cb;
6991 entry++) {
6992 if (entry->callback == compose_set_encoding_cb &&
6993 (CharSet)entry->callback_action == out_encoding) {
6994 p = q = path = g_strdup(entry->path);
6995 while (*p) {
6996 if (*p == '_') {
6997 if (p[1] == '_') {
6998 p++;
6999 *q++ = '_';
7001 } else
7002 *q++ = *p;
7003 p++;
7005 *q = '\0';
7006 item = gtk_item_factory_get_item(ifactory, path);
7007 gtk_widget_activate(item);
7008 g_free(path);
7009 break;
7014 static void compose_set_template_menu(Compose *compose)
7016 GSList *tmpl_list, *cur;
7017 GtkWidget *menu;
7018 GtkWidget *item;
7020 tmpl_list = template_get_config();
7022 menu = gtk_menu_new();
7024 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7025 Template *tmpl = (Template *)cur->data;
7027 item = gtk_menu_item_new_with_label(tmpl->name);
7028 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7029 g_signal_connect(G_OBJECT(item), "activate",
7030 G_CALLBACK(compose_template_activate_cb),
7031 compose);
7032 g_object_set_data(G_OBJECT(item), "template", tmpl);
7033 gtk_widget_show(item);
7036 gtk_widget_show(menu);
7037 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7040 void compose_update_actions_menu(Compose *compose)
7042 GtkItemFactory *ifactory;
7044 ifactory = gtk_item_factory_from_widget(compose->menubar);
7045 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7048 static void compose_update_privacy_systems_menu(Compose *compose)
7050 static gchar *branch_path = "/Options/Privacy System";
7051 GtkItemFactory *ifactory;
7052 GtkWidget *menuitem;
7053 GSList *systems, *cur;
7054 GList *amenu;
7055 GtkWidget *widget;
7056 GtkWidget *system_none;
7057 GSList *group;
7059 ifactory = gtk_item_factory_from_widget(compose->menubar);
7061 /* remove old entries */
7062 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7063 g_return_if_fail(menuitem != NULL);
7065 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7066 while (amenu != NULL) {
7067 GList *alist = amenu->next;
7068 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7069 amenu = alist;
7072 system_none = gtk_item_factory_get_widget(ifactory,
7073 "/Options/Privacy System/None");
7075 g_signal_connect(G_OBJECT(system_none), "activate",
7076 G_CALLBACK(compose_set_privacy_system_cb), compose);
7078 systems = privacy_get_system_ids();
7079 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7080 gchar *systemid = cur->data;
7082 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7083 widget = gtk_radio_menu_item_new_with_label(group,
7084 privacy_system_get_name(systemid));
7085 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7086 g_strdup(systemid), g_free);
7087 g_signal_connect(G_OBJECT(widget), "activate",
7088 G_CALLBACK(compose_set_privacy_system_cb), compose);
7090 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7091 gtk_widget_show(widget);
7092 g_free(systemid);
7094 g_slist_free(systems);
7097 void compose_reflect_prefs_all(void)
7099 GList *cur;
7100 Compose *compose;
7102 for (cur = compose_list; cur != NULL; cur = cur->next) {
7103 compose = (Compose *)cur->data;
7104 compose_set_template_menu(compose);
7108 void compose_reflect_prefs_pixmap_theme(void)
7110 GList *cur;
7111 Compose *compose;
7113 for (cur = compose_list; cur != NULL; cur = cur->next) {
7114 compose = (Compose *)cur->data;
7115 toolbar_update(TOOLBAR_COMPOSE, compose);
7119 static const gchar *compose_quote_char_from_context(Compose *compose)
7121 const gchar *qmark = NULL;
7123 g_return_val_if_fail(compose != NULL, NULL);
7125 switch (compose->mode) {
7126 /* use forward-specific quote char */
7127 case COMPOSE_FORWARD:
7128 case COMPOSE_FORWARD_AS_ATTACH:
7129 case COMPOSE_FORWARD_INLINE:
7130 if (compose->folder && compose->folder->prefs &&
7131 compose->folder->prefs->forward_with_format)
7132 qmark = compose->folder->prefs->forward_quotemark;
7133 else if (compose->account->forward_with_format)
7134 qmark = compose->account->forward_quotemark;
7135 else
7136 qmark = prefs_common.fw_quotemark;
7137 break;
7139 /* use reply-specific quote char in all other modes */
7140 default:
7141 if (compose->folder && compose->folder->prefs &&
7142 compose->folder->prefs->reply_with_format)
7143 qmark = compose->folder->prefs->reply_quotemark;
7144 else if (compose->account->reply_with_format)
7145 qmark = compose->account->reply_quotemark;
7146 else
7147 qmark = prefs_common.quotemark;
7148 break;
7151 if (qmark == NULL || *qmark == '\0')
7152 qmark = "> ";
7154 return qmark;
7157 static void compose_template_apply(Compose *compose, Template *tmpl,
7158 gboolean replace)
7160 GtkTextView *text;
7161 GtkTextBuffer *buffer;
7162 GtkTextMark *mark;
7163 GtkTextIter iter;
7164 const gchar *qmark;
7165 gchar *parsed_str = NULL;
7166 gint cursor_pos = 0;
7167 const gchar *err_msg = _("Template body format error at line %d.");
7168 if (!tmpl) return;
7170 /* process the body */
7172 text = GTK_TEXT_VIEW(compose->text);
7173 buffer = gtk_text_view_get_buffer(text);
7175 if (tmpl->value) {
7176 qmark = compose_quote_char_from_context(compose);
7178 if (compose->replyinfo != NULL) {
7180 if (replace)
7181 gtk_text_buffer_set_text(buffer, "", -1);
7182 mark = gtk_text_buffer_get_insert(buffer);
7183 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7185 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7186 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7188 } else if (compose->fwdinfo != NULL) {
7190 if (replace)
7191 gtk_text_buffer_set_text(buffer, "", -1);
7192 mark = gtk_text_buffer_get_insert(buffer);
7193 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7195 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7196 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7198 } else {
7199 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7201 GtkTextIter start, end;
7202 gchar *tmp = NULL;
7204 gtk_text_buffer_get_start_iter(buffer, &start);
7205 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7206 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7208 /* clear the buffer now */
7209 if (replace)
7210 gtk_text_buffer_set_text(buffer, "", -1);
7212 parsed_str = compose_quote_fmt(compose, dummyinfo,
7213 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7214 procmsg_msginfo_free( dummyinfo );
7216 g_free( tmp );
7218 } else {
7219 if (replace)
7220 gtk_text_buffer_set_text(buffer, "", -1);
7221 mark = gtk_text_buffer_get_insert(buffer);
7222 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7225 if (replace && parsed_str && compose->account->auto_sig)
7226 compose_insert_sig(compose, FALSE);
7228 if (replace && parsed_str) {
7229 gtk_text_buffer_get_start_iter(buffer, &iter);
7230 gtk_text_buffer_place_cursor(buffer, &iter);
7233 if (parsed_str) {
7234 cursor_pos = quote_fmt_get_cursor_pos();
7235 compose->set_cursor_pos = cursor_pos;
7236 if (cursor_pos == -1)
7237 cursor_pos = 0;
7238 gtk_text_buffer_get_start_iter(buffer, &iter);
7239 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7240 gtk_text_buffer_place_cursor(buffer, &iter);
7243 /* process the other fields */
7245 compose_template_apply_fields(compose, tmpl);
7246 quote_fmt_reset_vartable();
7247 compose_changed_cb(NULL, compose);
7250 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7252 MsgInfo* dummyinfo = NULL;
7253 MsgInfo *msginfo = NULL;
7254 gchar *buf = NULL;
7256 if (compose->replyinfo != NULL)
7257 msginfo = compose->replyinfo;
7258 else if (compose->fwdinfo != NULL)
7259 msginfo = compose->fwdinfo;
7260 else {
7261 dummyinfo = compose_msginfo_new_from_compose(compose);
7262 msginfo = dummyinfo;
7265 if (tmpl->to && *tmpl->to != '\0') {
7266 #ifdef USE_ASPELL
7267 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7268 compose->gtkaspell);
7269 #else
7270 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7271 #endif
7272 quote_fmt_scan_string(tmpl->to);
7273 quote_fmt_parse();
7275 buf = quote_fmt_get_buffer();
7276 if (buf == NULL) {
7277 alertpanel_error(_("Template To format error."));
7278 } else {
7279 compose_entry_append(compose, buf, COMPOSE_TO);
7283 if (tmpl->cc && *tmpl->cc != '\0') {
7284 #ifdef USE_ASPELL
7285 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7286 compose->gtkaspell);
7287 #else
7288 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7289 #endif
7290 quote_fmt_scan_string(tmpl->cc);
7291 quote_fmt_parse();
7293 buf = quote_fmt_get_buffer();
7294 if (buf == NULL) {
7295 alertpanel_error(_("Template Cc format error."));
7296 } else {
7297 compose_entry_append(compose, buf, COMPOSE_CC);
7301 if (tmpl->bcc && *tmpl->bcc != '\0') {
7302 #ifdef USE_ASPELL
7303 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7304 compose->gtkaspell);
7305 #else
7306 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7307 #endif
7308 quote_fmt_scan_string(tmpl->bcc);
7309 quote_fmt_parse();
7311 buf = quote_fmt_get_buffer();
7312 if (buf == NULL) {
7313 alertpanel_error(_("Template Bcc format error."));
7314 } else {
7315 compose_entry_append(compose, buf, COMPOSE_BCC);
7319 /* process the subject */
7320 if (tmpl->subject && *tmpl->subject != '\0') {
7321 #ifdef USE_ASPELL
7322 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account,
7323 compose->gtkaspell);
7324 #else
7325 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account);
7326 #endif
7327 quote_fmt_scan_string(tmpl->subject);
7328 quote_fmt_parse();
7330 buf = quote_fmt_get_buffer();
7331 if (buf == NULL) {
7332 alertpanel_error(_("Template subject format error."));
7333 } else {
7334 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7338 procmsg_msginfo_free( dummyinfo );
7341 static void compose_destroy(Compose *compose)
7343 GtkTextBuffer *buffer;
7344 GtkClipboard *clipboard;
7346 compose_list = g_list_remove(compose_list, compose);
7348 if (compose->updating) {
7349 debug_print("danger, not destroying anything now\n");
7350 compose->deferred_destroy = TRUE;
7351 return;
7353 /* NOTE: address_completion_end() does nothing with the window
7354 * however this may change. */
7355 address_completion_end(compose->window);
7357 slist_free_strings(compose->to_list);
7358 g_slist_free(compose->to_list);
7359 slist_free_strings(compose->newsgroup_list);
7360 g_slist_free(compose->newsgroup_list);
7361 slist_free_strings(compose->header_list);
7362 g_slist_free(compose->header_list);
7364 procmsg_msginfo_free(compose->targetinfo);
7365 procmsg_msginfo_free(compose->replyinfo);
7366 procmsg_msginfo_free(compose->fwdinfo);
7368 g_free(compose->replyto);
7369 g_free(compose->cc);
7370 g_free(compose->bcc);
7371 g_free(compose->newsgroups);
7372 g_free(compose->followup_to);
7374 g_free(compose->ml_post);
7376 g_free(compose->inreplyto);
7377 g_free(compose->references);
7378 g_free(compose->msgid);
7379 g_free(compose->boundary);
7381 g_free(compose->redirect_filename);
7382 if (compose->undostruct)
7383 undo_destroy(compose->undostruct);
7385 g_free(compose->sig_str);
7387 g_free(compose->exteditor_file);
7389 g_free(compose->orig_charset);
7391 g_free(compose->privacy_system);
7393 if (addressbook_get_target_compose() == compose)
7394 addressbook_set_target_compose(NULL);
7396 #if USE_ASPELL
7397 if (compose->gtkaspell) {
7398 gtkaspell_delete(compose->gtkaspell);
7399 compose->gtkaspell = NULL;
7401 #endif
7403 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7404 prefs_common.compose_height = compose->window->allocation.height;
7406 if (!gtk_widget_get_parent(compose->paned))
7407 gtk_widget_destroy(compose->paned);
7408 gtk_widget_destroy(compose->popupmenu);
7410 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7411 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7412 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7414 gtk_widget_destroy(compose->window);
7415 toolbar_destroy(compose->toolbar);
7416 g_free(compose->toolbar);
7417 g_mutex_free(compose->mutex);
7418 g_free(compose);
7421 static void compose_attach_info_free(AttachInfo *ainfo)
7423 g_free(ainfo->file);
7424 g_free(ainfo->content_type);
7425 g_free(ainfo->name);
7426 g_free(ainfo);
7429 static void compose_attach_remove_selected(Compose *compose)
7431 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7432 GtkTreeSelection *selection;
7433 GList *sel, *cur;
7434 GtkTreeModel *model;
7436 selection = gtk_tree_view_get_selection(tree_view);
7437 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7439 if (!sel)
7440 return;
7442 for (cur = sel; cur != NULL; cur = cur->next) {
7443 GtkTreePath *path = cur->data;
7444 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7445 (model, cur->data);
7446 cur->data = ref;
7447 gtk_tree_path_free(path);
7450 for (cur = sel; cur != NULL; cur = cur->next) {
7451 GtkTreeRowReference *ref = cur->data;
7452 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7453 GtkTreeIter iter;
7455 if (gtk_tree_model_get_iter(model, &iter, path))
7456 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7458 gtk_tree_path_free(path);
7459 gtk_tree_row_reference_free(ref);
7462 g_list_free(sel);
7465 static struct _AttachProperty
7467 GtkWidget *window;
7468 GtkWidget *mimetype_entry;
7469 GtkWidget *encoding_optmenu;
7470 GtkWidget *path_entry;
7471 GtkWidget *filename_entry;
7472 GtkWidget *ok_btn;
7473 GtkWidget *cancel_btn;
7474 } attach_prop;
7476 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7478 gtk_tree_path_free((GtkTreePath *)ptr);
7481 static void compose_attach_property(Compose *compose)
7483 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7484 AttachInfo *ainfo;
7485 GtkComboBox *optmenu;
7486 GtkTreeSelection *selection;
7487 GList *sel;
7488 GtkTreeModel *model;
7489 GtkTreeIter iter;
7490 GtkTreePath *path;
7491 static gboolean cancelled;
7493 /* only if one selected */
7494 selection = gtk_tree_view_get_selection(tree_view);
7495 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7496 return;
7498 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7499 if (!sel)
7500 return;
7502 path = (GtkTreePath *) sel->data;
7503 gtk_tree_model_get_iter(model, &iter, path);
7504 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7506 if (!ainfo) {
7507 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7508 g_list_free(sel);
7509 return;
7511 g_list_free(sel);
7513 if (!attach_prop.window)
7514 compose_attach_property_create(&cancelled);
7515 gtk_widget_grab_focus(attach_prop.ok_btn);
7516 gtk_widget_show(attach_prop.window);
7517 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
7519 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
7520 if (ainfo->encoding == ENC_UNKNOWN)
7521 combobox_select_by_data(optmenu, ENC_BASE64);
7522 else
7523 combobox_select_by_data(optmenu, ainfo->encoding);
7525 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
7526 ainfo->content_type ? ainfo->content_type : "");
7527 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
7528 ainfo->file ? ainfo->file : "");
7529 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
7530 ainfo->name ? ainfo->name : "");
7532 for (;;) {
7533 const gchar *entry_text;
7534 gchar *text;
7535 gchar *cnttype = NULL;
7536 gchar *file = NULL;
7537 off_t size = 0;
7539 cancelled = FALSE;
7540 gtk_main();
7542 gtk_widget_hide(attach_prop.window);
7544 if (cancelled)
7545 break;
7547 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
7548 if (*entry_text != '\0') {
7549 gchar *p;
7551 text = g_strstrip(g_strdup(entry_text));
7552 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
7553 cnttype = g_strdup(text);
7554 g_free(text);
7555 } else {
7556 alertpanel_error(_("Invalid MIME type."));
7557 g_free(text);
7558 continue;
7562 ainfo->encoding = combobox_get_active_data(optmenu);
7564 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
7565 if (*entry_text != '\0') {
7566 if (is_file_exist(entry_text) &&
7567 (size = get_file_size(entry_text)) > 0)
7568 file = g_strdup(entry_text);
7569 else {
7570 alertpanel_error
7571 (_("File doesn't exist or is empty."));
7572 g_free(cnttype);
7573 continue;
7577 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
7578 if (*entry_text != '\0') {
7579 g_free(ainfo->name);
7580 ainfo->name = g_strdup(entry_text);
7583 if (cnttype) {
7584 g_free(ainfo->content_type);
7585 ainfo->content_type = cnttype;
7587 if (file) {
7588 g_free(ainfo->file);
7589 ainfo->file = file;
7591 if (size)
7592 ainfo->size = size;
7594 /* update tree store */
7595 text = to_human_readable(ainfo->size);
7596 gtk_tree_model_get_iter(model, &iter, path);
7597 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
7598 COL_MIMETYPE, ainfo->content_type,
7599 COL_SIZE, text,
7600 COL_NAME, ainfo->name,
7601 -1);
7603 break;
7606 gtk_tree_path_free(path);
7609 #define SET_LABEL_AND_ENTRY(str, entry, top) \
7611 label = gtk_label_new(str); \
7612 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
7613 GTK_FILL, 0, 0, 0); \
7614 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
7616 entry = gtk_entry_new(); \
7617 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
7618 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
7621 static void compose_attach_property_create(gboolean *cancelled)
7623 GtkWidget *window;
7624 GtkWidget *vbox;
7625 GtkWidget *table;
7626 GtkWidget *label;
7627 GtkWidget *mimetype_entry;
7628 GtkWidget *hbox;
7629 GtkWidget *optmenu;
7630 GtkListStore *optmenu_menu;
7631 GtkWidget *path_entry;
7632 GtkWidget *filename_entry;
7633 GtkWidget *hbbox;
7634 GtkWidget *ok_btn;
7635 GtkWidget *cancel_btn;
7636 GList *mime_type_list, *strlist;
7637 GtkTreeIter iter;
7639 debug_print("Creating attach_property window...\n");
7641 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
7642 gtk_widget_set_size_request(window, 480, -1);
7643 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
7644 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
7645 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
7646 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
7647 g_signal_connect(G_OBJECT(window), "delete_event",
7648 G_CALLBACK(attach_property_delete_event),
7649 cancelled);
7650 g_signal_connect(G_OBJECT(window), "key_press_event",
7651 G_CALLBACK(attach_property_key_pressed),
7652 cancelled);
7654 vbox = gtk_vbox_new(FALSE, 8);
7655 gtk_container_add(GTK_CONTAINER(window), vbox);
7657 table = gtk_table_new(4, 2, FALSE);
7658 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
7659 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
7660 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
7662 label = gtk_label_new(_("MIME type"));
7663 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
7664 GTK_FILL, 0, 0, 0);
7665 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7666 mimetype_entry = gtk_combo_new();
7667 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
7668 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7670 /* stuff with list */
7671 mime_type_list = procmime_get_mime_type_list();
7672 strlist = NULL;
7673 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
7674 MimeType *type = (MimeType *) mime_type_list->data;
7675 gchar *tmp;
7677 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
7679 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
7680 g_free(tmp);
7681 else
7682 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
7683 (GCompareFunc)strcmp2);
7686 gtk_combo_set_popdown_strings(GTK_COMBO(mimetype_entry), strlist);
7688 for (mime_type_list = strlist; mime_type_list != NULL;
7689 mime_type_list = mime_type_list->next)
7690 g_free(mime_type_list->data);
7691 g_list_free(strlist);
7693 mimetype_entry = GTK_COMBO(mimetype_entry)->entry;
7695 label = gtk_label_new(_("Encoding"));
7696 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
7697 GTK_FILL, 0, 0, 0);
7698 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
7700 hbox = gtk_hbox_new(FALSE, 0);
7701 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
7702 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
7704 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
7705 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7707 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
7708 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
7709 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
7710 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
7711 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
7713 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
7715 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
7716 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
7718 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
7719 &ok_btn, GTK_STOCK_OK,
7720 NULL, NULL);
7721 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
7722 gtk_widget_grab_default(ok_btn);
7724 g_signal_connect(G_OBJECT(ok_btn), "clicked",
7725 G_CALLBACK(attach_property_ok),
7726 cancelled);
7727 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
7728 G_CALLBACK(attach_property_cancel),
7729 cancelled);
7731 gtk_widget_show_all(vbox);
7733 attach_prop.window = window;
7734 attach_prop.mimetype_entry = mimetype_entry;
7735 attach_prop.encoding_optmenu = optmenu;
7736 attach_prop.path_entry = path_entry;
7737 attach_prop.filename_entry = filename_entry;
7738 attach_prop.ok_btn = ok_btn;
7739 attach_prop.cancel_btn = cancel_btn;
7742 #undef SET_LABEL_AND_ENTRY
7744 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
7746 *cancelled = FALSE;
7747 gtk_main_quit();
7750 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
7752 *cancelled = TRUE;
7753 gtk_main_quit();
7756 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
7757 gboolean *cancelled)
7759 *cancelled = TRUE;
7760 gtk_main_quit();
7762 return TRUE;
7765 static gboolean attach_property_key_pressed(GtkWidget *widget,
7766 GdkEventKey *event,
7767 gboolean *cancelled)
7769 if (event && event->keyval == GDK_Escape) {
7770 *cancelled = TRUE;
7771 gtk_main_quit();
7773 return FALSE;
7776 static void compose_exec_ext_editor(Compose *compose)
7778 #ifdef G_OS_UNIX
7779 gchar *tmp;
7780 pid_t pid;
7781 gint pipe_fds[2];
7783 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
7784 G_DIR_SEPARATOR, compose);
7786 if (pipe(pipe_fds) < 0) {
7787 perror("pipe");
7788 g_free(tmp);
7789 return;
7792 if ((pid = fork()) < 0) {
7793 perror("fork");
7794 g_free(tmp);
7795 return;
7798 if (pid != 0) {
7799 /* close the write side of the pipe */
7800 close(pipe_fds[1]);
7802 compose->exteditor_file = g_strdup(tmp);
7803 compose->exteditor_pid = pid;
7805 compose_set_ext_editor_sensitive(compose, FALSE);
7807 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
7808 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
7809 G_IO_IN,
7810 compose_input_cb,
7811 compose);
7812 } else { /* process-monitoring process */
7813 pid_t pid_ed;
7815 if (setpgid(0, 0))
7816 perror("setpgid");
7818 /* close the read side of the pipe */
7819 close(pipe_fds[0]);
7821 if (compose_write_body_to_file(compose, tmp) < 0) {
7822 fd_write_all(pipe_fds[1], "2\n", 2);
7823 _exit(1);
7826 pid_ed = compose_exec_ext_editor_real(tmp);
7827 if (pid_ed < 0) {
7828 fd_write_all(pipe_fds[1], "1\n", 2);
7829 _exit(1);
7832 /* wait until editor is terminated */
7833 waitpid(pid_ed, NULL, 0);
7835 fd_write_all(pipe_fds[1], "0\n", 2);
7837 close(pipe_fds[1]);
7838 _exit(0);
7841 g_free(tmp);
7842 #endif /* G_OS_UNIX */
7845 #ifdef G_OS_UNIX
7846 static gint compose_exec_ext_editor_real(const gchar *file)
7848 gchar buf[1024];
7849 gchar *p;
7850 gchar **cmdline;
7851 pid_t pid;
7853 g_return_val_if_fail(file != NULL, -1);
7855 if ((pid = fork()) < 0) {
7856 perror("fork");
7857 return -1;
7860 if (pid != 0) return pid;
7862 /* grandchild process */
7864 if (setpgid(0, getppid()))
7865 perror("setpgid");
7867 if (prefs_common.ext_editor_cmd &&
7868 (p = strchr(prefs_common.ext_editor_cmd, '%')) &&
7869 *(p + 1) == 's' && !strchr(p + 2, '%')) {
7870 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, file);
7871 } else {
7872 if (prefs_common.ext_editor_cmd)
7873 g_warning("External editor command line is invalid: '%s'\n",
7874 prefs_common.ext_editor_cmd);
7875 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
7878 cmdline = strsplit_with_quote(buf, " ", 1024);
7879 execvp(cmdline[0], cmdline);
7881 perror("execvp");
7882 g_strfreev(cmdline);
7884 _exit(1);
7887 static gboolean compose_ext_editor_kill(Compose *compose)
7889 pid_t pgid = compose->exteditor_pid * -1;
7890 gint ret;
7892 ret = kill(pgid, 0);
7894 if (ret == 0 || (ret == -1 && EPERM == errno)) {
7895 AlertValue val;
7896 gchar *msg;
7898 msg = g_strdup_printf
7899 (_("The external editor is still working.\n"
7900 "Force terminating the process?\n"
7901 "process group id: %d"), -pgid);
7902 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
7903 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
7905 g_free(msg);
7907 if (val == G_ALERTALTERNATE) {
7908 g_source_remove(compose->exteditor_tag);
7909 g_io_channel_shutdown(compose->exteditor_ch,
7910 FALSE, NULL);
7911 g_io_channel_unref(compose->exteditor_ch);
7913 if (kill(pgid, SIGTERM) < 0) perror("kill");
7914 waitpid(compose->exteditor_pid, NULL, 0);
7916 g_warning("Terminated process group id: %d", -pgid);
7917 g_warning("Temporary file: %s",
7918 compose->exteditor_file);
7920 compose_set_ext_editor_sensitive(compose, TRUE);
7922 g_free(compose->exteditor_file);
7923 compose->exteditor_file = NULL;
7924 compose->exteditor_pid = -1;
7925 compose->exteditor_ch = NULL;
7926 compose->exteditor_tag = -1;
7927 } else
7928 return FALSE;
7931 return TRUE;
7934 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
7935 gpointer data)
7937 gchar buf[3] = "3";
7938 Compose *compose = (Compose *)data;
7939 gsize bytes_read;
7941 debug_print(_("Compose: input from monitoring process\n"));
7943 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
7945 g_io_channel_shutdown(source, FALSE, NULL);
7946 g_io_channel_unref(source);
7948 waitpid(compose->exteditor_pid, NULL, 0);
7950 if (buf[0] == '0') { /* success */
7951 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
7952 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
7954 gtk_text_buffer_set_text(buffer, "", -1);
7955 compose_insert_file(compose, compose->exteditor_file);
7956 compose_changed_cb(NULL, compose);
7958 if (g_unlink(compose->exteditor_file) < 0)
7959 FILE_OP_ERROR(compose->exteditor_file, "unlink");
7960 } else if (buf[0] == '1') { /* failed */
7961 g_warning("Couldn't exec external editor\n");
7962 if (g_unlink(compose->exteditor_file) < 0)
7963 FILE_OP_ERROR(compose->exteditor_file, "unlink");
7964 } else if (buf[0] == '2') {
7965 g_warning("Couldn't write to file\n");
7966 } else if (buf[0] == '3') {
7967 g_warning("Pipe read failed\n");
7970 compose_set_ext_editor_sensitive(compose, TRUE);
7972 g_free(compose->exteditor_file);
7973 compose->exteditor_file = NULL;
7974 compose->exteditor_pid = -1;
7975 compose->exteditor_ch = NULL;
7976 compose->exteditor_tag = -1;
7978 return FALSE;
7981 static void compose_set_ext_editor_sensitive(Compose *compose,
7982 gboolean sensitive)
7984 GtkItemFactory *ifactory;
7986 ifactory = gtk_item_factory_from_widget(compose->menubar);
7988 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
7989 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
7990 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
7991 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
7992 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
7993 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
7994 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
7995 sensitive);
7997 gtk_widget_set_sensitive(compose->text, sensitive);
7998 if (compose->toolbar->send_btn)
7999 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8000 if (compose->toolbar->sendl_btn)
8001 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8002 if (compose->toolbar->draft_btn)
8003 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8004 if (compose->toolbar->insert_btn)
8005 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8006 if (compose->toolbar->sig_btn)
8007 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8008 if (compose->toolbar->exteditor_btn)
8009 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8010 if (compose->toolbar->linewrap_current_btn)
8011 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8012 if (compose->toolbar->linewrap_all_btn)
8013 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8015 #endif /* G_OS_UNIX */
8018 * compose_undo_state_changed:
8020 * Change the sensivity of the menuentries undo and redo
8022 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8023 gint redo_state, gpointer data)
8025 GtkWidget *widget = GTK_WIDGET(data);
8026 GtkItemFactory *ifactory;
8028 g_return_if_fail(widget != NULL);
8030 ifactory = gtk_item_factory_from_widget(widget);
8032 switch (undo_state) {
8033 case UNDO_STATE_TRUE:
8034 if (!undostruct->undo_state) {
8035 undostruct->undo_state = TRUE;
8036 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8038 break;
8039 case UNDO_STATE_FALSE:
8040 if (undostruct->undo_state) {
8041 undostruct->undo_state = FALSE;
8042 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8044 break;
8045 case UNDO_STATE_UNCHANGED:
8046 break;
8047 case UNDO_STATE_REFRESH:
8048 menu_set_sensitive(ifactory, "/Edit/Undo",
8049 undostruct->undo_state);
8050 break;
8051 default:
8052 g_warning("Undo state not recognized");
8053 break;
8056 switch (redo_state) {
8057 case UNDO_STATE_TRUE:
8058 if (!undostruct->redo_state) {
8059 undostruct->redo_state = TRUE;
8060 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8062 break;
8063 case UNDO_STATE_FALSE:
8064 if (undostruct->redo_state) {
8065 undostruct->redo_state = FALSE;
8066 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8068 break;
8069 case UNDO_STATE_UNCHANGED:
8070 break;
8071 case UNDO_STATE_REFRESH:
8072 menu_set_sensitive(ifactory, "/Edit/Redo",
8073 undostruct->redo_state);
8074 break;
8075 default:
8076 g_warning("Redo state not recognized");
8077 break;
8081 /* callback functions */
8083 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8084 * includes "non-client" (windows-izm) in calculation, so this calculation
8085 * may not be accurate.
8087 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8088 GtkAllocation *allocation,
8089 GtkSHRuler *shruler)
8091 if (prefs_common.show_ruler) {
8092 gint char_width = 0, char_height = 0;
8093 gint line_width_in_chars;
8095 gtkut_get_font_size(GTK_WIDGET(widget),
8096 &char_width, &char_height);
8097 line_width_in_chars =
8098 (allocation->width - allocation->x) / char_width;
8100 /* got the maximum */
8101 gtk_ruler_set_range(GTK_RULER(shruler),
8102 0.0, line_width_in_chars, 0,
8103 /*line_width_in_chars*/ char_width);
8106 return TRUE;
8109 static void account_activated(GtkComboBox *optmenu, gpointer data)
8111 Compose *compose = (Compose *)data;
8113 PrefsAccount *ac;
8114 gchar *folderidentifier;
8115 gint account_id = 0;
8116 GtkTreeModel *menu;
8117 GtkTreeIter iter;
8119 /* Get ID of active account in the combo box */
8120 menu = gtk_combo_box_get_model(optmenu);
8121 gtk_combo_box_get_active_iter(optmenu, &iter);
8122 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8124 ac = account_find_from_id(account_id);
8125 g_return_if_fail(ac != NULL);
8127 if (ac != compose->account)
8128 compose_select_account(compose, ac, FALSE);
8130 /* Set message save folder */
8131 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8132 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8134 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8135 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8137 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8138 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8139 folderidentifier = folder_item_get_identifier(account_get_special_folder
8140 (compose->account, F_OUTBOX));
8141 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8142 g_free(folderidentifier);
8146 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8147 GtkTreeViewColumn *column, Compose *compose)
8149 compose_attach_property(compose);
8152 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8153 gpointer data)
8155 Compose *compose = (Compose *)data;
8156 GtkTreeSelection *attach_selection;
8157 gint attach_nr_selected;
8158 GtkItemFactory *ifactory;
8160 if (!event) return FALSE;
8162 if (event->button == 3) {
8163 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8164 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8165 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8167 if (attach_nr_selected > 0)
8169 menu_set_sensitive(ifactory, "/Remove", TRUE);
8170 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8171 } else {
8172 menu_set_sensitive(ifactory, "/Remove", FALSE);
8173 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8176 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8177 NULL, NULL, event->button, event->time);
8178 return TRUE;
8181 return FALSE;
8184 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8185 gpointer data)
8187 Compose *compose = (Compose *)data;
8189 if (!event) return FALSE;
8191 switch (event->keyval) {
8192 case GDK_Delete:
8193 compose_attach_remove_selected(compose);
8194 break;
8196 return FALSE;
8199 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8201 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8202 toolbar_comp_set_sensitive(compose, allow);
8203 menu_set_sensitive(ifactory, "/Message", allow);
8204 menu_set_sensitive(ifactory, "/Edit", allow);
8205 #if USE_ASPELL
8206 menu_set_sensitive(ifactory, "/Spelling", allow);
8207 #endif
8208 menu_set_sensitive(ifactory, "/Options", allow);
8209 menu_set_sensitive(ifactory, "/Tools", allow);
8210 menu_set_sensitive(ifactory, "/Help", allow);
8212 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8216 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8218 Compose *compose = (Compose *)data;
8220 if (prefs_common.work_offline &&
8221 !inc_offline_should_override(TRUE,
8222 _("Claws Mail needs network access in order "
8223 "to send this email.")))
8224 return;
8226 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8227 g_source_remove(compose->draft_timeout_tag);
8228 compose->draft_timeout_tag = -1;
8231 compose_send(compose);
8234 static void compose_send_later_cb(gpointer data, guint action,
8235 GtkWidget *widget)
8237 Compose *compose = (Compose *)data;
8238 gint val;
8240 inc_lock();
8241 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8242 inc_unlock();
8244 if (!val) {
8245 compose_close(compose);
8246 } else if (val == -1) {
8247 alertpanel_error(_("Could not queue message."));
8248 } else if (val == -2) {
8249 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8250 } else if (val == -3) {
8251 if (privacy_peek_error())
8252 alertpanel_error(_("Could not queue message for sending:\n\n"
8253 "Signature failed: %s"), privacy_get_error());
8254 } else if (val == -4) {
8255 alertpanel_error(_("Could not queue message for sending:\n\n"
8256 "Charset conversion failed."));
8257 } else if (val == -5) {
8258 alertpanel_error(_("Could not queue message for sending:\n\n"
8259 "Couldn't get recipient encryption key."));
8260 } else if (val == -6) {
8261 /* silent error */
8263 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8266 #define DRAFTED_AT_EXIT "drafted_at_exit"
8267 static void compose_register_draft(MsgInfo *info)
8269 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8270 DRAFTED_AT_EXIT, NULL);
8271 FILE *fp = fopen(filepath, "ab");
8273 if (fp) {
8274 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8275 info->msgnum);
8276 fclose(fp);
8279 g_free(filepath);
8282 gboolean compose_draft (gpointer data, guint action)
8284 Compose *compose = (Compose *)data;
8285 FolderItem *draft;
8286 gchar *tmp;
8287 gint msgnum;
8288 MsgFlags flag = {0, 0};
8289 static gboolean lock = FALSE;
8290 MsgInfo *newmsginfo;
8291 FILE *fp;
8292 gboolean target_locked = FALSE;
8294 if (lock) return FALSE;
8296 if (compose->sending)
8297 return TRUE;
8299 draft = account_get_special_folder(compose->account, F_DRAFT);
8300 g_return_val_if_fail(draft != NULL, FALSE);
8302 if (!g_mutex_trylock(compose->mutex)) {
8303 /* we don't want to lock the mutex once it's available,
8304 * because as the only other part of compose.c locking
8305 * it is compose_close - which means once unlocked,
8306 * the compose struct will be freed */
8307 debug_print("couldn't lock mutex, probably sending\n");
8308 return FALSE;
8311 lock = TRUE;
8313 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8314 G_DIR_SEPARATOR, compose);
8315 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8316 FILE_OP_ERROR(tmp, "fopen");
8317 goto unlock;
8320 /* chmod for security */
8321 if (change_file_mode_rw(fp, tmp) < 0) {
8322 FILE_OP_ERROR(tmp, "chmod");
8323 g_warning("can't change file mode\n");
8326 /* Save draft infos */
8327 fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id);
8328 fprintf(fp, "S:%s\n", compose->account->address);
8330 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8331 gchar *savefolderid;
8333 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8334 fprintf(fp, "SCF:%s\n", savefolderid);
8335 g_free(savefolderid);
8337 if (compose->return_receipt) {
8338 fprintf(fp, "RRCPT:1\n");
8340 if (compose->privacy_system) {
8341 fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing);
8342 fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption);
8343 fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system);
8346 /* Message-ID of message replying to */
8347 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8348 gchar *folderid;
8350 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8351 fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid);
8352 g_free(folderid);
8354 /* Message-ID of message forwarding to */
8355 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8356 gchar *folderid;
8358 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8359 fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid);
8360 g_free(folderid);
8363 /* end of headers */
8364 fprintf(fp, "X-Claws-End-Special-Headers: 1\n");
8366 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8367 fclose(fp);
8368 g_unlink(tmp);
8369 g_free(tmp);
8370 goto unlock;
8372 fclose(fp);
8374 if (compose->targetinfo) {
8375 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8376 flag.perm_flags = target_locked?MSG_LOCKED:0;
8378 flag.tmp_flags = MSG_DRAFT;
8380 folder_item_scan(draft);
8381 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8382 g_unlink(tmp);
8383 g_free(tmp);
8384 if (action != COMPOSE_AUTO_SAVE) {
8385 if (action != COMPOSE_DRAFT_FOR_EXIT)
8386 alertpanel_error(_("Could not save draft."));
8387 else {
8388 AlertValue val;
8389 gtkut_window_popup(compose->window);
8390 val = alertpanel_full(_("Could not save draft"),
8391 _("Could not save draft.\n"
8392 "Do you want to cancel exit or discard this email?"),
8393 _("_Cancel exit"), _("_Discard email"), NULL,
8394 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8395 if (val == G_ALERTALTERNATE) {
8396 lock = FALSE;
8397 g_mutex_unlock(compose->mutex); /* must be done before closing */
8398 compose_close(compose);
8399 return TRUE;
8400 } else {
8401 lock = FALSE;
8402 g_mutex_unlock(compose->mutex); /* must be done before closing */
8403 return FALSE;
8407 goto unlock;
8409 g_free(tmp);
8411 if (compose->mode == COMPOSE_REEDIT) {
8412 compose_remove_reedit_target(compose, TRUE);
8415 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8416 if (newmsginfo) {
8417 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8418 if (target_locked)
8419 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8420 else
8421 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8422 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8423 procmsg_msginfo_set_flags(newmsginfo, 0,
8424 MSG_HAS_ATTACHMENT);
8426 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8427 compose_register_draft(newmsginfo);
8429 procmsg_msginfo_free(newmsginfo);
8432 folder_item_scan(draft);
8434 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8435 lock = FALSE;
8436 g_mutex_unlock(compose->mutex); /* must be done before closing */
8437 compose_close(compose);
8438 return TRUE;
8439 } else {
8440 struct stat s;
8441 gchar *path;
8443 path = folder_item_fetch_msg(draft, msgnum);
8444 if (path == NULL) {
8445 debug_print("can't fetch %s:%d\n",draft->path, msgnum);
8446 goto unlock;
8448 if (g_stat(path, &s) < 0) {
8449 FILE_OP_ERROR(path, "stat");
8450 g_free(path);
8451 goto unlock;
8453 g_free(path);
8455 procmsg_msginfo_free(compose->targetinfo);
8456 compose->targetinfo = procmsg_msginfo_new();
8457 compose->targetinfo->msgnum = msgnum;
8458 compose->targetinfo->size = s.st_size;
8459 compose->targetinfo->mtime = s.st_mtime;
8460 compose->targetinfo->folder = draft;
8461 if (target_locked)
8462 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8463 compose->mode = COMPOSE_REEDIT;
8465 if (action == COMPOSE_AUTO_SAVE) {
8466 compose->autosaved_draft = compose->targetinfo;
8468 compose->modified = FALSE;
8469 compose_set_title(compose);
8471 unlock:
8472 lock = FALSE;
8473 g_mutex_unlock(compose->mutex);
8474 return TRUE;
8477 void compose_clear_exit_drafts(void)
8479 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8480 DRAFTED_AT_EXIT, NULL);
8481 if (is_file_exist(filepath))
8482 g_unlink(filepath);
8484 g_free(filepath);
8487 void compose_reopen_exit_drafts(void)
8489 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8490 DRAFTED_AT_EXIT, NULL);
8491 FILE *fp = fopen(filepath, "rb");
8492 gchar buf[1024];
8494 if (fp) {
8495 while (fgets(buf, sizeof(buf), fp)) {
8496 gchar **parts = g_strsplit(buf, "\t", 2);
8497 const gchar *folder = parts[0];
8498 int msgnum = parts[1] ? atoi(parts[1]):-1;
8500 if (folder && *folder && msgnum > -1) {
8501 FolderItem *item = folder_find_item_from_identifier(folder);
8502 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
8503 if (info)
8504 compose_reedit(info, FALSE);
8506 g_strfreev(parts);
8508 fclose(fp);
8510 g_free(filepath);
8511 compose_clear_exit_drafts();
8514 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
8516 compose_draft(data, action);
8519 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
8521 Compose *compose = (Compose *)data;
8522 GList *file_list;
8524 if (compose->redirect_filename != NULL)
8525 return;
8527 file_list = filesel_select_multiple_files_open(_("Select file"));
8529 if (file_list) {
8530 GList *tmp;
8532 for ( tmp = file_list; tmp; tmp = tmp->next) {
8533 gchar *file = (gchar *) tmp->data;
8534 gchar *utf8_filename = conv_filename_to_utf8(file);
8535 compose_attach_append(compose, file, utf8_filename, NULL);
8536 compose_changed_cb(NULL, compose);
8537 g_free(file);
8538 g_free(utf8_filename);
8540 g_list_free(file_list);
8544 static void compose_insert_file_cb(gpointer data, guint action,
8545 GtkWidget *widget)
8547 Compose *compose = (Compose *)data;
8548 GList *file_list;
8550 file_list = filesel_select_multiple_files_open(_("Select file"));
8552 if (file_list) {
8553 GList *tmp;
8555 for ( tmp = file_list; tmp; tmp = tmp->next) {
8556 gchar *file = (gchar *) tmp->data;
8557 gchar *filedup = g_strdup(file);
8558 gchar *shortfile = g_path_get_basename(filedup);
8559 ComposeInsertResult res;
8561 res = compose_insert_file(compose, file);
8562 if (res == COMPOSE_INSERT_READ_ERROR) {
8563 alertpanel_error(_("File '%s' could not be read."), shortfile);
8564 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
8565 alertpanel_error(_("File '%s' contained invalid characters\n"
8566 "for the current encoding, insertion may be incorrect."), shortfile);
8568 g_free(shortfile);
8569 g_free(filedup);
8570 g_free(file);
8572 g_list_free(file_list);
8576 static void compose_insert_sig_cb(gpointer data, guint action,
8577 GtkWidget *widget)
8579 Compose *compose = (Compose *)data;
8581 compose_insert_sig(compose, FALSE);
8584 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
8585 gpointer data)
8587 gint x, y;
8588 Compose *compose = (Compose *)data;
8590 gtkut_widget_get_uposition(widget, &x, &y);
8591 prefs_common.compose_x = x;
8592 prefs_common.compose_y = y;
8594 if (compose->sending || compose->updating)
8595 return TRUE;
8596 compose_close_cb(compose, 0, NULL);
8597 return TRUE;
8600 void compose_close_toolbar(Compose *compose)
8602 compose_close_cb(compose, 0, NULL);
8605 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
8607 Compose *compose = (Compose *)data;
8608 AlertValue val;
8610 #ifdef G_OS_UNIX
8611 if (compose->exteditor_tag != -1) {
8612 if (!compose_ext_editor_kill(compose))
8613 return;
8615 #endif
8617 if (compose->modified) {
8618 val = alertpanel(_("Discard message"),
8619 _("This message has been modified. Discard it?"),
8620 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
8622 switch (val) {
8623 case G_ALERTDEFAULT:
8624 if (prefs_common.autosave)
8625 compose_remove_draft(compose);
8626 break;
8627 case G_ALERTALTERNATE:
8628 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
8629 return;
8630 default:
8631 return;
8635 compose_close(compose);
8638 static void compose_set_encoding_cb(gpointer data, guint action,
8639 GtkWidget *widget)
8641 Compose *compose = (Compose *)data;
8643 if (GTK_CHECK_MENU_ITEM(widget)->active)
8644 compose->out_encoding = (CharSet)action;
8647 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
8649 Compose *compose = (Compose *)data;
8651 addressbook_open(compose);
8654 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
8656 Compose *compose = (Compose *)data;
8657 Template *tmpl;
8658 gchar *msg;
8659 AlertValue val;
8661 tmpl = g_object_get_data(G_OBJECT(widget), "template");
8662 g_return_if_fail(tmpl != NULL);
8664 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
8665 tmpl->name);
8666 val = alertpanel(_("Apply template"), msg,
8667 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
8668 g_free(msg);
8670 if (val == G_ALERTDEFAULT)
8671 compose_template_apply(compose, tmpl, TRUE);
8672 else if (val == G_ALERTALTERNATE)
8673 compose_template_apply(compose, tmpl, FALSE);
8676 static void compose_ext_editor_cb(gpointer data, guint action,
8677 GtkWidget *widget)
8679 Compose *compose = (Compose *)data;
8681 compose_exec_ext_editor(compose);
8684 static void compose_undo_cb(Compose *compose)
8686 gboolean prev_autowrap = compose->autowrap;
8688 compose->autowrap = FALSE;
8689 undo_undo(compose->undostruct);
8690 compose->autowrap = prev_autowrap;
8693 static void compose_redo_cb(Compose *compose)
8695 gboolean prev_autowrap = compose->autowrap;
8697 compose->autowrap = FALSE;
8698 undo_redo(compose->undostruct);
8699 compose->autowrap = prev_autowrap;
8702 static void entry_cut_clipboard(GtkWidget *entry)
8704 if (GTK_IS_EDITABLE(entry))
8705 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
8706 else if (GTK_IS_TEXT_VIEW(entry))
8707 gtk_text_buffer_cut_clipboard(
8708 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8709 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
8710 TRUE);
8713 static void entry_copy_clipboard(GtkWidget *entry)
8715 if (GTK_IS_EDITABLE(entry))
8716 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
8717 else if (GTK_IS_TEXT_VIEW(entry))
8718 gtk_text_buffer_copy_clipboard(
8719 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
8720 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
8723 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
8724 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
8726 if (GTK_IS_TEXT_VIEW(entry)) {
8727 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8728 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
8729 GtkTextIter start_iter, end_iter;
8730 gint start, end;
8731 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
8733 if (contents == NULL)
8734 return;
8736 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
8738 /* we shouldn't delete the selection when middle-click-pasting, or we
8739 * can't mid-click-paste our own selection */
8740 if (clip != GDK_SELECTION_PRIMARY) {
8741 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
8744 if (insert_place == NULL) {
8745 /* if insert_place isn't specified, insert at the cursor.
8746 * used for Ctrl-V pasting */
8747 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8748 start = gtk_text_iter_get_offset(&start_iter);
8749 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
8750 } else {
8751 /* if insert_place is specified, paste here.
8752 * used for mid-click-pasting */
8753 start = gtk_text_iter_get_offset(insert_place);
8754 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
8757 if (!wrap) {
8758 /* paste unwrapped: mark the paste so it's not wrapped later */
8759 end = start + strlen(contents);
8760 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
8761 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
8762 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
8763 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
8764 /* rewrap paragraph now (after a mid-click-paste) */
8765 mark_start = gtk_text_buffer_get_insert(buffer);
8766 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
8767 gtk_text_iter_backward_char(&start_iter);
8768 compose_beautify_paragraph(compose, &start_iter, TRUE);
8770 } else if (GTK_IS_EDITABLE(entry))
8771 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
8775 static void entry_allsel(GtkWidget *entry)
8777 if (GTK_IS_EDITABLE(entry))
8778 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
8779 else if (GTK_IS_TEXT_VIEW(entry)) {
8780 GtkTextIter startiter, enditer;
8781 GtkTextBuffer *textbuf;
8783 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
8784 gtk_text_buffer_get_start_iter(textbuf, &startiter);
8785 gtk_text_buffer_get_end_iter(textbuf, &enditer);
8787 gtk_text_buffer_move_mark_by_name(textbuf,
8788 "selection_bound", &startiter);
8789 gtk_text_buffer_move_mark_by_name(textbuf,
8790 "insert", &enditer);
8794 static void compose_cut_cb(Compose *compose)
8796 if (compose->focused_editable
8797 #ifndef MAEMO
8798 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8799 #endif
8801 entry_cut_clipboard(compose->focused_editable);
8804 static void compose_copy_cb(Compose *compose)
8806 if (compose->focused_editable
8807 #ifndef MAEMO
8808 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8809 #endif
8811 entry_copy_clipboard(compose->focused_editable);
8814 static void compose_paste_cb(Compose *compose)
8816 gint prev_autowrap;
8817 GtkTextBuffer *buffer;
8818 BLOCK_WRAP();
8819 if (compose->focused_editable &&
8820 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
8821 entry_paste_clipboard(compose, compose->focused_editable,
8822 prefs_common.linewrap_pastes,
8823 GDK_SELECTION_CLIPBOARD, NULL);
8824 UNBLOCK_WRAP();
8827 static void compose_paste_as_quote_cb(Compose *compose)
8829 gint wrap_quote = prefs_common.linewrap_quote;
8830 if (compose->focused_editable
8831 #ifndef MAEMO
8832 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8833 #endif
8835 /* let text_insert() (called directly or at a later time
8836 * after the gtk_editable_paste_clipboard) know that
8837 * text is to be inserted as a quotation. implemented
8838 * by using a simple refcount... */
8839 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
8840 G_OBJECT(compose->focused_editable),
8841 "paste_as_quotation"));
8842 g_object_set_data(G_OBJECT(compose->focused_editable),
8843 "paste_as_quotation",
8844 GINT_TO_POINTER(paste_as_quotation + 1));
8845 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
8846 entry_paste_clipboard(compose, compose->focused_editable,
8847 prefs_common.linewrap_pastes,
8848 GDK_SELECTION_CLIPBOARD, NULL);
8849 prefs_common.linewrap_quote = wrap_quote;
8853 static void compose_paste_no_wrap_cb(Compose *compose)
8855 gint prev_autowrap;
8856 GtkTextBuffer *buffer;
8857 BLOCK_WRAP();
8858 if (compose->focused_editable
8859 #ifndef MAEMO
8860 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8861 #endif
8863 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
8864 GDK_SELECTION_CLIPBOARD, NULL);
8865 UNBLOCK_WRAP();
8868 static void compose_paste_wrap_cb(Compose *compose)
8870 gint prev_autowrap;
8871 GtkTextBuffer *buffer;
8872 BLOCK_WRAP();
8873 if (compose->focused_editable
8874 #ifndef MAEMO
8875 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8876 #endif
8878 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
8879 GDK_SELECTION_CLIPBOARD, NULL);
8880 UNBLOCK_WRAP();
8883 static void compose_allsel_cb(Compose *compose)
8885 if (compose->focused_editable
8886 #ifndef MAEMO
8887 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
8888 #endif
8890 entry_allsel(compose->focused_editable);
8893 static void textview_move_beginning_of_line (GtkTextView *text)
8895 GtkTextBuffer *buffer;
8896 GtkTextMark *mark;
8897 GtkTextIter ins;
8899 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8901 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8902 mark = gtk_text_buffer_get_insert(buffer);
8903 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8904 gtk_text_iter_set_line_offset(&ins, 0);
8905 gtk_text_buffer_place_cursor(buffer, &ins);
8908 static void textview_move_forward_character (GtkTextView *text)
8910 GtkTextBuffer *buffer;
8911 GtkTextMark *mark;
8912 GtkTextIter ins;
8914 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8916 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8917 mark = gtk_text_buffer_get_insert(buffer);
8918 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8919 if (gtk_text_iter_forward_cursor_position(&ins))
8920 gtk_text_buffer_place_cursor(buffer, &ins);
8923 static void textview_move_backward_character (GtkTextView *text)
8925 GtkTextBuffer *buffer;
8926 GtkTextMark *mark;
8927 GtkTextIter ins;
8929 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8931 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8932 mark = gtk_text_buffer_get_insert(buffer);
8933 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8934 if (gtk_text_iter_backward_cursor_position(&ins))
8935 gtk_text_buffer_place_cursor(buffer, &ins);
8938 static void textview_move_forward_word (GtkTextView *text)
8940 GtkTextBuffer *buffer;
8941 GtkTextMark *mark;
8942 GtkTextIter ins;
8943 gint count;
8945 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8947 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8948 mark = gtk_text_buffer_get_insert(buffer);
8949 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8950 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
8951 if (gtk_text_iter_forward_word_ends(&ins, count)) {
8952 gtk_text_iter_backward_word_start(&ins);
8953 gtk_text_buffer_place_cursor(buffer, &ins);
8957 static void textview_move_backward_word (GtkTextView *text)
8959 GtkTextBuffer *buffer;
8960 GtkTextMark *mark;
8961 GtkTextIter ins;
8962 gint count;
8964 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8966 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8967 mark = gtk_text_buffer_get_insert(buffer);
8968 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8969 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
8970 if (gtk_text_iter_backward_word_starts(&ins, 1))
8971 gtk_text_buffer_place_cursor(buffer, &ins);
8974 static void textview_move_end_of_line (GtkTextView *text)
8976 GtkTextBuffer *buffer;
8977 GtkTextMark *mark;
8978 GtkTextIter ins;
8980 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8982 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8983 mark = gtk_text_buffer_get_insert(buffer);
8984 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
8985 if (gtk_text_iter_forward_to_line_end(&ins))
8986 gtk_text_buffer_place_cursor(buffer, &ins);
8989 static void textview_move_next_line (GtkTextView *text)
8991 GtkTextBuffer *buffer;
8992 GtkTextMark *mark;
8993 GtkTextIter ins;
8994 gint offset;
8996 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
8998 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8999 mark = gtk_text_buffer_get_insert(buffer);
9000 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9001 offset = gtk_text_iter_get_line_offset(&ins);
9002 if (gtk_text_iter_forward_line(&ins)) {
9003 gtk_text_iter_set_line_offset(&ins, offset);
9004 gtk_text_buffer_place_cursor(buffer, &ins);
9008 static void textview_move_previous_line (GtkTextView *text)
9010 GtkTextBuffer *buffer;
9011 GtkTextMark *mark;
9012 GtkTextIter ins;
9013 gint offset;
9015 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9017 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9018 mark = gtk_text_buffer_get_insert(buffer);
9019 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9020 offset = gtk_text_iter_get_line_offset(&ins);
9021 if (gtk_text_iter_backward_line(&ins)) {
9022 gtk_text_iter_set_line_offset(&ins, offset);
9023 gtk_text_buffer_place_cursor(buffer, &ins);
9027 static void textview_delete_forward_character (GtkTextView *text)
9029 GtkTextBuffer *buffer;
9030 GtkTextMark *mark;
9031 GtkTextIter ins, end_iter;
9033 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9035 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9036 mark = gtk_text_buffer_get_insert(buffer);
9037 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9038 end_iter = ins;
9039 if (gtk_text_iter_forward_char(&end_iter)) {
9040 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9044 static void textview_delete_backward_character (GtkTextView *text)
9046 GtkTextBuffer *buffer;
9047 GtkTextMark *mark;
9048 GtkTextIter ins, end_iter;
9050 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9052 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9053 mark = gtk_text_buffer_get_insert(buffer);
9054 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9055 end_iter = ins;
9056 if (gtk_text_iter_backward_char(&end_iter)) {
9057 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9061 static void textview_delete_forward_word (GtkTextView *text)
9063 GtkTextBuffer *buffer;
9064 GtkTextMark *mark;
9065 GtkTextIter ins, end_iter;
9067 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9069 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9070 mark = gtk_text_buffer_get_insert(buffer);
9071 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9072 end_iter = ins;
9073 if (gtk_text_iter_forward_word_end(&end_iter)) {
9074 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9078 static void textview_delete_backward_word (GtkTextView *text)
9080 GtkTextBuffer *buffer;
9081 GtkTextMark *mark;
9082 GtkTextIter ins, end_iter;
9084 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9086 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9087 mark = gtk_text_buffer_get_insert(buffer);
9088 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9089 end_iter = ins;
9090 if (gtk_text_iter_backward_word_start(&end_iter)) {
9091 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9095 static void textview_delete_line (GtkTextView *text)
9097 GtkTextBuffer *buffer;
9098 GtkTextMark *mark;
9099 GtkTextIter ins, start_iter, end_iter;
9100 gboolean found;
9102 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9104 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9105 mark = gtk_text_buffer_get_insert(buffer);
9106 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9108 start_iter = ins;
9109 gtk_text_iter_set_line_offset(&start_iter, 0);
9111 end_iter = ins;
9112 if (gtk_text_iter_ends_line(&end_iter))
9113 found = gtk_text_iter_forward_char(&end_iter);
9114 else
9115 found = gtk_text_iter_forward_to_line_end(&end_iter);
9117 if (found)
9118 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9121 static void textview_delete_to_line_end (GtkTextView *text)
9123 GtkTextBuffer *buffer;
9124 GtkTextMark *mark;
9125 GtkTextIter ins, end_iter;
9126 gboolean found;
9128 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9130 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9131 mark = gtk_text_buffer_get_insert(buffer);
9132 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9133 end_iter = ins;
9134 if (gtk_text_iter_ends_line(&end_iter))
9135 found = gtk_text_iter_forward_char(&end_iter);
9136 else
9137 found = gtk_text_iter_forward_to_line_end(&end_iter);
9138 if (found)
9139 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9142 static void compose_advanced_action_cb(Compose *compose,
9143 ComposeCallAdvancedAction action)
9145 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9146 static struct {
9147 void (*do_action) (GtkTextView *text);
9148 } action_table[] = {
9149 {textview_move_beginning_of_line},
9150 {textview_move_forward_character},
9151 {textview_move_backward_character},
9152 {textview_move_forward_word},
9153 {textview_move_backward_word},
9154 {textview_move_end_of_line},
9155 {textview_move_next_line},
9156 {textview_move_previous_line},
9157 {textview_delete_forward_character},
9158 {textview_delete_backward_character},
9159 {textview_delete_forward_word},
9160 {textview_delete_backward_word},
9161 {textview_delete_line},
9162 {NULL}, /* gtk_stext_delete_line_n */
9163 {textview_delete_to_line_end}
9166 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9168 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9169 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9170 if (action_table[action].do_action)
9171 action_table[action].do_action(text);
9172 else
9173 g_warning("Not implemented yet.");
9177 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9179 gchar *str = NULL;
9181 if (GTK_IS_EDITABLE(widget)) {
9182 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9183 gtk_editable_set_position(GTK_EDITABLE(widget),
9184 strlen(str));
9185 g_free(str);
9186 if (widget->parent && widget->parent->parent
9187 && widget->parent->parent->parent) {
9188 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9189 gint y = widget->allocation.y;
9190 gint height = widget->allocation.height;
9191 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9192 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9194 if (y < (int)shown->value) {
9195 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9197 if (y + height > (int)shown->value + (int)shown->page_size) {
9198 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9199 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9200 y + height - (int)shown->page_size - 1);
9201 } else {
9202 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9203 (int)shown->upper - (int)shown->page_size - 1);
9210 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9211 compose->focused_editable = widget;
9213 #ifdef MAEMO
9214 if (GTK_IS_TEXT_VIEW(widget)
9215 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9216 gtk_widget_ref(compose->notebook);
9217 gtk_widget_ref(compose->edit_vbox);
9218 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9219 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9220 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9221 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9222 gtk_widget_unref(compose->notebook);
9223 gtk_widget_unref(compose->edit_vbox);
9224 g_signal_handlers_block_by_func(G_OBJECT(widget),
9225 G_CALLBACK(compose_grab_focus_cb),
9226 compose);
9227 gtk_widget_grab_focus(widget);
9228 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9229 G_CALLBACK(compose_grab_focus_cb),
9230 compose);
9231 } else if (!GTK_IS_TEXT_VIEW(widget)
9232 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9233 gtk_widget_ref(compose->notebook);
9234 gtk_widget_ref(compose->edit_vbox);
9235 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9236 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9237 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9238 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9239 gtk_widget_unref(compose->notebook);
9240 gtk_widget_unref(compose->edit_vbox);
9241 g_signal_handlers_block_by_func(G_OBJECT(widget),
9242 G_CALLBACK(compose_grab_focus_cb),
9243 compose);
9244 gtk_widget_grab_focus(widget);
9245 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9246 G_CALLBACK(compose_grab_focus_cb),
9247 compose);
9249 #endif
9252 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9254 compose->modified = TRUE;
9255 #ifndef MAEMO
9256 compose_set_title(compose);
9257 #endif
9260 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9262 Compose *compose = (Compose *)data;
9264 if (action == 1)
9265 compose_wrap_all_full(compose, TRUE);
9266 else
9267 compose_beautify_paragraph(compose, NULL, TRUE);
9270 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9272 Compose *compose = (Compose *)data;
9274 message_search_compose(compose);
9277 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9278 GtkWidget *widget)
9280 Compose *compose = (Compose *)data;
9281 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9282 if (compose->autowrap)
9283 compose_wrap_all_full(compose, TRUE);
9284 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9287 static void compose_toggle_sign_cb(gpointer data, guint action,
9288 GtkWidget *widget)
9290 Compose *compose = (Compose *)data;
9292 if (GTK_CHECK_MENU_ITEM(widget)->active)
9293 compose->use_signing = TRUE;
9294 else
9295 compose->use_signing = FALSE;
9298 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9299 GtkWidget *widget)
9301 Compose *compose = (Compose *)data;
9303 if (GTK_CHECK_MENU_ITEM(widget)->active)
9304 compose->use_encryption = TRUE;
9305 else
9306 compose->use_encryption = FALSE;
9309 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9311 g_free(compose->privacy_system);
9313 compose->privacy_system = g_strdup(account->default_privacy_system);
9314 compose_update_privacy_system_menu_item(compose, warn);
9317 static void compose_toggle_ruler_cb(gpointer data, guint action,
9318 GtkWidget *widget)
9320 Compose *compose = (Compose *)data;
9322 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9323 gtk_widget_show(compose->ruler_hbox);
9324 prefs_common.show_ruler = TRUE;
9325 } else {
9326 gtk_widget_hide(compose->ruler_hbox);
9327 gtk_widget_queue_resize(compose->edit_vbox);
9328 prefs_common.show_ruler = FALSE;
9332 static void compose_attach_drag_received_cb (GtkWidget *widget,
9333 GdkDragContext *context,
9334 gint x,
9335 gint y,
9336 GtkSelectionData *data,
9337 guint info,
9338 guint time,
9339 gpointer user_data)
9341 Compose *compose = (Compose *)user_data;
9342 GList *list, *tmp;
9344 if (gdk_atom_name(data->type) &&
9345 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9346 && gtk_drag_get_source_widget(context) !=
9347 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9348 list = uri_list_extract_filenames((const gchar *)data->data);
9349 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9350 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9351 compose_attach_append
9352 (compose, (const gchar *)tmp->data,
9353 utf8_filename, NULL);
9354 g_free(utf8_filename);
9356 if (list) compose_changed_cb(NULL, compose);
9357 list_free_strings(list);
9358 g_list_free(list);
9359 } else if (gtk_drag_get_source_widget(context)
9360 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9361 /* comes from our summaryview */
9362 SummaryView * summaryview = NULL;
9363 GSList * list = NULL, *cur = NULL;
9365 if (mainwindow_get_mainwindow())
9366 summaryview = mainwindow_get_mainwindow()->summaryview;
9368 if (summaryview)
9369 list = summary_get_selected_msg_list(summaryview);
9371 for (cur = list; cur; cur = cur->next) {
9372 MsgInfo *msginfo = (MsgInfo *)cur->data;
9373 gchar *file = NULL;
9374 if (msginfo)
9375 file = procmsg_get_message_file_full(msginfo,
9376 TRUE, TRUE);
9377 if (file) {
9378 compose_attach_append(compose, (const gchar *)file,
9379 (const gchar *)file, "message/rfc822");
9380 g_free(file);
9383 g_slist_free(list);
9387 static gboolean compose_drag_drop(GtkWidget *widget,
9388 GdkDragContext *drag_context,
9389 gint x, gint y,
9390 guint time, gpointer user_data)
9392 /* not handling this signal makes compose_insert_drag_received_cb
9393 * called twice */
9394 return TRUE;
9397 static void compose_insert_drag_received_cb (GtkWidget *widget,
9398 GdkDragContext *drag_context,
9399 gint x,
9400 gint y,
9401 GtkSelectionData *data,
9402 guint info,
9403 guint time,
9404 gpointer user_data)
9406 Compose *compose = (Compose *)user_data;
9407 GList *list, *tmp;
9409 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9410 * does not work */
9411 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9412 AlertValue val = G_ALERTDEFAULT;
9414 switch (prefs_common.compose_dnd_mode) {
9415 case COMPOSE_DND_ASK:
9416 val = alertpanel_full(_("Insert or attach?"),
9417 _("Do you want to insert the contents of the file(s) "
9418 "into the message body, or attach it to the email?"),
9419 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9420 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9421 break;
9422 case COMPOSE_DND_INSERT:
9423 val = G_ALERTALTERNATE;
9424 break;
9425 case COMPOSE_DND_ATTACH:
9426 val = G_ALERTOTHER;
9427 break;
9428 default:
9429 /* unexpected case */
9430 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9433 if (val & G_ALERTDISABLE) {
9434 val &= ~G_ALERTDISABLE;
9435 /* remember what action to perform by default, only if we don't click Cancel */
9436 if (val == G_ALERTALTERNATE)
9437 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9438 else if (val == G_ALERTOTHER)
9439 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9442 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9443 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9444 return;
9445 } else if (val == G_ALERTOTHER) {
9446 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9447 return;
9449 list = uri_list_extract_filenames((const gchar *)data->data);
9450 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9451 compose_insert_file(compose, (const gchar *)tmp->data);
9453 list_free_strings(list);
9454 g_list_free(list);
9455 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9456 return;
9457 } else {
9458 #if GTK_CHECK_VERSION(2, 8, 0)
9459 /* do nothing, handled by GTK */
9460 #else
9461 gchar *tmpfile = get_tmp_file();
9462 str_write_to_file((const gchar *)data->data, tmpfile);
9463 compose_insert_file(compose, tmpfile);
9464 g_unlink(tmpfile);
9465 g_free(tmpfile);
9466 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9467 #endif
9468 return;
9470 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9473 static void compose_header_drag_received_cb (GtkWidget *widget,
9474 GdkDragContext *drag_context,
9475 gint x,
9476 gint y,
9477 GtkSelectionData *data,
9478 guint info,
9479 guint time,
9480 gpointer user_data)
9482 GtkEditable *entry = (GtkEditable *)user_data;
9483 gchar *email = (gchar *)data->data;
9485 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9486 * does not work */
9488 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
9489 gchar *decoded=g_new(gchar, strlen(email));
9490 int start = 0;
9492 email += strlen("mailto:");
9493 decode_uri(decoded, email); /* will fit */
9494 gtk_editable_delete_text(entry, 0, -1);
9495 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
9496 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9497 g_free(decoded);
9498 return;
9500 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9503 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
9504 GtkWidget *widget)
9506 Compose *compose = (Compose *)data;
9508 if (GTK_CHECK_MENU_ITEM(widget)->active)
9509 compose->return_receipt = TRUE;
9510 else
9511 compose->return_receipt = FALSE;
9514 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
9515 GtkWidget *widget)
9517 Compose *compose = (Compose *)data;
9519 if (GTK_CHECK_MENU_ITEM(widget)->active)
9520 compose->remove_references = TRUE;
9521 else
9522 compose->remove_references = FALSE;
9525 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
9526 GdkEventKey *event,
9527 ComposeHeaderEntry *headerentry)
9529 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
9530 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
9531 !(event->state & GDK_MODIFIER_MASK) &&
9532 (event->keyval == GDK_BackSpace) &&
9533 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
9534 gtk_container_remove
9535 (GTK_CONTAINER(headerentry->compose->header_table),
9536 headerentry->combo);
9537 gtk_container_remove
9538 (GTK_CONTAINER(headerentry->compose->header_table),
9539 headerentry->entry);
9540 headerentry->compose->header_list =
9541 g_slist_remove(headerentry->compose->header_list,
9542 headerentry);
9543 g_free(headerentry);
9544 } else if (event->keyval == GDK_Tab) {
9545 if (headerentry->compose->header_last == headerentry) {
9546 /* Override default next focus, and give it to subject_entry
9547 * instead of notebook tabs
9549 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
9550 gtk_widget_grab_focus(headerentry->compose->subject_entry);
9551 return TRUE;
9554 return FALSE;
9557 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
9558 ComposeHeaderEntry *headerentry)
9560 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
9561 compose_create_header_entry(headerentry->compose);
9562 g_signal_handlers_disconnect_matched
9563 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
9564 0, 0, NULL, NULL, headerentry);
9566 /* Automatically scroll down */
9567 compose_show_first_last_header(headerentry->compose, FALSE);
9570 return FALSE;
9573 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
9575 GtkAdjustment *vadj;
9577 g_return_if_fail(compose);
9578 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
9579 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
9581 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
9582 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
9585 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
9586 const gchar *text, gint len, Compose *compose)
9588 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
9589 (G_OBJECT(compose->text), "paste_as_quotation"));
9590 GtkTextMark *mark;
9592 g_return_if_fail(text != NULL);
9594 g_signal_handlers_block_by_func(G_OBJECT(buffer),
9595 G_CALLBACK(text_inserted),
9596 compose);
9597 if (paste_as_quotation) {
9598 gchar *new_text;
9599 const gchar *qmark;
9601 if (len < 0)
9602 len = strlen(text);
9604 new_text = g_strndup(text, len);
9606 qmark = compose_quote_char_from_context(compose);
9608 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9609 gtk_text_buffer_place_cursor(buffer, iter);
9611 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
9612 _("Quote format error at line %d."));
9613 quote_fmt_reset_vartable();
9614 g_free(new_text);
9615 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
9616 GINT_TO_POINTER(paste_as_quotation - 1));
9618 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9619 gtk_text_buffer_place_cursor(buffer, iter);
9620 } else {
9621 if (strcmp(text, "\n") || automatic_break
9622 || gtk_text_iter_starts_line(iter))
9623 gtk_text_buffer_insert(buffer, iter, text, len);
9624 else {
9625 debug_print("insert nowrap \\n\n");
9626 gtk_text_buffer_insert_with_tags_by_name(buffer,
9627 iter, text, len, "no_join", NULL);
9631 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
9633 compose_beautify_paragraph(compose, iter, FALSE);
9635 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
9636 gtk_text_buffer_delete_mark(buffer, mark);
9638 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
9639 G_CALLBACK(text_inserted),
9640 compose);
9641 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
9643 if (prefs_common.autosave &&
9644 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
9645 compose->draft_timeout_tag != -2 /* disabled while loading */)
9646 compose->draft_timeout_tag = g_timeout_add
9647 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
9649 static gint compose_defer_auto_save_draft(Compose *compose)
9651 compose->draft_timeout_tag = -1;
9652 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
9653 return FALSE;
9656 #if USE_ASPELL
9657 static void compose_check_all(Compose *compose)
9659 if (compose->gtkaspell)
9660 gtkaspell_check_all(compose->gtkaspell);
9663 static void compose_highlight_all(Compose *compose)
9665 if (compose->gtkaspell)
9666 gtkaspell_highlight_all(compose->gtkaspell);
9669 static void compose_check_backwards(Compose *compose)
9671 if (compose->gtkaspell)
9672 gtkaspell_check_backwards(compose->gtkaspell);
9673 else {
9674 GtkItemFactory *ifactory;
9675 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9676 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9677 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9681 static void compose_check_forwards_go(Compose *compose)
9683 if (compose->gtkaspell)
9684 gtkaspell_check_forwards_go(compose->gtkaspell);
9685 else {
9686 GtkItemFactory *ifactory;
9687 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
9688 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
9689 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
9692 #endif
9695 *\brief Guess originating forward account from MsgInfo and several
9696 * "common preference" settings. Return NULL if no guess.
9698 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
9700 PrefsAccount *account = NULL;
9702 g_return_val_if_fail(msginfo, NULL);
9703 g_return_val_if_fail(msginfo->folder, NULL);
9704 g_return_val_if_fail(msginfo->folder->prefs, NULL);
9706 if (msginfo->folder->prefs->enable_default_account)
9707 account = account_find_from_id(msginfo->folder->prefs->default_account);
9709 if (!account)
9710 account = msginfo->folder->folder->account;
9712 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
9713 gchar *to;
9714 Xstrdup_a(to, msginfo->to, return NULL);
9715 extract_address(to);
9716 account = account_find_from_address(to);
9719 if (!account && prefs_common.forward_account_autosel) {
9720 gchar cc[BUFFSIZE];
9721 if (!procheader_get_header_from_msginfo
9722 (msginfo, cc,sizeof cc , "Cc:")) {
9723 gchar *buf = cc + strlen("Cc:");
9724 extract_address(buf);
9725 account = account_find_from_address(buf);
9729 if (!account && prefs_common.forward_account_autosel) {
9730 gchar deliveredto[BUFFSIZE];
9731 if (!procheader_get_header_from_msginfo
9732 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
9733 gchar *buf = deliveredto + strlen("Delivered-To:");
9734 extract_address(buf);
9735 account = account_find_from_address(buf);
9739 return account;
9742 gboolean compose_close(Compose *compose)
9744 gint x, y;
9746 if (!g_mutex_trylock(compose->mutex)) {
9747 /* we have to wait for the (possibly deferred by auto-save)
9748 * drafting to be done, before destroying the compose under
9749 * it. */
9750 debug_print("waiting for drafting to finish...\n");
9751 compose_allow_user_actions(compose, FALSE);
9752 g_timeout_add (500, (GSourceFunc) compose_close, compose);
9753 return FALSE;
9755 g_return_val_if_fail(compose, FALSE);
9756 gtkut_widget_get_uposition(compose->window, &x, &y);
9757 prefs_common.compose_x = x;
9758 prefs_common.compose_y = y;
9759 g_mutex_unlock(compose->mutex);
9760 compose_destroy(compose);
9761 return FALSE;
9765 * Add entry field for each address in list.
9766 * \param compose E-Mail composition object.
9767 * \param listAddress List of (formatted) E-Mail addresses.
9769 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
9770 GList *node;
9771 gchar *addr;
9772 node = listAddress;
9773 while( node ) {
9774 addr = ( gchar * ) node->data;
9775 compose_entry_append( compose, addr, COMPOSE_TO );
9776 node = g_list_next( node );
9780 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
9781 guint action, gboolean opening_multiple)
9783 gchar *body = NULL;
9784 GSList *new_msglist = NULL;
9785 MsgInfo *tmp_msginfo = NULL;
9786 gboolean originally_enc = FALSE;
9787 Compose *compose = NULL;
9789 g_return_if_fail(msgview != NULL);
9791 g_return_if_fail(msginfo_list != NULL);
9793 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
9794 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
9795 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
9797 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
9798 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
9799 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
9800 orig_msginfo, mimeinfo);
9801 if (tmp_msginfo != NULL) {
9802 new_msglist = g_slist_append(NULL, tmp_msginfo);
9803 if (procmime_msginfo_is_encrypted(orig_msginfo)) {
9804 originally_enc = TRUE;
9806 tmp_msginfo->folder = orig_msginfo->folder;
9807 tmp_msginfo->msgnum = orig_msginfo->msgnum;
9812 if (!opening_multiple)
9813 body = messageview_get_selection(msgview);
9815 if (new_msglist) {
9816 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
9817 procmsg_msginfo_free(tmp_msginfo);
9818 g_slist_free(new_msglist);
9819 } else
9820 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
9822 if (originally_enc) {
9823 compose_force_encryption(compose, compose->account, FALSE);
9826 g_free(body);
9829 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
9830 guint action)
9832 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
9833 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
9834 GSList *cur = msginfo_list;
9835 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
9836 "messages. Opening the windows "
9837 "could take some time. Do you "
9838 "want to continue?"),
9839 g_slist_length(msginfo_list));
9840 if (g_slist_length(msginfo_list) > 9
9841 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
9842 != G_ALERTALTERNATE) {
9843 g_free(msg);
9844 return;
9846 g_free(msg);
9847 /* We'll open multiple compose windows */
9848 /* let the WM place the next windows */
9849 compose_force_window_origin = FALSE;
9850 for (; cur; cur = cur->next) {
9851 GSList tmplist;
9852 tmplist.data = cur->data;
9853 tmplist.next = NULL;
9854 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
9856 compose_force_window_origin = TRUE;
9857 } else {
9858 /* forwarding multiple mails as attachments is done via a
9859 * single compose window */
9860 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
9864 void compose_set_position(Compose *compose, gint pos)
9866 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9868 gtkut_text_view_set_position(text, pos);
9871 gboolean compose_search_string(Compose *compose,
9872 const gchar *str, gboolean case_sens)
9874 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9876 return gtkut_text_view_search_string(text, str, case_sens);
9879 gboolean compose_search_string_backward(Compose *compose,
9880 const gchar *str, gboolean case_sens)
9882 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9884 return gtkut_text_view_search_string_backward(text, str, case_sens);
9887 /* allocate a msginfo structure and populate its data from a compose data structure */
9888 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
9890 MsgInfo *newmsginfo;
9891 GSList *list;
9892 gchar buf[BUFFSIZE];
9894 g_return_val_if_fail( compose != NULL, NULL );
9896 newmsginfo = procmsg_msginfo_new();
9898 /* date is now */
9899 get_rfc822_date(buf, sizeof(buf));
9900 newmsginfo->date = g_strdup(buf);
9902 /* from */
9903 if (compose->from_name) {
9904 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
9905 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
9908 /* subject */
9909 if (compose->subject_entry)
9910 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
9912 /* to, cc, reply-to, newsgroups */
9913 for (list = compose->header_list; list; list = list->next) {
9914 gchar *header = gtk_editable_get_chars(
9915 GTK_EDITABLE(
9916 GTK_COMBO(((ComposeHeaderEntry *)list->data)->combo)->entry), 0, -1);
9917 gchar *entry = gtk_editable_get_chars(
9918 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
9920 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
9921 if ( newmsginfo->to == NULL ) {
9922 newmsginfo->to = g_strdup(entry);
9923 } else if (entry && *entry) {
9924 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
9925 g_free(newmsginfo->to);
9926 newmsginfo->to = tmp;
9928 } else
9929 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
9930 if ( newmsginfo->cc == NULL ) {
9931 newmsginfo->cc = g_strdup(entry);
9932 } else if (entry && *entry) {
9933 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
9934 g_free(newmsginfo->cc);
9935 newmsginfo->cc = tmp;
9937 } else
9938 if ( strcasecmp(header,
9939 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
9940 if ( newmsginfo->newsgroups == NULL ) {
9941 newmsginfo->newsgroups = g_strdup(entry);
9942 } else if (entry && *entry) {
9943 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
9944 g_free(newmsginfo->newsgroups);
9945 newmsginfo->newsgroups = tmp;
9949 g_free(header);
9950 g_free(entry);
9953 /* other data is unset */
9955 return newmsginfo;
9958 #ifdef USE_ASPELL
9959 /* update compose's dictionaries from folder dict settings */
9960 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
9961 FolderItem *folder_item)
9963 g_return_if_fail(compose != NULL);
9965 if (compose->gtkaspell && folder_item && folder_item->prefs) {
9966 FolderItemPrefs *prefs = folder_item->prefs;
9968 if (prefs->enable_default_dictionary)
9969 gtkaspell_change_dict(compose->gtkaspell,
9970 prefs->default_dictionary, FALSE);
9971 if (folder_item->prefs->enable_default_alt_dictionary)
9972 gtkaspell_change_alt_dict(compose->gtkaspell,
9973 prefs->default_alt_dictionary);
9974 if (prefs->enable_default_dictionary
9975 || prefs->enable_default_alt_dictionary)
9976 compose_spell_menu_changed(compose);
9979 #endif
9982 * End of Source.