2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2024 the Claws Mail team and Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "claws-features.h"
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
34 #ifdef GDK_WINDOWING_X11
38 #include <pango/pango-break.h>
43 #include <sys/types.h>
48 # include <sys/wait.h>
52 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
59 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
66 #include "mainwindow.h"
68 #ifndef USE_ALT_ADDRBOOK
69 #include "addressbook.h"
71 #include "addressbook-dbus.h"
72 #include "addressadd.h"
74 #include "folderview.h"
77 #include "stock_pixmap.h"
78 #include "send_message.h"
81 #include "customheader.h"
82 #include "prefs_common.h"
83 #include "prefs_account.h"
87 #include "procheader.h"
89 #include "statusbar.h"
91 #include "quoted-printable.h"
95 #include "gtkshruler.h"
97 #include "alertpanel.h"
98 #include "manage_window.h"
100 #include "folder_item_prefs.h"
101 #include "addr_compl.h"
102 #include "quote_fmt.h"
104 #include "foldersel.h"
107 #include "message_search.h"
108 #include "combobox.h"
112 #include "autofaces.h"
113 #include "spell_entry.h"
115 #include "file-utils.h"
118 #include "password.h"
119 #include "ldapserver.h"
133 #define N_ATTACH_COLS (N_COL_COLUMNS)
137 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED
= -1,
138 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE
= 0,
139 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER
,
140 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER
,
141 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD
,
142 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD
,
143 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE
,
144 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE
,
145 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE
,
146 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER
,
147 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER
,
148 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD
,
149 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD
,
150 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE
,
151 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
152 } ComposeCallAdvancedAction
;
156 PRIORITY_HIGHEST
= 1,
165 COMPOSE_INSERT_SUCCESS
,
166 COMPOSE_INSERT_READ_ERROR
,
167 COMPOSE_INSERT_INVALID_CHARACTER
,
168 COMPOSE_INSERT_NO_FILE
169 } ComposeInsertResult
;
173 COMPOSE_WRITE_FOR_SEND
,
174 COMPOSE_WRITE_FOR_STORE
179 COMPOSE_QUOTE_FORCED
,
186 SUBJECT_FIELD_PRESENT
,
191 #define MAX_REFERENCES_LEN 999
193 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
194 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
196 #define COMPOSE_PRIVACY_WARNING() { \
197 alertpanel_error(_("You have opted to sign and/or encrypt this " \
198 "message but have not selected a privacy system.\n\n" \
199 "Signing and encrypting have been disabled for this " \
204 #define INVALID_PID INVALID_HANDLE_VALUE
206 #define INVALID_PID -1
209 static GdkRGBA default_header_bgcolor
=
212 static GdkRGBA default_header_color
=
215 static GList
*compose_list
= NULL
;
216 static GSList
*extra_headers
= NULL
;
218 static Compose
*compose_generic_new (PrefsAccount
*account
,
222 GList
*listAddress
);
224 static Compose
*compose_create (PrefsAccount
*account
,
229 static void compose_entry_indicate (Compose
*compose
,
230 const gchar
*address
);
231 static Compose
*compose_followup_and_reply_to (MsgInfo
*msginfo
,
232 ComposeQuoteMode quote_mode
,
236 static Compose
*compose_forward_multiple (PrefsAccount
*account
,
237 GSList
*msginfo_list
);
238 static Compose
*compose_reply (MsgInfo
*msginfo
,
239 ComposeQuoteMode quote_mode
,
244 static Compose
*compose_reply_mode (ComposeMode mode
,
245 GSList
*msginfo_list
,
247 static void compose_template_apply_fields(Compose
*compose
, Template
*tmpl
);
248 static void compose_update_privacy_systems_menu(Compose
*compose
);
250 static GtkWidget
*compose_account_option_menu_create
252 static void compose_set_out_encoding (Compose
*compose
);
253 static void compose_set_template_menu (Compose
*compose
);
254 static void compose_destroy (Compose
*compose
);
256 static MailField
compose_entries_set (Compose
*compose
,
258 ComposeEntryType to_type
);
259 static gint
compose_parse_header (Compose
*compose
,
261 static gint
compose_parse_manual_headers (Compose
*compose
,
263 HeaderEntry
*entries
);
264 static gchar
*compose_parse_references (const gchar
*ref
,
267 static gchar
*compose_quote_fmt (Compose
*compose
,
273 gboolean need_unescape
,
274 const gchar
*err_msg
);
276 static void compose_reply_set_entry (Compose
*compose
,
282 followup_and_reply_to
);
283 static void compose_reedit_set_entry (Compose
*compose
,
286 static void compose_insert_sig (Compose
*compose
,
288 static ComposeInsertResult
compose_insert_file (Compose
*compose
,
291 static gboolean
compose_attach_append (Compose
*compose
,
294 const gchar
*content_type
,
295 const gchar
*charset
);
296 static void compose_attach_parts (Compose
*compose
,
299 static gboolean
compose_beautify_paragraph (Compose
*compose
,
300 GtkTextIter
*par_iter
,
302 static void compose_wrap_all (Compose
*compose
);
303 static void compose_wrap_all_full (Compose
*compose
,
306 static void compose_set_title (Compose
*compose
);
307 static void compose_select_account (Compose
*compose
,
308 PrefsAccount
*account
,
311 static PrefsAccount
*compose_current_mail_account(void);
312 /* static gint compose_send (Compose *compose); */
313 static gboolean compose_check_for_valid_recipient
315 static gboolean
compose_check_entries (Compose
*compose
,
316 gboolean check_everything
);
317 static gint
compose_write_to_file (Compose
*compose
,
320 gboolean attach_parts
);
321 static gint
compose_write_body_to_file (Compose
*compose
,
323 static gint
compose_remove_reedit_target (Compose
*compose
,
325 static void compose_remove_draft (Compose
*compose
);
326 static ComposeQueueResult
compose_queue_sub (Compose
*compose
,
330 gboolean perform_checks
,
331 gboolean remove_reedit_target
);
332 static int compose_add_attachments (Compose
*compose
,
335 static gchar
*compose_get_header (Compose
*compose
);
336 static gchar
*compose_get_manual_headers_info (Compose
*compose
);
338 static void compose_convert_header (Compose
*compose
,
343 gboolean addr_field
);
345 static void compose_attach_info_free (AttachInfo
*ainfo
);
346 static void compose_attach_remove_selected (GtkAction
*action
,
349 static void compose_template_apply (Compose
*compose
,
352 static void compose_attach_property (GtkAction
*action
,
354 static void compose_attach_property_create (gboolean
*cancelled
);
355 static void attach_property_ok (GtkWidget
*widget
,
356 gboolean
*cancelled
);
357 static void attach_property_cancel (GtkWidget
*widget
,
358 gboolean
*cancelled
);
359 static gint
attach_property_delete_event (GtkWidget
*widget
,
361 gboolean
*cancelled
);
362 static gboolean
attach_property_key_pressed (GtkWidget
*widget
,
364 gboolean
*cancelled
);
366 static void compose_exec_ext_editor (Compose
*compose
);
367 static gboolean
compose_ext_editor_kill (Compose
*compose
);
368 static void compose_ext_editor_closed_cb (GPid pid
,
371 static void compose_set_ext_editor_sensitive (Compose
*compose
,
373 static gboolean
compose_get_ext_editor_cmd_valid();
374 static gboolean
compose_get_ext_editor_uses_socket();
375 #ifdef GDK_WINDOWING_X11
376 static gboolean compose_ext_editor_plug_removed_cb
379 #endif /* GDK_WINDOWING_X11 */
381 static void compose_undo_state_changed (UndoMain
*undostruct
,
386 static void compose_create_header_entry (Compose
*compose
);
387 static void compose_add_header_entry (Compose
*compose
, const gchar
*header
,
388 gchar
*text
, ComposePrefType pref_type
);
389 static void compose_remove_header_entries(Compose
*compose
);
391 static void compose_update_priority_menu_item(Compose
* compose
);
393 static void compose_spell_menu_changed (void *data
);
394 static void compose_dict_changed (void *data
);
396 static void compose_add_field_list ( Compose
*compose
,
397 GList
*listAddress
);
399 /* callback functions */
401 static void compose_notebook_size_alloc (GtkNotebook
*notebook
,
402 GtkAllocation
*allocation
,
404 static gboolean
compose_edit_size_alloc (GtkEditable
*widget
,
405 GtkAllocation
*allocation
,
406 GtkSHRuler
*shruler
);
407 static void account_activated (GtkComboBox
*optmenu
,
409 static void attach_selected (GtkTreeView
*tree_view
,
410 GtkTreePath
*tree_path
,
411 GtkTreeViewColumn
*column
,
413 static gboolean
attach_button_pressed (GtkWidget
*widget
,
414 GdkEventButton
*event
,
416 static gboolean
attach_key_pressed (GtkWidget
*widget
,
419 static void compose_send_cb (GtkAction
*action
, gpointer data
);
420 static void compose_send_later_cb (GtkAction
*action
, gpointer data
);
422 static void compose_save_cb (GtkAction
*action
,
425 static void compose_attach_cb (GtkAction
*action
,
427 static void compose_insert_file_cb (GtkAction
*action
,
429 static void compose_insert_sig_cb (GtkAction
*action
,
431 static void compose_replace_sig_cb (GtkAction
*action
,
434 static void compose_close_cb (GtkAction
*action
,
436 static void compose_print_cb (GtkAction
*action
,
439 static void compose_set_encoding_cb (GtkAction
*action
, GtkRadioAction
*current
, gpointer data
);
441 static void compose_address_cb (GtkAction
*action
,
443 static void about_show_cb (GtkAction
*action
,
445 static void compose_template_activate_cb(GtkWidget
*widget
,
448 static void compose_ext_editor_cb (GtkAction
*action
,
451 static gint
compose_delete_cb (GtkWidget
*widget
,
455 static void compose_undo_cb (GtkAction
*action
,
457 static void compose_redo_cb (GtkAction
*action
,
459 static void compose_cut_cb (GtkAction
*action
,
461 static void compose_copy_cb (GtkAction
*action
,
463 static void compose_paste_cb (GtkAction
*action
,
465 static void compose_paste_as_quote_cb (GtkAction
*action
,
467 static void compose_paste_no_wrap_cb (GtkAction
*action
,
469 static void compose_paste_wrap_cb (GtkAction
*action
,
471 static void compose_allsel_cb (GtkAction
*action
,
474 static void compose_advanced_action_cb (GtkAction
*action
,
477 static void compose_grab_focus_cb (GtkWidget
*widget
,
480 static void compose_changed_cb (GtkTextBuffer
*textbuf
,
483 static void compose_wrap_cb (GtkAction
*action
,
485 static void compose_wrap_all_cb (GtkAction
*action
,
487 static void compose_find_cb (GtkAction
*action
,
489 static void compose_toggle_autowrap_cb (GtkToggleAction
*action
,
491 static void compose_toggle_autoindent_cb(GtkToggleAction
*action
,
494 static void compose_toggle_ruler_cb (GtkToggleAction
*action
,
496 static void compose_toggle_sign_cb (GtkToggleAction
*action
,
498 static void compose_toggle_encrypt_cb (GtkToggleAction
*action
,
500 static void compose_set_privacy_system_cb(GtkWidget
*widget
, gpointer data
);
501 static void compose_update_privacy_system_menu_item(Compose
* compose
, gboolean warn
);
502 static void compose_activate_privacy_system (Compose
*compose
,
503 PrefsAccount
*account
,
505 static void compose_apply_folder_privacy_settings(Compose
*compose
, FolderItem
*folder_item
);
506 static void compose_toggle_return_receipt_cb(GtkToggleAction
*action
,
508 static void compose_toggle_remove_refs_cb(GtkToggleAction
*action
,
510 static void compose_set_priority_cb (GtkAction
*action
, GtkRadioAction
*current
, gpointer data
);
511 static void compose_reply_change_mode (Compose
*compose
, ComposeMode action
);
512 static void compose_reply_change_mode_cb(GtkAction
*action
, GtkRadioAction
*current
, gpointer data
);
514 static void compose_attach_drag_received_cb (GtkWidget
*widget
,
515 GdkDragContext
*drag_context
,
518 GtkSelectionData
*data
,
522 static void compose_insert_drag_received_cb (GtkWidget
*widget
,
523 GdkDragContext
*drag_context
,
526 GtkSelectionData
*data
,
530 static void compose_header_drag_received_cb (GtkWidget
*widget
,
531 GdkDragContext
*drag_context
,
534 GtkSelectionData
*data
,
539 static gboolean
compose_drag_drop (GtkWidget
*widget
,
540 GdkDragContext
*drag_context
,
542 guint time
, gpointer user_data
);
543 static gboolean completion_set_focus_to_subject
548 static void text_inserted (GtkTextBuffer
*buffer
,
553 static Compose
*compose_generic_reply(MsgInfo
*msginfo
,
554 ComposeQuoteMode quote_mode
,
558 gboolean followup_and_reply_to
,
561 static void compose_headerentry_changed_cb (GtkWidget
*entry
,
562 ComposeHeaderEntry
*headerentry
);
563 static gboolean
compose_headerentry_key_press_event_cb(GtkWidget
*entry
,
565 ComposeHeaderEntry
*headerentry
);
566 static gboolean
compose_headerentry_button_clicked_cb (GtkWidget
*button
,
567 ComposeHeaderEntry
*headerentry
);
569 static void compose_show_first_last_header (Compose
*compose
, gboolean show_first
);
571 static void compose_allow_user_actions (Compose
*compose
, gboolean allow
);
573 static void compose_nothing_cb (GtkAction
*action
, gpointer data
)
579 static void compose_check_all (GtkAction
*action
, gpointer data
);
580 static void compose_highlight_all (GtkAction
*action
, gpointer data
);
581 static void compose_check_backwards (GtkAction
*action
, gpointer data
);
582 static void compose_check_forwards_go (GtkAction
*action
, gpointer data
);
585 static PrefsAccount
*compose_find_account (MsgInfo
*msginfo
);
587 static MsgInfo
*compose_msginfo_new_from_compose(Compose
*compose
);
590 static void compose_set_dictionaries_from_folder_prefs(Compose
*compose
,
591 FolderItem
*folder_item
);
593 static void compose_attach_update_label(Compose
*compose
);
594 static void compose_set_folder_prefs(Compose
*compose
, FolderItem
*folder
,
595 gboolean respect_default_to
);
596 static void compose_subject_entry_activated(GtkWidget
*widget
, gpointer data
);
597 static void from_name_activate_cb(GtkWidget
*widget
, gpointer data
);
599 static GtkActionEntry compose_popup_entries
[] =
601 {"Compose", NULL
, "Compose", NULL
, NULL
, NULL
},
602 {"Compose/Add", NULL
, N_("_Add..."), NULL
, NULL
, G_CALLBACK(compose_attach_cb
) },
603 {"Compose/Remove", NULL
, N_("_Remove"), NULL
, NULL
, G_CALLBACK(compose_attach_remove_selected
) },
604 {"Compose/---", NULL
, "---", NULL
, NULL
, NULL
},
605 {"Compose/Properties", NULL
, N_("_Properties..."), NULL
, NULL
, G_CALLBACK(compose_attach_property
) },
608 /* make sure to keep the key bindings in the tables below in sync with the default_menurc[] in prefs_other.c
609 as well as with tables in messageview.c */
610 static GtkActionEntry compose_entries
[] =
612 {"Menu", NULL
, "Menu", NULL
, NULL
, NULL
},
614 {"Message", NULL
, N_("_Message"), NULL
, NULL
, NULL
},
615 {"Edit", NULL
, N_("_Edit"), NULL
, NULL
, NULL
},
617 {"Spelling", NULL
, N_("_Spelling"), NULL
, NULL
, NULL
},
619 {"Options", NULL
, N_("_Options"), NULL
, NULL
, NULL
},
620 {"Tools", NULL
, N_("_Tools"), NULL
, NULL
, NULL
},
621 {"Help", NULL
, N_("_Help"), NULL
, NULL
, NULL
},
623 {"Message/Send", NULL
, N_("S_end"), "<control>Return", NULL
, G_CALLBACK(compose_send_cb
) },
624 {"Message/SendLater", NULL
, N_("Send _later"), "<shift><control>S", NULL
, G_CALLBACK(compose_send_later_cb
) },
625 {"Message/---", NULL
, "---", NULL
, NULL
, NULL
},
627 {"Message/AttachFile", NULL
, N_("_Attach file"), "<control>M", NULL
, G_CALLBACK(compose_attach_cb
) },
628 {"Message/InsertFile", NULL
, N_("_Insert file"), "<control>I", NULL
, G_CALLBACK(compose_insert_file_cb
) },
629 {"Message/InsertSig", NULL
, N_("Insert si_gnature"), "<control>G", NULL
, G_CALLBACK(compose_insert_sig_cb
) },
630 {"Message/ReplaceSig", NULL
, N_("_Replace signature"), NULL
, NULL
, G_CALLBACK(compose_replace_sig_cb
) },
631 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
632 {"Message/Save", NULL
, N_("_Save"), "<control>S", NULL
, G_CALLBACK(compose_save_cb
) }, /*COMPOSE_KEEP_EDITING*/
633 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
634 {"Message/Print", NULL
, N_("_Print"), NULL
, NULL
, G_CALLBACK(compose_print_cb
) },
635 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
636 {"Message/Close", NULL
, N_("_Close"), "<control>W", NULL
, G_CALLBACK(compose_close_cb
) },
639 {"Edit/Undo", NULL
, N_("_Undo"), "<control>Z", NULL
, G_CALLBACK(compose_undo_cb
) },
640 {"Edit/Redo", NULL
, N_("_Redo"), "<control>Y", NULL
, G_CALLBACK(compose_redo_cb
) },
641 {"Edit/---", NULL
, "---", NULL
, NULL
, NULL
},
643 {"Edit/Cut", NULL
, N_("Cu_t"), "<control>X", NULL
, G_CALLBACK(compose_cut_cb
) },
644 {"Edit/Copy", NULL
, N_("_Copy"), "<control>C", NULL
, G_CALLBACK(compose_copy_cb
) },
645 {"Edit/Paste", NULL
, N_("_Paste"), "<control>V", NULL
, G_CALLBACK(compose_paste_cb
) },
647 {"Edit/SpecialPaste", NULL
, N_("_Special paste"), NULL
, NULL
, NULL
},
648 {"Edit/SpecialPaste/AsQuotation", NULL
, N_("As _quotation"), NULL
, NULL
, G_CALLBACK(compose_paste_as_quote_cb
) },
649 {"Edit/SpecialPaste/Wrapped", NULL
, N_("_Wrapped"), NULL
, NULL
, G_CALLBACK(compose_paste_wrap_cb
) },
650 {"Edit/SpecialPaste/Unwrapped", NULL
, N_("_Unwrapped"), NULL
, NULL
, G_CALLBACK(compose_paste_no_wrap_cb
) },
652 {"Edit/SelectAll", NULL
, N_("Select _all"), "<control>A", NULL
, G_CALLBACK(compose_allsel_cb
) },
654 {"Edit/Advanced", NULL
, N_("A_dvanced"), NULL
, NULL
, NULL
},
655 {"Edit/Advanced/BackChar", NULL
, N_("Move a character backward"), "<shift><control>B", NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
656 {"Edit/Advanced/ForwChar", NULL
, N_("Move a character forward"), "<shift><control>F", NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
657 {"Edit/Advanced/BackWord", NULL
, N_("Move a word backward"), NULL
, NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
658 {"Edit/Advanced/ForwWord", NULL
, N_("Move a word forward"), NULL
, NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
659 {"Edit/Advanced/BegLine", NULL
, N_("Move to beginning of line"), NULL
, NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
660 {"Edit/Advanced/EndLine", NULL
, N_("Move to end of line"), "<control>E", NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
661 {"Edit/Advanced/PrevLine", NULL
, N_("Move to previous line"), "<control>P", NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
662 {"Edit/Advanced/NextLine", NULL
, N_("Move to next line"), "<control>N", NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
663 {"Edit/Advanced/DelBackChar", NULL
, N_("Delete a character backward"), "<control>H", NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
664 {"Edit/Advanced/DelForwChar", NULL
, N_("Delete a character forward"), "<control>D", NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
665 {"Edit/Advanced/DelBackWord", NULL
, N_("Delete a word backward"), NULL
, NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
666 {"Edit/Advanced/DelForwWord", NULL
, N_("Delete a word forward"), NULL
, NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
667 {"Edit/Advanced/DelLine", NULL
, N_("Delete line"), "<control>U", NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
668 {"Edit/Advanced/DelEndLine", NULL
, N_("Delete to end of line"), "<control>K", NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
670 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
671 {"Edit/Find", NULL
, N_("_Find"), "<control>F", NULL
, G_CALLBACK(compose_find_cb
) },
673 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
674 {"Edit/WrapPara", NULL
, N_("_Wrap current paragraph"), "<control>L", NULL
, G_CALLBACK(compose_wrap_cb
) }, /* 0 */
675 {"Edit/WrapAllLines", NULL
, N_("Wrap all long _lines"), "<control><alt>L", NULL
, G_CALLBACK(compose_wrap_all_cb
) }, /* 1 */
676 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
677 {"Edit/ExtEditor", NULL
, N_("Edit with e_xternal editor"), "<shift><control>X", NULL
, G_CALLBACK(compose_ext_editor_cb
) },
680 {"Spelling/CheckAllSel", NULL
, N_("_Check all or check selection"), NULL
, NULL
, G_CALLBACK(compose_check_all
) },
681 {"Spelling/HighlightAll", NULL
, N_("_Highlight all misspelled words"), NULL
, NULL
, G_CALLBACK(compose_highlight_all
) },
682 {"Spelling/CheckBackwards", NULL
, N_("Check _backwards misspelled word"), NULL
, NULL
, G_CALLBACK(compose_check_backwards
) },
683 {"Spelling/ForwardNext", NULL
, N_("_Forward to next misspelled word"), NULL
, NULL
, G_CALLBACK(compose_check_forwards_go
) },
685 {"Spelling/---", NULL
, "---", NULL
, NULL
, NULL
},
686 {"Spelling/Options", NULL
, N_("_Options"), NULL
, NULL
, NULL
},
690 {"Options/ReplyMode", NULL
, N_("Reply _mode"), NULL
, NULL
, NULL
},
691 {"Options/---", NULL
, "---", NULL
, NULL
, NULL
},
692 {"Options/PrivacySystem", NULL
, N_("Privacy _System"), NULL
, NULL
, NULL
},
693 {"Options/PrivacySystem/PlaceHolder", NULL
, "Placeholder", NULL
, NULL
, G_CALLBACK(compose_nothing_cb
) },
695 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
696 {"Options/Priority", NULL
, N_("_Priority"), NULL
, NULL
, NULL
},
698 {"Options/Encoding", NULL
, N_("Character _encoding"), NULL
, NULL
, NULL
},
699 {"Options/Encoding/---", NULL
, "---", NULL
, NULL
, NULL
},
700 #define ENC_ACTION(cs_char,c_char,string) \
701 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
703 {"Options/Encoding/Western", NULL
, N_("Western European"), NULL
, NULL
, NULL
},
704 {"Options/Encoding/Baltic", NULL
, N_("Baltic"), NULL
, NULL
, NULL
},
705 {"Options/Encoding/Hebrew", NULL
, N_("Hebrew"), NULL
, NULL
, NULL
},
706 {"Options/Encoding/Arabic", NULL
, N_("Arabic"), NULL
, NULL
, NULL
},
707 {"Options/Encoding/Cyrillic", NULL
, N_("Cyrillic"), NULL
, NULL
, NULL
},
708 {"Options/Encoding/Japanese", NULL
, N_("Japanese"), NULL
, NULL
, NULL
},
709 {"Options/Encoding/Chinese", NULL
, N_("Chinese"), NULL
, NULL
, NULL
},
710 {"Options/Encoding/Korean", NULL
, N_("Korean"), NULL
, NULL
, NULL
},
711 {"Options/Encoding/Thai", NULL
, N_("Thai"), NULL
, NULL
, NULL
},
714 {"Tools/AddressBook", NULL
, N_("_Address book"), "<shift><control>A", NULL
, G_CALLBACK(compose_address_cb
) },
716 {"Tools/Template", NULL
, N_("_Template"), NULL
, NULL
, NULL
},
717 {"Tools/Template/PlaceHolder", NULL
, "Placeholder", NULL
, NULL
, G_CALLBACK(compose_nothing_cb
) },
718 {"Tools/Actions", NULL
, N_("Actio_ns"), NULL
, NULL
, NULL
},
719 {"Tools/Actions/PlaceHolder", NULL
, "Placeholder", NULL
, NULL
, G_CALLBACK(compose_nothing_cb
) },
722 {"Help/About", NULL
, N_("_About"), NULL
, NULL
, G_CALLBACK(about_show_cb
) },
725 static GtkToggleActionEntry compose_toggle_entries
[] =
727 {"Edit/AutoWrap", NULL
, N_("Aut_o wrapping"), "<shift><control>L", NULL
, G_CALLBACK(compose_toggle_autowrap_cb
), FALSE
}, /* Toggle */
728 {"Edit/AutoIndent", NULL
, N_("Auto _indent"), NULL
, NULL
, G_CALLBACK(compose_toggle_autoindent_cb
), FALSE
}, /* Toggle */
729 {"Options/Sign", NULL
, N_("Si_gn"), NULL
, NULL
, G_CALLBACK(compose_toggle_sign_cb
), FALSE
}, /* Toggle */
730 {"Options/Encrypt", NULL
, N_("_Encrypt"), NULL
, NULL
, G_CALLBACK(compose_toggle_encrypt_cb
), FALSE
}, /* Toggle */
731 {"Options/RequestRetRcpt", NULL
, N_("_Request Return Receipt"), NULL
, NULL
, G_CALLBACK(compose_toggle_return_receipt_cb
), FALSE
}, /* Toggle */
732 {"Options/RemoveReferences", NULL
, N_("Remo_ve references"), NULL
, NULL
, G_CALLBACK(compose_toggle_remove_refs_cb
), FALSE
}, /* Toggle */
733 {"Tools/ShowRuler", NULL
, N_("Show _ruler"), NULL
, NULL
, G_CALLBACK(compose_toggle_ruler_cb
), FALSE
}, /* Toggle */
736 static GtkRadioActionEntry compose_radio_rm_entries
[] =
738 {"Options/ReplyMode/Normal", NULL
, N_("_Normal"), NULL
, NULL
, COMPOSE_REPLY
}, /* RADIO compose_reply_change_mode_cb */
739 {"Options/ReplyMode/All", NULL
, N_("_All"), NULL
, NULL
, COMPOSE_REPLY_TO_ALL
}, /* RADIO compose_reply_change_mode_cb */
740 {"Options/ReplyMode/Sender", NULL
, N_("_Sender"), NULL
, NULL
, COMPOSE_REPLY_TO_SENDER
}, /* RADIO compose_reply_change_mode_cb */
741 {"Options/ReplyMode/List", NULL
, N_("_Mailing-list"), NULL
, NULL
, COMPOSE_REPLY_TO_LIST
}, /* RADIO compose_reply_change_mode_cb */
744 static GtkRadioActionEntry compose_radio_prio_entries
[] =
746 {"Options/Priority/Highest", NULL
, N_("_Highest"), NULL
, NULL
, PRIORITY_HIGHEST
}, /* RADIO compose_set_priority_cb */
747 {"Options/Priority/High", NULL
, N_("Hi_gh"), NULL
, NULL
, PRIORITY_HIGH
}, /* RADIO compose_set_priority_cb */
748 {"Options/Priority/Normal", NULL
, N_("_Normal"), NULL
, NULL
, PRIORITY_NORMAL
}, /* RADIO compose_set_priority_cb */
749 {"Options/Priority/Low", NULL
, N_("Lo_w"), NULL
, NULL
, PRIORITY_LOW
}, /* RADIO compose_set_priority_cb */
750 {"Options/Priority/Lowest", NULL
, N_("_Lowest"), NULL
, NULL
, PRIORITY_LOWEST
}, /* RADIO compose_set_priority_cb */
753 static GtkRadioActionEntry compose_radio_enc_entries
[] =
755 ENC_ACTION(CS_AUTO
, C_AUTO
, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION(CS_US_ASCII
, C_US_ASCII
, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION(CS_UTF_8
, C_UTF_8
, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Western/"CS_ISO_8859_1
, C_ISO_8859_1
, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Western/"CS_ISO_8859_15
, C_ISO_8859_15
, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Western/"CS_WINDOWS_1252
, C_WINDOWS_1252
, "Windows-1252"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION(CS_ISO_8859_2
, C_ISO_8859_2
, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Baltic/"CS_ISO_8859_13
, C_ISO_8859_13
, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Baltic/"CS_ISO_8859_4
, C_ISO_8859_14
, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION(CS_ISO_8859_7
, C_ISO_8859_7
, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Hebrew/"CS_ISO_8859_8
, C_ISO_8859_8
, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Hebrew/"CS_WINDOWS_1255
, C_WINDOWS_1255
, "Windows-1255"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Arabic/"CS_ISO_8859_6
, C_ISO_8859_6
, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Arabic/"CS_WINDOWS_1256
, C_WINDOWS_1256
, "Windows-1256"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION(CS_ISO_8859_9
, C_ISO_8859_9
, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Cyrillic/"CS_ISO_8859_5
, C_ISO_8859_5
, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Cyrillic/"CS_KOI8_R
, C_KOI8_R
, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Cyrillic/"CS_MACCYR
, C_MACCYR
, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Cyrillic/"CS_KOI8_U
, C_KOI8_U
, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251
, C_WINDOWS_1251
, "Windows-1251"), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Japanese/"CS_ISO_2022_JP
, C_ISO_2022_JP
, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2
, C_ISO_2022_JP_2
, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
777 ENC_ACTION("Japanese/"CS_EUC_JP
, C_EUC_JP
, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
778 ENC_ACTION("Japanese/"CS_SHIFT_JIS
, C_SHIFT_JIS
, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
779 ENC_ACTION("Chinese/"CS_GB18030
, C_GB18030
, "_GB18030"), /* RADIO compose_set_encoding_cb */
780 ENC_ACTION("Chinese/"CS_GB2312
, C_GB2312
, "_GB2312"), /* RADIO compose_set_encoding_cb */
781 ENC_ACTION("Chinese/"CS_GBK
, C_GBK
, "GB_K"), /* RADIO compose_set_encoding_cb */
782 ENC_ACTION("Chinese/"CS_BIG5
, C_BIG5
, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
783 ENC_ACTION("Chinese/"CS_EUC_TW
, C_EUC_TW
, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
784 ENC_ACTION("Korean/"CS_EUC_KR
, C_EUC_KR
, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
785 ENC_ACTION("Korean/"CS_ISO_2022_KR
, C_ISO_2022_KR
, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
786 ENC_ACTION("Thai/"CS_TIS_620
, C_TIS_620
, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
787 ENC_ACTION("Thai/"CS_WINDOWS_874
, C_WINDOWS_874
, "_Windows-874"), /* RADIO compose_set_encoding_cb */
790 static GtkTargetEntry compose_mime_types
[] =
792 {"text/uri-list", 0, 0},
793 {"UTF8_STRING", 0, 0},
797 static gboolean
compose_put_existing_to_front(MsgInfo
*info
)
799 const GList
*compose_list
= compose_get_compose_list();
800 const GList
*elem
= NULL
;
803 for (elem
= compose_list
; elem
!= NULL
&& elem
->data
!= NULL
;
805 Compose
*c
= (Compose
*)elem
->data
;
807 if (!c
->targetinfo
|| !c
->targetinfo
->msgid
||
811 if (!strcmp(c
->targetinfo
->msgid
, info
->msgid
)) {
812 gtkut_window_popup(c
->window
);
820 static GdkRGBA quote_color1
=
822 static GdkRGBA quote_color2
=
824 static GdkRGBA quote_color3
=
827 static GdkRGBA quote_bgcolor1
=
829 static GdkRGBA quote_bgcolor2
=
831 static GdkRGBA quote_bgcolor3
=
834 static GdkRGBA signature_color
=
837 static GdkRGBA uri_color
=
840 static void compose_create_tags(GtkTextView
*text
, Compose
*compose
)
842 GtkTextBuffer
*buffer
;
843 GdkRGBA black
= { 0, 0, 0, 1 };
845 buffer
= gtk_text_view_get_buffer(text
);
847 if (prefs_common
.enable_color
) {
848 /* grab the quote colors, converting from an int to a GdkColor */
849 quote_color1
= prefs_common
.color
[COL_QUOTE_LEVEL1
];
850 quote_color2
= prefs_common
.color
[COL_QUOTE_LEVEL2
];
851 quote_color3
= prefs_common
.color
[COL_QUOTE_LEVEL3
];
852 quote_bgcolor1
= prefs_common
.color
[COL_QUOTE_LEVEL1_BG
];
853 quote_bgcolor2
= prefs_common
.color
[COL_QUOTE_LEVEL2_BG
];
854 quote_bgcolor3
= prefs_common
.color
[COL_QUOTE_LEVEL3_BG
];
855 signature_color
= prefs_common
.color
[COL_SIGNATURE
];
856 uri_color
= prefs_common
.color
[COL_URI
];
858 signature_color
= quote_color1
= quote_color2
= quote_color3
=
859 quote_bgcolor1
= quote_bgcolor2
= quote_bgcolor3
= uri_color
= black
;
862 if (prefs_common
.enable_color
&& prefs_common
.enable_bgcolor
) {
863 compose
->quote0_tag
= gtk_text_buffer_create_tag(buffer
, "quote0",
864 "foreground-rgba", "e_color1
,
865 "paragraph-background-rgba", "e_bgcolor1
,
867 compose
->quote1_tag
= gtk_text_buffer_create_tag(buffer
, "quote1",
868 "foreground-rgba", "e_color2
,
869 "paragraph-background-rgba", "e_bgcolor2
,
871 compose
->quote2_tag
= gtk_text_buffer_create_tag(buffer
, "quote2",
872 "foreground-rgba", "e_color3
,
873 "paragraph-background-rgba", "e_bgcolor3
,
876 compose
->quote0_tag
= gtk_text_buffer_create_tag(buffer
, "quote0",
877 "foreground-rgba", "e_color1
,
879 compose
->quote1_tag
= gtk_text_buffer_create_tag(buffer
, "quote1",
880 "foreground-rgba", "e_color2
,
882 compose
->quote2_tag
= gtk_text_buffer_create_tag(buffer
, "quote2",
883 "foreground-rgba", "e_color3
,
887 compose
->signature_tag
= gtk_text_buffer_create_tag(buffer
, "signature",
888 "foreground-rgba", &signature_color
,
891 compose
->uri_tag
= gtk_text_buffer_create_tag(buffer
, "link",
892 "foreground-rgba", &uri_color
,
894 compose
->no_wrap_tag
= gtk_text_buffer_create_tag(buffer
, "no_wrap", NULL
);
895 compose
->no_join_tag
= gtk_text_buffer_create_tag(buffer
, "no_join", NULL
);
898 Compose
*compose_new(PrefsAccount
*account
, const gchar
*mailto
,
901 return compose_generic_new(account
, mailto
, NULL
, attach_files
, NULL
);
904 Compose
*compose_new_with_folderitem(PrefsAccount
*account
, FolderItem
*item
, const gchar
*mailto
)
906 return compose_generic_new(account
, mailto
, item
, NULL
, NULL
);
909 Compose
*compose_new_with_list( PrefsAccount
*account
, GList
*listAddress
)
911 return compose_generic_new( account
, NULL
, NULL
, NULL
, listAddress
);
914 #define SCROLL_TO_CURSOR(compose) { \
915 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
916 gtk_text_view_get_buffer( \
917 GTK_TEXT_VIEW(compose->text))); \
918 gtk_text_view_scroll_mark_onscreen( \
919 GTK_TEXT_VIEW(compose->text), \
923 static void compose_set_save_to(Compose
*compose
, const gchar
*folderidentifier
)
926 if (folderidentifier
) {
927 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose
->savemsg_combo
));
928 prefs_common
.compose_save_to_history
= add_history(
929 prefs_common
.compose_save_to_history
, folderidentifier
);
930 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose
->savemsg_combo
),
931 prefs_common
.compose_save_to_history
);
934 entry
= GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose
->savemsg_combo
)));
935 if (folderidentifier
)
936 gtk_entry_set_text(GTK_ENTRY(entry
), folderidentifier
);
938 gtk_entry_set_text(GTK_ENTRY(entry
), "");
941 static gchar
*compose_get_save_to(Compose
*compose
)
944 gchar
*result
= NULL
;
945 entry
= GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose
->savemsg_combo
)));
946 result
= gtk_editable_get_chars(entry
, 0, -1);
949 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose
->savemsg_combo
));
950 prefs_common
.compose_save_to_history
= add_history(
951 prefs_common
.compose_save_to_history
, result
);
952 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose
->savemsg_combo
),
953 prefs_common
.compose_save_to_history
);
958 Compose
*compose_generic_new(PrefsAccount
*account
, const gchar
*mailto
, FolderItem
*item
,
959 GList
*attach_files
, GList
*listAddress
)
962 GtkTextView
*textview
;
963 GtkTextBuffer
*textbuf
;
965 const gchar
*subject_format
= NULL
;
966 const gchar
*body_format
= NULL
;
967 gchar
*mailto_from
= NULL
;
968 PrefsAccount
*mailto_account
= NULL
;
969 MsgInfo
* dummyinfo
= NULL
;
970 gint cursor_pos
= -1;
971 MailField mfield
= NO_FIELD_PRESENT
;
975 /* check if mailto defines a from */
976 if (mailto
&& *mailto
!= '\0') {
977 scan_mailto_url(mailto
, &mailto_from
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
);
978 /* mailto defines a from, check if we can get account prefs from it,
979 if not, the account prefs will be guessed using other ways, but we'll keep
982 mailto_account
= account_find_from_address(mailto_from
, TRUE
);
983 if (mailto_account
== NULL
) {
985 Xstrdup_a(tmp_from
, mailto_from
, return NULL
);
986 extract_address(tmp_from
);
987 mailto_account
= account_find_from_address(tmp_from
, TRUE
);
991 account
= mailto_account
;
994 /* if no account prefs set from mailto, set if from folder prefs (if any) */
995 if (!mailto_account
&& item
&& item
->prefs
&& item
->prefs
->enable_default_account
)
996 account
= account_find_from_id(item
->prefs
->default_account
);
998 /* if no account prefs set, fallback to the current one */
999 if (!account
) account
= cur_account
;
1000 cm_return_val_if_fail(account
!= NULL
, NULL
);
1002 compose
= compose_create(account
, item
, COMPOSE_NEW
, FALSE
);
1003 compose_apply_folder_privacy_settings(compose
, item
);
1005 if (privacy_system_can_sign(compose
->privacy_system
) == FALSE
&&
1006 (account
->default_encrypt
|| account
->default_sign
))
1007 COMPOSE_PRIVACY_WARNING();
1009 /* override from name if mailto asked for it */
1011 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), mailto_from
);
1012 g_free(mailto_from
);
1014 /* override from name according to folder properties */
1015 if (item
&& item
->prefs
&&
1016 item
->prefs
->compose_with_format
&&
1017 item
->prefs
->compose_override_from_format
&&
1018 *item
->prefs
->compose_override_from_format
!= '\0') {
1023 dummyinfo
= compose_msginfo_new_from_compose(compose
);
1025 /* decode \-escape sequences in the internal representation of the quote format */
1026 tmp
= g_malloc(strlen(item
->prefs
->compose_override_from_format
)+1);
1027 pref_get_unescaped_pref(tmp
, item
->prefs
->compose_override_from_format
);
1030 quote_fmt_init(dummyinfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
1031 compose
->gtkaspell
);
1033 quote_fmt_init(dummyinfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
1035 quote_fmt_scan_string(tmp
);
1038 buf
= quote_fmt_get_buffer();
1040 alertpanel_error(_("New message From format error."));
1042 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), buf
);
1043 quote_fmt_reset_vartable();
1044 quote_fmtlex_destroy();
1049 compose
->replyinfo
= NULL
;
1050 compose
->fwdinfo
= NULL
;
1052 textview
= GTK_TEXT_VIEW(compose
->text
);
1053 textbuf
= gtk_text_view_get_buffer(textview
);
1054 compose_create_tags(textview
, compose
);
1056 undo_block(compose
->undostruct
);
1058 compose_set_dictionaries_from_folder_prefs(compose
, item
);
1061 if (account
->auto_sig
)
1062 compose_insert_sig(compose
, FALSE
);
1063 gtk_text_buffer_get_start_iter(textbuf
, &iter
);
1064 gtk_text_buffer_place_cursor(textbuf
, &iter
);
1066 if (account
->protocol
!= A_NNTP
) {
1067 if (mailto
&& *mailto
!= '\0') {
1068 mfield
= compose_entries_set(compose
, mailto
, COMPOSE_TO
);
1071 compose_set_folder_prefs(compose
, item
, TRUE
);
1073 if (item
&& item
->ret_rcpt
) {
1074 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/RequestRetRcpt", TRUE
);
1077 if (mailto
&& *mailto
!= '\0') {
1078 if (!strchr(mailto
, '@'))
1079 mfield
= compose_entries_set(compose
, mailto
, COMPOSE_NEWSGROUPS
);
1081 mfield
= compose_entries_set(compose
, mailto
, COMPOSE_TO
);
1082 } else if (item
&& FOLDER_CLASS(item
->folder
) == news_get_class()) {
1083 compose_entry_append(compose
, item
->path
, COMPOSE_NEWSGROUPS
, PREF_FOLDER
);
1084 mfield
= TO_FIELD_PRESENT
;
1087 * CLAWS: just don't allow return receipt request, even if the user
1088 * may want to send an email. simple but foolproof.
1090 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/RequestRetRcpt", FALSE
);
1092 compose_add_field_list( compose
, listAddress
);
1094 if (item
&& item
->prefs
&& item
->prefs
->compose_with_format
) {
1095 subject_format
= item
->prefs
->compose_subject_format
;
1096 body_format
= item
->prefs
->compose_body_format
;
1097 } else if (account
->compose_with_format
) {
1098 subject_format
= account
->compose_subject_format
;
1099 body_format
= account
->compose_body_format
;
1100 } else if (prefs_common
.compose_with_format
) {
1101 subject_format
= prefs_common
.compose_subject_format
;
1102 body_format
= prefs_common
.compose_body_format
;
1105 if (subject_format
|| body_format
) {
1108 && *subject_format
!= '\0' )
1110 gchar
*subject
= NULL
;
1115 dummyinfo
= compose_msginfo_new_from_compose(compose
);
1117 /* decode \-escape sequences in the internal representation of the quote format */
1118 tmp
= g_malloc(strlen(subject_format
)+1);
1119 pref_get_unescaped_pref(tmp
, subject_format
);
1121 subject
= gtk_editable_get_chars(GTK_EDITABLE(compose
->subject_entry
), 0, -1);
1123 quote_fmt_init(dummyinfo
, NULL
, subject
, FALSE
, compose
->account
, FALSE
,
1124 compose
->gtkaspell
);
1126 quote_fmt_init(dummyinfo
, NULL
, subject
, FALSE
, compose
->account
, FALSE
);
1128 quote_fmt_scan_string(tmp
);
1131 buf
= quote_fmt_get_buffer();
1133 alertpanel_error(_("New message subject format error."));
1135 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), buf
);
1136 compose_attach_from_list(compose
, quote_fmt_get_attachments_list(), FALSE
);
1137 quote_fmt_reset_vartable();
1138 quote_fmtlex_destroy();
1142 mfield
= SUBJECT_FIELD_PRESENT
;
1146 && *body_format
!= '\0' )
1149 GtkTextBuffer
*buffer
;
1150 GtkTextIter start
, end
;
1154 dummyinfo
= compose_msginfo_new_from_compose(compose
);
1156 text
= GTK_TEXT_VIEW(compose
->text
);
1157 buffer
= gtk_text_view_get_buffer(text
);
1158 gtk_text_buffer_get_start_iter(buffer
, &start
);
1159 gtk_text_buffer_get_iter_at_offset(buffer
, &end
, -1);
1160 tmp
= gtk_text_buffer_get_text(buffer
, &start
, &end
, FALSE
);
1162 compose_quote_fmt(compose
, dummyinfo
,
1164 NULL
, tmp
, FALSE
, TRUE
,
1165 _("The body of the \"New message\" template has an error at line %d."));
1166 compose_attach_from_list(compose
, quote_fmt_get_attachments_list(), FALSE
);
1167 quote_fmt_reset_vartable();
1171 if (compose
->gtkaspell
&& compose
->gtkaspell
->check_while_typing
)
1172 gtkaspell_highlight_all(compose
->gtkaspell
);
1174 mfield
= BODY_FIELD_PRESENT
;
1178 procmsg_msginfo_free( &dummyinfo
);
1184 for (curr
= attach_files
; curr
!= NULL
; curr
= curr
->next
) {
1185 ainfo
= (AttachInfo
*) curr
->data
;
1187 compose_insert_file(compose
, ainfo
->file
);
1189 compose_attach_append(compose
, ainfo
->file
, ainfo
->file
,
1190 ainfo
->content_type
, ainfo
->charset
);
1194 compose_show_first_last_header(compose
, TRUE
);
1196 /* Set save folder */
1197 if (item
&& item
->prefs
&& item
->prefs
->save_copy_to_folder
) {
1198 gchar
*folderidentifier
;
1200 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
), TRUE
);
1201 gtk_widget_set_sensitive(GTK_WIDGET(compose
->savemsg_combo
), TRUE
);
1202 folderidentifier
= folder_item_get_identifier(item
);
1203 compose_set_save_to(compose
, folderidentifier
);
1204 g_free(folderidentifier
);
1207 /* Place cursor according to provided input (mfield) */
1209 case NO_FIELD_PRESENT
:
1210 if (compose
->header_last
)
1211 gtk_widget_grab_focus(compose
->header_last
->entry
);
1213 case TO_FIELD_PRESENT
:
1214 buf
= gtk_editable_get_chars(GTK_EDITABLE(compose
->subject_entry
), 0, -1);
1216 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), buf
);
1219 gtk_widget_grab_focus(compose
->subject_entry
);
1221 case SUBJECT_FIELD_PRESENT
:
1222 textview
= GTK_TEXT_VIEW(compose
->text
);
1225 textbuf
= gtk_text_view_get_buffer(textview
);
1228 mark
= gtk_text_buffer_get_insert(textbuf
);
1229 gtk_text_buffer_get_iter_at_mark(textbuf
, &iter
, mark
);
1230 gtk_text_buffer_insert(textbuf
, &iter
, "", -1);
1232 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1233 * only defers where it comes to the variable body
1234 * is not null. If no body is present compose->text
1235 * will be null in which case you cannot place the
1236 * cursor inside the component so. An empty component
1237 * is therefore created before placing the cursor
1239 case BODY_FIELD_PRESENT
:
1240 cursor_pos
= quote_fmt_get_cursor_pos();
1241 if (cursor_pos
== -1)
1242 gtk_widget_grab_focus(compose
->header_last
->entry
);
1244 gtk_widget_grab_focus(compose
->text
);
1248 undo_unblock(compose
->undostruct
);
1250 if (prefs_common
.auto_exteditor
)
1251 compose_exec_ext_editor(compose
);
1253 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_UNSET
;
1255 SCROLL_TO_CURSOR(compose
);
1257 compose
->modified
= FALSE
;
1258 compose_set_title(compose
);
1260 hooks_invoke(COMPOSE_CREATED_HOOKLIST
, compose
);
1265 static void compose_force_encryption(Compose
*compose
, PrefsAccount
*account
,
1266 gboolean override_pref
, const gchar
*system
)
1268 const gchar
*privacy
= NULL
;
1270 cm_return_if_fail(compose
!= NULL
);
1271 cm_return_if_fail(account
!= NULL
);
1273 if (privacy_system_can_encrypt(compose
->privacy_system
) == FALSE
||
1274 (override_pref
== FALSE
&& account
->default_encrypt_reply
== FALSE
))
1277 if (account
->default_privacy_system
&& strlen(account
->default_privacy_system
))
1278 privacy
= account
->default_privacy_system
;
1282 GSList
*privacy_avail
= privacy_get_system_ids();
1283 if (privacy_avail
&& g_slist_length(privacy_avail
)) {
1284 privacy
= (gchar
*)(privacy_avail
->data
);
1286 g_slist_free_full(privacy_avail
, g_free
);
1288 if (privacy
!= NULL
) {
1290 g_free(compose
->privacy_system
);
1291 compose
->privacy_system
= NULL
;
1292 g_free(compose
->encdata
);
1293 compose
->encdata
= NULL
;
1295 if (compose
->privacy_system
== NULL
)
1296 compose
->privacy_system
= g_strdup(privacy
);
1297 else if (*(compose
->privacy_system
) == '\0') {
1298 g_free(compose
->privacy_system
);
1299 g_free(compose
->encdata
);
1300 compose
->encdata
= NULL
;
1301 compose
->privacy_system
= g_strdup(privacy
);
1303 compose_update_privacy_system_menu_item(compose
, FALSE
);
1304 compose_use_encryption(compose
, TRUE
);
1308 static void compose_force_signing(Compose
*compose
, PrefsAccount
*account
, const gchar
*system
)
1310 const gchar
*privacy
= NULL
;
1311 if (privacy_system_can_sign(compose
->privacy_system
) == FALSE
)
1314 if (account
->default_privacy_system
&& strlen(account
->default_privacy_system
))
1315 privacy
= account
->default_privacy_system
;
1319 GSList
*privacy_avail
= privacy_get_system_ids();
1320 if (privacy_avail
&& g_slist_length(privacy_avail
)) {
1321 privacy
= (gchar
*)(privacy_avail
->data
);
1325 if (privacy
!= NULL
) {
1327 g_free(compose
->privacy_system
);
1328 compose
->privacy_system
= NULL
;
1329 g_free(compose
->encdata
);
1330 compose
->encdata
= NULL
;
1332 if (compose
->privacy_system
== NULL
)
1333 compose
->privacy_system
= g_strdup(privacy
);
1334 compose_update_privacy_system_menu_item(compose
, FALSE
);
1335 compose_use_signing(compose
, TRUE
);
1339 static Compose
*compose_reply_mode(ComposeMode mode
, GSList
*msginfo_list
, gchar
*body
)
1343 Compose
*compose
= NULL
;
1345 cm_return_val_if_fail(msginfo_list
!= NULL
, NULL
);
1347 msginfo
= (MsgInfo
*)g_slist_nth_data(msginfo_list
, 0);
1348 cm_return_val_if_fail(msginfo
!= NULL
, NULL
);
1350 list_len
= g_slist_length(msginfo_list
);
1354 case COMPOSE_REPLY_TO_ADDRESS
:
1355 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_CHECK
,
1356 FALSE
, prefs_common
.default_reply_list
, FALSE
, body
);
1358 case COMPOSE_REPLY_WITH_QUOTE
:
1359 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_FORCED
,
1360 FALSE
, prefs_common
.default_reply_list
, FALSE
, body
);
1362 case COMPOSE_REPLY_WITHOUT_QUOTE
:
1363 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_SKIP
,
1364 FALSE
, prefs_common
.default_reply_list
, FALSE
, NULL
);
1366 case COMPOSE_REPLY_TO_SENDER
:
1367 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_CHECK
,
1368 FALSE
, FALSE
, TRUE
, body
);
1370 case COMPOSE_FOLLOWUP_AND_REPLY_TO
:
1371 compose
= compose_followup_and_reply_to(msginfo
,
1372 COMPOSE_QUOTE_CHECK
,
1373 FALSE
, FALSE
, body
);
1375 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE
:
1376 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_FORCED
,
1377 FALSE
, FALSE
, TRUE
, body
);
1379 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE
:
1380 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_SKIP
,
1381 FALSE
, FALSE
, TRUE
, NULL
);
1383 case COMPOSE_REPLY_TO_ALL
:
1384 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_CHECK
,
1385 TRUE
, FALSE
, FALSE
, body
);
1387 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE
:
1388 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_FORCED
,
1389 TRUE
, FALSE
, FALSE
, body
);
1391 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE
:
1392 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_SKIP
,
1393 TRUE
, FALSE
, FALSE
, NULL
);
1395 case COMPOSE_REPLY_TO_LIST
:
1396 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_CHECK
,
1397 FALSE
, TRUE
, FALSE
, body
);
1399 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE
:
1400 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_FORCED
,
1401 FALSE
, TRUE
, FALSE
, body
);
1403 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE
:
1404 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_SKIP
,
1405 FALSE
, TRUE
, FALSE
, NULL
);
1407 case COMPOSE_FORWARD
:
1408 if (prefs_common
.forward_as_attachment
) {
1409 compose
= compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH
, msginfo_list
, body
);
1412 compose
= compose_reply_mode(COMPOSE_FORWARD_INLINE
, msginfo_list
, body
);
1416 case COMPOSE_FORWARD_INLINE
:
1417 /* check if we reply to more than one Message */
1418 if (list_len
== 1) {
1419 compose
= compose_forward(NULL
, msginfo
, FALSE
, body
, FALSE
, FALSE
);
1422 /* more messages FALL THROUGH */
1423 case COMPOSE_FORWARD_AS_ATTACH
:
1424 compose
= compose_forward_multiple(NULL
, msginfo_list
);
1426 case COMPOSE_REDIRECT
:
1427 compose
= compose_redirect(NULL
, msginfo
, FALSE
);
1430 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode
);
1433 if (compose
== NULL
) {
1434 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1438 compose
->rmode
= mode
;
1439 switch (compose
->rmode
) {
1441 case COMPOSE_REPLY_WITH_QUOTE
:
1442 case COMPOSE_REPLY_WITHOUT_QUOTE
:
1443 case COMPOSE_FOLLOWUP_AND_REPLY_TO
:
1444 debug_print("reply mode Normal\n");
1445 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/ReplyMode/Normal", TRUE
);
1446 compose_reply_change_mode(compose
, COMPOSE_REPLY
); /* force update */
1448 case COMPOSE_REPLY_TO_SENDER
:
1449 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE
:
1450 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE
:
1451 debug_print("reply mode Sender\n");
1452 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/ReplyMode/Sender", TRUE
);
1454 case COMPOSE_REPLY_TO_ALL
:
1455 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE
:
1456 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE
:
1457 debug_print("reply mode All\n");
1458 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/ReplyMode/All", TRUE
);
1460 case COMPOSE_REPLY_TO_LIST
:
1461 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE
:
1462 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE
:
1463 debug_print("reply mode List\n");
1464 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/ReplyMode/List", TRUE
);
1466 case COMPOSE_REPLY_TO_ADDRESS
:
1467 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/ReplyMode", FALSE
);
1475 static Compose
*compose_reply(MsgInfo
*msginfo
,
1476 ComposeQuoteMode quote_mode
,
1482 return compose_generic_reply(msginfo
, quote_mode
, to_all
, to_ml
,
1483 to_sender
, FALSE
, body
);
1486 static Compose
*compose_followup_and_reply_to(MsgInfo
*msginfo
,
1487 ComposeQuoteMode quote_mode
,
1492 return compose_generic_reply(msginfo
, quote_mode
, to_all
, FALSE
,
1493 to_sender
, TRUE
, body
);
1496 static void compose_extract_original_charset(Compose
*compose
)
1498 MsgInfo
*info
= NULL
;
1499 if (compose
->replyinfo
) {
1500 info
= compose
->replyinfo
;
1501 } else if (compose
->fwdinfo
) {
1502 info
= compose
->fwdinfo
;
1503 } else if (compose
->targetinfo
) {
1504 info
= compose
->targetinfo
;
1507 MimeInfo
*mimeinfo
= procmime_scan_message_short(info
);
1508 MimeInfo
*partinfo
= mimeinfo
;
1509 while (partinfo
&& partinfo
->type
!= MIMETYPE_TEXT
)
1510 partinfo
= procmime_mimeinfo_next(partinfo
);
1512 compose
->orig_charset
=
1513 g_strdup(procmime_mimeinfo_get_parameter(
1514 partinfo
, "charset"));
1516 procmime_mimeinfo_free_all(&mimeinfo
);
1520 #define SIGNAL_BLOCK(buffer) { \
1521 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1522 G_CALLBACK(compose_changed_cb), \
1524 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1525 G_CALLBACK(text_inserted), \
1529 #define SIGNAL_UNBLOCK(buffer) { \
1530 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1531 G_CALLBACK(compose_changed_cb), \
1533 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1534 G_CALLBACK(text_inserted), \
1538 static Compose
*compose_generic_reply(MsgInfo
*msginfo
,
1539 ComposeQuoteMode quote_mode
,
1540 gboolean to_all
, gboolean to_ml
,
1542 gboolean followup_and_reply_to
,
1546 PrefsAccount
*account
= NULL
;
1547 GtkTextView
*textview
;
1548 GtkTextBuffer
*textbuf
;
1549 gboolean quote
= FALSE
;
1550 const gchar
*qmark
= NULL
;
1551 const gchar
*body_fmt
= NULL
;
1552 gchar
*s_system
= NULL
;
1554 cm_return_val_if_fail(msginfo
!= NULL
, NULL
);
1555 cm_return_val_if_fail(msginfo
->folder
!= NULL
, NULL
);
1557 account
= account_get_reply_account(msginfo
, prefs_common
.reply_account_autosel
);
1559 cm_return_val_if_fail(account
!= NULL
, NULL
);
1561 compose
= compose_create(account
, msginfo
->folder
, COMPOSE_REPLY
, FALSE
);
1562 compose_apply_folder_privacy_settings(compose
, msginfo
->folder
);
1564 compose
->updating
= TRUE
;
1566 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/RemoveReferences", FALSE
);
1567 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/RemoveReferences", TRUE
);
1569 compose
->replyinfo
= procmsg_msginfo_get_full_info(msginfo
);
1570 if (!compose
->replyinfo
)
1571 compose
->replyinfo
= procmsg_msginfo_copy(msginfo
);
1573 compose_extract_original_charset(compose
);
1575 if (msginfo
->folder
&& msginfo
->folder
->ret_rcpt
)
1576 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/RequestRetRcpt", TRUE
);
1578 /* Set save folder */
1579 if (msginfo
->folder
&& msginfo
->folder
->prefs
&& msginfo
->folder
->prefs
->save_copy_to_folder
) {
1580 gchar
*folderidentifier
;
1582 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
), TRUE
);
1583 gtk_widget_set_sensitive(GTK_WIDGET(compose
->savemsg_combo
), TRUE
);
1584 folderidentifier
= folder_item_get_identifier(msginfo
->folder
);
1585 compose_set_save_to(compose
, folderidentifier
);
1586 g_free(folderidentifier
);
1589 if (compose_parse_header(compose
, msginfo
) < 0) {
1590 compose
->updating
= FALSE
;
1591 compose_destroy(compose
);
1595 /* override from name according to folder properties */
1596 if (msginfo
->folder
&& msginfo
->folder
->prefs
&&
1597 msginfo
->folder
->prefs
->reply_with_format
&&
1598 msginfo
->folder
->prefs
->reply_override_from_format
&&
1599 *msginfo
->folder
->prefs
->reply_override_from_format
!= '\0') {
1604 /* decode \-escape sequences in the internal representation of the quote format */
1605 tmp
= g_malloc(strlen(msginfo
->folder
->prefs
->reply_override_from_format
)+1);
1606 pref_get_unescaped_pref(tmp
, msginfo
->folder
->prefs
->reply_override_from_format
);
1609 quote_fmt_init(compose
->replyinfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
1610 compose
->gtkaspell
);
1612 quote_fmt_init(compose
->replyinfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
1614 quote_fmt_scan_string(tmp
);
1617 buf
= quote_fmt_get_buffer();
1619 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1621 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), buf
);
1622 quote_fmt_reset_vartable();
1623 quote_fmtlex_destroy();
1628 textview
= (GTK_TEXT_VIEW(compose
->text
));
1629 textbuf
= gtk_text_view_get_buffer(textview
);
1630 compose_create_tags(textview
, compose
);
1632 undo_block(compose
->undostruct
);
1634 compose_set_dictionaries_from_folder_prefs(compose
, msginfo
->folder
);
1635 gtkaspell_block_check(compose
->gtkaspell
);
1638 if (quote_mode
== COMPOSE_QUOTE_FORCED
||
1639 (quote_mode
== COMPOSE_QUOTE_CHECK
&& prefs_common
.reply_with_quote
)) {
1640 /* use the reply format of folder (if enabled), or the account's one
1641 (if enabled) or fallback to the global reply format, which is always
1642 enabled (even if empty), and use the relevant quotemark */
1644 if (msginfo
->folder
&& msginfo
->folder
->prefs
&&
1645 msginfo
->folder
->prefs
->reply_with_format
) {
1646 qmark
= msginfo
->folder
->prefs
->reply_quotemark
;
1647 body_fmt
= msginfo
->folder
->prefs
->reply_body_format
;
1649 } else if (account
->reply_with_format
) {
1650 qmark
= account
->reply_quotemark
;
1651 body_fmt
= account
->reply_body_format
;
1654 qmark
= prefs_common
.quotemark
;
1655 if (prefs_common
.quotefmt
&& *prefs_common
.quotefmt
)
1656 body_fmt
= gettext(prefs_common
.quotefmt
);
1663 /* empty quotemark is not allowed */
1664 if (qmark
== NULL
|| *qmark
== '\0')
1666 compose_quote_fmt(compose
, compose
->replyinfo
,
1667 body_fmt
, qmark
, body
, FALSE
, TRUE
,
1668 _("The body of the \"Reply\" template has an error at line %d."));
1669 compose_attach_from_list(compose
, quote_fmt_get_attachments_list(), FALSE
);
1670 quote_fmt_reset_vartable();
1673 if (MSG_IS_ENCRYPTED(compose
->replyinfo
->flags
)) {
1674 compose_force_encryption(compose
, account
, FALSE
, s_system
);
1677 privacy_msginfo_get_signed_state(compose
->replyinfo
, &s_system
);
1678 if (MSG_IS_SIGNED(compose
->replyinfo
->flags
) && account
->default_sign_reply
) {
1679 compose_force_signing(compose
, account
, s_system
);
1683 if (privacy_system_can_sign(compose
->privacy_system
) == FALSE
&&
1684 ((account
->default_encrypt
|| account
->default_sign
) ||
1685 (account
->default_encrypt_reply
&& MSG_IS_ENCRYPTED(compose
->replyinfo
->flags
)) ||
1686 (account
->default_sign_reply
&& MSG_IS_SIGNED(compose
->replyinfo
->flags
))))
1687 COMPOSE_PRIVACY_WARNING();
1689 SIGNAL_BLOCK(textbuf
);
1691 if (account
->auto_sig
)
1692 compose_insert_sig(compose
, FALSE
);
1694 compose_wrap_all(compose
);
1697 if (compose
->gtkaspell
&& compose
->gtkaspell
->check_while_typing
)
1698 gtkaspell_highlight_all(compose
->gtkaspell
);
1699 gtkaspell_unblock_check(compose
->gtkaspell
);
1701 SIGNAL_UNBLOCK(textbuf
);
1703 gtk_widget_grab_focus(compose
->text
);
1705 undo_unblock(compose
->undostruct
);
1707 if (prefs_common
.auto_exteditor
)
1708 compose_exec_ext_editor(compose
);
1710 compose
->modified
= FALSE
;
1711 compose_set_title(compose
);
1713 compose
->updating
= FALSE
;
1714 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_UNSET
; /* desinhibit auto-drafting after loading */
1715 SCROLL_TO_CURSOR(compose
);
1717 if (compose
->deferred_destroy
) {
1718 compose_destroy(compose
);
1726 #define INSERT_FW_HEADER(var, hdr) \
1727 if (msginfo->var && *msginfo->var) { \
1728 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1729 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1730 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1733 Compose
*compose_forward(PrefsAccount
*account
, MsgInfo
*msginfo
,
1734 gboolean as_attach
, const gchar
*body
,
1735 gboolean no_extedit
,
1739 GtkTextView
*textview
;
1740 GtkTextBuffer
*textbuf
;
1741 gint cursor_pos
= -1;
1744 cm_return_val_if_fail(msginfo
!= NULL
, NULL
);
1745 cm_return_val_if_fail(msginfo
->folder
!= NULL
, NULL
);
1747 if (!account
&& !(account
= compose_find_account(msginfo
)))
1748 account
= cur_account
;
1750 if (!prefs_common
.forward_as_attachment
)
1751 mode
= COMPOSE_FORWARD_INLINE
;
1753 mode
= COMPOSE_FORWARD
;
1754 compose
= compose_create(account
, msginfo
->folder
, mode
, batch
);
1756 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/RemoveReferences", TRUE
);
1757 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/RemoveReferences", TRUE
);
1759 if (compose_parse_header(compose
, msginfo
) < 0) {
1760 compose
->updating
= FALSE
;
1761 compose_destroy(compose
);
1765 compose_apply_folder_privacy_settings(compose
, msginfo
->folder
);
1767 compose
->updating
= TRUE
;
1768 compose
->fwdinfo
= procmsg_msginfo_get_full_info(msginfo
);
1769 if (!compose
->fwdinfo
)
1770 compose
->fwdinfo
= procmsg_msginfo_copy(msginfo
);
1772 compose_extract_original_charset(compose
);
1774 if (msginfo
->subject
&& *msginfo
->subject
) {
1775 gchar
*buf
, *buf2
, *p
;
1777 buf
= p
= g_strdup(msginfo
->subject
);
1778 p
+= subject_get_prefix_length(p
);
1779 memmove(buf
, p
, strlen(p
) + 1);
1781 buf2
= g_strdup_printf("Fw: %s", buf
);
1782 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), buf2
);
1788 /* override from name according to folder properties */
1789 if (msginfo
->folder
&& msginfo
->folder
->prefs
&&
1790 msginfo
->folder
->prefs
->forward_with_format
&&
1791 msginfo
->folder
->prefs
->forward_override_from_format
&&
1792 *msginfo
->folder
->prefs
->forward_override_from_format
!= '\0') {
1796 MsgInfo
*full_msginfo
= NULL
;
1799 full_msginfo
= procmsg_msginfo_get_full_info(msginfo
);
1801 full_msginfo
= procmsg_msginfo_copy(msginfo
);
1803 /* decode \-escape sequences in the internal representation of the quote format */
1804 tmp
= g_malloc(strlen(msginfo
->folder
->prefs
->forward_override_from_format
)+1);
1805 pref_get_unescaped_pref(tmp
, msginfo
->folder
->prefs
->forward_override_from_format
);
1808 gtkaspell_block_check(compose
->gtkaspell
);
1809 quote_fmt_init(full_msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
1810 compose
->gtkaspell
);
1812 quote_fmt_init(full_msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
1814 quote_fmt_scan_string(tmp
);
1817 buf
= quote_fmt_get_buffer();
1819 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1821 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), buf
);
1822 quote_fmt_reset_vartable();
1823 quote_fmtlex_destroy();
1826 procmsg_msginfo_free(&full_msginfo
);
1829 textview
= GTK_TEXT_VIEW(compose
->text
);
1830 textbuf
= gtk_text_view_get_buffer(textview
);
1831 compose_create_tags(textview
, compose
);
1833 undo_block(compose
->undostruct
);
1837 msgfile
= procmsg_get_message_file(msginfo
);
1838 if (!is_file_exist(msgfile
))
1839 g_warning("%s: file does not exist", msgfile
);
1841 compose_attach_append(compose
, msgfile
, msgfile
,
1842 "message/rfc822", NULL
);
1846 const gchar
*qmark
= NULL
;
1847 const gchar
*body_fmt
= NULL
;
1848 MsgInfo
*full_msginfo
;
1850 full_msginfo
= procmsg_msginfo_get_full_info(msginfo
);
1852 full_msginfo
= procmsg_msginfo_copy(msginfo
);
1854 /* use the forward format of folder (if enabled), or the account's one
1855 (if enabled) or fallback to the global forward format, which is always
1856 enabled (even if empty), and use the relevant quotemark */
1857 if (msginfo
->folder
&& msginfo
->folder
->prefs
&&
1858 msginfo
->folder
->prefs
->forward_with_format
) {
1859 qmark
= msginfo
->folder
->prefs
->forward_quotemark
;
1860 body_fmt
= msginfo
->folder
->prefs
->forward_body_format
;
1862 } else if (account
->forward_with_format
) {
1863 qmark
= account
->forward_quotemark
;
1864 body_fmt
= account
->forward_body_format
;
1867 qmark
= prefs_common
.fw_quotemark
;
1868 if (prefs_common
.fw_quotefmt
&& *prefs_common
.fw_quotefmt
)
1869 body_fmt
= gettext(prefs_common
.fw_quotefmt
);
1874 /* empty quotemark is not allowed */
1875 if (qmark
== NULL
|| *qmark
== '\0')
1878 compose_quote_fmt(compose
, full_msginfo
,
1879 body_fmt
, qmark
, body
, FALSE
, TRUE
,
1880 _("The body of the \"Forward\" template has an error at line %d."));
1881 compose_attach_from_list(compose
, quote_fmt_get_attachments_list(), FALSE
);
1882 quote_fmt_reset_vartable();
1883 compose_attach_parts(compose
, msginfo
);
1885 procmsg_msginfo_free(&full_msginfo
);
1888 SIGNAL_BLOCK(textbuf
);
1890 if (account
->auto_sig
)
1891 compose_insert_sig(compose
, FALSE
);
1893 compose_wrap_all(compose
);
1896 if (compose
->gtkaspell
&& compose
->gtkaspell
->check_while_typing
)
1897 gtkaspell_highlight_all(compose
->gtkaspell
);
1898 gtkaspell_unblock_check(compose
->gtkaspell
);
1900 SIGNAL_UNBLOCK(textbuf
);
1902 if (privacy_system_can_sign(compose
->privacy_system
) == FALSE
&&
1903 (account
->default_encrypt
|| account
->default_sign
))
1904 COMPOSE_PRIVACY_WARNING();
1906 cursor_pos
= quote_fmt_get_cursor_pos();
1907 if (cursor_pos
== -1)
1908 gtk_widget_grab_focus(compose
->header_last
->entry
);
1910 gtk_widget_grab_focus(compose
->text
);
1912 if (!no_extedit
&& prefs_common
.auto_exteditor
)
1913 compose_exec_ext_editor(compose
);
1916 if (msginfo
->folder
&& msginfo
->folder
->prefs
&& msginfo
->folder
->prefs
->save_copy_to_folder
) {
1917 gchar
*folderidentifier
;
1919 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
), TRUE
);
1920 gtk_widget_set_sensitive(GTK_WIDGET(compose
->savemsg_combo
), TRUE
);
1921 folderidentifier
= folder_item_get_identifier(msginfo
->folder
);
1922 compose_set_save_to(compose
, folderidentifier
);
1923 g_free(folderidentifier
);
1926 undo_unblock(compose
->undostruct
);
1928 compose
->modified
= FALSE
;
1929 compose_set_title(compose
);
1931 compose
->updating
= FALSE
;
1932 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_UNSET
; /* desinhibit auto-drafting after loading */
1933 SCROLL_TO_CURSOR(compose
);
1935 if (compose
->deferred_destroy
) {
1936 compose_destroy(compose
);
1940 hooks_invoke(COMPOSE_CREATED_HOOKLIST
, compose
);
1945 #undef INSERT_FW_HEADER
1947 static Compose
*compose_forward_multiple(PrefsAccount
*account
, GSList
*msginfo_list
)
1950 GtkTextView
*textview
;
1951 GtkTextBuffer
*textbuf
;
1955 gboolean single_mail
= TRUE
;
1957 cm_return_val_if_fail(msginfo_list
!= NULL
, NULL
);
1959 if (g_slist_length(msginfo_list
) > 1)
1960 single_mail
= FALSE
;
1962 for (msginfo
= msginfo_list
; msginfo
!= NULL
; msginfo
= msginfo
->next
)
1963 if (((MsgInfo
*)msginfo
->data
)->folder
== NULL
)
1966 /* guess account from first selected message */
1968 !(account
= compose_find_account(msginfo_list
->data
)))
1969 account
= cur_account
;
1971 cm_return_val_if_fail(account
!= NULL
, NULL
);
1973 for (msginfo
= msginfo_list
; msginfo
!= NULL
; msginfo
= msginfo
->next
) {
1974 if (msginfo
->data
) {
1975 MSG_UNSET_PERM_FLAGS(((MsgInfo
*)msginfo
->data
)->flags
, MSG_REPLIED
);
1976 MSG_SET_PERM_FLAGS(((MsgInfo
*)msginfo
->data
)->flags
, MSG_FORWARDED
);
1980 if (msginfo_list
== NULL
|| msginfo_list
->data
== NULL
) {
1981 g_warning("no msginfo_list");
1985 compose
= compose_create(account
, ((MsgInfo
*)msginfo_list
->data
)->folder
, COMPOSE_FORWARD
, FALSE
);
1986 compose_apply_folder_privacy_settings(compose
, ((MsgInfo
*)msginfo_list
->data
)->folder
);
1987 if (privacy_system_can_sign(compose
->privacy_system
) == FALSE
&&
1988 (account
->default_encrypt
|| account
->default_sign
))
1989 COMPOSE_PRIVACY_WARNING();
1991 compose
->updating
= TRUE
;
1993 /* override from name according to folder properties */
1994 if (msginfo_list
->data
) {
1995 MsgInfo
*msginfo
= msginfo_list
->data
;
1997 if (msginfo
->folder
&& msginfo
->folder
->prefs
&&
1998 msginfo
->folder
->prefs
->forward_with_format
&&
1999 msginfo
->folder
->prefs
->forward_override_from_format
&&
2000 *msginfo
->folder
->prefs
->forward_override_from_format
!= '\0') {
2005 /* decode \-escape sequences in the internal representation of the quote format */
2006 tmp
= g_malloc(strlen(msginfo
->folder
->prefs
->forward_override_from_format
)+1);
2007 pref_get_unescaped_pref(tmp
, msginfo
->folder
->prefs
->forward_override_from_format
);
2010 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
2011 compose
->gtkaspell
);
2013 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
2015 quote_fmt_scan_string(tmp
);
2018 buf
= quote_fmt_get_buffer();
2020 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2022 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), buf
);
2023 quote_fmt_reset_vartable();
2024 quote_fmtlex_destroy();
2030 textview
= GTK_TEXT_VIEW(compose
->text
);
2031 textbuf
= gtk_text_view_get_buffer(textview
);
2032 compose_create_tags(textview
, compose
);
2034 undo_block(compose
->undostruct
);
2035 for (msginfo
= msginfo_list
; msginfo
!= NULL
; msginfo
= msginfo
->next
) {
2036 msgfile
= procmsg_get_message_file((MsgInfo
*)msginfo
->data
);
2038 if (!is_file_exist(msgfile
))
2039 g_warning("%s: file does not exist", msgfile
);
2041 compose_attach_append(compose
, msgfile
, msgfile
,
2042 "message/rfc822", NULL
);
2047 MsgInfo
*info
= (MsgInfo
*)msginfo_list
->data
;
2048 if (info
->subject
&& *info
->subject
) {
2049 gchar
*buf
, *buf2
, *p
;
2051 buf
= p
= g_strdup(info
->subject
);
2052 p
+= subject_get_prefix_length(p
);
2053 memmove(buf
, p
, strlen(p
) + 1);
2055 buf2
= g_strdup_printf("Fw: %s", buf
);
2056 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), buf2
);
2062 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
),
2063 _("Fw: multiple emails"));
2066 SIGNAL_BLOCK(textbuf
);
2068 if (account
->auto_sig
)
2069 compose_insert_sig(compose
, FALSE
);
2071 compose_wrap_all(compose
);
2073 SIGNAL_UNBLOCK(textbuf
);
2075 gtk_text_buffer_get_start_iter(textbuf
, &iter
);
2076 gtk_text_buffer_place_cursor(textbuf
, &iter
);
2078 if (prefs_common
.auto_exteditor
)
2079 compose_exec_ext_editor(compose
);
2081 gtk_widget_grab_focus(compose
->header_last
->entry
);
2082 undo_unblock(compose
->undostruct
);
2083 compose
->modified
= FALSE
;
2084 compose_set_title(compose
);
2086 compose
->updating
= FALSE
;
2087 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_UNSET
; /* desinhibit auto-drafting after loading */
2088 SCROLL_TO_CURSOR(compose
);
2090 if (compose
->deferred_destroy
) {
2091 compose_destroy(compose
);
2095 hooks_invoke(COMPOSE_CREATED_HOOKLIST
, compose
);
2100 static gboolean
compose_is_sig_separator(Compose
*compose
, GtkTextBuffer
*textbuf
, GtkTextIter
*iter
)
2102 GtkTextIter start
= *iter
;
2103 GtkTextIter end_iter
;
2104 int start_pos
= gtk_text_iter_get_offset(&start
);
2106 if (!compose
->account
->sig_sep
)
2109 gtk_text_buffer_get_iter_at_offset(textbuf
, &end_iter
,
2110 start_pos
+strlen(compose
->account
->sig_sep
));
2112 /* check sig separator */
2113 str
= gtk_text_iter_get_text(&start
, &end_iter
);
2114 if (!strcmp(str
, compose
->account
->sig_sep
)) {
2116 /* check end of line (\n) */
2117 gtk_text_buffer_get_iter_at_offset(textbuf
, &start
,
2118 start_pos
+strlen(compose
->account
->sig_sep
));
2119 gtk_text_buffer_get_iter_at_offset(textbuf
, &end_iter
,
2120 start_pos
+strlen(compose
->account
->sig_sep
)+1);
2121 tmp
= gtk_text_iter_get_text(&start
, &end_iter
);
2122 if (!strcmp(tmp
,"\n")) {
2134 static gboolean
compose_update_folder_hook(gpointer source
, gpointer data
)
2136 FolderUpdateData
*hookdata
= (FolderUpdateData
*)source
;
2137 Compose
*compose
= (Compose
*)data
;
2138 FolderItem
*old_item
= NULL
;
2139 FolderItem
*new_item
= NULL
;
2140 gchar
*old_id
, *new_id
;
2142 if (!(hookdata
->update_flags
& FOLDER_REMOVE_FOLDERITEM
)
2143 && !(hookdata
->update_flags
& FOLDER_MOVE_FOLDERITEM
))
2146 old_item
= hookdata
->item
;
2147 new_item
= hookdata
->item2
;
2149 old_id
= folder_item_get_identifier(old_item
);
2150 new_id
= new_item
? folder_item_get_identifier(new_item
) : g_strdup("NULL");
2152 if (compose
->targetinfo
&& compose
->targetinfo
->folder
== old_item
) {
2153 debug_print("updating targetinfo folder: %s -> %s\n", old_id
, new_id
);
2154 compose
->targetinfo
->folder
= new_item
;
2157 if (compose
->replyinfo
&& compose
->replyinfo
->folder
== old_item
) {
2158 debug_print("updating replyinfo folder: %s -> %s\n", old_id
, new_id
);
2159 compose
->replyinfo
->folder
= new_item
;
2162 if (compose
->fwdinfo
&& compose
->fwdinfo
->folder
== old_item
) {
2163 debug_print("updating fwdinfo folder: %s -> %s\n", old_id
, new_id
);
2164 compose
->fwdinfo
->folder
= new_item
;
2172 static void compose_colorize_signature(Compose
*compose
)
2174 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose
->text
));
2176 GtkTextIter end_iter
;
2177 gtk_text_buffer_get_start_iter(buffer
, &iter
);
2178 while (gtk_text_iter_forward_line(&iter
))
2179 if (compose_is_sig_separator(compose
, buffer
, &iter
)) {
2180 gtk_text_buffer_get_end_iter(buffer
, &end_iter
);
2181 gtk_text_buffer_apply_tag_by_name(buffer
,"signature",&iter
, &end_iter
);
2185 #define BLOCK_WRAP() { \
2186 prev_autowrap = compose->autowrap; \
2187 buffer = gtk_text_view_get_buffer( \
2188 GTK_TEXT_VIEW(compose->text)); \
2189 compose->autowrap = FALSE; \
2191 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2192 G_CALLBACK(compose_changed_cb), \
2194 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2195 G_CALLBACK(text_inserted), \
2198 #define UNBLOCK_WRAP() { \
2199 compose->autowrap = prev_autowrap; \
2200 if (compose->autowrap) { \
2201 gint old = compose->draft_timeout_tag; \
2202 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2203 compose_wrap_all(compose); \
2204 compose->draft_timeout_tag = old; \
2207 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2208 G_CALLBACK(compose_changed_cb), \
2210 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2211 G_CALLBACK(text_inserted), \
2215 Compose
*compose_reedit(MsgInfo
*msginfo
, gboolean batch
)
2217 Compose
*compose
= NULL
;
2218 PrefsAccount
*account
= NULL
;
2219 GtkTextView
*textview
;
2220 GtkTextBuffer
*textbuf
;
2224 gboolean use_signing
= FALSE
;
2225 gboolean use_encryption
= FALSE
;
2226 gchar
*privacy_system
= NULL
;
2227 int priority
= PRIORITY_NORMAL
;
2228 MsgInfo
*replyinfo
= NULL
, *fwdinfo
= NULL
;
2229 gboolean autowrap
= prefs_common
.autowrap
;
2230 gboolean autoindent
= prefs_common
.auto_indent
;
2231 HeaderEntry
*manual_headers
= NULL
;
2233 cm_return_val_if_fail(msginfo
!= NULL
, NULL
);
2234 cm_return_val_if_fail(msginfo
->folder
!= NULL
, NULL
);
2236 if (compose_put_existing_to_front(msginfo
)) {
2240 if (folder_has_parent_of_type(msginfo
->folder
, F_QUEUE
) ||
2241 folder_has_parent_of_type(msginfo
->folder
, F_DRAFT
) ||
2242 folder_has_parent_of_type(msginfo
->folder
, F_OUTBOX
)) {
2243 gchar
*queueheader_buf
= NULL
;
2246 /* Select Account from queue headers */
2247 if (!procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
,
2248 "X-Claws-Account-Id:")) {
2249 id
= atoi(&queueheader_buf
[strlen("X-Claws-Account-Id:")]);
2250 account
= account_find_from_id(id
);
2251 g_free(queueheader_buf
);
2253 if (!procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
,
2254 "X-Sylpheed-Account-Id:")) {
2255 id
= atoi(&queueheader_buf
[strlen("X-Sylpheed-Account-Id:")]);
2256 account
= account_find_from_id(id
);
2257 g_free(queueheader_buf
);
2259 if (!account
&& !procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
,
2261 id
= atoi(&queueheader_buf
[strlen("NAID:")]);
2262 account
= account_find_from_id(id
);
2263 g_free(queueheader_buf
);
2265 if (!account
&& !procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
,
2267 id
= atoi(&queueheader_buf
[strlen("MAID:")]);
2268 account
= account_find_from_id(id
);
2269 g_free(queueheader_buf
);
2271 if (!account
&& !procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
,
2273 account
= account_find_from_address(queueheader_buf
, FALSE
);
2274 g_free(queueheader_buf
);
2276 if (!procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
,
2278 param
= atoi(&queueheader_buf
[strlen("X-Claws-Sign:")]);
2279 use_signing
= param
;
2280 g_free(queueheader_buf
);
2282 if (!procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
,
2283 "X-Sylpheed-Sign:")) {
2284 param
= atoi(&queueheader_buf
[strlen("X-Sylpheed-Sign:")]);
2285 use_signing
= param
;
2286 g_free(queueheader_buf
);
2288 if (!procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
,
2289 "X-Claws-Encrypt:")) {
2290 param
= atoi(&queueheader_buf
[strlen("X-Claws-Encrypt:")]);
2291 use_encryption
= param
;
2292 g_free(queueheader_buf
);
2294 if (!procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
,
2295 "X-Sylpheed-Encrypt:")) {
2296 param
= atoi(&queueheader_buf
[strlen("X-Sylpheed-Encrypt:")]);
2297 use_encryption
= param
;
2298 g_free(queueheader_buf
);
2300 if (!procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
,
2301 "X-Claws-Auto-Wrapping:")) {
2302 param
= atoi(&queueheader_buf
[strlen("X-Claws-Auto-Wrapping:")]);
2304 g_free(queueheader_buf
);
2306 if (!procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
,
2307 "X-Claws-Auto-Indent:")) {
2308 param
= atoi(&queueheader_buf
[strlen("X-Claws-Auto-Indent:")]);
2310 g_free(queueheader_buf
);
2312 if (!procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
,
2313 "X-Claws-Privacy-System:")) {
2314 privacy_system
= g_strdup(&queueheader_buf
[strlen("X-Claws-Privacy-System:")]);
2315 g_free(queueheader_buf
);
2317 if (!procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
,
2318 "X-Sylpheed-Privacy-System:")) {
2319 privacy_system
= g_strdup(&queueheader_buf
[strlen("X-Sylpheed-Privacy-System:")]);
2320 g_free(queueheader_buf
);
2322 if (!procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
,
2324 param
= atoi(&queueheader_buf
[strlen("X-Priority: ")]); /* mind the space */
2326 g_free(queueheader_buf
);
2328 if (!procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
,
2330 gchar
**tokens
= g_strsplit(&queueheader_buf
[strlen("RMID:")], "\t", 0);
2331 if (tokens
&& tokens
[0] && tokens
[1] && tokens
[2]) {
2332 FolderItem
*orig_item
= folder_find_item_from_identifier(tokens
[0]);
2333 if (orig_item
!= NULL
) {
2334 replyinfo
= folder_item_get_msginfo_by_msgid(orig_item
, tokens
[2]);
2339 g_free(queueheader_buf
);
2341 if (!procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
,
2343 gchar
**tokens
= g_strsplit(&queueheader_buf
[strlen("FMID:")], "\t", 0);
2344 if (tokens
&& tokens
[0] && tokens
[1] && tokens
[2]) {
2345 FolderItem
*orig_item
= folder_find_item_from_identifier(tokens
[0]);
2346 if (orig_item
!= NULL
) {
2347 fwdinfo
= folder_item_get_msginfo_by_msgid(orig_item
, tokens
[2]);
2352 g_free(queueheader_buf
);
2354 /* Get manual headers */
2355 if (!procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
,
2356 "X-Claws-Manual-Headers:")) {
2357 gchar
*listmh
= g_strdup(&queueheader_buf
[strlen("X-Claws-Manual-Headers:")]);
2358 if (listmh
&& *listmh
!= '\0') {
2359 debug_print("Got manual headers: %s\n", listmh
);
2360 manual_headers
= procheader_entries_from_str(listmh
);
2364 g_free(queueheader_buf
);
2367 account
= msginfo
->folder
->folder
->account
;
2370 if (!account
&& prefs_common
.reedit_account_autosel
) {
2372 if (!procheader_get_header_from_msginfo(msginfo
, &from
, "FROM:")) {
2373 extract_address(from
);
2374 account
= account_find_from_address(from
, FALSE
);
2380 account
= cur_account
;
2383 g_warning("can't select account");
2385 procheader_entries_free(manual_headers
);
2389 compose
= compose_create(account
, msginfo
->folder
, COMPOSE_REEDIT
, batch
);
2391 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/RemoveReferences", FALSE
);
2392 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/RemoveReferences", TRUE
);
2393 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Edit/AutoWrap", autowrap
);
2394 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Edit/AutoIndent", autoindent
);
2395 compose
->autowrap
= autowrap
;
2396 compose
->replyinfo
= replyinfo
;
2397 compose
->fwdinfo
= fwdinfo
;
2399 compose
->updating
= TRUE
;
2400 compose
->priority
= priority
;
2402 if (privacy_system
!= NULL
) {
2403 compose
->privacy_system
= privacy_system
;
2404 compose_use_signing(compose
, use_signing
);
2405 compose_use_encryption(compose
, use_encryption
);
2406 compose_update_privacy_system_menu_item(compose
, FALSE
);
2408 compose_activate_privacy_system(compose
, account
, FALSE
);
2410 compose_apply_folder_privacy_settings(compose
, msginfo
->folder
);
2411 if (privacy_system_can_sign(compose
->privacy_system
) == FALSE
&&
2412 (account
->default_encrypt
|| account
->default_sign
))
2413 COMPOSE_PRIVACY_WARNING();
2415 compose
->targetinfo
= procmsg_msginfo_copy(msginfo
);
2416 compose
->targetinfo
->tags
= g_slist_copy(msginfo
->tags
);
2418 compose_extract_original_charset(compose
);
2420 if (folder_has_parent_of_type(msginfo
->folder
, F_QUEUE
) ||
2421 folder_has_parent_of_type(msginfo
->folder
, F_DRAFT
) ||
2422 folder_has_parent_of_type(msginfo
->folder
, F_OUTBOX
)) {
2423 gchar
*queueheader_buf
= NULL
;
2425 /* Set message save folder */
2426 if (!procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
, "SCF:")) {
2427 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
), TRUE
);
2428 gtk_widget_set_sensitive(GTK_WIDGET(compose
->savemsg_combo
), TRUE
);
2429 compose_set_save_to(compose
, &queueheader_buf
[4]);
2430 g_free(queueheader_buf
);
2432 if (!procheader_get_header_from_msginfo(msginfo
, &queueheader_buf
, "RRCPT:")) {
2433 gint active
= atoi(&queueheader_buf
[strlen("RRCPT:")]);
2435 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/RequestRetRcpt", TRUE
);
2437 g_free(queueheader_buf
);
2441 if (compose_parse_header(compose
, msginfo
) < 0) {
2442 compose
->updating
= FALSE
;
2443 compose_destroy(compose
);
2445 procheader_entries_free(manual_headers
);
2448 compose_reedit_set_entry(compose
, msginfo
);
2450 textview
= GTK_TEXT_VIEW(compose
->text
);
2451 textbuf
= gtk_text_view_get_buffer(textview
);
2452 compose_create_tags(textview
, compose
);
2454 mark
= gtk_text_buffer_get_insert(textbuf
);
2455 gtk_text_buffer_get_iter_at_mark(textbuf
, &iter
, mark
);
2457 g_signal_handlers_block_by_func(G_OBJECT(textbuf
),
2458 G_CALLBACK(compose_changed_cb
),
2461 if (MSG_IS_ENCRYPTED(msginfo
->flags
)) {
2462 fp
= procmime_get_first_encrypted_text_content(msginfo
);
2464 compose_force_encryption(compose
, account
, TRUE
, NULL
);
2467 fp
= procmime_get_first_text_content(msginfo
);
2470 g_warning("can't get text part");
2474 gchar buf
[BUFFSIZE
];
2475 gboolean prev_autowrap
;
2476 GtkTextBuffer
*buffer
;
2478 while (claws_fgets(buf
, sizeof(buf
), fp
) != NULL
) {
2480 gtk_text_buffer_insert(textbuf
, &iter
, buf
, -1);
2486 compose_attach_parts(compose
, msginfo
);
2488 compose_colorize_signature(compose
);
2490 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf
),
2491 G_CALLBACK(compose_changed_cb
),
2494 if (manual_headers
!= NULL
) {
2495 if (compose_parse_manual_headers(compose
, msginfo
, manual_headers
) < 0) {
2496 procheader_entries_free(manual_headers
);
2497 compose
->updating
= FALSE
;
2498 compose_destroy(compose
);
2501 procheader_entries_free(manual_headers
);
2504 gtk_widget_grab_focus(compose
->text
);
2506 if (prefs_common
.auto_exteditor
) {
2507 compose_exec_ext_editor(compose
);
2509 compose
->modified
= FALSE
;
2510 compose_set_title(compose
);
2512 compose
->updating
= FALSE
;
2513 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_UNSET
; /* desinhibit auto-drafting after loading */
2514 SCROLL_TO_CURSOR(compose
);
2516 if (compose
->deferred_destroy
) {
2517 compose_destroy(compose
);
2521 compose
->sig_str
= account_get_signature_str(compose
->account
);
2523 hooks_invoke(COMPOSE_CREATED_HOOKLIST
, compose
);
2528 Compose
*compose_redirect(PrefsAccount
*account
, MsgInfo
*msginfo
,
2535 cm_return_val_if_fail(msginfo
!= NULL
, NULL
);
2538 account
= account_get_reply_account(msginfo
,
2539 prefs_common
.reply_account_autosel
);
2540 cm_return_val_if_fail(account
!= NULL
, NULL
);
2542 compose
= compose_create(account
, msginfo
->folder
, COMPOSE_REDIRECT
, batch
);
2544 compose
->updating
= TRUE
;
2546 compose_create_tags(GTK_TEXT_VIEW(compose
->text
), compose
);
2547 compose
->replyinfo
= NULL
;
2548 compose
->fwdinfo
= NULL
;
2550 compose_show_first_last_header(compose
, TRUE
);
2552 gtk_widget_grab_focus(compose
->header_last
->entry
);
2554 filename
= procmsg_get_message_file(msginfo
);
2556 if (filename
== NULL
) {
2557 compose
->updating
= FALSE
;
2558 compose_destroy(compose
);
2563 compose
->redirect_filename
= filename
;
2565 /* Set save folder */
2566 item
= msginfo
->folder
;
2567 if (item
&& item
->prefs
&& item
->prefs
->save_copy_to_folder
) {
2568 gchar
*folderidentifier
;
2570 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
), TRUE
);
2571 gtk_widget_set_sensitive(GTK_WIDGET(compose
->savemsg_combo
), TRUE
);
2572 folderidentifier
= folder_item_get_identifier(item
);
2573 compose_set_save_to(compose
, folderidentifier
);
2574 g_free(folderidentifier
);
2577 compose_attach_parts(compose
, msginfo
);
2579 if (msginfo
->subject
)
2580 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
),
2582 gtk_editable_set_editable(GTK_EDITABLE(compose
->subject_entry
), FALSE
);
2584 compose_quote_fmt(compose
, msginfo
, "%M", NULL
, NULL
, FALSE
, FALSE
,
2585 _("The body of the \"Redirect\" template has an error at line %d."));
2586 quote_fmt_reset_vartable();
2587 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose
->text
), FALSE
);
2589 compose_colorize_signature(compose
);
2591 cm_menu_set_sensitive_full(compose
->ui_manager
, "Popup/Compose/Add", FALSE
);
2592 cm_menu_set_sensitive_full(compose
->ui_manager
, "Popup/Compose/Remove", FALSE
);
2593 cm_menu_set_sensitive_full(compose
->ui_manager
, "Popup/Compose/Properties", FALSE
);
2595 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Message/SendLater", FALSE
);
2596 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Message/Save", FALSE
);
2597 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Message/InsertFile", FALSE
);
2598 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Message/AttachFile", FALSE
);
2599 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Message/InsertSig", FALSE
);
2600 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Message/ReplaceSig", FALSE
);
2601 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit", FALSE
);
2602 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options", FALSE
);
2603 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Tools/ShowRuler", FALSE
);
2604 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Tools/Actions", FALSE
);
2606 if (compose
->toolbar
->sendl_btn
)
2607 gtk_widget_set_sensitive(compose
->toolbar
->sendl_btn
, FALSE
);
2608 if (compose
->toolbar
->draft_btn
)
2609 gtk_widget_set_sensitive(compose
->toolbar
->draft_btn
, FALSE
);
2610 if (compose
->toolbar
->insert_btn
)
2611 gtk_widget_set_sensitive(compose
->toolbar
->insert_btn
, FALSE
);
2612 if (compose
->toolbar
->attach_btn
)
2613 gtk_widget_set_sensitive(compose
->toolbar
->attach_btn
, FALSE
);
2614 if (compose
->toolbar
->sig_btn
)
2615 gtk_widget_set_sensitive(compose
->toolbar
->sig_btn
, FALSE
);
2616 if (compose
->toolbar
->exteditor_btn
)
2617 gtk_widget_set_sensitive(compose
->toolbar
->exteditor_btn
, FALSE
);
2618 if (compose
->toolbar
->linewrap_current_btn
)
2619 gtk_widget_set_sensitive(compose
->toolbar
->linewrap_current_btn
, FALSE
);
2620 if (compose
->toolbar
->linewrap_all_btn
)
2621 gtk_widget_set_sensitive(compose
->toolbar
->linewrap_all_btn
, FALSE
);
2622 if (compose
->toolbar
->privacy_sign_btn
)
2623 gtk_widget_set_sensitive(compose
->toolbar
->privacy_sign_btn
, FALSE
);
2624 if (compose
->toolbar
->privacy_encrypt_btn
)
2625 gtk_widget_set_sensitive(compose
->toolbar
->privacy_encrypt_btn
, FALSE
);
2627 compose
->modified
= FALSE
;
2628 compose_set_title(compose
);
2629 compose
->updating
= FALSE
;
2630 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_UNSET
; /* desinhibit auto-drafting after loading */
2631 SCROLL_TO_CURSOR(compose
);
2633 if (compose
->deferred_destroy
) {
2634 compose_destroy(compose
);
2638 hooks_invoke(COMPOSE_CREATED_HOOKLIST
, compose
);
2643 const GList
*compose_get_compose_list(void)
2645 return compose_list
;
2648 void compose_entry_append(Compose
*compose
, const gchar
*address
,
2649 ComposeEntryType type
, ComposePrefType pref_type
)
2651 const gchar
*header
;
2653 gboolean in_quote
= FALSE
;
2654 if (!address
|| *address
== '\0') return;
2661 header
= N_("Bcc:");
2663 case COMPOSE_REPLYTO
:
2664 header
= N_("Reply-To:");
2666 case COMPOSE_NEWSGROUPS
:
2667 header
= N_("Newsgroups:");
2669 case COMPOSE_FOLLOWUPTO
:
2670 header
= N_( "Followup-To:");
2672 case COMPOSE_INREPLYTO
:
2673 header
= N_( "In-Reply-To:");
2680 header
= prefs_common_translated_header_name(header
);
2682 cur
= begin
= (gchar
*)address
;
2684 /* we separate the line by commas, but not if we're inside a quoted
2686 while (*cur
!= '\0') {
2688 in_quote
= !in_quote
;
2689 if (*cur
== ',' && !in_quote
) {
2690 gchar
*tmp
= g_strdup(begin
);
2692 tmp
[cur
-begin
]='\0';
2695 while (*tmp
== ' ' || *tmp
== '\t')
2697 compose_add_header_entry(compose
, header
, tmp
, pref_type
);
2698 compose_entry_indicate(compose
, tmp
);
2705 gchar
*tmp
= g_strdup(begin
);
2707 tmp
[cur
-begin
]='\0';
2708 while (*tmp
== ' ' || *tmp
== '\t')
2710 compose_add_header_entry(compose
, header
, tmp
, pref_type
);
2711 compose_entry_indicate(compose
, tmp
);
2716 static void compose_entry_indicate(Compose
*compose
, const gchar
*mailto
)
2722 for (h_list
= compose
->header_list
; h_list
!= NULL
; h_list
= h_list
->next
) {
2723 entry
= GTK_ENTRY(((ComposeHeaderEntry
*)h_list
->data
)->entry
);
2724 if (gtk_entry_get_text(entry
) &&
2725 !g_utf8_collate(gtk_entry_get_text(entry
), mailto
)) {
2726 /* Modify background color */
2727 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_bgcolor
, color
);
2728 gtk_widget_modify_base(
2729 GTK_WIDGET(((ComposeHeaderEntry
*)h_list
->data
)->entry
),
2730 GTK_STATE_NORMAL
, &color
);
2732 /* Modify foreground color */
2733 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_color
, color
);
2734 gtk_widget_modify_text(
2735 GTK_WIDGET(((ComposeHeaderEntry
*)h_list
->data
)->entry
),
2736 GTK_STATE_NORMAL
, &color
);
2741 void compose_toolbar_cb(gint action
, gpointer data
)
2743 ToolbarItem
*toolbar_item
= (ToolbarItem
*)data
;
2744 Compose
*compose
= (Compose
*)toolbar_item
->parent
;
2746 cm_return_if_fail(compose
!= NULL
);
2750 compose_send_cb(NULL
, compose
);
2753 compose_send_later_cb(NULL
, compose
);
2756 compose_draft(compose
, COMPOSE_QUIT_EDITING
);
2759 compose_insert_file_cb(NULL
, compose
);
2762 compose_attach_cb(NULL
, compose
);
2765 compose_insert_sig(compose
, FALSE
);
2768 compose_insert_sig(compose
, TRUE
);
2771 compose_ext_editor_cb(NULL
, compose
);
2773 case A_LINEWRAP_CURRENT
:
2774 compose_beautify_paragraph(compose
, NULL
, TRUE
);
2776 case A_LINEWRAP_ALL
:
2777 compose_wrap_all_full(compose
, TRUE
);
2780 compose_address_cb(NULL
, compose
);
2783 case A_CHECK_SPELLING
:
2784 compose_check_all(NULL
, compose
);
2787 case A_PRIVACY_SIGN
:
2789 case A_PRIVACY_ENCRYPT
:
2796 static MailField
compose_entries_set(Compose
*compose
, const gchar
*mailto
, ComposeEntryType to_type
)
2801 gchar
*subject
= NULL
;
2805 gchar
**attach
= NULL
;
2806 gchar
*inreplyto
= NULL
;
2807 MailField mfield
= NO_FIELD_PRESENT
;
2809 /* get mailto parts but skip from */
2810 scan_mailto_url(mailto
, NULL
, &to
, &cc
, &bcc
, &subject
, &body
, &attach
, &inreplyto
);
2813 compose_entry_append(compose
, to
, to_type
, PREF_MAILTO
);
2814 mfield
= TO_FIELD_PRESENT
;
2817 compose_entry_append(compose
, cc
, COMPOSE_CC
, PREF_MAILTO
);
2819 compose_entry_append(compose
, bcc
, COMPOSE_BCC
, PREF_MAILTO
);
2821 if (!g_utf8_validate (subject
, -1, NULL
)) {
2822 temp
= g_locale_to_utf8 (subject
, -1, NULL
, &len
, NULL
);
2823 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), temp
);
2826 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), subject
);
2828 mfield
= SUBJECT_FIELD_PRESENT
;
2831 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
2832 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(text
);
2835 gboolean prev_autowrap
= compose
->autowrap
;
2837 compose
->autowrap
= FALSE
;
2839 mark
= gtk_text_buffer_get_insert(buffer
);
2840 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
2842 if (!g_utf8_validate (body
, -1, NULL
)) {
2843 temp
= g_locale_to_utf8 (body
, -1, NULL
, &len
, NULL
);
2844 gtk_text_buffer_insert(buffer
, &iter
, temp
, -1);
2847 gtk_text_buffer_insert(buffer
, &iter
, body
, -1);
2849 gtk_text_buffer_insert(buffer
, &iter
, "\n", 1);
2851 compose
->autowrap
= prev_autowrap
;
2852 if (compose
->autowrap
)
2853 compose_wrap_all(compose
);
2854 mfield
= BODY_FIELD_PRESENT
;
2858 gint i
= 0, att
= 0;
2859 gchar
*warn_files
= NULL
;
2860 while (attach
[i
] != NULL
) {
2861 gchar
*utf8_filename
= conv_filename_to_utf8(attach
[i
]);
2862 if (utf8_filename
) {
2863 if (compose_attach_append(compose
, attach
[i
], utf8_filename
, NULL
, NULL
)) {
2864 gchar
*tmp
= g_strdup_printf("%s%s\n",
2865 warn_files
?warn_files
:"",
2871 g_free(utf8_filename
);
2873 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2878 alertpanel_notice(ngettext(
2879 "The following file has been attached: \n%s",
2880 "The following files have been attached: \n%s", att
), warn_files
);
2885 compose_entry_append(compose
, inreplyto
, COMPOSE_INREPLYTO
, PREF_MAILTO
);
2898 static gint
compose_parse_header(Compose
*compose
, MsgInfo
*msginfo
)
2900 static HeaderEntry hentry
[] = {
2901 {"Reply-To:", NULL
, TRUE
},
2902 {"Cc:", NULL
, TRUE
},
2903 {"References:", NULL
, FALSE
},
2904 {"Bcc:", NULL
, TRUE
},
2905 {"Newsgroups:", NULL
, TRUE
},
2906 {"Followup-To:", NULL
, TRUE
},
2907 {"List-Post:", NULL
, FALSE
},
2908 {"X-Priority:", NULL
, FALSE
},
2909 {NULL
, NULL
, FALSE
}
2926 cm_return_val_if_fail(msginfo
!= NULL
, -1);
2928 if ((fp
= procmsg_open_message(msginfo
, FALSE
)) == NULL
) return -1;
2929 procheader_get_header_fields(fp
, hentry
);
2932 if (hentry
[H_REPLY_TO
].body
!= NULL
) {
2933 if (hentry
[H_REPLY_TO
].body
[0] != '\0') {
2935 conv_unmime_header(hentry
[H_REPLY_TO
].body
,
2938 g_free(hentry
[H_REPLY_TO
].body
);
2939 hentry
[H_REPLY_TO
].body
= NULL
;
2941 if (hentry
[H_CC
].body
!= NULL
) {
2942 compose
->cc
= conv_unmime_header(hentry
[H_CC
].body
, NULL
, TRUE
);
2943 g_free(hentry
[H_CC
].body
);
2944 hentry
[H_CC
].body
= NULL
;
2946 if (hentry
[H_REFERENCES
].body
!= NULL
) {
2947 if (compose
->mode
== COMPOSE_REEDIT
)
2948 compose
->references
= hentry
[H_REFERENCES
].body
;
2950 compose
->references
= compose_parse_references
2951 (hentry
[H_REFERENCES
].body
, msginfo
->msgid
);
2952 g_free(hentry
[H_REFERENCES
].body
);
2954 hentry
[H_REFERENCES
].body
= NULL
;
2956 if (hentry
[H_BCC
].body
!= NULL
) {
2957 if (compose
->mode
== COMPOSE_REEDIT
)
2959 conv_unmime_header(hentry
[H_BCC
].body
, NULL
, TRUE
);
2960 g_free(hentry
[H_BCC
].body
);
2961 hentry
[H_BCC
].body
= NULL
;
2963 if (hentry
[H_NEWSGROUPS
].body
!= NULL
) {
2964 compose
->newsgroups
= hentry
[H_NEWSGROUPS
].body
;
2965 hentry
[H_NEWSGROUPS
].body
= NULL
;
2967 if (hentry
[H_FOLLOWUP_TO
].body
!= NULL
) {
2968 if (hentry
[H_FOLLOWUP_TO
].body
[0] != '\0') {
2969 compose
->followup_to
=
2970 conv_unmime_header(hentry
[H_FOLLOWUP_TO
].body
,
2973 g_free(hentry
[H_FOLLOWUP_TO
].body
);
2974 hentry
[H_FOLLOWUP_TO
].body
= NULL
;
2976 if (hentry
[H_LIST_POST
].body
!= NULL
) {
2977 gchar
*to
= NULL
, *start
= NULL
;
2979 extract_address(hentry
[H_LIST_POST
].body
);
2980 if (hentry
[H_LIST_POST
].body
[0] != '\0') {
2981 start
= strstr(hentry
[H_LIST_POST
].body
, "mailto:");
2983 scan_mailto_url(start
? start
: hentry
[H_LIST_POST
].body
,
2984 NULL
, &to
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
);
2987 g_free(compose
->ml_post
);
2988 compose
->ml_post
= to
;
2991 g_free(hentry
[H_LIST_POST
].body
);
2992 hentry
[H_LIST_POST
].body
= NULL
;
2995 /* CLAWS - X-Priority */
2996 if (compose
->mode
== COMPOSE_REEDIT
)
2997 if (hentry
[H_X_PRIORITY
].body
!= NULL
) {
3000 priority
= atoi(hentry
[H_X_PRIORITY
].body
);
3001 g_free(hentry
[H_X_PRIORITY
].body
);
3003 hentry
[H_X_PRIORITY
].body
= NULL
;
3005 if (priority
< PRIORITY_HIGHEST
||
3006 priority
> PRIORITY_LOWEST
)
3007 priority
= PRIORITY_NORMAL
;
3009 compose
->priority
= priority
;
3012 if (compose
->mode
== COMPOSE_REEDIT
) {
3013 if (msginfo
->inreplyto
&& *msginfo
->inreplyto
)
3014 compose
->inreplyto
= g_strdup(msginfo
->inreplyto
);
3016 if (msginfo
->msgid
&& *msginfo
->msgid
&&
3017 compose
->folder
!= NULL
&&
3018 compose
->folder
->stype
== F_DRAFT
)
3019 compose
->msgid
= g_strdup(msginfo
->msgid
);
3021 if (msginfo
->msgid
&& *msginfo
->msgid
&&
3022 (compose
->mode
!= COMPOSE_FORWARD
&&
3023 compose
->mode
!= COMPOSE_FORWARD_INLINE
&&
3024 compose
->mode
!= COMPOSE_FORWARD_AS_ATTACH
))
3025 compose
->inreplyto
= g_strdup(msginfo
->msgid
);
3027 if (!compose
->references
) {
3028 if (msginfo
->msgid
&& *msginfo
->msgid
) {
3029 if (msginfo
->inreplyto
&& *msginfo
->inreplyto
)
3030 compose
->references
=
3031 g_strdup_printf("<%s>\n\t<%s>",
3035 compose
->references
=
3036 g_strconcat("<", msginfo
->msgid
, ">",
3038 } else if (msginfo
->inreplyto
&& *msginfo
->inreplyto
) {
3039 compose
->references
=
3040 g_strconcat("<", msginfo
->inreplyto
, ">",
3049 static gint
compose_parse_manual_headers(Compose
*compose
, MsgInfo
*msginfo
, HeaderEntry
*entries
)
3054 cm_return_val_if_fail(msginfo
!= NULL
, -1);
3056 if ((fp
= procmsg_open_message(msginfo
, FALSE
)) == NULL
) return -1;
3057 procheader_get_header_fields(fp
, entries
);
3061 while (he
!= NULL
&& he
->name
!= NULL
) {
3063 GtkListStore
*model
= NULL
;
3065 debug_print("Adding manual header: %s with value %s\n", he
->name
, he
->body
);
3066 model
= GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose
->header_last
->combo
)));
3067 COMBOBOX_ADD(model
, he
->name
, COMPOSE_TO
);
3068 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose
->header_last
->combo
), &iter
);
3069 gtk_entry_set_text(GTK_ENTRY(compose
->header_last
->entry
), he
->body
);
3076 static gchar
*compose_parse_references(const gchar
*ref
, const gchar
*msgid
)
3078 GSList
*ref_id_list
, *cur
;
3081 ref_id_list
= references_list_append(NULL
, ref
);
3082 if (!ref_id_list
) return NULL
;
3083 if (msgid
&& *msgid
)
3084 ref_id_list
= g_slist_append(ref_id_list
, g_strdup(msgid
));
3089 for (cur
= ref_id_list
; cur
!= NULL
; cur
= cur
->next
)
3090 /* "<" + Message-ID + ">" + CR+LF+TAB */
3091 len
+= strlen((gchar
*)cur
->data
) + 5;
3093 if (len
> MAX_REFERENCES_LEN
) {
3094 /* remove second message-ID */
3095 if (ref_id_list
&& ref_id_list
->next
&&
3096 ref_id_list
->next
->next
) {
3097 g_free(ref_id_list
->next
->data
);
3098 ref_id_list
= g_slist_remove
3099 (ref_id_list
, ref_id_list
->next
->data
);
3101 slist_free_strings_full(ref_id_list
);
3108 new_ref
= g_string_new("");
3109 for (cur
= ref_id_list
; cur
!= NULL
; cur
= cur
->next
) {
3110 if (new_ref
->len
> 0)
3111 g_string_append(new_ref
, "\n\t");
3112 g_string_append_printf(new_ref
, "<%s>", (gchar
*)cur
->data
);
3115 slist_free_strings_full(ref_id_list
);
3117 return g_string_free(new_ref
, FALSE
);
3120 static gchar
*compose_quote_fmt(Compose
*compose
, MsgInfo
*msginfo
,
3121 const gchar
*fmt
, const gchar
*qmark
,
3122 const gchar
*body
, gboolean rewrap
,
3123 gboolean need_unescape
,
3124 const gchar
*err_msg
)
3126 MsgInfo
* dummyinfo
= NULL
;
3127 gchar
*quote_str
= NULL
;
3129 gboolean prev_autowrap
;
3130 const gchar
*trimmed_body
= body
;
3131 gint cursor_pos
= -1;
3132 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
3133 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(text
);
3138 SIGNAL_BLOCK(buffer
);
3141 dummyinfo
= compose_msginfo_new_from_compose(compose
);
3142 msginfo
= dummyinfo
;
3145 if (qmark
!= NULL
) {
3147 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
3148 compose
->gtkaspell
);
3150 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
3152 quote_fmt_scan_string(qmark
);
3155 buf
= quote_fmt_get_buffer();
3158 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3160 Xstrdup_a(quote_str
, buf
, goto error
)
3163 if (fmt
&& *fmt
!= '\0') {
3166 while (*trimmed_body
== '\n')
3170 quote_fmt_init(msginfo
, quote_str
, trimmed_body
, FALSE
, compose
->account
, FALSE
,
3171 compose
->gtkaspell
);
3173 quote_fmt_init(msginfo
, quote_str
, trimmed_body
, FALSE
, compose
->account
, FALSE
);
3175 if (need_unescape
) {
3178 /* decode \-escape sequences in the internal representation of the quote format */
3179 tmp
= g_malloc(strlen(fmt
)+1);
3180 pref_get_unescaped_pref(tmp
, fmt
);
3181 quote_fmt_scan_string(tmp
);
3185 quote_fmt_scan_string(fmt
);
3189 buf
= quote_fmt_get_buffer();
3192 gint line
= quote_fmt_get_line();
3193 alertpanel_error(err_msg
, line
);
3201 prev_autowrap
= compose
->autowrap
;
3202 compose
->autowrap
= FALSE
;
3204 mark
= gtk_text_buffer_get_insert(buffer
);
3205 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
3206 if (g_utf8_validate(buf
, -1, NULL
)) {
3207 gtk_text_buffer_insert(buffer
, &iter
, buf
, -1);
3209 gchar
*tmpout
= NULL
;
3210 tmpout
= conv_codeset_strdup
3211 (buf
, conv_get_locale_charset_str_no_utf8(),
3213 if (!tmpout
|| !g_utf8_validate(tmpout
, -1, NULL
)) {
3215 tmpout
= g_malloc(strlen(buf
)*2+1);
3216 conv_localetodisp(tmpout
, strlen(buf
)*2+1, buf
);
3218 gtk_text_buffer_insert(buffer
, &iter
, tmpout
, -1);
3222 cursor_pos
= quote_fmt_get_cursor_pos();
3223 if (cursor_pos
== -1)
3224 cursor_pos
= gtk_text_iter_get_offset(&iter
);
3225 compose
->set_cursor_pos
= cursor_pos
;
3227 gtk_text_buffer_get_start_iter(buffer
, &iter
);
3228 gtk_text_buffer_get_iter_at_offset(buffer
, &iter
, cursor_pos
);
3229 gtk_text_buffer_place_cursor(buffer
, &iter
);
3231 compose
->autowrap
= prev_autowrap
;
3232 if (compose
->autowrap
&& rewrap
)
3233 compose_wrap_all(compose
);
3240 SIGNAL_UNBLOCK(buffer
);
3242 procmsg_msginfo_free( &dummyinfo
);
3247 /* if ml_post is of type addr@host and from is of type
3248 * addr-anything@host, return TRUE
3250 static gboolean
is_subscription(const gchar
*ml_post
, const gchar
*from
)
3252 gchar
*left_ml
= NULL
;
3253 gchar
*right_ml
= NULL
;
3254 gchar
*left_from
= NULL
;
3255 gchar
*right_from
= NULL
;
3256 gboolean result
= FALSE
;
3258 if (!ml_post
|| !from
)
3261 left_ml
= g_strdup(ml_post
);
3262 if (strstr(left_ml
, "@")) {
3263 right_ml
= strstr(left_ml
, "@")+1;
3264 *(strstr(left_ml
, "@")) = '\0';
3267 left_from
= g_strdup(from
);
3268 if (strstr(left_from
, "@")) {
3269 right_from
= strstr(left_from
, "@")+1;
3270 *(strstr(left_from
, "@")) = '\0';
3273 if (right_ml
&& right_from
3274 && !strncmp(left_from
, left_ml
, strlen(left_ml
))
3275 && !strcmp(right_from
, right_ml
)) {
3284 static void compose_set_folder_prefs(Compose
*compose
, FolderItem
*folder
,
3285 gboolean respect_default_to
)
3289 if (!folder
|| !folder
->prefs
)
3292 if (folder
->prefs
->enable_default_from
) {
3293 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), folder
->prefs
->default_from
);
3294 compose_entry_indicate(compose
, folder
->prefs
->default_from
);
3296 if (respect_default_to
&& folder
->prefs
->enable_default_to
) {
3297 compose_entry_append(compose
, folder
->prefs
->default_to
,
3298 COMPOSE_TO
, PREF_FOLDER
);
3299 compose_entry_indicate(compose
, folder
->prefs
->default_to
);
3301 if (folder
->prefs
->enable_default_cc
) {
3302 compose_entry_append(compose
, folder
->prefs
->default_cc
,
3303 COMPOSE_CC
, PREF_FOLDER
);
3304 compose_entry_indicate(compose
, folder
->prefs
->default_cc
);
3306 if (folder
->prefs
->enable_default_bcc
) {
3307 compose_entry_append(compose
, folder
->prefs
->default_bcc
,
3308 COMPOSE_BCC
, PREF_FOLDER
);
3309 compose_entry_indicate(compose
, folder
->prefs
->default_bcc
);
3311 if (folder
->prefs
->enable_default_replyto
) {
3312 compose_entry_append(compose
, folder
->prefs
->default_replyto
,
3313 COMPOSE_REPLYTO
, PREF_FOLDER
);
3314 compose_entry_indicate(compose
, folder
->prefs
->default_replyto
);
3318 static void compose_reply_set_subject(Compose
*compose
, MsgInfo
*msginfo
)
3323 if (!compose
|| !msginfo
)
3326 if (msginfo
->subject
&& *msginfo
->subject
) {
3327 buf
= p
= g_strdup(msginfo
->subject
);
3328 p
+= subject_get_prefix_length(p
);
3329 memmove(buf
, p
, strlen(p
) + 1);
3331 buf2
= g_strdup_printf("Re: %s", buf
);
3332 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), buf2
);
3337 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), "Re: ");
3340 static void compose_reply_set_entry(Compose
*compose
, MsgInfo
*msginfo
,
3341 gboolean to_all
, gboolean to_ml
,
3343 gboolean followup_and_reply_to
)
3345 GSList
*cc_list
= NULL
;
3348 gchar
*replyto
= NULL
;
3349 gchar
*ac_email
= NULL
;
3351 gboolean reply_to_ml
= FALSE
;
3352 gboolean default_reply_to
= FALSE
;
3354 cm_return_if_fail(compose
->account
!= NULL
);
3355 cm_return_if_fail(msginfo
!= NULL
);
3357 reply_to_ml
= to_ml
&& compose
->ml_post
;
3359 default_reply_to
= msginfo
->folder
&&
3360 msginfo
->folder
->prefs
->enable_default_reply_to
;
3362 if (compose
->account
->protocol
!= A_NNTP
) {
3363 compose_set_folder_prefs(compose
, msginfo
->folder
, FALSE
);
3365 if (reply_to_ml
&& !default_reply_to
) {
3367 gboolean is_subscr
= is_subscription(compose
->ml_post
,
3370 /* normal answer to ml post with a reply-to */
3371 compose_entry_append(compose
,
3373 COMPOSE_TO
, PREF_ML
);
3374 if (compose
->replyto
)
3375 compose_entry_append(compose
,
3377 COMPOSE_CC
, PREF_ML
);
3379 /* answer to subscription confirmation */
3380 if (compose
->replyto
)
3381 compose_entry_append(compose
,
3383 COMPOSE_TO
, PREF_ML
);
3384 else if (msginfo
->from
)
3385 compose_entry_append(compose
,
3387 COMPOSE_TO
, PREF_ML
);
3390 else if (!(to_all
|| to_sender
) && default_reply_to
) {
3391 compose_entry_append(compose
,
3392 msginfo
->folder
->prefs
->default_reply_to
,
3393 COMPOSE_TO
, PREF_FOLDER
);
3394 compose_entry_indicate(compose
,
3395 msginfo
->folder
->prefs
->default_reply_to
);
3401 compose_entry_append(compose
, msginfo
->from
,
3402 COMPOSE_TO
, PREF_NONE
);
3404 Xstrdup_a(tmp1
, msginfo
->from
, return);
3405 extract_address(tmp1
);
3406 compose_entry_append(compose
,
3407 (!account_find_from_address(tmp1
, FALSE
))
3410 COMPOSE_TO
, PREF_NONE
);
3411 if (compose
->replyto
)
3412 compose_entry_append(compose
,
3414 COMPOSE_CC
, PREF_NONE
);
3416 if (!folder_has_parent_of_type(msginfo
->folder
, F_QUEUE
) &&
3417 !folder_has_parent_of_type(msginfo
->folder
, F_OUTBOX
) &&
3418 !folder_has_parent_of_type(msginfo
->folder
, F_DRAFT
)) {
3419 if (compose
->replyto
) {
3420 compose_entry_append(compose
,
3422 COMPOSE_TO
, PREF_NONE
);
3424 compose_entry_append(compose
,
3425 msginfo
->from
? msginfo
->from
: "",
3426 COMPOSE_TO
, PREF_NONE
);
3429 /* replying to own mail, use original recp */
3430 compose_entry_append(compose
,
3431 msginfo
->to
? msginfo
->to
: "",
3432 COMPOSE_TO
, PREF_NONE
);
3433 compose_entry_append(compose
,
3434 msginfo
->cc
? msginfo
->cc
: "",
3435 COMPOSE_CC
, PREF_NONE
);
3440 if (to_sender
|| (compose
->followup_to
&&
3441 !strncmp(compose
->followup_to
, "poster", 6)))
3442 compose_entry_append
3444 (compose
->replyto
? compose
->replyto
:
3445 msginfo
->from
? msginfo
->from
: ""),
3446 COMPOSE_TO
, PREF_NONE
);
3448 else if (followup_and_reply_to
|| to_all
) {
3449 compose_entry_append
3451 (compose
->replyto
? compose
->replyto
:
3452 msginfo
->from
? msginfo
->from
: ""),
3453 COMPOSE_TO
, PREF_NONE
);
3455 compose_entry_append
3457 compose
->followup_to
? compose
->followup_to
:
3458 compose
->newsgroups
? compose
->newsgroups
: "",
3459 COMPOSE_NEWSGROUPS
, PREF_NONE
);
3461 compose_entry_append
3463 msginfo
->cc
? msginfo
->cc
: "",
3464 COMPOSE_CC
, PREF_NONE
);
3467 compose_entry_append
3469 compose
->followup_to
? compose
->followup_to
:
3470 compose
->newsgroups
? compose
->newsgroups
: "",
3471 COMPOSE_NEWSGROUPS
, PREF_NONE
);
3473 compose_reply_set_subject(compose
, msginfo
);
3475 if (to_ml
&& compose
->ml_post
) return;
3476 if (!to_all
|| compose
->account
->protocol
== A_NNTP
) return;
3478 if (compose
->replyto
) {
3479 Xstrdup_a(replyto
, compose
->replyto
, return);
3480 extract_address(replyto
);
3482 if (msginfo
->from
) {
3483 Xstrdup_a(from
, msginfo
->from
, return);
3484 extract_address(from
);
3487 if (replyto
&& from
)
3488 cc_list
= address_list_append_with_comments(cc_list
, from
);
3489 if (to_all
&& msginfo
->folder
&&
3490 msginfo
->folder
->prefs
->enable_default_reply_to
)
3491 cc_list
= address_list_append_with_comments(cc_list
,
3492 msginfo
->folder
->prefs
->default_reply_to
);
3493 cc_list
= address_list_append_with_comments(cc_list
, msginfo
->to
);
3494 cc_list
= address_list_append_with_comments(cc_list
, compose
->cc
);
3496 ac_email
= g_utf8_strdown(compose
->account
->address
, -1);
3499 for (cur
= cc_list
; cur
!= NULL
; cur
= cur
->next
) {
3500 gchar
*addr
= g_utf8_strdown(cur
->data
, -1);
3501 extract_address(addr
);
3503 if (strcmp(ac_email
, addr
))
3504 compose_entry_append(compose
, (gchar
*)cur
->data
,
3505 COMPOSE_CC
, PREF_NONE
);
3507 debug_print("Cc address same as compose account's, ignoring\n");
3512 slist_free_strings_full(cc_list
);
3518 #define SET_ENTRY(entry, str) \
3521 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3524 #define SET_ADDRESS(type, str) \
3527 compose_entry_append(compose, str, type, PREF_NONE); \
3530 static void compose_reedit_set_entry(Compose
*compose
, MsgInfo
*msginfo
)
3532 cm_return_if_fail(msginfo
!= NULL
);
3534 SET_ENTRY(subject_entry
, msginfo
->subject
);
3535 SET_ENTRY(from_name
, msginfo
->from
);
3536 SET_ADDRESS(COMPOSE_TO
, msginfo
->to
);
3537 SET_ADDRESS(COMPOSE_CC
, compose
->cc
);
3538 SET_ADDRESS(COMPOSE_BCC
, compose
->bcc
);
3539 SET_ADDRESS(COMPOSE_REPLYTO
, compose
->replyto
);
3540 SET_ADDRESS(COMPOSE_NEWSGROUPS
, compose
->newsgroups
);
3541 SET_ADDRESS(COMPOSE_FOLLOWUPTO
, compose
->followup_to
);
3543 compose_update_priority_menu_item(compose
);
3544 compose_update_privacy_system_menu_item(compose
, FALSE
);
3545 compose_show_first_last_header(compose
, TRUE
);
3551 static void compose_insert_sig(Compose
*compose
, gboolean replace
)
3553 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
3554 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(text
);
3556 GtkTextIter iter
, iter_end
;
3557 gint cur_pos
, ins_pos
;
3558 gboolean prev_autowrap
;
3559 gboolean found
= FALSE
;
3560 gboolean exists
= FALSE
;
3562 cm_return_if_fail(compose
->account
!= NULL
);
3566 g_signal_handlers_block_by_func(G_OBJECT(buffer
),
3567 G_CALLBACK(compose_changed_cb
),
3570 mark
= gtk_text_buffer_get_insert(buffer
);
3571 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
3572 cur_pos
= gtk_text_iter_get_offset (&iter
);
3575 gtk_text_buffer_get_end_iter(buffer
, &iter
);
3577 exists
= (compose
->sig_str
!= NULL
);
3580 GtkTextIter first_iter
, start_iter
, end_iter
;
3582 gtk_text_buffer_get_start_iter(buffer
, &first_iter
);
3584 if (!exists
|| compose
->sig_str
[0] == '\0')
3587 found
= gtk_text_iter_forward_to_tag_toggle(&first_iter
,
3588 compose
->signature_tag
);
3591 /* include previous \n\n */
3592 gtk_text_iter_backward_chars(&first_iter
, 1);
3593 start_iter
= first_iter
;
3594 end_iter
= first_iter
;
3596 found
= gtk_text_iter_forward_to_tag_toggle(&end_iter
,
3597 compose
->signature_tag
);
3598 found
&= gtk_text_iter_forward_to_tag_toggle(&end_iter
,
3599 compose
->signature_tag
);
3601 gtk_text_buffer_delete(buffer
, &start_iter
, &end_iter
);
3607 g_free(compose
->sig_str
);
3608 compose
->sig_str
= account_get_signature_str(compose
->account
);
3610 cur_pos
= gtk_text_iter_get_offset(&iter
);
3612 if (!compose
->sig_str
|| (replace
&& !compose
->account
->auto_sig
)) {
3613 g_free(compose
->sig_str
);
3614 compose
->sig_str
= NULL
;
3616 if (compose
->sig_inserted
== FALSE
)
3617 gtk_text_buffer_insert(buffer
, &iter
, "\n", -1);
3618 compose
->sig_inserted
= TRUE
;
3620 cur_pos
= gtk_text_iter_get_offset(&iter
);
3621 gtk_text_buffer_insert(buffer
, &iter
, compose
->sig_str
, -1);
3623 gtk_text_buffer_get_iter_at_offset(buffer
, &iter
, cur_pos
);
3624 gtk_text_iter_forward_chars(&iter
, 1);
3625 gtk_text_buffer_get_end_iter(buffer
, &iter_end
);
3626 gtk_text_buffer_apply_tag_by_name(buffer
,"signature",&iter
, &iter_end
);
3628 if (cur_pos
> gtk_text_buffer_get_char_count (buffer
))
3629 cur_pos
= gtk_text_buffer_get_char_count (buffer
);
3632 /* put the cursor where it should be
3633 * either where the quote_fmt says, either where it was */
3634 if (compose
->set_cursor_pos
< 0)
3635 gtk_text_buffer_get_iter_at_offset(buffer
, &iter
, ins_pos
);
3637 gtk_text_buffer_get_iter_at_offset(buffer
, &iter
,
3638 compose
->set_cursor_pos
);
3640 compose
->set_cursor_pos
= -1;
3641 gtk_text_buffer_place_cursor(buffer
, &iter
);
3642 g_signal_handlers_unblock_by_func(G_OBJECT(buffer
),
3643 G_CALLBACK(compose_changed_cb
),
3649 static ComposeInsertResult
compose_insert_file(Compose
*compose
, const gchar
*file
)
3652 GtkTextBuffer
*buffer
;
3655 const gchar
*cur_encoding
;
3656 gchar buf
[BUFFSIZE
];
3659 gboolean prev_autowrap
;
3663 GError
*error
= NULL
;
3669 GString
*file_contents
= NULL
;
3670 ComposeInsertResult result
= COMPOSE_INSERT_SUCCESS
;
3672 cm_return_val_if_fail(file
!= NULL
, COMPOSE_INSERT_NO_FILE
);
3674 /* get the size of the file we are about to insert */
3676 f
= g_file_new_for_path(file
);
3677 fi
= g_file_query_info(f
, "standard::size",
3678 G_FILE_QUERY_INFO_NONE
, NULL
, &error
);
3680 if (error
!= NULL
) {
3681 g_warning(error
->message
);
3683 g_error_free(error
);
3687 ret
= g_stat(file
, &file_stat
);
3690 gchar
*shortfile
= g_path_get_basename(file
);
3691 alertpanel_error(_("Could not get size of file '%s'."), shortfile
);
3693 return COMPOSE_INSERT_NO_FILE
;
3694 } else if (prefs_common
.warn_large_insert
== TRUE
) {
3696 size
= g_file_info_get_size(fi
);
3700 size
= file_stat
.st_size
;
3703 /* ask user for confirmation if the file is large */
3704 if (prefs_common
.warn_large_insert_size
< 0 ||
3705 size
> ((goffset
) prefs_common
.warn_large_insert_size
* 1024)) {
3709 msg
= g_strdup_printf(_("You are about to insert a file of %s "
3710 "in the message body. Are you sure you want to do that?"),
3711 to_human_readable(size
));
3712 aval
= alertpanel_full(_("Are you sure?"), msg
, NULL
, _("_Cancel"),
3713 NULL
, _("_Insert"), NULL
, NULL
, ALERTFOCUS_SECOND
, TRUE
,
3714 NULL
, ALERT_QUESTION
);
3717 /* do we ask for confirmation next time? */
3718 if (aval
& G_ALERTDISABLE
) {
3719 /* no confirmation next time, disable feature in preferences */
3720 aval
&= ~G_ALERTDISABLE
;
3721 prefs_common
.warn_large_insert
= FALSE
;
3724 /* abort file insertion if user canceled action */
3725 if (aval
!= G_ALERTALTERNATE
) {
3726 return COMPOSE_INSERT_NO_FILE
;
3732 if ((fp
= claws_fopen(file
, "rb")) == NULL
) {
3733 FILE_OP_ERROR(file
, "claws_fopen");
3734 return COMPOSE_INSERT_READ_ERROR
;
3737 prev_autowrap
= compose
->autowrap
;
3738 compose
->autowrap
= FALSE
;
3740 text
= GTK_TEXT_VIEW(compose
->text
);
3741 buffer
= gtk_text_view_get_buffer(text
);
3742 mark
= gtk_text_buffer_get_insert(buffer
);
3743 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
3745 g_signal_handlers_block_by_func(G_OBJECT(buffer
),
3746 G_CALLBACK(text_inserted
),
3749 cur_encoding
= conv_get_locale_charset_str_no_utf8();
3751 file_contents
= g_string_new("");
3752 while (claws_fgets(buf
, sizeof(buf
), fp
) != NULL
) {
3755 if (g_utf8_validate(buf
, -1, NULL
) == TRUE
)
3756 str
= g_strdup(buf
);
3758 codeconv_set_strict(TRUE
);
3759 str
= conv_codeset_strdup
3760 (buf
, cur_encoding
, CS_INTERNAL
);
3761 codeconv_set_strict(FALSE
);
3764 result
= COMPOSE_INSERT_INVALID_CHARACTER
;
3770 /* strip <CR> if DOS/Windows file,
3771 replace <CR> with <LF> if Macintosh file. */
3774 if (len
> 0 && str
[len
- 1] != '\n') {
3776 if (str
[len
] == '\r') str
[len
] = '\n';
3779 file_contents
= g_string_append(file_contents
, str
);
3783 if (result
== COMPOSE_INSERT_SUCCESS
) {
3784 gtk_text_buffer_insert(buffer
, &iter
, file_contents
->str
, -1);
3786 compose_changed_cb(NULL
, compose
);
3787 g_signal_handlers_unblock_by_func(G_OBJECT(buffer
),
3788 G_CALLBACK(text_inserted
),
3790 compose
->autowrap
= prev_autowrap
;
3791 if (compose
->autowrap
)
3792 compose_wrap_all(compose
);
3794 gchar
*filename
= g_path_get_basename(file
);
3795 debug_print("Can't insert file '%s' (invalid character)\n", filename
);
3799 g_string_free(file_contents
, TRUE
);
3805 static gboolean
compose_attach_append(Compose
*compose
, const gchar
*file
,
3806 const gchar
*filename
,
3807 const gchar
*content_type
,
3808 const gchar
*charset
)
3816 GtkListStore
*store
;
3818 gboolean has_binary
= FALSE
;
3820 if (!is_file_exist(file
)) {
3821 gchar
*file_from_uri
= g_filename_from_uri(file
, NULL
, NULL
);
3822 gboolean result
= FALSE
;
3823 if (file_from_uri
&& is_file_exist(file_from_uri
)) {
3824 result
= compose_attach_append(
3825 compose
, file_from_uri
,
3826 filename
, content_type
,
3829 g_free(file_from_uri
);
3832 alertpanel_error("File %s doesn't exist or permission denied\n", filename
);
3835 if ((size
= get_file_size(file
)) < 0) {
3836 alertpanel_error("Can't get file size of %s\n", filename
);
3840 /* In batch mode, we allow 0-length files to be attached no questions asked */
3841 if (size
== 0 && !compose
->batch
) {
3842 gchar
* msg
= g_strdup_printf(_("File %s is empty."), filename
);
3843 AlertValue aval
= alertpanel_full(_("Empty file"), msg
,
3844 NULL
, _("_Cancel"), NULL
, _("_Attach anyway"),
3845 NULL
, NULL
, ALERTFOCUS_SECOND
, FALSE
, NULL
, ALERT_WARNING
);
3848 if (aval
!= G_ALERTALTERNATE
) {
3852 if ((fp
= claws_fopen(file
, "rb")) == NULL
) {
3853 alertpanel_error(_("Can't read %s."), filename
);
3858 ainfo
= g_new0(AttachInfo
, 1);
3859 auto_ainfo
= g_auto_pointer_new_with_free
3860 (ainfo
, (GFreeFunc
) compose_attach_info_free
);
3861 ainfo
->file
= g_strdup(file
);
3864 ainfo
->content_type
= g_strdup(content_type
);
3865 if (!g_ascii_strcasecmp(content_type
, "message/rfc822")) {
3867 MsgFlags flags
= {0, 0};
3869 if (procmime_get_encoding_for_text_file(file
, &has_binary
) == ENC_7BIT
)
3870 ainfo
->encoding
= ENC_7BIT
;
3872 ainfo
->encoding
= ENC_8BIT
;
3874 msginfo
= procheader_parse_file(file
, flags
, FALSE
, FALSE
);
3875 if (msginfo
&& msginfo
->subject
)
3876 name
= g_strdup(msginfo
->subject
);
3878 name
= g_path_get_basename(filename
? filename
: file
);
3880 ainfo
->name
= g_strdup_printf(_("Message: %s"), name
);
3882 procmsg_msginfo_free(&msginfo
);
3884 if (!g_ascii_strncasecmp(content_type
, "text/", 5)) {
3885 ainfo
->charset
= g_strdup(charset
);
3886 ainfo
->encoding
= procmime_get_encoding_for_text_file(file
, &has_binary
);
3888 ainfo
->encoding
= ENC_BASE64
;
3890 name
= g_path_get_basename(filename
? filename
: file
);
3891 ainfo
->name
= g_strdup(name
);
3895 ainfo
->content_type
= procmime_get_mime_type(file
);
3896 if (!ainfo
->content_type
) {
3897 ainfo
->content_type
=
3898 g_strdup("application/octet-stream");
3899 ainfo
->encoding
= ENC_BASE64
;
3900 } else if (!g_ascii_strncasecmp(ainfo
->content_type
, "text/", 5))
3902 procmime_get_encoding_for_text_file(file
, &has_binary
);
3904 ainfo
->encoding
= ENC_BASE64
;
3905 name
= g_path_get_basename(filename
? filename
: file
);
3906 ainfo
->name
= g_strdup(name
);
3910 if (ainfo
->name
!= NULL
3911 && !strcmp(ainfo
->name
, ".")) {
3912 g_free(ainfo
->name
);
3916 if (!strcmp(ainfo
->content_type
, "unknown") || has_binary
) {
3917 g_free(ainfo
->content_type
);
3918 ainfo
->content_type
= g_strdup("application/octet-stream");
3919 g_free(ainfo
->charset
);
3920 ainfo
->charset
= NULL
;
3923 ainfo
->size
= (goffset
)size
;
3924 size_text
= to_human_readable((goffset
)size
);
3926 store
= GTK_LIST_STORE(gtk_tree_view_get_model
3927 (GTK_TREE_VIEW(compose
->attach_clist
)));
3929 gtk_list_store_append(store
, &iter
);
3930 gtk_list_store_set(store
, &iter
,
3931 COL_MIMETYPE
, ainfo
->content_type
,
3932 COL_SIZE
, size_text
,
3933 COL_NAME
, ainfo
->name
,
3934 COL_CHARSET
, ainfo
->charset
,
3936 COL_AUTODATA
, auto_ainfo
,
3939 g_auto_pointer_free(auto_ainfo
);
3940 compose_attach_update_label(compose
);
3944 void compose_use_signing(Compose
*compose
, gboolean use_signing
)
3946 compose
->use_signing
= use_signing
;
3947 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/Sign", use_signing
);
3950 void compose_use_encryption(Compose
*compose
, gboolean use_encryption
)
3952 compose
->use_encryption
= use_encryption
;
3953 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/Encrypt", use_encryption
);
3956 #define NEXT_PART_NOT_CHILD(info) \
3958 node = info->node; \
3959 while (node->children) \
3960 node = g_node_last_child(node); \
3961 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3964 static void compose_attach_parts(Compose
*compose
, MsgInfo
*msginfo
)
3968 MimeInfo
*firsttext
= NULL
;
3969 MimeInfo
*encrypted
= NULL
;
3972 const gchar
*partname
= NULL
;
3974 mimeinfo
= procmime_scan_message(msginfo
);
3975 if (!mimeinfo
) return;
3977 if (mimeinfo
->node
->children
== NULL
) {
3978 procmime_mimeinfo_free_all(&mimeinfo
);
3982 /* find first content part */
3983 child
= (MimeInfo
*) mimeinfo
->node
->children
->data
;
3984 while (child
&& child
->node
->children
&& (child
->type
== MIMETYPE_MULTIPART
))
3985 child
= (MimeInfo
*)child
->node
->children
->data
;
3988 if (child
->type
== MIMETYPE_TEXT
) {
3990 debug_print("First text part found\n");
3991 } else if (compose
->mode
== COMPOSE_REEDIT
&&
3992 child
->type
== MIMETYPE_APPLICATION
&&
3993 !g_ascii_strcasecmp(child
->subtype
, "pgp-encrypted")) {
3994 encrypted
= (MimeInfo
*)child
->node
->parent
->data
;
3997 child
= (MimeInfo
*) mimeinfo
->node
->children
->data
;
3998 while (child
!= NULL
) {
4001 if (child
== encrypted
) {
4002 /* skip this part of tree */
4003 NEXT_PART_NOT_CHILD(child
);
4007 if (child
->type
== MIMETYPE_MULTIPART
) {
4008 /* get the actual content */
4009 child
= procmime_mimeinfo_next(child
);
4013 if (child
== firsttext
) {
4014 child
= procmime_mimeinfo_next(child
);
4018 outfile
= procmime_get_tmp_file_name(child
);
4019 if ((err
= procmime_get_part(outfile
, child
)) < 0)
4020 g_warning("can't get the part of multipart message. (%s)", g_strerror(-err
));
4022 gchar
*content_type
;
4024 content_type
= procmime_get_content_type_str(child
->type
, child
->subtype
);
4026 /* if we meet a pgp signature, we don't attach it, but
4027 * we force signing. */
4028 if ((strcmp(content_type
, "application/pgp-signature") &&
4029 strcmp(content_type
, "application/pkcs7-signature") &&
4030 strcmp(content_type
, "application/x-pkcs7-signature"))
4031 || compose
->mode
== COMPOSE_REDIRECT
) {
4032 partname
= procmime_mimeinfo_get_parameter(child
, "filename");
4033 if (partname
== NULL
)
4034 partname
= procmime_mimeinfo_get_parameter(child
, "name");
4035 if (partname
== NULL
)
4037 compose_attach_append(compose
, outfile
,
4038 partname
, content_type
,
4039 procmime_mimeinfo_get_parameter(child
, "charset"));
4041 compose_force_signing(compose
, compose
->account
, NULL
);
4043 g_free(content_type
);
4046 NEXT_PART_NOT_CHILD(child
);
4048 procmime_mimeinfo_free_all(&mimeinfo
);
4051 #undef NEXT_PART_NOT_CHILD
4056 WAIT_FOR_INDENT_CHAR
,
4057 WAIT_FOR_INDENT_CHAR_OR_SPACE
,
4060 /* return indent length, we allow:
4061 indent characters followed by indent characters or spaces/tabs,
4062 alphabets and numbers immediately followed by indent characters,
4063 and the repeating sequences of the above
4064 If quote ends with multiple spaces, only the first one is included. */
4065 static gchar
*compose_get_quote_str(GtkTextBuffer
*buffer
,
4066 const GtkTextIter
*start
, gint
*len
)
4068 GtkTextIter iter
= *start
;
4072 IndentState state
= WAIT_FOR_INDENT_CHAR
;
4075 gint alnum_count
= 0;
4076 gint space_count
= 0;
4079 if (prefs_common
.quote_chars
== NULL
) {
4083 while (!gtk_text_iter_ends_line(&iter
)) {
4084 wc
= gtk_text_iter_get_char(&iter
);
4085 if (g_unichar_iswide(wc
))
4087 clen
= g_unichar_to_utf8(wc
, ch
);
4091 is_indent
= strchr(prefs_common
.quote_chars
, ch
[0]) ? TRUE
: FALSE
;
4092 is_space
= g_unichar_isspace(wc
);
4094 if (state
== WAIT_FOR_INDENT_CHAR
) {
4095 if (!is_indent
&& !g_unichar_isalnum(wc
))
4098 quote_len
+= alnum_count
+ space_count
+ 1;
4099 alnum_count
= space_count
= 0;
4100 state
= WAIT_FOR_INDENT_CHAR_OR_SPACE
;
4103 } else if (state
== WAIT_FOR_INDENT_CHAR_OR_SPACE
) {
4104 if (!is_indent
&& !is_space
&& !g_unichar_isalnum(wc
))
4108 else if (is_indent
) {
4109 quote_len
+= alnum_count
+ space_count
+ 1;
4110 alnum_count
= space_count
= 0;
4113 state
= WAIT_FOR_INDENT_CHAR
;
4117 gtk_text_iter_forward_char(&iter
);
4120 if (quote_len
> 0 && space_count
> 0)
4126 if (quote_len
> 0) {
4128 gtk_text_iter_forward_chars(&iter
, quote_len
);
4129 return gtk_text_buffer_get_text(buffer
, start
, &iter
, FALSE
);
4135 /* return >0 if the line is itemized */
4136 static int compose_itemized_length(GtkTextBuffer
*buffer
,
4137 const GtkTextIter
*start
)
4139 GtkTextIter iter
= *start
;
4144 if (gtk_text_iter_ends_line(&iter
))
4149 wc
= gtk_text_iter_get_char(&iter
);
4150 if (!g_unichar_isspace(wc
))
4152 gtk_text_iter_forward_char(&iter
);
4153 if (gtk_text_iter_ends_line(&iter
))
4157 clen
= g_unichar_to_utf8(wc
, ch
);
4158 if (!((clen
== 1 && strchr("*-+", ch
[0])) ||
4160 wc
== 0x2022 || /* BULLET */
4161 wc
== 0x2023 || /* TRIANGULAR BULLET */
4162 wc
== 0x2043 || /* HYPHEN BULLET */
4163 wc
== 0x204c || /* BLACK LEFTWARDS BULLET */
4164 wc
== 0x204d || /* BLACK RIGHTWARDS BULLET */
4165 wc
== 0x2219 || /* BULLET OPERATOR */
4166 wc
== 0x25d8 || /* INVERSE BULLET */
4167 wc
== 0x25e6 || /* WHITE BULLET */
4168 wc
== 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4169 wc
== 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4170 wc
== 0x2767 || /* ROTATED FLORAL HEART BULLET */
4171 wc
== 0x29be || /* CIRCLED WHITE BULLET */
4172 wc
== 0x29bf /* CIRCLED BULLET */
4176 gtk_text_iter_forward_char(&iter
);
4177 if (gtk_text_iter_ends_line(&iter
))
4179 wc
= gtk_text_iter_get_char(&iter
);
4180 if (g_unichar_isspace(wc
)) {
4186 /* return the string at the start of the itemization */
4187 static gchar
* compose_get_itemized_chars(GtkTextBuffer
*buffer
,
4188 const GtkTextIter
*start
)
4190 GtkTextIter iter
= *start
;
4193 GString
*item_chars
= g_string_new("");
4195 if (gtk_text_iter_ends_line(&iter
)) {
4196 g_string_free(item_chars
, TRUE
);
4202 wc
= gtk_text_iter_get_char(&iter
);
4203 if (!g_unichar_isspace(wc
))
4205 gtk_text_iter_forward_char(&iter
);
4206 if (gtk_text_iter_ends_line(&iter
))
4208 g_string_append_unichar(item_chars
, wc
);
4211 return g_string_free(item_chars
, FALSE
);
4214 /* return the number of spaces at a line's start */
4215 static int compose_left_offset_length(GtkTextBuffer
*buffer
,
4216 const GtkTextIter
*start
)
4218 GtkTextIter iter
= *start
;
4221 if (gtk_text_iter_ends_line(&iter
))
4225 wc
= gtk_text_iter_get_char(&iter
);
4226 if (!g_unichar_isspace(wc
))
4229 gtk_text_iter_forward_char(&iter
);
4230 if (gtk_text_iter_ends_line(&iter
))
4234 gtk_text_iter_forward_char(&iter
);
4235 if (gtk_text_iter_ends_line(&iter
))
4240 static gboolean
compose_get_line_break_pos(GtkTextBuffer
*buffer
,
4241 const GtkTextIter
*start
,
4242 GtkTextIter
*break_pos
,
4246 GtkTextIter iter
= *start
, line_end
= *start
;
4247 PangoLogAttr
*attrs
;
4254 gboolean can_break
= FALSE
;
4255 gboolean do_break
= FALSE
;
4256 gboolean was_white
= FALSE
;
4257 gboolean prev_dont_break
= FALSE
;
4259 gtk_text_iter_forward_to_line_end(&line_end
);
4260 str
= gtk_text_buffer_get_text(buffer
, &iter
, &line_end
, FALSE
);
4261 len
= g_utf8_strlen(str
, -1);
4265 g_warning("compose_get_line_break_pos: len = 0!");
4269 /* g_print("breaking line: %d: %s (len = %d)\n",
4270 gtk_text_iter_get_line(&iter), str, len); */
4272 attrs
= g_new(PangoLogAttr
, len
+ 1);
4274 pango_default_break(str
, -1, NULL
, attrs
, len
+ 1);
4278 /* skip quote and leading spaces */
4279 for (i
= 0; *p
!= '\0' && i
< len
; i
++) {
4282 wc
= g_utf8_get_char(p
);
4283 if (i
>= quote_len
&& !g_unichar_isspace(wc
))
4285 if (g_unichar_iswide(wc
))
4287 else if (*p
== '\t')
4291 p
= g_utf8_next_char(p
);
4294 for (; *p
!= '\0' && i
< len
; i
++) {
4295 PangoLogAttr
*attr
= attrs
+ i
;
4296 gunichar wc
= g_utf8_get_char(p
);
4299 /* attr->is_line_break will be false for some characters that
4300 * we want to break a line before, like '/' or ':', so we
4301 * also allow breaking on any non-wide character. The
4302 * mentioned pango attribute is still useful to decide on
4303 * line breaks when wide characters are involved. */
4304 if ((!g_unichar_iswide(wc
) || attr
->is_line_break
)
4305 && can_break
&& was_white
&& !prev_dont_break
)
4308 was_white
= attr
->is_white
;
4310 /* don't wrap URI */
4311 if ((uri_len
= get_uri_len(p
)) > 0) {
4313 if (pos
> 0 && col
> max_col
) {
4323 if (g_unichar_iswide(wc
)) {
4325 if (prev_dont_break
&& can_break
&& attr
->is_line_break
)
4327 } else if (*p
== '\t')
4331 if (pos
> 0 && col
> max_col
) {
4336 if (*p
== '-' || *p
== '/')
4337 prev_dont_break
= TRUE
;
4339 prev_dont_break
= FALSE
;
4341 p
= g_utf8_next_char(p
);
4345 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4350 *break_pos
= *start
;
4351 gtk_text_iter_set_line_offset(break_pos
, pos
);
4356 static gboolean
compose_join_next_line(Compose
*compose
,
4357 GtkTextBuffer
*buffer
,
4359 const gchar
*quote_str
)
4361 GtkTextIter iter_
= *iter
, cur
, prev
, next
, end
;
4362 PangoLogAttr attrs
[3];
4364 gchar
*next_quote_str
;
4367 gboolean keep_cursor
= FALSE
;
4369 if (!gtk_text_iter_forward_line(&iter_
) ||
4370 gtk_text_iter_ends_line(&iter_
)) {
4373 next_quote_str
= compose_get_quote_str(buffer
, &iter_
, "e_len
);
4375 if ((quote_str
|| next_quote_str
) &&
4376 g_strcmp0(quote_str
, next_quote_str
) != 0) {
4377 g_free(next_quote_str
);
4380 g_free(next_quote_str
);
4383 if (quote_len
> 0) {
4384 gtk_text_iter_forward_chars(&end
, quote_len
);
4385 if (gtk_text_iter_ends_line(&end
)) {
4390 /* don't join itemized lines */
4391 if (compose_itemized_length(buffer
, &end
) > 0) {
4395 /* don't join signature separator */
4396 if (compose_is_sig_separator(compose
, buffer
, &iter_
)) {
4399 /* delete quote str */
4401 gtk_text_buffer_delete(buffer
, &iter_
, &end
);
4403 /* don't join line breaks put by the user */
4405 gtk_text_iter_backward_char(&cur
);
4406 if (gtk_text_iter_has_tag(&cur
, compose
->no_join_tag
)) {
4407 gtk_text_iter_forward_char(&cur
);
4411 gtk_text_iter_forward_char(&cur
);
4412 /* delete linebreak and extra spaces */
4413 while (gtk_text_iter_backward_char(&cur
)) {
4414 wc1
= gtk_text_iter_get_char(&cur
);
4415 if (!g_unichar_isspace(wc1
))
4420 while (!gtk_text_iter_ends_line(&cur
)) {
4421 wc1
= gtk_text_iter_get_char(&cur
);
4422 if (!g_unichar_isspace(wc1
))
4424 gtk_text_iter_forward_char(&cur
);
4427 if (!gtk_text_iter_equal(&prev
, &next
)) {
4430 mark
= gtk_text_buffer_get_insert(buffer
);
4431 gtk_text_buffer_get_iter_at_mark(buffer
, &cur
, mark
);
4432 if (gtk_text_iter_equal(&prev
, &cur
))
4434 gtk_text_buffer_delete(buffer
, &prev
, &next
);
4438 /* insert space if required */
4439 gtk_text_iter_backward_char(&prev
);
4440 wc1
= gtk_text_iter_get_char(&prev
);
4441 wc2
= gtk_text_iter_get_char(&next
);
4442 gtk_text_iter_forward_char(&next
);
4443 str
= gtk_text_buffer_get_text(buffer
, &prev
, &next
, FALSE
);
4444 pango_default_break(str
, -1, NULL
, attrs
, 3);
4445 if (!attrs
[1].is_line_break
||
4446 (!g_unichar_iswide(wc1
) || !g_unichar_iswide(wc2
))) {
4447 gtk_text_buffer_insert(buffer
, &iter_
, " ", 1);
4449 gtk_text_iter_backward_char(&iter_
);
4450 gtk_text_buffer_place_cursor(buffer
, &iter_
);
4459 #define ADD_TXT_POS(bp_, ep_, pti_) \
4460 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4461 last = last->next; \
4462 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4463 last->next = NULL; \
4465 g_warning("alloc error scanning URIs"); \
4468 static gboolean
compose_beautify_paragraph(Compose
*compose
, GtkTextIter
*par_iter
, gboolean force
)
4470 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
4471 GtkTextBuffer
*buffer
;
4472 GtkTextIter iter
, break_pos
, end_of_line
;
4473 gchar
*quote_str
= NULL
;
4475 gboolean wrap_quote
= force
|| prefs_common
.linewrap_quote
;
4476 gboolean prev_autowrap
= compose
->autowrap
;
4477 gint startq_offset
= -1, noq_offset
= -1;
4478 gint uri_start
= -1, uri_stop
= -1;
4479 gint nouri_start
= -1, nouri_stop
= -1;
4480 gint num_blocks
= 0;
4481 gint quotelevel
= -1;
4482 gboolean modified
= force
;
4483 gboolean removed
= FALSE
;
4484 gboolean modified_before_remove
= FALSE
;
4486 gboolean start
= TRUE
;
4487 gint itemized_len
= 0, rem_item_len
= 0;
4488 gchar
*itemized_chars
= NULL
;
4489 gboolean item_continuation
= FALSE
;
4494 if (compose
->draft_timeout_tag
== COMPOSE_DRAFT_TIMEOUT_FORBIDDEN
) {
4498 compose
->autowrap
= FALSE
;
4500 buffer
= gtk_text_view_get_buffer(text
);
4501 undo_wrapping(compose
->undostruct
, TRUE
);
4506 mark
= gtk_text_buffer_get_insert(buffer
);
4507 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
4511 if (compose
->draft_timeout_tag
== COMPOSE_DRAFT_TIMEOUT_FORBIDDEN
) {
4512 if (gtk_text_iter_ends_line(&iter
)) {
4513 while (gtk_text_iter_ends_line(&iter
) &&
4514 gtk_text_iter_forward_line(&iter
))
4517 while (gtk_text_iter_backward_line(&iter
)) {
4518 if (gtk_text_iter_ends_line(&iter
)) {
4519 gtk_text_iter_forward_line(&iter
);
4525 /* move to line start */
4526 gtk_text_iter_set_line_offset(&iter
, 0);
4529 itemized_len
= compose_itemized_length(buffer
, &iter
);
4531 if (!itemized_len
) {
4532 itemized_len
= compose_left_offset_length(buffer
, &iter
);
4533 item_continuation
= TRUE
;
4537 itemized_chars
= compose_get_itemized_chars(buffer
, &iter
);
4539 /* go until paragraph end (empty line) */
4540 while (start
|| !gtk_text_iter_ends_line(&iter
)) {
4541 gchar
*scanpos
= NULL
;
4542 /* parse table - in order of priority */
4544 const gchar
*needle
; /* token */
4546 /* token search function */
4547 gchar
*(*search
) (const gchar
*haystack
,
4548 const gchar
*needle
);
4549 /* part parsing function */
4550 gboolean (*parse
) (const gchar
*start
,
4551 const gchar
*scanpos
,
4555 /* part to URI function */
4556 gchar
*(*build_uri
) (const gchar
*bp
,
4560 static struct table parser
[] = {
4561 {"http://", strcasestr
, get_uri_part
, make_uri_string
},
4562 {"https://", strcasestr
, get_uri_part
, make_uri_string
},
4563 {"ftp://", strcasestr
, get_uri_part
, make_uri_string
},
4564 {"ftps://", strcasestr
, get_uri_part
, make_uri_string
},
4565 {"sftp://", strcasestr
, get_uri_part
, make_uri_string
},
4566 {"gopher://",strcasestr
, get_uri_part
, make_uri_string
},
4567 {"www.", strcasestr
, get_uri_part
, make_http_string
},
4568 {"webcal://",strcasestr
, get_uri_part
, make_uri_string
},
4569 {"webcals://",strcasestr
, get_uri_part
, make_uri_string
},
4570 {"mailto:", strcasestr
, get_uri_part
, make_uri_string
},
4571 {"@", strcasestr
, get_email_part
, make_email_string
}
4573 const gint PARSE_ELEMS
= sizeof parser
/ sizeof parser
[0];
4574 gint last_index
= PARSE_ELEMS
;
4576 gchar
*o_walk
= NULL
, *walk
= NULL
, *bp
= NULL
, *ep
= NULL
;
4580 if (!prev_autowrap
&& num_blocks
== 0) {
4582 g_signal_handlers_block_by_func(G_OBJECT(buffer
),
4583 G_CALLBACK(text_inserted
),
4586 if (gtk_text_iter_has_tag(&iter
, compose
->no_wrap_tag
) && !force
)
4589 uri_start
= uri_stop
= -1;
4591 quote_str
= compose_get_quote_str(buffer
, &iter
, "e_len
);
4594 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4595 if (startq_offset
== -1)
4596 startq_offset
= gtk_text_iter_get_offset(&iter
);
4597 quotelevel
= get_quote_level(quote_str
, prefs_common
.quote_chars
);
4598 if (quotelevel
> 2) {
4599 /* recycle colors */
4600 if (prefs_common
.recycle_quote_colors
)
4609 if (startq_offset
== -1)
4610 noq_offset
= gtk_text_iter_get_offset(&iter
);
4614 if (prev_autowrap
== FALSE
&& !force
&& !wrap_quote
) {
4617 if (gtk_text_iter_ends_line(&iter
)) {
4619 } else if (compose_get_line_break_pos(buffer
, &iter
, &break_pos
,
4620 prefs_common
.linewrap_len
,
4622 GtkTextIter prev
, next
, cur
;
4623 if (prev_autowrap
!= FALSE
|| force
) {
4624 compose
->automatic_break
= TRUE
;
4626 gtk_text_buffer_insert(buffer
, &break_pos
, "\n", 1);
4627 compose
->automatic_break
= FALSE
;
4628 if (itemized_len
&& compose
->autoindent
) {
4629 gtk_text_buffer_insert(buffer
, &break_pos
, itemized_chars
, -1);
4630 if (!item_continuation
)
4631 gtk_text_buffer_insert(buffer
, &break_pos
, " ", 2);
4633 } else if (quote_str
&& wrap_quote
) {
4634 compose
->automatic_break
= TRUE
;
4636 gtk_text_buffer_insert(buffer
, &break_pos
, "\n", 1);
4637 compose
->automatic_break
= FALSE
;
4638 if (itemized_len
&& compose
->autoindent
) {
4639 gtk_text_buffer_insert(buffer
, &break_pos
, itemized_chars
, -1);
4640 if (!item_continuation
)
4641 gtk_text_buffer_insert(buffer
, &break_pos
, " ", 2);
4645 /* remove trailing spaces */
4647 rem_item_len
= itemized_len
;
4648 while (compose
->autoindent
&& rem_item_len
-- > 0)
4649 gtk_text_iter_backward_char(&cur
);
4650 gtk_text_iter_backward_char(&cur
);
4653 while (!gtk_text_iter_starts_line(&cur
)) {
4656 gtk_text_iter_backward_char(&cur
);
4657 wc
= gtk_text_iter_get_char(&cur
);
4658 if (!g_unichar_isspace(wc
))
4662 if (!gtk_text_iter_equal(&prev
, &next
)) {
4663 gtk_text_buffer_delete(buffer
, &prev
, &next
);
4665 gtk_text_iter_forward_char(&break_pos
);
4669 gtk_text_buffer_insert(buffer
, &break_pos
,
4673 modified
|= compose_join_next_line(compose
, buffer
, &iter
, quote_str
);
4675 /* move iter to current line start */
4676 gtk_text_iter_set_line_offset(&iter
, 0);
4683 /* move iter to next line start */
4689 if (!prev_autowrap
&& num_blocks
> 0) {
4691 g_signal_handlers_unblock_by_func(G_OBJECT(buffer
),
4692 G_CALLBACK(text_inserted
),
4696 while (!gtk_text_iter_ends_line(&end_of_line
)) {
4697 gtk_text_iter_forward_char(&end_of_line
);
4699 o_walk
= walk
= gtk_text_buffer_get_text(buffer
, &iter
, &end_of_line
, FALSE
);
4701 nouri_start
= gtk_text_iter_get_offset(&iter
);
4702 nouri_stop
= gtk_text_iter_get_offset(&end_of_line
);
4704 walk_pos
= gtk_text_iter_get_offset(&iter
);
4705 /* FIXME: this looks phony. scanning for anything in the parse table */
4706 for (n
= 0; n
< PARSE_ELEMS
; n
++) {
4709 tmp
= parser
[n
].search(walk
, parser
[n
].needle
);
4711 if (scanpos
== NULL
|| tmp
< scanpos
) {
4720 /* check if URI can be parsed */
4721 if (parser
[last_index
].parse(walk
, scanpos
, (const gchar
**)&bp
,
4722 (const gchar
**)&ep
, FALSE
)
4723 && (size_t) (ep
- bp
- 1) > strlen(parser
[last_index
].needle
)) {
4727 strlen(parser
[last_index
].needle
);
4730 uri_start
= walk_pos
+ (bp
- o_walk
);
4731 uri_stop
= walk_pos
+ (ep
- o_walk
);
4735 gtk_text_iter_forward_line(&iter
);
4738 if (startq_offset
!= -1) {
4739 GtkTextIter startquote
, endquote
;
4740 gtk_text_buffer_get_iter_at_offset(
4741 buffer
, &startquote
, startq_offset
);
4744 switch (quotelevel
) {
4746 if (!gtk_text_iter_has_tag(&startquote
, compose
->quote0_tag
) ||
4747 !gtk_text_iter_has_tag(&end_of_line
, compose
->quote0_tag
)) {
4748 gtk_text_buffer_apply_tag_by_name(
4749 buffer
, "quote0", &startquote
, &endquote
);
4750 gtk_text_buffer_remove_tag_by_name(
4751 buffer
, "quote1", &startquote
, &endquote
);
4752 gtk_text_buffer_remove_tag_by_name(
4753 buffer
, "quote2", &startquote
, &endquote
);
4758 if (!gtk_text_iter_has_tag(&startquote
, compose
->quote1_tag
) ||
4759 !gtk_text_iter_has_tag(&end_of_line
, compose
->quote1_tag
)) {
4760 gtk_text_buffer_apply_tag_by_name(
4761 buffer
, "quote1", &startquote
, &endquote
);
4762 gtk_text_buffer_remove_tag_by_name(
4763 buffer
, "quote0", &startquote
, &endquote
);
4764 gtk_text_buffer_remove_tag_by_name(
4765 buffer
, "quote2", &startquote
, &endquote
);
4770 if (!gtk_text_iter_has_tag(&startquote
, compose
->quote2_tag
) ||
4771 !gtk_text_iter_has_tag(&end_of_line
, compose
->quote2_tag
)) {
4772 gtk_text_buffer_apply_tag_by_name(
4773 buffer
, "quote2", &startquote
, &endquote
);
4774 gtk_text_buffer_remove_tag_by_name(
4775 buffer
, "quote0", &startquote
, &endquote
);
4776 gtk_text_buffer_remove_tag_by_name(
4777 buffer
, "quote1", &startquote
, &endquote
);
4783 } else if (noq_offset
!= -1) {
4784 GtkTextIter startnoquote
, endnoquote
;
4785 gtk_text_buffer_get_iter_at_offset(
4786 buffer
, &startnoquote
, noq_offset
);
4789 if ((gtk_text_iter_has_tag(&startnoquote
, compose
->quote0_tag
)
4790 && gtk_text_iter_has_tag(&end_of_line
, compose
->quote0_tag
)) ||
4791 (gtk_text_iter_has_tag(&startnoquote
, compose
->quote1_tag
)
4792 && gtk_text_iter_has_tag(&end_of_line
, compose
->quote1_tag
)) ||
4793 (gtk_text_iter_has_tag(&startnoquote
, compose
->quote2_tag
)
4794 && gtk_text_iter_has_tag(&end_of_line
, compose
->quote2_tag
))) {
4795 gtk_text_buffer_remove_tag_by_name(
4796 buffer
, "quote0", &startnoquote
, &endnoquote
);
4797 gtk_text_buffer_remove_tag_by_name(
4798 buffer
, "quote1", &startnoquote
, &endnoquote
);
4799 gtk_text_buffer_remove_tag_by_name(
4800 buffer
, "quote2", &startnoquote
, &endnoquote
);
4806 if (uri_start
!= nouri_start
&& uri_stop
!= nouri_stop
) {
4807 GtkTextIter nouri_start_iter
, nouri_end_iter
;
4808 gtk_text_buffer_get_iter_at_offset(
4809 buffer
, &nouri_start_iter
, nouri_start
);
4810 gtk_text_buffer_get_iter_at_offset(
4811 buffer
, &nouri_end_iter
, nouri_stop
);
4812 if (gtk_text_iter_has_tag(&nouri_start_iter
, compose
->uri_tag
) &&
4813 gtk_text_iter_has_tag(&nouri_end_iter
, compose
->uri_tag
)) {
4814 gtk_text_buffer_remove_tag_by_name(
4815 buffer
, "link", &nouri_start_iter
, &nouri_end_iter
);
4816 modified_before_remove
= modified
;
4821 if (uri_start
>= 0 && uri_stop
> 0) {
4822 GtkTextIter uri_start_iter
, uri_end_iter
, back
;
4823 gtk_text_buffer_get_iter_at_offset(
4824 buffer
, &uri_start_iter
, uri_start
);
4825 gtk_text_buffer_get_iter_at_offset(
4826 buffer
, &uri_end_iter
, uri_stop
);
4827 back
= uri_end_iter
;
4828 gtk_text_iter_backward_char(&back
);
4829 if (!gtk_text_iter_has_tag(&uri_start_iter
, compose
->uri_tag
) ||
4830 !gtk_text_iter_has_tag(&back
, compose
->uri_tag
)) {
4831 gtk_text_buffer_apply_tag_by_name(
4832 buffer
, "link", &uri_start_iter
, &uri_end_iter
);
4834 if (removed
&& !modified_before_remove
) {
4840 /* debug_print("not modified, out after %d lines\n", lines); */
4844 /* debug_print("modified, out after %d lines\n", lines); */
4846 g_free(itemized_chars
);
4849 undo_wrapping(compose
->undostruct
, FALSE
);
4850 compose
->autowrap
= prev_autowrap
;
4855 void compose_action_cb(void *data
)
4857 Compose
*compose
= (Compose
*)data
;
4858 compose_wrap_all(compose
);
4861 static void compose_wrap_all(Compose
*compose
)
4863 compose_wrap_all_full(compose
, FALSE
);
4866 static void compose_wrap_all_full(Compose
*compose
, gboolean force
)
4868 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
4869 GtkTextBuffer
*buffer
;
4871 gboolean modified
= TRUE
;
4873 buffer
= gtk_text_view_get_buffer(text
);
4875 gtk_text_buffer_get_start_iter(buffer
, &iter
);
4877 undo_wrapping(compose
->undostruct
, TRUE
);
4879 while (!gtk_text_iter_is_end(&iter
) && modified
)
4880 modified
= compose_beautify_paragraph(compose
, &iter
, force
);
4882 undo_wrapping(compose
->undostruct
, FALSE
);
4886 static void compose_set_title(Compose
*compose
)
4892 edited
= compose
->modified
? _(" [Edited]") : "";
4894 subject
= gtk_editable_get_chars(
4895 GTK_EDITABLE(compose
->subject_entry
), 0, -1);
4897 #ifndef GENERIC_UMPC
4898 if (subject
&& strlen(subject
))
4899 str
= g_strdup_printf(_("%s - Compose message%s"),
4902 str
= g_strdup_printf(_("[no subject] - Compose message%s"), edited
);
4904 str
= g_strdup(_("Compose message"));
4907 gtk_window_set_title(GTK_WINDOW(compose
->window
), str
);
4913 * compose_current_mail_account:
4915 * Find a current mail account (the currently selected account, or the
4916 * default account, if a news account is currently selected). If a
4917 * mail account cannot be found, display an error message.
4919 * Return value: Mail account, or NULL if not found.
4921 static PrefsAccount
*
4922 compose_current_mail_account(void)
4926 if (cur_account
&& cur_account
->protocol
!= A_NNTP
)
4929 ac
= account_get_default();
4930 if (!ac
|| ac
->protocol
== A_NNTP
) {
4931 alertpanel_error(_("Account for sending mail is not specified.\n"
4932 "Please select a mail account before sending."));
4939 #define QUOTE_IF_REQUIRED(out, str) \
4941 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4945 len = strlen(str) + 3; \
4946 if ((__tmp = alloca(len)) == NULL) { \
4947 g_warning("can't allocate memory"); \
4948 g_string_free(header, TRUE); \
4951 g_snprintf(__tmp, len, "\"%s\"", str); \
4956 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4957 g_warning("can't allocate memory"); \
4958 g_string_free(header, TRUE); \
4961 strcpy(__tmp, str); \
4967 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4969 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4973 len = strlen(str) + 3; \
4974 if ((__tmp = alloca(len)) == NULL) { \
4975 g_warning("can't allocate memory"); \
4978 g_snprintf(__tmp, len, "\"%s\"", str); \
4983 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4984 g_warning("can't allocate memory"); \
4987 strcpy(__tmp, str); \
4993 static void compose_select_account(Compose
*compose
, PrefsAccount
*account
,
4996 gchar
*from
= NULL
, *header
= NULL
;
4997 ComposeHeaderEntry
*header_entry
;
5000 cm_return_if_fail(account
!= NULL
);
5002 compose
->account
= account
;
5003 if (account
->name
&& *account
->name
) {
5005 QUOTE_IF_REQUIRED_NORMAL(buf
, account
->name
, return);
5006 qbuf
= escape_internal_quotes(buf
, '"');
5007 from
= g_strdup_printf("%s <%s>",
5008 qbuf
, account
->address
);
5011 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), from
);
5013 from
= g_strdup_printf("<%s>",
5015 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), from
);
5020 compose_set_title(compose
);
5022 compose_activate_privacy_system(compose
, account
, FALSE
);
5024 if (account
->default_sign
&& privacy_system_can_sign(compose
->privacy_system
) &&
5025 compose
->mode
!= COMPOSE_REDIRECT
)
5026 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/Sign", TRUE
);
5028 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/Sign", FALSE
);
5029 if (account
->default_encrypt
&& privacy_system_can_encrypt(compose
->privacy_system
) &&
5030 compose
->mode
!= COMPOSE_REDIRECT
)
5031 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/Encrypt", TRUE
);
5033 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/Encrypt", FALSE
);
5035 if (!init
&& compose
->mode
!= COMPOSE_REDIRECT
) {
5036 undo_block(compose
->undostruct
);
5037 compose_insert_sig(compose
, TRUE
);
5038 undo_unblock(compose
->undostruct
);
5041 header_entry
= (ComposeHeaderEntry
*) compose
->header_list
->data
;
5042 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry
->combo
), &iter
))
5043 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
5044 header_entry
->combo
)), &iter
, COMBOBOX_TEXT
, &header
, -1);
5046 if (header
&& !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry
->entry
)))) {
5047 if (account
->protocol
== A_NNTP
) {
5048 if (!strcmp(header
, _("To:")))
5049 combobox_select_by_text(
5050 GTK_COMBO_BOX(header_entry
->combo
),
5053 if (!strcmp(header
, _("Newsgroups:")))
5054 combobox_select_by_text(
5055 GTK_COMBO_BOX(header_entry
->combo
),
5063 /* use account's dict info if set */
5064 if (compose
->gtkaspell
) {
5065 if (account
->enable_default_dictionary
)
5066 gtkaspell_change_dict(compose
->gtkaspell
,
5067 account
->default_dictionary
, FALSE
);
5068 if (account
->enable_default_alt_dictionary
)
5069 gtkaspell_change_alt_dict(compose
->gtkaspell
,
5070 account
->default_alt_dictionary
);
5071 if (account
->enable_default_dictionary
5072 || account
->enable_default_alt_dictionary
)
5073 compose_spell_menu_changed(compose
);
5078 gboolean
compose_check_for_valid_recipient(Compose
*compose
) {
5079 gchar
*recipient_headers_mail
[] = {"To:", "Cc:", "Bcc:", NULL
};
5080 gchar
*recipient_headers_news
[] = {"Newsgroups:", NULL
};
5081 gboolean recipient_found
= FALSE
;
5085 /* free to and newsgroup list */
5086 slist_free_strings_full(compose
->to_list
);
5087 compose
->to_list
= NULL
;
5089 slist_free_strings_full(compose
->newsgroup_list
);
5090 compose
->newsgroup_list
= NULL
;
5092 /* search header entries for to and newsgroup entries */
5093 for (list
= compose
->header_list
; list
; list
= list
->next
) {
5096 header
= gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry
*)list
->data
)->combo
)))), 0, -1);
5097 entry
= gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry
*)list
->data
)->entry
), 0, -1);
5100 if (entry
[0] != '\0') {
5101 for (strptr
= recipient_headers_mail
; *strptr
!= NULL
; strptr
++) {
5102 if (!g_ascii_strcasecmp(header
, prefs_common_translated_header_name(*strptr
))) {
5103 compose
->to_list
= address_list_append(compose
->to_list
, entry
);
5104 recipient_found
= TRUE
;
5107 for (strptr
= recipient_headers_news
; *strptr
!= NULL
; strptr
++) {
5108 if (!g_ascii_strcasecmp(header
, prefs_common_translated_header_name(*strptr
))) {
5109 compose
->newsgroup_list
= newsgroup_list_append(compose
->newsgroup_list
, entry
);
5110 recipient_found
= TRUE
;
5117 return recipient_found
;
5120 static gboolean
compose_check_for_set_recipients(Compose
*compose
)
5122 if (compose
->account
->set_autocc
&& compose
->account
->auto_cc
) {
5123 gboolean found_other
= FALSE
;
5125 /* search header entries for to and newsgroup entries */
5126 for (list
= compose
->header_list
; list
; list
= list
->next
) {
5129 entry
= gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry
*)list
->data
)->entry
), 0, -1);
5130 header
= gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry
*)list
->data
)->combo
)))), 0, -1);
5133 if (strcmp(entry
, compose
->account
->auto_cc
)
5134 || strcmp(header
, prefs_common_translated_header_name("Cc:"))) {
5145 if (compose
->batch
) {
5146 gtk_widget_show_all(compose
->window
);
5148 text
= g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5149 prefs_common_translated_header_name("Cc"));
5150 aval
= alertpanel(_("Send"),
5152 NULL
, _("_Cancel"), NULL
, _("_Send"), NULL
, NULL
, ALERTFOCUS_SECOND
);
5154 if (aval
!= G_ALERTALTERNATE
)
5158 if (compose
->account
->set_autobcc
&& compose
->account
->auto_bcc
) {
5159 gboolean found_other
= FALSE
;
5161 /* search header entries for to and newsgroup entries */
5162 for (list
= compose
->header_list
; list
; list
= list
->next
) {
5165 entry
= gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry
*)list
->data
)->entry
), 0, -1);
5166 header
= gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry
*)list
->data
)->combo
)))), 0, -1);
5169 if (strcmp(entry
, compose
->account
->auto_bcc
)
5170 || strcmp(header
, prefs_common_translated_header_name("Bcc:"))) {
5182 if (compose
->batch
) {
5183 gtk_widget_show_all(compose
->window
);
5185 text
= g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5186 prefs_common_translated_header_name("Bcc"));
5187 aval
= alertpanel(_("Send"),
5189 NULL
, _("_Cancel"), NULL
, _("_Send"), NULL
, NULL
, ALERTFOCUS_SECOND
);
5191 if (aval
!= G_ALERTALTERNATE
)
5198 static gboolean
compose_check_entries(Compose
*compose
, gboolean check_everything
)
5202 if (compose_check_for_valid_recipient(compose
) == FALSE
) {
5203 if (compose
->batch
) {
5204 gtk_widget_show_all(compose
->window
);
5206 alertpanel_error(_("Recipient is not specified."));
5210 if (compose_check_for_set_recipients(compose
) == FALSE
) {
5214 if (!compose
->batch
&& prefs_common
.warn_empty_subj
== TRUE
) {
5215 str
= gtk_entry_get_text(GTK_ENTRY(compose
->subject_entry
));
5216 if (*str
== '\0' && check_everything
== TRUE
&&
5217 compose
->mode
!= COMPOSE_REDIRECT
) {
5221 message
= g_strdup_printf(_("Subject is empty. %s"),
5222 compose
->sending
?_("Send it anyway?"):
5223 _("Queue it anyway?"));
5225 aval
= alertpanel_full(compose
->sending
?_("Send"):_("Send later"), message
,
5226 NULL
, _("_Cancel"), NULL
, compose
->sending
?_("_Send"):_("_Queue"),
5227 NULL
, NULL
, ALERTFOCUS_FIRST
, TRUE
, NULL
, ALERT_QUESTION
);
5229 if (aval
& G_ALERTDISABLE
) {
5230 aval
&= ~G_ALERTDISABLE
;
5231 prefs_common
.warn_empty_subj
= FALSE
;
5233 if (aval
!= G_ALERTALTERNATE
)
5238 if (!compose
->batch
&& prefs_common
.warn_sending_many_recipients_num
> 0
5239 && check_everything
== TRUE
) {
5243 /* count To and Cc recipients */
5244 for (list
= compose
->header_list
; list
; list
= list
->next
) {
5248 header
= gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry
*)list
->data
)->combo
)))), 0, -1);
5249 entry
= gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry
*)list
->data
)->entry
), 0, -1);
5252 if ((entry
[0] != '\0') &&
5253 (!strcmp(header
, prefs_common_translated_header_name("To:")) ||
5254 !strcmp(header
, prefs_common_translated_header_name("Cc:")))) {
5260 if (cnt
> prefs_common
.warn_sending_many_recipients_num
) {
5264 message
= g_strdup_printf(_("Sending to %d recipients. %s"), cnt
,
5265 compose
->sending
?_("Send it anyway?"):
5266 _("Queue it anyway?"));
5268 aval
= alertpanel_full(compose
->sending
?_("Send"):_("Send later"), message
,
5269 NULL
, _("_Cancel"), NULL
, compose
->sending
?_("_Send"):_("_Queue"),
5270 NULL
, NULL
, ALERTFOCUS_FIRST
, TRUE
, NULL
, ALERT_QUESTION
);
5272 if (aval
& G_ALERTDISABLE
) {
5273 aval
&= ~G_ALERTDISABLE
;
5274 prefs_common
.warn_sending_many_recipients_num
= 0;
5276 if (aval
!= G_ALERTALTERNATE
)
5281 if (check_everything
&& hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST
, compose
))
5287 static void _display_queue_error(ComposeQueueResult val
)
5290 case COMPOSE_QUEUE_SUCCESS
:
5292 case COMPOSE_QUEUE_ERROR_NO_MSG
:
5293 alertpanel_error(_("Could not queue message."));
5295 case COMPOSE_QUEUE_ERROR_WITH_ERRNO
:
5296 alertpanel_error(_("Could not queue message:\n\n%s."),
5299 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED
:
5300 alertpanel_error(_("Could not queue message for sending:\n\n"
5301 "Signature failed: %s"),
5302 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5304 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED
:
5305 alertpanel_error(_("Could not queue message for sending:\n\n"
5306 "Encryption failed: %s"),
5307 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5309 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION
:
5310 alertpanel_error(_("Could not queue message for sending:\n\n"
5311 "Charset conversion failed."));
5313 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY
:
5314 alertpanel_error(_("Could not queue message for sending:\n\n"
5315 "Couldn't get recipient encryption key."));
5317 case COMPOSE_QUEUE_SIGNING_CANCELLED
:
5318 debug_print("signing cancelled\n");
5321 /* unhandled error */
5322 debug_print("oops, unhandled compose_queue() return value %d\n",
5328 gint
compose_send(Compose
*compose
)
5331 FolderItem
*folder
= NULL
;
5332 ComposeQueueResult val
= COMPOSE_QUEUE_ERROR_NO_MSG
;
5333 gchar
*msgpath
= NULL
;
5334 gboolean discard_window
= FALSE
;
5335 gchar
*errstr
= NULL
;
5336 gchar
*tmsgid
= NULL
;
5337 MainWindow
*mainwin
= mainwindow_get_mainwindow();
5338 gboolean queued_removed
= FALSE
;
5340 if (prefs_common
.send_dialog_invisible
5341 || compose
->batch
== TRUE
)
5342 discard_window
= TRUE
;
5344 compose_allow_user_actions (compose
, FALSE
);
5345 compose
->sending
= TRUE
;
5347 if (compose_check_entries(compose
, TRUE
) == FALSE
) {
5348 if (compose
->batch
) {
5349 gtk_widget_show_all(compose
->window
);
5355 val
= compose_queue(compose
, &msgnum
, &folder
, &msgpath
, TRUE
);
5357 if (val
!= COMPOSE_QUEUE_SUCCESS
) {
5358 if (compose
->batch
) {
5359 gtk_widget_show_all(compose
->window
);
5362 _display_queue_error(val
);
5367 tmsgid
= compose
->msgid
? g_strdup(compose
->msgid
) : NULL
;
5368 if (discard_window
) {
5369 compose
->sending
= FALSE
;
5370 compose_close(compose
);
5371 /* No more compose access in the normal codepath
5372 * after this point! */
5377 alertpanel_error(_("The message was queued but could not be "
5378 "sent.\nUse \"Send queued messages\" from "
5379 "the main window to retry."));
5380 if (!discard_window
) {
5387 if (msgpath
== NULL
) {
5388 msgpath
= folder_item_fetch_msg(folder
, msgnum
);
5389 val
= procmsg_send_message_queue_with_lock(msgpath
, &errstr
, folder
, msgnum
, &queued_removed
);
5392 val
= procmsg_send_message_queue_with_lock(msgpath
, &errstr
, folder
, msgnum
, &queued_removed
);
5393 claws_unlink(msgpath
);
5396 if (!discard_window
) {
5398 if (!queued_removed
)
5399 folder_item_remove_msg(folder
, msgnum
);
5400 folder_item_scan(folder
);
5402 /* make sure we delete that */
5403 MsgInfo
*tmp
= folder_item_get_msginfo_by_msgid(folder
, tmsgid
);
5405 debug_print("removing %d via %s\n", tmp
->msgnum
, tmsgid
);
5406 folder_item_remove_msg(folder
, tmp
->msgnum
);
5407 procmsg_msginfo_free(&tmp
);
5414 if (!queued_removed
)
5415 folder_item_remove_msg(folder
, msgnum
);
5416 folder_item_scan(folder
);
5418 /* make sure we delete that */
5419 MsgInfo
*tmp
= folder_item_get_msginfo_by_msgid(folder
, tmsgid
);
5421 debug_print("removing %d via %s\n", tmp
->msgnum
, tmsgid
);
5422 folder_item_remove_msg(folder
, tmp
->msgnum
);
5423 procmsg_msginfo_free(&tmp
);
5426 if (!discard_window
) {
5427 compose
->sending
= FALSE
;
5428 compose_allow_user_actions (compose
, TRUE
);
5429 compose_close(compose
);
5433 alertpanel_error_log(_("%s\nYou can try to \"Send\" again "
5434 "or queue the message with \"Send later\""), errstr
);
5437 alertpanel_error_log(_("The message was queued but could not be "
5438 "sent.\nUse \"Send queued messages\" from "
5439 "the main window to retry."));
5441 if (!discard_window
) {
5450 toolbar_main_set_sensitive(mainwin
);
5451 main_window_set_menu_sensitive(mainwin
);
5457 compose_allow_user_actions (compose
, TRUE
);
5458 compose
->sending
= FALSE
;
5459 compose
->modified
= TRUE
;
5460 toolbar_main_set_sensitive(mainwin
);
5461 main_window_set_menu_sensitive(mainwin
);
5466 static gboolean
compose_use_attach(Compose
*compose
)
5468 GtkTreeModel
*model
= gtk_tree_view_get_model
5469 (GTK_TREE_VIEW(compose
->attach_clist
));
5470 return gtk_tree_model_iter_n_children(model
, NULL
) > 0;
5473 static gint
compose_redirect_write_headers_from_headerlist(Compose
*compose
,
5476 gchar buf
[BUFFSIZE
];
5478 gboolean first_to_address
;
5479 gboolean first_cc_address
;
5481 ComposeHeaderEntry
*headerentry
;
5482 const gchar
*headerentryname
;
5483 const gchar
*cc_hdr
;
5484 const gchar
*to_hdr
;
5485 gboolean err
= FALSE
;
5487 debug_print("Writing redirect header\n");
5489 cc_hdr
= prefs_common_translated_header_name("Cc:");
5490 to_hdr
= prefs_common_translated_header_name("To:");
5492 first_to_address
= TRUE
;
5493 for (list
= compose
->header_list
; list
; list
= list
->next
) {
5494 headerentry
= ((ComposeHeaderEntry
*)list
->data
);
5495 headerentryname
= gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry
->combo
)))));
5497 if (g_utf8_collate(headerentryname
, to_hdr
) == 0) {
5498 const gchar
*entstr
= gtk_entry_get_text(GTK_ENTRY(headerentry
->entry
));
5499 Xstrdup_a(str
, entstr
, return -1);
5501 if (str
[0] != '\0') {
5502 compose_convert_header
5503 (compose
, buf
, sizeof(buf
), str
,
5504 strlen("Resent-To") + 2, TRUE
);
5506 if (first_to_address
) {
5507 err
|= (fprintf(fp
, "Resent-To: ") < 0);
5508 first_to_address
= FALSE
;
5510 err
|= (fprintf(fp
, ",") < 0);
5512 err
|= (fprintf(fp
, "%s", buf
) < 0);
5516 if (!first_to_address
) {
5517 err
|= (fprintf(fp
, "\n") < 0);
5520 first_cc_address
= TRUE
;
5521 for (list
= compose
->header_list
; list
; list
= list
->next
) {
5522 headerentry
= ((ComposeHeaderEntry
*)list
->data
);
5523 headerentryname
= gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry
->combo
)))));
5525 if (g_utf8_collate(headerentryname
, cc_hdr
) == 0) {
5526 const gchar
*strg
= gtk_entry_get_text(GTK_ENTRY(headerentry
->entry
));
5527 Xstrdup_a(str
, strg
, return -1);
5529 if (str
[0] != '\0') {
5530 compose_convert_header
5531 (compose
, buf
, sizeof(buf
), str
,
5532 strlen("Resent-Cc") + 2, TRUE
);
5534 if (first_cc_address
) {
5535 err
|= (fprintf(fp
, "Resent-Cc: ") < 0);
5536 first_cc_address
= FALSE
;
5538 err
|= (fprintf(fp
, ",") < 0);
5540 err
|= (fprintf(fp
, "%s", buf
) < 0);
5544 if (!first_cc_address
) {
5545 err
|= (fprintf(fp
, "\n") < 0);
5548 return (err
? -1:0);
5551 static gint
compose_redirect_write_headers(Compose
*compose
, FILE *fp
)
5553 gchar date
[RFC822_DATE_BUFFSIZE
];
5554 gchar buf
[BUFFSIZE
];
5556 const gchar
*entstr
;
5557 /* struct utsname utsbuf; */
5558 gboolean err
= FALSE
;
5560 cm_return_val_if_fail(fp
!= NULL
, -1);
5561 cm_return_val_if_fail(compose
->account
!= NULL
, -1);
5562 cm_return_val_if_fail(compose
->account
->address
!= NULL
, -1);
5565 if (prefs_common
.hide_timezone
)
5566 get_rfc822_date_hide_tz(date
, sizeof(date
));
5568 get_rfc822_date(date
, sizeof(date
));
5569 err
|= (fprintf(fp
, "Resent-Date: %s\n", date
) < 0);
5572 if (compose
->account
->name
&& *compose
->account
->name
) {
5573 compose_convert_header
5574 (compose
, buf
, sizeof(buf
), compose
->account
->name
,
5575 strlen("From: "), TRUE
);
5576 err
|= (fprintf(fp
, "Resent-From: %s <%s>\n",
5577 buf
, compose
->account
->address
) < 0);
5579 err
|= (fprintf(fp
, "Resent-From: %s\n", compose
->account
->address
) < 0);
5582 entstr
= gtk_entry_get_text(GTK_ENTRY(compose
->subject_entry
));
5583 if (*entstr
!= '\0') {
5584 Xstrdup_a(str
, entstr
, return -1);
5587 compose_convert_header(compose
, buf
, sizeof(buf
), str
,
5588 strlen("Subject: "), FALSE
);
5589 err
|= (fprintf(fp
, "Subject: %s\n", buf
) < 0);
5593 /* Resent-Message-ID */
5594 if (compose
->account
->gen_msgid
) {
5595 gchar
*addr
= prefs_account_generate_msgid(compose
->account
);
5596 err
|= (fprintf(fp
, "Resent-Message-ID: <%s>\n", addr
) < 0);
5598 g_free(compose
->msgid
);
5599 compose
->msgid
= addr
;
5601 compose
->msgid
= NULL
;
5604 if (compose_redirect_write_headers_from_headerlist(compose
, fp
))
5607 /* separator between header and body */
5608 err
|= (claws_fputs("\n", fp
) == EOF
);
5610 return (err
? -1:0);
5613 static gint
compose_redirect_write_to_file(Compose
*compose
, FILE *fdest
)
5618 gchar rewrite_buf
[BUFFSIZE
];
5620 gboolean skip
= FALSE
;
5621 gboolean err
= FALSE
;
5622 gchar
*not_included
[]={
5623 "Return-Path:", "Delivered-To:", "Received:",
5624 "Subject:", "X-UIDL:", "AF:",
5625 "NF:", "PS:", "SRH:",
5626 "SFN:", "DSR:", "MID:",
5627 "CFG:", "PT:", "S:",
5628 "RQ:", "SSV:", "NSV:",
5629 "SSH:", "R:", "MAID:",
5630 "NAID:", "RMID:", "FMID:",
5631 "SCF:", "RRCPT:", "NG:",
5632 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5633 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5634 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5635 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5636 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5641 if ((fp
= claws_fopen(compose
->redirect_filename
, "rb")) == NULL
) {
5642 FILE_OP_ERROR(compose
->redirect_filename
, "claws_fopen");
5646 while ((ret
= procheader_get_one_field_asis(&buf
, fp
)) != -1) {
5648 for (i
= 0; not_included
[i
] != NULL
; i
++) {
5649 if (g_ascii_strncasecmp(buf
, not_included
[i
],
5650 strlen(not_included
[i
])) == 0) {
5660 if (claws_fputs(buf
, fdest
) == -1) {
5666 if (!prefs_common
.redirect_keep_from
) {
5667 if (g_ascii_strncasecmp(buf
, "From:",
5668 strlen("From:")) == 0) {
5669 err
|= (claws_fputs(" (by way of ", fdest
) == EOF
);
5670 if (compose
->account
->name
5671 && *compose
->account
->name
) {
5672 gchar buffer
[BUFFSIZE
];
5674 compose_convert_header
5675 (compose
, buffer
, sizeof(buffer
),
5676 compose
->account
->name
,
5679 err
|= (fprintf(fdest
, "%s <%s>",
5681 compose
->account
->address
) < 0);
5683 err
|= (fprintf(fdest
, "%s",
5684 compose
->account
->address
) < 0);
5685 err
|= (claws_fputs(")", fdest
) == EOF
);
5691 if (claws_fputs("\n", fdest
) == -1)
5698 if (compose_redirect_write_headers(compose
, fdest
))
5701 while ((len
= claws_fread(rewrite_buf
, sizeof(gchar
), sizeof(rewrite_buf
), fp
)) > 0) {
5702 if (claws_fwrite(rewrite_buf
, sizeof(gchar
), len
, fdest
) != len
)
5716 static gint
compose_write_to_file(Compose
*compose
, FILE *fp
, gint action
, gboolean attach_parts
)
5718 GtkTextBuffer
*buffer
;
5719 GtkTextIter start
, end
, tmp
;
5720 gchar
*chars
, *tmp_enc_file
= NULL
, *content
;
5722 const gchar
*out_codeset
;
5723 EncodingType encoding
= ENC_UNKNOWN
;
5724 MimeInfo
*mimemsg
, *mimetext
;
5726 const gchar
*src_codeset
= CS_INTERNAL
;
5727 gchar
*from_addr
= NULL
;
5728 gchar
*from_name
= NULL
;
5731 if (action
== COMPOSE_WRITE_FOR_SEND
) {
5732 attach_parts
= TRUE
;
5734 /* We're sending the message, generate a Message-ID
5736 if (compose
->msgid
== NULL
&&
5737 compose
->account
->gen_msgid
) {
5738 compose
->msgid
= prefs_account_generate_msgid(compose
->account
);
5742 /* create message MimeInfo */
5743 mimemsg
= procmime_mimeinfo_new();
5744 mimemsg
->type
= MIMETYPE_MESSAGE
;
5745 mimemsg
->subtype
= g_strdup("rfc822");
5746 mimemsg
->content
= MIMECONTENT_MEM
;
5747 mimemsg
->tmp
= TRUE
; /* must free content later */
5748 mimemsg
->data
.mem
= compose_get_header(compose
);
5750 /* Create text part MimeInfo */
5751 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose
->text
));
5752 gtk_text_buffer_get_end_iter(buffer
, &end
);
5755 /* We make sure that there is a newline at the end. */
5756 if (action
== COMPOSE_WRITE_FOR_SEND
&& gtk_text_iter_backward_char(&tmp
)) {
5757 chars
= gtk_text_buffer_get_text(buffer
, &tmp
, &end
, FALSE
);
5758 if (*chars
!= '\n') {
5759 gtk_text_buffer_insert(buffer
, &end
, "\n", 1);
5764 /* get all composed text */
5765 gtk_text_buffer_get_start_iter(buffer
, &start
);
5766 chars
= gtk_text_buffer_get_text(buffer
, &start
, &end
, FALSE
);
5768 out_codeset
= conv_get_charset_str(compose
->out_encoding
);
5770 if (!out_codeset
&& is_ascii_str(chars
)) {
5771 out_codeset
= CS_US_ASCII
;
5772 } else if (prefs_common
.outgoing_fallback_to_ascii
&&
5773 is_ascii_str(chars
)) {
5774 out_codeset
= CS_US_ASCII
;
5775 encoding
= ENC_7BIT
;
5779 gchar
*test_conv_global_out
= NULL
;
5780 gchar
*test_conv_reply
= NULL
;
5782 /* automatic mode. be automatic. */
5783 codeconv_set_strict(TRUE
);
5785 out_codeset
= conv_get_outgoing_charset_str();
5787 debug_print("trying to convert to %s\n", out_codeset
);
5788 test_conv_global_out
= conv_codeset_strdup(chars
, src_codeset
, out_codeset
);
5791 if (!test_conv_global_out
&& compose
->orig_charset
5792 && strcmp(compose
->orig_charset
, CS_US_ASCII
)) {
5793 out_codeset
= compose
->orig_charset
;
5794 debug_print("failure; trying to convert to %s\n", out_codeset
);
5795 test_conv_reply
= conv_codeset_strdup(chars
, src_codeset
, out_codeset
);
5798 if (!test_conv_global_out
&& !test_conv_reply
) {
5800 out_codeset
= CS_INTERNAL
;
5801 debug_print("failure; finally using %s\n", out_codeset
);
5803 g_free(test_conv_global_out
);
5804 g_free(test_conv_reply
);
5805 codeconv_set_strict(FALSE
);
5808 if (encoding
== ENC_UNKNOWN
) {
5809 if (prefs_common
.encoding_method
== CTE_BASE64
)
5810 encoding
= ENC_BASE64
;
5811 else if (prefs_common
.encoding_method
== CTE_QUOTED_PRINTABLE
)
5812 encoding
= ENC_QUOTED_PRINTABLE
;
5813 else if (prefs_common
.encoding_method
== CTE_8BIT
)
5814 encoding
= ENC_8BIT
;
5816 encoding
= procmime_get_encoding_for_charset(out_codeset
);
5819 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5820 src_codeset
, out_codeset
, procmime_get_encoding_str(encoding
));
5822 if (action
== COMPOSE_WRITE_FOR_SEND
) {
5823 codeconv_set_strict(TRUE
);
5824 buf
= conv_codeset_strdup(chars
, src_codeset
, out_codeset
);
5825 codeconv_set_strict(FALSE
);
5830 msg
= g_strdup_printf(_("Can't convert the character encoding of the message \n"
5831 "to the specified %s charset.\n"
5832 "Send it as %s?"), out_codeset
, src_codeset
);
5833 aval
= alertpanel_full(_("Error"), msg
, NULL
, _("_Cancel"),
5834 NULL
, _("_Send"), NULL
, NULL
, ALERTFOCUS_SECOND
, FALSE
,
5838 if (aval
!= G_ALERTALTERNATE
) {
5840 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION
;
5843 out_codeset
= src_codeset
;
5849 out_codeset
= src_codeset
;
5854 /* check for line length limit */
5855 if (action
== COMPOSE_WRITE_FOR_SEND
&&
5856 encoding
!= ENC_QUOTED_PRINTABLE
&& encoding
!= ENC_BASE64
&&
5857 check_line_length(buf
, 1000, &line
) < 0) {
5858 if (encoding
== ENC_8BIT
) {
5861 msg
= g_strdup_printf
5862 (_("Line %d exceeds the line length limit (998 bytes).\n"
5863 "The contents of the message might be broken on the way "
5864 "to the recipient."), line
+ 1);
5865 aval
= alertpanel(_("Warning"), msg
, NULL
, _("Send safely"), NULL
, _("Send as-is"),
5866 NULL
, NULL
, ALERTFOCUS_FIRST
);
5868 if (aval
!= G_ALERTALTERNATE
)
5869 encoding
= ENC_QUOTED_PRINTABLE
;
5871 debug_print("Line %d exceeds the line length limit (998 bytes), "
5872 "switching to QP transfer encoding\n", line
+ 1);
5873 encoding
= ENC_QUOTED_PRINTABLE
;
5877 if (prefs_common
.rewrite_first_from
&& (encoding
== ENC_8BIT
|| encoding
== ENC_7BIT
)) {
5878 if (!strncmp(buf
, "From ", sizeof("From ")-1) ||
5879 strstr(buf
, "\nFrom ") != NULL
) {
5880 encoding
= ENC_QUOTED_PRINTABLE
;
5884 mimetext
= procmime_mimeinfo_new();
5885 mimetext
->content
= MIMECONTENT_MEM
;
5886 mimetext
->tmp
= TRUE
; /* must free content later */
5887 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5888 * and free the data, which we need later. */
5889 mimetext
->data
.mem
= g_strdup(buf
);
5890 mimetext
->type
= MIMETYPE_TEXT
;
5891 mimetext
->subtype
= g_strdup("plain");
5892 g_hash_table_insert(mimetext
->typeparameters
, g_strdup("charset"),
5893 g_strdup(out_codeset
));
5895 /* protect trailing spaces when signing message */
5896 if (action
== COMPOSE_WRITE_FOR_SEND
&& compose
->use_signing
&&
5897 privacy_system_can_sign(compose
->privacy_system
)) {
5898 encoding
= ENC_QUOTED_PRINTABLE
;
5901 debug_print("main text: %" G_GSIZE_FORMAT
" bytes encoded as %s in %d\n",
5902 strlen(buf
), out_codeset
, encoding
);
5904 if (encoding
!= ENC_UNKNOWN
)
5905 procmime_encode_content(mimetext
, encoding
);
5907 /* append attachment parts */
5908 if (compose_use_attach(compose
) && attach_parts
) {
5909 MimeInfo
*mimempart
;
5910 gchar
*boundary
= NULL
;
5911 mimempart
= procmime_mimeinfo_new();
5912 mimempart
->content
= MIMECONTENT_EMPTY
;
5913 mimempart
->type
= MIMETYPE_MULTIPART
;
5914 mimempart
->subtype
= g_strdup("mixed");
5918 boundary
= generate_mime_boundary(NULL
);
5919 } while (strstr(buf
, boundary
) != NULL
);
5921 g_hash_table_insert(mimempart
->typeparameters
, g_strdup("boundary"),
5924 mimetext
->disposition
= DISPOSITIONTYPE_INLINE
;
5926 g_node_append(mimempart
->node
, mimetext
->node
);
5927 g_node_append(mimemsg
->node
, mimempart
->node
);
5929 if (compose_add_attachments(compose
, mimempart
, action
) < 0)
5930 return COMPOSE_QUEUE_ERROR_NO_MSG
;
5932 g_node_append(mimemsg
->node
, mimetext
->node
);
5936 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose
->from_name
))) != 0) {
5937 gchar
*spec
= gtk_editable_get_chars(GTK_EDITABLE(compose
->from_name
), 0, -1);
5938 /* extract name and address */
5939 if (strstr(spec
, " <") && strstr(spec
, ">")) {
5940 from_addr
= g_strdup(strrchr(spec
, '<')+1);
5941 *(strrchr(from_addr
, '>')) = '\0';
5942 from_name
= g_strdup(spec
);
5943 *(strrchr(from_name
, '<')) = '\0';
5950 /* sign message if sending */
5951 if (action
== COMPOSE_WRITE_FOR_SEND
&& compose
->use_signing
&&
5952 privacy_system_can_sign(compose
->privacy_system
))
5953 if (!privacy_sign(compose
->privacy_system
, mimemsg
,
5954 compose
->account
, from_addr
)) {
5957 if (!privacy_peek_error())
5958 return COMPOSE_QUEUE_SIGNING_CANCELLED
;
5960 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED
;
5965 if (compose
->use_encryption
) {
5966 if (compose
->encdata
!= NULL
&&
5967 strcmp(compose
->encdata
, "_DONT_ENCRYPT_")) {
5969 /* First, write an unencrypted copy and save it to outbox, if
5970 * user wants that. */
5971 if (compose
->account
->save_encrypted_as_clear_text
) {
5972 debug_print("saving sent message unencrypted...\n");
5973 FILE *tmpfp
= get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file
);
5975 claws_fclose(tmpfp
);
5977 /* fp now points to a file with headers written,
5978 * let's make a copy. */
5980 content
= file_read_stream_to_str(fp
);
5982 str_write_to_file(content
, tmp_enc_file
, TRUE
);
5985 /* Now write the unencrypted body. */
5986 if ((tmpfp
= claws_fopen(tmp_enc_file
, "a")) != NULL
) {
5987 procmime_write_mimeinfo(mimemsg
, tmpfp
);
5988 claws_fclose(tmpfp
);
5990 outbox
= folder_find_item_from_identifier(compose_get_save_to(compose
));
5992 outbox
= folder_get_default_outbox();
5994 procmsg_save_to_outbox(outbox
, tmp_enc_file
, TRUE
);
5995 claws_unlink(tmp_enc_file
);
5997 g_warning("can't open file '%s'", tmp_enc_file
);
6000 g_warning("couldn't get tempfile");
6003 if (!privacy_encrypt(compose
->privacy_system
, mimemsg
, compose
->encdata
)) {
6004 debug_print("Couldn't encrypt mime structure: %s.\n",
6005 privacy_get_error());
6007 g_free(tmp_enc_file
);
6008 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED
;
6013 g_free(tmp_enc_file
);
6015 procmime_write_mimeinfo(mimemsg
, fp
);
6017 procmime_mimeinfo_free_all(&mimemsg
);
6022 static gint
compose_write_body_to_file(Compose
*compose
, const gchar
*file
)
6024 GtkTextBuffer
*buffer
;
6025 GtkTextIter start
, end
;
6030 if ((fp
= claws_fopen(file
, "wb")) == NULL
) {
6031 FILE_OP_ERROR(file
, "claws_fopen");
6035 /* chmod for security */
6036 if (change_file_mode_rw(fp
, file
) < 0) {
6037 FILE_OP_ERROR(file
, "chmod");
6038 g_warning("can't change file mode");
6041 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose
->text
));
6042 gtk_text_buffer_get_start_iter(buffer
, &start
);
6043 gtk_text_buffer_get_end_iter(buffer
, &end
);
6044 tmp
= gtk_text_buffer_get_text(buffer
, &start
, &end
, FALSE
);
6046 chars
= conv_codeset_strdup
6047 (tmp
, CS_INTERNAL
, conv_get_locale_charset_str());
6056 len
= strlen(chars
);
6057 if (claws_fwrite(chars
, sizeof(gchar
), len
, fp
) != len
) {
6058 FILE_OP_ERROR(file
, "claws_fwrite");
6067 if (claws_safe_fclose(fp
) == EOF
) {
6068 FILE_OP_ERROR(file
, "claws_fclose");
6075 static gint
compose_remove_reedit_target(Compose
*compose
, gboolean force
)
6078 MsgInfo
*msginfo
= compose
->targetinfo
;
6080 cm_return_val_if_fail(compose
->mode
== COMPOSE_REEDIT
, -1);
6081 if (!msginfo
) return -1;
6083 if (!force
&& MSG_IS_LOCKED(msginfo
->flags
))
6086 item
= msginfo
->folder
;
6087 cm_return_val_if_fail(item
!= NULL
, -1);
6089 if (procmsg_msg_exist(msginfo
) &&
6090 (folder_has_parent_of_type(item
, F_QUEUE
) ||
6091 folder_has_parent_of_type(item
, F_DRAFT
)
6092 || msginfo
== compose
->autosaved_draft
)) {
6093 if (folder_item_remove_msg(item
, msginfo
->msgnum
) < 0) {
6094 g_warning("can't remove the old message");
6097 debug_print("removed reedit target %d\n", msginfo
->msgnum
);
6104 static void compose_remove_draft(Compose
*compose
)
6107 MsgInfo
*msginfo
= compose
->targetinfo
;
6108 drafts
= account_get_special_folder(compose
->account
, F_DRAFT
);
6110 if (procmsg_msg_exist(msginfo
)) {
6111 folder_item_remove_msg(drafts
, msginfo
->msgnum
);
6116 ComposeQueueResult
compose_queue(Compose
*compose
, gint
*msgnum
, FolderItem
**item
, gchar
**msgpath
,
6117 gboolean remove_reedit_target
)
6119 return compose_queue_sub (compose
, msgnum
, item
, msgpath
, FALSE
, remove_reedit_target
);
6122 static gboolean
compose_warn_encryption(Compose
*compose
)
6124 const gchar
*warning
= privacy_get_encrypt_warning(compose
->privacy_system
);
6125 AlertValue val
= G_ALERTALTERNATE
;
6127 if (warning
== NULL
)
6130 val
= alertpanel_full(_("Encryption warning"), warning
,
6131 NULL
, _("_Cancel"), NULL
, _("C_ontinue"), NULL
, NULL
,
6132 ALERTFOCUS_SECOND
, TRUE
, NULL
, ALERT_WARNING
);
6133 if (val
& G_ALERTDISABLE
) {
6134 val
&= ~G_ALERTDISABLE
;
6135 if (val
== G_ALERTALTERNATE
)
6136 privacy_inhibit_encrypt_warning(compose
->privacy_system
,
6140 if (val
== G_ALERTALTERNATE
) {
6147 static ComposeQueueResult
compose_queue_sub(Compose
*compose
, gint
*msgnum
, FolderItem
**item
,
6148 gchar
**msgpath
, gboolean perform_checks
,
6149 gboolean remove_reedit_target
)
6156 PrefsAccount
*mailac
= NULL
, *newsac
= NULL
;
6157 gboolean err
= FALSE
;
6159 debug_print("queueing message...\n");
6160 cm_return_val_if_fail(compose
->account
!= NULL
, -1);
6162 if (compose_check_entries(compose
, perform_checks
) == FALSE
) {
6163 if (compose
->batch
) {
6164 gtk_widget_show_all(compose
->window
);
6166 return COMPOSE_QUEUE_ERROR_NO_MSG
;
6169 if (!compose
->to_list
&& !compose
->newsgroup_list
) {
6170 g_warning("can't get recipient list");
6171 return COMPOSE_QUEUE_ERROR_NO_MSG
;
6174 if (compose
->to_list
) {
6175 mailac
= compose
->account
;
6176 if (!mailac
&& cur_account
&& cur_account
->protocol
!= A_NNTP
)
6177 mailac
= cur_account
;
6178 else if (!mailac
&& !(mailac
= compose_current_mail_account())) {
6179 alertpanel_error(_("No account for sending mails available!"));
6180 return COMPOSE_QUEUE_ERROR_NO_MSG
;
6184 if (compose
->newsgroup_list
) {
6185 if (compose
->account
->protocol
== A_NNTP
)
6186 newsac
= compose
->account
;
6188 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6189 return COMPOSE_QUEUE_ERROR_NO_MSG
;
6193 /* write queue header */
6194 tmp
= g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6195 G_DIR_SEPARATOR
, compose
, (guint
) rand());
6196 debug_print("queuing to %s\n", tmp
);
6197 if ((fp
= claws_fopen(tmp
, "w+b")) == NULL
) {
6198 FILE_OP_ERROR(tmp
, "claws_fopen");
6200 return COMPOSE_QUEUE_ERROR_WITH_ERRNO
;
6203 if (change_file_mode_rw(fp
, tmp
) < 0) {
6204 FILE_OP_ERROR(tmp
, "chmod");
6205 g_warning("can't change file mode");
6208 /* queueing variables */
6209 err
|= (fprintf(fp
, "AF:\n") < 0);
6210 err
|= (fprintf(fp
, "NF:0\n") < 0);
6211 err
|= (fprintf(fp
, "PS:10\n") < 0);
6212 err
|= (fprintf(fp
, "SRH:1\n") < 0);
6213 err
|= (fprintf(fp
, "SFN:\n") < 0);
6214 err
|= (fprintf(fp
, "DSR:\n") < 0);
6216 err
|= (fprintf(fp
, "MID:<%s>\n", compose
->msgid
) < 0);
6218 err
|= (fprintf(fp
, "MID:\n") < 0);
6219 err
|= (fprintf(fp
, "CFG:\n") < 0);
6220 err
|= (fprintf(fp
, "PT:0\n") < 0);
6221 err
|= (fprintf(fp
, "S:%s\n", compose
->account
->address
) < 0);
6222 err
|= (fprintf(fp
, "RQ:\n") < 0);
6224 err
|= (fprintf(fp
, "SSV:%s\n", mailac
->smtp_server
) < 0);
6226 err
|= (fprintf(fp
, "SSV:\n") < 0);
6228 err
|= (fprintf(fp
, "NSV:%s\n", newsac
->nntp_server
) < 0);
6230 err
|= (fprintf(fp
, "NSV:\n") < 0);
6231 err
|= (fprintf(fp
, "SSH:\n") < 0);
6232 /* write recipient list */
6233 if (compose
->to_list
) {
6234 err
|= (fprintf(fp
, "R:<%s>", (gchar
*)compose
->to_list
->data
) < 0);
6235 for (cur
= compose
->to_list
->next
; cur
!= NULL
;
6237 err
|= (fprintf(fp
, ",<%s>", (gchar
*)cur
->data
) < 0);
6238 err
|= (fprintf(fp
, "\n") < 0);
6240 /* write newsgroup list */
6241 if (compose
->newsgroup_list
) {
6242 err
|= (fprintf(fp
, "NG:") < 0);
6243 err
|= (fprintf(fp
, "%s", (gchar
*)compose
->newsgroup_list
->data
) < 0);
6244 for (cur
= compose
->newsgroup_list
->next
; cur
!= NULL
; cur
= cur
->next
)
6245 err
|= (fprintf(fp
, ",%s", (gchar
*)cur
->data
) < 0);
6246 err
|= (fprintf(fp
, "\n") < 0);
6250 err
|= (fprintf(fp
, "MAID:%d\n", mailac
->account_id
) < 0);
6252 err
|= (fprintf(fp
, "NAID:%d\n", newsac
->account_id
) < 0);
6255 if (compose
->privacy_system
!= NULL
) {
6256 err
|= (fprintf(fp
, "X-Claws-Privacy-System:%s\n", compose
->privacy_system
) < 0);
6257 err
|= (fprintf(fp
, "X-Claws-Sign:%d\n", compose
->use_signing
) < 0);
6258 if (compose
->use_encryption
) {
6259 if (!compose_warn_encryption(compose
)) {
6263 return COMPOSE_QUEUE_ERROR_NO_MSG
;
6265 if (mailac
&& mailac
->encrypt_to_self
) {
6266 GSList
*tmp_list
= g_slist_copy(compose
->to_list
);
6267 tmp_list
= g_slist_append(tmp_list
, compose
->account
->address
);
6268 compose
->encdata
= privacy_get_encrypt_data(compose
->privacy_system
, tmp_list
);
6269 g_slist_free(tmp_list
);
6271 compose
->encdata
= privacy_get_encrypt_data(compose
->privacy_system
, compose
->to_list
);
6273 if (compose
->encdata
!= NULL
) {
6274 if (strcmp(compose
->encdata
, "_DONT_ENCRYPT_")) {
6275 err
|= (fprintf(fp
, "X-Claws-Encrypt:%d\n", compose
->use_encryption
) < 0);
6276 err
|= (fprintf(fp
, "X-Claws-Encrypt-Data:%s\n",
6277 compose
->encdata
) < 0);
6278 } /* else we finally dont want to encrypt */
6280 err
|= (fprintf(fp
, "X-Claws-Encrypt:%d\n", compose
->use_encryption
) < 0);
6281 /* and if encdata was null, it means there's been a problem in
6284 g_warning("failed to write queue message");
6288 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY
;
6293 /* Save copy folder */
6294 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
))) {
6295 gchar
*savefolderid
;
6297 savefolderid
= compose_get_save_to(compose
);
6298 err
|= (fprintf(fp
, "SCF:%s\n", savefolderid
) < 0);
6299 g_free(savefolderid
);
6301 /* Save copy folder */
6302 if (compose
->return_receipt
) {
6303 err
|= (fprintf(fp
, "RRCPT:1\n") < 0);
6305 /* Message-ID of message replying to */
6306 if ((compose
->replyinfo
!= NULL
) && (compose
->replyinfo
->msgid
!= NULL
)) {
6307 gchar
*folderid
= NULL
;
6309 if (compose
->replyinfo
->folder
)
6310 folderid
= folder_item_get_identifier(compose
->replyinfo
->folder
);
6311 if (folderid
== NULL
)
6312 folderid
= g_strdup("NULL");
6314 err
|= (fprintf(fp
, "RMID:%s\t%d\t%s\n", folderid
, compose
->replyinfo
->msgnum
, compose
->replyinfo
->msgid
) < 0);
6317 /* Message-ID of message forwarding to */
6318 if ((compose
->fwdinfo
!= NULL
) && (compose
->fwdinfo
->msgid
!= NULL
)) {
6319 gchar
*folderid
= NULL
;
6321 if (compose
->fwdinfo
->folder
)
6322 folderid
= folder_item_get_identifier(compose
->fwdinfo
->folder
);
6323 if (folderid
== NULL
)
6324 folderid
= g_strdup("NULL");
6326 err
|= (fprintf(fp
, "FMID:%s\t%d\t%s\n", folderid
, compose
->fwdinfo
->msgnum
, compose
->fwdinfo
->msgid
) < 0);
6330 err
|= (fprintf(fp
, "X-Claws-Auto-Wrapping:%d\n", compose
->autowrap
) < 0);
6331 err
|= (fprintf(fp
, "X-Claws-Auto-Indent:%d\n", compose
->autoindent
) < 0);
6333 /* end of headers */
6334 err
|= (fprintf(fp
, "X-Claws-End-Special-Headers: 1\n") < 0);
6336 if (compose
->redirect_filename
!= NULL
) {
6337 if (compose_redirect_write_to_file(compose
, fp
) < 0) {
6341 return COMPOSE_QUEUE_ERROR_WITH_ERRNO
;
6345 if ((result
= compose_write_to_file(compose
, fp
, COMPOSE_WRITE_FOR_SEND
, TRUE
)) < 0) {
6353 g_warning("failed to write queue message");
6357 return COMPOSE_QUEUE_ERROR_WITH_ERRNO
;
6359 if (claws_safe_fclose(fp
) == EOF
) {
6360 FILE_OP_ERROR(tmp
, "claws_fclose");
6363 return COMPOSE_QUEUE_ERROR_WITH_ERRNO
;
6366 if (item
&& *item
) {
6369 queue
= account_get_special_folder(compose
->account
, F_QUEUE
);
6372 g_warning("can't find queue folder");
6375 return COMPOSE_QUEUE_ERROR_NO_MSG
;
6377 folder_item_scan(queue
);
6378 if ((num
= folder_item_add_msg(queue
, tmp
, NULL
, FALSE
)) < 0) {
6379 g_warning("can't queue the message");
6382 return COMPOSE_QUEUE_ERROR_NO_MSG
;
6385 if (msgpath
== NULL
) {
6391 if (compose
->mode
== COMPOSE_REEDIT
&& compose
->targetinfo
) {
6392 MsgInfo
*mi
= folder_item_get_msginfo(queue
, num
);
6394 procmsg_msginfo_change_flags(mi
,
6395 compose
->targetinfo
->flags
.perm_flags
,
6396 compose
->targetinfo
->flags
.tmp_flags
& ~(MSG_COPY
| MSG_MOVE
| MSG_MOVE_DONE
),
6399 g_slist_free(mi
->tags
);
6400 mi
->tags
= g_slist_copy(compose
->targetinfo
->tags
);
6401 procmsg_msginfo_free(&mi
);
6405 if (compose
->mode
== COMPOSE_REEDIT
&& remove_reedit_target
) {
6406 compose_remove_reedit_target(compose
, FALSE
);
6409 if ((msgnum
!= NULL
) && (item
!= NULL
)) {
6414 return COMPOSE_QUEUE_SUCCESS
;
6417 static int compose_add_attachments(Compose
*compose
, MimeInfo
*parent
, gint action
)
6420 GtkTreeView
*tree_view
= GTK_TREE_VIEW(compose
->attach_clist
);
6425 GError
*error
= NULL
;
6430 gchar
*type
, *subtype
;
6431 GtkTreeModel
*model
;
6434 model
= gtk_tree_view_get_model(tree_view
);
6436 if (!gtk_tree_model_get_iter_first(model
, &iter
))
6439 gtk_tree_model_get(model
, &iter
, COL_DATA
, &ainfo
, -1);
6441 if (!is_file_exist(ainfo
->file
)) {
6442 gchar
*msg
= g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo
->file
);
6443 AlertValue val
= alertpanel_full(_("Warning"), msg
, NULL
,
6444 action
== COMPOSE_WRITE_FOR_STORE
? _("Cancel drafting"): _("Cancel sending"),
6445 NULL
, _("Ignore attachment"), NULL
, NULL
,
6446 ALERTFOCUS_FIRST
, FALSE
, NULL
, ALERT_WARNING
);
6448 if (val
== G_ALERTDEFAULT
) {
6454 f
= g_file_new_for_path(ainfo
->file
);
6455 fi
= g_file_query_info(f
, "standard::size",
6456 G_FILE_QUERY_INFO_NONE
, NULL
, &error
);
6457 if (error
!= NULL
) {
6458 g_warning(error
->message
);
6459 g_error_free(error
);
6463 size
= g_file_info_get_size(fi
);
6467 if (g_stat(ainfo
->file
, &statbuf
) < 0)
6469 size
= statbuf
.st_size
;
6472 mimepart
= procmime_mimeinfo_new();
6473 mimepart
->content
= MIMECONTENT_FILE
;
6474 mimepart
->data
.filename
= g_strdup(ainfo
->file
);
6475 mimepart
->tmp
= FALSE
; /* or we destroy our attachment */
6476 mimepart
->offset
= 0;
6477 mimepart
->length
= size
;
6479 type
= g_strdup(ainfo
->content_type
);
6481 if (!strchr(type
, '/')) {
6483 type
= g_strdup("application/octet-stream");
6486 subtype
= strchr(type
, '/') + 1;
6487 *(subtype
- 1) = '\0';
6488 mimepart
->type
= procmime_get_media_type(type
);
6489 mimepart
->subtype
= g_strdup(subtype
);
6492 if (mimepart
->type
== MIMETYPE_MESSAGE
&&
6493 !g_ascii_strcasecmp(mimepart
->subtype
, "rfc822")) {
6494 mimepart
->disposition
= DISPOSITIONTYPE_INLINE
;
6495 } else if (mimepart
->type
== MIMETYPE_TEXT
) {
6496 if (!ainfo
->name
&& g_ascii_strcasecmp(mimepart
->subtype
, "plain")) {
6497 /* Text parts with no name come from multipart/alternative
6498 * forwards. Make sure the recipient won't look at the
6499 * original HTML part by mistake. */
6500 mimepart
->disposition
= DISPOSITIONTYPE_ATTACHMENT
;
6501 ainfo
->name
= g_strdup_printf(_("Original %s part"),
6505 g_hash_table_insert(mimepart
->typeparameters
,
6506 g_strdup("charset"), g_strdup(ainfo
->charset
));
6508 if (ainfo
->name
&& mimepart
->type
!= MIMETYPE_MESSAGE
) {
6509 if (mimepart
->type
== MIMETYPE_APPLICATION
&&
6510 !g_strcmp0(mimepart
->subtype
, "octet-stream"))
6511 g_hash_table_insert(mimepart
->typeparameters
,
6512 g_strdup("name"), g_strdup(ainfo
->name
));
6513 g_hash_table_insert(mimepart
->dispositionparameters
,
6514 g_strdup("filename"), g_strdup(ainfo
->name
));
6515 mimepart
->disposition
= DISPOSITIONTYPE_ATTACHMENT
;
6518 if (mimepart
->type
== MIMETYPE_MESSAGE
6519 || mimepart
->type
== MIMETYPE_MULTIPART
)
6520 ainfo
->encoding
= ENC_BINARY
;
6521 else if (compose
->use_signing
|| compose
->fwdinfo
!= NULL
) {
6522 if (ainfo
->encoding
== ENC_7BIT
)
6523 ainfo
->encoding
= ENC_QUOTED_PRINTABLE
;
6524 else if (ainfo
->encoding
== ENC_8BIT
)
6525 ainfo
->encoding
= ENC_BASE64
;
6528 procmime_encode_content(mimepart
, ainfo
->encoding
);
6530 g_node_append(parent
->node
, mimepart
->node
);
6531 } while (gtk_tree_model_iter_next(model
, &iter
));
6536 static gchar
*compose_quote_list_of_addresses(gchar
*str
)
6538 GSList
*list
= NULL
, *item
= NULL
;
6539 gchar
*qname
= NULL
, *faddr
= NULL
, *result
= NULL
;
6541 list
= address_list_append_with_comments(list
, str
);
6542 for (item
= list
; item
!= NULL
; item
= item
->next
) {
6543 gchar
*spec
= item
->data
;
6544 gchar
*endofname
= strstr(spec
, " <");
6545 if (endofname
!= NULL
) {
6548 QUOTE_IF_REQUIRED_NORMAL(qname
, spec
, return NULL
);
6549 qqname
= escape_internal_quotes(qname
, '"');
6551 if (*qname
!= *spec
|| qqname
!= qname
) { /* has been quoted, compute new */
6552 gchar
*addr
= g_strdup(endofname
);
6553 gchar
*name
= (qqname
!= qname
)? qqname
: g_strdup(qname
);
6554 faddr
= g_strconcat(name
, addr
, NULL
);
6557 debug_print("new auto-quoted address: '%s'\n", faddr
);
6561 result
= g_strdup((faddr
!= NULL
)? faddr
: spec
);
6563 gchar
*tmp
= g_strconcat(result
,
6565 (faddr
!= NULL
)? faddr
: spec
,
6570 if (faddr
!= NULL
) {
6575 slist_free_strings_full(list
);
6580 #define IS_IN_CUSTOM_HEADER(header) \
6581 (compose->account->add_customhdr && \
6582 custom_header_find(compose->account->customhdr_list, header) != NULL)
6584 static const gchar
*compose_untranslated_header_name(gchar
*header_name
)
6586 /* return the untranslated header name, if header_name is a known
6587 header name, in either its translated or untranslated form, with
6588 or without trailing colon. otherwise, returns header_name. */
6589 gchar
*translated_header_name
;
6590 gchar
*translated_header_name_wcolon
;
6591 const gchar
*untranslated_header_name
;
6592 const gchar
*untranslated_header_name_wcolon
;
6595 cm_return_val_if_fail(header_name
!= NULL
, NULL
);
6597 for (i
= 0; HEADERS
[i
].header_name
!= NULL
; i
++) {
6598 untranslated_header_name
= HEADERS
[i
].header_name
;
6599 untranslated_header_name_wcolon
= HEADERS
[i
].header_name_w_colon
;
6601 translated_header_name
= gettext(untranslated_header_name
);
6602 translated_header_name_wcolon
= gettext(untranslated_header_name_wcolon
);
6604 if (!strcmp(header_name
, untranslated_header_name
) ||
6605 !strcmp(header_name
, translated_header_name
)) {
6606 return untranslated_header_name
;
6608 if (!strcmp(header_name
, untranslated_header_name_wcolon
) ||
6609 !strcmp(header_name
, translated_header_name_wcolon
)) {
6610 return untranslated_header_name_wcolon
;
6614 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name
);
6618 static void compose_add_headerfield_from_headerlist(Compose
*compose
,
6620 const gchar
*fieldname
,
6621 const gchar
*seperator
)
6623 gchar
*str
, *fieldname_w_colon
;
6624 gboolean add_field
= FALSE
;
6626 ComposeHeaderEntry
*headerentry
;
6627 const gchar
*headerentryname
;
6628 const gchar
*trans_fieldname
;
6631 if (IS_IN_CUSTOM_HEADER(fieldname
))
6634 debug_print("Adding %s-fields\n", fieldname
);
6636 fieldstr
= g_string_sized_new(64);
6638 fieldname_w_colon
= g_strconcat(fieldname
, ":", NULL
);
6639 trans_fieldname
= prefs_common_translated_header_name(fieldname_w_colon
);
6641 for (list
= compose
->header_list
; list
; list
= list
->next
) {
6642 headerentry
= ((ComposeHeaderEntry
*)list
->data
);
6643 headerentryname
= gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry
->combo
)))));
6645 if (!g_utf8_collate(trans_fieldname
, headerentryname
)) {
6646 gchar
* ustr
= gtk_editable_get_chars(GTK_EDITABLE(headerentry
->entry
), 0, -1);
6648 str
= compose_quote_list_of_addresses(ustr
);
6650 if (str
!= NULL
&& str
[0] != '\0') {
6652 g_string_append(fieldstr
, seperator
);
6653 g_string_append(fieldstr
, str
);
6662 buf
= g_new0(gchar
, fieldstr
->len
* 4 + 256);
6663 compose_convert_header
6664 (compose
, buf
, fieldstr
->len
* 4 + 256, fieldstr
->str
,
6665 strlen(fieldname
) + 2, TRUE
);
6666 g_string_append_printf(header
, "%s: %s\n", fieldname
, buf
);
6670 g_free(fieldname_w_colon
);
6671 g_string_free(fieldstr
, TRUE
);
6676 static gchar
*compose_get_manual_headers_info(Compose
*compose
)
6678 GString
*sh_header
= g_string_new(" ");
6680 gchar
*std_headers
[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL
};
6682 for (list
= compose
->header_list
; list
; list
= list
->next
) {
6683 ComposeHeaderEntry
*headerentry
;
6686 gchar
*headername_wcolon
;
6687 const gchar
*headername_trans
;
6689 gboolean standard_header
= FALSE
;
6691 headerentry
= ((ComposeHeaderEntry
*)list
->data
);
6693 tmp
= g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry
->combo
))))));
6695 if (*tmp
== '\0' || strchr(tmp
, ' ') != NULL
|| strchr(tmp
, '\r') != NULL
|| strchr(tmp
, '\n') != NULL
) {
6700 if (!strstr(tmp
, ":")) {
6701 headername_wcolon
= g_strconcat(tmp
, ":", NULL
);
6702 headername
= g_strdup(tmp
);
6704 headername_wcolon
= g_strdup(tmp
);
6705 headername
= g_strdup(strtok(tmp
, ":"));
6709 string
= std_headers
;
6710 while (*string
!= NULL
) {
6711 headername_trans
= prefs_common_translated_header_name(*string
);
6712 if (!strcmp(headername_trans
, headername_wcolon
))
6713 standard_header
= TRUE
;
6716 if (!standard_header
&& !IS_IN_CUSTOM_HEADER(headername
))
6717 g_string_append_printf(sh_header
, "%s ", headername
);
6719 g_free(headername_wcolon
);
6721 g_string_truncate(sh_header
, strlen(sh_header
->str
) - 1); /* remove last space */
6722 return g_string_free(sh_header
, FALSE
);
6725 static gchar
*compose_get_header(Compose
*compose
)
6727 gchar date
[RFC822_DATE_BUFFSIZE
];
6728 gchar buf
[BUFFSIZE
];
6729 const gchar
*entry_str
;
6733 gchar
*std_headers
[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL
};
6735 gchar
*from_name
= NULL
, *from_address
= NULL
;
6738 cm_return_val_if_fail(compose
->account
!= NULL
, NULL
);
6739 cm_return_val_if_fail(compose
->account
->address
!= NULL
, NULL
);
6741 header
= g_string_sized_new(64);
6744 if (prefs_common
.hide_timezone
)
6745 get_rfc822_date_hide_tz(date
, sizeof(date
));
6747 get_rfc822_date(date
, sizeof(date
));
6748 g_string_append_printf(header
, "Date: %s\n", date
);
6752 if (compose
->account
->name
&& *compose
->account
->name
) {
6754 QUOTE_IF_REQUIRED(buf
, compose
->account
->name
);
6755 tmp
= g_strdup_printf("%s <%s>",
6756 buf
, compose
->account
->address
);
6758 tmp
= g_strdup_printf("%s",
6759 compose
->account
->address
);
6761 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose
->from_name
)), tmp
)
6762 || strlen(gtk_entry_get_text(GTK_ENTRY(compose
->from_name
))) == 0) {
6764 from_name
= compose
->account
->name
? g_strdup(compose
->account
->name
):NULL
;
6765 from_address
= g_strdup(compose
->account
->address
);
6767 gchar
*spec
= gtk_editable_get_chars(GTK_EDITABLE(compose
->from_name
), 0, -1);
6768 /* extract name and address */
6769 if (strstr(spec
, " <") && strstr(spec
, ">")) {
6770 from_address
= g_strdup(strrchr(spec
, '<')+1);
6771 *(strrchr(from_address
, '>')) = '\0';
6772 from_name
= g_strdup(spec
);
6773 *(strrchr(from_name
, '<')) = '\0';
6776 from_address
= g_strdup(spec
);
6783 if (from_name
&& *from_name
) {
6785 compose_convert_header
6786 (compose
, buf
, sizeof(buf
), from_name
,
6787 strlen("From: "), TRUE
);
6788 QUOTE_IF_REQUIRED(name
, buf
);
6789 qname
= escape_internal_quotes(name
, '"');
6791 g_string_append_printf(header
, "From: %s <%s>\n",
6792 qname
, from_address
);
6793 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6794 compose
->return_receipt
) {
6795 compose_convert_header(compose
, buf
, sizeof(buf
), from_name
,
6796 strlen("Disposition-Notification-To: "),
6798 g_string_append_printf(header
, "Disposition-Notification-To: %s <%s>\n", buf
, from_address
);
6803 g_string_append_printf(header
, "From: %s\n", from_address
);
6804 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6805 compose
->return_receipt
)
6806 g_string_append_printf(header
, "Disposition-Notification-To: %s\n", from_address
);
6810 g_free(from_address
);
6813 compose_add_headerfield_from_headerlist(compose
, header
, "To", ", ");
6816 compose_add_headerfield_from_headerlist(compose
, header
, "Newsgroups", ",");
6819 compose_add_headerfield_from_headerlist(compose
, header
, "Cc", ", ");
6823 * If this account is a NNTP account remove Bcc header from
6824 * message body since it otherwise will be publicly shown
6826 if (compose
->account
->protocol
!= A_NNTP
)
6827 compose_add_headerfield_from_headerlist(compose
, header
, "Bcc", ", ");
6830 str
= gtk_editable_get_chars(GTK_EDITABLE(compose
->subject_entry
), 0, -1);
6832 if (*str
!= '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6835 compose_convert_header(compose
, buf
, sizeof(buf
), str
,
6836 strlen("Subject: "), FALSE
);
6837 g_string_append_printf(header
, "Subject: %s\n", buf
);
6843 if (compose
->msgid
!= NULL
&& strlen(compose
->msgid
) > 0) {
6844 g_string_append_printf(header
, "Message-ID: <%s>\n",
6848 if (compose
->remove_references
== FALSE
) {
6850 if (compose
->inreplyto
&& compose
->to_list
)
6851 g_string_append_printf(header
, "In-Reply-To: <%s>\n", compose
->inreplyto
);
6854 if (compose
->references
)
6855 g_string_append_printf(header
, "References: %s\n", compose
->references
);
6859 compose_add_headerfield_from_headerlist(compose
, header
, "Followup-To", ",");
6862 compose_add_headerfield_from_headerlist(compose
, header
, "Reply-To", ", ");
6865 if (compose
->account
->organization
&&
6866 strlen(compose
->account
->organization
) &&
6867 !IS_IN_CUSTOM_HEADER("Organization")) {
6868 compose_convert_header(compose
, buf
, sizeof(buf
),
6869 compose
->account
->organization
,
6870 strlen("Organization: "), FALSE
);
6871 g_string_append_printf(header
, "Organization: %s\n", buf
);
6874 /* Program version and system info */
6875 if (compose
->account
->gen_xmailer
&&
6876 g_slist_length(compose
->to_list
) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6877 !compose
->newsgroup_list
) {
6878 g_string_append_printf(header
, "X-Mailer: %s (GTK %d.%d.%d; %s)\n",
6880 gtk_major_version
, gtk_minor_version
, gtk_micro_version
,
6883 if (compose
->account
->gen_xmailer
&&
6884 g_slist_length(compose
->newsgroup_list
) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6885 g_string_append_printf(header
, "X-Newsreader: %s (GTK %d.%d.%d; %s)\n",
6887 gtk_major_version
, gtk_minor_version
, gtk_micro_version
,
6891 /* custom headers */
6892 if (compose
->account
->add_customhdr
) {
6895 for (cur
= compose
->account
->customhdr_list
; cur
!= NULL
;
6897 CustomHeader
*chdr
= (CustomHeader
*)cur
->data
;
6899 if (custom_header_is_allowed(chdr
->name
)
6900 && chdr
->value
!= NULL
6901 && *(chdr
->value
) != '\0') {
6902 compose_convert_header
6903 (compose
, buf
, sizeof(buf
),
6905 strlen(chdr
->name
) + 2, FALSE
);
6906 g_string_append_printf(header
, "%s: %s\n", chdr
->name
, buf
);
6911 /* Automatic Faces and X-Faces */
6912 if (get_account_xface (buf
, sizeof(buf
), compose
->account
->account_name
) == 0) {
6913 g_string_append_printf(header
, "X-Face: %s\n", buf
);
6915 else if (get_default_xface (buf
, sizeof(buf
)) == 0) {
6916 g_string_append_printf(header
, "X-Face: %s\n", buf
);
6918 if (get_account_face (buf
, sizeof(buf
), compose
->account
->account_name
) == 0) {
6919 g_string_append_printf(header
, "Face: %s\n", buf
);
6921 else if (get_default_face (buf
, sizeof(buf
)) == 0) {
6922 g_string_append_printf(header
, "Face: %s\n", buf
);
6926 switch (compose
->priority
) {
6927 case PRIORITY_HIGHEST
: g_string_append_printf(header
, "Importance: high\n"
6928 "X-Priority: 1 (Highest)\n");
6930 case PRIORITY_HIGH
: g_string_append_printf(header
, "Importance: high\n"
6931 "X-Priority: 2 (High)\n");
6933 case PRIORITY_NORMAL
: break;
6934 case PRIORITY_LOW
: g_string_append_printf(header
, "Importance: low\n"
6935 "X-Priority: 4 (Low)\n");
6937 case PRIORITY_LOWEST
: g_string_append_printf(header
, "Importance: low\n"
6938 "X-Priority: 5 (Lowest)\n");
6940 default: debug_print("compose: priority unknown : %d\n",
6944 /* get special headers */
6945 for (list
= compose
->header_list
; list
; list
= list
->next
) {
6946 ComposeHeaderEntry
*headerentry
;
6949 gchar
*headername_wcolon
;
6950 const gchar
*headername_trans
;
6953 gboolean standard_header
= FALSE
;
6955 headerentry
= ((ComposeHeaderEntry
*)list
->data
);
6957 tmp
= g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry
->combo
))))));
6959 if (*tmp
== '\0' || strchr(tmp
, ' ') != NULL
|| strchr(tmp
, '\r') != NULL
|| strchr(tmp
, '\n') != NULL
) {
6964 if (!strstr(tmp
, ":")) {
6965 headername_wcolon
= g_strconcat(tmp
, ":", NULL
);
6966 headername
= g_strdup(tmp
);
6968 headername_wcolon
= g_strdup(tmp
);
6969 headername
= g_strdup(strtok(tmp
, ":"));
6973 entry_str
= gtk_entry_get_text(GTK_ENTRY(headerentry
->entry
));
6974 Xstrdup_a(headervalue
, entry_str
, {
6976 g_free(headername_wcolon
);
6977 g_string_free(header
, TRUE
);
6980 subst_char(headervalue
, '\r', ' ');
6981 subst_char(headervalue
, '\n', ' ');
6982 g_strstrip(headervalue
);
6983 if (*headervalue
!= '\0') {
6984 string
= std_headers
;
6985 while (*string
!= NULL
&& !standard_header
) {
6986 headername_trans
= prefs_common_translated_header_name(*string
);
6987 /* support mixed translated and untranslated headers */
6988 if (!strcmp(headername_trans
, headername_wcolon
) || !strcmp(*string
, headername_wcolon
))
6989 standard_header
= TRUE
;
6992 if (!standard_header
&& !IS_IN_CUSTOM_HEADER(headername
)) {
6993 /* store untranslated header name */
6994 g_string_append_printf(header
, "%s %s\n",
6995 compose_untranslated_header_name(headername_wcolon
), headervalue
);
6999 g_free(headername_wcolon
);
7002 return g_string_free(header
, FALSE
);
7005 #undef IS_IN_CUSTOM_HEADER
7007 static void compose_convert_header(Compose
*compose
, gchar
*dest
, gint len
, gchar
*src
,
7008 gint header_len
, gboolean addr_field
)
7010 gchar
*tmpstr
= NULL
;
7011 const gchar
*out_codeset
= NULL
;
7013 cm_return_if_fail(src
!= NULL
);
7014 cm_return_if_fail(dest
!= NULL
);
7016 if (len
< 1) return;
7018 tmpstr
= g_strdup(src
);
7020 subst_char(tmpstr
, '\n', ' ');
7021 subst_char(tmpstr
, '\r', ' ');
7024 if (!g_utf8_validate(tmpstr
, -1, NULL
)) {
7025 gchar
*mybuf
= g_malloc(strlen(tmpstr
)*2 +1);
7026 conv_localetodisp(mybuf
, strlen(tmpstr
)*2 +1, tmpstr
);
7031 codeconv_set_strict(TRUE
);
7032 conv_encode_header_full(dest
, len
, tmpstr
, header_len
, addr_field
,
7033 conv_get_charset_str(compose
->out_encoding
));
7034 codeconv_set_strict(FALSE
);
7036 if (!dest
|| *dest
== '\0') {
7037 gchar
*test_conv_global_out
= NULL
;
7038 gchar
*test_conv_reply
= NULL
;
7040 /* automatic mode. be automatic. */
7041 codeconv_set_strict(TRUE
);
7043 out_codeset
= conv_get_outgoing_charset_str();
7045 debug_print("trying to convert to %s\n", out_codeset
);
7046 test_conv_global_out
= conv_codeset_strdup(src
, CS_INTERNAL
, out_codeset
);
7049 if (!test_conv_global_out
&& compose
->orig_charset
7050 && strcmp(compose
->orig_charset
, CS_US_ASCII
)) {
7051 out_codeset
= compose
->orig_charset
;
7052 debug_print("failure; trying to convert to %s\n", out_codeset
);
7053 test_conv_reply
= conv_codeset_strdup(src
, CS_INTERNAL
, out_codeset
);
7056 if (!test_conv_global_out
&& !test_conv_reply
) {
7058 out_codeset
= CS_INTERNAL
;
7059 debug_print("finally using %s\n", out_codeset
);
7061 g_free(test_conv_global_out
);
7062 g_free(test_conv_reply
);
7063 conv_encode_header_full(dest
, len
, tmpstr
, header_len
, addr_field
,
7065 codeconv_set_strict(FALSE
);
7070 static void compose_add_to_addressbook_cb(GtkMenuItem
*menuitem
, gpointer user_data
)
7074 cm_return_if_fail(user_data
!= NULL
);
7076 address
= g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data
)));
7077 g_strstrip(address
);
7078 if (*address
!= '\0') {
7079 gchar
*name
= procheader_get_fromname(address
);
7080 extract_address(address
);
7081 #ifndef USE_ALT_ADDRBOOK
7082 addressbook_add_contact(name
, address
, NULL
, NULL
);
7084 debug_print("%s: %s\n", name
, address
);
7085 if (addressadd_selection(name
, address
, NULL
, NULL
)) {
7086 debug_print( "addressbook_add_contact - added\n" );
7093 static void compose_entry_popup_extend(GtkEntry
*entry
, GtkMenu
*menu
, gpointer user_data
)
7095 GtkWidget
*menuitem
;
7098 cm_return_if_fail(menu
!= NULL
);
7099 cm_return_if_fail(GTK_IS_MENU_SHELL(menu
));
7101 menuitem
= gtk_separator_menu_item_new();
7102 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu
), menuitem
);
7103 gtk_widget_show(menuitem
);
7105 menuitem
= gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7106 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu
), menuitem
);
7108 address
= g_strdup(gtk_entry_get_text(GTK_ENTRY(entry
)));
7109 g_strstrip(address
);
7110 if (*address
== '\0') {
7111 gtk_widget_set_sensitive(GTK_WIDGET(menuitem
), FALSE
);
7114 g_signal_connect(G_OBJECT(menuitem
), "activate",
7115 G_CALLBACK(compose_add_to_addressbook_cb
), entry
);
7116 gtk_widget_show(menuitem
);
7119 void compose_add_extra_header(gchar
*header
, GtkListStore
*model
)
7122 if (strcmp(header
, "")) {
7123 COMBOBOX_ADD(model
, header
, COMPOSE_TO
);
7127 void compose_add_extra_header_entries(GtkListStore
*model
)
7131 gchar buf
[BUFFSIZE
];
7134 if (extra_headers
== NULL
) {
7135 exhrc
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
, "extraheaderrc", NULL
);
7136 if ((exh
= claws_fopen(exhrc
, "rb")) == NULL
) {
7137 debug_print("extra headers file not found\n");
7138 goto extra_headers_done
;
7140 while (claws_fgets(buf
, BUFFSIZE
, exh
) != NULL
) {
7141 lastc
= strlen(buf
) - 1; /* remove trailing control chars */
7142 while (lastc
>= 0 && buf
[lastc
] != ':')
7143 buf
[lastc
--] = '\0';
7144 if (lastc
> 0 && buf
[0] != '#' && buf
[lastc
] == ':') {
7145 buf
[lastc
] = '\0'; /* remove trailing : for comparison */
7146 if (custom_header_is_allowed(buf
)) {
7148 extra_headers
= g_slist_prepend(extra_headers
, g_strdup(buf
));
7151 g_message("disallowed extra header line: %s\n", buf
);
7155 g_message("invalid extra header line: %s\n", buf
);
7161 extra_headers
= g_slist_prepend(extra_headers
, g_strdup("")); /* end of list */
7162 extra_headers
= g_slist_reverse(extra_headers
);
7164 g_slist_foreach(extra_headers
, (GFunc
)compose_add_extra_header
, (gpointer
)model
);
7168 static void _ldap_srv_func(gpointer data
, gpointer user_data
)
7170 LdapServer
*server
= (LdapServer
*)data
;
7171 gboolean
*enable
= (gboolean
*)user_data
;
7173 debug_print("%s server '%s'\n", (*enable
== TRUE
? "enabling" : "disabling"), server
->control
->hostName
);
7174 server
->searchFlag
= *enable
;
7178 static void compose_create_header_entry(Compose
*compose
)
7180 gchar
*headers
[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL
};
7187 const gchar
*header
= NULL
;
7188 ComposeHeaderEntry
*headerentry
;
7189 gboolean standard_header
= FALSE
;
7190 GtkListStore
*model
;
7193 headerentry
= g_new0(ComposeHeaderEntry
, 1);
7195 /* Combo box model */
7196 model
= gtk_list_store_new(3, G_TYPE_STRING
, G_TYPE_INT
, G_TYPE_BOOLEAN
);
7197 COMBOBOX_ADD(model
, prefs_common_translated_header_name("To:"),
7199 COMBOBOX_ADD(model
, prefs_common_translated_header_name("Cc:"),
7201 COMBOBOX_ADD(model
, prefs_common_translated_header_name("Bcc:"),
7203 COMBOBOX_ADD(model
, prefs_common_translated_header_name("Newsgroups:"),
7204 COMPOSE_NEWSGROUPS
);
7205 COMBOBOX_ADD(model
, prefs_common_translated_header_name("Reply-To:"),
7207 COMBOBOX_ADD(model
, prefs_common_translated_header_name("Followup-To:"),
7208 COMPOSE_FOLLOWUPTO
);
7209 compose_add_extra_header_entries(model
);
7212 combo
= gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model
));
7213 GtkCellRenderer
*cell
= gtk_cell_renderer_text_new();
7214 gtk_cell_renderer_set_alignment(cell
, 0.0, 0.5);
7215 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo
), cell
, TRUE
);
7216 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo
), 0);
7217 gtk_combo_box_set_active(GTK_COMBO_BOX(combo
), 0);
7218 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo
))), "grab_focus",
7219 G_CALLBACK(compose_grab_focus_cb
), compose
);
7220 gtk_widget_show(combo
);
7222 gtk_grid_attach(GTK_GRID(compose
->header_table
), combo
, 0, compose
->header_nextrow
,
7224 if (compose
->header_last
&& (compose
->draft_timeout_tag
!= COMPOSE_DRAFT_TIMEOUT_FORBIDDEN
)) {
7225 const gchar
*last_header_entry
= gtk_entry_get_text(
7226 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose
->header_last
->combo
)))));
7228 while (*string
!= NULL
) {
7229 if (!strcmp(prefs_common_translated_header_name(*string
), last_header_entry
))
7230 standard_header
= TRUE
;
7233 if (standard_header
)
7234 header
= gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose
->header_last
->combo
)))));
7236 if (!compose
->header_last
|| !standard_header
) {
7237 switch(compose
->account
->protocol
) {
7239 header
= prefs_common_translated_header_name("Newsgroups:");
7242 header
= prefs_common_translated_header_name("To:");
7247 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo
)))), header
);
7249 gtk_editable_set_editable(
7250 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo
)))),
7251 prefs_common
.type_any_header
);
7253 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo
)))), "grab_focus",
7254 G_CALLBACK(compose_grab_focus_cb
), compose
);
7256 /* Entry field with cleanup button */
7257 button
= gtk_button_new_from_icon_name("edit-clear", GTK_ICON_SIZE_MENU
);
7258 gtk_widget_show(button
);
7259 CLAWS_SET_TIP(button
,
7260 _("Delete entry contents"));
7261 entry
= gtk_entry_new();
7262 gtk_widget_show(entry
);
7263 CLAWS_SET_TIP(entry
,
7264 _("Use <tab> to autocomplete from addressbook"));
7265 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
7266 gtk_widget_show(hbox
);
7267 gtk_box_pack_start (GTK_BOX (hbox
), entry
, TRUE
, TRUE
, 0);
7268 gtk_box_pack_start (GTK_BOX (hbox
), button
, FALSE
, FALSE
, 0);
7269 gtk_grid_attach(GTK_GRID(compose
->header_table
), hbox
, 1, compose
->header_nextrow
,
7271 gtk_widget_set_hexpand(hbox
, TRUE
);
7272 gtk_widget_set_halign(hbox
, GTK_ALIGN_FILL
);
7274 g_signal_connect(G_OBJECT(entry
), "key-press-event",
7275 G_CALLBACK(compose_headerentry_key_press_event_cb
),
7277 g_signal_connect(G_OBJECT(entry
), "changed",
7278 G_CALLBACK(compose_headerentry_changed_cb
),
7280 g_signal_connect_after(G_OBJECT(entry
), "grab_focus",
7281 G_CALLBACK(compose_grab_focus_cb
), compose
);
7283 g_signal_connect(G_OBJECT(button
), "clicked",
7284 G_CALLBACK(compose_headerentry_button_clicked_cb
),
7288 gtk_drag_dest_set(entry
, GTK_DEST_DEFAULT_ALL
, compose_mime_types
,
7289 sizeof(compose_mime_types
)/sizeof(compose_mime_types
[0]),
7290 GDK_ACTION_COPY
| GDK_ACTION_MOVE
);
7291 g_signal_connect(G_OBJECT(entry
), "drag_data_received",
7292 G_CALLBACK(compose_header_drag_received_cb
),
7294 g_signal_connect(G_OBJECT(entry
), "drag-drop",
7295 G_CALLBACK(compose_drag_drop
),
7297 g_signal_connect(G_OBJECT(entry
), "populate-popup",
7298 G_CALLBACK(compose_entry_popup_extend
),
7302 #ifndef PASSWORD_CRYPTO_OLD
7303 GSList
*pwd_servers
= addrindex_get_password_protected_ldap_servers();
7304 if (pwd_servers
!= NULL
&& primary_passphrase() == NULL
) {
7305 gboolean enable
= FALSE
;
7306 debug_print("Primary passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7307 /* Temporarily disable password-protected LDAP servers,
7308 * because user did not provide a primary passphrase.
7309 * We can safely enable searchFlag on all servers in this list
7310 * later, since addrindex_get_password_protected_ldap_servers()
7311 * includes servers which have it enabled initially. */
7312 g_slist_foreach(pwd_servers
, _ldap_srv_func
, &enable
);
7313 compose
->passworded_ldap_servers
= pwd_servers
;
7315 #endif /* PASSWORD_CRYPTO_OLD */
7316 #endif /* USE_LDAP */
7318 address_completion_register_entry(GTK_ENTRY(entry
), TRUE
);
7320 headerentry
->compose
= compose
;
7321 headerentry
->combo
= combo
;
7322 headerentry
->entry
= entry
;
7323 headerentry
->button
= button
;
7324 headerentry
->hbox
= hbox
;
7325 headerentry
->headernum
= compose
->header_nextrow
;
7326 headerentry
->type
= PREF_NONE
;
7328 compose
->header_nextrow
++;
7329 compose
->header_last
= headerentry
;
7330 compose
->header_list
=
7331 g_slist_append(compose
->header_list
,
7335 static void compose_add_header_entry(Compose
*compose
, const gchar
*header
,
7336 gchar
*text
, ComposePrefType pref_type
)
7338 ComposeHeaderEntry
*last_header
= compose
->header_last
;
7339 gchar
*tmp
= g_strdup(text
), *email
;
7340 gboolean replyto_hdr
;
7342 replyto_hdr
= (!strcasecmp(header
,
7343 prefs_common_translated_header_name("Reply-To:")) ||
7345 prefs_common_translated_header_name("Followup-To:")) ||
7347 prefs_common_translated_header_name("In-Reply-To:")));
7349 extract_address(tmp
);
7350 email
= g_utf8_strdown(tmp
, -1);
7352 if (replyto_hdr
== FALSE
&&
7353 g_hash_table_lookup(compose
->email_hashtable
, email
) != NULL
)
7355 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7356 header
, text
, (gint
) pref_type
);
7362 if (!strcasecmp(header
, prefs_common_translated_header_name("In-Reply-To:")))
7363 gtk_entry_set_text(GTK_ENTRY(
7364 gtk_bin_get_child(GTK_BIN(last_header
->combo
))), header
);
7366 combobox_select_by_text(GTK_COMBO_BOX(last_header
->combo
), header
);
7367 gtk_entry_set_text(GTK_ENTRY(last_header
->entry
), text
);
7368 last_header
->type
= pref_type
;
7370 if (replyto_hdr
== FALSE
)
7371 g_hash_table_insert(compose
->email_hashtable
, email
,
7372 GUINT_TO_POINTER(1));
7379 static void compose_destroy_headerentry(Compose
*compose
,
7380 ComposeHeaderEntry
*headerentry
)
7382 gchar
*text
= gtk_editable_get_chars(GTK_EDITABLE(headerentry
->entry
), 0, -1);
7385 extract_address(text
);
7386 email
= g_utf8_strdown(text
, -1);
7387 g_hash_table_remove(compose
->email_hashtable
, email
);
7391 gtk_widget_destroy(headerentry
->combo
);
7392 gtk_widget_destroy(headerentry
->entry
);
7393 gtk_widget_destroy(headerentry
->button
);
7394 gtk_widget_destroy(headerentry
->hbox
);
7395 g_free(headerentry
);
7398 static void compose_remove_header_entries(Compose
*compose
)
7401 for (list
= compose
->header_list
; list
; list
= list
->next
)
7402 compose_destroy_headerentry(compose
, (ComposeHeaderEntry
*)list
->data
);
7404 compose
->header_last
= NULL
;
7405 g_slist_free(compose
->header_list
);
7406 compose
->header_list
= NULL
;
7407 compose
->header_nextrow
= 1;
7408 compose_create_header_entry(compose
);
7411 static GtkWidget
*compose_create_header(Compose
*compose
)
7413 GtkWidget
*from_optmenu_hbox
;
7414 GtkWidget
*header_table_main
;
7415 GtkWidget
*header_scrolledwin
;
7416 GtkWidget
*header_table
;
7418 /* parent with account selection and from header */
7419 header_table_main
= gtk_grid_new();
7420 gtk_widget_show(header_table_main
);
7421 gtk_container_set_border_width(GTK_CONTAINER(header_table_main
), BORDER_WIDTH
);
7423 from_optmenu_hbox
= compose_account_option_menu_create(compose
);
7424 gtk_grid_attach(GTK_GRID(header_table_main
),from_optmenu_hbox
, 0, 0, 1, 1);
7425 gtk_widget_set_hexpand(from_optmenu_hbox
, TRUE
);
7426 gtk_widget_set_halign(from_optmenu_hbox
, GTK_ALIGN_FILL
);
7428 /* child with header labels and entries */
7429 header_scrolledwin
= gtk_scrolled_window_new(NULL
, NULL
);
7430 gtk_widget_show(header_scrolledwin
);
7431 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin
), GTK_POLICY_NEVER
, GTK_POLICY_AUTOMATIC
);
7433 header_table
= gtk_grid_new();
7434 gtk_widget_show(header_table
);
7435 gtk_container_set_border_width(GTK_CONTAINER(header_table
), 0);
7436 gtk_container_add(GTK_CONTAINER(header_scrolledwin
), header_table
);
7437 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table
),
7438 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin
)));
7439 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin
))), GTK_SHADOW_NONE
);
7441 gtk_grid_attach(GTK_GRID(header_table_main
), header_scrolledwin
, 0, 1, 1, 1);
7442 gtk_widget_set_vexpand(header_scrolledwin
, TRUE
);
7443 gtk_widget_set_valign(header_scrolledwin
, GTK_ALIGN_FILL
);
7445 compose
->header_table
= header_table
;
7446 compose
->header_list
= NULL
;
7447 compose
->header_nextrow
= 0;
7449 compose_create_header_entry(compose
);
7451 compose
->table
= NULL
;
7453 return header_table_main
;
7456 static gboolean
popup_attach_button_pressed(GtkWidget
*widget
, gpointer data
)
7458 Compose
*compose
= (Compose
*)data
;
7459 GdkEventButton event
;
7462 event
.time
= gtk_get_current_event_time();
7464 return attach_button_pressed(compose
->attach_clist
, &event
, compose
);
7467 static GtkWidget
*compose_create_attach(Compose
*compose
)
7469 GtkWidget
*attach_scrwin
;
7470 GtkWidget
*attach_clist
;
7472 GtkListStore
*store
;
7473 GtkCellRenderer
*renderer
;
7474 GtkTreeViewColumn
*column
;
7475 GtkTreeSelection
*selection
;
7477 /* attachment list */
7478 attach_scrwin
= gtk_scrolled_window_new(NULL
, NULL
);
7479 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin
),
7480 GTK_POLICY_AUTOMATIC
,
7481 GTK_POLICY_AUTOMATIC
);
7482 gtk_widget_set_size_request(attach_scrwin
, -1, 80);
7484 store
= gtk_list_store_new(N_ATTACH_COLS
,
7490 G_TYPE_AUTO_POINTER
,
7492 attach_clist
= GTK_WIDGET(gtk_tree_view_new_with_model
7493 (GTK_TREE_MODEL(store
)));
7494 gtk_container_add(GTK_CONTAINER(attach_scrwin
), attach_clist
);
7495 g_object_unref(store
);
7497 renderer
= gtk_cell_renderer_text_new();
7498 column
= gtk_tree_view_column_new_with_attributes
7499 (_("Mime type"), renderer
, "text",
7500 COL_MIMETYPE
, NULL
);
7501 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist
), column
);
7503 renderer
= gtk_cell_renderer_text_new();
7504 column
= gtk_tree_view_column_new_with_attributes
7505 (_("Size"), renderer
, "text",
7507 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist
), column
);
7509 renderer
= gtk_cell_renderer_text_new();
7510 column
= gtk_tree_view_column_new_with_attributes
7511 (_("Name"), renderer
, "text",
7513 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist
), column
);
7515 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist
));
7516 gtk_tree_selection_set_mode(selection
, GTK_SELECTION_MULTIPLE
);
7518 g_signal_connect(G_OBJECT(attach_clist
), "row_activated",
7519 G_CALLBACK(attach_selected
), compose
);
7520 g_signal_connect(G_OBJECT(attach_clist
), "button_press_event",
7521 G_CALLBACK(attach_button_pressed
), compose
);
7522 g_signal_connect(G_OBJECT(attach_clist
), "popup-menu",
7523 G_CALLBACK(popup_attach_button_pressed
), compose
);
7524 g_signal_connect(G_OBJECT(attach_clist
), "key_press_event",
7525 G_CALLBACK(attach_key_pressed
), compose
);
7528 gtk_drag_dest_set(attach_clist
,
7529 GTK_DEST_DEFAULT_ALL
, compose_mime_types
,
7530 sizeof(compose_mime_types
)/sizeof(compose_mime_types
[0]),
7531 GDK_ACTION_COPY
| GDK_ACTION_MOVE
);
7532 g_signal_connect(G_OBJECT(attach_clist
), "drag_data_received",
7533 G_CALLBACK(compose_attach_drag_received_cb
),
7535 g_signal_connect(G_OBJECT(attach_clist
), "drag-drop",
7536 G_CALLBACK(compose_drag_drop
),
7539 compose
->attach_scrwin
= attach_scrwin
;
7540 compose
->attach_clist
= attach_clist
;
7542 return attach_scrwin
;
7545 static void compose_savemsg_select_cb(GtkWidget
*widget
, Compose
*compose
);
7547 static GtkWidget
*compose_create_others(Compose
*compose
)
7550 GtkWidget
*savemsg_checkbtn
;
7551 GtkWidget
*savemsg_combo
;
7552 GtkWidget
*savemsg_select
;
7555 gchar
*folderidentifier
;
7557 /* Table for settings */
7558 table
= gtk_grid_new();
7559 gtk_container_set_border_width(GTK_CONTAINER(table
), BORDER_WIDTH
);
7560 gtk_widget_show(table
);
7561 gtk_grid_set_row_spacing(GTK_GRID(table
), VSPACING_NARROW
);
7564 /* Save Message to folder */
7565 savemsg_checkbtn
= gtk_check_button_new_with_label(_("Save Message to "));
7566 gtk_widget_show(savemsg_checkbtn
);
7567 gtk_grid_attach(GTK_GRID(table
), savemsg_checkbtn
, 0, rowcount
, 1, 1);
7568 if (account_get_special_folder(compose
->account
, F_OUTBOX
)) {
7569 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn
), prefs_common
.savemsg
);
7572 savemsg_combo
= gtk_combo_box_text_new_with_entry();
7573 compose
->savemsg_checkbtn
= savemsg_checkbtn
;
7574 compose
->savemsg_combo
= savemsg_combo
;
7575 gtk_widget_show(savemsg_combo
);
7577 if (prefs_common
.compose_save_to_history
)
7578 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo
),
7579 prefs_common
.compose_save_to_history
);
7580 gtk_grid_attach(GTK_GRID(table
), savemsg_combo
, 1, rowcount
, 1, 1);
7581 gtk_widget_set_hexpand(savemsg_combo
, TRUE
);
7582 gtk_widget_set_halign(savemsg_combo
, GTK_ALIGN_FILL
);
7583 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo
), prefs_common
.savemsg
);
7584 g_signal_connect_after(G_OBJECT(savemsg_combo
), "grab_focus",
7585 G_CALLBACK(compose_grab_focus_cb
), compose
);
7586 if (account_get_special_folder(compose
->account
, F_OUTBOX
)) {
7587 if (compose
->account
->set_sent_folder
|| prefs_common
.savemsg
)
7588 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn
), TRUE
);
7590 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn
), FALSE
);
7591 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo
), TRUE
);
7592 folderidentifier
= folder_item_get_identifier(account_get_special_folder
7593 (compose
->account
, F_OUTBOX
));
7594 compose_set_save_to(compose
, folderidentifier
);
7595 g_free(folderidentifier
);
7598 savemsg_select
= gtkut_get_browse_file_btn(_("_Browse"));
7599 gtk_widget_show(savemsg_select
);
7600 gtk_grid_attach(GTK_GRID(table
), savemsg_select
, 2, rowcount
, 1, 1);
7601 g_signal_connect(G_OBJECT(savemsg_select
), "clicked",
7602 G_CALLBACK(compose_savemsg_select_cb
),
7608 static void compose_savemsg_select_cb(GtkWidget
*widget
, Compose
*compose
)
7613 dest
= foldersel_folder_sel(NULL
, FOLDER_SEL_COPY
, NULL
, FALSE
,
7614 _("Select folder to save message to"));
7617 path
= folder_item_get_identifier(dest
);
7619 compose_set_save_to(compose
, path
);
7623 static void entry_paste_clipboard(Compose
*compose
, GtkWidget
*entry
, gboolean wrap
,
7624 GdkAtom clip
, GtkTextIter
*insert_place
);
7627 static gboolean
text_clicked(GtkWidget
*text
, GdkEventButton
*event
,
7631 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
7633 if (event
->button
== 3) {
7635 GtkTextIter sel_start
, sel_end
;
7636 gboolean stuff_selected
;
7638 /* move the cursor to allow GtkAspell to check the word
7639 * under the mouse */
7640 if (event
->x
&& event
->y
) {
7641 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text
),
7642 GTK_TEXT_WINDOW_TEXT
, event
->x
, event
->y
,
7644 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text
),
7647 GtkTextMark
*mark
= gtk_text_buffer_get_insert(buffer
);
7648 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
7651 stuff_selected
= gtk_text_buffer_get_selection_bounds(
7653 &sel_start
, &sel_end
);
7655 gtk_text_buffer_place_cursor (buffer
, &iter
);
7656 /* reselect stuff */
7658 && gtk_text_iter_in_range(&iter
, &sel_start
, &sel_end
)) {
7659 gtk_text_buffer_select_range(buffer
,
7660 &sel_start
, &sel_end
);
7662 return FALSE
; /* pass the event so that the right-click goes through */
7665 if (event
->button
== 2) {
7670 /* get the middle-click position to paste at the correct place */
7671 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text
),
7672 GTK_TEXT_WINDOW_TEXT
, event
->x
, event
->y
,
7674 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text
),
7677 entry_paste_clipboard(compose
, text
,
7678 prefs_common
.linewrap_pastes
,
7679 GDK_SELECTION_PRIMARY
, &iter
);
7687 static void compose_spell_menu_changed(void *data
)
7689 Compose
*compose
= (Compose
*)data
;
7691 GtkWidget
*menuitem
;
7692 GtkWidget
*parent_item
;
7693 GtkMenu
*menu
= GTK_MENU(gtk_menu_new());
7696 if (compose
->gtkaspell
== NULL
)
7699 parent_item
= gtk_ui_manager_get_widget(compose
->ui_manager
,
7700 "/Menu/Spelling/Options");
7702 /* setting the submenu removes /Spelling/Options from the factory
7703 * so we need to save it */
7705 if (parent_item
== NULL
) {
7706 parent_item
= compose
->aspell_options_menu
;
7707 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item
), NULL
);
7709 compose
->aspell_options_menu
= parent_item
;
7711 spell_menu
= gtkaspell_make_config_menu(compose
->gtkaspell
);
7713 spell_menu
= g_slist_reverse(spell_menu
);
7714 for (items
= spell_menu
;
7715 items
; items
= items
->next
) {
7716 menuitem
= GTK_WIDGET(GTK_MENU_ITEM(items
->data
));
7717 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu
), GTK_WIDGET(menuitem
));
7718 gtk_widget_show(GTK_WIDGET(menuitem
));
7720 g_slist_free(spell_menu
);
7722 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item
), GTK_WIDGET(menu
));
7723 gtk_widget_show(parent_item
);
7726 static void compose_dict_changed(void *data
)
7728 Compose
*compose
= (Compose
*) data
;
7730 if(!compose
->gtkaspell
)
7732 if(compose
->gtkaspell
->recheck_when_changing_dict
== FALSE
)
7735 gtkaspell_highlight_all(compose
->gtkaspell
);
7736 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose
->subject_entry
));
7740 static gboolean
compose_popup_menu(GtkWidget
*widget
, gpointer data
)
7742 Compose
*compose
= (Compose
*)data
;
7743 GdkEventButton event
;
7746 event
.time
= gtk_get_current_event_time();
7750 return text_clicked(compose
->text
, &event
, compose
);
7753 static gboolean compose_force_window_origin
= TRUE
;
7754 static Compose
*compose_create(PrefsAccount
*account
,
7763 GtkWidget
*handlebox
;
7765 GtkWidget
*notebook
;
7767 GtkWidget
*attach_hbox
;
7768 GtkWidget
*attach_lab1
;
7769 GtkWidget
*attach_lab2
;
7774 GtkWidget
*subject_hbox
;
7775 GtkWidget
*subject_frame
;
7776 GtkWidget
*subject_entry
;
7780 GtkWidget
*edit_vbox
;
7781 GtkWidget
*ruler_hbox
;
7783 GtkWidget
*scrolledwin
;
7785 GtkTextBuffer
*buffer
;
7786 GtkClipboard
*clipboard
;
7788 UndoMain
*undostruct
;
7790 GtkWidget
*popupmenu
;
7791 GtkWidget
*tmpl_menu
;
7792 GtkActionGroup
*action_group
= NULL
;
7795 GtkAspell
* gtkaspell
= NULL
;
7798 static GdkGeometry geometry
;
7799 GdkRectangle workarea
= {0};
7801 cm_return_val_if_fail(account
!= NULL
, NULL
);
7803 default_header_bgcolor
= prefs_common
.color
[COL_DEFAULT_HEADER_BG
],
7804 default_header_color
= prefs_common
.color
[COL_DEFAULT_HEADER
],
7806 debug_print("Creating compose window...\n");
7807 compose
= g_new0(Compose
, 1);
7809 compose
->batch
= batch
;
7810 compose
->account
= account
;
7811 compose
->folder
= folder
;
7813 g_mutex_init(&compose
->mutex
);
7814 compose
->set_cursor_pos
= -1;
7816 window
= gtkut_window_new(GTK_WINDOW_TOPLEVEL
, "compose");
7818 gtk_window_set_resizable(GTK_WINDOW(window
), TRUE
);
7819 gtk_window_set_default_size(GTK_WINDOW(window
), prefs_common
.compose_width
,
7820 prefs_common
.compose_height
);
7822 gdk_monitor_get_workarea(gdk_display_get_primary_monitor(gdk_display_get_default()),
7825 if (!geometry
.max_width
) {
7826 geometry
.max_width
= workarea
.width
;
7827 geometry
.max_height
= workarea
.height
;
7830 gtk_window_set_geometry_hints(GTK_WINDOW(window
), NULL
,
7831 &geometry
, GDK_HINT_MAX_SIZE
);
7832 if (!geometry
.min_width
) {
7833 geometry
.min_width
= 600;
7834 geometry
.min_height
= 440;
7836 gtk_window_set_geometry_hints(GTK_WINDOW(window
), NULL
,
7837 &geometry
, GDK_HINT_MIN_SIZE
);
7839 #ifndef GENERIC_UMPC
7840 if (compose_force_window_origin
)
7841 gtk_window_move(GTK_WINDOW(window
), prefs_common
.compose_x
,
7842 prefs_common
.compose_y
);
7844 g_signal_connect(G_OBJECT(window
), "delete_event",
7845 G_CALLBACK(compose_delete_cb
), compose
);
7846 MANAGE_WINDOW_SIGNALS_CONNECT(window
);
7847 gtk_widget_realize(window
);
7849 gtkut_widget_set_composer_icon(window
);
7851 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
7852 gtk_container_add(GTK_CONTAINER(window
), vbox
);
7854 compose
->ui_manager
= gtk_ui_manager_new();
7855 action_group
= cm_menu_create_action_group_full(compose
->ui_manager
,"Menu", compose_entries
,
7856 G_N_ELEMENTS(compose_entries
), (gpointer
)compose
);
7857 gtk_action_group_add_toggle_actions(action_group
, compose_toggle_entries
,
7858 G_N_ELEMENTS(compose_toggle_entries
), (gpointer
)compose
);
7859 gtk_action_group_add_radio_actions(action_group
, compose_radio_rm_entries
,
7860 G_N_ELEMENTS(compose_radio_rm_entries
), COMPOSE_REPLY
, G_CALLBACK(compose_reply_change_mode_cb
), (gpointer
)compose
);
7861 gtk_action_group_add_radio_actions(action_group
, compose_radio_prio_entries
,
7862 G_N_ELEMENTS(compose_radio_prio_entries
), PRIORITY_NORMAL
, G_CALLBACK(compose_set_priority_cb
), (gpointer
)compose
);
7863 gtk_action_group_add_radio_actions(action_group
, compose_radio_enc_entries
,
7864 G_N_ELEMENTS(compose_radio_enc_entries
), C_AUTO
, G_CALLBACK(compose_set_encoding_cb
), (gpointer
)compose
);
7866 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/", "Menu", NULL
, GTK_UI_MANAGER_MENUBAR
)
7868 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU
)
7869 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU
)
7871 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU
)
7873 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU
)
7874 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU
)
7875 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU
)
7878 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM
)
7879 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM
)
7880 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR
)
7881 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM
)
7882 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM
)
7883 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM
)
7884 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM
)
7885 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR
)
7886 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM
)
7887 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR
)
7888 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM
)
7889 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR
)
7890 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM
)
7893 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM
)
7894 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM
)
7895 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR
)
7897 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM
)
7898 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM
)
7899 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM
)
7901 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU
)
7902 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM
)
7903 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM
)
7904 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM
)
7906 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM
)
7908 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU
)
7909 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM
)
7910 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM
)
7911 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM
)
7912 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM
)
7913 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM
)
7914 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM
)
7915 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM
)
7916 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM
)
7917 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM
)
7918 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM
)
7919 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM
)
7920 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM
)
7921 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM
)
7922 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM
)
7924 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR
)
7926 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM
)
7927 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM
)
7928 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM
)
7929 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM
)
7930 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM
)
7932 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR
)
7933 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM
)
7937 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM
)
7938 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM
)
7939 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM
)
7940 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM
)
7941 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR
)
7942 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU
)
7946 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU
)
7947 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM
)
7948 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM
)
7949 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM
)
7950 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM
)
7952 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR
)
7953 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU
)
7954 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM
)
7955 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM
)
7956 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM
)
7959 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR
)
7960 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU
)
7961 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM
)
7962 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM
)
7963 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM
)
7964 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM
)
7965 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM
)
7967 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR
)
7968 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM
)
7969 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR
)
7970 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM
)
7971 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR
)
7973 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU
)
7975 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", CS_AUTO
, "Options/Encoding/"CS_AUTO
, GTK_UI_MANAGER_MENUITEM
)
7976 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR
)
7977 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", CS_US_ASCII
, "Options/Encoding/"CS_US_ASCII
, GTK_UI_MANAGER_MENUITEM
)
7978 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", CS_UTF_8
, "Options/Encoding/"CS_UTF_8
, GTK_UI_MANAGER_MENUITEM
)
7979 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR
)
7981 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU
)
7982 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Western", CS_ISO_8859_1
, "Options/Encoding/Western/"CS_ISO_8859_1
, GTK_UI_MANAGER_MENUITEM
)
7983 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Western", CS_ISO_8859_15
, "Options/Encoding/Western/"CS_ISO_8859_15
, GTK_UI_MANAGER_MENUITEM
)
7984 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252
, "Options/Encoding/Western/"CS_WINDOWS_1252
, GTK_UI_MANAGER_MENUITEM
)
7986 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", CS_ISO_8859_2
, "Options/Encoding/"CS_ISO_8859_2
, GTK_UI_MANAGER_MENUITEM
)
7988 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU
)
7989 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Baltic", CS_ISO_8859_13
, "Options/Encoding/Baltic/"CS_ISO_8859_13
, GTK_UI_MANAGER_MENUITEM
)
7990 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Baltic", CS_ISO_8859_4
, "Options/Encoding/Baltic/"CS_ISO_8859_4
, GTK_UI_MANAGER_MENUITEM
)
7992 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", CS_ISO_8859_7
, "Options/Encoding/"CS_ISO_8859_7
, GTK_UI_MANAGER_MENUITEM
)
7994 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU
)
7995 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Hebrew", CS_ISO_8859_8
, "Options/Encoding/Hebrew/"CS_ISO_8859_8
, GTK_UI_MANAGER_MENUITEM
)
7996 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255
, "Options/Encoding/Hebrew/"CS_WINDOWS_1255
, GTK_UI_MANAGER_MENUITEM
)
7998 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU
)
7999 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Arabic", CS_ISO_8859_6
, "Options/Encoding/Arabic/"CS_ISO_8859_6
, GTK_UI_MANAGER_MENUITEM
)
8000 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256
, "Options/Encoding/Arabic/"CS_WINDOWS_1256
, GTK_UI_MANAGER_MENUITEM
)
8002 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", CS_ISO_8859_9
, "Options/Encoding/"CS_ISO_8859_9
, GTK_UI_MANAGER_MENUITEM
)
8004 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU
)
8005 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Cyrillic", CS_ISO_8859_5
, "Options/Encoding/Cyrillic/"CS_ISO_8859_5
, GTK_UI_MANAGER_MENUITEM
)
8006 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R
, "Options/Encoding/Cyrillic/"CS_KOI8_R
, GTK_UI_MANAGER_MENUITEM
)
8007 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR
, "Options/Encoding/Cyrillic/"CS_MACCYR
, GTK_UI_MANAGER_MENUITEM
)
8008 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U
, "Options/Encoding/Cyrillic/"CS_KOI8_U
, GTK_UI_MANAGER_MENUITEM
)
8009 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251
, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251
, GTK_UI_MANAGER_MENUITEM
)
8011 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU
)
8012 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Japanese", CS_ISO_2022_JP
, "Options/Encoding/Japanese/"CS_ISO_2022_JP
, GTK_UI_MANAGER_MENUITEM
)
8013 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Japanese", CS_ISO_2022_JP_2
, "Options/Encoding/Japanese/"CS_ISO_2022_JP_2
, GTK_UI_MANAGER_MENUITEM
)
8014 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Japanese", CS_EUC_JP
, "Options/Encoding/Japanese/"CS_EUC_JP
, GTK_UI_MANAGER_MENUITEM
)
8015 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS
, "Options/Encoding/Japanese/"CS_SHIFT_JIS
, GTK_UI_MANAGER_MENUITEM
)
8017 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU
)
8018 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Chinese", CS_GB18030
, "Options/Encoding/Chinese/"CS_GB18030
, GTK_UI_MANAGER_MENUITEM
)
8019 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Chinese", CS_GB2312
, "Options/Encoding/Chinese/"CS_GB2312
, GTK_UI_MANAGER_MENUITEM
)
8020 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Chinese", CS_GBK
, "Options/Encoding/Chinese/"CS_GBK
, GTK_UI_MANAGER_MENUITEM
)
8021 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Chinese", CS_BIG5
, "Options/Encoding/Chinese/"CS_BIG5
, GTK_UI_MANAGER_MENUITEM
)
8022 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Chinese", CS_EUC_TW
, "Options/Encoding/Chinese/"CS_EUC_TW
, GTK_UI_MANAGER_MENUITEM
)
8024 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU
)
8025 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Korean", CS_EUC_KR
, "Options/Encoding/Korean/"CS_EUC_KR
, GTK_UI_MANAGER_MENUITEM
)
8026 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Korean", CS_ISO_2022_KR
, "Options/Encoding/Korean/"CS_ISO_2022_KR
, GTK_UI_MANAGER_MENUITEM
)
8028 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU
)
8029 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Thai", CS_TIS_620
, "Options/Encoding/Thai/"CS_TIS_620
, GTK_UI_MANAGER_MENUITEM
)
8030 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874
, "Options/Encoding/Thai/"CS_WINDOWS_874
, GTK_UI_MANAGER_MENUITEM
)
8034 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM
)
8035 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM
)
8036 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU
)
8037 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM
)
8038 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU
)
8039 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM
)
8042 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM
)
8044 menubar
= gtk_ui_manager_get_widget(compose
->ui_manager
, "/Menu");
8045 gtk_widget_show_all(menubar
);
8047 gtk_window_add_accel_group(GTK_WINDOW(window
), gtk_ui_manager_get_accel_group(compose
->ui_manager
));
8048 gtk_box_pack_start(GTK_BOX(vbox
), menubar
, FALSE
, TRUE
, 0);
8050 handlebox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
8051 gtk_box_pack_start(GTK_BOX(vbox
), handlebox
, FALSE
, FALSE
, 0);
8053 gtk_widget_realize(handlebox
);
8054 compose
->toolbar
= toolbar_create(TOOLBAR_COMPOSE
, handlebox
,
8057 vbox2
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 2);
8058 gtk_box_pack_start(GTK_BOX(vbox
), vbox2
, TRUE
, TRUE
, 0);
8059 gtk_container_set_border_width(GTK_CONTAINER(vbox2
), 0);
8062 notebook
= gtk_notebook_new();
8063 gtk_widget_show(notebook
);
8065 /* header labels and entries */
8066 gtk_notebook_append_page(GTK_NOTEBOOK(notebook
),
8067 compose_create_header(compose
),
8068 gtk_label_new_with_mnemonic(_("Hea_der")));
8069 /* attachment list */
8070 attach_hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
8071 gtk_widget_show(attach_hbox
);
8073 attach_lab1
= gtk_label_new_with_mnemonic(_("_Attachments"));
8074 gtk_widget_show(attach_lab1
);
8075 gtk_box_pack_start(GTK_BOX(attach_hbox
), attach_lab1
, TRUE
, TRUE
, 0);
8077 attach_lab2
= gtk_label_new("");
8078 gtk_widget_show(attach_lab2
);
8079 gtk_box_pack_start(GTK_BOX(attach_hbox
), attach_lab2
, FALSE
, FALSE
, 0);
8081 gtk_notebook_append_page(GTK_NOTEBOOK(notebook
),
8082 compose_create_attach(compose
),
8085 gtk_notebook_append_page(GTK_NOTEBOOK(notebook
),
8086 compose_create_others(compose
),
8087 gtk_label_new_with_mnemonic(_("Othe_rs")));
8090 subject_hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
8091 gtk_widget_show(subject_hbox
);
8093 subject_frame
= gtk_frame_new(NULL
);
8094 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame
), GTK_SHADOW_NONE
);
8095 gtk_box_pack_start(GTK_BOX(subject_hbox
), subject_frame
, TRUE
, TRUE
, 0);
8096 gtk_widget_show(subject_frame
);
8098 subject
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, HSPACING_NARROW
);
8099 gtk_container_set_border_width(GTK_CONTAINER(subject
), 0);
8100 gtk_widget_show(subject
);
8102 label
= gtk_label_new_with_mnemonic(_("S_ubject:"));
8103 gtk_box_pack_start(GTK_BOX(subject
), label
, FALSE
, FALSE
, 0);
8104 gtk_widget_show(label
);
8107 subject_entry
= claws_spell_entry_new();
8109 subject_entry
= gtk_entry_new();
8111 gtk_box_pack_start(GTK_BOX(subject
), subject_entry
, TRUE
, TRUE
, 0);
8112 g_signal_connect_after(G_OBJECT(subject_entry
), "grab_focus",
8113 G_CALLBACK(compose_grab_focus_cb
), compose
);
8114 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), subject_entry
);
8115 gtk_widget_show(subject_entry
);
8116 compose
->subject_entry
= subject_entry
;
8117 gtk_container_add(GTK_CONTAINER(subject_frame
), subject
);
8119 edit_vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
8121 gtk_box_pack_start(GTK_BOX(edit_vbox
), subject_hbox
, FALSE
, FALSE
, 0);
8124 ruler_hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
8125 gtk_box_pack_start(GTK_BOX(edit_vbox
), ruler_hbox
, FALSE
, FALSE
, 0);
8127 ruler
= gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL
);
8128 gtk_shruler_set_range(GTK_SHRULER(ruler
), 0.0, 100.0, 1.0);
8129 gtk_box_pack_start(GTK_BOX(ruler_hbox
), ruler
, TRUE
, TRUE
,
8133 scrolledwin
= gtk_scrolled_window_new(NULL
, NULL
);
8134 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin
),
8135 GTK_POLICY_AUTOMATIC
,
8136 GTK_POLICY_AUTOMATIC
);
8137 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin
),
8139 gtk_box_pack_start(GTK_BOX(edit_vbox
), scrolledwin
, TRUE
, TRUE
, 0);
8141 text
= gtk_text_view_new();
8142 if (prefs_common
.show_compose_margin
) {
8143 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text
), 6);
8144 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text
), 6);
8146 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
8147 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text
), GTK_WRAP_WORD_CHAR
);
8148 gtk_text_view_set_editable(GTK_TEXT_VIEW(text
), TRUE
);
8149 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
8150 gtk_text_buffer_add_selection_clipboard(buffer
, clipboard
);
8152 gtk_container_add(GTK_CONTAINER(scrolledwin
), text
);
8153 g_signal_connect_after(G_OBJECT(text
), "size_allocate",
8154 G_CALLBACK(compose_edit_size_alloc
),
8156 g_signal_connect(G_OBJECT(buffer
), "changed",
8157 G_CALLBACK(compose_changed_cb
), compose
);
8158 g_signal_connect(G_OBJECT(text
), "grab_focus",
8159 G_CALLBACK(compose_grab_focus_cb
), compose
);
8160 g_signal_connect(G_OBJECT(buffer
), "insert_text",
8161 G_CALLBACK(text_inserted
), compose
);
8162 g_signal_connect(G_OBJECT(text
), "button_press_event",
8163 G_CALLBACK(text_clicked
), compose
);
8164 g_signal_connect(G_OBJECT(text
), "popup-menu",
8165 G_CALLBACK(compose_popup_menu
), compose
);
8166 g_signal_connect(G_OBJECT(subject_entry
), "changed",
8167 G_CALLBACK(compose_changed_cb
), compose
);
8168 g_signal_connect(G_OBJECT(subject_entry
), "activate",
8169 G_CALLBACK(compose_subject_entry_activated
), compose
);
8172 gtk_drag_dest_set(text
, GTK_DEST_DEFAULT_ALL
, compose_mime_types
,
8173 sizeof(compose_mime_types
)/sizeof(compose_mime_types
[0]),
8174 GDK_ACTION_COPY
| GDK_ACTION_MOVE
);
8175 g_signal_connect(G_OBJECT(text
), "drag_data_received",
8176 G_CALLBACK(compose_insert_drag_received_cb
),
8178 g_signal_connect(G_OBJECT(text
), "drag-drop",
8179 G_CALLBACK(compose_drag_drop
),
8181 g_signal_connect(G_OBJECT(text
), "key-press-event",
8182 G_CALLBACK(completion_set_focus_to_subject
),
8184 gtk_widget_show_all(vbox
);
8186 /* pane between attach clist and text */
8187 paned
= gtk_paned_new(GTK_ORIENTATION_VERTICAL
);
8188 gtk_box_pack_start(GTK_BOX(vbox2
), paned
, TRUE
, TRUE
, 0);
8189 gtk_paned_pack1(GTK_PANED(paned
), notebook
, FALSE
, FALSE
);
8190 gtk_paned_pack2(GTK_PANED(paned
), edit_vbox
, TRUE
, FALSE
);
8191 gtk_paned_set_position(GTK_PANED(paned
), prefs_common
.compose_notebook_height
);
8192 g_signal_connect(G_OBJECT(notebook
), "size_allocate",
8193 G_CALLBACK(compose_notebook_size_alloc
), paned
);
8195 gtk_widget_show_all(paned
);
8198 if (prefs_common
.textfont
) {
8199 PangoFontDescription
*font_desc
;
8201 font_desc
= pango_font_description_from_string
8202 (prefs_common
.textfont
);
8204 gtk_widget_override_font(text
, font_desc
);
8205 pango_font_description_free(font_desc
);
8209 gtk_action_group_add_actions(action_group
, compose_popup_entries
,
8210 G_N_ELEMENTS(compose_popup_entries
), (gpointer
)compose
);
8211 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/", "Popup", NULL
, GTK_UI_MANAGER_MENUBAR
)
8212 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU
)
8213 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM
)
8214 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM
)
8215 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR
)
8216 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM
)
8218 popupmenu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose
->ui_manager
, "/Popup/Compose")));
8220 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit/Undo", FALSE
);
8221 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit/Redo", FALSE
);
8222 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/RemoveReferences", FALSE
);
8224 tmpl_menu
= gtk_ui_manager_get_widget(compose
->ui_manager
, "/Menu/Tools/Template");
8226 undostruct
= undo_init(text
);
8227 undo_set_change_state_func(undostruct
, &compose_undo_state_changed
,
8230 address_completion_start(window
);
8232 compose
->window
= window
;
8233 compose
->vbox
= vbox
;
8234 compose
->menubar
= menubar
;
8235 compose
->handlebox
= handlebox
;
8237 compose
->vbox2
= vbox2
;
8239 compose
->paned
= paned
;
8241 compose
->attach_label
= attach_lab2
;
8243 compose
->notebook
= notebook
;
8244 compose
->edit_vbox
= edit_vbox
;
8245 compose
->ruler_hbox
= ruler_hbox
;
8246 compose
->ruler
= ruler
;
8247 compose
->scrolledwin
= scrolledwin
;
8248 compose
->text
= text
;
8250 compose
->focused_editable
= NULL
;
8252 compose
->popupmenu
= popupmenu
;
8254 compose
->tmpl_menu
= tmpl_menu
;
8256 compose
->mode
= mode
;
8257 compose
->rmode
= mode
;
8259 compose
->targetinfo
= NULL
;
8260 compose
->replyinfo
= NULL
;
8261 compose
->fwdinfo
= NULL
;
8263 compose
->email_hashtable
= g_hash_table_new_full(g_str_hash
,
8264 g_str_equal
, (GDestroyNotify
) g_free
, NULL
);
8266 compose
->replyto
= NULL
;
8268 compose
->bcc
= NULL
;
8269 compose
->followup_to
= NULL
;
8271 compose
->ml_post
= NULL
;
8273 compose
->inreplyto
= NULL
;
8274 compose
->references
= NULL
;
8275 compose
->msgid
= NULL
;
8276 compose
->boundary
= NULL
;
8278 compose
->autowrap
= prefs_common
.autowrap
;
8279 compose
->autoindent
= prefs_common
.auto_indent
;
8280 compose
->use_signing
= FALSE
;
8281 compose
->use_encryption
= FALSE
;
8282 compose
->privacy_system
= NULL
;
8283 compose
->encdata
= NULL
;
8285 compose
->modified
= FALSE
;
8287 compose
->return_receipt
= FALSE
;
8289 compose
->to_list
= NULL
;
8290 compose
->newsgroup_list
= NULL
;
8292 compose
->undostruct
= undostruct
;
8294 compose
->sig_str
= NULL
;
8296 compose
->exteditor_file
= NULL
;
8297 compose
->exteditor_pid
= INVALID_PID
;
8298 compose
->exteditor_tag
= -1;
8299 compose
->exteditor_socket
= NULL
;
8300 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_FORBIDDEN
; /* inhibit auto-drafting while loading */
8302 compose
->folder_update_callback_id
=
8303 hooks_register_hook(FOLDER_UPDATE_HOOKLIST
,
8304 compose_update_folder_hook
,
8305 (gpointer
) compose
);
8308 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Spelling", FALSE
);
8309 if (mode
!= COMPOSE_REDIRECT
) {
8310 if (prefs_common
.enable_aspell
&& prefs_common
.dictionary
&&
8311 strcmp(prefs_common
.dictionary
, "")) {
8312 gtkaspell
= gtkaspell_new(prefs_common
.dictionary
,
8313 prefs_common
.alt_dictionary
,
8314 conv_get_locale_charset_str(),
8315 prefs_common
.color
[COL_MISSPELLED
],
8316 prefs_common
.check_while_typing
,
8317 prefs_common
.recheck_when_changing_dict
,
8318 prefs_common
.use_alternate
,
8319 prefs_common
.use_both_dicts
,
8320 GTK_TEXT_VIEW(text
),
8321 GTK_WINDOW(compose
->window
),
8322 compose_dict_changed
,
8323 compose_spell_menu_changed
,
8326 alertpanel_error(_("Spell checker could not "
8328 gtkaspell_checkers_strerror());
8329 gtkaspell_checkers_reset_error();
8331 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Spelling", TRUE
);
8335 compose
->gtkaspell
= gtkaspell
;
8336 compose_spell_menu_changed(compose
);
8337 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry
), gtkaspell
);
8340 compose_select_account(compose
, account
, TRUE
);
8342 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Edit/AutoWrap", prefs_common
.autowrap
);
8343 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Edit/AutoIndent", prefs_common
.auto_indent
);
8345 if (account
->set_autocc
&& account
->auto_cc
&& mode
!= COMPOSE_REEDIT
)
8346 compose_entry_append(compose
, account
->auto_cc
, COMPOSE_CC
, PREF_ACCOUNT
);
8348 if (account
->set_autobcc
&& account
->auto_bcc
&& mode
!= COMPOSE_REEDIT
)
8349 compose_entry_append(compose
, account
->auto_bcc
, COMPOSE_BCC
, PREF_ACCOUNT
);
8351 if (account
->set_autoreplyto
&& account
->auto_replyto
&& mode
!= COMPOSE_REEDIT
)
8352 compose_entry_append(compose
, account
->auto_replyto
, COMPOSE_REPLYTO
, PREF_ACCOUNT
);
8354 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/ReplyMode", compose
->mode
== COMPOSE_REPLY
);
8355 if (account
->protocol
!= A_NNTP
)
8356 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose
->header_last
->combo
)))),
8357 prefs_common_translated_header_name("To:"));
8359 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose
->header_last
->combo
)))),
8360 prefs_common_translated_header_name("Newsgroups:"));
8362 #ifndef USE_ALT_ADDRBOOK
8363 addressbook_set_target_compose(compose
);
8365 if (mode
!= COMPOSE_REDIRECT
)
8366 compose_set_template_menu(compose
);
8368 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Tools/Template", FALSE
);
8371 compose_list
= g_list_append(compose_list
, compose
);
8373 if (!prefs_common
.show_ruler
)
8374 gtk_widget_hide(ruler_hbox
);
8376 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Tools/ShowRuler", prefs_common
.show_ruler
);
8379 compose
->priority
= PRIORITY_NORMAL
;
8380 compose_update_priority_menu_item(compose
);
8382 compose_set_out_encoding(compose
);
8385 compose_update_actions_menu(compose
);
8387 /* Privacy Systems menu */
8388 compose_update_privacy_systems_menu(compose
);
8389 compose_activate_privacy_system(compose
, account
, TRUE
);
8391 toolbar_set_style(compose
->toolbar
->toolbar
, compose
->handlebox
, prefs_common
.toolbar_style
);
8393 gtk_widget_realize(window
);
8395 gtk_widget_show(window
);
8401 static GtkWidget
*compose_account_option_menu_create(Compose
*compose
)
8406 GtkWidget
*optmenubox
;
8407 GtkWidget
*fromlabel
;
8410 GtkWidget
*from_name
= NULL
;
8412 gint num
= 0, def_menu
= 0;
8414 accounts
= account_get_list();
8415 cm_return_val_if_fail(accounts
!= NULL
, NULL
);
8417 optmenubox
= gtk_event_box_new();
8418 optmenu
= gtkut_sc_combobox_create(optmenubox
, FALSE
);
8419 menu
= GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu
)));
8421 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 4);
8422 from_name
= gtk_entry_new();
8424 g_signal_connect_after(G_OBJECT(from_name
), "grab_focus",
8425 G_CALLBACK(compose_grab_focus_cb
), compose
);
8426 g_signal_connect_after(G_OBJECT(from_name
), "activate",
8427 G_CALLBACK(from_name_activate_cb
), optmenu
);
8429 for (; accounts
!= NULL
; accounts
= accounts
->next
, num
++) {
8430 PrefsAccount
*ac
= (PrefsAccount
*)accounts
->data
;
8431 gchar
*name
, *from
= NULL
;
8433 if (ac
== compose
->account
) def_menu
= num
;
8435 name
= g_markup_printf_escaped("<i>%s</i>",
8438 if (ac
== compose
->account
) {
8439 if (ac
->name
&& *ac
->name
) {
8441 QUOTE_IF_REQUIRED_NORMAL(buf
, ac
->name
, return NULL
);
8442 from
= g_strdup_printf("%s <%s>",
8444 gtk_entry_set_text(GTK_ENTRY(from_name
), from
);
8446 from
= g_strdup_printf("%s",
8448 gtk_entry_set_text(GTK_ENTRY(from_name
), from
);
8450 if (cur_account
!= compose
->account
) {
8453 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_bgcolor
, color
);
8454 gtk_widget_modify_base(
8455 GTK_WIDGET(from_name
),
8456 GTK_STATE_NORMAL
, &color
);
8457 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_color
, color
);
8458 gtk_widget_modify_text(
8459 GTK_WIDGET(from_name
),
8460 GTK_STATE_NORMAL
, &color
);
8463 COMBOBOX_ADD(menu
, name
, ac
->account_id
);
8468 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu
), def_menu
);
8470 g_signal_connect(G_OBJECT(optmenu
), "changed",
8471 G_CALLBACK(account_activated
),
8473 g_signal_connect(G_OBJECT(from_name
), "populate-popup",
8474 G_CALLBACK(compose_entry_popup_extend
),
8477 fromlabel
= gtk_label_new_with_mnemonic(_("_From:"));
8478 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel
), from_name
);
8480 gtk_box_pack_start(GTK_BOX(hbox
), fromlabel
, FALSE
, FALSE
, 4);
8481 gtk_box_pack_start(GTK_BOX(hbox
), optmenubox
, FALSE
, FALSE
, 0);
8482 gtk_box_pack_start(GTK_BOX(hbox
), from_name
, TRUE
, TRUE
, 0);
8484 CLAWS_SET_TIP(optmenubox
,
8485 _("Account to use for this email"));
8486 CLAWS_SET_TIP(from_name
,
8487 _("Sender address to be used"));
8489 compose
->account_combo
= optmenu
;
8490 compose
->from_name
= from_name
;
8495 static void compose_set_priority_cb(GtkAction
*action
, GtkRadioAction
*current
, gpointer data
)
8497 gboolean active
= gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current
));
8498 gint value
= gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current
));
8499 Compose
*compose
= (Compose
*) data
;
8501 compose
->priority
= value
;
8505 static void compose_reply_change_mode(Compose
*compose
,
8508 gboolean was_modified
= compose
->modified
;
8510 gboolean all
= FALSE
, ml
= FALSE
, sender
= FALSE
, followup
= FALSE
;
8512 cm_return_if_fail(compose
->replyinfo
!= NULL
);
8514 if (action
== COMPOSE_REPLY
&& prefs_common
.default_reply_list
)
8516 if (action
== COMPOSE_REPLY
&& compose
->rmode
== COMPOSE_FOLLOWUP_AND_REPLY_TO
)
8518 if (action
== COMPOSE_REPLY_TO_ALL
)
8520 if (action
== COMPOSE_REPLY_TO_SENDER
)
8522 if (action
== COMPOSE_REPLY_TO_LIST
)
8525 compose_remove_header_entries(compose
);
8526 compose_reply_set_entry(compose
, compose
->replyinfo
, all
, ml
, sender
, followup
);
8527 if (compose
->account
->set_autocc
&& compose
->account
->auto_cc
)
8528 compose_entry_append(compose
, compose
->account
->auto_cc
, COMPOSE_CC
, PREF_ACCOUNT
);
8530 if (compose
->account
->set_autobcc
&& compose
->account
->auto_bcc
)
8531 compose_entry_append(compose
, compose
->account
->auto_bcc
, COMPOSE_BCC
, PREF_ACCOUNT
);
8533 if (compose
->account
->set_autoreplyto
&& compose
->account
->auto_replyto
)
8534 compose_entry_append(compose
, compose
->account
->auto_replyto
, COMPOSE_REPLYTO
, PREF_ACCOUNT
);
8535 compose_show_first_last_header(compose
, TRUE
);
8536 compose
->modified
= was_modified
;
8537 compose_set_title(compose
);
8540 static void compose_reply_change_mode_cb(GtkAction
*action
, GtkRadioAction
*current
, gpointer data
)
8542 gboolean active
= gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current
));
8543 gint value
= gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current
));
8544 Compose
*compose
= (Compose
*) data
;
8547 compose_reply_change_mode(compose
, value
);
8550 static void compose_update_priority_menu_item(Compose
* compose
)
8552 GtkWidget
*menuitem
= NULL
;
8553 switch (compose
->priority
) {
8554 case PRIORITY_HIGHEST
:
8555 menuitem
= gtk_ui_manager_get_widget
8556 (compose
->ui_manager
, "/Menu/Options/Priority/Highest");
8559 menuitem
= gtk_ui_manager_get_widget
8560 (compose
->ui_manager
, "/Menu/Options/Priority/High");
8562 case PRIORITY_NORMAL
:
8563 menuitem
= gtk_ui_manager_get_widget
8564 (compose
->ui_manager
, "/Menu/Options/Priority/Normal");
8567 menuitem
= gtk_ui_manager_get_widget
8568 (compose
->ui_manager
, "/Menu/Options/Priority/Low");
8570 case PRIORITY_LOWEST
:
8571 menuitem
= gtk_ui_manager_get_widget
8572 (compose
->ui_manager
, "/Menu/Options/Priority/Lowest");
8575 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem
), TRUE
);
8578 static void compose_set_privacy_system_cb(GtkWidget
*widget
, gpointer data
)
8580 Compose
*compose
= (Compose
*) data
;
8582 gboolean can_sign
= FALSE
, can_encrypt
= FALSE
;
8584 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget
));
8586 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget
)))
8589 systemid
= g_object_get_data(G_OBJECT(widget
), "privacy_system");
8590 g_free(compose
->privacy_system
);
8591 compose
->privacy_system
= NULL
;
8592 g_free(compose
->encdata
);
8593 compose
->encdata
= NULL
;
8594 if (systemid
!= NULL
) {
8595 compose
->privacy_system
= g_strdup(systemid
);
8597 can_sign
= privacy_system_can_sign(systemid
);
8598 can_encrypt
= privacy_system_can_encrypt(systemid
);
8601 debug_print("activated privacy system: %s\n", systemid
!= NULL
? systemid
: "None");
8603 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/Sign", can_sign
);
8604 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/Encrypt", can_encrypt
);
8605 if (compose
->toolbar
->privacy_sign_btn
!= NULL
) {
8606 gtk_widget_set_sensitive(
8607 GTK_WIDGET(compose
->toolbar
->privacy_sign_btn
),
8609 gtk_toggle_tool_button_set_active(
8610 GTK_TOGGLE_TOOL_BUTTON(compose
->toolbar
->privacy_sign_btn
),
8611 can_sign
? compose
->use_signing
: FALSE
);
8613 if (compose
->toolbar
->privacy_encrypt_btn
!= NULL
) {
8614 gtk_widget_set_sensitive(
8615 GTK_WIDGET(compose
->toolbar
->privacy_encrypt_btn
),
8617 gtk_toggle_tool_button_set_active(
8618 GTK_TOGGLE_TOOL_BUTTON(compose
->toolbar
->privacy_encrypt_btn
),
8619 can_encrypt
? compose
->use_encryption
: FALSE
);
8623 static void compose_update_privacy_system_menu_item(Compose
* compose
, gboolean warn
)
8625 static gchar
*branch_path
= "/Menu/Options/PrivacySystem";
8626 GtkWidget
*menuitem
= NULL
;
8627 GList
*children
, *amenu
;
8628 gboolean can_sign
= FALSE
, can_encrypt
= FALSE
;
8629 gboolean found
= FALSE
;
8631 if (compose
->privacy_system
!= NULL
) {
8633 menuitem
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8634 gtk_ui_manager_get_widget(compose
->ui_manager
, branch_path
)));
8635 cm_return_if_fail(menuitem
!= NULL
);
8637 children
= gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem
)));
8640 while (amenu
!= NULL
) {
8641 systemid
= g_object_get_data(G_OBJECT(amenu
->data
), "privacy_system");
8642 if (systemid
!= NULL
) {
8643 if (strcmp(systemid
, compose
->privacy_system
) == 0 &&
8644 GTK_IS_CHECK_MENU_ITEM(amenu
->data
)) {
8645 menuitem
= GTK_WIDGET(amenu
->data
);
8647 can_sign
= privacy_system_can_sign(systemid
);
8648 can_encrypt
= privacy_system_can_encrypt(systemid
);
8652 } else if (strlen(compose
->privacy_system
) == 0 &&
8653 GTK_IS_CHECK_MENU_ITEM(amenu
->data
)) {
8654 menuitem
= GTK_WIDGET(amenu
->data
);
8657 can_encrypt
= FALSE
;
8662 amenu
= amenu
->next
;
8664 g_list_free(children
);
8665 if (menuitem
!= NULL
)
8666 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem
), TRUE
);
8668 if (warn
&& !found
&& strlen(compose
->privacy_system
)) {
8669 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8670 "will not be able to sign or encrypt this message."),
8671 compose
->privacy_system
);
8675 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/Sign", can_sign
);
8676 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/Encrypt", can_encrypt
);
8677 if (compose
->toolbar
->privacy_sign_btn
!= NULL
) {
8678 gtk_widget_set_sensitive(
8679 GTK_WIDGET(compose
->toolbar
->privacy_sign_btn
),
8682 if (compose
->toolbar
->privacy_encrypt_btn
!= NULL
) {
8683 gtk_widget_set_sensitive(
8684 GTK_WIDGET(compose
->toolbar
->privacy_encrypt_btn
),
8689 static void compose_set_out_encoding(Compose
*compose
)
8691 CharSet out_encoding
;
8692 const gchar
*branch
= NULL
;
8693 out_encoding
= conv_get_charset_from_str(prefs_common
.outgoing_charset
);
8695 switch(out_encoding
) {
8696 case C_AUTO
: branch
= "Menu/Options/Encoding/" CS_AUTO
; break;
8697 case C_US_ASCII
: branch
= "Menu/Options/Encoding/" CS_US_ASCII
; break;
8698 case C_UTF_8
: branch
= "Menu/Options/Encoding/" CS_UTF_8
; break;
8699 case C_ISO_8859_2
: branch
= "Menu/Options/Encoding/" CS_ISO_8859_2
; break;
8700 case C_ISO_8859_7
: branch
= "Menu/Options/Encoding/" CS_ISO_8859_7
; break;
8701 case C_ISO_8859_9
: branch
= "Menu/Options/Encoding/" CS_ISO_8859_9
; break;
8702 case C_ISO_8859_1
: branch
= "Menu/Options/Encoding/Western/" CS_ISO_8859_1
; break;
8703 case C_ISO_8859_15
: branch
= "Menu/Options/Encoding/Western/" CS_ISO_8859_15
; break;
8704 case C_WINDOWS_1252
: branch
= "Menu/Options/Encoding/Western/" CS_WINDOWS_1252
; break;
8705 case C_ISO_8859_13
: branch
= "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13
; break;
8706 case C_ISO_8859_4
: branch
= "Menu/Options/Encoding/Baltic" CS_ISO_8859_4
; break;
8707 case C_ISO_8859_8
: branch
= "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8
; break;
8708 case C_WINDOWS_1255
: branch
= "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255
; break;
8709 case C_ISO_8859_6
: branch
= "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6
; break;
8710 case C_WINDOWS_1256
: branch
= "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256
; break;
8711 case C_ISO_8859_5
: branch
= "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5
; break;
8712 case C_KOI8_R
: branch
= "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R
; break;
8713 case C_MACCYR
: branch
= "Menu/Options/Encoding/Cyrillic/" CS_MACCYR
; break;
8714 case C_KOI8_U
: branch
= "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U
; break;
8715 case C_WINDOWS_1251
: branch
= "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251
; break;
8716 case C_ISO_2022_JP
: branch
= "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP
; break;
8717 case C_ISO_2022_JP_2
: branch
= "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2
; break;
8718 case C_EUC_JP
: branch
= "Menu/Options/Encoding/Japanese/" CS_EUC_JP
; break;
8719 case C_SHIFT_JIS
: branch
= "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS
; break;
8720 case C_GB18030
: branch
= "Menu/Options/Encoding/Chinese/" CS_GB18030
; break;
8721 case C_GB2312
: branch
= "Menu/Options/Encoding/Chinese/" CS_GB2312
; break;
8722 case C_GBK
: branch
= "Menu/Options/Encoding/Chinese/" CS_GBK
; break;
8723 case C_BIG5
: branch
= "Menu/Options/Encoding/Chinese/" CS_BIG5
; break;
8724 case C_EUC_TW
: branch
= "Menu/Options/Encoding/Chinese/" CS_EUC_TW
; break;
8725 case C_EUC_KR
: branch
= "Menu/Options/Encoding/Korean/" CS_EUC_KR
; break;
8726 case C_ISO_2022_KR
: branch
= "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR
; break;
8727 case C_TIS_620
: branch
= "Menu/Options/Encoding/Thai/" CS_TIS_620
; break;
8728 case C_WINDOWS_874
: branch
= "Menu/Options/Encoding/Thai/" CS_WINDOWS_874
; break;
8729 default: branch
= "Menu/Options/Encoding/" CS_AUTO
; break;
8731 cm_toggle_menu_set_active_full(compose
->ui_manager
, (gchar
*)branch
, TRUE
);
8734 static void compose_set_template_menu(Compose
*compose
)
8736 GSList
*tmpl_list
, *cur
;
8740 tmpl_list
= template_get_config();
8742 menu
= gtk_menu_new();
8744 gtk_menu_set_accel_group (GTK_MENU (menu
),
8745 gtk_ui_manager_get_accel_group(compose
->ui_manager
));
8746 for (cur
= tmpl_list
; cur
!= NULL
; cur
= cur
->next
) {
8747 Template
*tmpl
= (Template
*)cur
->data
;
8748 gchar
*accel_path
= NULL
;
8749 item
= gtk_menu_item_new_with_label(tmpl
->name
);
8750 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8751 g_signal_connect(G_OBJECT(item
), "activate",
8752 G_CALLBACK(compose_template_activate_cb
),
8754 g_object_set_data(G_OBJECT(item
), "template", tmpl
);
8755 gtk_widget_show(item
);
8756 accel_path
= g_strconcat("<ComposeTemplates>" , "/", tmpl
->name
, NULL
);
8757 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item
), accel_path
);
8761 gtk_widget_show(menu
);
8762 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose
->tmpl_menu
), menu
);
8765 void compose_update_actions_menu(Compose
*compose
)
8767 action_update_compose_menu(compose
->ui_manager
, "/Menu/Tools/Actions", compose
);
8770 static void compose_update_privacy_systems_menu(Compose
*compose
)
8772 static gchar
*branch_path
= "/Menu/Options/PrivacySystem";
8773 GSList
*systems
, *cur
;
8775 GtkWidget
*system_none
;
8777 GtkWidget
*privacy_menuitem
= gtk_ui_manager_get_widget(compose
->ui_manager
, branch_path
);
8778 GtkWidget
*privacy_menu
= gtk_menu_new();
8780 system_none
= gtk_radio_menu_item_new_with_mnemonic(NULL
, _("_None"));
8781 g_object_set_data_full(G_OBJECT(system_none
), "privacy_system", NULL
, NULL
);
8783 g_signal_connect(G_OBJECT(system_none
), "activate",
8784 G_CALLBACK(compose_set_privacy_system_cb
), compose
);
8786 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu
), system_none
);
8787 gtk_widget_show(system_none
);
8789 systems
= privacy_get_system_ids();
8790 for (cur
= systems
; cur
!= NULL
; cur
= g_slist_next(cur
)) {
8791 gchar
*systemid
= cur
->data
;
8793 group
= gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none
));
8794 widget
= gtk_radio_menu_item_new_with_label(group
,
8795 privacy_system_get_name(systemid
));
8796 g_object_set_data_full(G_OBJECT(widget
), "privacy_system",
8797 g_strdup(systemid
), g_free
);
8798 g_signal_connect(G_OBJECT(widget
), "activate",
8799 G_CALLBACK(compose_set_privacy_system_cb
), compose
);
8801 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu
), widget
);
8802 gtk_widget_show(widget
);
8805 g_slist_free(systems
);
8806 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem
), privacy_menu
);
8807 gtk_widget_show_all(privacy_menu
);
8808 gtk_widget_show_all(privacy_menuitem
);
8811 void compose_reflect_prefs_all(void)
8816 for (cur
= compose_list
; cur
!= NULL
; cur
= cur
->next
) {
8817 compose
= (Compose
*)cur
->data
;
8818 compose_set_template_menu(compose
);
8822 void compose_reflect_prefs_pixmap_theme(void)
8827 for (cur
= compose_list
; cur
!= NULL
; cur
= cur
->next
) {
8828 compose
= (Compose
*)cur
->data
;
8829 toolbar_update(TOOLBAR_COMPOSE
, compose
);
8833 static const gchar
*compose_quote_char_from_context(Compose
*compose
)
8835 const gchar
*qmark
= NULL
;
8837 cm_return_val_if_fail(compose
!= NULL
, NULL
);
8839 switch (compose
->mode
) {
8840 /* use forward-specific quote char */
8841 case COMPOSE_FORWARD
:
8842 case COMPOSE_FORWARD_AS_ATTACH
:
8843 case COMPOSE_FORWARD_INLINE
:
8844 if (compose
->folder
&& compose
->folder
->prefs
&&
8845 compose
->folder
->prefs
->forward_with_format
)
8846 qmark
= compose
->folder
->prefs
->forward_quotemark
;
8847 else if (compose
->account
->forward_with_format
)
8848 qmark
= compose
->account
->forward_quotemark
;
8850 qmark
= prefs_common
.fw_quotemark
;
8853 /* use reply-specific quote char in all other modes */
8855 if (compose
->folder
&& compose
->folder
->prefs
&&
8856 compose
->folder
->prefs
->reply_with_format
)
8857 qmark
= compose
->folder
->prefs
->reply_quotemark
;
8858 else if (compose
->account
->reply_with_format
)
8859 qmark
= compose
->account
->reply_quotemark
;
8861 qmark
= prefs_common
.quotemark
;
8865 if (qmark
== NULL
|| *qmark
== '\0')
8871 static void compose_template_apply(Compose
*compose
, Template
*tmpl
,
8875 GtkTextBuffer
*buffer
;
8879 gchar
*parsed_str
= NULL
;
8880 gint cursor_pos
= 0;
8881 const gchar
*err_msg
= _("The body of the template has an error at line %d.");
8884 /* process the body */
8886 text
= GTK_TEXT_VIEW(compose
->text
);
8887 buffer
= gtk_text_view_get_buffer(text
);
8890 qmark
= compose_quote_char_from_context(compose
);
8892 if (compose
->replyinfo
!= NULL
) {
8895 gtk_text_buffer_set_text(buffer
, "", -1);
8896 mark
= gtk_text_buffer_get_insert(buffer
);
8897 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
8899 parsed_str
= compose_quote_fmt(compose
, compose
->replyinfo
,
8900 tmpl
->value
, qmark
, NULL
, FALSE
, FALSE
, err_msg
);
8902 } else if (compose
->fwdinfo
!= NULL
) {
8905 gtk_text_buffer_set_text(buffer
, "", -1);
8906 mark
= gtk_text_buffer_get_insert(buffer
);
8907 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
8909 parsed_str
= compose_quote_fmt(compose
, compose
->fwdinfo
,
8910 tmpl
->value
, qmark
, NULL
, FALSE
, FALSE
, err_msg
);
8913 MsgInfo
* dummyinfo
= compose_msginfo_new_from_compose(compose
);
8915 GtkTextIter start
, end
;
8918 gtk_text_buffer_get_start_iter(buffer
, &start
);
8919 gtk_text_buffer_get_iter_at_offset(buffer
, &end
, -1);
8920 tmp
= gtk_text_buffer_get_text(buffer
, &start
, &end
, FALSE
);
8922 /* clear the buffer now */
8924 gtk_text_buffer_set_text(buffer
, "", -1);
8926 parsed_str
= compose_quote_fmt(compose
, dummyinfo
,
8927 tmpl
->value
, qmark
, tmp
, FALSE
, FALSE
, err_msg
);
8928 procmsg_msginfo_free( &dummyinfo
);
8934 gtk_text_buffer_set_text(buffer
, "", -1);
8935 mark
= gtk_text_buffer_get_insert(buffer
);
8936 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
8939 if (replace
&& parsed_str
&& compose
->account
->auto_sig
)
8940 compose_insert_sig(compose
, FALSE
);
8942 if (replace
&& parsed_str
) {
8943 gtk_text_buffer_get_start_iter(buffer
, &iter
);
8944 gtk_text_buffer_place_cursor(buffer
, &iter
);
8948 cursor_pos
= quote_fmt_get_cursor_pos();
8949 compose
->set_cursor_pos
= cursor_pos
;
8950 if (cursor_pos
== -1)
8952 gtk_text_buffer_get_start_iter(buffer
, &iter
);
8953 gtk_text_buffer_get_iter_at_offset(buffer
, &iter
, cursor_pos
);
8954 gtk_text_buffer_place_cursor(buffer
, &iter
);
8957 /* process the other fields */
8959 compose_attach_from_list(compose
, quote_fmt_get_attachments_list(), FALSE
);
8960 compose_template_apply_fields(compose
, tmpl
);
8961 quote_fmt_reset_vartable();
8962 quote_fmtlex_destroy();
8964 compose_changed_cb(NULL
, compose
);
8967 if (compose
->gtkaspell
&& compose
->gtkaspell
->check_while_typing
)
8968 gtkaspell_highlight_all(compose
->gtkaspell
);
8972 static void compose_template_apply_fields_error(const gchar
*header
)
8977 tr
= g_strdup(C_("'%s' stands for a header name",
8978 "Template '%s' format error."));
8979 text
= g_strdup_printf(tr
, prefs_common_translated_header_name(header
));
8980 alertpanel_error("%s", text
);
8986 static void compose_template_apply_fields(Compose
*compose
, Template
*tmpl
)
8988 MsgInfo
* dummyinfo
= NULL
;
8989 MsgInfo
*msginfo
= NULL
;
8992 if (compose
->replyinfo
!= NULL
)
8993 msginfo
= compose
->replyinfo
;
8994 else if (compose
->fwdinfo
!= NULL
)
8995 msginfo
= compose
->fwdinfo
;
8997 dummyinfo
= compose_msginfo_new_from_compose(compose
);
8998 msginfo
= dummyinfo
;
9001 if (tmpl
->from
&& *tmpl
->from
!= '\0') {
9003 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
9004 compose
->gtkaspell
);
9006 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
9008 quote_fmt_scan_string(tmpl
->from
);
9011 buf
= quote_fmt_get_buffer();
9013 compose_template_apply_fields_error("From");
9015 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), buf
);
9018 quote_fmt_reset_vartable();
9019 quote_fmtlex_destroy();
9022 if (tmpl
->to
&& *tmpl
->to
!= '\0') {
9024 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
9025 compose
->gtkaspell
);
9027 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
9029 quote_fmt_scan_string(tmpl
->to
);
9032 buf
= quote_fmt_get_buffer();
9034 compose_template_apply_fields_error("To");
9036 compose_entry_append(compose
, buf
, COMPOSE_TO
, PREF_TEMPLATE
);
9039 quote_fmt_reset_vartable();
9040 quote_fmtlex_destroy();
9043 if (tmpl
->cc
&& *tmpl
->cc
!= '\0') {
9045 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
9046 compose
->gtkaspell
);
9048 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
9050 quote_fmt_scan_string(tmpl
->cc
);
9053 buf
= quote_fmt_get_buffer();
9055 compose_template_apply_fields_error("Cc");
9057 compose_entry_append(compose
, buf
, COMPOSE_CC
, PREF_TEMPLATE
);
9060 quote_fmt_reset_vartable();
9061 quote_fmtlex_destroy();
9064 if (tmpl
->bcc
&& *tmpl
->bcc
!= '\0') {
9066 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
9067 compose
->gtkaspell
);
9069 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
9071 quote_fmt_scan_string(tmpl
->bcc
);
9074 buf
= quote_fmt_get_buffer();
9076 compose_template_apply_fields_error("Bcc");
9078 compose_entry_append(compose
, buf
, COMPOSE_BCC
, PREF_TEMPLATE
);
9081 quote_fmt_reset_vartable();
9082 quote_fmtlex_destroy();
9085 if (tmpl
->replyto
&& *tmpl
->replyto
!= '\0') {
9087 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
9088 compose
->gtkaspell
);
9090 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
9092 quote_fmt_scan_string(tmpl
->replyto
);
9095 buf
= quote_fmt_get_buffer();
9097 compose_template_apply_fields_error("Reply-To");
9099 compose_entry_append(compose
, buf
, COMPOSE_REPLYTO
, PREF_TEMPLATE
);
9102 quote_fmt_reset_vartable();
9103 quote_fmtlex_destroy();
9106 /* process the subject */
9107 if (tmpl
->subject
&& *tmpl
->subject
!= '\0') {
9109 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
9110 compose
->gtkaspell
);
9112 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
9114 quote_fmt_scan_string(tmpl
->subject
);
9117 buf
= quote_fmt_get_buffer();
9119 compose_template_apply_fields_error("Subject");
9121 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), buf
);
9124 quote_fmt_reset_vartable();
9125 quote_fmtlex_destroy();
9128 procmsg_msginfo_free( &dummyinfo
);
9131 static void compose_destroy(Compose
*compose
)
9133 GtkAllocation allocation
;
9134 GtkTextBuffer
*buffer
;
9135 GtkClipboard
*clipboard
;
9137 compose_list
= g_list_remove(compose_list
, compose
);
9140 gboolean enable
= TRUE
;
9141 g_slist_foreach(compose
->passworded_ldap_servers
,
9142 _ldap_srv_func
, &enable
);
9143 g_slist_free(compose
->passworded_ldap_servers
);
9146 if (compose
->updating
) {
9147 debug_print("danger, not destroying anything now\n");
9148 compose
->deferred_destroy
= TRUE
;
9152 /* NOTE: address_completion_end() does nothing with the window
9153 * however this may change. */
9154 address_completion_end(compose
->window
);
9156 slist_free_strings_full(compose
->to_list
);
9157 slist_free_strings_full(compose
->newsgroup_list
);
9158 slist_free_strings_full(compose
->header_list
);
9160 slist_free_strings_full(extra_headers
);
9161 extra_headers
= NULL
;
9163 compose
->header_list
= compose
->newsgroup_list
= compose
->to_list
= NULL
;
9165 g_hash_table_destroy(compose
->email_hashtable
);
9167 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST
,
9168 compose
->folder_update_callback_id
);
9170 procmsg_msginfo_free(&(compose
->targetinfo
));
9171 procmsg_msginfo_free(&(compose
->replyinfo
));
9172 procmsg_msginfo_free(&(compose
->fwdinfo
));
9174 g_free(compose
->replyto
);
9175 g_free(compose
->cc
);
9176 g_free(compose
->bcc
);
9177 g_free(compose
->newsgroups
);
9178 g_free(compose
->followup_to
);
9180 g_free(compose
->ml_post
);
9182 g_free(compose
->inreplyto
);
9183 g_free(compose
->references
);
9184 g_free(compose
->msgid
);
9185 g_free(compose
->boundary
);
9187 g_free(compose
->redirect_filename
);
9188 if (compose
->undostruct
)
9189 undo_destroy(compose
->undostruct
);
9191 g_free(compose
->sig_str
);
9193 g_free(compose
->exteditor_file
);
9195 g_free(compose
->orig_charset
);
9197 g_free(compose
->privacy_system
);
9198 g_free(compose
->encdata
);
9200 #ifndef USE_ALT_ADDRBOOK
9201 if (addressbook_get_target_compose() == compose
)
9202 addressbook_set_target_compose(NULL
);
9205 if (compose
->gtkaspell
) {
9206 gtkaspell_delete(compose
->gtkaspell
);
9207 compose
->gtkaspell
= NULL
;
9211 if (!compose
->batch
) {
9212 gtk_window_get_size(GTK_WINDOW(compose
->window
),
9213 &allocation
.width
, &allocation
.height
);
9214 prefs_common
.compose_width
= allocation
.width
;
9215 prefs_common
.compose_height
= allocation
.height
;
9218 if (!gtk_widget_get_parent(compose
->paned
))
9219 gtk_widget_destroy(compose
->paned
);
9220 gtk_widget_destroy(compose
->popupmenu
);
9222 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose
->text
));
9223 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
9224 gtk_text_buffer_remove_selection_clipboard(buffer
, clipboard
);
9226 message_search_close(compose
);
9227 gtk_widget_destroy(compose
->window
);
9228 toolbar_destroy(compose
->toolbar
);
9229 g_free(compose
->toolbar
);
9230 g_mutex_clear(&compose
->mutex
);
9234 static void compose_attach_info_free(AttachInfo
*ainfo
)
9236 g_free(ainfo
->file
);
9237 g_free(ainfo
->content_type
);
9238 g_free(ainfo
->name
);
9239 g_free(ainfo
->charset
);
9243 static void compose_attach_update_label(Compose
*compose
)
9248 GtkTreeModel
*model
;
9252 if (compose
== NULL
)
9255 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(compose
->attach_clist
));
9256 if (!gtk_tree_model_get_iter_first(model
, &iter
)) {
9257 gtk_label_set_text(GTK_LABEL(compose
->attach_label
), "");
9261 gtk_tree_model_get(model
, &iter
, COL_DATA
, &ainfo
, -1);
9262 total_size
= ainfo
->size
;
9263 while(gtk_tree_model_iter_next(model
, &iter
)) {
9264 gtk_tree_model_get(model
, &iter
, COL_DATA
, &ainfo
, -1);
9265 total_size
+= ainfo
->size
;
9268 text
= g_strdup_printf(" (%d/%s)", i
, to_human_readable(total_size
));
9269 gtk_label_set_text(GTK_LABEL(compose
->attach_label
), text
);
9273 static void compose_attach_remove_selected(GtkAction
*action
, gpointer data
)
9275 Compose
*compose
= (Compose
*)data
;
9276 GtkTreeView
*tree_view
= GTK_TREE_VIEW(compose
->attach_clist
);
9277 GtkTreeSelection
*selection
;
9279 GtkTreeModel
*model
;
9281 selection
= gtk_tree_view_get_selection(tree_view
);
9282 sel
= gtk_tree_selection_get_selected_rows(selection
, &model
);
9283 cm_return_if_fail(sel
);
9285 for (cur
= sel
; cur
!= NULL
; cur
= cur
->next
) {
9286 GtkTreePath
*path
= cur
->data
;
9287 GtkTreeRowReference
*ref
= gtk_tree_row_reference_new
9290 gtk_tree_path_free(path
);
9293 for (cur
= sel
; cur
!= NULL
; cur
= cur
->next
) {
9294 GtkTreeRowReference
*ref
= cur
->data
;
9295 GtkTreePath
*path
= gtk_tree_row_reference_get_path(ref
);
9298 if (gtk_tree_model_get_iter(model
, &iter
, path
))
9299 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
9301 gtk_tree_path_free(path
);
9302 gtk_tree_row_reference_free(ref
);
9306 compose_attach_update_label(compose
);
9309 static struct _AttachProperty
9312 GtkWidget
*mimetype_entry
;
9313 GtkWidget
*encoding_optmenu
;
9314 GtkWidget
*path_entry
;
9315 GtkWidget
*filename_entry
;
9317 GtkWidget
*cancel_btn
;
9320 static void gtk_tree_path_free_(gpointer ptr
, gpointer data
)
9322 gtk_tree_path_free((GtkTreePath
*)ptr
);
9325 static void compose_attach_property(GtkAction
*action
, gpointer data
)
9327 Compose
*compose
= (Compose
*)data
;
9328 GtkTreeView
*tree_view
= GTK_TREE_VIEW(compose
->attach_clist
);
9330 GtkComboBox
*optmenu
;
9331 GtkTreeSelection
*selection
;
9333 GtkTreeModel
*model
;
9336 static gboolean cancelled
;
9338 /* only if one selected */
9339 selection
= gtk_tree_view_get_selection(tree_view
);
9340 if (gtk_tree_selection_count_selected_rows(selection
) != 1)
9343 sel
= gtk_tree_selection_get_selected_rows(selection
, &model
);
9344 cm_return_if_fail(sel
);
9346 path
= (GtkTreePath
*) sel
->data
;
9347 gtk_tree_model_get_iter(model
, &iter
, path
);
9348 gtk_tree_model_get(model
, &iter
, COL_DATA
, &ainfo
, -1);
9351 g_list_foreach(sel
, gtk_tree_path_free_
, NULL
);
9357 if (!attach_prop
.window
)
9358 compose_attach_property_create(&cancelled
);
9359 gtk_window_set_modal(GTK_WINDOW(attach_prop
.window
), TRUE
);
9360 gtk_widget_grab_focus(attach_prop
.ok_btn
);
9361 gtk_widget_show(attach_prop
.window
);
9362 gtk_window_set_transient_for(GTK_WINDOW(attach_prop
.window
),
9363 GTK_WINDOW(compose
->window
));
9365 optmenu
= GTK_COMBO_BOX(attach_prop
.encoding_optmenu
);
9366 if (ainfo
->encoding
== ENC_UNKNOWN
)
9367 combobox_select_by_data(optmenu
, ENC_BASE64
);
9369 combobox_select_by_data(optmenu
, ainfo
->encoding
);
9371 gtk_entry_set_text(GTK_ENTRY(attach_prop
.mimetype_entry
),
9372 ainfo
->content_type
? ainfo
->content_type
: "");
9373 gtk_entry_set_text(GTK_ENTRY(attach_prop
.path_entry
),
9374 ainfo
->file
? ainfo
->file
: "");
9375 gtk_entry_set_text(GTK_ENTRY(attach_prop
.filename_entry
),
9376 ainfo
->name
? ainfo
->name
: "");
9379 const gchar
*entry_text
;
9381 gchar
*cnttype
= NULL
;
9388 gtk_widget_hide(attach_prop
.window
);
9389 gtk_window_set_modal(GTK_WINDOW(attach_prop
.window
), FALSE
);
9394 entry_text
= gtk_entry_get_text(GTK_ENTRY(attach_prop
.mimetype_entry
));
9395 if (*entry_text
!= '\0') {
9398 text
= g_strstrip(g_strdup(entry_text
));
9399 if ((p
= strchr(text
, '/')) && !strchr(p
+ 1, '/')) {
9400 cnttype
= g_strdup(text
);
9403 alertpanel_error(_("Invalid MIME type."));
9409 ainfo
->encoding
= combobox_get_active_data(optmenu
);
9411 entry_text
= gtk_entry_get_text(GTK_ENTRY(attach_prop
.path_entry
));
9412 if (*entry_text
!= '\0') {
9413 if (is_file_exist(entry_text
) &&
9414 (size
= get_file_size(entry_text
)) > 0)
9415 file
= g_strdup(entry_text
);
9418 (_("File doesn't exist or is empty."));
9424 entry_text
= gtk_entry_get_text(GTK_ENTRY(attach_prop
.filename_entry
));
9425 if (*entry_text
!= '\0') {
9426 g_free(ainfo
->name
);
9427 ainfo
->name
= g_strdup(entry_text
);
9431 g_free(ainfo
->content_type
);
9432 ainfo
->content_type
= cnttype
;
9435 g_free(ainfo
->file
);
9439 ainfo
->size
= (goffset
)size
;
9441 /* update tree store */
9442 text
= to_human_readable(ainfo
->size
);
9443 gtk_tree_model_get_iter(model
, &iter
, path
);
9444 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
9445 COL_MIMETYPE
, ainfo
->content_type
,
9447 COL_NAME
, ainfo
->name
,
9448 COL_CHARSET
, ainfo
->charset
,
9454 gtk_tree_path_free(path
);
9457 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9459 label = gtk_label_new(str); \
9460 gtk_grid_attach(GTK_GRID(table), label, 0, top, 1, 1); \
9461 gtk_label_set_xalign(GTK_LABEL(label), 0.0); \
9462 entry = gtk_entry_new(); \
9463 gtk_grid_attach(GTK_GRID(table), entry, 1, top, 1, 1); \
9466 static void compose_attach_property_create(gboolean
*cancelled
)
9472 GtkWidget
*mimetype_entry
;
9475 GtkListStore
*optmenu_menu
;
9476 GtkWidget
*path_entry
;
9477 GtkWidget
*filename_entry
;
9480 GtkWidget
*cancel_btn
;
9481 GList
*mime_type_list
, *strlist
;
9484 debug_print("Creating attach_property window...\n");
9486 window
= gtkut_window_new(GTK_WINDOW_TOPLEVEL
, "compose_attach_property");
9487 gtk_widget_set_size_request(window
, 480, -1);
9488 gtk_container_set_border_width(GTK_CONTAINER(window
), 8);
9489 gtk_window_set_title(GTK_WINDOW(window
), _("Properties"));
9490 gtk_window_set_position(GTK_WINDOW(window
), GTK_WIN_POS_CENTER
);
9491 gtk_window_set_type_hint(GTK_WINDOW(window
), GDK_WINDOW_TYPE_HINT_DIALOG
);
9492 g_signal_connect(G_OBJECT(window
), "delete_event",
9493 G_CALLBACK(attach_property_delete_event
),
9495 g_signal_connect(G_OBJECT(window
), "key_press_event",
9496 G_CALLBACK(attach_property_key_pressed
),
9499 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 8);
9500 gtk_container_add(GTK_CONTAINER(window
), vbox
);
9502 table
= gtk_grid_new();
9503 gtk_box_pack_start(GTK_BOX(vbox
), table
, FALSE
, FALSE
, 0);
9504 gtk_grid_set_row_spacing(GTK_GRID(table
), 8);
9505 gtk_grid_set_column_spacing(GTK_GRID(table
), 8);
9507 label
= gtk_label_new(_("MIME type"));
9508 gtk_grid_attach(GTK_GRID(table
), label
, 0, 0, 1, 1);
9509 gtk_label_set_xalign(GTK_LABEL(label
), 0.0);
9510 mimetype_entry
= gtk_combo_box_text_new_with_entry();
9511 gtk_grid_attach(GTK_GRID(table
), mimetype_entry
, 1, 0, 1, 1);
9512 gtk_widget_set_hexpand(mimetype_entry
, TRUE
);
9513 gtk_widget_set_halign(mimetype_entry
, GTK_ALIGN_FILL
);
9515 /* stuff with list */
9516 mime_type_list
= procmime_get_mime_type_list();
9518 for (; mime_type_list
!= NULL
; mime_type_list
= mime_type_list
->next
) {
9519 MimeType
*type
= (MimeType
*) mime_type_list
->data
;
9522 tmp
= g_strdup_printf("%s/%s", type
->type
, type
->sub_type
);
9524 if (g_list_find_custom(strlist
, tmp
, (GCompareFunc
)g_strcmp0
))
9527 strlist
= g_list_insert_sorted(strlist
, (gpointer
)tmp
,
9528 (GCompareFunc
)g_strcmp0
);
9531 for (mime_type_list
= strlist
; mime_type_list
!= NULL
;
9532 mime_type_list
= mime_type_list
->next
) {
9533 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry
), mime_type_list
->data
);
9534 g_free(mime_type_list
->data
);
9536 g_list_free(strlist
);
9537 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry
), 0);
9538 mimetype_entry
= gtk_bin_get_child(GTK_BIN((mimetype_entry
)));
9540 label
= gtk_label_new(_("Encoding"));
9541 gtk_grid_attach(GTK_GRID(table
), label
, 0, 1, 1, 1);
9542 gtk_label_set_xalign(GTK_LABEL(label
), 0.0);
9544 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
9545 gtk_grid_attach(GTK_GRID(table
), hbox
, 1, 1, 1, 1);
9546 gtk_widget_set_hexpand(hbox
, TRUE
);
9547 gtk_widget_set_halign(hbox
, GTK_ALIGN_FILL
);
9549 optmenu
= gtkut_sc_combobox_create(NULL
, TRUE
);
9550 optmenu_menu
= GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu
)));
9552 COMBOBOX_ADD(optmenu_menu
, "7bit", ENC_7BIT
);
9553 COMBOBOX_ADD(optmenu_menu
, "8bit", ENC_8BIT
);
9554 COMBOBOX_ADD(optmenu_menu
, "quoted-printable", ENC_QUOTED_PRINTABLE
);
9555 COMBOBOX_ADD(optmenu_menu
, "base64", ENC_BASE64
);
9556 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu
), 0);
9558 gtk_box_pack_start(GTK_BOX(hbox
), optmenu
, TRUE
, TRUE
, 0);
9560 SET_LABEL_AND_ENTRY(_("Path"), path_entry
, 2);
9561 SET_LABEL_AND_ENTRY(_("File name"), filename_entry
, 3);
9563 gtkut_stock_button_set_create(&hbbox
, &cancel_btn
, NULL
, _("_Cancel"),
9564 &ok_btn
, NULL
, _("_OK"),
9566 gtk_box_pack_end(GTK_BOX(vbox
), hbbox
, FALSE
, FALSE
, 0);
9567 gtk_widget_grab_default(ok_btn
);
9569 g_signal_connect(G_OBJECT(ok_btn
), "clicked",
9570 G_CALLBACK(attach_property_ok
),
9572 g_signal_connect(G_OBJECT(cancel_btn
), "clicked",
9573 G_CALLBACK(attach_property_cancel
),
9576 gtk_widget_show_all(vbox
);
9578 attach_prop
.window
= window
;
9579 attach_prop
.mimetype_entry
= mimetype_entry
;
9580 attach_prop
.encoding_optmenu
= optmenu
;
9581 attach_prop
.path_entry
= path_entry
;
9582 attach_prop
.filename_entry
= filename_entry
;
9583 attach_prop
.ok_btn
= ok_btn
;
9584 attach_prop
.cancel_btn
= cancel_btn
;
9587 #undef SET_LABEL_AND_ENTRY
9589 static void attach_property_ok(GtkWidget
*widget
, gboolean
*cancelled
)
9595 static void attach_property_cancel(GtkWidget
*widget
, gboolean
*cancelled
)
9601 static gint
attach_property_delete_event(GtkWidget
*widget
, GdkEventAny
*event
,
9602 gboolean
*cancelled
)
9610 static gboolean
attach_property_key_pressed(GtkWidget
*widget
,
9612 gboolean
*cancelled
)
9614 if (event
&& event
->keyval
== GDK_KEY_Escape
) {
9618 if (event
&& (event
->keyval
== GDK_KEY_KP_Enter
||
9619 event
->keyval
== GDK_KEY_Return
)) {
9627 static gboolean
compose_can_autosave(Compose
*compose
)
9629 if (compose
->privacy_system
&& compose
->use_encryption
)
9630 return prefs_common
.autosave
&& prefs_common
.autosave_encrypted
;
9632 return prefs_common
.autosave
;
9636 * compose_exec_ext_editor:
9638 * Open (and optionally embed) external editor
9640 static void compose_exec_ext_editor(Compose
*compose
)
9643 #ifdef GDK_WINDOWING_X11
9645 Window socket_wid
= 0;
9647 #endif /* GDK_WINDOWING_X11 */
9649 GError
*error
= NULL
;
9653 tmp
= g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9654 G_DIR_SEPARATOR
, compose
);
9656 if (compose_write_body_to_file(compose
, tmp
) < 0) {
9657 alertpanel_error(_("Could not write the body to file:\n%s"),
9663 #ifdef GDK_WINDOWING_X11
9664 if (compose_get_ext_editor_uses_socket()) {
9665 if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
9666 /* Only allow one socket */
9667 if (compose
->exteditor_socket
!= NULL
) {
9668 if (gtk_widget_is_focus(compose
->exteditor_socket
)) {
9669 /* Move the focus off of the socket */
9670 gtk_widget_child_focus(compose
->window
, GTK_DIR_TAB_BACKWARD
);
9675 /* Create the receiving GtkSocket */
9676 socket
= gtk_socket_new ();
9677 g_signal_connect (G_OBJECT(socket
), "plug-removed",
9678 G_CALLBACK(compose_ext_editor_plug_removed_cb
),
9680 gtk_box_pack_start(GTK_BOX(compose
->edit_vbox
), socket
, TRUE
, TRUE
, 0);
9681 gtk_widget_set_size_request(socket
, prefs_common
.compose_width
, -1);
9682 /* Realize the socket so that we can use its ID */
9683 gtk_widget_realize(socket
);
9684 socket_wid
= gtk_socket_get_id(GTK_SOCKET (socket
));
9685 compose
->exteditor_socket
= socket
;
9687 debug_print("Socket communication with an external editor is only available on X11.\n");
9690 if (compose_get_ext_editor_uses_socket()) {
9691 alertpanel_error(_("Socket communication with an external editor is only available on X11."));
9695 #endif /* GDK_WINDOWING_X11 */
9697 if (compose_get_ext_editor_cmd_valid()) {
9698 #ifdef GDK_WINDOWING_X11
9699 if (compose_get_ext_editor_uses_socket() && GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
9700 p
= g_strdup(prefs_common_get_ext_editor_cmd());
9701 s
= strstr(p
, "%w");
9703 if (strstr(p
, "%s") < s
)
9704 cmd
= g_strdup_printf(p
, tmp
, socket_wid
);
9706 cmd
= g_strdup_printf(p
, socket_wid
, tmp
);
9709 cmd
= g_strdup_printf(prefs_common_get_ext_editor_cmd(), tmp
);
9712 cmd
= g_strdup_printf(prefs_common_get_ext_editor_cmd(), tmp
);
9713 #endif /* GDK_WINDOWING_X11 */
9715 if (prefs_common_get_ext_editor_cmd())
9716 g_warning("external editor command-line is invalid: '%s'",
9717 prefs_common_get_ext_editor_cmd());
9718 cmd
= g_strdup_printf(DEFAULT_EDITOR_CMD
, tmp
);
9721 argv
= strsplit_with_quote(cmd
, " ", 0);
9723 if (!g_spawn_async(NULL
, argv
, NULL
,
9724 G_SPAWN_DO_NOT_REAP_CHILD
| G_SPAWN_SEARCH_PATH
,
9725 NULL
, NULL
, &pid
, &error
)) {
9726 alertpanel_error(_("Could not spawn the following "
9727 "external editor command:\n%s\n%s"),
9728 cmd
, error
? error
->message
: _("Unknown error"));
9730 g_error_free(error
);
9739 compose
->exteditor_file
= g_strdup(tmp
);
9740 compose
->exteditor_pid
= pid
;
9741 compose
->exteditor_tag
= g_child_watch_add(pid
,
9742 compose_ext_editor_closed_cb
,
9745 compose_set_ext_editor_sensitive(compose
, FALSE
);
9751 * compose_ext_editor_cb:
9753 * External editor has closed (called by g_child_watch)
9755 static void compose_ext_editor_closed_cb(GPid pid
, gint exit_status
, gpointer data
)
9757 Compose
*compose
= (Compose
*)data
;
9758 GError
*error
= NULL
;
9759 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
9760 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(text
);
9761 GtkTextIter start
, end
;
9764 #if GLIB_CHECK_VERSION(2,70,0)
9765 if (!g_spawn_check_wait_status(exit_status
, &error
)) {
9767 if (!g_spawn_check_exit_status(exit_status
, &error
)) {
9770 _("External editor stopped with an error: %s"),
9771 error
? error
->message
: _("Unknown error"));
9773 g_error_free(error
);
9775 g_spawn_close_pid(compose
->exteditor_pid
);
9777 gtk_text_buffer_set_text(buffer
, "", -1);
9778 compose_insert_file(compose
, compose
->exteditor_file
);
9779 compose_changed_cb(NULL
, compose
);
9781 /* Check if we should save the draft or not */
9782 if (compose_can_autosave(compose
))
9783 compose_draft((gpointer
)compose
, COMPOSE_AUTO_SAVE
);
9785 if (claws_unlink(compose
->exteditor_file
) < 0)
9786 FILE_OP_ERROR(compose
->exteditor_file
, "unlink");
9788 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose
->text
));
9789 gtk_text_buffer_get_start_iter(buffer
, &start
);
9790 gtk_text_buffer_get_end_iter(buffer
, &end
);
9791 chars
= gtk_text_buffer_get_text(buffer
, &start
, &end
, FALSE
);
9792 if (chars
&& strlen(chars
) > 0)
9793 compose
->modified
= TRUE
;
9796 compose_set_ext_editor_sensitive(compose
, TRUE
);
9798 g_free(compose
->exteditor_file
);
9799 compose
->exteditor_file
= NULL
;
9800 compose
->exteditor_pid
= INVALID_PID
;
9801 compose
->exteditor_tag
= -1;
9802 #ifdef GDK_WINDOWING_X11
9803 if (compose
->exteditor_socket
&& GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
9804 gtk_widget_destroy(compose
->exteditor_socket
);
9805 compose
->exteditor_socket
= NULL
;
9807 #endif /* GDK_WINDOWING_X11 */
9811 static gboolean
compose_get_ext_editor_cmd_valid()
9813 gboolean has_s
= FALSE
;
9814 gboolean has_w
= FALSE
;
9815 const gchar
*p
= prefs_common_get_ext_editor_cmd();
9818 while ((p
= strchr(p
, '%'))) {
9824 } else if (*p
== 'w') {
9835 static gboolean
compose_ext_editor_kill(Compose
*compose
)
9837 GPid pid
= compose
->exteditor_pid
;
9838 gchar
*pidmsg
= NULL
;
9844 pidmsg
= g_strdup_printf(_("process id: %" G_PID_FORMAT
), pid
);
9846 msg
= g_strdup_printf
9847 (_("The external editor is still working.\n"
9848 "Force terminating the process?\n"
9850 val
= alertpanel_full(_("Notice"), msg
, NULL
, _("_No"), NULL
, _("_Yes"),
9851 NULL
, NULL
, ALERTFOCUS_FIRST
, FALSE
, NULL
,
9855 if (val
== G_ALERTALTERNATE
) {
9856 g_source_remove(compose
->exteditor_tag
);
9859 if (!TerminateProcess(compose
->exteditor_pid
, 0))
9860 perror("TerminateProcess");
9862 if (kill(pid
, SIGTERM
) < 0) perror("kill");
9863 waitpid(compose
->exteditor_pid
, NULL
, 0);
9864 #endif /* G_OS_WIN32 */
9866 g_warning("terminated %s, temporary file: %s",
9867 pidmsg
, compose
->exteditor_file
);
9868 g_spawn_close_pid(compose
->exteditor_pid
);
9870 compose_set_ext_editor_sensitive(compose
, TRUE
);
9872 g_free(compose
->exteditor_file
);
9873 compose
->exteditor_file
= NULL
;
9874 compose
->exteditor_pid
= INVALID_PID
;
9875 compose
->exteditor_tag
= -1;
9887 static char *ext_editor_menu_entries
[] = {
9888 "Menu/Message/Send",
9889 "Menu/Message/SendLater",
9890 "Menu/Message/InsertFile",
9891 "Menu/Message/InsertSig",
9892 "Menu/Message/ReplaceSig",
9893 "Menu/Message/Save",
9894 "Menu/Message/Print",
9899 "Menu/Tools/ShowRuler",
9900 "Menu/Tools/Actions",
9905 static void compose_set_ext_editor_sensitive(Compose
*compose
,
9910 for (i
= 0; ext_editor_menu_entries
[i
]; ++i
) {
9911 cm_menu_set_sensitive_full(compose
->ui_manager
,
9912 ext_editor_menu_entries
[i
], sensitive
);
9915 #ifdef GDK_WINDOWING_X11
9916 if (compose_get_ext_editor_uses_socket() && GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
9918 if (compose
->exteditor_socket
)
9919 gtk_widget_hide(compose
->exteditor_socket
);
9920 gtk_widget_show(compose
->scrolledwin
);
9921 if (prefs_common
.show_ruler
)
9922 gtk_widget_show(compose
->ruler_hbox
);
9923 /* Fix the focus, as it doesn't go anywhere when the
9924 * socket is hidden or destroyed */
9925 gtk_widget_child_focus(compose
->window
, GTK_DIR_TAB_BACKWARD
);
9927 g_assert (compose
->exteditor_socket
!= NULL
);
9928 /* Fix the focus, as it doesn't go anywhere when the
9929 * edit box is hidden */
9930 if (gtk_widget_is_focus(compose
->text
))
9931 gtk_widget_child_focus(compose
->window
, GTK_DIR_TAB_BACKWARD
);
9932 gtk_widget_hide(compose
->scrolledwin
);
9933 gtk_widget_hide(compose
->ruler_hbox
);
9934 gtk_widget_show(compose
->exteditor_socket
);
9937 gtk_widget_set_sensitive(compose
->text
, sensitive
);
9940 gtk_widget_set_sensitive(compose
->text
, sensitive
);
9941 #endif /* GDK_WINDOWING_X11 */
9942 if (compose
->toolbar
->send_btn
)
9943 gtk_widget_set_sensitive(compose
->toolbar
->send_btn
, sensitive
);
9944 if (compose
->toolbar
->sendl_btn
)
9945 gtk_widget_set_sensitive(compose
->toolbar
->sendl_btn
, sensitive
);
9946 if (compose
->toolbar
->draft_btn
)
9947 gtk_widget_set_sensitive(compose
->toolbar
->draft_btn
, sensitive
);
9948 if (compose
->toolbar
->insert_btn
)
9949 gtk_widget_set_sensitive(compose
->toolbar
->insert_btn
, sensitive
);
9950 if (compose
->toolbar
->sig_btn
)
9951 gtk_widget_set_sensitive(compose
->toolbar
->sig_btn
, sensitive
);
9952 if (compose
->toolbar
->exteditor_btn
)
9953 gtk_widget_set_sensitive(compose
->toolbar
->exteditor_btn
, sensitive
);
9954 if (compose
->toolbar
->linewrap_current_btn
)
9955 gtk_widget_set_sensitive(compose
->toolbar
->linewrap_current_btn
, sensitive
);
9956 if (compose
->toolbar
->linewrap_all_btn
)
9957 gtk_widget_set_sensitive(compose
->toolbar
->linewrap_all_btn
, sensitive
);
9960 static gboolean
compose_get_ext_editor_uses_socket()
9962 return (prefs_common_get_ext_editor_cmd() &&
9963 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9966 #ifdef GDK_WINDOWING_X11
9967 static gboolean
compose_ext_editor_plug_removed_cb(GtkSocket
*socket
, Compose
*compose
)
9969 compose
->exteditor_socket
= NULL
;
9970 /* returning FALSE allows destruction of the socket */
9973 #endif /* GDK_WINDOWING_X11 */
9976 * compose_undo_state_changed:
9978 * Change the sensivity of the menuentries undo and redo
9980 static void compose_undo_state_changed(UndoMain
*undostruct
, gint undo_state
,
9981 gint redo_state
, gpointer data
)
9983 Compose
*compose
= (Compose
*)data
;
9985 switch (undo_state
) {
9986 case UNDO_STATE_TRUE
:
9987 if (!undostruct
->undo_state
) {
9988 undostruct
->undo_state
= TRUE
;
9989 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit/Undo", TRUE
);
9992 case UNDO_STATE_FALSE
:
9993 if (undostruct
->undo_state
) {
9994 undostruct
->undo_state
= FALSE
;
9995 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit/Undo", FALSE
);
9998 case UNDO_STATE_UNCHANGED
:
10000 case UNDO_STATE_REFRESH
:
10001 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit/Undo", undostruct
->undo_state
);
10004 g_warning("undo state not recognized");
10008 switch (redo_state
) {
10009 case UNDO_STATE_TRUE
:
10010 if (!undostruct
->redo_state
) {
10011 undostruct
->redo_state
= TRUE
;
10012 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit/Redo", TRUE
);
10015 case UNDO_STATE_FALSE
:
10016 if (undostruct
->redo_state
) {
10017 undostruct
->redo_state
= FALSE
;
10018 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit/Redo", FALSE
);
10021 case UNDO_STATE_UNCHANGED
:
10023 case UNDO_STATE_REFRESH
:
10024 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit/Redo", undostruct
->redo_state
);
10027 g_warning("redo state not recognized");
10032 /* callback functions */
10034 static void compose_notebook_size_alloc(GtkNotebook
*notebook
,
10035 GtkAllocation
*allocation
,
10038 prefs_common
.compose_notebook_height
= gtk_paned_get_position(paned
);
10041 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10042 * includes "non-client" (windows-izm) in calculation, so this calculation
10043 * may not be accurate.
10045 static gboolean
compose_edit_size_alloc(GtkEditable
*widget
,
10046 GtkAllocation
*allocation
,
10047 GtkSHRuler
*shruler
)
10049 if (prefs_common
.show_ruler
) {
10050 gint char_width
= 0, char_height
= 0;
10051 gint line_width_in_chars
;
10053 gtkut_get_font_size(GTK_WIDGET(widget
),
10054 &char_width
, &char_height
);
10055 line_width_in_chars
=
10056 (allocation
->width
- allocation
->x
) / char_width
;
10058 /* got the maximum */
10059 gtk_shruler_set_range(GTK_SHRULER(shruler
),
10060 0.0, line_width_in_chars
, 0);
10069 ComposePrefType type
;
10070 gboolean entry_marked
;
10071 } HeaderEntryState
;
10073 static void account_activated(GtkComboBox
*optmenu
, gpointer data
)
10075 Compose
*compose
= (Compose
*)data
;
10078 gchar
*folderidentifier
;
10079 gint account_id
= 0;
10080 GtkTreeModel
*menu
;
10082 GSList
*list
, *saved_list
= NULL
;
10083 HeaderEntryState
*state
;
10085 /* Get ID of active account in the combo box */
10086 menu
= gtk_combo_box_get_model(optmenu
);
10087 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu
, &iter
));
10088 gtk_tree_model_get(menu
, &iter
, 1, &account_id
, -1);
10090 ac
= account_find_from_id(account_id
);
10091 cm_return_if_fail(ac
!= NULL
);
10093 if (ac
!= compose
->account
) {
10094 compose_select_account(compose
, ac
, FALSE
);
10096 for (list
= compose
->header_list
; list
; list
= list
->next
) {
10097 ComposeHeaderEntry
*hentry
=(ComposeHeaderEntry
*)list
->data
;
10099 if (hentry
->type
== PREF_ACCOUNT
|| !list
->next
) {
10100 compose_destroy_headerentry(compose
, hentry
);
10103 state
= g_malloc0(sizeof(HeaderEntryState
));
10104 state
->header
= gtk_editable_get_chars(GTK_EDITABLE(
10105 gtk_bin_get_child(GTK_BIN(hentry
->combo
))), 0, -1);
10106 state
->entry
= gtk_editable_get_chars(
10107 GTK_EDITABLE(hentry
->entry
), 0, -1);
10108 state
->type
= hentry
->type
;
10110 saved_list
= g_slist_append(saved_list
, state
);
10111 compose_destroy_headerentry(compose
, hentry
);
10114 compose
->header_last
= NULL
;
10115 g_slist_free(compose
->header_list
);
10116 compose
->header_list
= NULL
;
10117 compose
->header_nextrow
= 1;
10118 compose_create_header_entry(compose
);
10120 if (ac
->set_autocc
&& ac
->auto_cc
)
10121 compose_entry_append(compose
, ac
->auto_cc
,
10122 COMPOSE_CC
, PREF_ACCOUNT
);
10123 if (ac
->set_autobcc
&& ac
->auto_bcc
)
10124 compose_entry_append(compose
, ac
->auto_bcc
,
10125 COMPOSE_BCC
, PREF_ACCOUNT
);
10126 if (ac
->set_autoreplyto
&& ac
->auto_replyto
)
10127 compose_entry_append(compose
, ac
->auto_replyto
,
10128 COMPOSE_REPLYTO
, PREF_ACCOUNT
);
10130 for (list
= saved_list
; list
; list
= list
->next
) {
10131 state
= (HeaderEntryState
*) list
->data
;
10133 compose_add_header_entry(compose
, state
->header
,
10134 state
->entry
, state
->type
);
10136 g_free(state
->header
);
10137 g_free(state
->entry
);
10140 g_slist_free(saved_list
);
10142 combobox_select_by_data(GTK_COMBO_BOX(compose
->header_last
->combo
),
10143 (ac
->protocol
== A_NNTP
) ?
10144 COMPOSE_NEWSGROUPS
: COMPOSE_TO
);
10147 /* Set message save folder */
10148 compose_set_save_to(compose
, NULL
);
10149 if (compose
->folder
&& compose
->folder
->prefs
&& compose
->folder
->prefs
->save_copy_to_folder
) {
10150 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
), TRUE
);
10151 gtk_widget_set_sensitive(GTK_WIDGET(compose
->savemsg_combo
), TRUE
);
10152 folderidentifier
= folder_item_get_identifier(compose
->folder
);
10153 compose_set_save_to(compose
, folderidentifier
);
10154 g_free(folderidentifier
);
10155 } else if (account_get_special_folder(compose
->account
, F_OUTBOX
)) {
10156 if (compose
->account
->set_sent_folder
|| prefs_common
.savemsg
)
10157 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
), TRUE
);
10159 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
), FALSE
);
10160 gtk_widget_set_sensitive(GTK_WIDGET(compose
->savemsg_combo
), TRUE
);
10161 folderidentifier
= folder_item_get_identifier(account_get_special_folder
10162 (compose
->account
, F_OUTBOX
));
10163 compose_set_save_to(compose
, folderidentifier
);
10164 g_free(folderidentifier
);
10168 static void attach_selected(GtkTreeView
*tree_view
, GtkTreePath
*tree_path
,
10169 GtkTreeViewColumn
*column
, Compose
*compose
)
10171 compose_attach_property(NULL
, compose
);
10174 static gboolean
attach_button_pressed(GtkWidget
*widget
, GdkEventButton
*event
,
10177 Compose
*compose
= (Compose
*)data
;
10178 GtkTreeSelection
*attach_selection
;
10179 gint attach_nr_selected
;
10182 if (!event
|| compose
->redirect_filename
!= NULL
)
10185 if (event
->button
== 3) {
10186 attach_selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(widget
));
10187 attach_nr_selected
= gtk_tree_selection_count_selected_rows(attach_selection
);
10189 /* If no rows, or just one row is selected, right-click should
10190 * open menu relevant to the row being right-clicked on. We
10191 * achieve that by selecting the clicked row first. If more
10192 * than one row is selected, we shouldn't modify the selection,
10193 * as user may want to remove selected rows (attachments). */
10194 if (attach_nr_selected
< 2) {
10195 gtk_tree_selection_unselect_all(attach_selection
);
10196 attach_nr_selected
= 0;
10197 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget
),
10198 event
->x
, event
->y
, &path
, NULL
, NULL
, NULL
);
10199 if (path
!= NULL
) {
10200 gtk_tree_selection_select_path(attach_selection
, path
);
10201 gtk_tree_path_free(path
);
10202 attach_nr_selected
++;
10206 cm_menu_set_sensitive_full(compose
->ui_manager
, "Popup/Compose/Remove", (attach_nr_selected
> 0));
10207 /* Properties menu item makes no sense with more than one row
10208 * selected, the properties dialog can only edit one attachment. */
10209 cm_menu_set_sensitive_full(compose
->ui_manager
, "Popup/Compose/Properties", (attach_nr_selected
== 1));
10211 gtk_menu_popup_at_pointer(GTK_MENU(compose
->popupmenu
), NULL
);
10219 static gboolean
attach_key_pressed(GtkWidget
*widget
, GdkEventKey
*event
,
10222 Compose
*compose
= (Compose
*)data
;
10224 if (!event
) return FALSE
;
10226 switch (event
->keyval
) {
10227 case GDK_KEY_Delete
:
10228 compose_attach_remove_selected(NULL
, compose
);
10234 static void compose_allow_user_actions (Compose
*compose
, gboolean allow
)
10236 toolbar_comp_set_sensitive(compose
, allow
);
10237 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Message", allow
);
10238 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit", allow
);
10240 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Spelling", allow
);
10242 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options", allow
);
10243 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Tools", allow
);
10244 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Help", allow
);
10246 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose
->text
), allow
);
10250 static void compose_send_cb(GtkAction
*action
, gpointer data
)
10252 Compose
*compose
= (Compose
*)data
;
10255 if (compose
->exteditor_tag
!= -1) {
10256 debug_print("ignoring send: external editor still open\n");
10260 if (prefs_common
.work_offline
&&
10261 !inc_offline_should_override(TRUE
,
10262 _("Claws Mail needs network access in order "
10263 "to send this email.")))
10266 if (compose
->draft_timeout_tag
>= 0) { /* CLAWS: disable draft timeout */
10267 g_source_remove(compose
->draft_timeout_tag
);
10268 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_UNSET
;
10271 compose_send(compose
);
10274 static void compose_send_later_cb(GtkAction
*action
, gpointer data
)
10276 Compose
*compose
= (Compose
*)data
;
10277 ComposeQueueResult val
;
10280 compose_allow_user_actions(compose
, FALSE
);
10281 val
= compose_queue_sub(compose
, NULL
, NULL
, NULL
, TRUE
, TRUE
);
10282 compose_allow_user_actions(compose
, TRUE
);
10285 if (val
== COMPOSE_QUEUE_SUCCESS
) {
10286 compose_close(compose
);
10288 _display_queue_error(val
);
10291 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10294 #define DRAFTED_AT_EXIT "drafted_at_exit"
10295 static void compose_register_draft(MsgInfo
*info
)
10297 gchar
*filepath
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
10298 DRAFTED_AT_EXIT
, NULL
);
10299 FILE *fp
= claws_fopen(filepath
, "ab");
10302 gchar
*name
= folder_item_get_identifier(info
->folder
);
10303 fprintf(fp
, "%s\t%d\n", name
, info
->msgnum
);
10311 gboolean
compose_draft (gpointer data
, guint action
)
10313 Compose
*compose
= (Compose
*)data
;
10315 FolderItemPrefs
*prefs
;
10319 MsgFlags flag
= {0, 0};
10320 static gboolean lock
= FALSE
;
10321 MsgInfo
*newmsginfo
;
10323 gboolean target_locked
= FALSE
;
10324 gboolean err
= FALSE
;
10327 if (lock
) return FALSE
;
10329 if (compose
->sending
)
10332 draft
= account_get_special_folder(compose
->account
, F_DRAFT
);
10333 cm_return_val_if_fail(draft
!= NULL
, FALSE
);
10335 if (!g_mutex_trylock(&compose
->mutex
)) {
10336 /* we don't want to lock the mutex once it's available,
10337 * because as the only other part of compose.c locking
10338 * it is compose_close - which means once unlocked,
10339 * the compose struct will be freed */
10340 debug_print("couldn't lock mutex, probably sending\n");
10346 tmp
= g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10347 G_DIR_SEPARATOR
, compose
);
10348 if ((fp
= claws_fopen(tmp
, "wb")) == NULL
) {
10349 FILE_OP_ERROR(tmp
, "claws_fopen");
10353 /* chmod for security unless folder chmod is set */
10354 prefs
= draft
->prefs
;
10355 if (prefs
&& prefs
->enable_folder_chmod
&& prefs
->folder_chmod
) {
10356 filemode
= prefs
->folder_chmod
;
10357 if (filemode
& S_IRGRP
) filemode
|= S_IWGRP
;
10358 if (filemode
& S_IROTH
) filemode
|= S_IWOTH
;
10359 if (chmod(tmp
, filemode
) < 0)
10360 FILE_OP_ERROR(tmp
, "chmod");
10361 } else if (change_file_mode_rw(fp
, tmp
) < 0) {
10362 FILE_OP_ERROR(tmp
, "chmod");
10363 g_warning("can't change file mode");
10366 /* Save draft infos */
10367 err
|= (fprintf(fp
, "X-Claws-Account-Id:%d\n", compose
->account
->account_id
) < 0);
10368 err
|= (fprintf(fp
, "S:%s\n", compose
->account
->address
) < 0);
10370 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
))) {
10371 gchar
*savefolderid
;
10373 savefolderid
= compose_get_save_to(compose
);
10374 err
|= (fprintf(fp
, "SCF:%s\n", savefolderid
) < 0);
10375 g_free(savefolderid
);
10377 if (compose
->return_receipt
) {
10378 err
|= (fprintf(fp
, "RRCPT:1\n") < 0);
10380 if (compose
->privacy_system
) {
10381 err
|= (fprintf(fp
, "X-Claws-Sign:%d\n", compose
->use_signing
) < 0);
10382 err
|= (fprintf(fp
, "X-Claws-Encrypt:%d\n", compose
->use_encryption
) < 0);
10383 err
|= (fprintf(fp
, "X-Claws-Privacy-System:%s\n", compose
->privacy_system
) < 0);
10386 /* Message-ID of message replying to */
10387 if ((compose
->replyinfo
!= NULL
) && (compose
->replyinfo
->msgid
!= NULL
)) {
10388 gchar
*folderid
= NULL
;
10390 if (compose
->replyinfo
->folder
)
10391 folderid
= folder_item_get_identifier(compose
->replyinfo
->folder
);
10392 if (folderid
== NULL
)
10393 folderid
= g_strdup("NULL");
10395 err
|= (fprintf(fp
, "RMID:%s\t%d\t%s\n", folderid
, compose
->replyinfo
->msgnum
, compose
->replyinfo
->msgid
) < 0);
10398 /* Message-ID of message forwarding to */
10399 if ((compose
->fwdinfo
!= NULL
) && (compose
->fwdinfo
->msgid
!= NULL
)) {
10400 gchar
*folderid
= NULL
;
10402 if (compose
->fwdinfo
->folder
)
10403 folderid
= folder_item_get_identifier(compose
->fwdinfo
->folder
);
10404 if (folderid
== NULL
)
10405 folderid
= g_strdup("NULL");
10407 err
|= (fprintf(fp
, "FMID:%s\t%d\t%s\n", folderid
, compose
->fwdinfo
->msgnum
, compose
->fwdinfo
->msgid
) < 0);
10411 err
|= (fprintf(fp
, "X-Claws-Auto-Wrapping:%d\n", compose
->autowrap
) < 0);
10412 err
|= (fprintf(fp
, "X-Claws-Auto-Indent:%d\n", compose
->autoindent
) < 0);
10414 sheaders
= compose_get_manual_headers_info(compose
);
10415 err
|= (fprintf(fp
, "X-Claws-Manual-Headers:%s\n", sheaders
) < 0);
10418 /* end of headers */
10419 err
|= (fprintf(fp
, "X-Claws-End-Special-Headers: 1\n") < 0);
10426 if (compose_write_to_file(compose
, fp
, COMPOSE_WRITE_FOR_STORE
, action
!= COMPOSE_AUTO_SAVE
) < 0) {
10430 if (claws_safe_fclose(fp
) == EOF
) {
10434 flag
.perm_flags
= MSG_NEW
|MSG_UNREAD
;
10435 if (compose
->targetinfo
) {
10436 target_locked
= MSG_IS_LOCKED(compose
->targetinfo
->flags
);
10438 flag
.perm_flags
|= MSG_LOCKED
;
10440 flag
.tmp_flags
= MSG_DRAFT
;
10442 folder_item_scan(draft
);
10443 if ((msgnum
= folder_item_add_msg(draft
, tmp
, &flag
, TRUE
)) < 0) {
10444 MsgInfo
*tmpinfo
= NULL
;
10445 debug_print("didn't get msgnum after adding draft [%s]\n", compose
->msgid
?compose
->msgid
:"no msgid");
10446 if (compose
->msgid
) {
10447 tmpinfo
= folder_item_get_msginfo_by_msgid(draft
, compose
->msgid
);
10450 msgnum
= tmpinfo
->msgnum
;
10451 procmsg_msginfo_free(&tmpinfo
);
10452 debug_print("got draft msgnum %d from scanning\n", msgnum
);
10454 debug_print("didn't get draft msgnum after scanning\n");
10457 debug_print("got draft msgnum %d from adding\n", msgnum
);
10463 if (action
!= COMPOSE_AUTO_SAVE
) {
10464 if (action
!= COMPOSE_DRAFT_FOR_EXIT
)
10465 alertpanel_error(_("Could not save draft."));
10468 gtkut_window_popup(compose
->window
);
10469 val
= alertpanel_full(_("Could not save draft"),
10470 _("Could not save draft.\n"
10471 "Do you want to cancel exit or discard this email?"),
10472 NULL
, _("_Cancel exit"), NULL
, _("_Discard email"),
10473 NULL
, NULL
, ALERTFOCUS_FIRST
, FALSE
, NULL
, ALERT_QUESTION
);
10474 if (val
== G_ALERTALTERNATE
) {
10476 g_mutex_unlock(&compose
->mutex
); /* must be done before closing */
10477 compose_close(compose
);
10481 g_mutex_unlock(&compose
->mutex
); /* must be done before closing */
10490 if (compose
->mode
== COMPOSE_REEDIT
) {
10491 compose_remove_reedit_target(compose
, TRUE
);
10494 newmsginfo
= folder_item_get_msginfo(draft
, msgnum
);
10497 procmsg_msginfo_unset_flags(newmsginfo
, ~0, ~0);
10499 procmsg_msginfo_set_flags(newmsginfo
, MSG_NEW
|MSG_UNREAD
|MSG_LOCKED
, MSG_DRAFT
);
10501 procmsg_msginfo_set_flags(newmsginfo
, MSG_NEW
|MSG_UNREAD
, MSG_DRAFT
);
10502 if (compose_use_attach(compose
) && action
!= COMPOSE_AUTO_SAVE
)
10503 procmsg_msginfo_set_flags(newmsginfo
, 0,
10504 MSG_HAS_ATTACHMENT
);
10506 if (action
== COMPOSE_DRAFT_FOR_EXIT
) {
10507 compose_register_draft(newmsginfo
);
10509 procmsg_msginfo_free(&newmsginfo
);
10512 folder_item_scan(draft
);
10514 if (action
== COMPOSE_QUIT_EDITING
|| action
== COMPOSE_DRAFT_FOR_EXIT
) {
10516 g_mutex_unlock(&compose
->mutex
); /* must be done before closing */
10517 compose_close(compose
);
10524 GError
*error
= NULL
;
10529 goffset size
, mtime
;
10531 path
= folder_item_fetch_msg(draft
, msgnum
);
10532 if (path
== NULL
) {
10533 debug_print("can't fetch %s:%d\n", draft
->path
, msgnum
);
10537 f
= g_file_new_for_path(path
);
10538 fi
= g_file_query_info(f
, "standard::size,time::modified",
10539 G_FILE_QUERY_INFO_NONE
, NULL
, &error
);
10540 if (error
!= NULL
) {
10541 debug_print("couldn't query file info for '%s': %s\n",
10542 path
, error
->message
);
10543 g_error_free(error
);
10548 size
= g_file_info_get_size(fi
);
10549 g_file_info_get_modification_time(fi
, &tv
);
10551 g_object_unref(fi
);
10554 if (g_stat(path
, &s
) < 0) {
10555 FILE_OP_ERROR(path
, "stat");
10560 mtime
= s
.st_mtime
;
10564 procmsg_msginfo_free(&(compose
->targetinfo
));
10565 compose
->targetinfo
= procmsg_msginfo_new();
10566 compose
->targetinfo
->msgnum
= msgnum
;
10567 compose
->targetinfo
->size
= size
;
10568 compose
->targetinfo
->mtime
= mtime
;
10569 compose
->targetinfo
->folder
= draft
;
10571 procmsg_msginfo_set_flags(compose
->targetinfo
, MSG_LOCKED
, 0);
10572 compose
->mode
= COMPOSE_REEDIT
;
10574 if (action
== COMPOSE_AUTO_SAVE
) {
10575 compose
->modified
= FALSE
;
10576 compose
->autosaved_draft
= compose
->targetinfo
;
10578 compose_set_title(compose
);
10582 g_mutex_unlock(&compose
->mutex
);
10586 void compose_clear_exit_drafts(void)
10588 gchar
*filepath
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
10589 DRAFTED_AT_EXIT
, NULL
);
10590 if (is_file_exist(filepath
))
10591 claws_unlink(filepath
);
10596 void compose_reopen_exit_drafts(void)
10598 gchar
*filepath
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
10599 DRAFTED_AT_EXIT
, NULL
);
10600 FILE *fp
= claws_fopen(filepath
, "rb");
10604 while (claws_fgets(buf
, sizeof(buf
), fp
)) {
10605 gchar
**parts
= g_strsplit(buf
, "\t", 2);
10606 const gchar
*folder
= parts
[0];
10607 int msgnum
= parts
[1] ? atoi(parts
[1]):-1;
10609 if (folder
&& *folder
&& msgnum
> -1) {
10610 FolderItem
*item
= folder_find_item_from_identifier(folder
);
10611 MsgInfo
*info
= folder_item_get_msginfo(item
, msgnum
);
10613 compose_reedit(info
, FALSE
);
10620 compose_clear_exit_drafts();
10623 static void compose_save_cb(GtkAction
*action
, gpointer data
)
10625 Compose
*compose
= (Compose
*)data
;
10626 compose_draft(compose
, COMPOSE_KEEP_EDITING
);
10627 compose
->rmode
= COMPOSE_REEDIT
;
10628 compose
->modified
= FALSE
;
10629 compose_set_title(compose
);
10632 void compose_attach_from_list(Compose
*compose
, GList
*file_list
, gboolean free_data
)
10634 if (compose
&& file_list
) {
10637 for ( tmp
= file_list
; tmp
; tmp
= tmp
->next
) {
10638 gchar
*file
= (gchar
*) tmp
->data
;
10639 gchar
*utf8_filename
= conv_filename_to_utf8(file
);
10640 compose_attach_append(compose
, file
, utf8_filename
, NULL
, NULL
);
10641 compose_changed_cb(NULL
, compose
);
10646 g_free(utf8_filename
);
10651 static void compose_attach_cb(GtkAction
*action
, gpointer data
)
10653 Compose
*compose
= (Compose
*)data
;
10656 if (compose
->redirect_filename
!= NULL
)
10659 /* Set focus_window properly, in case we were called via popup menu,
10660 * which unsets it (via focus_out_event callback on compose window). */
10661 manage_window_focus_in(compose
->window
, NULL
, NULL
);
10663 file_list
= filesel_select_multiple_files_open(_("Select file"), NULL
);
10666 compose_attach_from_list(compose
, file_list
, TRUE
);
10667 g_list_free(file_list
);
10671 static void compose_insert_file_cb(GtkAction
*action
, gpointer data
)
10673 Compose
*compose
= (Compose
*)data
;
10675 gint files_inserted
= 0;
10677 file_list
= filesel_select_multiple_files_open(_("Select file"), NULL
);
10682 for ( tmp
= file_list
; tmp
; tmp
= tmp
->next
) {
10683 gchar
*file
= (gchar
*) tmp
->data
;
10684 gchar
*filedup
= g_strdup(file
);
10685 gchar
*shortfile
= g_path_get_basename(filedup
);
10686 ComposeInsertResult res
;
10687 /* insert the file if the file is short or if the user confirmed that
10688 he/she wants to insert the large file */
10689 res
= compose_insert_file(compose
, file
);
10690 if (res
== COMPOSE_INSERT_READ_ERROR
) {
10691 alertpanel_error(_("File '%s' could not be read."), shortfile
);
10692 } else if (res
== COMPOSE_INSERT_INVALID_CHARACTER
) {
10693 alertpanel_error(_("File '%s' contained invalid characters\n"
10694 "for the current encoding, insertion may be incorrect."),
10696 } else if (res
== COMPOSE_INSERT_SUCCESS
)
10703 g_list_free(file_list
);
10707 if (files_inserted
> 0 && compose
->gtkaspell
&&
10708 compose
->gtkaspell
->check_while_typing
)
10709 gtkaspell_highlight_all(compose
->gtkaspell
);
10713 static void compose_insert_sig_cb(GtkAction
*action
, gpointer data
)
10715 Compose
*compose
= (Compose
*)data
;
10717 compose_insert_sig(compose
, FALSE
);
10720 static void compose_replace_sig_cb(GtkAction
*action
, gpointer data
)
10722 Compose
*compose
= (Compose
*)data
;
10724 compose_insert_sig(compose
, TRUE
);
10727 static gint
compose_delete_cb(GtkWidget
*widget
, GdkEventAny
*event
,
10731 Compose
*compose
= (Compose
*)data
;
10733 gtk_window_get_position(GTK_WINDOW(widget
), &x
, &y
);
10734 if (!compose
->batch
) {
10735 prefs_common
.compose_x
= x
;
10736 prefs_common
.compose_y
= y
;
10738 if (compose
->sending
|| compose
->updating
)
10740 compose_close_cb(NULL
, compose
);
10744 void compose_close_toolbar(Compose
*compose
)
10746 compose_close_cb(NULL
, compose
);
10749 static void compose_close_cb(GtkAction
*action
, gpointer data
)
10751 Compose
*compose
= (Compose
*)data
;
10754 if (compose
->exteditor_tag
!= -1) {
10755 if (!compose_ext_editor_kill(compose
))
10759 if (compose
->modified
) {
10760 gboolean reedit
= (compose
->rmode
== COMPOSE_REEDIT
);
10761 if (!g_mutex_trylock(&compose
->mutex
)) {
10762 /* we don't want to lock the mutex once it's available,
10763 * because as the only other part of compose.c locking
10764 * it is compose_close - which means once unlocked,
10765 * the compose struct will be freed */
10766 debug_print("couldn't lock mutex, probably sending\n");
10769 if (!reedit
|| (compose
->folder
!= NULL
&& compose
->folder
->stype
== F_DRAFT
)) {
10770 val
= alertpanel(_("Discard message"),
10771 _("This message has been modified. Discard it?"),
10772 NULL
, _("_Discard"), NULL
, _("_Save to Drafts"), NULL
, _("_Cancel"),
10775 val
= alertpanel(_("Save changes"),
10776 _("This message has been modified. Save the latest changes?"),
10777 NULL
, _("_Don't save"), NULL
, _("_Save to Drafts"), NULL
, _("_Cancel"),
10778 ALERTFOCUS_SECOND
);
10780 g_mutex_unlock(&compose
->mutex
);
10782 case G_ALERTDEFAULT
:
10783 if (compose_can_autosave(compose
) && !reedit
)
10784 compose_remove_draft(compose
);
10786 case G_ALERTALTERNATE
:
10787 compose_draft(data
, COMPOSE_QUIT_EDITING
);
10794 compose_close(compose
);
10797 static void compose_print_cb(GtkAction
*action
, gpointer data
)
10799 Compose
*compose
= (Compose
*) data
;
10801 compose_draft((gpointer
)compose
, COMPOSE_AUTO_SAVE
);
10802 if (compose
->targetinfo
)
10803 messageview_print(compose
->targetinfo
, FALSE
, -1, -1, 0);
10806 static void compose_set_encoding_cb(GtkAction
*action
, GtkRadioAction
*current
, gpointer data
)
10808 gboolean active
= gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current
));
10809 gint value
= gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current
));
10810 Compose
*compose
= (Compose
*) data
;
10813 compose
->out_encoding
= (CharSet
)value
;
10816 static void compose_address_cb(GtkAction
*action
, gpointer data
)
10818 Compose
*compose
= (Compose
*)data
;
10820 #ifndef USE_ALT_ADDRBOOK
10821 addressbook_open(compose
);
10823 GError
* error
= NULL
;
10824 addressbook_connect_signals(compose
);
10825 addressbook_dbus_open(TRUE
, &error
);
10827 g_warning("%s", error
->message
);
10828 g_error_free(error
);
10833 static void about_show_cb(GtkAction
*action
, gpointer data
)
10838 static void compose_template_activate_cb(GtkWidget
*widget
, gpointer data
)
10840 Compose
*compose
= (Compose
*)data
;
10845 tmpl
= g_object_get_data(G_OBJECT(widget
), "template");
10846 cm_return_if_fail(tmpl
!= NULL
);
10848 msg
= g_strdup_printf(_("Do you want to apply the template '%s'?"),
10850 val
= alertpanel(_("Apply template"), msg
,
10851 NULL
, _("_Replace"), NULL
, _("_Insert"), NULL
, _("_Cancel"),
10855 if (val
== G_ALERTDEFAULT
)
10856 compose_template_apply(compose
, tmpl
, TRUE
);
10857 else if (val
== G_ALERTALTERNATE
)
10858 compose_template_apply(compose
, tmpl
, FALSE
);
10861 static void compose_ext_editor_cb(GtkAction
*action
, gpointer data
)
10863 Compose
*compose
= (Compose
*)data
;
10865 if (compose
->exteditor_tag
!= -1) {
10866 debug_print("ignoring open external editor: external editor still open\n");
10869 compose_exec_ext_editor(compose
);
10872 static void compose_undo_cb(GtkAction
*action
, gpointer data
)
10874 Compose
*compose
= (Compose
*)data
;
10875 gboolean prev_autowrap
= compose
->autowrap
;
10877 compose
->autowrap
= FALSE
;
10878 undo_undo(compose
->undostruct
);
10879 compose
->autowrap
= prev_autowrap
;
10882 static void compose_redo_cb(GtkAction
*action
, gpointer data
)
10884 Compose
*compose
= (Compose
*)data
;
10885 gboolean prev_autowrap
= compose
->autowrap
;
10887 compose
->autowrap
= FALSE
;
10888 undo_redo(compose
->undostruct
);
10889 compose
->autowrap
= prev_autowrap
;
10892 static void entry_cut_clipboard(GtkWidget
*entry
)
10894 if (GTK_IS_EDITABLE(entry
))
10895 gtk_editable_cut_clipboard (GTK_EDITABLE(entry
));
10896 else if (GTK_IS_TEXT_VIEW(entry
))
10897 gtk_text_buffer_cut_clipboard(
10898 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry
)),
10899 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
),
10903 static void entry_copy_clipboard(GtkWidget
*entry
)
10905 if (GTK_IS_EDITABLE(entry
))
10906 gtk_editable_copy_clipboard (GTK_EDITABLE(entry
));
10907 else if (GTK_IS_TEXT_VIEW(entry
))
10908 gtk_text_buffer_copy_clipboard(
10909 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry
)),
10910 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
));
10913 static void paste_text(Compose
*compose
, GtkWidget
*entry
,
10914 gboolean wrap
, GdkAtom clip
, GtkTextIter
*insert_place
,
10915 const gchar
*contents
)
10917 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry
));
10918 GtkTextMark
*mark_start
= gtk_text_buffer_get_insert(buffer
);
10919 GtkTextIter start_iter
, end_iter
;
10922 if (contents
== NULL
)
10925 /* we shouldn't delete the selection when middle-click-pasting, or we
10926 * can't mid-click-paste our own selection */
10927 if (clip
!= GDK_SELECTION_PRIMARY
) {
10928 undo_paste_clipboard(GTK_TEXT_VIEW(compose
->text
), compose
->undostruct
);
10929 gtk_text_buffer_delete_selection(buffer
, FALSE
, TRUE
);
10932 if (insert_place
== NULL
) {
10933 /* if insert_place isn't specified, insert at the cursor.
10934 * used for Ctrl-V pasting */
10935 gtk_text_buffer_get_iter_at_mark(buffer
, &start_iter
, mark_start
);
10936 start
= gtk_text_iter_get_offset(&start_iter
);
10937 gtk_text_buffer_insert(buffer
, &start_iter
, contents
, strlen(contents
));
10939 /* if insert_place is specified, paste here.
10940 * used for mid-click-pasting */
10941 start
= gtk_text_iter_get_offset(insert_place
);
10942 gtk_text_buffer_insert(buffer
, insert_place
, contents
, strlen(contents
));
10943 if (prefs_common
.primary_paste_unselects
)
10944 gtk_text_buffer_select_range(buffer
, insert_place
, insert_place
);
10948 /* paste unwrapped: mark the paste so it's not wrapped later */
10949 end
= start
+ strlen(contents
);
10950 gtk_text_buffer_get_iter_at_offset(buffer
, &start_iter
, start
);
10951 gtk_text_buffer_get_iter_at_offset(buffer
, &end_iter
, end
);
10952 gtk_text_buffer_apply_tag_by_name(buffer
, "no_wrap", &start_iter
, &end_iter
);
10953 } else if (wrap
&& clip
== GDK_SELECTION_PRIMARY
) {
10954 /* rewrap paragraph now (after a mid-click-paste) */
10955 mark_start
= gtk_text_buffer_get_insert(buffer
);
10956 gtk_text_buffer_get_iter_at_mark(buffer
, &start_iter
, mark_start
);
10957 gtk_text_iter_backward_char(&start_iter
);
10958 compose_beautify_paragraph(compose
, &start_iter
, TRUE
);
10960 compose
->modified
= TRUE
;
10963 static void attach_uri_list(Compose
*compose
, GtkSelectionData
*data
)
10967 gchar
*warn_files
= NULL
;
10969 list
= uri_list_extract_filenames(
10970 (const gchar
*)gtk_selection_data_get_data(data
));
10971 for (tmp
= list
; tmp
!= NULL
; tmp
= tmp
->next
) {
10972 gchar
*utf8_filename
= conv_filename_to_utf8((const gchar
*)tmp
->data
);
10973 gchar
*tmp_f
= g_strdup_printf("%s%s\n",
10974 warn_files
?warn_files
:"",
10976 g_free(warn_files
);
10977 warn_files
= tmp_f
;
10979 compose_attach_append
10980 (compose
, (const gchar
*)tmp
->data
,
10981 utf8_filename
, NULL
, NULL
);
10982 g_free(utf8_filename
);
10985 AlertValue val
= 0;
10986 compose_changed_cb(NULL
, compose
);
10987 if (prefs_common
.notify_pasted_attachments
) {
10990 msg
= g_strdup_printf(ngettext("The following file has been attached: \n%s",
10991 "The following files have been attached: \n%s", att
), warn_files
);
10992 val
= alertpanel_full(_("Notice"), msg
,
10993 NULL
, _("_Close"), NULL
, NULL
, NULL
, NULL
,
10994 ALERTFOCUS_FIRST
, TRUE
, NULL
, ALERT_NOTICE
);
10997 g_free(warn_files
);
10998 if (val
& G_ALERTDISABLE
)
10999 prefs_common
.notify_pasted_attachments
= FALSE
;
11001 list_free_strings_full(list
);
11004 int attach_image(Compose
*compose
, GtkSelectionData
*data
, const gchar
*subtype
)
11007 const guchar
*contents
;
11014 cm_return_val_if_fail(data
!= NULL
, -1);
11016 contents
= gtk_selection_data_get_data(data
);
11017 len
= gtk_selection_data_get_length(data
);
11019 tmpf
= get_tmp_file();
11020 file
= g_strconcat(tmpf
, "-image.", subtype
, NULL
);
11023 debug_print("writing image to %s\n", file
);
11025 if ((fp
= claws_fopen(file
, "wb")) == NULL
) {
11026 FILE_OP_ERROR(file
, "claws_fopen");
11031 if (claws_fwrite(contents
, 1, len
, fp
) != len
) {
11032 FILE_OP_ERROR(file
, "claws_fwrite");
11034 if (claws_unlink(file
) < 0)
11035 FILE_OP_ERROR(file
, "unlink");
11040 r
= claws_safe_fclose(fp
);
11043 FILE_OP_ERROR(file
, "claws_fclose");
11044 if (claws_unlink(file
) < 0)
11045 FILE_OP_ERROR(file
, "unlink");
11050 type
= g_strconcat("image/", subtype
, NULL
);
11052 compose_attach_append(compose
, (const gchar
*)file
,
11053 (const gchar
*)file
, type
, NULL
);
11055 alertpanel_notice(_("The pasted image has been attached as: \n%s"), file
);
11063 static void entry_paste_clipboard(Compose
*compose
, GtkWidget
*entry
,
11064 gboolean wrap
, GdkAtom clip
, GtkTextIter
*insert_place
)
11066 if (GTK_IS_TEXT_VIEW(entry
)) {
11067 GdkAtom types
= gdk_atom_intern ("TARGETS", FALSE
);
11068 GdkAtom
*targets
= NULL
;
11069 int n_targets
= 0, i
;
11070 gboolean paste_done
= FALSE
;
11071 GtkClipboard
*clipboard
= gtk_clipboard_get(clip
);
11073 GtkSelectionData
*contents
= gtk_clipboard_wait_for_contents(
11076 if (contents
!= NULL
) {
11077 gtk_selection_data_get_targets(contents
, &targets
, &n_targets
);
11078 gtk_selection_data_free(contents
);
11081 for (i
= 0; i
< n_targets
; i
++) {
11082 GdkAtom atom
= targets
[i
];
11083 gchar
*atom_type
= gdk_atom_name(atom
);
11085 if (atom_type
!= NULL
&& strchr(atom_type
, '/')) {
11086 GtkSelectionData
*data
= gtk_clipboard_wait_for_contents(
11088 debug_print("got contents of type %s\n", atom_type
);
11089 if (!strcmp(atom_type
, "text/plain")) {
11090 /* let the default text handler handle it */
11092 } else if (!strcmp(atom_type
, "text/uri-list")) {
11093 attach_uri_list(compose
, data
);
11097 } else if (!strncmp(atom_type
, "image/", strlen("image/"))) {
11098 gchar
*subtype
= g_strdup((gchar
*)(strstr(atom_type
, "/")+1));
11099 debug_print("image of type %s\n", subtype
);
11100 attach_image(compose
, data
, subtype
);
11109 gchar
*def_text
= gtk_clipboard_wait_for_text(clipboard
);
11110 paste_text(compose
, entry
, wrap
, clip
,
11111 insert_place
, def_text
);
11115 } else if (GTK_IS_EDITABLE(entry
)) {
11116 gtk_editable_paste_clipboard (GTK_EDITABLE(entry
));
11117 compose
->modified
= TRUE
;
11121 static void entry_allsel(GtkWidget
*entry
)
11123 if (GTK_IS_EDITABLE(entry
))
11124 gtk_editable_select_region(GTK_EDITABLE(entry
), 0, -1);
11125 else if (GTK_IS_TEXT_VIEW(entry
)) {
11126 GtkTextIter startiter
, enditer
;
11127 GtkTextBuffer
*textbuf
;
11129 textbuf
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry
));
11130 gtk_text_buffer_get_start_iter(textbuf
, &startiter
);
11131 gtk_text_buffer_get_end_iter(textbuf
, &enditer
);
11133 gtk_text_buffer_move_mark_by_name(textbuf
,
11134 "selection_bound", &startiter
);
11135 gtk_text_buffer_move_mark_by_name(textbuf
,
11136 "insert", &enditer
);
11140 static void compose_cut_cb(GtkAction
*action
, gpointer data
)
11142 Compose
*compose
= (Compose
*)data
;
11143 if (compose
->focused_editable
11144 #ifndef GENERIC_UMPC
11145 && gtk_widget_has_focus(compose
->focused_editable
)
11148 entry_cut_clipboard(compose
->focused_editable
);
11151 static void compose_copy_cb(GtkAction
*action
, gpointer data
)
11153 Compose
*compose
= (Compose
*)data
;
11154 if (compose
->focused_editable
11155 #ifndef GENERIC_UMPC
11156 && gtk_widget_has_focus(compose
->focused_editable
)
11159 entry_copy_clipboard(compose
->focused_editable
);
11162 static void compose_paste_cb(GtkAction
*action
, gpointer data
)
11164 Compose
*compose
= (Compose
*)data
;
11165 gint prev_autowrap
;
11166 GtkTextBuffer
*buffer
;
11168 if (compose
->focused_editable
11169 #ifndef GENERIC_UMPC
11170 && gtk_widget_has_focus(compose
->focused_editable
)
11173 entry_paste_clipboard(compose
, compose
->focused_editable
,
11174 prefs_common
.linewrap_pastes
,
11175 GDK_SELECTION_CLIPBOARD
, NULL
);
11180 #ifndef GENERIC_UMPC
11181 gtk_widget_has_focus(compose
->text
) &&
11183 compose
->gtkaspell
&&
11184 compose
->gtkaspell
->check_while_typing
)
11185 gtkaspell_highlight_all(compose
->gtkaspell
);
11189 static void compose_paste_as_quote_cb(GtkAction
*action
, gpointer data
)
11191 Compose
*compose
= (Compose
*)data
;
11192 gint wrap_quote
= prefs_common
.linewrap_quote
;
11193 if (compose
->focused_editable
11194 #ifndef GENERIC_UMPC
11195 && gtk_widget_has_focus(compose
->focused_editable
)
11198 /* let text_insert() (called directly or at a later time
11199 * after the gtk_editable_paste_clipboard) know that
11200 * text is to be inserted as a quotation. implemented
11201 * by using a simple refcount... */
11202 gint paste_as_quotation
= GPOINTER_TO_INT(g_object_get_data(
11203 G_OBJECT(compose
->focused_editable
),
11204 "paste_as_quotation"));
11205 g_object_set_data(G_OBJECT(compose
->focused_editable
),
11206 "paste_as_quotation",
11207 GINT_TO_POINTER(paste_as_quotation
+ 1));
11208 prefs_common
.linewrap_quote
= prefs_common
.linewrap_pastes
;
11209 entry_paste_clipboard(compose
, compose
->focused_editable
,
11210 prefs_common
.linewrap_pastes
,
11211 GDK_SELECTION_CLIPBOARD
, NULL
);
11212 prefs_common
.linewrap_quote
= wrap_quote
;
11216 static void compose_paste_no_wrap_cb(GtkAction
*action
, gpointer data
)
11218 Compose
*compose
= (Compose
*)data
;
11219 gint prev_autowrap
;
11220 GtkTextBuffer
*buffer
;
11222 if (compose
->focused_editable
11223 #ifndef GENERIC_UMPC
11224 && gtk_widget_has_focus(compose
->focused_editable
)
11227 entry_paste_clipboard(compose
, compose
->focused_editable
, FALSE
,
11228 GDK_SELECTION_CLIPBOARD
, NULL
);
11233 #ifndef GENERIC_UMPC
11234 gtk_widget_has_focus(compose
->text
) &&
11236 compose
->gtkaspell
&&
11237 compose
->gtkaspell
->check_while_typing
)
11238 gtkaspell_highlight_all(compose
->gtkaspell
);
11242 static void compose_paste_wrap_cb(GtkAction
*action
, gpointer data
)
11244 Compose
*compose
= (Compose
*)data
;
11245 gint prev_autowrap
;
11246 GtkTextBuffer
*buffer
;
11248 if (compose
->focused_editable
11249 #ifndef GENERIC_UMPC
11250 && gtk_widget_has_focus(compose
->focused_editable
)
11253 entry_paste_clipboard(compose
, compose
->focused_editable
, TRUE
,
11254 GDK_SELECTION_CLIPBOARD
, NULL
);
11259 #ifndef GENERIC_UMPC
11260 gtk_widget_has_focus(compose
->text
) &&
11262 compose
->gtkaspell
&&
11263 compose
->gtkaspell
->check_while_typing
)
11264 gtkaspell_highlight_all(compose
->gtkaspell
);
11268 static void compose_allsel_cb(GtkAction
*action
, gpointer data
)
11270 Compose
*compose
= (Compose
*)data
;
11271 if (compose
->focused_editable
11272 #ifndef GENERIC_UMPC
11273 && gtk_widget_has_focus(compose
->focused_editable
)
11276 entry_allsel(compose
->focused_editable
);
11279 static void textview_move_beginning_of_line (GtkTextView
*text
)
11281 GtkTextBuffer
*buffer
;
11285 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
11287 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
11288 mark
= gtk_text_buffer_get_insert(buffer
);
11289 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
11290 gtk_text_iter_set_line_offset(&ins
, 0);
11291 gtk_text_buffer_place_cursor(buffer
, &ins
);
11294 static void textview_move_forward_character (GtkTextView
*text
)
11296 GtkTextBuffer
*buffer
;
11300 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
11302 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
11303 mark
= gtk_text_buffer_get_insert(buffer
);
11304 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
11305 if (gtk_text_iter_forward_cursor_position(&ins
))
11306 gtk_text_buffer_place_cursor(buffer
, &ins
);
11309 static void textview_move_backward_character (GtkTextView
*text
)
11311 GtkTextBuffer
*buffer
;
11315 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
11317 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
11318 mark
= gtk_text_buffer_get_insert(buffer
);
11319 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
11320 if (gtk_text_iter_backward_cursor_position(&ins
))
11321 gtk_text_buffer_place_cursor(buffer
, &ins
);
11324 static void textview_move_forward_word (GtkTextView
*text
)
11326 GtkTextBuffer
*buffer
;
11331 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
11333 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
11334 mark
= gtk_text_buffer_get_insert(buffer
);
11335 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
11336 count
= gtk_text_iter_inside_word (&ins
) ? 2 : 1;
11337 if (gtk_text_iter_forward_word_ends(&ins
, count
)) {
11338 gtk_text_iter_backward_word_start(&ins
);
11339 gtk_text_buffer_place_cursor(buffer
, &ins
);
11343 static void textview_move_backward_word (GtkTextView
*text
)
11345 GtkTextBuffer
*buffer
;
11349 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
11351 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
11352 mark
= gtk_text_buffer_get_insert(buffer
);
11353 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
11354 if (gtk_text_iter_backward_word_starts(&ins
, 1))
11355 gtk_text_buffer_place_cursor(buffer
, &ins
);
11358 static void textview_move_end_of_line (GtkTextView
*text
)
11360 GtkTextBuffer
*buffer
;
11364 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
11366 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
11367 mark
= gtk_text_buffer_get_insert(buffer
);
11368 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
11369 if (gtk_text_iter_forward_to_line_end(&ins
))
11370 gtk_text_buffer_place_cursor(buffer
, &ins
);
11373 static void textview_move_next_line (GtkTextView
*text
)
11375 GtkTextBuffer
*buffer
;
11380 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
11382 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
11383 mark
= gtk_text_buffer_get_insert(buffer
);
11384 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
11385 offset
= gtk_text_iter_get_line_offset(&ins
);
11386 if (gtk_text_iter_forward_line(&ins
)) {
11387 gtk_text_iter_set_line_offset(&ins
, offset
);
11388 gtk_text_buffer_place_cursor(buffer
, &ins
);
11392 static void textview_move_previous_line (GtkTextView
*text
)
11394 GtkTextBuffer
*buffer
;
11399 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
11401 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
11402 mark
= gtk_text_buffer_get_insert(buffer
);
11403 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
11404 offset
= gtk_text_iter_get_line_offset(&ins
);
11405 if (gtk_text_iter_backward_line(&ins
)) {
11406 gtk_text_iter_set_line_offset(&ins
, offset
);
11407 gtk_text_buffer_place_cursor(buffer
, &ins
);
11411 static void textview_delete_forward_character (GtkTextView
*text
)
11413 GtkTextBuffer
*buffer
;
11415 GtkTextIter ins
, end_iter
;
11417 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
11419 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
11420 mark
= gtk_text_buffer_get_insert(buffer
);
11421 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
11423 if (gtk_text_iter_forward_char(&end_iter
)) {
11424 gtk_text_buffer_delete(buffer
, &ins
, &end_iter
);
11428 static void textview_delete_backward_character (GtkTextView
*text
)
11430 GtkTextBuffer
*buffer
;
11432 GtkTextIter ins
, end_iter
;
11434 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
11436 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
11437 mark
= gtk_text_buffer_get_insert(buffer
);
11438 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
11440 if (gtk_text_iter_backward_char(&end_iter
)) {
11441 gtk_text_buffer_delete(buffer
, &end_iter
, &ins
);
11445 static void textview_delete_forward_word (GtkTextView
*text
)
11447 GtkTextBuffer
*buffer
;
11449 GtkTextIter ins
, end_iter
;
11451 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
11453 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
11454 mark
= gtk_text_buffer_get_insert(buffer
);
11455 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
11457 if (gtk_text_iter_forward_word_end(&end_iter
)) {
11458 gtk_text_buffer_delete(buffer
, &ins
, &end_iter
);
11462 static void textview_delete_backward_word (GtkTextView
*text
)
11464 GtkTextBuffer
*buffer
;
11466 GtkTextIter ins
, end_iter
;
11468 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
11470 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
11471 mark
= gtk_text_buffer_get_insert(buffer
);
11472 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
11474 if (gtk_text_iter_backward_word_start(&end_iter
)) {
11475 gtk_text_buffer_delete(buffer
, &end_iter
, &ins
);
11479 static void textview_delete_line (GtkTextView
*text
)
11481 GtkTextBuffer
*buffer
;
11483 GtkTextIter ins
, start_iter
, end_iter
;
11485 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
11487 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
11488 mark
= gtk_text_buffer_get_insert(buffer
);
11489 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
11492 gtk_text_iter_set_line_offset(&start_iter
, 0);
11495 if (gtk_text_iter_ends_line(&end_iter
)){
11496 if (!gtk_text_iter_forward_char(&end_iter
))
11497 gtk_text_iter_backward_char(&start_iter
);
11500 gtk_text_iter_forward_to_line_end(&end_iter
);
11501 gtk_text_buffer_delete(buffer
, &start_iter
, &end_iter
);
11504 static void textview_delete_to_line_end (GtkTextView
*text
)
11506 GtkTextBuffer
*buffer
;
11508 GtkTextIter ins
, end_iter
;
11510 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
11512 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
11513 mark
= gtk_text_buffer_get_insert(buffer
);
11514 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
11516 if (gtk_text_iter_ends_line(&end_iter
))
11517 gtk_text_iter_forward_char(&end_iter
);
11519 gtk_text_iter_forward_to_line_end(&end_iter
);
11520 gtk_text_buffer_delete(buffer
, &ins
, &end_iter
);
11523 #define DO_ACTION(name, act) { \
11524 if(!strcmp(name, a_name)) { \
11528 static ComposeCallAdvancedAction
compose_call_advanced_action_from_path(GtkAction
*action
)
11530 const gchar
*a_name
= gtk_action_get_name(action
);
11531 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER
);
11532 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER
);
11533 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD
);
11534 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD
);
11535 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE
);
11536 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE
);
11537 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE
);
11538 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE
);
11539 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER
);
11540 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER
);
11541 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD
);
11542 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD
);
11543 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE
);
11544 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
);
11545 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED
;
11548 static void compose_advanced_action_cb(GtkAction
*gaction
, gpointer data
)
11550 Compose
*compose
= (Compose
*)data
;
11551 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
11552 ComposeCallAdvancedAction action
= COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED
;
11554 action
= compose_call_advanced_action_from_path(gaction
);
11557 void (*do_action
) (GtkTextView
*text
);
11558 } action_table
[] = {
11559 {textview_move_beginning_of_line
},
11560 {textview_move_forward_character
},
11561 {textview_move_backward_character
},
11562 {textview_move_forward_word
},
11563 {textview_move_backward_word
},
11564 {textview_move_end_of_line
},
11565 {textview_move_next_line
},
11566 {textview_move_previous_line
},
11567 {textview_delete_forward_character
},
11568 {textview_delete_backward_character
},
11569 {textview_delete_forward_word
},
11570 {textview_delete_backward_word
},
11571 {textview_delete_line
},
11572 {textview_delete_to_line_end
}
11575 if (!gtk_widget_has_focus(GTK_WIDGET(text
))) return;
11577 if (action
>= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE
&&
11578 action
<= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
) {
11579 if (action_table
[action
].do_action
)
11580 action_table
[action
].do_action(text
);
11582 g_warning("not implemented yet");
11586 static void compose_grab_focus_cb(GtkWidget
*widget
, Compose
*compose
)
11588 GtkAllocation allocation
;
11592 if (GTK_IS_EDITABLE(widget
)) {
11593 str
= gtk_editable_get_chars(GTK_EDITABLE(widget
), 0, -1);
11594 gtk_editable_set_position(GTK_EDITABLE(widget
),
11597 if ((parent
= gtk_widget_get_parent(widget
))
11598 && (parent
= gtk_widget_get_parent(parent
))
11599 && (parent
= gtk_widget_get_parent(parent
))) {
11600 if (GTK_IS_SCROLLED_WINDOW(parent
)) {
11601 gtk_widget_get_allocation(widget
, &allocation
);
11602 gint y
= allocation
.y
;
11603 gint height
= allocation
.height
;
11604 GtkAdjustment
*shown
= gtk_scrolled_window_get_vadjustment
11605 (GTK_SCROLLED_WINDOW(parent
));
11607 gfloat value
= gtk_adjustment_get_value(shown
);
11608 gfloat upper
= gtk_adjustment_get_upper(shown
);
11609 gfloat page_size
= gtk_adjustment_get_page_size(shown
);
11610 if (y
< (int)value
) {
11611 gtk_adjustment_set_value(shown
, y
- 1);
11613 if ((y
+ height
) > ((int)value
+ (int)page_size
)) {
11614 if ((y
- height
- 1) < ((int)upper
- (int)page_size
)) {
11615 gtk_adjustment_set_value(shown
,
11616 y
+ height
- (int)page_size
- 1);
11618 gtk_adjustment_set_value(shown
,
11619 (int)upper
- (int)page_size
- 1);
11626 if (GTK_IS_EDITABLE(widget
) || GTK_IS_TEXT_VIEW(widget
))
11627 compose
->focused_editable
= widget
;
11629 #ifdef GENERIC_UMPC
11630 if (GTK_IS_TEXT_VIEW(widget
)
11631 && gtk_paned_get_child1(GTK_PANED(compose
->paned
)) != compose
->edit_vbox
) {
11632 g_object_ref(compose
->notebook
);
11633 g_object_ref(compose
->edit_vbox
);
11634 gtk_container_remove(GTK_CONTAINER(compose
->paned
), compose
->notebook
);
11635 gtk_container_remove(GTK_CONTAINER(compose
->paned
), compose
->edit_vbox
);
11636 gtk_paned_add1(GTK_PANED(compose
->paned
), compose
->edit_vbox
);
11637 gtk_paned_add2(GTK_PANED(compose
->paned
), compose
->notebook
);
11638 g_object_unref(compose
->notebook
);
11639 g_object_unref(compose
->edit_vbox
);
11640 g_signal_handlers_block_by_func(G_OBJECT(widget
),
11641 G_CALLBACK(compose_grab_focus_cb
),
11643 gtk_widget_grab_focus(widget
);
11644 g_signal_handlers_unblock_by_func(G_OBJECT(widget
),
11645 G_CALLBACK(compose_grab_focus_cb
),
11647 } else if (!GTK_IS_TEXT_VIEW(widget
)
11648 && gtk_paned_get_child1(GTK_PANED(compose
->paned
)) != compose
->notebook
) {
11649 g_object_ref(compose
->notebook
);
11650 g_object_ref(compose
->edit_vbox
);
11651 gtk_container_remove(GTK_CONTAINER(compose
->paned
), compose
->notebook
);
11652 gtk_container_remove(GTK_CONTAINER(compose
->paned
), compose
->edit_vbox
);
11653 gtk_paned_add1(GTK_PANED(compose
->paned
), compose
->notebook
);
11654 gtk_paned_add2(GTK_PANED(compose
->paned
), compose
->edit_vbox
);
11655 g_object_unref(compose
->notebook
);
11656 g_object_unref(compose
->edit_vbox
);
11657 g_signal_handlers_block_by_func(G_OBJECT(widget
),
11658 G_CALLBACK(compose_grab_focus_cb
),
11660 gtk_widget_grab_focus(widget
);
11661 g_signal_handlers_unblock_by_func(G_OBJECT(widget
),
11662 G_CALLBACK(compose_grab_focus_cb
),
11668 static void compose_changed_cb(GtkTextBuffer
*textbuf
, Compose
*compose
)
11670 compose
->modified
= TRUE
;
11671 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11672 #ifndef GENERIC_UMPC
11673 compose_set_title(compose
);
11677 static void compose_wrap_cb(GtkAction
*action
, gpointer data
)
11679 Compose
*compose
= (Compose
*)data
;
11680 compose_beautify_paragraph(compose
, NULL
, TRUE
);
11683 static void compose_wrap_all_cb(GtkAction
*action
, gpointer data
)
11685 Compose
*compose
= (Compose
*)data
;
11686 compose_wrap_all_full(compose
, TRUE
);
11689 static void compose_find_cb(GtkAction
*action
, gpointer data
)
11691 Compose
*compose
= (Compose
*)data
;
11693 message_search_compose(compose
);
11696 static void compose_toggle_autowrap_cb(GtkToggleAction
*action
,
11699 Compose
*compose
= (Compose
*)data
;
11700 compose
->autowrap
= gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action
));
11701 if (compose
->autowrap
)
11702 compose_wrap_all_full(compose
, TRUE
);
11703 compose
->autowrap
= gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action
));
11706 static void compose_toggle_autoindent_cb(GtkToggleAction
*action
,
11709 Compose
*compose
= (Compose
*)data
;
11710 compose
->autoindent
= gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action
));
11713 static void compose_toggle_sign_cb(GtkToggleAction
*action
, gpointer data
)
11715 Compose
*compose
= (Compose
*)data
;
11717 compose
->use_signing
= gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action
));
11718 if (compose
->toolbar
->privacy_sign_btn
!= NULL
)
11719 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose
->toolbar
->privacy_sign_btn
), compose
->use_signing
);
11722 static void compose_toggle_encrypt_cb(GtkToggleAction
*action
, gpointer data
)
11724 Compose
*compose
= (Compose
*)data
;
11726 compose
->use_encryption
= gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action
));
11727 if (compose
->toolbar
->privacy_encrypt_btn
!= NULL
)
11728 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose
->toolbar
->privacy_encrypt_btn
), compose
->use_encryption
);
11731 static void compose_activate_privacy_system(Compose
*compose
, PrefsAccount
*account
, gboolean warn
)
11733 g_free(compose
->privacy_system
);
11734 g_free(compose
->encdata
);
11736 compose
->privacy_system
= g_strdup(account
->default_privacy_system
);
11737 compose_update_privacy_system_menu_item(compose
, warn
);
11740 static void compose_apply_folder_privacy_settings(Compose
*compose
, FolderItem
*folder_item
)
11742 if (folder_item
!= NULL
) {
11743 if (folder_item
->prefs
->always_sign
!= SIGN_OR_ENCRYPT_DEFAULT
&&
11744 privacy_system_can_sign(compose
->privacy_system
)) {
11745 compose_use_signing(compose
,
11746 (folder_item
->prefs
->always_sign
== SIGN_OR_ENCRYPT_ALWAYS
) ? TRUE
: FALSE
);
11748 if (folder_item
->prefs
->always_encrypt
!= SIGN_OR_ENCRYPT_DEFAULT
&&
11749 privacy_system_can_encrypt(compose
->privacy_system
)) {
11750 compose_use_encryption(compose
,
11751 (folder_item
->prefs
->always_encrypt
== SIGN_OR_ENCRYPT_ALWAYS
) ? TRUE
: FALSE
);
11756 static void compose_toggle_ruler_cb(GtkToggleAction
*action
, gpointer data
)
11758 Compose
*compose
= (Compose
*)data
;
11760 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action
))) {
11761 gtk_widget_show(compose
->ruler_hbox
);
11762 prefs_common
.show_ruler
= TRUE
;
11764 gtk_widget_hide(compose
->ruler_hbox
);
11765 gtk_widget_queue_resize(compose
->edit_vbox
);
11766 prefs_common
.show_ruler
= FALSE
;
11770 static void compose_attach_drag_received_cb (GtkWidget
*widget
,
11771 GdkDragContext
*context
,
11774 GtkSelectionData
*data
,
11777 gpointer user_data
)
11779 Compose
*compose
= (Compose
*)user_data
;
11782 type
= gtk_selection_data_get_data_type(data
);
11783 if ((gdk_atom_name(type
) && !strcmp(gdk_atom_name(type
), "text/uri-list"))
11784 && gtk_drag_get_source_widget(context
) !=
11785 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview
)) {
11786 attach_uri_list(compose
, data
);
11787 } else if (gtk_drag_get_source_widget(context
)
11788 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview
)) {
11789 /* comes from our summaryview */
11790 SummaryView
* summaryview
= NULL
;
11791 GSList
* list
= NULL
, *cur
= NULL
;
11793 if (mainwindow_get_mainwindow())
11794 summaryview
= mainwindow_get_mainwindow()->summaryview
;
11797 list
= summary_get_selected_msg_list(summaryview
);
11799 for (cur
= list
; cur
; cur
= cur
->next
) {
11800 MsgInfo
*msginfo
= (MsgInfo
*)cur
->data
;
11801 gchar
*file
= NULL
;
11803 file
= procmsg_get_message_file_full(msginfo
,
11806 compose_attach_append(compose
, (const gchar
*)file
,
11807 (const gchar
*)file
, "message/rfc822", NULL
);
11811 g_slist_free(list
);
11815 static gboolean
compose_drag_drop(GtkWidget
*widget
,
11816 GdkDragContext
*drag_context
,
11818 guint time
, gpointer user_data
)
11820 /* not handling this signal makes compose_insert_drag_received_cb
11825 static gboolean completion_set_focus_to_subject
11826 (GtkWidget
*widget
,
11827 GdkEventKey
*event
,
11830 cm_return_val_if_fail(compose
!= NULL
, FALSE
);
11832 /* make backtab move to subject field */
11833 if(event
->keyval
== GDK_KEY_ISO_Left_Tab
) {
11834 gtk_widget_grab_focus(compose
->subject_entry
);
11840 static void compose_insert_drag_received_cb (GtkWidget
*widget
,
11841 GdkDragContext
*drag_context
,
11844 GtkSelectionData
*data
,
11847 gpointer user_data
)
11849 Compose
*compose
= (Compose
*)user_data
;
11855 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11857 type
= gtk_selection_data_get_data_type(data
);
11858 if (gdk_atom_name(type
) && !strcmp(gdk_atom_name(type
), "text/uri-list")) {
11859 AlertValue val
= G_ALERTDEFAULT
;
11860 const gchar
* ddata
= (const gchar
*)gtk_selection_data_get_data(data
);
11862 list
= uri_list_extract_filenames(ddata
);
11863 num_files
= g_list_length(list
);
11864 if (list
== NULL
&& strstr(ddata
, "://")) {
11865 /* Assume a list of no files, and data has ://, is a remote link */
11866 gchar
*tmpdata
= g_strstrip(g_strdup(ddata
));
11867 gchar
*tmpfile
= get_tmp_file();
11868 str_write_to_file(tmpdata
, tmpfile
, TRUE
);
11870 compose_insert_file(compose
, tmpfile
);
11871 claws_unlink(tmpfile
);
11873 gtk_drag_finish(drag_context
, TRUE
, FALSE
, time
);
11874 compose_beautify_paragraph(compose
, NULL
, TRUE
);
11877 switch (prefs_common
.compose_dnd_mode
) {
11878 case COMPOSE_DND_ASK
:
11879 msg
= g_strdup_printf(
11881 "Do you want to insert the contents of the file "
11882 "into the message body, or attach it to the email?",
11883 "Do you want to insert the contents of the %d files "
11884 "into the message body, or attach them to the email?",
11887 val
= alertpanel_full(_("Insert or attach?"), msg
,
11888 NULL
, _("_Cancel"), NULL
, _("_Insert"), NULL
, _("_Attach"),
11889 ALERTFOCUS_SECOND
, TRUE
, NULL
, ALERT_QUESTION
);
11892 case COMPOSE_DND_INSERT
:
11893 val
= G_ALERTALTERNATE
;
11895 case COMPOSE_DND_ATTACH
:
11896 val
= G_ALERTOTHER
;
11899 /* unexpected case */
11900 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11903 if (val
& G_ALERTDISABLE
) {
11904 val
&= ~G_ALERTDISABLE
;
11905 /* remember what action to perform by default, only if we don't click Cancel */
11906 if (val
== G_ALERTALTERNATE
)
11907 prefs_common
.compose_dnd_mode
= COMPOSE_DND_INSERT
;
11908 else if (val
== G_ALERTOTHER
)
11909 prefs_common
.compose_dnd_mode
= COMPOSE_DND_ATTACH
;
11912 if (val
== G_ALERTDEFAULT
|| val
== G_ALERTCANCEL
) {
11913 gtk_drag_finish(drag_context
, FALSE
, FALSE
, time
);
11914 list_free_strings_full(list
);
11916 } else if (val
== G_ALERTOTHER
) {
11917 compose_attach_drag_received_cb(widget
, drag_context
, x
, y
, data
, info
, time
, user_data
);
11918 list_free_strings_full(list
);
11922 for (tmp
= list
; tmp
!= NULL
; tmp
= tmp
->next
) {
11923 compose_insert_file(compose
, (const gchar
*)tmp
->data
);
11925 list_free_strings_full(list
);
11926 gtk_drag_finish(drag_context
, TRUE
, FALSE
, time
);
11931 static void compose_header_drag_received_cb (GtkWidget
*widget
,
11932 GdkDragContext
*drag_context
,
11935 GtkSelectionData
*data
,
11938 gpointer user_data
)
11940 GtkEditable
*entry
= (GtkEditable
*)user_data
;
11941 const gchar
*email
= (const gchar
*)gtk_selection_data_get_data(data
);
11943 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11946 if (!strncmp(email
, "mailto:", strlen("mailto:"))) {
11947 gchar
*decoded
=g_new(gchar
, strlen(email
));
11950 decode_uri(decoded
, email
+ strlen("mailto:")); /* will fit */
11951 gtk_editable_delete_text(entry
, 0, -1);
11952 gtk_editable_insert_text(entry
, decoded
, strlen(decoded
), &start
);
11953 gtk_drag_finish(drag_context
, TRUE
, FALSE
, time
);
11957 gtk_drag_finish(drag_context
, TRUE
, FALSE
, time
);
11960 static void compose_toggle_return_receipt_cb(GtkToggleAction
*action
, gpointer data
)
11962 Compose
*compose
= (Compose
*)data
;
11964 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action
)))
11965 compose
->return_receipt
= TRUE
;
11967 compose
->return_receipt
= FALSE
;
11970 static void compose_toggle_remove_refs_cb(GtkToggleAction
*action
, gpointer data
)
11972 Compose
*compose
= (Compose
*)data
;
11974 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action
)))
11975 compose
->remove_references
= TRUE
;
11977 compose
->remove_references
= FALSE
;
11980 static gboolean
compose_headerentry_button_clicked_cb (GtkWidget
*button
,
11981 ComposeHeaderEntry
*headerentry
)
11983 gtk_entry_set_text(GTK_ENTRY(headerentry
->entry
), "");
11984 gtk_widget_modify_base(GTK_WIDGET(headerentry
->entry
), GTK_STATE_NORMAL
, NULL
);
11985 gtk_widget_modify_text(GTK_WIDGET(headerentry
->entry
), GTK_STATE_NORMAL
, NULL
);
11989 static gboolean
compose_headerentry_key_press_event_cb(GtkWidget
*entry
,
11990 GdkEventKey
*event
,
11991 ComposeHeaderEntry
*headerentry
)
11993 if ((g_slist_length(headerentry
->compose
->header_list
) > 0) &&
11994 ((headerentry
->headernum
+ 1) != headerentry
->compose
->header_nextrow
) &&
11995 !(event
->state
& GDK_MODIFIER_MASK
) &&
11996 (event
->keyval
== GDK_KEY_BackSpace
) &&
11997 (strlen(gtk_entry_get_text(GTK_ENTRY(entry
))) == 0)) {
11998 gtk_container_remove
11999 (GTK_CONTAINER(headerentry
->compose
->header_table
),
12000 headerentry
->combo
);
12001 gtk_container_remove
12002 (GTK_CONTAINER(headerentry
->compose
->header_table
),
12003 headerentry
->entry
);
12004 headerentry
->compose
->header_list
=
12005 g_slist_remove(headerentry
->compose
->header_list
,
12007 g_free(headerentry
);
12008 } else if (event
->keyval
== GDK_KEY_Tab
) {
12009 if (headerentry
->compose
->header_last
== headerentry
) {
12010 /* Override default next focus, and give it to subject_entry
12011 * instead of notebook tabs
12013 g_signal_stop_emission_by_name(G_OBJECT(entry
), "key-press-event");
12014 gtk_widget_grab_focus(headerentry
->compose
->subject_entry
);
12021 static gboolean
scroll_postpone(gpointer data
)
12023 Compose
*compose
= (Compose
*)data
;
12025 if (compose
->batch
)
12028 GTK_EVENTS_FLUSH();
12029 compose_show_first_last_header(compose
, FALSE
);
12033 static void compose_headerentry_changed_cb(GtkWidget
*entry
,
12034 ComposeHeaderEntry
*headerentry
)
12036 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry
))) != 0) {
12037 compose_create_header_entry(headerentry
->compose
);
12038 g_signal_handlers_disconnect_matched
12039 (G_OBJECT(entry
), G_SIGNAL_MATCH_DATA
,
12040 0, 0, NULL
, NULL
, headerentry
);
12042 if (!headerentry
->compose
->batch
)
12043 g_timeout_add(0, scroll_postpone
, headerentry
->compose
);
12047 static gboolean
compose_defer_auto_save_draft(Compose
*compose
)
12049 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_UNSET
;
12050 compose_draft((gpointer
)compose
, COMPOSE_AUTO_SAVE
);
12054 static void compose_show_first_last_header(Compose
*compose
, gboolean show_first
)
12056 GtkAdjustment
*vadj
;
12058 cm_return_if_fail(compose
);
12063 cm_return_if_fail(GTK_IS_WIDGET(compose
->header_table
));
12064 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose
->header_table
)));
12065 vadj
= gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(gtk_widget_get_parent(compose
->header_table
)));
12066 gtk_adjustment_set_value(vadj
, (show_first
?
12067 gtk_adjustment_get_lower(vadj
) :
12068 (gtk_adjustment_get_upper(vadj
) -
12069 gtk_adjustment_get_page_size(vadj
))));
12072 static void text_inserted(GtkTextBuffer
*buffer
, GtkTextIter
*iter
,
12073 const gchar
*text
, gint len
, Compose
*compose
)
12075 gint paste_as_quotation
= GPOINTER_TO_INT(g_object_get_data
12076 (G_OBJECT(compose
->text
), "paste_as_quotation"));
12079 cm_return_if_fail(text
!= NULL
);
12081 g_signal_handlers_block_by_func(G_OBJECT(buffer
),
12082 G_CALLBACK(text_inserted
),
12084 if (paste_as_quotation
) {
12086 const gchar
*qmark
;
12088 GtkTextIter start_iter
;
12091 len
= strlen(text
);
12093 new_text
= g_strndup(text
, len
);
12095 qmark
= compose_quote_char_from_context(compose
);
12097 mark
= gtk_text_buffer_create_mark(buffer
, NULL
, iter
, FALSE
);
12098 gtk_text_buffer_place_cursor(buffer
, iter
);
12100 pos
= gtk_text_iter_get_offset(iter
);
12102 compose_quote_fmt(compose
, NULL
, "%Q", qmark
, new_text
, TRUE
, FALSE
,
12103 _("Quote format error at line %d."));
12104 quote_fmt_reset_vartable();
12106 g_object_set_data(G_OBJECT(compose
->text
), "paste_as_quotation",
12107 GINT_TO_POINTER(paste_as_quotation
- 1));
12109 gtk_text_buffer_get_iter_at_mark(buffer
, iter
, mark
);
12110 gtk_text_buffer_place_cursor(buffer
, iter
);
12111 gtk_text_buffer_delete_mark(buffer
, mark
);
12113 gtk_text_buffer_get_iter_at_offset(buffer
, &start_iter
, pos
);
12114 mark
= gtk_text_buffer_create_mark(buffer
, NULL
, &start_iter
, FALSE
);
12115 compose_beautify_paragraph(compose
, &start_iter
, FALSE
);
12116 gtk_text_buffer_get_iter_at_mark(buffer
, &start_iter
, mark
);
12117 gtk_text_buffer_delete_mark(buffer
, mark
);
12119 if (strcmp(text
, "\n") || compose
->automatic_break
12120 || gtk_text_iter_starts_line(iter
)) {
12121 GtkTextIter before_ins
;
12122 gtk_text_buffer_insert(buffer
, iter
, text
, len
);
12123 if (!strstr(text
, "\n") && gtk_text_iter_has_tag(iter
, compose
->no_join_tag
)) {
12124 before_ins
= *iter
;
12125 gtk_text_iter_backward_chars(&before_ins
, len
);
12126 gtk_text_buffer_remove_tag_by_name(buffer
, "no_join", &before_ins
, iter
);
12129 /* check if the preceding is just whitespace or quote */
12130 GtkTextIter start_line
;
12131 gchar
*tmp
= NULL
, *quote
= NULL
;
12132 gint quote_len
= 0, is_normal
= 0;
12133 start_line
= *iter
;
12134 gtk_text_iter_set_line_offset(&start_line
, 0);
12135 tmp
= gtk_text_buffer_get_text(buffer
, &start_line
, iter
, FALSE
);
12138 if (*tmp
== '\0') {
12141 quote
= compose_get_quote_str(buffer
, &start_line
, "e_len
);
12149 gtk_text_buffer_insert(buffer
, iter
, text
, len
);
12151 gtk_text_buffer_insert_with_tags_by_name(buffer
,
12152 iter
, text
, len
, "no_join", NULL
);
12157 if (!paste_as_quotation
) {
12158 mark
= gtk_text_buffer_create_mark(buffer
, NULL
, iter
, FALSE
);
12159 compose_beautify_paragraph(compose
, iter
, FALSE
);
12160 gtk_text_buffer_get_iter_at_mark(buffer
, iter
, mark
);
12161 gtk_text_buffer_delete_mark(buffer
, mark
);
12164 g_signal_handlers_unblock_by_func(G_OBJECT(buffer
),
12165 G_CALLBACK(text_inserted
),
12167 g_signal_stop_emission_by_name(G_OBJECT(buffer
), "insert-text");
12169 if (compose_can_autosave(compose
) &&
12170 gtk_text_buffer_get_char_count(buffer
) % prefs_common
.autosave_length
== 0 &&
12171 compose
->draft_timeout_tag
!= COMPOSE_DRAFT_TIMEOUT_FORBIDDEN
/* disabled while loading */)
12172 compose
->draft_timeout_tag
= g_timeout_add
12173 (500, (GSourceFunc
) compose_defer_auto_save_draft
, compose
);
12177 static void compose_check_all(GtkAction
*action
, gpointer data
)
12179 Compose
*compose
= (Compose
*)data
;
12180 if (!compose
->gtkaspell
)
12183 if (gtk_widget_has_focus(compose
->subject_entry
))
12184 claws_spell_entry_check_all(
12185 CLAWS_SPELL_ENTRY(compose
->subject_entry
));
12187 gtkaspell_check_all(compose
->gtkaspell
);
12190 static void compose_highlight_all(GtkAction
*action
, gpointer data
)
12192 Compose
*compose
= (Compose
*)data
;
12193 if (compose
->gtkaspell
) {
12194 claws_spell_entry_recheck_all(
12195 CLAWS_SPELL_ENTRY(compose
->subject_entry
));
12196 gtkaspell_highlight_all(compose
->gtkaspell
);
12200 static void compose_check_backwards(GtkAction
*action
, gpointer data
)
12202 Compose
*compose
= (Compose
*)data
;
12203 if (!compose
->gtkaspell
) {
12204 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Spelling", FALSE
);
12208 if (gtk_widget_has_focus(compose
->subject_entry
))
12209 claws_spell_entry_check_backwards(
12210 CLAWS_SPELL_ENTRY(compose
->subject_entry
));
12212 gtkaspell_check_backwards(compose
->gtkaspell
);
12215 static void compose_check_forwards_go(GtkAction
*action
, gpointer data
)
12217 Compose
*compose
= (Compose
*)data
;
12218 if (!compose
->gtkaspell
) {
12219 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Spelling", FALSE
);
12223 if (gtk_widget_has_focus(compose
->subject_entry
))
12224 claws_spell_entry_check_forwards_go(
12225 CLAWS_SPELL_ENTRY(compose
->subject_entry
));
12227 gtkaspell_check_forwards_go(compose
->gtkaspell
);
12232 *\brief Guess originating forward account from MsgInfo and several
12233 * "common preference" settings. Return NULL if no guess.
12235 static PrefsAccount
*compose_find_account(MsgInfo
*msginfo
)
12237 PrefsAccount
*account
= NULL
;
12239 cm_return_val_if_fail(msginfo
, NULL
);
12240 cm_return_val_if_fail(msginfo
->folder
, NULL
);
12241 cm_return_val_if_fail(msginfo
->folder
->prefs
, NULL
);
12243 if (msginfo
->folder
->prefs
->enable_default_account
)
12244 account
= account_find_from_id(msginfo
->folder
->prefs
->default_account
);
12246 if (!account
&& msginfo
->to
&& prefs_common
.forward_account_autosel
) {
12248 Xstrdup_a(to
, msginfo
->to
, return NULL
);
12249 extract_address(to
);
12250 account
= account_find_from_address(to
, FALSE
);
12253 if (!account
&& prefs_common
.forward_account_autosel
) {
12255 if (!procheader_get_header_from_msginfo
12256 (msginfo
, &cc
, "Cc:")) {
12257 gchar
*buf
= cc
+ strlen("Cc:");
12258 extract_address(buf
);
12259 account
= account_find_from_address(buf
, FALSE
);
12264 if (!account
&& prefs_common
.forward_account_autosel
) {
12265 gchar
*deliveredto
= NULL
;
12266 if (!procheader_get_header_from_msginfo
12267 (msginfo
, &deliveredto
, "Delivered-To:")) {
12268 gchar
*buf
= deliveredto
+ strlen("Delivered-To:");
12269 extract_address(buf
);
12270 account
= account_find_from_address(buf
, FALSE
);
12271 g_free(deliveredto
);
12276 account
= msginfo
->folder
->folder
->account
;
12281 gboolean
compose_close(Compose
*compose
)
12285 cm_return_val_if_fail(compose
, FALSE
);
12287 if (!g_mutex_trylock(&compose
->mutex
)) {
12288 /* we have to wait for the (possibly deferred by auto-save)
12289 * drafting to be done, before destroying the compose under
12291 debug_print("waiting for drafting to finish...\n");
12292 compose_allow_user_actions(compose
, FALSE
);
12293 if (compose
->close_timeout_tag
== 0) {
12294 compose
->close_timeout_tag
=
12295 g_timeout_add (500, (GSourceFunc
) compose_close
,
12301 if (compose
->draft_timeout_tag
>= 0) {
12302 g_source_remove(compose
->draft_timeout_tag
);
12303 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_FORBIDDEN
;
12306 gtk_window_get_position(GTK_WINDOW(compose
->window
), &x
, &y
);
12307 if (!compose
->batch
) {
12308 prefs_common
.compose_x
= x
;
12309 prefs_common
.compose_y
= y
;
12311 g_mutex_unlock(&compose
->mutex
);
12312 compose_destroy(compose
);
12317 * Add entry field for each address in list.
12318 * \param compose E-Mail composition object.
12319 * \param listAddress List of (formatted) E-Mail addresses.
12321 static void compose_add_field_list( Compose
*compose
, GList
*listAddress
) {
12324 node
= listAddress
;
12326 addr
= ( gchar
* ) node
->data
;
12327 compose_entry_append( compose
, addr
, COMPOSE_TO
, PREF_NONE
);
12328 node
= g_list_next( node
);
12332 static void compose_reply_from_messageview_real(MessageView
*msgview
, GSList
*msginfo_list
,
12333 guint action
, gboolean opening_multiple
)
12335 gchar
*body
= NULL
;
12336 GSList
*new_msglist
= NULL
;
12337 MsgInfo
*tmp_msginfo
= NULL
;
12338 gboolean originally_enc
= FALSE
;
12339 gboolean originally_sig
= FALSE
;
12340 Compose
*compose
= NULL
;
12341 gchar
*s_system
= NULL
;
12343 cm_return_if_fail(msginfo_list
!= NULL
);
12345 if (g_slist_length(msginfo_list
) == 1 && !opening_multiple
&& msgview
!= NULL
) {
12346 MimeInfo
*mimeinfo
= messageview_get_selected_mime_part(msgview
);
12347 MsgInfo
*orig_msginfo
= (MsgInfo
*)msginfo_list
->data
;
12349 if (mimeinfo
!= NULL
&& mimeinfo
->type
== MIMETYPE_MESSAGE
&&
12350 !g_ascii_strcasecmp(mimeinfo
->subtype
, "rfc822")) {
12351 tmp_msginfo
= procmsg_msginfo_new_from_mimeinfo(
12352 orig_msginfo
, mimeinfo
);
12353 if (tmp_msginfo
!= NULL
) {
12354 new_msglist
= g_slist_append(NULL
, tmp_msginfo
);
12356 originally_enc
= MSG_IS_ENCRYPTED(orig_msginfo
->flags
);
12357 privacy_msginfo_get_signed_state(orig_msginfo
, &s_system
);
12358 originally_sig
= MSG_IS_SIGNED(orig_msginfo
->flags
);
12360 tmp_msginfo
->folder
= orig_msginfo
->folder
;
12361 tmp_msginfo
->msgnum
= orig_msginfo
->msgnum
;
12362 if (orig_msginfo
->tags
) {
12363 tmp_msginfo
->tags
= g_slist_copy(orig_msginfo
->tags
);
12364 tmp_msginfo
->folder
->tags_dirty
= TRUE
;
12370 if (!opening_multiple
&& msgview
!= NULL
)
12371 body
= messageview_get_selection(msgview
);
12374 compose
= compose_reply_mode((ComposeMode
)action
, new_msglist
, body
);
12375 procmsg_msginfo_free(&tmp_msginfo
);
12376 g_slist_free(new_msglist
);
12378 compose
= compose_reply_mode((ComposeMode
)action
, msginfo_list
, body
);
12380 if (compose
&& originally_enc
) {
12381 compose_force_encryption(compose
, compose
->account
, FALSE
, s_system
);
12384 if (compose
&& originally_sig
&& compose
->account
->default_sign_reply
) {
12385 compose_force_signing(compose
, compose
->account
, s_system
);
12389 hooks_invoke(COMPOSE_CREATED_HOOKLIST
, compose
);
12392 void compose_reply_from_messageview(MessageView
*msgview
, GSList
*msginfo_list
,
12395 if ((!prefs_common
.forward_as_attachment
|| action
!= COMPOSE_FORWARD
)
12396 && msginfo_list
!= NULL
12397 && action
!= COMPOSE_FORWARD_AS_ATTACH
&& g_slist_length(msginfo_list
) > 1) {
12398 GSList
*cur
= msginfo_list
;
12399 gchar
*msg
= g_strdup_printf(_("You are about to reply to %d "
12400 "messages. Opening the windows "
12401 "could take some time. Do you "
12402 "want to continue?"),
12403 g_slist_length(msginfo_list
));
12404 if (g_slist_length(msginfo_list
) > 9
12405 && alertpanel(_("Warning"), msg
, NULL
, _("_Cancel"), NULL
, _("_Yes"),
12406 NULL
, NULL
, ALERTFOCUS_SECOND
) != G_ALERTALTERNATE
) {
12411 /* We'll open multiple compose windows */
12412 /* let the WM place the next windows */
12413 compose_force_window_origin
= FALSE
;
12414 for (; cur
; cur
= cur
->next
) {
12416 tmplist
.data
= cur
->data
;
12417 tmplist
.next
= NULL
;
12418 compose_reply_from_messageview_real(msgview
, &tmplist
, action
, TRUE
);
12420 compose_force_window_origin
= TRUE
;
12422 /* forwarding multiple mails as attachments is done via a
12423 * single compose window */
12424 if (msginfo_list
!= NULL
) {
12425 compose_reply_from_messageview_real(msgview
, msginfo_list
, action
, FALSE
);
12426 } else if (msgview
!= NULL
) {
12428 tmplist
.data
= msgview
->msginfo
;
12429 tmplist
.next
= NULL
;
12430 compose_reply_from_messageview_real(msgview
, &tmplist
, action
, FALSE
);
12432 debug_print("Nothing to reply to\n");
12437 void compose_check_for_email_account(Compose
*compose
)
12439 PrefsAccount
*ac
= NULL
, *curr
= NULL
;
12445 if (compose
->account
&& compose
->account
->protocol
== A_NNTP
) {
12446 ac
= account_get_cur_account();
12447 if (ac
->protocol
== A_NNTP
) {
12448 list
= account_get_list();
12450 for( ; list
!= NULL
; list
= g_list_next(list
)) {
12451 curr
= (PrefsAccount
*) list
->data
;
12452 if (curr
->protocol
!= A_NNTP
) {
12458 combobox_select_by_data(GTK_COMBO_BOX(compose
->account_combo
),
12463 void compose_reply_to_address(MessageView
*msgview
, MsgInfo
*msginfo
,
12464 const gchar
*address
)
12466 GSList
*msginfo_list
= NULL
;
12467 gchar
*body
= messageview_get_selection(msgview
);
12470 msginfo_list
= g_slist_prepend(msginfo_list
, msginfo
);
12472 compose
= compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS
, msginfo_list
, body
);
12473 compose_check_for_email_account(compose
);
12474 compose_set_folder_prefs(compose
, msginfo
->folder
, FALSE
);
12475 compose_entry_append(compose
, address
, COMPOSE_TO
, PREF_NONE
);
12476 compose_reply_set_subject(compose
, msginfo
);
12479 hooks_invoke(COMPOSE_CREATED_HOOKLIST
, compose
);
12482 void compose_set_position(Compose
*compose
, gint pos
)
12484 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
12486 gtkut_text_view_set_position(text
, pos
);
12489 gboolean
compose_search_string(Compose
*compose
,
12490 const gchar
*str
, gboolean case_sens
)
12492 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
12494 return gtkut_text_view_search_string(text
, str
, case_sens
);
12497 gboolean
compose_search_string_backward(Compose
*compose
,
12498 const gchar
*str
, gboolean case_sens
)
12500 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
12502 return gtkut_text_view_search_string_backward(text
, str
, case_sens
);
12505 /* allocate a msginfo structure and populate its data from a compose data structure */
12506 static MsgInfo
*compose_msginfo_new_from_compose(Compose
*compose
)
12508 MsgInfo
*newmsginfo
;
12510 gchar date
[RFC822_DATE_BUFFSIZE
];
12512 cm_return_val_if_fail( compose
!= NULL
, NULL
);
12514 newmsginfo
= procmsg_msginfo_new();
12517 get_rfc822_date(date
, sizeof(date
));
12518 newmsginfo
->date
= g_strdup(date
);
12521 if (compose
->from_name
) {
12522 newmsginfo
->from
= gtk_editable_get_chars(GTK_EDITABLE(compose
->from_name
), 0, -1);
12523 newmsginfo
->fromname
= procheader_get_fromname(newmsginfo
->from
);
12527 if (compose
->subject_entry
)
12528 newmsginfo
->subject
= gtk_editable_get_chars(GTK_EDITABLE(compose
->subject_entry
), 0, -1);
12530 /* to, cc, reply-to, newsgroups */
12531 for (list
= compose
->header_list
; list
; list
= list
->next
) {
12532 gchar
*header
= gtk_editable_get_chars(
12534 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry
*)list
->data
)->combo
)))), 0, -1);
12535 gchar
*entry
= gtk_editable_get_chars(
12536 GTK_EDITABLE(((ComposeHeaderEntry
*)list
->data
)->entry
), 0, -1);
12538 if ( strcasecmp(header
, prefs_common_translated_header_name("To:")) == 0 ) {
12539 if ( newmsginfo
->to
== NULL
) {
12540 newmsginfo
->to
= g_strdup(entry
);
12541 } else if (entry
&& *entry
) {
12542 gchar
*tmp
= g_strconcat(newmsginfo
->to
, ", ", entry
, NULL
);
12543 g_free(newmsginfo
->to
);
12544 newmsginfo
->to
= tmp
;
12547 if ( strcasecmp(header
, prefs_common_translated_header_name("Cc:")) == 0 ) {
12548 if ( newmsginfo
->cc
== NULL
) {
12549 newmsginfo
->cc
= g_strdup(entry
);
12550 } else if (entry
&& *entry
) {
12551 gchar
*tmp
= g_strconcat(newmsginfo
->cc
, ", ", entry
, NULL
);
12552 g_free(newmsginfo
->cc
);
12553 newmsginfo
->cc
= tmp
;
12556 if ( strcasecmp(header
,
12557 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12558 if ( newmsginfo
->newsgroups
== NULL
) {
12559 newmsginfo
->newsgroups
= g_strdup(entry
);
12560 } else if (entry
&& *entry
) {
12561 gchar
*tmp
= g_strconcat(newmsginfo
->newsgroups
, ", ", entry
, NULL
);
12562 g_free(newmsginfo
->newsgroups
);
12563 newmsginfo
->newsgroups
= tmp
;
12571 /* other data is unset */
12577 /* update compose's dictionaries from folder dict settings */
12578 static void compose_set_dictionaries_from_folder_prefs(Compose
*compose
,
12579 FolderItem
*folder_item
)
12581 cm_return_if_fail(compose
!= NULL
);
12583 if (compose
->gtkaspell
&& folder_item
&& folder_item
->prefs
) {
12584 FolderItemPrefs
*prefs
= folder_item
->prefs
;
12586 if (prefs
->enable_default_dictionary
)
12587 gtkaspell_change_dict(compose
->gtkaspell
,
12588 prefs
->default_dictionary
, FALSE
);
12589 if (folder_item
->prefs
->enable_default_alt_dictionary
)
12590 gtkaspell_change_alt_dict(compose
->gtkaspell
,
12591 prefs
->default_alt_dictionary
);
12592 if (prefs
->enable_default_dictionary
12593 || prefs
->enable_default_alt_dictionary
)
12594 compose_spell_menu_changed(compose
);
12599 static void compose_subject_entry_activated(GtkWidget
*widget
, gpointer data
)
12601 Compose
*compose
= (Compose
*)data
;
12603 cm_return_if_fail(compose
!= NULL
);
12605 gtk_widget_grab_focus(compose
->text
);
12608 static void from_name_activate_cb(GtkWidget
*widget
, gpointer data
)
12610 gtk_combo_box_popup(GTK_COMBO_BOX(data
));