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.
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
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>
65 #include <sys/types.h>
71 # include <sys/wait.h>
75 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
79 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
86 #include "mainwindow.h"
88 #include "addressbook.h"
89 #include "folderview.h"
92 #include "stock_pixmap.h"
93 #include "send_message.h"
96 #include "customheader.h"
97 #include "prefs_common.h"
98 #include "prefs_account.h"
102 #include "procheader.h"
103 #include "procmime.h"
104 #include "statusbar.h"
107 #include "quoted-printable.h"
108 #include "codeconv.h"
110 #include "gtkutils.h"
112 #include "alertpanel.h"
113 #include "manage_window.h"
114 #include "gtkshruler.h"
116 #include "addr_compl.h"
117 #include "quote_fmt.h"
119 #include "foldersel.h"
122 #include "message_search.h"
123 #include "combobox.h"
137 #define N_ATTACH_COLS (N_COL_COLUMNS)
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
;
160 PRIORITY_HIGHEST
= 1,
169 COMPOSE_INSERT_SUCCESS
,
170 COMPOSE_INSERT_READ_ERROR
,
171 COMPOSE_INSERT_INVALID_CHARACTER
,
172 COMPOSE_INSERT_NO_FILE
173 } ComposeInsertResult
;
177 COMPOSE_WRITE_FOR_SEND
,
178 COMPOSE_WRITE_FOR_STORE
183 COMPOSE_QUOTE_FORCED
,
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
,
198 GPtrArray
*attach_files
,
199 GList
*listAddress
);
201 static Compose
*compose_create (PrefsAccount
*account
,
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
,
213 static Compose
*compose_forward_multiple (PrefsAccount
*account
,
214 GSList
*msginfo_list
);
215 static Compose
*compose_reply (MsgInfo
*msginfo
,
216 ComposeQuoteMode quote_mode
,
221 static Compose
*compose_reply_mode (ComposeMode mode
,
222 GSList
*msginfo_list
,
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
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
,
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
,
240 static gchar
*compose_parse_references (const gchar
*ref
,
243 static gchar
*compose_quote_fmt (Compose
*compose
,
249 gboolean need_unescape
,
250 const gchar
*err_msg
);
252 static void compose_reply_set_entry (Compose
*compose
,
258 followup_and_reply_to
);
259 static void compose_reedit_set_entry (Compose
*compose
,
262 static void compose_insert_sig (Compose
*compose
,
264 static gchar
*compose_get_signature_str (Compose
*compose
);
265 static ComposeInsertResult
compose_insert_file (Compose
*compose
,
268 static gboolean
compose_attach_append (Compose
*compose
,
271 const gchar
*content_type
);
272 static void compose_attach_parts (Compose
*compose
,
275 static void compose_beautify_paragraph (Compose
*compose
,
276 GtkTextIter
*par_iter
,
278 static void compose_wrap_all (Compose
*compose
);
279 static void compose_wrap_all_full (Compose
*compose
,
282 static void compose_set_title (Compose
*compose
);
283 static void compose_select_account (Compose
*compose
,
284 PrefsAccount
*account
,
287 static PrefsAccount
*compose_current_mail_account(void);
288 /* static gint compose_send (Compose *compose); */
289 static gboolean compose_check_for_valid_recipient
291 static gboolean
compose_check_entries (Compose
*compose
,
292 gboolean check_everything
);
293 static gint
compose_write_to_file (Compose
*compose
,
296 gboolean attach_parts
);
297 static gint
compose_write_body_to_file (Compose
*compose
,
299 static gint
compose_remove_reedit_target (Compose
*compose
,
301 static void compose_remove_draft (Compose
*compose
);
302 static gint
compose_queue_sub (Compose
*compose
,
306 gboolean check_subject
,
307 gboolean remove_reedit_target
);
308 static void compose_add_attachments (Compose
*compose
,
310 static gchar
*compose_get_header (Compose
*compose
);
312 static void compose_convert_header (Compose
*compose
,
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
,
330 gboolean
*cancelled
);
331 static gboolean
attach_property_key_pressed (GtkWidget
*widget
,
333 gboolean
*cancelled
);
335 static void compose_exec_ext_editor (Compose
*compose
);
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
,
342 static void compose_set_ext_editor_sensitive (Compose
*compose
,
344 #endif /* G_OS_UNIX */
346 static void compose_undo_state_changed (UndoMain
*undostruct
,
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
);
357 static void compose_spell_menu_changed (void *data
);
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
,
369 static void attach_selected (GtkTreeView
*tree_view
,
370 GtkTreePath
*tree_path
,
371 GtkTreeViewColumn
*column
,
373 static gboolean
attach_button_pressed (GtkWidget
*widget
,
374 GdkEventButton
*event
,
376 static gboolean
attach_key_pressed (GtkWidget
*widget
,
379 static void compose_send_cb (gpointer data
,
382 static void compose_send_later_cb (gpointer data
,
386 static void compose_draft_cb (gpointer data
,
390 static void compose_attach_cb (gpointer data
,
393 static void compose_insert_file_cb (gpointer data
,
396 static void compose_insert_sig_cb (gpointer data
,
400 static void compose_close_cb (gpointer data
,
404 static void compose_set_encoding_cb (gpointer data
,
408 static void compose_address_cb (gpointer data
,
411 static void compose_template_activate_cb(GtkWidget
*widget
,
414 static void compose_ext_editor_cb (gpointer data
,
418 static gint
compose_delete_cb (GtkWidget
*widget
,
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
,
438 static void compose_changed_cb (GtkTextBuffer
*textbuf
,
441 static void compose_wrap_cb (gpointer data
,
444 static void compose_find_cb (gpointer data
,
447 static void compose_toggle_autowrap_cb (gpointer data
,
451 static void compose_toggle_ruler_cb (gpointer data
,
454 static void compose_toggle_sign_cb (gpointer data
,
457 static void compose_toggle_encrypt_cb (gpointer data
,
460 static void compose_set_privacy_system_cb(GtkWidget
*widget
,
462 static void compose_update_privacy_system_menu_item(Compose
* compose
, gboolean warn
);
463 static void activate_privacy_system (Compose
*compose
,
464 PrefsAccount
*account
,
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
,
470 static void compose_toggle_remove_refs_cb(gpointer data
, guint action
,
472 static void compose_set_priority_cb (gpointer data
,
475 static void compose_reply_change_mode (gpointer data
,
479 static void compose_attach_drag_received_cb (GtkWidget
*widget
,
480 GdkDragContext
*drag_context
,
483 GtkSelectionData
*data
,
487 static void compose_insert_drag_received_cb (GtkWidget
*widget
,
488 GdkDragContext
*drag_context
,
491 GtkSelectionData
*data
,
495 static void compose_header_drag_received_cb (GtkWidget
*widget
,
496 GdkDragContext
*drag_context
,
499 GtkSelectionData
*data
,
504 static gboolean
compose_drag_drop (GtkWidget
*widget
,
505 GdkDragContext
*drag_context
,
507 guint time
, gpointer user_data
);
509 static void text_inserted (GtkTextBuffer
*buffer
,
514 static Compose
*compose_generic_reply(MsgInfo
*msginfo
,
515 ComposeQuoteMode quote_mode
,
519 gboolean followup_and_reply_to
,
522 static gboolean
compose_headerentry_changed_cb (GtkWidget
*entry
,
523 ComposeHeaderEntry
*headerentry
);
524 static gboolean
compose_headerentry_key_press_event_cb(GtkWidget
*entry
,
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
);
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
);
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
);
545 static void compose_set_dictionaries_from_folder_prefs(Compose
*compose
,
546 FolderItem
*folder_item
);
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"),
592 compose_advanced_action_cb
,
593 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER
,
595 {N_("/_Edit/A_dvanced/Move a character forward"),
597 compose_advanced_action_cb
,
598 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER
,
600 {N_("/_Edit/A_dvanced/Move a word backward"),
602 compose_advanced_action_cb
,
603 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD
,
605 {N_("/_Edit/A_dvanced/Move a word forward"),
607 compose_advanced_action_cb
,
608 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD
,
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
,
615 {N_("/_Edit/A_dvanced/Move to end of line"),
617 compose_advanced_action_cb
,
618 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE
,
620 {N_("/_Edit/A_dvanced/Move to previous line"),
622 compose_advanced_action_cb
,
623 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE
,
625 {N_("/_Edit/A_dvanced/Move to next line"),
627 compose_advanced_action_cb
,
628 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE
,
630 {N_("/_Edit/A_dvanced/Delete a character backward"),
632 compose_advanced_action_cb
,
633 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER
,
635 {N_("/_Edit/A_dvanced/Delete a character forward"),
637 compose_advanced_action_cb
,
638 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER
,
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
,
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
,
650 {N_("/_Edit/A_dvanced/Delete line"),
652 compose_advanced_action_cb
,
653 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE
,
655 {N_("/_Edit/A_dvanced/Delete entire line"),
657 compose_advanced_action_cb
,
658 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE_N
,
660 {N_("/_Edit/A_dvanced/Delete to end of line"),
662 compose_advanced_action_cb
,
663 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
,
665 {N_("/_Edit/---"), NULL
, NULL
, 0, "<Separator>"},
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
},
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>"},
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)"),
786 {N_("/_Options/Character _encoding/Traditional Chinese (_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},
817 static gboolean
compose_put_existing_to_front(MsgInfo
*info
)
819 GList
*compose_list
= compose_get_compose_list();
823 for (elem
= compose_list
; elem
!= NULL
&& elem
->data
!= NULL
;
825 Compose
*c
= (Compose
*)elem
->data
;
827 if (!c
->targetinfo
|| !c
->targetinfo
->msgid
||
831 if (!strcmp(c
->targetinfo
->msgid
, info
->msgid
)) {
832 gtkut_window_popup(c
->window
);
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
= {
861 static GdkColor uri_color
= {
868 static void compose_create_tags(GtkTextView
*text
, Compose
*compose
)
870 GtkTextBuffer
*buffer
;
871 GdkColor black
= {(gulong
)0, (gushort
)0, (gushort
)0, (gushort
)0};
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
,
883 gtkut_convert_int_to_gdk_color(prefs_common
.quote_level2_col
,
885 gtkut_convert_int_to_gdk_color(prefs_common
.quote_level3_col
,
887 gtkut_convert_int_to_gdk_color(prefs_common
.quote_level1_bgcol
,
889 gtkut_convert_int_to_gdk_color(prefs_common
.quote_level2_bgcol
,
891 gtkut_convert_int_to_gdk_color(prefs_common
.quote_level3_bgcol
,
893 gtkut_convert_int_to_gdk_color(prefs_common
.signature_col
,
895 gtkut_convert_int_to_gdk_color(prefs_common
.uri_col
,
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", "e_color1
,
905 "paragraph-background-gdk", "e_bgcolor1
,
907 gtk_text_buffer_create_tag(buffer
, "quote1",
908 "foreground-gdk", "e_color2
,
909 "paragraph-background-gdk", "e_bgcolor2
,
911 gtk_text_buffer_create_tag(buffer
, "quote2",
912 "foreground-gdk", "e_color3
,
913 "paragraph-background-gdk", "e_bgcolor3
,
916 gtk_text_buffer_create_tag(buffer
, "quote0",
917 "foreground-gdk", "e_color1
,
919 gtk_text_buffer_create_tag(buffer
, "quote1",
920 "foreground-gdk", "e_color2
,
922 gtk_text_buffer_create_tag(buffer
, "quote2",
923 "foreground-gdk", "e_color3
,
927 gtk_text_buffer_create_tag(buffer
, "signature",
928 "foreground-gdk", &signature_color
,
930 gtk_text_buffer_create_tag(buffer
, "link",
931 "foreground-gdk", &uri_color
,
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
) {
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
)
980 GtkTextView
*textview
;
981 GtkTextBuffer
*textbuf
;
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
);
1006 compose_set_dictionaries_from_folder_prefs(compose
, item
);
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
);
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
;
1054 && *subject_format
!= '\0' )
1056 gchar
*subject
= 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);
1068 quote_fmt_init(dummyinfo
, NULL
, subject
, FALSE
, compose
->account
,
1069 compose
->gtkaspell
);
1071 quote_fmt_init(dummyinfo
, NULL
, subject
, FALSE
, compose
->account
);
1073 quote_fmt_scan_string(tmp
);
1076 buf
= quote_fmt_get_buffer();
1078 alertpanel_error(_("New message subject format error."));
1080 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), buf
);
1081 quote_fmt_reset_vartable();
1088 && *body_format
!= '\0' )
1091 GtkTextBuffer
*buffer
;
1092 GtkTextIter start
, end
;
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
,
1106 NULL
, tmp
, FALSE
, TRUE
,
1107 _("New message body format error at line %d."));
1108 quote_fmt_reset_vartable();
1113 procmsg_msginfo_free( dummyinfo
);
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
);
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
)
1161 if (account
->default_privacy_system
1162 && strlen(account
->default_privacy_system
)) {
1163 privacy
= account
->default_privacy_system
;
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
;
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
)
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
);
1215 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_CHECK
,
1216 FALSE
, prefs_common
.default_reply_list
, FALSE
, body
);
1218 case COMPOSE_REPLY_WITH_QUOTE
:
1219 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_FORCED
,
1220 FALSE
, prefs_common
.default_reply_list
, FALSE
, body
);
1222 case COMPOSE_REPLY_WITHOUT_QUOTE
:
1223 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_SKIP
,
1224 FALSE
, prefs_common
.default_reply_list
, FALSE
, NULL
);
1226 case COMPOSE_REPLY_TO_SENDER
:
1227 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_CHECK
,
1228 FALSE
, FALSE
, TRUE
, body
);
1230 case COMPOSE_FOLLOWUP_AND_REPLY_TO
:
1231 compose
= compose_followup_and_reply_to(msginfo
,
1232 COMPOSE_QUOTE_CHECK
,
1233 FALSE
, FALSE
, body
);
1235 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE
:
1236 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_FORCED
,
1237 FALSE
, FALSE
, TRUE
, body
);
1239 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE
:
1240 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_SKIP
,
1241 FALSE
, FALSE
, TRUE
, NULL
);
1243 case COMPOSE_REPLY_TO_ALL
:
1244 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_CHECK
,
1245 TRUE
, FALSE
, FALSE
, body
);
1247 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE
:
1248 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_FORCED
,
1249 TRUE
, FALSE
, FALSE
, body
);
1251 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE
:
1252 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_SKIP
,
1253 TRUE
, FALSE
, FALSE
, NULL
);
1255 case COMPOSE_REPLY_TO_LIST
:
1256 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_CHECK
,
1257 FALSE
, TRUE
, FALSE
, body
);
1259 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE
:
1260 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_FORCED
,
1261 FALSE
, TRUE
, FALSE
, body
);
1263 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE
:
1264 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_SKIP
,
1265 FALSE
, TRUE
, FALSE
, NULL
);
1267 case COMPOSE_FORWARD
:
1268 if (prefs_common
.forward_as_attachment
) {
1269 compose
= compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH
, msginfo_list
, body
);
1272 compose
= compose_reply_mode(COMPOSE_FORWARD_INLINE
, msginfo_list
, body
);
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
);
1282 /* more messages FALL THROUGH */
1283 case COMPOSE_FORWARD_AS_ATTACH
:
1284 compose
= compose_forward_multiple(NULL
, msginfo_list
);
1286 case COMPOSE_REDIRECT
:
1287 compose
= compose_redirect(NULL
, msginfo
, FALSE
);
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
) {
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 */
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
);
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
);
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
);
1329 static Compose
*compose_reply(MsgInfo
*msginfo
,
1330 ComposeQuoteMode quote_mode
,
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
,
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
;
1361 MimeInfo
*mimeinfo
= procmime_scan_message(info
);
1362 MimeInfo
*partinfo
= mimeinfo
;
1363 while (partinfo
&& partinfo
->type
!= MIMETYPE_TEXT
)
1364 partinfo
= procmime_mimeinfo_next(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), \
1378 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1379 G_CALLBACK(text_inserted), \
1383 #define SIGNAL_UNBLOCK(buffer) { \
1384 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1385 G_CALLBACK(compose_changed_cb), \
1387 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1388 G_CALLBACK(text_inserted), \
1392 static Compose
*compose_generic_reply(MsgInfo
*msginfo
,
1393 ComposeQuoteMode quote_mode
,
1394 gboolean to_all
, gboolean to_ml
,
1396 gboolean followup_and_reply_to
,
1399 GtkItemFactory
*ifactory
;
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
);
1451 compose_set_dictionaries_from_folder_prefs(compose
, msginfo
->folder
);
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 */
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
;
1470 qmark
= prefs_common
.quotemark
;
1471 body_fmt
= prefs_common
.quotefmt
;
1476 /* empty quotemark is not allowed */
1477 if (qmark
== NULL
|| *qmark
== '\0')
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
);
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
,
1531 GtkTextView
*textview
;
1532 GtkTextBuffer
*textbuf
;
1535 g_return_val_if_fail(msginfo
!= NULL
, NULL
);
1536 g_return_val_if_fail(msginfo
->folder
!= NULL
, NULL
);
1539 !(account
= compose_guess_forward_account_from_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
);
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
);
1574 msgfile
= procmsg_get_message_file(msginfo
);
1575 if (!is_file_exist(msgfile
))
1576 g_warning("%s: file not exist\n", msgfile
);
1578 compose_attach_append(compose
, msgfile
, msgfile
,
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
);
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
;
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')
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
);
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
);
1664 #undef INSERT_FW_HEADER
1666 static Compose
*compose_forward_multiple(PrefsAccount
*account
, GSList
*msginfo_list
)
1669 GtkTextView
*textview
;
1670 GtkTextBuffer
*textbuf
;
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
)
1685 /* guess account from first selected message */
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
);
1713 compose_attach_append(compose
, msgfile
, msgfile
,
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
);
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
);
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
);
1772 if (!compose
->account
->sig_sep
)
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
)) {
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")) {
1800 static void compose_colorize_signature(Compose
*compose
)
1802 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose
->text
));
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
;
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
)) {
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
];
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 */
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]);
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]);
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
);
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
);
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:")) {
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:")]);
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
);
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
),
2000 if (procmime_msginfo_is_encrypted(msginfo
)) {
2001 fp
= procmime_get_first_encrypted_text_content(msginfo
);
2003 compose_force_encryption(compose
, account
, TRUE
);
2006 fp
= procmime_get_first_text_content(msginfo
);
2009 g_warning("Can't get text part\n");
2013 gboolean prev_autowrap
= compose
->autowrap
;
2015 compose
->autowrap
= FALSE
;
2016 while (fgets(buf
, sizeof(buf
), fp
) != NULL
) {
2018 gtk_text_buffer_insert(textbuf
, &iter
, buf
, -1);
2020 compose_wrap_all_full(compose
, FALSE
);
2021 compose
->autowrap
= prev_autowrap
;
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
),
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
);
2049 compose
->sig_str
= compose_get_signature_str(compose
);
2054 Compose
*compose_redirect(PrefsAccount
*account
, MsgInfo
*msginfo
,
2059 GtkItemFactory
*ifactory
;
2062 g_return_val_if_fail(msginfo
!= NULL
, NULL
);
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
);
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
),
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
);
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
;
2171 gboolean in_quote
= FALSE
;
2172 if (!address
|| *address
== '\0') return;
2179 header
= N_("Bcc:");
2181 case COMPOSE_REPLYTO
:
2182 header
= N_("Reply-To:");
2184 case COMPOSE_NEWSGROUPS
:
2185 header
= N_("Newsgroups:");
2187 case COMPOSE_FOLLOWUPTO
:
2188 header
= N_( "Followup-To:");
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
2201 while (*cur
!= '\0') {
2203 in_quote
= !in_quote
;
2204 if (*cur
== ',' && !in_quote
) {
2205 gchar
*tmp
= g_strdup(begin
);
2207 tmp
[cur
-begin
]='\0';
2210 while (*tmp
== ' ' || *tmp
== '\t')
2212 compose_add_header_entry(compose
, header
, tmp
);
2219 gchar
*tmp
= g_strdup(begin
);
2221 tmp
[cur
-begin
]='\0';
2224 while (*tmp
== ' ' || *tmp
== '\t')
2226 compose_add_header_entry(compose
, header
, 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
;
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
);
2273 compose_send_cb(compose
, 0, NULL
);
2276 compose_send_later_cb(compose
, 0, NULL
);
2279 compose_draft_cb(compose
, COMPOSE_QUIT_EDITING
, NULL
);
2282 compose_insert_file_cb(compose
, 0, NULL
);
2285 compose_attach_cb(compose
, 0, NULL
);
2288 compose_insert_sig(compose
, FALSE
);
2291 compose_ext_editor_cb(compose
, 0, NULL
);
2293 case A_LINEWRAP_CURRENT
:
2294 compose_beautify_paragraph(compose
, NULL
, TRUE
);
2296 case A_LINEWRAP_ALL
:
2297 compose_wrap_all_full(compose
, TRUE
);
2300 compose_address_cb(compose
, 0, NULL
);
2303 case A_CHECK_SPELLING
:
2304 compose_check_all(compose
);
2312 static void compose_entries_set(Compose
*compose
, const gchar
*mailto
)
2316 gchar
*subject
= NULL
;
2320 gchar
*attach
= NULL
;
2322 scan_mailto_url(mailto
, &to
, &cc
, NULL
, &subject
, &body
, &attach
);
2325 compose_entry_append(compose
, to
, COMPOSE_TO
);
2327 compose_entry_append(compose
, cc
, COMPOSE_CC
);
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
);
2334 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), subject
);
2338 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
2339 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(text
);
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);
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
);
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
);
2371 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
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
}};
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
);
2413 if (hentry
[H_REPLY_TO
].body
!= NULL
) {
2414 if (hentry
[H_REPLY_TO
].body
[0] != '\0') {
2416 conv_unmime_header(hentry
[H_REPLY_TO
].body
,
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
;
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
)
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
,
2454 g_free(hentry
[H_FOLLOWUP_TO
].body
);
2455 hentry
[H_FOLLOWUP_TO
].body
= NULL
;
2457 if (hentry
[H_LIST_POST
].body
!= 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
);
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
) {
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
);
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>",
2507 compose
->references
=
2508 g_strconcat("<", msginfo
->msgid
, ">",
2510 } else if (msginfo
->inreplyto
&& *msginfo
->inreplyto
) {
2511 compose
->references
=
2512 g_strconcat("<", msginfo
->inreplyto
, ">",
2520 static gchar
*compose_parse_references(const gchar
*ref
, const gchar
*msgid
)
2522 GSList
*ref_id_list
, *cur
;
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
));
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
);
2546 slist_free_strings(ref_id_list
);
2547 g_slist_free(ref_id_list
);
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
);
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
;
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
);
2588 SIGNAL_BLOCK(buffer
);
2591 dummyinfo
= compose_msginfo_new_from_compose(compose
);
2592 msginfo
= dummyinfo
;
2595 if (qmark
!= NULL
) {
2597 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
,
2598 compose
->gtkaspell
);
2600 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
);
2602 quote_fmt_scan_string(qmark
);
2605 buf
= quote_fmt_get_buffer();
2607 alertpanel_error(_("Quote mark format error."));
2609 Xstrdup_a(quote_str
, buf
, goto error
)
2612 if (fmt
&& *fmt
!= '\0') {
2615 while (*trimmed_body
== '\n')
2619 quote_fmt_init(msginfo
, quote_str
, trimmed_body
, FALSE
, compose
->account
,
2620 compose
->gtkaspell
);
2622 quote_fmt_init(msginfo
, quote_str
, trimmed_body
, FALSE
, compose
->account
);
2624 if (need_unescape
) {
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
);
2634 quote_fmt_scan_string(fmt
);
2638 buf
= quote_fmt_get_buffer();
2640 gint line
= quote_fmt_get_line();
2641 gchar
*msg
= g_strdup_printf(err_msg
, line
);
2642 alertpanel_error(msg
);
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);
2657 gchar
*tmpout
= NULL
;
2658 tmpout
= conv_codeset_strdup
2659 (buf
, conv_get_locale_charset_str_no_utf8(),
2661 if (!tmpout
|| !g_utf8_validate(tmpout
, -1, NULL
)) {
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);
2670 cursor_pos
= quote_fmt_get_cursor_pos();
2671 compose
->set_cursor_pos
= cursor_pos
;
2672 if (cursor_pos
== -1) {
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
);
2688 SIGNAL_UNBLOCK(buffer
);
2690 procmsg_msginfo_free( dummyinfo
);
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
)
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
)) {
2732 static gboolean
same_address(const gchar
*addr1
, const gchar
*addr2
)
2734 gchar
*my_addr1
, *my_addr2
;
2736 if (!addr1
|| !addr2
)
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
,
2751 gboolean followup_and_reply_to
)
2753 GSList
*cc_list
= 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
,
2776 /* normal answer to ml post with a reply-to */
2777 compose_entry_append(compose
,
2780 if (compose
->replyto
2781 && !same_address(compose
->ml_post
, compose
->replyto
))
2782 compose_entry_append(compose
,
2786 /* answer to subscription confirmation */
2787 if (compose
->replyto
)
2788 compose_entry_append(compose
,
2791 else if (msginfo
->from
)
2792 compose_entry_append(compose
,
2797 else if (!(to_all
|| to_sender
) && default_reply_to
) {
2798 compose_entry_append(compose
,
2799 msginfo
->folder
->prefs
->default_reply_to
,
2801 compose_entry_mark_default_to(compose
,
2802 msginfo
->folder
->prefs
->default_reply_to
);
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
: "",
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
: "",
2824 /* replying to own mail, use original recp */
2825 compose_entry_append(compose
,
2826 msginfo
->to
? msginfo
->to
: "",
2828 compose_entry_append(compose
,
2829 msginfo
->cc
? msginfo
->cc
: "",
2835 if (to_sender
|| (compose
->followup_to
&&
2836 !strncmp(compose
->followup_to
, "poster", 6)))
2837 compose_entry_append
2839 (compose
->replyto
? compose
->replyto
:
2840 msginfo
->from
? msginfo
->from
: ""),
2843 else if (followup_and_reply_to
|| to_all
) {
2844 compose_entry_append
2846 (compose
->replyto
? compose
->replyto
:
2847 msginfo
->from
? msginfo
->from
: ""),
2850 compose_entry_append
2852 compose
->followup_to
? compose
->followup_to
:
2853 compose
->newsgroups
? compose
->newsgroups
: "",
2854 COMPOSE_NEWSGROUPS
);
2857 compose_entry_append
2859 compose
->followup_to
? compose
->followup_to
:
2860 compose
->newsgroups
? compose
->newsgroups
: "",
2861 COMPOSE_NEWSGROUPS
);
2864 if (msginfo
->subject
&& *msginfo
->subject
) {
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
);
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
);
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
;
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
);
2919 g_hash_table_insert(to_table
, addr
, GINT_TO_POINTER(1));
2923 hash_free_strings(to_table
);
2924 g_hash_table_destroy(to_table
);
2927 for (cur
= cc_list
; cur
!= NULL
; cur
= cur
->next
)
2928 compose_entry_append(compose
, (gchar
*)cur
->data
,
2930 slist_free_strings(cc_list
);
2931 g_slist_free(cc_list
);
2936 #define SET_ENTRY(entry, str) \
2939 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
2942 #define SET_ADDRESS(type, 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
);
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
);
2974 GtkTextIter iter
, iter_end
;
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
),
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
;
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')
3006 found
= gtk_text_iter_forward_search(&first_iter
,
3008 GTK_TEXT_SEARCH_TEXT_ONLY
,
3009 &start_iter
, &end_iter
,
3013 gtk_text_buffer_delete(buffer
, &start_iter
, &end_iter
);
3017 if (replace
&& !found
&& search
&& strlen(search
) > 2
3018 && search
[0] == '\n' && search
[1] == '\n') {
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
);
3031 gtk_text_buffer_insert(buffer
, &iter
, compose
->sig_str
+ 1, -1);
3033 gtk_text_buffer_insert(buffer
, &iter
, compose
->sig_str
, -1);
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
);
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
),
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
)
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
);
3081 if (compose
->account
->sig_type
== SIG_COMMAND
)
3082 sig_body
= get_command_output(compose
->account
->sig_path
);
3086 tmp
= file_read_to_str(compose
->account
->sig_path
);
3089 sig_body
= normalize_newlines(tmp
);
3093 if (compose
->account
->sig_sep
) {
3094 sig_str
= g_strconcat("\n\n", compose
->account
->sig_sep
, "\n", sig_body
,
3098 sig_str
= g_strconcat("\n\n", sig_body
, NULL
);
3101 if (g_utf8_validate(sig_str
, -1, NULL
) == TRUE
)
3102 utf8_sig_str
= sig_str
;
3104 utf8_sig_str
= conv_codeset_strdup
3105 (sig_str
, conv_get_locale_charset_str_no_utf8(),
3111 return utf8_sig_str
;
3114 static ComposeInsertResult
compose_insert_file(Compose
*compose
, const gchar
*file
)
3117 GtkTextBuffer
*buffer
;
3120 const gchar
*cur_encoding
;
3121 gchar buf
[BUFFSIZE
];
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
),
3146 cur_encoding
= conv_get_locale_charset_str_no_utf8();
3148 while (fgets(buf
, sizeof(buf
), fp
) != NULL
) {
3151 if (g_utf8_validate(buf
, -1, NULL
) == TRUE
)
3152 str
= g_strdup(buf
);
3154 str
= conv_codeset_strdup
3155 (buf
, cur_encoding
, CS_INTERNAL
);
3158 /* strip <CR> if DOS/Windows file,
3159 replace <CR> with <LF> if Macintosh file. */
3162 if (len
> 0 && str
[len
- 1] != '\n') {
3164 if (str
[len
] == '\r') str
[len
] = '\n';
3167 gtk_text_buffer_insert(buffer
, &iter
, str
, -1);
3171 g_signal_handlers_unblock_by_func(G_OBJECT(buffer
),
3172 G_CALLBACK(text_inserted
),
3174 compose
->autowrap
= prev_autowrap
;
3175 if (compose
->autowrap
)
3176 compose_wrap_all(compose
);
3181 return COMPOSE_INSERT_INVALID_CHARACTER
;
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
)
3196 GtkListStore
*store
;
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
,
3209 g_free(file_from_uri
);
3212 alertpanel_error("File %s doesn't exist\n", filename
);
3215 if ((size
= get_file_size(file
)) < 0) {
3216 alertpanel_error("Can't get file size of %s\n", filename
);
3220 alertpanel_error(_("File %s is empty."), filename
);
3223 if ((fp
= g_fopen(file
, "rb")) == NULL
) {
3224 alertpanel_error(_("Can't read %s."), filename
);
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
);
3235 ainfo
->content_type
= g_strdup(content_type
);
3236 if (!g_ascii_strcasecmp(content_type
, "message/rfc822")) {
3238 MsgFlags flags
= {0, 0};
3240 if (procmime_get_encoding_for_text_file(file
, &has_binary
) == ENC_7BIT
)
3241 ainfo
->encoding
= ENC_7BIT
;
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
);
3249 name
= g_path_get_basename(filename
? filename
: file
);
3251 ainfo
->name
= g_strdup_printf(_("Message: %s"), name
);
3253 procmsg_msginfo_free(msginfo
);
3255 if (!g_ascii_strncasecmp(content_type
, "text", 4))
3256 ainfo
->encoding
= procmime_get_encoding_for_text_file(file
, &has_binary
);
3258 ainfo
->encoding
= ENC_BASE64
;
3259 name
= g_path_get_basename(filename
? filename
: file
);
3260 ainfo
->name
= g_strdup(name
);
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))
3271 procmime_get_encoding_for_text_file(file
, &has_binary
);
3273 ainfo
->encoding
= ENC_BASE64
;
3274 name
= g_path_get_basename(filename
? filename
: file
);
3275 ainfo
->name
= g_strdup(name
);
3279 if (ainfo
->name
!= NULL
3280 && !strcmp(ainfo
->name
, ".")) {
3281 g_free(ainfo
->name
);
3285 if (!strcmp(ainfo
->content_type
, "unknown") || has_binary
) {
3286 g_free(ainfo
->content_type
);
3287 ainfo
->content_type
= g_strdup("application/octet-stream");
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
,
3302 COL_AUTODATA
, auto_ainfo
,
3305 g_auto_pointer_free(auto_ainfo
);
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
),
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
),
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
)
3348 MimeInfo
*firsttext
= NULL
;
3349 MimeInfo
*encrypted
= NULL
;
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
);
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
) {
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
) {
3380 if (child
== encrypted
) {
3381 /* skip this part of tree */
3382 NEXT_PART_NOT_CHILD(child
);
3386 if (child
->type
== MIMETYPE_MULTIPART
) {
3387 /* get the actual content */
3388 child
= procmime_mimeinfo_next(child
);
3392 if (child
== firsttext
) {
3393 child
= procmime_mimeinfo_next(child
);
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
));
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
)
3416 compose_attach_append(compose
, outfile
,
3417 partname
, content_type
);
3419 compose_force_signing(compose
, compose
->account
);
3421 g_free(content_type
);
3424 NEXT_PART_NOT_CHILD(child
);
3426 procmime_mimeinfo_free_all(mimeinfo
);
3429 #undef NEXT_PART_NOT_CHILD
3434 WAIT_FOR_INDENT_CHAR
,
3435 WAIT_FOR_INDENT_CHAR_OR_SPACE
,
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
;
3450 IndentState state
= WAIT_FOR_INDENT_CHAR
;
3453 gint alnum_count
= 0;
3454 gint space_count
= 0;
3457 if (prefs_common
.quote_chars
== NULL
) {
3461 while (!gtk_text_iter_ends_line(&iter
)) {
3462 wc
= gtk_text_iter_get_char(&iter
);
3463 if (g_unichar_iswide(wc
))
3465 clen
= g_unichar_to_utf8(wc
, ch
);
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
))
3476 quote_len
+= alnum_count
+ space_count
+ 1;
3477 alnum_count
= space_count
= 0;
3478 state
= WAIT_FOR_INDENT_CHAR_OR_SPACE
;
3481 } else if (state
== WAIT_FOR_INDENT_CHAR_OR_SPACE
) {
3482 if (!is_indent
&& !is_space
&& !g_unichar_isalnum(wc
))
3486 else if (is_indent
) {
3487 quote_len
+= alnum_count
+ space_count
+ 1;
3488 alnum_count
= space_count
= 0;
3491 state
= WAIT_FOR_INDENT_CHAR
;
3495 gtk_text_iter_forward_char(&iter
);
3498 if (quote_len
> 0 && space_count
> 0)
3504 if (quote_len
> 0) {
3506 gtk_text_iter_forward_chars(&iter
, quote_len
);
3507 return gtk_text_buffer_get_text(buffer
, start
, &iter
, FALSE
);
3513 /* return TRUE if the line is itemized */
3514 static gboolean
compose_is_itemized(GtkTextBuffer
*buffer
,
3515 const GtkTextIter
*start
)
3517 GtkTextIter iter
= *start
;
3522 if (gtk_text_iter_ends_line(&iter
))
3526 wc
= gtk_text_iter_get_char(&iter
);
3527 if (!g_unichar_isspace(wc
))
3529 gtk_text_iter_forward_char(&iter
);
3530 if (gtk_text_iter_ends_line(&iter
))
3534 clen
= g_unichar_to_utf8(wc
, ch
);
3538 if (!strchr("*-+", ch
[0]))
3541 gtk_text_iter_forward_char(&iter
);
3542 if (gtk_text_iter_ends_line(&iter
))
3544 wc
= gtk_text_iter_get_char(&iter
);
3545 if (g_unichar_isspace(wc
))
3551 static gboolean
compose_get_line_break_pos(GtkTextBuffer
*buffer
,
3552 const GtkTextIter
*start
,
3553 GtkTextIter
*break_pos
,
3557 GtkTextIter iter
= *start
, line_end
= *start
;
3558 PangoLogAttr
*attrs
;
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);
3581 /* skip quote and leading spaces */
3582 for (i
= 0; *p
!= '\0' && i
< len
; i
++) {
3585 wc
= g_utf8_get_char(p
);
3586 if (i
>= quote_len
&& !g_unichar_isspace(wc
))
3588 if (g_unichar_iswide(wc
))
3590 else if (*p
== '\t')
3594 p
= g_utf8_next_char(p
);
3597 for (; *p
!= '\0' && i
< len
; i
++) {
3598 PangoLogAttr
*attr
= attrs
+ i
;
3602 if (attr
->is_line_break
&& can_break
&& was_white
&& !prev_dont_break
)
3605 was_white
= attr
->is_white
;
3607 /* don't wrap URI */
3608 if ((uri_len
= get_uri_len(p
)) > 0) {
3610 if (pos
> 0 && col
> max_col
) {
3620 wc
= g_utf8_get_char(p
);
3621 if (g_unichar_iswide(wc
)) {
3623 if (prev_dont_break
&& can_break
&& attr
->is_line_break
)
3625 } else if (*p
== '\t')
3629 if (pos
> 0 && col
> max_col
) {
3634 if (*p
== '-' || *p
== '/')
3635 prev_dont_break
= TRUE
;
3637 prev_dont_break
= FALSE
;
3639 p
= g_utf8_next_char(p
);
3643 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break
, pos
, col
);
3648 *break_pos
= *start
;
3649 gtk_text_iter_set_line_offset(break_pos
, pos
);
3654 static gboolean
compose_join_next_line(Compose
*compose
,
3655 GtkTextBuffer
*buffer
,
3657 const gchar
*quote_str
)
3659 GtkTextIter iter_
= *iter
, cur
, prev
, next
, end
;
3660 PangoLogAttr attrs
[3];
3662 gchar
*next_quote_str
;
3665 gboolean keep_cursor
= FALSE
;
3667 if (!gtk_text_iter_forward_line(&iter_
) ||
3668 gtk_text_iter_ends_line(&iter_
))
3671 next_quote_str
= compose_get_quote_str(buffer
, &iter_
, "e_len
);
3673 if ((quote_str
|| next_quote_str
) &&
3674 strcmp2(quote_str
, next_quote_str
) != 0) {
3675 g_free(next_quote_str
);
3678 g_free(next_quote_str
);
3681 if (quote_len
> 0) {
3682 gtk_text_iter_forward_chars(&end
, quote_len
);
3683 if (gtk_text_iter_ends_line(&end
))
3687 /* don't join itemized lines */
3688 if (compose_is_itemized(buffer
, &end
))
3691 /* don't join signature separator */
3692 if (compose_is_sig_separator(compose
, buffer
, &iter_
))
3695 /* delete quote str */
3697 gtk_text_buffer_delete(buffer
, &iter_
, &end
);
3699 /* don't join line breaks put by the user */
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
);
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
))
3716 while (!gtk_text_iter_ends_line(&cur
)) {
3717 wc1
= gtk_text_iter_get_char(&cur
);
3718 if (!g_unichar_isspace(wc1
))
3720 gtk_text_iter_forward_char(&cur
);
3723 if (!gtk_text_iter_equal(&prev
, &next
)) {
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
))
3730 gtk_text_buffer_delete(buffer
, &prev
, &next
);
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);
3745 gtk_text_iter_backward_char(&iter_
);
3746 gtk_text_buffer_place_cursor(buffer
, &iter_
);
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; \
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
;
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
);
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
))
3799 while (gtk_text_iter_backward_line(&iter
)) {
3800 if (gtk_text_iter_ends_line(&iter
)) {
3801 gtk_text_iter_forward_line(&iter
);
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 */
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
,
3824 /* part to URI function */
3825 gchar
*(*build_uri
) (const gchar
*bp
,
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
;
3841 gchar
*o_walk
= NULL
, *walk
= NULL
, *bp
= NULL
, *ep
= NULL
;
3844 if (!prev_autowrap
&& num_blocks
== 0) {
3846 g_signal_handlers_block_by_func(G_OBJECT(buffer
),
3847 G_CALLBACK(text_inserted
),
3850 if (gtk_text_iter_has_tag(&iter
, compose
->no_wrap_tag
) && !force
)
3853 uri_start
= uri_stop
= -1;
3855 quote_str
= compose_get_quote_str(buffer
, &iter
, "e_len
);
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
)
3873 if (startq_offset
== -1)
3874 noq_offset
= gtk_text_iter_get_offset(&iter
);
3878 if (prev_autowrap
== FALSE
&& !force
&& !wrap_quote
) {
3881 if (compose_get_line_break_pos(buffer
, &iter
, &break_pos
,
3882 prefs_common
.linewrap_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
;
3896 /* remove trailing spaces */
3898 gtk_text_iter_backward_char(&cur
);
3900 while (!gtk_text_iter_starts_line(&cur
)) {
3903 gtk_text_iter_backward_char(&cur
);
3904 wc
= gtk_text_iter_get_char(&cur
);
3905 if (!g_unichar_isspace(wc
))
3909 if (!gtk_text_iter_equal(&prev
, &next
)) {
3910 gtk_text_buffer_delete(buffer
, &prev
, &next
);
3912 gtk_text_iter_forward_char(&break_pos
);
3916 gtk_text_buffer_insert(buffer
, &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);
3930 /* move iter to next line start */
3935 if (!prev_autowrap
&& num_blocks
> 0) {
3937 g_signal_handlers_unblock_by_func(G_OBJECT(buffer
),
3938 G_CALLBACK(text_inserted
),
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
++) {
3955 tmp
= parser
[n
].search(walk
, parser
[n
].needle
);
3957 if (scanpos
== NULL
|| tmp
< 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
)) {
3973 strlen(parser
[last_index
].needle
);
3976 uri_start
= walk_pos
+ (bp
- o_walk
);
3977 uri_stop
= walk_pos
+ (ep
- o_walk
);
3981 gtk_text_iter_forward_line(&iter
);
3984 if (startq_offset
!= -1) {
3985 GtkTextIter startquote
, endquote
;
3986 gtk_text_buffer_get_iter_at_offset(
3987 buffer
, &startquote
, startq_offset
);
3990 switch (quotelevel
) {
3991 case 0: gtk_text_buffer_apply_tag_by_name(
3992 buffer
, "quote0", &startquote
, &endquote
);
3994 case 1: gtk_text_buffer_apply_tag_by_name(
3995 buffer
, "quote1", &startquote
, &endquote
);
3997 case 2: gtk_text_buffer_apply_tag_by_name(
3998 buffer
, "quote2", &startquote
, &endquote
);
4002 } else if (noq_offset
!= -1) {
4003 GtkTextIter startnoquote
, endnoquote
;
4004 gtk_text_buffer_get_iter_at_offset(
4005 buffer
, &startnoquote
, noq_offset
);
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
);
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
);
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
;
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
)
4073 edited
= compose
->modified
? _(" [Edited]") : "";
4075 subject
= gtk_editable_get_chars(
4076 GTK_EDITABLE(compose
->subject_entry
), 0, -1);
4079 if (subject
&& strlen(subject
))
4080 str
= g_strdup_printf(_("%s - Compose message%s"),
4083 str
= g_strdup_printf(_("[no subject] - Compose message%s"), edited
);
4085 str
= g_strdup(_("Compose message"));
4088 gtk_window_set_title(GTK_WINDOW(compose
->window
), str
);
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)
4107 if (cur_account
&& cur_account
->protocol
!= A_NNTP
)
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."));
4120 #define QUOTE_IF_REQUIRED(out, str) \
4122 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
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); \
4132 g_snprintf(__tmp, len, "\"%s\"", str); \
4137 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4138 g_warning("can't allocate memory\n"); \
4139 g_string_free(header, TRUE); \
4142 strcpy(__tmp, str); \
4148 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4150 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4154 len = strlen(str) + 3; \
4155 if ((__tmp = alloca(len)) == NULL) { \
4156 g_warning("can't allocate memory\n"); \
4159 g_snprintf(__tmp, len, "\"%s\"", str); \
4164 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4165 g_warning("can't allocate memory\n"); \
4168 strcpy(__tmp, str); \
4174 static void compose_select_account(Compose
*compose
, PrefsAccount
*account
,
4177 GtkItemFactory
*ifactory
;
4180 g_return_if_fail(account
!= NULL
);
4182 compose
->account
= account
;
4184 if (account
->name
&& *account
->name
) {
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
);
4191 from
= g_strdup_printf("<%s>",
4193 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), 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
);
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
);
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
);
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
);
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
;
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
) {
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);
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
;
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
;
4284 /* search header entries for to and newsgroup entries */
4285 for (list
= compose
->header_list
; list
; list
= list
->next
) {
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);
4291 if (strcmp(entry
, compose
->account
->auto_cc
)
4292 || strcmp(header
, prefs_common_translated_header_name("Cc:"))) {
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
)
4312 if (compose
->account
->set_autobcc
&& compose
->account
->auto_bcc
) {
4313 gboolean found_other
= FALSE
;
4315 /* search header entries for to and newsgroup entries */
4316 for (list
= compose
->header_list
; list
; list
= list
->next
) {
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);
4322 if (strcmp(entry
, compose
->account
->auto_bcc
)
4323 || strcmp(header
, prefs_common_translated_header_name("Bcc:"))) {
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
)
4346 static gboolean
compose_check_entries(Compose
*compose
, gboolean check_everything
)
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."));
4358 if (compose_check_for_set_recipients(compose
) == 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
) {
4368 aval
= alertpanel(_("Send"),
4369 _("Subject is empty. Send it anyway?"),
4370 GTK_STOCK_CANCEL
, _("+_Send"), NULL
);
4371 if (aval
!= G_ALERTALTERNATE
)
4376 if (check_everything
&& hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST
, compose
))
4382 gint
compose_send(Compose
*compose
)
4385 FolderItem
*folder
= NULL
;
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
);
4409 val
= compose_queue(compose
, &msgnum
, &folder
, &msgpath
, TRUE
);
4412 if (compose
->batch
) {
4413 gtk_widget_show_all(compose
->window
);
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) {
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
));
4430 alertpanel_error(_("Could not queue message for sending."));
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! */
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
) {
4455 if (msgpath
== NULL
) {
4456 msgpath
= folder_item_fetch_msg(folder
, msgnum
);
4457 val
= procmsg_send_message_queue(msgpath
, &errstr
, folder
, msgnum
, &queued_removed
);
4460 val
= procmsg_send_message_queue(msgpath
, &errstr
, folder
, msgnum
, &queued_removed
);
4464 if (!discard_window
) {
4466 if (!queued_removed
)
4467 folder_item_remove_msg(folder
, msgnum
);
4468 folder_item_scan(folder
);
4470 /* make sure we delete that */
4471 MsgInfo
*tmp
= folder_item_get_msginfo_by_msgid(folder
, tmsgid
);
4473 debug_print("removing %d via %s\n", tmp
->msgnum
, tmsgid
);
4474 folder_item_remove_msg(folder
, tmp
->msgnum
);
4475 procmsg_msginfo_free(tmp
);
4482 if (!queued_removed
)
4483 folder_item_remove_msg(folder
, msgnum
);
4484 folder_item_scan(folder
);
4486 /* make sure we delete that */
4487 MsgInfo
*tmp
= folder_item_get_msginfo_by_msgid(folder
, tmsgid
);
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
);
4501 gchar
*tmp
= g_strdup_printf(_("%s\nUse \"Send queued messages\" from "
4502 "the main window to retry."), errstr
);
4504 alertpanel_error_log(tmp
);
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
) {
4520 toolbar_main_set_sensitive(mainwin
);
4521 main_window_set_menu_sensitive(mainwin
);
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
);
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
,
4546 gchar buf
[BUFFSIZE
];
4548 gboolean first_to_address
;
4549 gboolean first_cc_address
;
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);
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
;
4581 fprintf(fp
, "%s", buf
);
4585 if (!first_to_address
) {
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);
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
;
4609 fprintf(fp
, "%s", buf
);
4613 if (!first_cc_address
) {
4620 static gint
compose_redirect_write_headers(Compose
*compose
, FILE *fp
)
4622 gchar buf
[BUFFSIZE
];
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);
4632 get_rfc822_date(buf
, sizeof(buf
));
4633 fprintf(fp
, "Resent-Date: %s\n", buf
);
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
);
4643 fprintf(fp
, "Resent-From: %s\n", compose
->account
->address
);
4646 entstr
= gtk_entry_get_text(GTK_ENTRY(compose
->subject_entry
));
4647 if (*entstr
!= '\0') {
4648 Xstrdup_a(str
, entstr
, return -1);
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 */
4672 static gint
compose_redirect_write_to_file(Compose
*compose
, FILE *fdest
)
4676 gchar buf
[BUFFSIZE
];
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:",
4695 if ((fp
= g_fopen(compose
->redirect_filename
, "rb")) == NULL
) {
4696 FILE_OP_ERROR(compose
->redirect_filename
, "fopen");
4700 while (procheader_get_one_field_asis(buf
, sizeof(buf
), fp
) != -1) {
4702 for (i
= 0; not_included
[i
] != NULL
; i
++) {
4703 if (g_ascii_strncasecmp(buf
, not_included
[i
],
4704 strlen(not_included
[i
])) == 0) {
4711 if (fputs(buf
, fdest
) == -1)
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
,
4725 fprintf(fdest
, "%s <%s>",
4727 compose
->account
->address
);
4729 fprintf(fdest
, "%s",
4730 compose
->account
->address
);
4735 if (fputs("\n", fdest
) == -1)
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
)
4755 static gint
compose_write_to_file(Compose
*compose
, FILE *fp
, gint action
, gboolean attach_parts
)
4757 GtkTextBuffer
*buffer
;
4758 GtkTextIter start
, end
;
4761 const gchar
*out_codeset
;
4762 EncodingType encoding
;
4763 MimeInfo
*mimemsg
, *mimetext
;
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
)) {
4786 out_codeset
= CS_US_ASCII
;
4787 encoding
= ENC_7BIT
;
4789 const gchar
*src_codeset
= CS_INTERNAL
;
4791 out_codeset
= conv_get_charset_str(compose
->out_encoding
);
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();
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
) {
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
;
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
);
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
);
4854 if (aval
!= G_ALERTALTERNATE
) {
4859 out_codeset
= src_codeset
;
4865 out_codeset
= src_codeset
;
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) {
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"
4909 "Send it anyway?"), line
+ 1);
4910 aval
= alertpanel(_("Warning"), msg
, GTK_STOCK_CANCEL
, GTK_STOCK_OK
, NULL
);
4912 if (aval
!= G_ALERTALTERNATE
) {
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");
4932 boundary
= generate_mime_boundary(NULL
);
4933 } while (strstr(buf
, boundary
) != NULL
);
4935 g_hash_table_insert(mimempart
->typeparameters
, g_strdup("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
);
4945 g_node_append(mimemsg
->node
, mimetext
->node
);
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
))
4955 procmime_write_mimeinfo(mimemsg
, fp
);
4957 procmime_mimeinfo_free_all(mimemsg
);
4962 static gint
compose_write_body_to_file(Compose
*compose
, const gchar
*file
)
4964 GtkTextBuffer
*buffer
;
4965 GtkTextIter start
, end
;
4970 if ((fp
= g_fopen(file
, "wb")) == NULL
) {
4971 FILE_OP_ERROR(file
, "fopen");
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());
4990 if (!chars
) return -1;
4993 len
= strlen(chars
);
4994 if (fwrite(chars
, sizeof(gchar
), len
, fp
) != len
) {
4995 FILE_OP_ERROR(file
, "fwrite");
5004 if (fclose(fp
) == EOF
) {
5005 FILE_OP_ERROR(file
, "fclose");
5012 static gint
compose_remove_reedit_target(Compose
*compose
, gboolean force
)
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
))
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");
5039 static void compose_remove_draft(Compose
*compose
)
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
)
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
,
5075 if (val
== G_ALERTALTERNATE
) {
5082 static gint
compose_queue_sub(Compose
*compose
, gint
*msgnum
, FolderItem
**item
,
5083 gchar
**msgpath
, gboolean check_subject
,
5084 gboolean remove_reedit_target
)
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);
5099 if (compose_check_entries(compose
, check_subject
) == FALSE
) {
5101 if (compose
->batch
) {
5102 gtk_widget_show_all(compose
->window
);
5107 if (!compose
->to_list
&& !compose
->newsgroup_list
) {
5108 g_warning("can't get recipient list.");
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())) {
5120 alertpanel_error(_("No account for sending mails available!"));
5125 if (compose
->newsgroup_list
) {
5126 if (compose
->account
->protocol
== A_NNTP
)
5127 newsac
= compose
->account
;
5128 else if (!newsac
->protocol
!= A_NNTP
) {
5130 alertpanel_error(_("No account for posting news available!"));
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");
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");
5158 fprintf(fp
, "MID:<%s>\n", compose
->msgid
);
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");
5166 fprintf(fp
, "SSV:%s\n", mailac
->smtp_server
);
5168 fprintf(fp
, "SSV:\n");
5170 fprintf(fp
, "NSV:%s\n", newsac
->nntp_server
);
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
;
5179 fprintf(fp
, ",<%s>", (gchar
*)cur
->data
);
5182 /* write newsgroup list */
5183 if (compose
->newsgroup_list
) {
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
);
5190 /* Sylpheed account IDs */
5192 fprintf(fp
, "MAID:%d\n", mailac
->account_id
);
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
) {
5202 if (!compose_warn_encryption(compose
)) {
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
);
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",
5222 } /* else we finally dont want to encrypt */
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
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
)) {
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
);
5257 /* Message-ID of message forwarding to */
5258 if ((compose
->fwdinfo
!= NULL
) && (compose
->fwdinfo
->msgid
!= NULL
)) {
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
);
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) {
5279 if ((result
= compose_write_to_file(compose
, fp
, COMPOSE_WRITE_FOR_SEND
, TRUE
)) < 0) {
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");
5296 if (item
&& *item
) {
5299 queue
= account_get_special_folder(compose
->account
, F_QUEUE
);
5302 g_warning("can't find queue folder\n");
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");
5317 if (msgpath
== NULL
) {
5323 if (compose
->mode
== COMPOSE_REEDIT
&& remove_reedit_target
) {
5324 compose_remove_reedit_target(compose
, FALSE
);
5327 if ((msgnum
!= NULL
) && (item
!= NULL
)) {
5335 static void compose_add_attachments(Compose
*compose
, MimeInfo
*parent
)
5338 GtkTreeView
*tree_view
= GTK_TREE_VIEW(compose
->attach_clist
);
5340 struct stat statbuf
;
5341 gchar
*type
, *subtype
;
5342 GtkTreeModel
*model
;
5345 model
= gtk_tree_view_get_model(tree_view
);
5347 if (!gtk_tree_model_get_iter_first(model
, &iter
))
5350 gtk_tree_model_get(model
, &iter
,
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
, '/')) {
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
);
5376 if (mimepart
->type
== MIMETYPE_MESSAGE
&&
5377 !g_ascii_strcasecmp(mimepart
->subtype
, "rfc822")) {
5378 mimepart
->disposition
= DISPOSITIONTYPE_INLINE
;
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
,
5408 const gchar
*fieldname
,
5409 const gchar
*seperator
)
5411 gchar
*str
, *fieldname_w_colon
;
5412 gboolean add_field
= FALSE
;
5414 ComposeHeaderEntry
*headerentry
;
5415 const gchar
*headerentryname
;
5416 const gchar
*trans_fieldname
;
5419 if (IS_IN_CUSTOM_HEADER(fieldname
))
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);
5436 if (str
[0] != '\0') {
5438 g_string_append(fieldstr
, seperator
);
5439 g_string_append(fieldstr
, str
);
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
);
5456 g_free(fieldname_w_colon
);
5457 g_string_free(fieldstr
, TRUE
);
5462 static gchar
*compose_get_header(Compose
*compose
)
5464 gchar buf
[BUFFSIZE
];
5465 const gchar
*entry_str
;
5469 gchar
*std_headers
[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL
};
5471 gchar
*from_name
= NULL
, *from_address
= NULL
;
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);
5480 get_rfc822_date(buf
, sizeof(buf
));
5481 g_string_append_printf(header
, "Date: %s\n", buf
);
5485 if (compose
->account
->name
&& *compose
->account
->name
) {
5487 QUOTE_IF_REQUIRED(buf
, compose
->account
->name
);
5488 tmp
= g_strdup_printf("%s <%s>",
5489 buf
, compose
->account
->address
);
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) {
5497 from_name
= compose
->account
->name
? g_strdup(compose
->account
->name
):NULL
;
5498 from_address
= g_strdup(compose
->account
->address
);
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';
5509 from_address
= g_strdup(spec
);
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
);
5525 g_string_append_printf(header
, "From: %s\n", from_address
);
5528 g_free(from_address
);
5531 compose_add_headerfield_from_headerlist(compose
, header
, "To", ", ");
5534 compose_add_headerfield_from_headerlist(compose
, header
, "Newsgroups", ",");
5537 compose_add_headerfield_from_headerlist(compose
, header
, "Cc", ", ");
5540 compose_add_headerfield_from_headerlist(compose
, header
, "Bcc", ", ");
5543 str
= gtk_editable_get_chars(GTK_EDITABLE(compose
->subject_entry
), 0, -1);
5545 if (*str
!= '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5548 compose_convert_header(compose
, buf
, sizeof(buf
), str
,
5549 strlen("Subject: "), FALSE
);
5550 g_string_append_printf(header
, "Subject: %s\n", buf
);
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
) {
5564 if (compose
->inreplyto
&& compose
->to_list
)
5565 g_string_append_printf(header
, "In-Reply-To: <%s>\n", compose
->inreplyto
);
5568 if (compose
->references
)
5569 g_string_append_printf(header
, "References: %s\n", compose
->references
);
5573 compose_add_headerfield_from_headerlist(compose
, header
, "Followup-To", ",");
5576 compose_add_headerfield_from_headerlist(compose
, header
, "Reply-To", ", ");
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",
5593 gtk_major_version
, gtk_minor_version
, gtk_micro_version
,
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",
5599 gtk_major_version
, gtk_minor_version
, gtk_micro_version
,
5603 /* custom headers */
5604 if (compose
->account
->add_customhdr
) {
5607 for (cur
= compose
->account
->customhdr_list
; cur
!= NULL
;
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
);
5622 switch (compose
->priority
) {
5623 case PRIORITY_HIGHEST
: g_string_append_printf(header
, "Importance: high\n"
5624 "X-Priority: 1 (Highest)\n");
5626 case PRIORITY_HIGH
: g_string_append_printf(header
, "Importance: high\n"
5627 "X-Priority: 2 (High)\n");
5629 case PRIORITY_NORMAL
: break;
5630 case PRIORITY_LOW
: g_string_append_printf(header
, "Importance: low\n"
5631 "X-Priority: 4 (Low)\n");
5633 case PRIORITY_LOWEST
: g_string_append_printf(header
, "Importance: low\n"
5634 "X-Priority: 5 (Lowest)\n");
5636 default: debug_print("compose: priority unknown : %d\n",
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: "),
5649 g_string_append_printf(header
, "Disposition-Notification-To: %s <%s>\n", buf
, compose
->account
->address
);
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
;
5660 gchar
*headername_wcolon
;
5661 const gchar
*headername_trans
;
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
) {
5674 if (!strstr(tmp
, ":")) {
5675 headername_wcolon
= g_strconcat(tmp
, ":", NULL
);
5676 headername
= g_strdup(tmp
);
5678 headername_wcolon
= g_strdup(tmp
);
5679 headername
= g_strdup(strtok(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
;
5694 if (!standard_header
&& !IS_IN_CUSTOM_HEADER(headername
))
5695 g_string_append_printf(header
, "%s %s\n", headername_wcolon
, headervalue
);
5698 g_free(headername_wcolon
);
5702 g_string_free(header
, FALSE
);
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', ' ');
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
);
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();
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
) {
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
,
5767 codeconv_set_strict(FALSE
);
5772 static void compose_add_to_addressbook_cb(GtkMenuItem
*menuitem
, gpointer user_data
)
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
);
5788 static void compose_entry_popup_extend(GtkEntry
*entry
, GtkMenu
*menu
, gpointer user_data
)
5790 GtkWidget
*menuitem
;
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
};
5820 GList
*combo_list
= NULL
;
5822 const gchar
*header
= NULL
;
5823 ComposeHeaderEntry
*headerentry
;
5824 gboolean standard_header
= FALSE
;
5826 headerentry
= g_new0(ComposeHeaderEntry
, 1);
5829 combo
= gtk_combo_new();
5831 while(*string
!= NULL
) {
5832 combo_list
= g_list_append(combo_list
, (gchar
*)prefs_common_translated_header_name(*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
));
5848 while (*string
!= NULL
) {
5849 if (!strcmp(*string
, last_header_entry
))
5850 standard_header
= TRUE
;
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
) {
5859 header
= prefs_common_translated_header_name("Newsgroups:");
5862 header
= prefs_common_translated_header_name("To:");
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
);
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
),
5884 g_signal_connect(G_OBJECT(entry
), "changed",
5885 G_CALLBACK(compose_headerentry_changed_cb
),
5887 g_signal_connect_after(G_OBJECT(entry
), "grab_focus",
5888 G_CALLBACK(compose_grab_focus_cb
), compose
);
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
),
5897 g_signal_connect(G_OBJECT(entry
), "drag-drop",
5898 G_CALLBACK(compose_drag_drop
),
5900 g_signal_connect(G_OBJECT(entry
), "populate-popup",
5901 G_CALLBACK(compose_entry_popup_extend
),
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
,
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
)
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
;
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
);
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);
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
;
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
,
6015 G_TYPE_AUTO_POINTER
,
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",
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",
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
);
6050 g_signal_connect(G_OBJECT(attach_clist
), "popup-menu",
6051 G_CALLBACK(popup_attach_button_pressed
), compose
);
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
);
6058 g_signal_connect(G_OBJECT(attach_clist
), "key_press_event",
6059 G_CALLBACK(attach_key_pressed
), compose
);
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
),
6069 g_signal_connect(G_OBJECT(attach_clist
), "drag-drop",
6070 G_CALLBACK(compose_drag_drop
),
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
)
6085 GtkWidget
*savemsg_checkbtn
;
6086 GtkWidget
*savemsg_entry
;
6087 GtkWidget
*savemsg_select
;
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
);
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
),
6131 compose
->savemsg_checkbtn
= savemsg_checkbtn
;
6132 compose
->savemsg_entry
= savemsg_entry
;
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
)
6148 dest
= foldersel_folder_sel(NULL
, FOLDER_SEL_COPY
, NULL
);
6151 path
= folder_item_get_identifier(dest
);
6153 gtk_entry_set_text(GTK_ENTRY(compose
->savemsg_entry
), 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), \
6169 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
6170 G_CALLBACK(text_inserted), \
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), \
6181 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
6182 G_CALLBACK(text_inserted), \
6187 static gboolean
text_clicked(GtkWidget
*text
, GdkEventButton
*event
,
6191 GtkTextBuffer
*buffer
;
6193 if (event
->button
== 3) {
6195 GtkTextIter sel_start
, sel_end
;
6196 gboolean stuff_selected
;
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
,
6203 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text
),
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 */
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 */
6220 if (event
->button
== 2) {
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
,
6229 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text
),
6232 entry_paste_clipboard(compose
, text
,
6233 prefs_common
.linewrap_pastes
,
6234 GDK_SELECTION_PRIMARY
, &iter
);
6242 static void compose_spell_menu_changed(void *data
)
6244 Compose
*compose
= (Compose
*)data
;
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
);
6252 if (compose
->gtkaspell
== NULL
)
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
));
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
));
6283 static gboolean
compose_popup_menu(GtkWidget
*widget
, gpointer data
)
6285 Compose
*compose
= (Compose
*)data
;
6286 GdkEventButton event
;
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
,
6304 GtkWidget
*handlebox
;
6306 GtkWidget
*notebook
;
6311 GtkWidget
*subject_hbox
;
6312 GtkWidget
*subject_frame
;
6313 GtkWidget
*subject_entry
;
6317 GtkWidget
*edit_vbox
;
6318 GtkWidget
*ruler_hbox
;
6320 GtkWidget
*scrolledwin
;
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
;
6334 GtkWidget
*menuitem
;
6337 GtkAspell
* gtkaspell
= NULL
;
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
);
6380 if (compose_force_window_origin
)
6381 gtk_widget_set_uposition(window
, prefs_common
.compose_x
,
6382 prefs_common
.compose_y
);
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();
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
,
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);
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")));
6428 gtk_notebook_append_page(GTK_NOTEBOOK(notebook
),
6429 compose_create_others(compose
),
6430 gtk_label_new_with_mnemonic(_("Othe_rs")));
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);
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
,
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
),
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
),
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
);
6501 g_signal_connect(G_OBJECT(text
), "popup-menu",
6502 G_CALLBACK(compose_popup_menu
), compose
);
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
);
6509 g_signal_connect(G_OBJECT(subject_entry
), "changed",
6510 G_CALLBACK(compose_changed_cb
), compose
);
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
),
6519 g_signal_connect(G_OBJECT(text
), "drag-drop",
6520 G_CALLBACK(compose_drag_drop
),
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
);
6529 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window
) )
6530 gtk_widget_set_size_request(edit_vbox
, -1, mode
== COMPOSE_NEW
? 300 : 280);
6532 gtk_widget_set_size_request(edit_vbox
, -1, mode
== COMPOSE_NEW
? 250 : 230);
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
);
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
,
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
,
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
;
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 */
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
,
6652 alertpanel_error(_("Spell checker could not "
6654 gtkaspell_checkers_strerror());
6655 gtkaspell_checkers_reset_error();
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
);
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:"));
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
);
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
);
6713 compose
->priority
= PRIORITY_NORMAL
;
6714 compose_update_priority_menu_item(compose
);
6716 compose_set_out_encoding(compose
);
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
);
6727 gtk_widget_realize(window
);
6729 gtk_widget_show(window
);
6731 maemo_window_full_screen_if_needed(GTK_WINDOW(window
));
6732 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window
));
6739 static GtkWidget
*compose_account_option_menu_create(Compose
*compose
)
6744 GtkWidget
*optmenubox
;
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>"),
6773 if (ac
== compose
->account
) {
6774 if (ac
->name
&& *ac
->name
) {
6776 QUOTE_IF_REQUIRED_NORMAL(buf
, ac
->name
, return NULL
);
6777 from
= g_strdup_printf("%s <%s>",
6779 gtk_entry_set_text(GTK_ENTRY(from_name
), from
);
6781 from
= g_strdup_printf("%s",
6783 gtk_entry_set_text(GTK_ENTRY(from_name
), from
);
6786 COMBOBOX_ADD(menu
, name
, ac
->account_id
);
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
),
6796 g_signal_connect(G_OBJECT(from_name
), "populate-popup",
6797 G_CALLBACK(compose_entry_popup_extend
),
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
;
6813 static void compose_set_priority_cb(gpointer data
,
6817 Compose
*compose
= (Compose
*) data
;
6818 compose
->priority
= action
;
6821 static void compose_reply_change_mode(gpointer data
,
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
)
6834 if (action
== COMPOSE_REPLY
&& compose
->rmode
== COMPOSE_FOLLOWUP_AND_REPLY_TO
)
6836 if (action
== COMPOSE_REPLY_TO_ALL
)
6838 if (action
== COMPOSE_REPLY_TO_SENDER
)
6840 if (action
== COMPOSE_REPLY_TO_LIST
)
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");
6871 menuitem
= gtk_item_factory_get_item
6872 (ifactory
, "/Options/Priority/High");
6874 case PRIORITY_NORMAL
:
6875 menuitem
= gtk_item_factory_get_item
6876 (ifactory
, "/Options/Priority/Normal");
6879 menuitem
= gtk_item_factory_get_item
6880 (ifactory
, "/Options/Priority/Low");
6882 case PRIORITY_LOWEST
:
6883 menuitem
= gtk_item_factory_get_item
6884 (ifactory
, "/Options/Priority/Lowest");
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
;
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
)
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
;
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
) {
6933 menuitem
= gtk_item_factory_get_widget(ifactory
, branch_path
);
6934 g_return_if_fail(menuitem
!= NULL
);
6936 amenu
= GTK_MENU_SHELL(menuitem
)->children
;
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
);
6951 } else if (strlen(compose
->privacy_system
) == 0) {
6952 menuitem
= GTK_WIDGET(amenu
->data
);
6955 can_encrypt
= FALSE
;
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
);
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
;
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
;
6992 if (entry
->callback
== compose_set_encoding_cb
&&
6993 (CharSet
)entry
->callback_action
== out_encoding
) {
6994 p
= q
= path
= g_strdup(entry
->path
);
7006 item
= gtk_item_factory_get_item(ifactory
, path
);
7007 gtk_widget_activate(item
);
7014 static void compose_set_template_menu(Compose
*compose
)
7016 GSList
*tmpl_list
, *cur
;
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
),
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
;
7056 GtkWidget
*system_none
;
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
));
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
);
7094 g_slist_free(systems
);
7097 void compose_reflect_prefs_all(void)
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)
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
;
7136 qmark
= prefs_common
.fw_quotemark
;
7139 /* use reply-specific quote char in all other modes */
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
;
7147 qmark
= prefs_common
.quotemark
;
7151 if (qmark
== NULL
|| *qmark
== '\0')
7157 static void compose_template_apply(Compose
*compose
, Template
*tmpl
,
7161 GtkTextBuffer
*buffer
;
7165 gchar
*parsed_str
= NULL
;
7166 gint cursor_pos
= 0;
7167 const gchar
*err_msg
= _("Template body format error at line %d.");
7170 /* process the body */
7172 text
= GTK_TEXT_VIEW(compose
->text
);
7173 buffer
= gtk_text_view_get_buffer(text
);
7176 qmark
= compose_quote_char_from_context(compose
);
7178 if (compose
->replyinfo
!= NULL
) {
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
) {
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
);
7199 MsgInfo
* dummyinfo
= compose_msginfo_new_from_compose(compose
);
7201 GtkTextIter start
, end
;
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 */
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
);
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
);
7234 cursor_pos
= quote_fmt_get_cursor_pos();
7235 compose
->set_cursor_pos
= cursor_pos
;
7236 if (cursor_pos
== -1)
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
;
7256 if (compose
->replyinfo
!= NULL
)
7257 msginfo
= compose
->replyinfo
;
7258 else if (compose
->fwdinfo
!= NULL
)
7259 msginfo
= compose
->fwdinfo
;
7261 dummyinfo
= compose_msginfo_new_from_compose(compose
);
7262 msginfo
= dummyinfo
;
7265 if (tmpl
->to
&& *tmpl
->to
!= '\0') {
7267 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
,
7268 compose
->gtkaspell
);
7270 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
);
7272 quote_fmt_scan_string(tmpl
->to
);
7275 buf
= quote_fmt_get_buffer();
7277 alertpanel_error(_("Template To format error."));
7279 compose_entry_append(compose
, buf
, COMPOSE_TO
);
7283 if (tmpl
->cc
&& *tmpl
->cc
!= '\0') {
7285 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
,
7286 compose
->gtkaspell
);
7288 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
);
7290 quote_fmt_scan_string(tmpl
->cc
);
7293 buf
= quote_fmt_get_buffer();
7295 alertpanel_error(_("Template Cc format error."));
7297 compose_entry_append(compose
, buf
, COMPOSE_CC
);
7301 if (tmpl
->bcc
&& *tmpl
->bcc
!= '\0') {
7303 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
,
7304 compose
->gtkaspell
);
7306 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
);
7308 quote_fmt_scan_string(tmpl
->bcc
);
7311 buf
= quote_fmt_get_buffer();
7313 alertpanel_error(_("Template Bcc format error."));
7315 compose_entry_append(compose
, buf
, COMPOSE_BCC
);
7319 /* process the subject */
7320 if (tmpl
->subject
&& *tmpl
->subject
!= '\0') {
7322 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
,
7323 compose
->gtkaspell
);
7325 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
);
7327 quote_fmt_scan_string(tmpl
->subject
);
7330 buf
= quote_fmt_get_buffer();
7332 alertpanel_error(_("Template subject format error."));
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
;
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
);
7397 if (compose
->gtkaspell
) {
7398 gtkaspell_delete(compose
->gtkaspell
);
7399 compose
->gtkaspell
= NULL
;
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
);
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
);
7429 static void compose_attach_remove_selected(Compose
*compose
)
7431 GtkTreeView
*tree_view
= GTK_TREE_VIEW(compose
->attach_clist
);
7432 GtkTreeSelection
*selection
;
7434 GtkTreeModel
*model
;
7436 selection
= gtk_tree_view_get_selection(tree_view
);
7437 sel
= gtk_tree_selection_get_selected_rows(selection
, &model
);
7442 for (cur
= sel
; cur
!= NULL
; cur
= cur
->next
) {
7443 GtkTreePath
*path
= cur
->data
;
7444 GtkTreeRowReference
*ref
= gtk_tree_row_reference_new
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
);
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
);
7465 static struct _AttachProperty
7468 GtkWidget
*mimetype_entry
;
7469 GtkWidget
*encoding_optmenu
;
7470 GtkWidget
*path_entry
;
7471 GtkWidget
*filename_entry
;
7473 GtkWidget
*cancel_btn
;
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
);
7485 GtkComboBox
*optmenu
;
7486 GtkTreeSelection
*selection
;
7488 GtkTreeModel
*model
;
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)
7498 sel
= gtk_tree_selection_get_selected_rows(selection
, &model
);
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);
7507 g_list_foreach(sel
, gtk_tree_path_free_
, NULL
);
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
);
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
: "");
7533 const gchar
*entry_text
;
7535 gchar
*cnttype
= NULL
;
7542 gtk_widget_hide(attach_prop
.window
);
7547 entry_text
= gtk_entry_get_text(GTK_ENTRY(attach_prop
.mimetype_entry
));
7548 if (*entry_text
!= '\0') {
7551 text
= g_strstrip(g_strdup(entry_text
));
7552 if ((p
= strchr(text
, '/')) && !strchr(p
+ 1, '/')) {
7553 cnttype
= g_strdup(text
);
7556 alertpanel_error(_("Invalid MIME type."));
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
);
7571 (_("File doesn't exist or is empty."));
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
);
7584 g_free(ainfo
->content_type
);
7585 ainfo
->content_type
= cnttype
;
7588 g_free(ainfo
->file
);
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
,
7600 COL_NAME
, ainfo
->name
,
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
)
7627 GtkWidget
*mimetype_entry
;
7630 GtkListStore
*optmenu_menu
;
7631 GtkWidget
*path_entry
;
7632 GtkWidget
*filename_entry
;
7635 GtkWidget
*cancel_btn
;
7636 GList
*mime_type_list
, *strlist
;
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
),
7650 g_signal_connect(G_OBJECT(window
), "key_press_event",
7651 G_CALLBACK(attach_property_key_pressed
),
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),
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();
7673 for (; mime_type_list
!= NULL
; mime_type_list
= mime_type_list
->next
) {
7674 MimeType
*type
= (MimeType
*) mime_type_list
->data
;
7677 tmp
= g_strdup_printf("%s/%s", type
->type
, type
->sub_type
);
7679 if (g_list_find_custom(strlist
, tmp
, (GCompareFunc
)strcmp2
))
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,
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
,
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
),
7727 g_signal_connect(G_OBJECT(cancel_btn
), "clicked",
7728 G_CALLBACK(attach_property_cancel
),
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
)
7750 static void attach_property_cancel(GtkWidget
*widget
, gboolean
*cancelled
)
7756 static gint
attach_property_delete_event(GtkWidget
*widget
, GdkEventAny
*event
,
7757 gboolean
*cancelled
)
7765 static gboolean
attach_property_key_pressed(GtkWidget
*widget
,
7767 gboolean
*cancelled
)
7769 if (event
&& event
->keyval
== GDK_Escape
) {
7776 static void compose_exec_ext_editor(Compose
*compose
)
7783 tmp
= g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
7784 G_DIR_SEPARATOR
, compose
);
7786 if (pipe(pipe_fds
) < 0) {
7792 if ((pid
= fork()) < 0) {
7799 /* close the write side of the pipe */
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
,
7812 } else { /* process-monitoring process */
7818 /* close the read side of the pipe */
7821 if (compose_write_body_to_file(compose
, tmp
) < 0) {
7822 fd_write_all(pipe_fds
[1], "2\n", 2);
7826 pid_ed
= compose_exec_ext_editor_real(tmp
);
7828 fd_write_all(pipe_fds
[1], "1\n", 2);
7832 /* wait until editor is terminated */
7833 waitpid(pid_ed
, NULL
, 0);
7835 fd_write_all(pipe_fds
[1], "0\n", 2);
7842 #endif /* G_OS_UNIX */
7846 static gint
compose_exec_ext_editor_real(const gchar
*file
)
7853 g_return_val_if_fail(file
!= NULL
, -1);
7855 if ((pid
= fork()) < 0) {
7860 if (pid
!= 0) return pid
;
7862 /* grandchild process */
7864 if (setpgid(0, getppid()))
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
);
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
);
7882 g_strfreev(cmdline
);
7887 static gboolean
compose_ext_editor_kill(Compose
*compose
)
7889 pid_t pgid
= compose
->exteditor_pid
* -1;
7892 ret
= kill(pgid
, 0);
7894 if (ret
== 0 || (ret
== -1 && EPERM
== errno
)) {
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
);
7907 if (val
== G_ALERTALTERNATE
) {
7908 g_source_remove(compose
->exteditor_tag
);
7909 g_io_channel_shutdown(compose
->exteditor_ch
,
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;
7934 static gboolean
compose_input_cb(GIOChannel
*source
, GIOCondition condition
,
7938 Compose
*compose
= (Compose
*)data
;
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;
7981 static void compose_set_ext_editor_sensitive(Compose
*compose
,
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",
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
);
8039 case UNDO_STATE_FALSE
:
8040 if (undostruct
->undo_state
) {
8041 undostruct
->undo_state
= FALSE
;
8042 menu_set_sensitive(ifactory
, "/Edit/Undo", FALSE
);
8045 case UNDO_STATE_UNCHANGED
:
8047 case UNDO_STATE_REFRESH
:
8048 menu_set_sensitive(ifactory
, "/Edit/Undo",
8049 undostruct
->undo_state
);
8052 g_warning("Undo state not recognized");
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
);
8063 case UNDO_STATE_FALSE
:
8064 if (undostruct
->redo_state
) {
8065 undostruct
->redo_state
= FALSE
;
8066 menu_set_sensitive(ifactory
, "/Edit/Redo", FALSE
);
8069 case UNDO_STATE_UNCHANGED
:
8071 case UNDO_STATE_REFRESH
:
8072 menu_set_sensitive(ifactory
, "/Edit/Redo",
8073 undostruct
->redo_state
);
8076 g_warning("Redo state not recognized");
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
);
8109 static void account_activated(GtkComboBox
*optmenu
, gpointer data
)
8111 Compose
*compose
= (Compose
*)data
;
8114 gchar
*folderidentifier
;
8115 gint account_id
= 0;
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
,
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
);
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
);
8184 static gboolean
attach_key_pressed(GtkWidget
*widget
, GdkEventKey
*event
,
8187 Compose
*compose
= (Compose
*)data
;
8189 if (!event
) return FALSE
;
8191 switch (event
->keyval
) {
8193 compose_attach_remove_selected(compose
);
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
);
8206 menu_set_sensitive(ifactory
, "/Spelling", allow
);
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.")))
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
,
8237 Compose
*compose
= (Compose
*)data
;
8241 val
= compose_queue_sub(compose
, NULL
, NULL
, NULL
, TRUE
, TRUE
);
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) {
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");
8274 fprintf(fp
, "%s\t%d\n", folder_item_get_identifier(info
->folder
),
8282 gboolean
compose_draft (gpointer data
, guint action
)
8284 Compose
*compose
= (Compose
*)data
;
8288 MsgFlags flag
= {0, 0};
8289 static gboolean lock
= FALSE
;
8290 MsgInfo
*newmsginfo
;
8292 gboolean target_locked
= FALSE
;
8294 if (lock
) return FALSE
;
8296 if (compose
->sending
)
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");
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");
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
)) {
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
);
8354 /* Message-ID of message forwarding to */
8355 if ((compose
->fwdinfo
!= NULL
) && (compose
->fwdinfo
->msgid
!= NULL
)) {
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
);
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) {
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) {
8384 if (action
!= COMPOSE_AUTO_SAVE
) {
8385 if (action
!= COMPOSE_DRAFT_FOR_EXIT
)
8386 alertpanel_error(_("Could not save draft."));
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
) {
8397 g_mutex_unlock(compose
->mutex
); /* must be done before closing */
8398 compose_close(compose
);
8402 g_mutex_unlock(compose
->mutex
); /* must be done before closing */
8411 if (compose
->mode
== COMPOSE_REEDIT
) {
8412 compose_remove_reedit_target(compose
, TRUE
);
8415 newmsginfo
= folder_item_get_msginfo(draft
, msgnum
);
8417 procmsg_msginfo_unset_flags(newmsginfo
, ~0, ~0);
8419 procmsg_msginfo_set_flags(newmsginfo
, MSG_LOCKED
, MSG_DRAFT
);
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
) {
8436 g_mutex_unlock(compose
->mutex
); /* must be done before closing */
8437 compose_close(compose
);
8443 path
= folder_item_fetch_msg(draft
, msgnum
);
8445 debug_print("can't fetch %s:%d\n",draft
->path
, msgnum
);
8448 if (g_stat(path
, &s
) < 0) {
8449 FILE_OP_ERROR(path
, "stat");
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
;
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
);
8473 g_mutex_unlock(compose
->mutex
);
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
))
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");
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
);
8504 compose_reedit(info
, FALSE
);
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
;
8524 if (compose
->redirect_filename
!= NULL
)
8527 file_list
= filesel_select_multiple_files_open(_("Select file"));
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
);
8538 g_free(utf8_filename
);
8540 g_list_free(file_list
);
8544 static void compose_insert_file_cb(gpointer data
, guint action
,
8547 Compose
*compose
= (Compose
*)data
;
8550 file_list
= filesel_select_multiple_files_open(_("Select file"));
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
);
8572 g_list_free(file_list
);
8576 static void compose_insert_sig_cb(gpointer data
, guint action
,
8579 Compose
*compose
= (Compose
*)data
;
8581 compose_insert_sig(compose
, FALSE
);
8584 static gint
compose_delete_cb(GtkWidget
*widget
, GdkEventAny
*event
,
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
)
8596 compose_close_cb(compose
, 0, NULL
);
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
;
8611 if (compose
->exteditor_tag
!= -1) {
8612 if (!compose_ext_editor_kill(compose
))
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
);
8623 case G_ALERTDEFAULT
:
8624 if (prefs_common
.autosave
)
8625 compose_remove_draft(compose
);
8627 case G_ALERTALTERNATE
:
8628 compose_draft_cb(data
, COMPOSE_QUIT_EDITING
, NULL
);
8635 compose_close(compose
);
8638 static void compose_set_encoding_cb(gpointer data
, guint action
,
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
;
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' ?"),
8666 val
= alertpanel(_("Apply template"), msg
,
8667 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL
);
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
,
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
),
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
;
8731 gchar
*contents
= gtk_clipboard_wait_for_text(gtk_clipboard_get(clip
));
8733 if (contents
== NULL
)
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
));
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
));
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
8798 && GTK_WIDGET_HAS_FOCUS(compose
->focused_editable
)
8801 entry_cut_clipboard(compose
->focused_editable
);
8804 static void compose_copy_cb(Compose
*compose
)
8806 if (compose
->focused_editable
8808 && GTK_WIDGET_HAS_FOCUS(compose
->focused_editable
)
8811 entry_copy_clipboard(compose
->focused_editable
);
8814 static void compose_paste_cb(Compose
*compose
)
8817 GtkTextBuffer
*buffer
;
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
);
8827 static void compose_paste_as_quote_cb(Compose
*compose
)
8829 gint wrap_quote
= prefs_common
.linewrap_quote
;
8830 if (compose
->focused_editable
8832 && GTK_WIDGET_HAS_FOCUS(compose
->focused_editable
)
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
)
8856 GtkTextBuffer
*buffer
;
8858 if (compose
->focused_editable
8860 && GTK_WIDGET_HAS_FOCUS(compose
->focused_editable
)
8863 entry_paste_clipboard(compose
, compose
->focused_editable
, FALSE
,
8864 GDK_SELECTION_CLIPBOARD
, NULL
);
8868 static void compose_paste_wrap_cb(Compose
*compose
)
8871 GtkTextBuffer
*buffer
;
8873 if (compose
->focused_editable
8875 && GTK_WIDGET_HAS_FOCUS(compose
->focused_editable
)
8878 entry_paste_clipboard(compose
, compose
->focused_editable
, TRUE
,
8879 GDK_SELECTION_CLIPBOARD
, NULL
);
8883 static void compose_allsel_cb(Compose
*compose
)
8885 if (compose
->focused_editable
8887 && GTK_WIDGET_HAS_FOCUS(compose
->focused_editable
)
8890 entry_allsel(compose
->focused_editable
);
8893 static void textview_move_beginning_of_line (GtkTextView
*text
)
8895 GtkTextBuffer
*buffer
;
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
;
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
;
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
;
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
;
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
;
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
;
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
;
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
;
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
);
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
;
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
);
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
;
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
);
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
;
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
);
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
;
9099 GtkTextIter ins
, start_iter
, end_iter
;
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
);
9109 gtk_text_iter_set_line_offset(&start_iter
, 0);
9112 if (gtk_text_iter_ends_line(&end_iter
))
9113 found
= gtk_text_iter_forward_char(&end_iter
);
9115 found
= gtk_text_iter_forward_to_line_end(&end_iter
);
9118 gtk_text_buffer_delete(buffer
, &start_iter
, &end_iter
);
9121 static void textview_delete_to_line_end (GtkTextView
*text
)
9123 GtkTextBuffer
*buffer
;
9125 GtkTextIter ins
, end_iter
;
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
);
9134 if (gtk_text_iter_ends_line(&end_iter
))
9135 found
= gtk_text_iter_forward_char(&end_iter
);
9137 found
= gtk_text_iter_forward_to_line_end(&end_iter
);
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
);
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
);
9173 g_warning("Not implemented yet.");
9177 static void compose_grab_focus_cb(GtkWidget
*widget
, Compose
*compose
)
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
),
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);
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
;
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
),
9227 gtk_widget_grab_focus(widget
);
9228 g_signal_handlers_unblock_by_func(G_OBJECT(widget
),
9229 G_CALLBACK(compose_grab_focus_cb
),
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
),
9244 gtk_widget_grab_focus(widget
);
9245 g_signal_handlers_unblock_by_func(G_OBJECT(widget
),
9246 G_CALLBACK(compose_grab_focus_cb
),
9252 static void compose_changed_cb(GtkTextBuffer
*textbuf
, Compose
*compose
)
9254 compose
->modified
= TRUE
;
9256 compose_set_title(compose
);
9260 static void compose_wrap_cb(gpointer data
, guint action
, GtkWidget
*widget
)
9262 Compose
*compose
= (Compose
*)data
;
9265 compose_wrap_all_full(compose
, TRUE
);
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
,
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
,
9290 Compose
*compose
= (Compose
*)data
;
9292 if (GTK_CHECK_MENU_ITEM(widget
)->active
)
9293 compose
->use_signing
= TRUE
;
9295 compose
->use_signing
= FALSE
;
9298 static void compose_toggle_encrypt_cb(gpointer data
, guint action
,
9301 Compose
*compose
= (Compose
*)data
;
9303 if (GTK_CHECK_MENU_ITEM(widget
)->active
)
9304 compose
->use_encryption
= TRUE
;
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
,
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
;
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
,
9336 GtkSelectionData
*data
,
9341 Compose
*compose
= (Compose
*)user_data
;
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
);
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
;
9369 list
= summary_get_selected_msg_list(summaryview
);
9371 for (cur
= list
; cur
; cur
= cur
->next
) {
9372 MsgInfo
*msginfo
= (MsgInfo
*)cur
->data
;
9375 file
= procmsg_get_message_file_full(msginfo
,
9378 compose_attach_append(compose
, (const gchar
*)file
,
9379 (const gchar
*)file
, "message/rfc822");
9387 static gboolean
compose_drag_drop(GtkWidget
*widget
,
9388 GdkDragContext
*drag_context
,
9390 guint time
, gpointer user_data
)
9392 /* not handling this signal makes compose_insert_drag_received_cb
9397 static void compose_insert_drag_received_cb (GtkWidget
*widget
,
9398 GdkDragContext
*drag_context
,
9401 GtkSelectionData
*data
,
9406 Compose
*compose
= (Compose
*)user_data
;
9409 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
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
);
9422 case COMPOSE_DND_INSERT
:
9423 val
= G_ALERTALTERNATE
;
9425 case COMPOSE_DND_ATTACH
:
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
);
9445 } else if (val
== G_ALERTOTHER
) {
9446 compose_attach_drag_received_cb(widget
, drag_context
, x
, y
, data
, info
, time
, user_data
);
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
);
9455 gtk_drag_finish(drag_context
, TRUE
, FALSE
, time
);
9458 #if GTK_CHECK_VERSION(2, 8, 0)
9459 /* do nothing, handled by GTK */
9461 gchar
*tmpfile
= get_tmp_file();
9462 str_write_to_file((const gchar
*)data
->data
, tmpfile
);
9463 compose_insert_file(compose
, tmpfile
);
9466 gtk_drag_finish(drag_context
, TRUE
, FALSE
, time
);
9470 gtk_drag_finish(drag_context
, TRUE
, FALSE
, time
);
9473 static void compose_header_drag_received_cb (GtkWidget
*widget
,
9474 GdkDragContext
*drag_context
,
9477 GtkSelectionData
*data
,
9482 GtkEditable
*entry
= (GtkEditable
*)user_data
;
9483 gchar
*email
= (gchar
*)data
->data
;
9485 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
9488 if (!strncmp(email
, "mailto:", strlen("mailto:"))) {
9489 gchar
*decoded
=g_new(gchar
, strlen(email
));
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
);
9500 gtk_drag_finish(drag_context
, TRUE
, FALSE
, time
);
9503 static void compose_toggle_return_receipt_cb(gpointer data
, guint action
,
9506 Compose
*compose
= (Compose
*)data
;
9508 if (GTK_CHECK_MENU_ITEM(widget
)->active
)
9509 compose
->return_receipt
= TRUE
;
9511 compose
->return_receipt
= FALSE
;
9514 static void compose_toggle_remove_refs_cb(gpointer data
, guint action
,
9517 Compose
*compose
= (Compose
*)data
;
9519 if (GTK_CHECK_MENU_ITEM(widget
)->active
)
9520 compose
->remove_references
= TRUE
;
9522 compose
->remove_references
= FALSE
;
9525 static gboolean
compose_headerentry_key_press_event_cb(GtkWidget
*entry
,
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
,
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
);
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
);
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"));
9592 g_return_if_fail(text
!= NULL
);
9594 g_signal_handlers_block_by_func(G_OBJECT(buffer
),
9595 G_CALLBACK(text_inserted
),
9597 if (paste_as_quotation
) {
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();
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
);
9621 if (strcmp(text
, "\n") || automatic_break
9622 || gtk_text_iter_starts_line(iter
))
9623 gtk_text_buffer_insert(buffer
, iter
, text
, len
);
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
),
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
);
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
);
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
);
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
);
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
);
9710 account
= msginfo
->folder
->folder
->account
;
9712 if (!account
&& msginfo
->to
&& prefs_common
.forward_account_autosel
) {
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
) {
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
);
9742 gboolean
compose_close(Compose
*compose
)
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
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
);
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
);
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
) {
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
)
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
);
9816 compose
= compose_reply_mode((ComposeMode
)action
, new_msglist
, body
);
9817 procmsg_msginfo_free(tmp_msginfo
);
9818 g_slist_free(new_msglist
);
9820 compose
= compose_reply_mode((ComposeMode
)action
, msginfo_list
, body
);
9822 if (originally_enc
) {
9823 compose_force_encryption(compose
, compose
->account
, FALSE
);
9829 void compose_reply_from_messageview(MessageView
*msgview
, GSList
*msginfo_list
,
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
) {
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
) {
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
;
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
;
9892 gchar buf
[BUFFSIZE
];
9894 g_return_val_if_fail( compose
!= NULL
, NULL
);
9896 newmsginfo
= procmsg_msginfo_new();
9899 get_rfc822_date(buf
, sizeof(buf
));
9900 newmsginfo
->date
= g_strdup(buf
);
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
);
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(
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
;
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
;
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
;
9953 /* other data is unset */
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
);