compose.c (292487B)
1 /* 2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client 3 * Copyright (C) 1999-2025 the Claws Mail team and Hiroyuki Yamamoto 4 * 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. 9 * 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. 14 * 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/>. 17 */ 18 19 #include "defs.h" 20 21 #ifndef PANGO_ENABLE_ENGINE 22 # define PANGO_ENABLE_ENGINE 23 #endif 24 25 #include <glib.h> 26 #include <glib/gi18n.h> 27 #include <gdk/gdkkeysyms.h> 28 #include <gtk/gtk.h> 29 #ifdef GDK_WINDOWING_X11 30 #include <gtk/gtkx.h> 31 #endif 32 33 #include <pango/pango-break.h> 34 #include <stdio.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <ctype.h> 38 #include <sys/types.h> 39 #include <sys/stat.h> 40 #include <unistd.h> 41 #include <time.h> 42 #include <sys/wait.h> 43 #include <wchar.h> 44 #include <wctype.h> 45 #include <signal.h> 46 #include <err.h> 47 #include <errno.h> 48 #include <libgen.h> 49 50 #include "claws.h" 51 #include "main.h" 52 #include "mainwindow.h" 53 #include "compose.h" 54 #include "addressbook.h" 55 #include "folderview.h" 56 #include "procmsg.h" 57 #include "menu.h" 58 #include "stock_pixmap.h" 59 #include "send_message.h" 60 #include "imap.h" 61 #include "customheader.h" 62 #include "prefs_common.h" 63 #include "prefs_account.h" 64 #include "action.h" 65 #include "account.h" 66 #include "filesel.h" 67 #include "procheader.h" 68 #include "procmime.h" 69 #include "quoted-printable.h" 70 #include "codeconv.h" 71 #include "utils.h" 72 #include "gtkutils.h" 73 #include "socket.h" 74 #include "alertpanel.h" 75 #include "manage_window.h" 76 #include "folder.h" 77 #include "folder_item_prefs.h" 78 #include "addr_compl.h" 79 #include "undo.h" 80 #include "foldersel.h" 81 #include "toolbar.h" 82 #include "inc.h" 83 #include "message_search.h" 84 #include "combobox.h" 85 #include "hooks.h" 86 #include "headers.h" 87 #include "file-utils.h" 88 #include "fence.h" 89 90 enum 91 { 92 COL_MIMETYPE = 0, 93 COL_SIZE = 1, 94 COL_NAME = 2, 95 COL_CHARSET = 3, 96 COL_DATA = 4, 97 COL_AUTODATA = 5, 98 N_COL_COLUMNS 99 }; 100 101 #define N_ATTACH_COLS (N_COL_COLUMNS) 102 103 typedef enum 104 { 105 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1, 106 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0, 107 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER, 108 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER, 109 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD, 110 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD, 111 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE, 112 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE, 113 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE, 114 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER, 115 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER, 116 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD, 117 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD, 118 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE, 119 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END 120 } ComposeCallAdvancedAction; 121 122 typedef enum 123 { 124 COMPOSE_INSERT_SUCCESS, 125 COMPOSE_INSERT_READ_ERROR, 126 COMPOSE_INSERT_INVALID_CHARACTER, 127 COMPOSE_INSERT_NO_FILE 128 } ComposeInsertResult; 129 130 typedef enum 131 { 132 COMPOSE_WRITE_FOR_SEND, 133 COMPOSE_WRITE_FOR_STORE 134 } ComposeWriteType; 135 136 typedef enum 137 { 138 COMPOSE_QUOTE_FORCED, 139 COMPOSE_QUOTE_CHECK, 140 COMPOSE_QUOTE_SKIP 141 } ComposeQuoteMode; 142 143 typedef enum { 144 TO_FIELD_PRESENT, 145 SUBJECT_FIELD_PRESENT, 146 BODY_FIELD_PRESENT, 147 NO_FIELD_PRESENT 148 } MailField; 149 150 #define MAX_REFERENCES_LEN 999 151 152 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1 153 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2 154 155 #define INVALID_PID -1 156 157 static GdkRGBA default_header_bgcolor = 158 {0, 0, 0, 1}; 159 160 static GdkRGBA default_header_color = 161 {0, 0, 0, 1}; 162 163 static GList *compose_list = NULL; 164 static GSList *extra_headers = NULL; 165 166 static Compose *compose_generic_new (PrefsAccount *account, 167 const gchar *to, 168 FolderItem *item, 169 GList *attach_files, 170 GList *listAddress ); 171 172 static Compose *compose_create (PrefsAccount *account, 173 FolderItem *item, 174 ComposeMode mode, 175 gboolean batch); 176 177 static void compose_entry_indicate (Compose *compose, 178 const gchar *address); 179 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo, 180 ComposeQuoteMode quote_mode, 181 gboolean to_all, 182 gboolean to_sender, 183 const gchar *body); 184 static Compose *compose_forward_multiple (PrefsAccount *account, 185 GSList *msginfo_list); 186 static Compose *compose_reply (MsgInfo *msginfo, 187 ComposeQuoteMode quote_mode, 188 gboolean to_all, 189 gboolean to_ml, 190 gboolean to_sender, 191 const gchar *body); 192 static Compose *compose_reply_mode (ComposeMode mode, 193 GSList *msginfo_list, 194 gchar *body); 195 196 static GtkWidget *compose_account_option_menu_create 197 (Compose *compose); 198 static void compose_set_out_encoding (Compose *compose); 199 static void compose_destroy (Compose *compose); 200 201 static MailField compose_entries_set (Compose *compose, 202 const gchar *mailto, 203 ComposeEntryType to_type); 204 static gint compose_parse_header (Compose *compose, 205 MsgInfo *msginfo); 206 static gint compose_parse_manual_headers (Compose *compose, 207 MsgInfo *msginfo, 208 HeaderEntry *entries); 209 static gchar *compose_parse_references (const gchar *ref, 210 const gchar *msgid); 211 212 213 static void compose_reply_set_entry (Compose *compose, 214 MsgInfo *msginfo, 215 gboolean to_all, 216 gboolean to_ml, 217 gboolean to_sender, 218 gboolean 219 followup_and_reply_to); 220 static void compose_reedit_set_entry (Compose *compose, 221 MsgInfo *msginfo); 222 223 static ComposeInsertResult compose_insert_file (Compose *compose, 224 const gchar *file); 225 226 static gboolean compose_attach_append (Compose *compose, 227 const gchar *file, 228 const gchar *type, 229 const gchar *content_type, 230 const gchar *charset); 231 static void compose_attach_parts (Compose *compose, 232 MsgInfo *msginfo); 233 234 static gboolean compose_beautify_paragraph (Compose *compose, 235 GtkTextIter *par_iter, 236 gboolean force); 237 static void compose_wrap_all (Compose *compose); 238 static void compose_wrap_all_full (Compose *compose, 239 gboolean autowrap); 240 241 static void compose_set_title (Compose *compose); 242 static void compose_select_account (Compose *compose, 243 PrefsAccount *account, 244 gboolean init); 245 246 static PrefsAccount *compose_current_mail_account(void); 247 /* static gint compose_send (Compose *compose); */ 248 static gboolean compose_check_for_valid_recipient 249 (Compose *compose); 250 static gboolean compose_check_entries (Compose *compose, 251 gboolean check_everything); 252 static gint compose_write_to_file (Compose *compose, 253 FILE *fp, 254 gint action, 255 gboolean attach_parts); 256 static gint compose_write_body_to_file (Compose *compose, 257 const gchar *file); 258 static gint compose_remove_reedit_target (Compose *compose, 259 gboolean force); 260 static void compose_remove_draft (Compose *compose); 261 static ComposeQueueResult compose_queue_sub (Compose *compose, 262 gint *msgnum, 263 FolderItem **item, 264 gchar **msgpath, 265 gboolean perform_checks, 266 gboolean remove_reedit_target); 267 static int compose_add_attachments (Compose *compose, 268 MimeInfo *parent, 269 gint action); 270 static gchar *compose_get_header (Compose *compose); 271 static gchar *compose_get_manual_headers_info (Compose *compose); 272 273 static void compose_convert_header (Compose *compose, 274 gchar *dest, 275 gint len, 276 gchar *src, 277 gint header_len, 278 gboolean addr_field); 279 280 static void compose_attach_info_free (AttachInfo *ainfo); 281 static void compose_attach_remove_selected (GtkAction *action, 282 gpointer data); 283 284 static void compose_attach_property (GtkAction *action, 285 gpointer data); 286 static void compose_attach_property_create (gboolean *cancelled); 287 static void attach_property_ok (GtkWidget *widget, 288 gboolean *cancelled); 289 static void attach_property_cancel (GtkWidget *widget, 290 gboolean *cancelled); 291 static gint attach_property_delete_event (GtkWidget *widget, 292 GdkEventAny *event, 293 gboolean *cancelled); 294 static gboolean attach_property_key_pressed (GtkWidget *widget, 295 GdkEventKey *event, 296 gboolean *cancelled); 297 298 static void compose_exec_ext_editor (Compose *compose); 299 static gboolean compose_ext_editor_kill (Compose *compose); 300 static void compose_ext_editor_closed_cb (GPid pid, 301 gint exit_status, 302 gpointer data); 303 static void compose_set_ext_editor_sensitive (Compose *compose, 304 gboolean sensitive); 305 static gboolean compose_get_ext_editor_cmd_valid(); 306 static gboolean compose_get_ext_editor_uses_socket(); 307 #ifdef GDK_WINDOWING_X11 308 static gboolean compose_ext_editor_plug_removed_cb 309 (GtkSocket *socket, 310 Compose *compose); 311 #endif /* GDK_WINDOWING_X11 */ 312 313 static void compose_undo_state_changed (UndoMain *undostruct, 314 gint undo_state, 315 gint redo_state, 316 gpointer data); 317 318 static void compose_create_header_entry (Compose *compose); 319 static void compose_add_header_entry (Compose *compose, const gchar *header, 320 gchar *text, ComposePrefType pref_type); 321 static void compose_remove_header_entries(Compose *compose); 322 323 static void compose_add_field_list ( Compose *compose, 324 GList *listAddress ); 325 326 /* callback functions */ 327 328 static void compose_notebook_size_alloc (GtkNotebook *notebook, 329 GtkAllocation *allocation, 330 GtkPaned *paned); 331 static void account_activated (GtkComboBox *optmenu, 332 gpointer data); 333 static void attach_selected (GtkTreeView *tree_view, 334 GtkTreePath *tree_path, 335 GtkTreeViewColumn *column, 336 Compose *compose); 337 static gboolean attach_button_pressed (GtkWidget *widget, 338 GdkEventButton *event, 339 gpointer data); 340 static gboolean attach_key_pressed (GtkWidget *widget, 341 GdkEventKey *event, 342 gpointer data); 343 static void compose_send_cb (GtkAction *action, gpointer data); 344 static void compose_send_later_cb (GtkAction *action, gpointer data); 345 346 static void compose_save_cb (GtkAction *action, 347 gpointer data); 348 349 static void compose_attach_cb (GtkAction *action, 350 gpointer data); 351 static void compose_insert_file_cb (GtkAction *action, 352 gpointer data); 353 354 static void compose_close_cb (GtkAction *action, 355 gpointer data); 356 357 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data); 358 359 static void compose_address_cb (GtkAction *action, 360 gpointer data); 361 362 static void compose_ext_editor_cb (GtkAction *action, 363 gpointer data); 364 365 static gint compose_delete_cb (GtkWidget *widget, 366 GdkEventAny *event, 367 gpointer data); 368 369 static void compose_undo_cb (GtkAction *action, 370 gpointer data); 371 static void compose_redo_cb (GtkAction *action, 372 gpointer data); 373 static void compose_cut_cb (GtkAction *action, 374 gpointer data); 375 static void compose_copy_cb (GtkAction *action, 376 gpointer data); 377 static void compose_paste_cb (GtkAction *action, 378 gpointer data); 379 static void compose_paste_as_quote_cb (GtkAction *action, 380 gpointer data); 381 382 static void compose_allsel_cb (GtkAction *action, 383 gpointer data); 384 385 static void compose_advanced_action_cb (GtkAction *action, 386 gpointer data); 387 388 static void compose_grab_focus_cb (GtkWidget *widget, 389 Compose *compose); 390 391 static void compose_changed_cb (GtkTextBuffer *textbuf, 392 Compose *compose); 393 394 static void compose_wrap_cb (GtkAction *action, 395 gpointer data); 396 static void compose_wrap_all_cb (GtkAction *action, 397 gpointer data); 398 static void compose_find_cb (GtkAction *action, 399 gpointer data); 400 static void compose_toggle_autowrap_cb (GtkToggleAction *action, 401 gpointer data); 402 static void compose_toggle_autoindent_cb(GtkToggleAction *action, 403 gpointer data); 404 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data); 405 static void compose_reply_change_mode (Compose *compose, ComposeMode action); 406 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data); 407 408 static void compose_attach_drag_received_cb (GtkWidget *widget, 409 GdkDragContext *drag_context, 410 gint x, 411 gint y, 412 GtkSelectionData *data, 413 guint info, 414 guint time, 415 gpointer user_data); 416 static void compose_insert_drag_received_cb (GtkWidget *widget, 417 GdkDragContext *drag_context, 418 gint x, 419 gint y, 420 GtkSelectionData *data, 421 guint info, 422 guint time, 423 gpointer user_data); 424 static void compose_header_drag_received_cb (GtkWidget *widget, 425 GdkDragContext *drag_context, 426 gint x, 427 gint y, 428 GtkSelectionData *data, 429 guint info, 430 guint time, 431 gpointer user_data); 432 433 static gboolean compose_drag_drop (GtkWidget *widget, 434 GdkDragContext *drag_context, 435 gint x, gint y, 436 guint time, gpointer user_data); 437 static gboolean completion_set_focus_to_subject 438 (GtkWidget *widget, 439 GdkEventKey *event, 440 Compose *user_data); 441 442 static void text_inserted (GtkTextBuffer *buffer, 443 GtkTextIter *iter, 444 const gchar *text, 445 gint len, 446 Compose *compose); 447 static Compose *compose_generic_reply(MsgInfo *msginfo, 448 ComposeQuoteMode quote_mode, 449 gboolean to_all, 450 gboolean to_ml, 451 gboolean to_sender, 452 gboolean followup_and_reply_to, 453 const gchar *body); 454 455 static void compose_headerentry_changed_cb (GtkWidget *entry, 456 ComposeHeaderEntry *headerentry); 457 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry, 458 GdkEventKey *event, 459 ComposeHeaderEntry *headerentry); 460 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button, 461 ComposeHeaderEntry *headerentry); 462 463 static void compose_show_first_last_header (Compose *compose, gboolean show_first); 464 465 static void compose_allow_user_actions (Compose *compose, gboolean allow); 466 467 static void compose_nothing_cb (GtkAction *action, gpointer data) 468 { 469 470 } 471 472 static PrefsAccount *compose_find_account (MsgInfo *msginfo); 473 474 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose); 475 476 static void compose_attach_update_label(Compose *compose); 477 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder, 478 gboolean respect_default_to); 479 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data); 480 static void from_name_activate_cb(GtkWidget *widget, gpointer data); 481 482 static GtkActionEntry compose_popup_entries[] = 483 { 484 {"Compose", NULL, "Write", NULL, NULL, NULL }, 485 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) }, 486 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) }, 487 {"Compose/---", NULL, "---", NULL, NULL, NULL }, 488 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) }, 489 }; 490 491 /* make sure to keep the key bindings in the tables below in sync with the default_menurc[] in prefs_other.c 492 as well as with tables in messageview.c */ 493 static GtkActionEntry compose_entries[] = 494 { 495 {"Menu", NULL, "Menu", NULL, NULL, NULL }, 496 /* menus */ 497 {"Message", NULL, N_("_Message"), NULL, NULL, NULL }, 498 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL }, 499 {"Options", NULL, N_("_Options"), NULL, NULL, NULL }, 500 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL }, 501 /* Message menu */ 502 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) }, 503 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) }, 504 {"Message/---", NULL, "---", NULL, NULL, NULL }, 505 506 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) }, 507 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) }, 508 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */ 509 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/ 510 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */ 511 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) }, 512 513 /* Edit menu */ 514 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) }, 515 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) }, 516 {"Edit/---", NULL, "---", NULL, NULL, NULL }, 517 518 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) }, 519 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) }, 520 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) }, 521 522 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL }, 523 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) }, 524 525 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) }, 526 527 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL }, 528 {"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*/ 529 {"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*/ 530 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/ 531 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/ 532 {"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*/ 533 {"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*/ 534 {"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*/ 535 {"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*/ 536 {"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*/ 537 {"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*/ 538 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/ 539 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/ 540 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/ 541 {"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*/ 542 543 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */ 544 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) }, 545 546 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */ 547 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */ 548 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */ 549 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */ 550 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) }, 551 552 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL }, 553 {"Options/---", NULL, "---", NULL, NULL, NULL }, 554 555 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL }, 556 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL }, 557 #define ENC_ACTION(cs_char,c_char,string) \ 558 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char } 559 560 /* Tools menu */ 561 {"Tools/AddressBook", NULL, N_("_Address book"), "<shift><control>A", NULL, G_CALLBACK(compose_address_cb) }, 562 563 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL }, 564 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) }, 565 }; 566 567 static GtkToggleActionEntry compose_toggle_entries[] = 568 { 569 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */ 570 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */ 571 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */ 572 }; 573 574 static GtkRadioActionEntry compose_radio_rm_entries[] = 575 { 576 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */ 577 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */ 578 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */ 579 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */ 580 }; 581 582 static GtkRadioActionEntry compose_radio_enc_entries[] = 583 { 584 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */ 585 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), 586 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), 587 }; 588 589 static GtkTargetEntry compose_mime_types[] = 590 { 591 {"text/uri-list", 0, 0}, 592 {"UTF8_STRING", 0, 0}, 593 {"text/plain", 0, 0} 594 }; 595 596 static gboolean compose_put_existing_to_front(MsgInfo *info) 597 { 598 const GList *compose_list = compose_get_compose_list(); 599 const GList *elem = NULL; 600 601 if (compose_list) { 602 for (elem = compose_list; elem != NULL && elem->data != NULL; 603 elem = elem->next) { 604 Compose *c = (Compose*)elem->data; 605 606 if (!c->targetinfo || !c->targetinfo->msgid || 607 !info->msgid) 608 continue; 609 610 if (!strcmp(c->targetinfo->msgid, info->msgid)) { 611 gtkut_window_popup(c->window); 612 return TRUE; 613 } 614 } 615 } 616 return FALSE; 617 } 618 619 static GdkRGBA quote_color1 = 620 {0, 0, 0, 1}; 621 static GdkRGBA quote_color2 = 622 {0, 0, 0, 1}; 623 static GdkRGBA quote_color3 = 624 {0, 0, 0, 1}; 625 626 static GdkRGBA quote_bgcolor1 = 627 {0, 0, 0, 1}; 628 static GdkRGBA quote_bgcolor2 = 629 {0, 0, 0, 1}; 630 static GdkRGBA quote_bgcolor3 = 631 {0, 0, 0, 1}; 632 633 static GdkRGBA signature_color = 634 {0.5, 0.5, 0.5, 1}; 635 636 static GdkRGBA uri_color = 637 {0, 0, 0, 1}; 638 639 static void compose_create_tags(GtkTextView *text, Compose *compose) 640 { 641 GtkTextBuffer *buffer; 642 GdkRGBA black = { 0, 0, 0, 1 }; 643 644 buffer = gtk_text_view_get_buffer(text); 645 646 if (prefs_common.enable_color) { 647 /* grab the quote colors, converting from an int to a GdkColor */ 648 quote_color1 = prefs_common.color[COL_QUOTE_LEVEL1]; 649 quote_color2 = prefs_common.color[COL_QUOTE_LEVEL2]; 650 quote_color3 = prefs_common.color[COL_QUOTE_LEVEL3]; 651 quote_bgcolor1 = prefs_common.color[COL_QUOTE_LEVEL1_BG]; 652 quote_bgcolor2 = prefs_common.color[COL_QUOTE_LEVEL2_BG]; 653 quote_bgcolor3 = prefs_common.color[COL_QUOTE_LEVEL3_BG]; 654 signature_color = prefs_common.color[COL_SIGNATURE]; 655 uri_color = prefs_common.color[COL_URI]; 656 } else { 657 signature_color = quote_color1 = quote_color2 = quote_color3 = 658 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black; 659 } 660 661 if (prefs_common.enable_color && prefs_common.enable_bgcolor) { 662 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0", 663 "foreground-rgba", "e_color1, 664 "paragraph-background-rgba", "e_bgcolor1, 665 NULL); 666 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1", 667 "foreground-rgba", "e_color2, 668 "paragraph-background-rgba", "e_bgcolor2, 669 NULL); 670 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2", 671 "foreground-rgba", "e_color3, 672 "paragraph-background-rgba", "e_bgcolor3, 673 NULL); 674 } else { 675 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0", 676 "foreground-rgba", "e_color1, 677 NULL); 678 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1", 679 "foreground-rgba", "e_color2, 680 NULL); 681 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2", 682 "foreground-rgba", "e_color3, 683 NULL); 684 } 685 686 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature", 687 "foreground-rgba", &signature_color, 688 NULL); 689 690 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link", 691 "foreground-rgba", &uri_color, 692 NULL); 693 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL); 694 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL); 695 } 696 697 Compose *compose_new(PrefsAccount *account, const gchar *mailto, 698 GList *attach_files) 699 { 700 return compose_generic_new(account, mailto, NULL, attach_files, NULL); 701 } 702 703 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto) 704 { 705 return compose_generic_new(account, mailto, item, NULL, NULL); 706 } 707 708 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress ) 709 { 710 return compose_generic_new( account, NULL, NULL, NULL, listAddress ); 711 } 712 713 #define SCROLL_TO_CURSOR(compose) { \ 714 GtkTextMark *cmark = gtk_text_buffer_get_insert( \ 715 gtk_text_view_get_buffer( \ 716 GTK_TEXT_VIEW(compose->text))); \ 717 gtk_text_view_scroll_mark_onscreen( \ 718 GTK_TEXT_VIEW(compose->text), \ 719 cmark); \ 720 } 721 722 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier) 723 { 724 GtkEditable *entry; 725 if (folderidentifier) { 726 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo)); 727 prefs_common.compose_save_to_history = add_history( 728 prefs_common.compose_save_to_history, folderidentifier); 729 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo), 730 prefs_common.compose_save_to_history); 731 } 732 733 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo))); 734 if (folderidentifier) 735 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier); 736 else 737 gtk_entry_set_text(GTK_ENTRY(entry), ""); 738 } 739 740 static gchar *compose_get_save_to(Compose *compose) 741 { 742 GtkEditable *entry; 743 gchar *result = NULL; 744 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo))); 745 result = gtk_editable_get_chars(entry, 0, -1); 746 747 if (result) { 748 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo)); 749 prefs_common.compose_save_to_history = add_history( 750 prefs_common.compose_save_to_history, result); 751 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo), 752 prefs_common.compose_save_to_history); 753 } 754 return result; 755 } 756 757 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item, 758 GList *attach_files, GList *listAddress ) 759 { 760 Compose *compose; 761 GtkTextView *textview; 762 GtkTextBuffer *textbuf; 763 GtkTextIter iter; 764 gchar *mailto_from = NULL; 765 PrefsAccount *mailto_account = NULL; 766 MsgInfo* dummyinfo = NULL; 767 MailField mfield = NO_FIELD_PRESENT; 768 gchar* buf; 769 GtkTextMark *mark; 770 771 /* check if mailto defines a from */ 772 if (mailto && *mailto != '\0') { 773 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL); 774 /* mailto defines a from, check if we can get account prefs from it, 775 if not, the account prefs will be guessed using other ways, but we'll keep 776 the from anyway */ 777 if (mailto_from) { 778 mailto_account = account_find_from_address(mailto_from); 779 if (mailto_account == NULL) { 780 gchar *tmp_from; 781 Xstrdup_a(tmp_from, mailto_from, return NULL); 782 extract_address(tmp_from); 783 mailto_account = account_find_from_address(tmp_from); 784 } 785 } 786 if (mailto_account) 787 account = mailto_account; 788 } 789 790 /* if no account prefs set from mailto, set if from folder prefs (if any) */ 791 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account) 792 account = account_find_from_id(item->prefs->default_account); 793 794 /* if no account prefs set, fallback to the current one */ 795 if (!account) account = cur_account; 796 cm_return_val_if_fail(account != NULL, NULL); 797 798 compose = compose_create(account, item, COMPOSE_NEW, FALSE); 799 800 /* override from name if mailto asked for it */ 801 if (mailto_from) { 802 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from); 803 g_free(mailto_from); 804 } 805 compose->replyinfo = NULL; 806 compose->fwdinfo = NULL; 807 808 textview = GTK_TEXT_VIEW(compose->text); 809 textbuf = gtk_text_view_get_buffer(textview); 810 compose_create_tags(textview, compose); 811 812 undo_block(compose->undostruct); 813 814 gtk_text_buffer_get_start_iter(textbuf, &iter); 815 gtk_text_buffer_place_cursor(textbuf, &iter); 816 817 if (mailto && *mailto != '\0') 818 mfield = compose_entries_set(compose, mailto, COMPOSE_TO); 819 else 820 compose_set_folder_prefs(compose, item, TRUE); 821 822 compose_add_field_list( compose, listAddress ); 823 824 procmsg_msginfo_free( &dummyinfo ); 825 826 if (attach_files) { 827 GList *curr; 828 AttachInfo *ainfo; 829 830 for (curr = attach_files ; curr != NULL ; curr = curr->next) { 831 ainfo = (AttachInfo *) curr->data; 832 if (ainfo->insert) 833 compose_insert_file(compose, ainfo->file); 834 else 835 compose_attach_append(compose, ainfo->file, ainfo->file, 836 ainfo->content_type, ainfo->charset); 837 } 838 } 839 840 compose_show_first_last_header(compose, TRUE); 841 842 /* Set save folder */ 843 if (item && item->prefs && item->prefs->save_copy_to_folder) { 844 gchar *folderidentifier; 845 846 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE); 847 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE); 848 folderidentifier = folder_item_get_identifier(item); 849 compose_set_save_to(compose, folderidentifier); 850 g_free(folderidentifier); 851 } 852 853 /* Place cursor according to provided input (mfield) */ 854 switch (mfield) { 855 case NO_FIELD_PRESENT: 856 if (compose->header_last) 857 gtk_widget_grab_focus(compose->header_last->entry); 858 break; 859 case TO_FIELD_PRESENT: 860 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1); 861 if (buf) { 862 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf); 863 g_free(buf); 864 } 865 gtk_widget_grab_focus(compose->subject_entry); 866 break; 867 case SUBJECT_FIELD_PRESENT: 868 textview = GTK_TEXT_VIEW(compose->text); 869 if (!textview) 870 break; 871 textbuf = gtk_text_view_get_buffer(textview); 872 if (!textbuf) 873 break; 874 mark = gtk_text_buffer_get_insert(textbuf); 875 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark); 876 gtk_text_buffer_insert(textbuf, &iter, "", -1); 877 /* 878 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT 879 * only defers where it comes to the variable body 880 * is not null. If no body is present compose->text 881 * will be null in which case you cannot place the 882 * cursor inside the component so. An empty component 883 * is therefore created before placing the cursor 884 */ 885 case BODY_FIELD_PRESENT: 886 gtk_widget_grab_focus(compose->text); 887 break; 888 } 889 890 undo_unblock(compose->undostruct); 891 892 if (prefs_common.auto_exteditor) 893 compose_exec_ext_editor(compose); 894 895 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; 896 897 SCROLL_TO_CURSOR(compose); 898 899 compose->modified = FALSE; 900 compose_set_title(compose); 901 902 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose); 903 904 return compose; 905 } 906 907 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body) 908 { 909 MsgInfo *msginfo; 910 guint list_len; 911 Compose *compose = NULL; 912 913 cm_return_val_if_fail(msginfo_list != NULL, NULL); 914 915 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0); 916 cm_return_val_if_fail(msginfo != NULL, NULL); 917 918 list_len = g_slist_length(msginfo_list); 919 920 switch (mode) { 921 case COMPOSE_REPLY: 922 case COMPOSE_REPLY_TO_ADDRESS: 923 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK, 924 FALSE, prefs_common.default_reply_list, FALSE, body); 925 break; 926 case COMPOSE_REPLY_WITH_QUOTE: 927 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED, 928 FALSE, prefs_common.default_reply_list, FALSE, body); 929 break; 930 case COMPOSE_REPLY_WITHOUT_QUOTE: 931 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP, 932 FALSE, prefs_common.default_reply_list, FALSE, NULL); 933 break; 934 case COMPOSE_REPLY_TO_SENDER: 935 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK, 936 FALSE, FALSE, TRUE, body); 937 break; 938 case COMPOSE_FOLLOWUP_AND_REPLY_TO: 939 compose = compose_followup_and_reply_to(msginfo, 940 COMPOSE_QUOTE_CHECK, 941 FALSE, FALSE, body); 942 break; 943 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE: 944 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED, 945 FALSE, FALSE, TRUE, body); 946 break; 947 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE: 948 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP, 949 FALSE, FALSE, TRUE, NULL); 950 break; 951 case COMPOSE_REPLY_TO_ALL: 952 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK, 953 TRUE, FALSE, FALSE, body); 954 break; 955 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE: 956 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED, 957 TRUE, FALSE, FALSE, body); 958 break; 959 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE: 960 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP, 961 TRUE, FALSE, FALSE, NULL); 962 break; 963 case COMPOSE_REPLY_TO_LIST: 964 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK, 965 FALSE, TRUE, FALSE, body); 966 break; 967 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE: 968 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED, 969 FALSE, TRUE, FALSE, body); 970 break; 971 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE: 972 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP, 973 FALSE, TRUE, FALSE, NULL); 974 break; 975 case COMPOSE_FORWARD: 976 if (prefs_common.forward_as_attachment) { 977 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body); 978 return compose; 979 } else { 980 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body); 981 return compose; 982 } 983 break; 984 case COMPOSE_FORWARD_INLINE: 985 /* check if we reply to more than one Message */ 986 if (list_len == 1) { 987 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE); 988 break; 989 } 990 /* more messages FALL THROUGH */ 991 case COMPOSE_FORWARD_AS_ATTACH: 992 compose = compose_forward_multiple(NULL, msginfo_list); 993 break; 994 case COMPOSE_REDIRECT: 995 compose = compose_redirect(NULL, msginfo, FALSE); 996 break; 997 default: 998 g_warning("compose_reply_mode(): invalid Write Mode: %d", mode); 999 } 1000 1001 if (compose == NULL) { 1002 alertpanel_error(_("Unable to reply. The original email probably doesn't exist.")); 1003 return NULL; 1004 } 1005 1006 compose->rmode = mode; 1007 switch (compose->rmode) { 1008 case COMPOSE_REPLY: 1009 case COMPOSE_REPLY_WITH_QUOTE: 1010 case COMPOSE_REPLY_WITHOUT_QUOTE: 1011 case COMPOSE_FOLLOWUP_AND_REPLY_TO: 1012 debug_print("reply mode Normal\n"); 1013 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE); 1014 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */ 1015 break; 1016 case COMPOSE_REPLY_TO_SENDER: 1017 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE: 1018 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE: 1019 debug_print("reply mode Sender\n"); 1020 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE); 1021 break; 1022 case COMPOSE_REPLY_TO_ALL: 1023 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE: 1024 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE: 1025 debug_print("reply mode All\n"); 1026 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE); 1027 break; 1028 case COMPOSE_REPLY_TO_LIST: 1029 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE: 1030 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE: 1031 debug_print("reply mode List\n"); 1032 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE); 1033 break; 1034 case COMPOSE_REPLY_TO_ADDRESS: 1035 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE); 1036 break; 1037 default: 1038 break; 1039 } 1040 return compose; 1041 } 1042 1043 static Compose *compose_reply(MsgInfo *msginfo, 1044 ComposeQuoteMode quote_mode, 1045 gboolean to_all, 1046 gboolean to_ml, 1047 gboolean to_sender, 1048 const gchar *body) 1049 { 1050 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml, 1051 to_sender, FALSE, body); 1052 } 1053 1054 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo, 1055 ComposeQuoteMode quote_mode, 1056 gboolean to_all, 1057 gboolean to_sender, 1058 const gchar *body) 1059 { 1060 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE, 1061 to_sender, TRUE, body); 1062 } 1063 1064 static void compose_extract_original_charset(Compose *compose) 1065 { 1066 MsgInfo *info = NULL; 1067 if (compose->replyinfo) { 1068 info = compose->replyinfo; 1069 } else if (compose->fwdinfo) { 1070 info = compose->fwdinfo; 1071 } else if (compose->targetinfo) { 1072 info = compose->targetinfo; 1073 } 1074 if (info) { 1075 MimeInfo *mimeinfo = procmime_scan_message_short(info); 1076 MimeInfo *partinfo = mimeinfo; 1077 while (partinfo && partinfo->type != MIMETYPE_TEXT) 1078 partinfo = procmime_mimeinfo_next(partinfo); 1079 if (partinfo) { 1080 compose->orig_charset = 1081 g_strdup(procmime_mimeinfo_get_parameter( 1082 partinfo, "charset")); 1083 } 1084 procmime_mimeinfo_free_all(&mimeinfo); 1085 } 1086 } 1087 1088 #define SIGNAL_BLOCK(buffer) { \ 1089 g_signal_handlers_block_by_func(G_OBJECT(buffer), \ 1090 G_CALLBACK(compose_changed_cb), \ 1091 compose); \ 1092 g_signal_handlers_block_by_func(G_OBJECT(buffer), \ 1093 G_CALLBACK(text_inserted), \ 1094 compose); \ 1095 } 1096 1097 #define SIGNAL_UNBLOCK(buffer) { \ 1098 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \ 1099 G_CALLBACK(compose_changed_cb), \ 1100 compose); \ 1101 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \ 1102 G_CALLBACK(text_inserted), \ 1103 compose); \ 1104 } 1105 1106 static Compose *compose_generic_reply(MsgInfo *msginfo, 1107 ComposeQuoteMode quote_mode, 1108 gboolean to_all, gboolean to_ml, 1109 gboolean to_sender, 1110 gboolean followup_and_reply_to, 1111 const gchar *body) 1112 { 1113 Compose *compose; 1114 PrefsAccount *account = NULL; 1115 GtkTextView *textview; 1116 GtkTextBuffer *textbuf; 1117 gchar *s_system = NULL; 1118 cm_return_val_if_fail(msginfo != NULL, NULL); 1119 cm_return_val_if_fail(msginfo->folder != NULL, NULL); 1120 1121 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel); 1122 1123 cm_return_val_if_fail(account != NULL, NULL); 1124 1125 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE); 1126 1127 compose->updating = TRUE; 1128 1129 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE); 1130 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE); 1131 1132 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo); 1133 if (!compose->replyinfo) 1134 compose->replyinfo = procmsg_msginfo_copy(msginfo); 1135 1136 compose_extract_original_charset(compose); 1137 1138 /* Set save folder */ 1139 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) { 1140 gchar *folderidentifier; 1141 1142 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE); 1143 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE); 1144 folderidentifier = folder_item_get_identifier(msginfo->folder); 1145 compose_set_save_to(compose, folderidentifier); 1146 g_free(folderidentifier); 1147 } 1148 1149 if (compose_parse_header(compose, msginfo) < 0) { 1150 compose->updating = FALSE; 1151 compose_destroy(compose); 1152 return NULL; 1153 } 1154 1155 textview = (GTK_TEXT_VIEW(compose->text)); 1156 textbuf = gtk_text_view_get_buffer(textview); 1157 compose_create_tags(textview, compose); 1158 1159 undo_block(compose->undostruct); 1160 1161 g_free(s_system); 1162 1163 SIGNAL_BLOCK(textbuf); 1164 compose_wrap_all(compose); 1165 SIGNAL_UNBLOCK(textbuf); 1166 1167 gtk_widget_grab_focus(compose->text); 1168 1169 undo_unblock(compose->undostruct); 1170 1171 if (prefs_common.auto_exteditor) 1172 compose_exec_ext_editor(compose); 1173 1174 compose->modified = FALSE; 1175 compose_set_title(compose); 1176 1177 compose->updating = FALSE; 1178 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */ 1179 SCROLL_TO_CURSOR(compose); 1180 1181 if (compose->deferred_destroy) { 1182 compose_destroy(compose); 1183 return NULL; 1184 } 1185 1186 return compose; 1187 } 1188 1189 #define INSERT_FW_HEADER(var, hdr) \ 1190 if (msginfo->var && *msginfo->var) { \ 1191 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \ 1192 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \ 1193 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \ 1194 } 1195 1196 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo, 1197 gboolean as_attach, const gchar *body, 1198 gboolean no_extedit, 1199 gboolean batch) 1200 { 1201 Compose *compose; 1202 GtkTextView *textview; 1203 GtkTextBuffer *textbuf; 1204 ComposeMode mode; 1205 1206 cm_return_val_if_fail(msginfo != NULL, NULL); 1207 cm_return_val_if_fail(msginfo->folder != NULL, NULL); 1208 1209 if (!account && !(account = compose_find_account(msginfo))) 1210 account = cur_account; 1211 1212 if (!prefs_common.forward_as_attachment) 1213 mode = COMPOSE_FORWARD_INLINE; 1214 else 1215 mode = COMPOSE_FORWARD; 1216 compose = compose_create(account, msginfo->folder, mode, batch); 1217 1218 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE); 1219 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE); 1220 1221 if (compose_parse_header(compose, msginfo) < 0) { 1222 compose->updating = FALSE; 1223 compose_destroy(compose); 1224 return NULL; 1225 } 1226 1227 compose->updating = TRUE; 1228 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo); 1229 if (!compose->fwdinfo) 1230 compose->fwdinfo = procmsg_msginfo_copy(msginfo); 1231 1232 compose_extract_original_charset(compose); 1233 1234 if (msginfo->subject && *msginfo->subject) { 1235 gchar *buf, *buf2, *p; 1236 1237 buf = p = g_strdup(msginfo->subject); 1238 p += subject_get_prefix_length(p); 1239 memmove(buf, p, strlen(p) + 1); 1240 1241 buf2 = g_strdup_printf("Fw: %s", buf); 1242 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2); 1243 1244 g_free(buf); 1245 g_free(buf2); 1246 } 1247 1248 textview = GTK_TEXT_VIEW(compose->text); 1249 textbuf = gtk_text_view_get_buffer(textview); 1250 compose_create_tags(textview, compose); 1251 1252 undo_block(compose->undostruct); 1253 if (as_attach) { 1254 gchar *msgfile; 1255 1256 msgfile = procmsg_get_message_file(msginfo); 1257 if (!is_file_exist(msgfile)) 1258 g_warning("%s: file does not exist", msgfile); 1259 else 1260 compose_attach_append(compose, msgfile, msgfile, 1261 "message/rfc822", NULL); 1262 1263 g_free(msgfile); 1264 } 1265 1266 SIGNAL_BLOCK(textbuf); 1267 compose_wrap_all(compose); 1268 SIGNAL_UNBLOCK(textbuf); 1269 1270 gtk_widget_grab_focus(compose->text); 1271 1272 if (!no_extedit && prefs_common.auto_exteditor) 1273 compose_exec_ext_editor(compose); 1274 1275 /*save folder*/ 1276 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) { 1277 gchar *folderidentifier; 1278 1279 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE); 1280 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE); 1281 folderidentifier = folder_item_get_identifier(msginfo->folder); 1282 compose_set_save_to(compose, folderidentifier); 1283 g_free(folderidentifier); 1284 } 1285 1286 undo_unblock(compose->undostruct); 1287 1288 compose->modified = FALSE; 1289 compose_set_title(compose); 1290 1291 compose->updating = FALSE; 1292 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */ 1293 SCROLL_TO_CURSOR(compose); 1294 1295 if (compose->deferred_destroy) { 1296 compose_destroy(compose); 1297 return NULL; 1298 } 1299 1300 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose); 1301 1302 return compose; 1303 } 1304 1305 #undef INSERT_FW_HEADER 1306 1307 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list) 1308 { 1309 Compose *compose; 1310 GtkTextView *textview; 1311 GtkTextBuffer *textbuf; 1312 GtkTextIter iter; 1313 GSList *msginfo; 1314 gchar *msgfile; 1315 gboolean single_mail = TRUE; 1316 1317 cm_return_val_if_fail(msginfo_list != NULL, NULL); 1318 1319 if (g_slist_length(msginfo_list) > 1) 1320 single_mail = FALSE; 1321 1322 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) 1323 if (((MsgInfo *)msginfo->data)->folder == NULL) 1324 return NULL; 1325 1326 /* guess account from first selected message */ 1327 if (!account && 1328 !(account = compose_find_account(msginfo_list->data))) 1329 account = cur_account; 1330 1331 cm_return_val_if_fail(account != NULL, NULL); 1332 1333 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) { 1334 if (msginfo->data) { 1335 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED); 1336 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED); 1337 } 1338 } 1339 1340 if (msginfo_list == NULL || msginfo_list->data == NULL) { 1341 g_warning("no msginfo_list"); 1342 return NULL; 1343 } 1344 1345 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE); 1346 1347 compose->updating = TRUE; 1348 1349 textview = GTK_TEXT_VIEW(compose->text); 1350 textbuf = gtk_text_view_get_buffer(textview); 1351 compose_create_tags(textview, compose); 1352 1353 undo_block(compose->undostruct); 1354 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) { 1355 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data); 1356 1357 if (!is_file_exist(msgfile)) 1358 g_warning("%s: file does not exist", msgfile); 1359 else 1360 compose_attach_append(compose, msgfile, msgfile, 1361 "message/rfc822", NULL); 1362 g_free(msgfile); 1363 } 1364 1365 if (single_mail) { 1366 MsgInfo *info = (MsgInfo *)msginfo_list->data; 1367 if (info->subject && *info->subject) { 1368 gchar *buf, *buf2, *p; 1369 1370 buf = p = g_strdup(info->subject); 1371 p += subject_get_prefix_length(p); 1372 memmove(buf, p, strlen(p) + 1); 1373 1374 buf2 = g_strdup_printf("Fw: %s", buf); 1375 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2); 1376 1377 g_free(buf); 1378 g_free(buf2); 1379 } 1380 } else { 1381 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), 1382 _("Fw: multiple emails")); 1383 } 1384 1385 SIGNAL_BLOCK(textbuf); 1386 compose_wrap_all(compose); 1387 SIGNAL_UNBLOCK(textbuf); 1388 1389 gtk_text_buffer_get_start_iter(textbuf, &iter); 1390 gtk_text_buffer_place_cursor(textbuf, &iter); 1391 1392 if (prefs_common.auto_exteditor) 1393 compose_exec_ext_editor(compose); 1394 1395 gtk_widget_grab_focus(compose->header_last->entry); 1396 undo_unblock(compose->undostruct); 1397 compose->modified = FALSE; 1398 compose_set_title(compose); 1399 1400 compose->updating = FALSE; 1401 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */ 1402 SCROLL_TO_CURSOR(compose); 1403 1404 if (compose->deferred_destroy) { 1405 compose_destroy(compose); 1406 return NULL; 1407 } 1408 1409 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose); 1410 1411 return compose; 1412 } 1413 1414 static gboolean compose_update_folder_hook(gpointer source, gpointer data) 1415 { 1416 FolderUpdateData *hookdata = (FolderUpdateData *)source; 1417 Compose *compose = (Compose *)data; 1418 FolderItem *old_item = NULL; 1419 FolderItem *new_item = NULL; 1420 gchar *old_id, *new_id; 1421 1422 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM) 1423 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM)) 1424 return FALSE; 1425 1426 old_item = hookdata->item; 1427 new_item = hookdata->item2; 1428 1429 old_id = folder_item_get_identifier(old_item); 1430 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL"); 1431 1432 if (compose->targetinfo && compose->targetinfo->folder == old_item) { 1433 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id); 1434 compose->targetinfo->folder = new_item; 1435 } 1436 1437 if (compose->replyinfo && compose->replyinfo->folder == old_item) { 1438 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id); 1439 compose->replyinfo->folder = new_item; 1440 } 1441 1442 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) { 1443 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id); 1444 compose->fwdinfo->folder = new_item; 1445 } 1446 1447 g_free(old_id); 1448 g_free(new_id); 1449 return FALSE; 1450 } 1451 1452 #define BLOCK_WRAP() { \ 1453 prev_autowrap = compose->autowrap; \ 1454 buffer = gtk_text_view_get_buffer( \ 1455 GTK_TEXT_VIEW(compose->text)); \ 1456 compose->autowrap = FALSE; \ 1457 \ 1458 g_signal_handlers_block_by_func(G_OBJECT(buffer), \ 1459 G_CALLBACK(compose_changed_cb), \ 1460 compose); \ 1461 g_signal_handlers_block_by_func(G_OBJECT(buffer), \ 1462 G_CALLBACK(text_inserted), \ 1463 compose); \ 1464 } 1465 #define UNBLOCK_WRAP() { \ 1466 compose->autowrap = prev_autowrap; \ 1467 if (compose->autowrap) { \ 1468 gint old = compose->draft_timeout_tag; \ 1469 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \ 1470 compose_wrap_all(compose); \ 1471 compose->draft_timeout_tag = old; \ 1472 } \ 1473 \ 1474 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \ 1475 G_CALLBACK(compose_changed_cb), \ 1476 compose); \ 1477 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \ 1478 G_CALLBACK(text_inserted), \ 1479 compose); \ 1480 } 1481 1482 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch) 1483 { 1484 Compose *compose = NULL; 1485 PrefsAccount *account = NULL; 1486 GtkTextView *textview; 1487 GtkTextBuffer *textbuf; 1488 GtkTextMark *mark; 1489 GtkTextIter iter; 1490 FILE *fp; 1491 MsgInfo *replyinfo = NULL, *fwdinfo = NULL; 1492 gboolean autowrap = prefs_common.autowrap; 1493 gboolean autoindent = prefs_common.auto_indent; 1494 HeaderEntry *manual_headers = NULL; 1495 1496 cm_return_val_if_fail(msginfo != NULL, NULL); 1497 cm_return_val_if_fail(msginfo->folder != NULL, NULL); 1498 1499 if (compose_put_existing_to_front(msginfo)) { 1500 return NULL; 1501 } 1502 1503 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) || 1504 folder_has_parent_of_type(msginfo->folder, F_DRAFT) || 1505 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) { 1506 gchar *queueheader_buf = NULL; 1507 gint id, param; 1508 1509 /* Select Account from queue headers */ 1510 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, 1511 "X-Claws-Account-Id:")) { 1512 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]); 1513 account = account_find_from_id(id); 1514 g_free(queueheader_buf); 1515 } 1516 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, 1517 "X-Sylpheed-Account-Id:")) { 1518 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]); 1519 account = account_find_from_id(id); 1520 g_free(queueheader_buf); 1521 } 1522 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf, 1523 "NAID:")) { 1524 id = atoi(&queueheader_buf[strlen("NAID:")]); 1525 account = account_find_from_id(id); 1526 g_free(queueheader_buf); 1527 } 1528 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf, 1529 "MAID:")) { 1530 id = atoi(&queueheader_buf[strlen("MAID:")]); 1531 account = account_find_from_id(id); 1532 g_free(queueheader_buf); 1533 } 1534 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf, 1535 "S:")) { 1536 account = account_find_from_address(queueheader_buf); 1537 g_free(queueheader_buf); 1538 } 1539 1540 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, 1541 "X-Claws-Auto-Wrapping:")) { 1542 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]); 1543 autowrap = param; 1544 g_free(queueheader_buf); 1545 } 1546 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, 1547 "X-Claws-Auto-Indent:")) { 1548 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]); 1549 autoindent = param; 1550 g_free(queueheader_buf); 1551 } 1552 1553 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, 1554 "RMID:")) { 1555 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0); 1556 if (tokens && tokens[0] && tokens[1] && tokens[2]) { 1557 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]); 1558 if (orig_item != NULL) { 1559 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]); 1560 } 1561 } 1562 if (tokens) 1563 g_strfreev(tokens); 1564 g_free(queueheader_buf); 1565 } 1566 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, 1567 "FMID:")) { 1568 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0); 1569 if (tokens && tokens[0] && tokens[1] && tokens[2]) { 1570 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]); 1571 if (orig_item != NULL) { 1572 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]); 1573 } 1574 } 1575 if (tokens) 1576 g_strfreev(tokens); 1577 g_free(queueheader_buf); 1578 } 1579 /* Get manual headers */ 1580 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, 1581 "X-Claws-Manual-Headers:")) { 1582 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]); 1583 if (listmh && *listmh != '\0') { 1584 debug_print("Got manual headers: %s\n", listmh); 1585 manual_headers = procheader_entries_from_str(listmh); 1586 } 1587 if (listmh) 1588 g_free(listmh); 1589 g_free(queueheader_buf); 1590 } 1591 } else { 1592 account = msginfo->folder->folder->account; 1593 } 1594 1595 if (!account && prefs_common.reedit_account_autosel) { 1596 gchar *from = NULL; 1597 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) { 1598 extract_address(from); 1599 account = account_find_from_address(from); 1600 } 1601 if (from) 1602 g_free(from); 1603 } 1604 if (!account) { 1605 account = cur_account; 1606 } 1607 if (!account) { 1608 g_warning("can't select account"); 1609 if (manual_headers) 1610 procheader_entries_free(manual_headers); 1611 return NULL; 1612 } 1613 1614 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch); 1615 1616 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE); 1617 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE); 1618 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap); 1619 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent); 1620 compose->autowrap = autowrap; 1621 compose->replyinfo = replyinfo; 1622 compose->fwdinfo = fwdinfo; 1623 1624 compose->updating = TRUE; 1625 1626 compose->targetinfo = procmsg_msginfo_copy(msginfo); 1627 compose->targetinfo->tags = g_slist_copy(msginfo->tags); 1628 1629 compose_extract_original_charset(compose); 1630 1631 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) || 1632 folder_has_parent_of_type(msginfo->folder, F_DRAFT) || 1633 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) { 1634 gchar *queueheader_buf = NULL; 1635 1636 /* Set message save folder */ 1637 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) { 1638 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE); 1639 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE); 1640 compose_set_save_to(compose, &queueheader_buf[4]); 1641 g_free(queueheader_buf); 1642 } 1643 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) { 1644 g_free(queueheader_buf); 1645 } 1646 } 1647 1648 if (compose_parse_header(compose, msginfo) < 0) { 1649 compose->updating = FALSE; 1650 compose_destroy(compose); 1651 if (manual_headers) 1652 procheader_entries_free(manual_headers); 1653 return NULL; 1654 } 1655 compose_reedit_set_entry(compose, msginfo); 1656 1657 textview = GTK_TEXT_VIEW(compose->text); 1658 textbuf = gtk_text_view_get_buffer(textview); 1659 compose_create_tags(textview, compose); 1660 1661 mark = gtk_text_buffer_get_insert(textbuf); 1662 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark); 1663 1664 g_signal_handlers_block_by_func(G_OBJECT(textbuf), 1665 G_CALLBACK(compose_changed_cb), 1666 compose); 1667 fp = procmime_get_first_text_content(msginfo); 1668 if (fp == NULL) { 1669 g_warning("can't get text part"); 1670 } 1671 1672 if (fp != NULL) { 1673 gchar buf[BUFFSIZE]; 1674 gboolean prev_autowrap; 1675 GtkTextBuffer *buffer; 1676 BLOCK_WRAP(); 1677 while (fgets(buf, sizeof(buf), fp) != NULL) { 1678 strcrchomp(buf); 1679 gtk_text_buffer_insert(textbuf, &iter, buf, -1); 1680 } 1681 UNBLOCK_WRAP(); 1682 fclose(fp); 1683 } 1684 1685 compose_attach_parts(compose, msginfo); 1686 1687 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf), 1688 G_CALLBACK(compose_changed_cb), 1689 compose); 1690 1691 if (manual_headers != NULL) { 1692 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) { 1693 procheader_entries_free(manual_headers); 1694 compose->updating = FALSE; 1695 compose_destroy(compose); 1696 return NULL; 1697 } 1698 procheader_entries_free(manual_headers); 1699 } 1700 1701 gtk_widget_grab_focus(compose->text); 1702 1703 if (prefs_common.auto_exteditor) { 1704 compose_exec_ext_editor(compose); 1705 } 1706 compose->modified = FALSE; 1707 compose_set_title(compose); 1708 1709 compose->updating = FALSE; 1710 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */ 1711 SCROLL_TO_CURSOR(compose); 1712 1713 if (compose->deferred_destroy) { 1714 compose_destroy(compose); 1715 return NULL; 1716 } 1717 1718 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose); 1719 1720 return compose; 1721 } 1722 1723 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo, 1724 gboolean batch) 1725 { 1726 Compose *compose; 1727 gchar *filename; 1728 FolderItem *item; 1729 1730 cm_return_val_if_fail(msginfo != NULL, NULL); 1731 1732 if (!account) 1733 account = account_get_reply_account(msginfo, 1734 prefs_common.reply_account_autosel); 1735 cm_return_val_if_fail(account != NULL, NULL); 1736 1737 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch); 1738 1739 compose->updating = TRUE; 1740 1741 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose); 1742 compose->replyinfo = NULL; 1743 compose->fwdinfo = NULL; 1744 1745 compose_show_first_last_header(compose, TRUE); 1746 1747 gtk_widget_grab_focus(compose->header_last->entry); 1748 1749 filename = procmsg_get_message_file(msginfo); 1750 1751 if (filename == NULL) { 1752 compose->updating = FALSE; 1753 compose_destroy(compose); 1754 1755 return NULL; 1756 } 1757 1758 compose->redirect_filename = filename; 1759 1760 /* Set save folder */ 1761 item = msginfo->folder; 1762 if (item && item->prefs && item->prefs->save_copy_to_folder) { 1763 gchar *folderidentifier; 1764 1765 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE); 1766 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE); 1767 folderidentifier = folder_item_get_identifier(item); 1768 compose_set_save_to(compose, folderidentifier); 1769 g_free(folderidentifier); 1770 } 1771 1772 compose_attach_parts(compose, msginfo); 1773 1774 if (msginfo->subject) 1775 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), 1776 msginfo->subject); 1777 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE); 1778 1779 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE); 1780 1781 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE); 1782 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE); 1783 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE); 1784 1785 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", FALSE); 1786 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE); 1787 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE); 1788 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE); 1789 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE); 1790 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE); 1791 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE); 1792 1793 if (compose->toolbar->sendl_btn) 1794 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, FALSE); 1795 if (compose->toolbar->draft_btn) 1796 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE); 1797 if (compose->toolbar->insert_btn) 1798 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE); 1799 if (compose->toolbar->attach_btn) 1800 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE); 1801 if (compose->toolbar->sig_btn) 1802 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE); 1803 if (compose->toolbar->exteditor_btn) 1804 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE); 1805 if (compose->toolbar->linewrap_current_btn) 1806 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE); 1807 if (compose->toolbar->linewrap_all_btn) 1808 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE); 1809 1810 compose->modified = FALSE; 1811 compose_set_title(compose); 1812 compose->updating = FALSE; 1813 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */ 1814 SCROLL_TO_CURSOR(compose); 1815 1816 if (compose->deferred_destroy) { 1817 compose_destroy(compose); 1818 return NULL; 1819 } 1820 1821 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose); 1822 1823 return compose; 1824 } 1825 1826 const GList *compose_get_compose_list(void) 1827 { 1828 return compose_list; 1829 } 1830 1831 void compose_entry_append(Compose *compose, const gchar *address, 1832 ComposeEntryType type, ComposePrefType pref_type) 1833 { 1834 const gchar *header; 1835 gchar *cur, *begin; 1836 gboolean in_quote = FALSE; 1837 if (!address || *address == '\0') return; 1838 1839 switch (type) { 1840 case COMPOSE_CC: 1841 header = N_("Cc:"); 1842 break; 1843 case COMPOSE_BCC: 1844 header = N_("Bcc:"); 1845 break; 1846 case COMPOSE_REPLYTO: 1847 header = N_("Reply-To:"); 1848 break; 1849 case COMPOSE_FOLLOWUPTO: 1850 header = N_( "Followup-To:"); 1851 break; 1852 case COMPOSE_INREPLYTO: 1853 header = N_( "In-Reply-To:"); 1854 break; 1855 case COMPOSE_TO: 1856 default: 1857 header = N_("To:"); 1858 break; 1859 } 1860 1861 cur = begin = (gchar *)address; 1862 1863 /* we separate the line by commas, but not if we're inside a quoted 1864 * string */ 1865 while (*cur != '\0') { 1866 if (*cur == '"') 1867 in_quote = !in_quote; 1868 if (*cur == ',' && !in_quote) { 1869 gchar *tmp = g_strdup(begin); 1870 gchar *o_tmp = tmp; 1871 tmp[cur-begin]='\0'; 1872 cur++; 1873 begin = cur; 1874 while (*tmp == ' ' || *tmp == '\t') 1875 tmp++; 1876 compose_add_header_entry(compose, header, tmp, pref_type); 1877 compose_entry_indicate(compose, tmp); 1878 g_free(o_tmp); 1879 continue; 1880 } 1881 cur++; 1882 } 1883 if (begin < cur) { 1884 gchar *tmp = g_strdup(begin); 1885 gchar *o_tmp = tmp; 1886 tmp[cur-begin]='\0'; 1887 while (*tmp == ' ' || *tmp == '\t') 1888 tmp++; 1889 compose_add_header_entry(compose, header, tmp, pref_type); 1890 compose_entry_indicate(compose, tmp); 1891 g_free(o_tmp); 1892 } 1893 } 1894 1895 static void compose_entry_indicate(Compose *compose, const gchar *mailto) 1896 { 1897 GSList *h_list; 1898 GtkEntry *entry; 1899 GdkColor color; 1900 1901 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) { 1902 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry); 1903 if (gtk_entry_get_text(entry) && 1904 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) { 1905 /* Modify background color */ 1906 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_bgcolor, color); 1907 gtk_widget_modify_base( 1908 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry), 1909 GTK_STATE_NORMAL, &color); 1910 1911 /* Modify foreground color */ 1912 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_color, color); 1913 gtk_widget_modify_text( 1914 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry), 1915 GTK_STATE_NORMAL, &color); 1916 } 1917 } 1918 } 1919 1920 void compose_toolbar_cb(gint action, gpointer data) 1921 { 1922 ToolbarItem *toolbar_item = (ToolbarItem*)data; 1923 Compose *compose = (Compose*)toolbar_item->parent; 1924 1925 cm_return_if_fail(compose != NULL); 1926 1927 switch(action) { 1928 case A_SEND: 1929 compose_send_cb(NULL, compose); 1930 break; 1931 case A_SEND_LATER: 1932 compose_send_later_cb(NULL, compose); 1933 break; 1934 case A_DRAFT: 1935 compose_draft(compose, COMPOSE_QUIT_EDITING); 1936 break; 1937 case A_INSERT: 1938 compose_insert_file_cb(NULL, compose); 1939 break; 1940 case A_ATTACH: 1941 compose_attach_cb(NULL, compose); 1942 break; 1943 case A_EXTEDITOR: 1944 compose_ext_editor_cb(NULL, compose); 1945 break; 1946 case A_LINEWRAP_CURRENT: 1947 compose_beautify_paragraph(compose, NULL, TRUE); 1948 break; 1949 case A_LINEWRAP_ALL: 1950 compose_wrap_all_full(compose, TRUE); 1951 break; 1952 case A_ADDRBOOK: 1953 compose_address_cb(NULL, compose); 1954 break; 1955 case A_PRIVACY_SIGN: 1956 break; 1957 case A_PRIVACY_ENCRYPT: 1958 break; 1959 default: 1960 break; 1961 } 1962 } 1963 1964 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type) 1965 { 1966 gchar *to = NULL; 1967 gchar *cc = NULL; 1968 gchar *bcc = NULL; 1969 gchar *subject = NULL; 1970 gchar *body = NULL; 1971 gchar *temp = NULL; 1972 gsize len = 0; 1973 gchar **attach = NULL; 1974 gchar *inreplyto = NULL; 1975 MailField mfield = NO_FIELD_PRESENT; 1976 1977 /* get mailto parts but skip from */ 1978 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto); 1979 1980 if (to) { 1981 compose_entry_append(compose, to, to_type, PREF_MAILTO); 1982 mfield = TO_FIELD_PRESENT; 1983 } 1984 if (cc) 1985 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO); 1986 if (bcc) 1987 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO); 1988 if (subject) { 1989 if (!g_utf8_validate (subject, -1, NULL)) { 1990 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL); 1991 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp); 1992 g_free(temp); 1993 } else { 1994 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject); 1995 } 1996 mfield = SUBJECT_FIELD_PRESENT; 1997 } 1998 if (body) { 1999 GtkTextView *text = GTK_TEXT_VIEW(compose->text); 2000 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text); 2001 GtkTextMark *mark; 2002 GtkTextIter iter; 2003 gboolean prev_autowrap = compose->autowrap; 2004 2005 compose->autowrap = FALSE; 2006 2007 mark = gtk_text_buffer_get_insert(buffer); 2008 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark); 2009 2010 if (!g_utf8_validate (body, -1, NULL)) { 2011 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL); 2012 gtk_text_buffer_insert(buffer, &iter, temp, -1); 2013 g_free(temp); 2014 } else { 2015 gtk_text_buffer_insert(buffer, &iter, body, -1); 2016 } 2017 gtk_text_buffer_insert(buffer, &iter, "\n", 1); 2018 2019 compose->autowrap = prev_autowrap; 2020 if (compose->autowrap) 2021 compose_wrap_all(compose); 2022 mfield = BODY_FIELD_PRESENT; 2023 } 2024 2025 if (attach) { 2026 gint i = 0; 2027 gchar *warn_files = NULL; 2028 while (attach[i] != NULL) { 2029 gchar *utf8_filename = conv_filename_to_utf8(attach[i]); 2030 if (utf8_filename) { 2031 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) { 2032 gchar *tmp = g_strdup_printf("%s%s\n", 2033 warn_files?warn_files:"", 2034 utf8_filename); 2035 g_free(warn_files); 2036 warn_files = tmp; 2037 } 2038 g_free(utf8_filename); 2039 } else { 2040 alertpanel_error(_("Couldn't attach a file (charset conversion failed).")); 2041 } 2042 i++; 2043 } 2044 if (warn_files) { 2045 alertpanel_notice("Attachments:\n%s", warn_files); 2046 g_free(warn_files); 2047 } 2048 } 2049 if (inreplyto) 2050 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO); 2051 2052 g_free(to); 2053 g_free(cc); 2054 g_free(bcc); 2055 g_free(subject); 2056 g_free(body); 2057 g_strfreev(attach); 2058 g_free(inreplyto); 2059 2060 return mfield; 2061 } 2062 2063 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo) 2064 { 2065 static HeaderEntry hentry[] = { 2066 {"Reply-To:", NULL, TRUE }, 2067 {"Cc:", NULL, TRUE }, 2068 {"References:", NULL, FALSE }, 2069 {"Bcc:", NULL, TRUE }, 2070 {"Newsgroups:", NULL, TRUE }, 2071 {"Followup-To:", NULL, TRUE }, 2072 {"List-Post:", NULL, FALSE }, 2073 {NULL, NULL, FALSE } 2074 }; 2075 2076 enum 2077 { 2078 H_REPLY_TO = 0, 2079 H_CC = 1, 2080 H_REFERENCES = 2, 2081 H_BCC = 3, 2082 H_NEWSGROUPS = 4, 2083 H_FOLLOWUP_TO = 5, 2084 H_LIST_POST = 6, 2085 }; 2086 2087 FILE *fp; 2088 2089 cm_return_val_if_fail(msginfo != NULL, -1); 2090 2091 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1; 2092 procheader_get_header_fields(fp, hentry); 2093 fclose(fp); 2094 2095 if (hentry[H_REPLY_TO].body != NULL) { 2096 if (hentry[H_REPLY_TO].body[0] != '\0') { 2097 compose->replyto = 2098 conv_unmime_header(hentry[H_REPLY_TO].body, 2099 NULL, TRUE); 2100 } 2101 g_free(hentry[H_REPLY_TO].body); 2102 hentry[H_REPLY_TO].body = NULL; 2103 } 2104 if (hentry[H_CC].body != NULL) { 2105 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE); 2106 g_free(hentry[H_CC].body); 2107 hentry[H_CC].body = NULL; 2108 } 2109 if (hentry[H_REFERENCES].body != NULL) { 2110 if (compose->mode == COMPOSE_REEDIT) 2111 compose->references = hentry[H_REFERENCES].body; 2112 else { 2113 compose->references = compose_parse_references 2114 (hentry[H_REFERENCES].body, msginfo->msgid); 2115 g_free(hentry[H_REFERENCES].body); 2116 } 2117 hentry[H_REFERENCES].body = NULL; 2118 } 2119 if (hentry[H_BCC].body != NULL) { 2120 if (compose->mode == COMPOSE_REEDIT) 2121 compose->bcc = 2122 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE); 2123 g_free(hentry[H_BCC].body); 2124 hentry[H_BCC].body = NULL; 2125 } 2126 if (hentry[H_FOLLOWUP_TO].body != NULL) { 2127 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') { 2128 compose->followup_to = 2129 conv_unmime_header(hentry[H_FOLLOWUP_TO].body, 2130 NULL, TRUE); 2131 } 2132 g_free(hentry[H_FOLLOWUP_TO].body); 2133 hentry[H_FOLLOWUP_TO].body = NULL; 2134 } 2135 if (hentry[H_LIST_POST].body != NULL) { 2136 gchar *to = NULL, *start = NULL; 2137 2138 extract_address(hentry[H_LIST_POST].body); 2139 if (hentry[H_LIST_POST].body[0] != '\0') { 2140 start = strstr(hentry[H_LIST_POST].body, "mailto:"); 2141 2142 scan_mailto_url(start ? start : hentry[H_LIST_POST].body, 2143 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL); 2144 2145 if (to) { 2146 g_free(compose->ml_post); 2147 compose->ml_post = to; 2148 } 2149 } 2150 g_free(hentry[H_LIST_POST].body); 2151 hentry[H_LIST_POST].body = NULL; 2152 } 2153 2154 if (compose->mode == COMPOSE_REEDIT) { 2155 if (msginfo->inreplyto && *msginfo->inreplyto) 2156 compose->inreplyto = g_strdup(msginfo->inreplyto); 2157 2158 if (msginfo->msgid && *msginfo->msgid && 2159 compose->folder != NULL && 2160 compose->folder->stype == F_DRAFT) 2161 compose->msgid = g_strdup(msginfo->msgid); 2162 } else { 2163 if (msginfo->msgid && *msginfo->msgid && 2164 (compose->mode != COMPOSE_FORWARD && 2165 compose->mode != COMPOSE_FORWARD_INLINE && 2166 compose->mode != COMPOSE_FORWARD_AS_ATTACH)) 2167 compose->inreplyto = g_strdup(msginfo->msgid); 2168 2169 if (!compose->references) { 2170 if (msginfo->msgid && *msginfo->msgid) { 2171 if (msginfo->inreplyto && *msginfo->inreplyto) 2172 compose->references = 2173 g_strdup_printf("<%s>\n\t<%s>", 2174 msginfo->inreplyto, 2175 msginfo->msgid); 2176 else 2177 compose->references = 2178 g_strconcat("<", msginfo->msgid, ">", 2179 NULL); 2180 } else if (msginfo->inreplyto && *msginfo->inreplyto) { 2181 compose->references = 2182 g_strconcat("<", msginfo->inreplyto, ">", 2183 NULL); 2184 } 2185 } 2186 } 2187 2188 return 0; 2189 } 2190 2191 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries) 2192 { 2193 FILE *fp; 2194 HeaderEntry *he; 2195 2196 cm_return_val_if_fail(msginfo != NULL, -1); 2197 2198 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1; 2199 procheader_get_header_fields(fp, entries); 2200 fclose(fp); 2201 2202 he = entries; 2203 while (he != NULL && he->name != NULL) { 2204 GtkTreeIter iter; 2205 GtkListStore *model = NULL; 2206 2207 debug_print("Adding manual header: %s with value %s\n", he->name, he->body); 2208 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo))); 2209 COMBOBOX_ADD(model, he->name, COMPOSE_TO); 2210 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter); 2211 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body); 2212 ++he; 2213 } 2214 2215 return 0; 2216 } 2217 2218 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid) 2219 { 2220 GSList *ref_id_list, *cur; 2221 GString *new_ref; 2222 2223 ref_id_list = references_list_append(NULL, ref); 2224 if (!ref_id_list) return NULL; 2225 if (msgid && *msgid) 2226 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid)); 2227 2228 for (;;) { 2229 gint len = 0; 2230 2231 for (cur = ref_id_list; cur != NULL; cur = cur->next) 2232 /* "<" + Message-ID + ">" + CR+LF+TAB */ 2233 len += strlen((gchar *)cur->data) + 5; 2234 2235 if (len > MAX_REFERENCES_LEN) { 2236 /* remove second message-ID */ 2237 if (ref_id_list && ref_id_list->next && 2238 ref_id_list->next->next) { 2239 g_free(ref_id_list->next->data); 2240 ref_id_list = g_slist_remove 2241 (ref_id_list, ref_id_list->next->data); 2242 } else { 2243 slist_free_strings_full(ref_id_list); 2244 return NULL; 2245 } 2246 } else 2247 break; 2248 } 2249 2250 new_ref = g_string_new(""); 2251 for (cur = ref_id_list; cur != NULL; cur = cur->next) { 2252 if (new_ref->len > 0) 2253 g_string_append(new_ref, "\n\t"); 2254 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data); 2255 } 2256 2257 slist_free_strings_full(ref_id_list); 2258 2259 return g_string_free(new_ref, FALSE); 2260 } 2261 2262 /* if ml_post is of type addr@host and from is of type 2263 * addr-anything@host, return TRUE 2264 */ 2265 static gboolean is_subscription(const gchar *ml_post, const gchar *from) 2266 { 2267 gchar *left_ml = NULL; 2268 gchar *right_ml = NULL; 2269 gchar *left_from = NULL; 2270 gchar *right_from = NULL; 2271 gboolean result = FALSE; 2272 2273 if (!ml_post || !from) 2274 return FALSE; 2275 2276 left_ml = g_strdup(ml_post); 2277 if (strstr(left_ml, "@")) { 2278 right_ml = strstr(left_ml, "@")+1; 2279 *(strstr(left_ml, "@")) = '\0'; 2280 } 2281 2282 left_from = g_strdup(from); 2283 if (strstr(left_from, "@")) { 2284 right_from = strstr(left_from, "@")+1; 2285 *(strstr(left_from, "@")) = '\0'; 2286 } 2287 2288 if (right_ml && right_from 2289 && !strncmp(left_from, left_ml, strlen(left_ml)) 2290 && !strcmp(right_from, right_ml)) { 2291 result = TRUE; 2292 } 2293 g_free(left_ml); 2294 g_free(left_from); 2295 2296 return result; 2297 } 2298 2299 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder, 2300 gboolean respect_default_to) 2301 { 2302 if (!compose) 2303 return; 2304 if (!folder || !folder->prefs) 2305 return; 2306 2307 if (folder->prefs->enable_default_from) { 2308 gtk_entry_set_text(GTK_ENTRY(compose->from_name), folder->prefs->default_from); 2309 compose_entry_indicate(compose, folder->prefs->default_from); 2310 } 2311 if (respect_default_to && folder->prefs->enable_default_to) { 2312 compose_entry_append(compose, folder->prefs->default_to, 2313 COMPOSE_TO, PREF_FOLDER); 2314 compose_entry_indicate(compose, folder->prefs->default_to); 2315 } 2316 if (folder->prefs->enable_default_cc) { 2317 compose_entry_append(compose, folder->prefs->default_cc, 2318 COMPOSE_CC, PREF_FOLDER); 2319 compose_entry_indicate(compose, folder->prefs->default_cc); 2320 } 2321 if (folder->prefs->enable_default_bcc) { 2322 compose_entry_append(compose, folder->prefs->default_bcc, 2323 COMPOSE_BCC, PREF_FOLDER); 2324 compose_entry_indicate(compose, folder->prefs->default_bcc); 2325 } 2326 if (folder->prefs->enable_default_replyto) { 2327 compose_entry_append(compose, folder->prefs->default_replyto, 2328 COMPOSE_REPLYTO, PREF_FOLDER); 2329 compose_entry_indicate(compose, folder->prefs->default_replyto); 2330 } 2331 } 2332 2333 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo) 2334 { 2335 gchar *buf, *buf2; 2336 gchar *p; 2337 2338 if (!compose || !msginfo) 2339 return; 2340 2341 if (msginfo->subject && *msginfo->subject) { 2342 buf = p = g_strdup(msginfo->subject); 2343 p += subject_get_prefix_length(p); 2344 memmove(buf, p, strlen(p) + 1); 2345 2346 buf2 = g_strdup_printf("Re: %s", buf); 2347 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2); 2348 2349 g_free(buf2); 2350 g_free(buf); 2351 } else 2352 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: "); 2353 } 2354 2355 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo, 2356 gboolean to_all, gboolean to_ml, 2357 gboolean to_sender, 2358 gboolean followup_and_reply_to) 2359 { 2360 GSList *cc_list = NULL; 2361 GSList *cur; 2362 gchar *from = NULL; 2363 gchar *replyto = NULL; 2364 gchar *ac_email = NULL; 2365 2366 gboolean reply_to_ml = FALSE; 2367 gboolean default_reply_to = FALSE; 2368 2369 cm_return_if_fail(compose->account != NULL); 2370 cm_return_if_fail(msginfo != NULL); 2371 2372 reply_to_ml = to_ml && compose->ml_post; 2373 2374 default_reply_to = msginfo->folder && 2375 msginfo->folder->prefs->enable_default_reply_to; 2376 2377 if (1) { 2378 compose_set_folder_prefs(compose, msginfo->folder, FALSE); 2379 2380 if (reply_to_ml && !default_reply_to) { 2381 2382 gboolean is_subscr = is_subscription(compose->ml_post, 2383 msginfo->from); 2384 if (!is_subscr) { 2385 /* normal answer to ml post with a reply-to */ 2386 compose_entry_append(compose, 2387 compose->ml_post, 2388 COMPOSE_TO, PREF_ML); 2389 if (compose->replyto) 2390 compose_entry_append(compose, 2391 compose->replyto, 2392 COMPOSE_CC, PREF_ML); 2393 } else { 2394 /* answer to subscription confirmation */ 2395 if (compose->replyto) 2396 compose_entry_append(compose, 2397 compose->replyto, 2398 COMPOSE_TO, PREF_ML); 2399 else if (msginfo->from) 2400 compose_entry_append(compose, 2401 msginfo->from, 2402 COMPOSE_TO, PREF_ML); 2403 } 2404 } 2405 else if (!(to_all || to_sender) && default_reply_to) { 2406 compose_entry_append(compose, 2407 msginfo->folder->prefs->default_reply_to, 2408 COMPOSE_TO, PREF_FOLDER); 2409 compose_entry_indicate(compose, 2410 msginfo->folder->prefs->default_reply_to); 2411 } else { 2412 gchar *tmp1 = NULL; 2413 if (!msginfo->from) 2414 return; 2415 if (to_sender) 2416 compose_entry_append(compose, msginfo->from, 2417 COMPOSE_TO, PREF_NONE); 2418 else if (to_all) { 2419 Xstrdup_a(tmp1, msginfo->from, return); 2420 extract_address(tmp1); 2421 compose_entry_append(compose, 2422 (!account_find_from_address(tmp1)) 2423 ? msginfo->from : 2424 msginfo->to, 2425 COMPOSE_TO, PREF_NONE); 2426 if (compose->replyto) 2427 compose_entry_append(compose, 2428 compose->replyto, 2429 COMPOSE_CC, PREF_NONE); 2430 } else { 2431 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) && 2432 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) && 2433 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) { 2434 if (compose->replyto) { 2435 compose_entry_append(compose, 2436 compose->replyto, 2437 COMPOSE_TO, PREF_NONE); 2438 } else { 2439 compose_entry_append(compose, 2440 msginfo->from ? msginfo->from : "", 2441 COMPOSE_TO, PREF_NONE); 2442 } 2443 } else { 2444 /* replying to own mail, use original recp */ 2445 compose_entry_append(compose, 2446 msginfo->to ? msginfo->to : "", 2447 COMPOSE_TO, PREF_NONE); 2448 compose_entry_append(compose, 2449 msginfo->cc ? msginfo->cc : "", 2450 COMPOSE_CC, PREF_NONE); 2451 } 2452 } 2453 } 2454 } 2455 compose_reply_set_subject(compose, msginfo); 2456 2457 if (to_ml && compose->ml_post) return; 2458 if (!to_all) return; 2459 2460 if (compose->replyto) { 2461 Xstrdup_a(replyto, compose->replyto, return); 2462 extract_address(replyto); 2463 } 2464 if (msginfo->from) { 2465 Xstrdup_a(from, msginfo->from, return); 2466 extract_address(from); 2467 } 2468 2469 if (replyto && from) 2470 cc_list = address_list_append_with_comments(cc_list, from); 2471 if (to_all && msginfo->folder && 2472 msginfo->folder->prefs->enable_default_reply_to) 2473 cc_list = address_list_append_with_comments(cc_list, 2474 msginfo->folder->prefs->default_reply_to); 2475 cc_list = address_list_append_with_comments(cc_list, msginfo->to); 2476 cc_list = address_list_append_with_comments(cc_list, compose->cc); 2477 2478 ac_email = g_utf8_strdown(compose->account->address, -1); 2479 2480 if (cc_list) { 2481 for (cur = cc_list; cur != NULL; cur = cur->next) { 2482 gchar *addr = g_utf8_strdown(cur->data, -1); 2483 extract_address(addr); 2484 2485 if (strcmp(ac_email, addr)) 2486 compose_entry_append(compose, (gchar *)cur->data, 2487 COMPOSE_CC, PREF_NONE); 2488 else 2489 debug_print("Cc address same as write account's, ignoring\n"); 2490 2491 g_free(addr); 2492 } 2493 2494 slist_free_strings_full(cc_list); 2495 } 2496 2497 g_free(ac_email); 2498 } 2499 2500 #define SET_ENTRY(entry, str) \ 2501 { \ 2502 if (str && *str) \ 2503 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \ 2504 } 2505 2506 #define SET_ADDRESS(type, str) \ 2507 { \ 2508 if (str && *str) \ 2509 compose_entry_append(compose, str, type, PREF_NONE); \ 2510 } 2511 2512 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo) 2513 { 2514 cm_return_if_fail(msginfo != NULL); 2515 2516 SET_ENTRY(subject_entry, msginfo->subject); 2517 SET_ENTRY(from_name, msginfo->from); 2518 SET_ADDRESS(COMPOSE_TO, msginfo->to); 2519 SET_ADDRESS(COMPOSE_CC, compose->cc); 2520 SET_ADDRESS(COMPOSE_BCC, compose->bcc); 2521 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto); 2522 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to); 2523 2524 compose_show_first_last_header(compose, TRUE); 2525 } 2526 2527 #undef SET_ENTRY 2528 #undef SET_ADDRESS 2529 2530 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file) 2531 { 2532 GtkTextView *text; 2533 GtkTextBuffer *buffer; 2534 GtkTextMark *mark; 2535 GtkTextIter iter; 2536 const gchar *cur_encoding; 2537 gchar buf[BUFFSIZE]; 2538 gint len; 2539 FILE *fp; 2540 gboolean prev_autowrap; 2541 GStatBuf file_stat; 2542 int ret; 2543 goffset size; 2544 GString *file_contents = NULL; 2545 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS; 2546 2547 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE); 2548 2549 /* get the size of the file we are about to insert */ 2550 2551 ret = g_stat(file, &file_stat); 2552 if (ret != 0) { 2553 gchar *shortfile = g_path_get_basename(file); 2554 alertpanel_error(_("Could not get size of file '%s'."), shortfile); 2555 g_free(shortfile); 2556 return COMPOSE_INSERT_NO_FILE; 2557 } else if (prefs_common.warn_large_insert == TRUE) { 2558 size = file_stat.st_size; 2559 2560 /* ask user for confirmation if the file is large */ 2561 if (prefs_common.warn_large_insert_size < 0 || 2562 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) { 2563 AlertValue aval; 2564 gchar *msg; 2565 2566 msg = g_strdup_printf(_("You are about to insert a file of %s " 2567 "in the message body. Are you sure you want to do that?"), 2568 to_human_readable(size)); 2569 aval = alertpanel_full(_("Are you sure?"), msg, NULL, _("_Cancel"), 2570 NULL, _("_Insert"), NULL, NULL, ALERTFOCUS_SECOND, TRUE, 2571 NULL, ALERT_QUESTION); 2572 g_free(msg); 2573 2574 /* do we ask for confirmation next time? */ 2575 if (aval & G_ALERTDISABLE) { 2576 /* no confirmation next time, disable feature in preferences */ 2577 aval &= ~G_ALERTDISABLE; 2578 prefs_common.warn_large_insert = FALSE; 2579 } 2580 2581 /* abort file insertion if user canceled action */ 2582 if (aval != G_ALERTALTERNATE) { 2583 return COMPOSE_INSERT_NO_FILE; 2584 } 2585 } 2586 } 2587 2588 2589 if ((fp = g_fopen(file, "rb")) == NULL) { 2590 FILE_OP_ERROR(file, "g_fopen"); 2591 return COMPOSE_INSERT_READ_ERROR; 2592 } 2593 2594 prev_autowrap = compose->autowrap; 2595 compose->autowrap = FALSE; 2596 2597 text = GTK_TEXT_VIEW(compose->text); 2598 buffer = gtk_text_view_get_buffer(text); 2599 mark = gtk_text_buffer_get_insert(buffer); 2600 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark); 2601 2602 g_signal_handlers_block_by_func(G_OBJECT(buffer), 2603 G_CALLBACK(text_inserted), 2604 compose); 2605 2606 cur_encoding = conv_get_locale_charset_str_no_utf8(); 2607 2608 file_contents = g_string_new(""); 2609 while (fgets(buf, sizeof(buf), fp) != NULL) { 2610 gchar *str; 2611 2612 if (g_utf8_validate(buf, -1, NULL) == TRUE) 2613 str = g_strdup(buf); 2614 else { 2615 codeconv_set_strict(TRUE); 2616 str = conv_codeset_strdup 2617 (buf, cur_encoding, CS_INTERNAL); 2618 codeconv_set_strict(FALSE); 2619 2620 if (!str) { 2621 result = COMPOSE_INSERT_INVALID_CHARACTER; 2622 break; 2623 } 2624 } 2625 if (!str) continue; 2626 2627 /* strip <CR> if DOS/Windows file, 2628 replace <CR> with <LF> if Macintosh file. */ 2629 strcrchomp(str); 2630 len = strlen(str); 2631 if (len > 0 && str[len - 1] != '\n') { 2632 while (--len >= 0) 2633 if (str[len] == '\r') str[len] = '\n'; 2634 } 2635 2636 file_contents = g_string_append(file_contents, str); 2637 g_free(str); 2638 } 2639 2640 if (result == COMPOSE_INSERT_SUCCESS) { 2641 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1); 2642 2643 compose_changed_cb(NULL, compose); 2644 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), 2645 G_CALLBACK(text_inserted), 2646 compose); 2647 compose->autowrap = prev_autowrap; 2648 if (compose->autowrap) 2649 compose_wrap_all(compose); 2650 } else { 2651 gchar *filename = g_path_get_basename(file); 2652 debug_print("Can't insert file '%s' (invalid character)\n", filename); 2653 g_free(filename); 2654 } 2655 2656 g_string_free(file_contents, TRUE); 2657 fclose(fp); 2658 2659 return result; 2660 } 2661 2662 static gboolean compose_attach_append(Compose *compose, const gchar *file, 2663 const gchar *filename, 2664 const gchar *content_type, 2665 const gchar *charset) 2666 { 2667 AttachInfo *ainfo; 2668 GtkTreeIter iter; 2669 FILE *fp; 2670 off_t size; 2671 GAuto *auto_ainfo; 2672 gchar *size_text; 2673 GtkListStore *store; 2674 gchar *name; 2675 gboolean has_binary = FALSE; 2676 2677 if (!is_file_exist(file)) { 2678 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL); 2679 gboolean result = FALSE; 2680 if (file_from_uri && is_file_exist(file_from_uri)) { 2681 result = compose_attach_append( 2682 compose, file_from_uri, 2683 filename, content_type, 2684 charset); 2685 } 2686 g_free(file_from_uri); 2687 if (result) 2688 return TRUE; 2689 alertpanel_error("File %s doesn't exist or permission denied\n", filename); 2690 return FALSE; 2691 } 2692 if ((size = get_file_size(file)) < 0) { 2693 alertpanel_error("Can't get file size of %s\n", filename); 2694 return FALSE; 2695 } 2696 2697 /* In batch mode, we allow 0-length files to be attached no questions asked */ 2698 if (size == 0 && !compose->batch) { 2699 gchar * msg = g_strdup_printf(_("File %s is empty."), filename); 2700 AlertValue aval = alertpanel_full(_("Empty file"), msg, 2701 NULL, _("_Cancel"), NULL, _("_Attach anyway"), 2702 NULL, NULL, ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING); 2703 g_free(msg); 2704 2705 if (aval != G_ALERTALTERNATE) { 2706 return FALSE; 2707 } 2708 } 2709 if ((fp = g_fopen(file, "rb")) == NULL) { 2710 alertpanel_error(_("Can't read %s."), filename); 2711 return FALSE; 2712 } 2713 fclose(fp); 2714 2715 ainfo = g_new0(AttachInfo, 1); 2716 auto_ainfo = g_auto_pointer_new_with_free 2717 (ainfo, (GFreeFunc) compose_attach_info_free); 2718 ainfo->file = g_strdup(file); 2719 2720 if (content_type) { 2721 ainfo->content_type = g_strdup(content_type); 2722 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) { 2723 MsgInfo *msginfo; 2724 MsgFlags flags = {0, 0}; 2725 2726 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT) 2727 ainfo->encoding = ENC_7BIT; 2728 else 2729 ainfo->encoding = ENC_8BIT; 2730 2731 msginfo = procheader_parse_file(file, flags, FALSE, FALSE); 2732 if (msginfo && msginfo->subject) 2733 name = g_strdup(msginfo->subject); 2734 else 2735 name = g_path_get_basename(filename ? filename : file); 2736 2737 ainfo->name = g_strdup_printf(_("Message: %s"), name); 2738 2739 procmsg_msginfo_free(&msginfo); 2740 } else { 2741 if (!g_ascii_strncasecmp(content_type, "text/", 5)) { 2742 ainfo->charset = g_strdup(charset); 2743 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary); 2744 } else { 2745 ainfo->encoding = ENC_BASE64; 2746 } 2747 name = g_path_get_basename(filename ? filename : file); 2748 ainfo->name = g_strdup(name); 2749 } 2750 g_free(name); 2751 } else { 2752 ainfo->content_type = procmime_get_mime_type(file); 2753 if (!ainfo->content_type) { 2754 ainfo->content_type = 2755 g_strdup("application/octet-stream"); 2756 ainfo->encoding = ENC_BASE64; 2757 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5)) 2758 ainfo->encoding = 2759 procmime_get_encoding_for_text_file(file, &has_binary); 2760 else 2761 ainfo->encoding = ENC_BASE64; 2762 name = g_path_get_basename(filename ? filename : file); 2763 ainfo->name = g_strdup(name); 2764 g_free(name); 2765 } 2766 2767 if (ainfo->name != NULL 2768 && !strcmp(ainfo->name, ".")) { 2769 g_free(ainfo->name); 2770 ainfo->name = NULL; 2771 } 2772 2773 if (!strcmp(ainfo->content_type, "unknown") || has_binary) { 2774 g_free(ainfo->content_type); 2775 ainfo->content_type = g_strdup("application/octet-stream"); 2776 g_free(ainfo->charset); 2777 ainfo->charset = NULL; 2778 } 2779 2780 ainfo->size = (goffset)size; 2781 size_text = to_human_readable((goffset)size); 2782 2783 store = GTK_LIST_STORE(gtk_tree_view_get_model 2784 (GTK_TREE_VIEW(compose->attach_clist))); 2785 2786 gtk_list_store_append(store, &iter); 2787 gtk_list_store_set(store, &iter, 2788 COL_MIMETYPE, ainfo->content_type, 2789 COL_SIZE, size_text, 2790 COL_NAME, ainfo->name, 2791 COL_CHARSET, ainfo->charset, 2792 COL_DATA, ainfo, 2793 COL_AUTODATA, auto_ainfo, 2794 -1); 2795 2796 g_auto_pointer_free(auto_ainfo); 2797 compose_attach_update_label(compose); 2798 return TRUE; 2799 } 2800 2801 #define NEXT_PART_NOT_CHILD(info) \ 2802 { \ 2803 node = info->node; \ 2804 while (node->children) \ 2805 node = g_node_last_child(node); \ 2806 info = procmime_mimeinfo_next((MimeInfo *)node->data); \ 2807 } 2808 2809 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo) 2810 { 2811 MimeInfo *mimeinfo; 2812 MimeInfo *child; 2813 MimeInfo *firsttext = NULL; 2814 GNode *node; 2815 gchar *outfile; 2816 const gchar *partname = NULL; 2817 2818 mimeinfo = procmime_scan_message(msginfo); 2819 if (!mimeinfo) return; 2820 2821 if (mimeinfo->node->children == NULL) { 2822 procmime_mimeinfo_free_all(&mimeinfo); 2823 return; 2824 } 2825 2826 /* find first content part */ 2827 child = (MimeInfo *) mimeinfo->node->children->data; 2828 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART)) 2829 child = (MimeInfo *)child->node->children->data; 2830 2831 if (child) { 2832 if (child->type == MIMETYPE_TEXT) { 2833 firsttext = child; 2834 debug_print("First text part found\n"); 2835 } 2836 } 2837 child = (MimeInfo *) mimeinfo->node->children->data; 2838 while (child != NULL) { 2839 gint err; 2840 2841 if (child->type == MIMETYPE_MULTIPART) { 2842 /* get the actual content */ 2843 child = procmime_mimeinfo_next(child); 2844 continue; 2845 } 2846 2847 if (child == firsttext) { 2848 child = procmime_mimeinfo_next(child); 2849 continue; 2850 } 2851 2852 outfile = procmime_get_tmp_file_name(child); 2853 if ((err = procmime_get_part(outfile, child)) < 0) 2854 g_warning("can't get the part of multipart message. (%s)", g_strerror(-err)); 2855 g_free(outfile); 2856 NEXT_PART_NOT_CHILD(child); 2857 } 2858 procmime_mimeinfo_free_all(&mimeinfo); 2859 } 2860 2861 #undef NEXT_PART_NOT_CHILD 2862 2863 typedef enum { 2864 WAIT_FOR_INDENT_CHAR, 2865 WAIT_FOR_INDENT_CHAR_OR_SPACE, 2866 } IndentState; 2867 2868 /* return indent length, we allow: 2869 indent characters followed by indent characters or spaces/tabs, 2870 alphabets and numbers immediately followed by indent characters, 2871 and the repeating sequences of the above 2872 If quote ends with multiple spaces, only the first one is included. */ 2873 static gchar *compose_get_quote_str(GtkTextBuffer *buffer, 2874 const GtkTextIter *start, gint *len) 2875 { 2876 GtkTextIter iter = *start; 2877 gunichar wc; 2878 gchar ch[6]; 2879 gint clen; 2880 IndentState state = WAIT_FOR_INDENT_CHAR; 2881 gboolean is_space; 2882 gboolean is_indent; 2883 gint alnum_count = 0; 2884 gint space_count = 0; 2885 gint quote_len = 0; 2886 2887 if (prefs_common.quote_chars == NULL) { 2888 return 0 ; 2889 } 2890 2891 while (!gtk_text_iter_ends_line(&iter)) { 2892 wc = gtk_text_iter_get_char(&iter); 2893 if (g_unichar_iswide(wc)) 2894 break; 2895 clen = g_unichar_to_utf8(wc, ch); 2896 if (clen != 1) 2897 break; 2898 2899 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE; 2900 is_space = g_unichar_isspace(wc); 2901 2902 if (state == WAIT_FOR_INDENT_CHAR) { 2903 if (!is_indent && !g_unichar_isalnum(wc)) 2904 break; 2905 if (is_indent) { 2906 quote_len += alnum_count + space_count + 1; 2907 alnum_count = space_count = 0; 2908 state = WAIT_FOR_INDENT_CHAR_OR_SPACE; 2909 } else 2910 alnum_count++; 2911 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) { 2912 if (!is_indent && !is_space && !g_unichar_isalnum(wc)) 2913 break; 2914 if (is_space) 2915 space_count++; 2916 else if (is_indent) { 2917 quote_len += alnum_count + space_count + 1; 2918 alnum_count = space_count = 0; 2919 } else { 2920 alnum_count++; 2921 state = WAIT_FOR_INDENT_CHAR; 2922 } 2923 } 2924 2925 gtk_text_iter_forward_char(&iter); 2926 } 2927 2928 if (quote_len > 0 && space_count > 0) 2929 quote_len++; 2930 2931 if (len) 2932 *len = quote_len; 2933 2934 if (quote_len > 0) { 2935 iter = *start; 2936 gtk_text_iter_forward_chars(&iter, quote_len); 2937 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE); 2938 } 2939 2940 return NULL; 2941 } 2942 2943 /* return >0 if the line is itemized */ 2944 static int compose_itemized_length(GtkTextBuffer *buffer, 2945 const GtkTextIter *start) 2946 { 2947 GtkTextIter iter = *start; 2948 gunichar wc; 2949 gchar ch[6]; 2950 gint clen; 2951 gint len = 0; 2952 if (gtk_text_iter_ends_line(&iter)) 2953 return 0; 2954 2955 while (1) { 2956 len++; 2957 wc = gtk_text_iter_get_char(&iter); 2958 if (!g_unichar_isspace(wc)) 2959 break; 2960 gtk_text_iter_forward_char(&iter); 2961 if (gtk_text_iter_ends_line(&iter)) 2962 return 0; 2963 } 2964 2965 clen = g_unichar_to_utf8(wc, ch); 2966 if (!((clen == 1 && strchr("*-+", ch[0])) || 2967 (clen == 3 && ( 2968 wc == 0x2022 || /* BULLET */ 2969 wc == 0x2023 || /* TRIANGULAR BULLET */ 2970 wc == 0x2043 || /* HYPHEN BULLET */ 2971 wc == 0x204c || /* BLACK LEFTWARDS BULLET */ 2972 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */ 2973 wc == 0x2219 || /* BULLET OPERATOR */ 2974 wc == 0x25d8 || /* INVERSE BULLET */ 2975 wc == 0x25e6 || /* WHITE BULLET */ 2976 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */ 2977 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */ 2978 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */ 2979 wc == 0x29be || /* CIRCLED WHITE BULLET */ 2980 wc == 0x29bf /* CIRCLED BULLET */ 2981 )))) 2982 return 0; 2983 2984 gtk_text_iter_forward_char(&iter); 2985 if (gtk_text_iter_ends_line(&iter)) 2986 return 0; 2987 wc = gtk_text_iter_get_char(&iter); 2988 if (g_unichar_isspace(wc)) { 2989 return len+1; 2990 } 2991 return 0; 2992 } 2993 2994 /* return the string at the start of the itemization */ 2995 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer, 2996 const GtkTextIter *start) 2997 { 2998 GtkTextIter iter = *start; 2999 gunichar wc; 3000 GString *item_chars = g_string_new(""); 3001 3002 if (gtk_text_iter_ends_line(&iter)) { 3003 g_string_free(item_chars, TRUE); 3004 return NULL; 3005 } 3006 3007 while (1) { 3008 wc = gtk_text_iter_get_char(&iter); 3009 if (!g_unichar_isspace(wc)) 3010 break; 3011 gtk_text_iter_forward_char(&iter); 3012 if (gtk_text_iter_ends_line(&iter)) 3013 break; 3014 g_string_append_unichar(item_chars, wc); 3015 } 3016 3017 return g_string_free(item_chars, FALSE); 3018 } 3019 3020 /* return the number of spaces at a line's start */ 3021 static int compose_left_offset_length(GtkTextBuffer *buffer, 3022 const GtkTextIter *start) 3023 { 3024 GtkTextIter iter = *start; 3025 gunichar wc; 3026 gint len = 0; 3027 if (gtk_text_iter_ends_line(&iter)) 3028 return 0; 3029 3030 while (1) { 3031 wc = gtk_text_iter_get_char(&iter); 3032 if (!g_unichar_isspace(wc)) 3033 break; 3034 len++; 3035 gtk_text_iter_forward_char(&iter); 3036 if (gtk_text_iter_ends_line(&iter)) 3037 return 0; 3038 } 3039 3040 gtk_text_iter_forward_char(&iter); 3041 if (gtk_text_iter_ends_line(&iter)) 3042 return 0; 3043 return len; 3044 } 3045 3046 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer, 3047 const GtkTextIter *start, 3048 GtkTextIter *break_pos, 3049 gint max_col, 3050 gint quote_len) 3051 { 3052 GtkTextIter iter = *start, line_end = *start; 3053 PangoLogAttr *attrs; 3054 gchar *str; 3055 gchar *p; 3056 gint len; 3057 gint i; 3058 gint col = 0; 3059 gint pos = 0; 3060 gboolean can_break = FALSE; 3061 gboolean do_break = FALSE; 3062 gboolean was_white = FALSE; 3063 gboolean prev_dont_break = FALSE; 3064 3065 gtk_text_iter_forward_to_line_end(&line_end); 3066 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE); 3067 len = g_utf8_strlen(str, -1); 3068 3069 if (len == 0) { 3070 g_free(str); 3071 g_warning("compose_get_line_break_pos: len = 0!"); 3072 return FALSE; 3073 } 3074 3075 /* g_print("breaking line: %d: %s (len = %d)\n", 3076 gtk_text_iter_get_line(&iter), str, len); */ 3077 3078 attrs = g_new(PangoLogAttr, len + 1); 3079 3080 pango_default_break(str, -1, NULL, attrs, len + 1); 3081 3082 p = str; 3083 3084 /* skip quote and leading spaces */ 3085 for (i = 0; *p != '\0' && i < len; i++) { 3086 gunichar wc; 3087 3088 wc = g_utf8_get_char(p); 3089 if (i >= quote_len && !g_unichar_isspace(wc)) 3090 break; 3091 if (g_unichar_iswide(wc)) 3092 col += 2; 3093 else if (*p == '\t') 3094 col += 8; 3095 else 3096 col++; 3097 p = g_utf8_next_char(p); 3098 } 3099 3100 for (; *p != '\0' && i < len; i++) { 3101 PangoLogAttr *attr = attrs + i; 3102 gunichar wc = g_utf8_get_char(p); 3103 gint uri_len; 3104 3105 /* attr->is_line_break will be false for some characters that 3106 * we want to break a line before, like '/' or ':', so we 3107 * also allow breaking on any non-wide character. The 3108 * mentioned pango attribute is still useful to decide on 3109 * line breaks when wide characters are involved. */ 3110 if ((!g_unichar_iswide(wc) || attr->is_line_break) 3111 && can_break && was_white && !prev_dont_break) 3112 pos = i; 3113 3114 was_white = attr->is_white; 3115 3116 /* don't wrap URI */ 3117 if ((uri_len = get_uri_len(p)) > 0) { 3118 col += uri_len; 3119 if (pos > 0 && col > max_col) { 3120 do_break = TRUE; 3121 break; 3122 } 3123 i += uri_len - 1; 3124 p += uri_len; 3125 can_break = TRUE; 3126 continue; 3127 } 3128 3129 if (g_unichar_iswide(wc)) { 3130 col += 2; 3131 if (prev_dont_break && can_break && attr->is_line_break) 3132 pos = i; 3133 } else if (*p == '\t') 3134 col += 8; 3135 else 3136 col++; 3137 if (pos > 0 && col > max_col) { 3138 do_break = TRUE; 3139 break; 3140 } 3141 3142 if (*p == '-' || *p == '/') 3143 prev_dont_break = TRUE; 3144 else 3145 prev_dont_break = FALSE; 3146 3147 p = g_utf8_next_char(p); 3148 can_break = TRUE; 3149 } 3150 3151 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */ 3152 3153 g_free(attrs); 3154 g_free(str); 3155 3156 *break_pos = *start; 3157 gtk_text_iter_set_line_offset(break_pos, pos); 3158 3159 return do_break; 3160 } 3161 3162 static gboolean compose_join_next_line(Compose *compose, 3163 GtkTextBuffer *buffer, 3164 GtkTextIter *iter, 3165 const gchar *quote_str) 3166 { 3167 GtkTextIter iter_ = *iter, cur, prev, next, end; 3168 PangoLogAttr attrs[3]; 3169 gchar *str; 3170 gchar *next_quote_str; 3171 gunichar wc1, wc2; 3172 gint quote_len; 3173 gboolean keep_cursor = FALSE; 3174 3175 if (!gtk_text_iter_forward_line(&iter_) || 3176 gtk_text_iter_ends_line(&iter_)) { 3177 return FALSE; 3178 } 3179 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len); 3180 3181 if ((quote_str || next_quote_str) && 3182 g_strcmp0(quote_str, next_quote_str) != 0) { 3183 g_free(next_quote_str); 3184 return FALSE; 3185 } 3186 g_free(next_quote_str); 3187 3188 end = iter_; 3189 if (quote_len > 0) { 3190 gtk_text_iter_forward_chars(&end, quote_len); 3191 if (gtk_text_iter_ends_line(&end)) { 3192 return FALSE; 3193 } 3194 } 3195 3196 /* don't join itemized lines */ 3197 if (compose_itemized_length(buffer, &end) > 0) { 3198 return FALSE; 3199 } 3200 3201 /* delete quote str */ 3202 if (quote_len > 0) 3203 gtk_text_buffer_delete(buffer, &iter_, &end); 3204 3205 /* don't join line breaks put by the user */ 3206 prev = cur = iter_; 3207 gtk_text_iter_backward_char(&cur); 3208 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) { 3209 gtk_text_iter_forward_char(&cur); 3210 *iter = cur; 3211 return FALSE; 3212 } 3213 gtk_text_iter_forward_char(&cur); 3214 /* delete linebreak and extra spaces */ 3215 while (gtk_text_iter_backward_char(&cur)) { 3216 wc1 = gtk_text_iter_get_char(&cur); 3217 if (!g_unichar_isspace(wc1)) 3218 break; 3219 prev = cur; 3220 } 3221 next = cur = iter_; 3222 while (!gtk_text_iter_ends_line(&cur)) { 3223 wc1 = gtk_text_iter_get_char(&cur); 3224 if (!g_unichar_isspace(wc1)) 3225 break; 3226 gtk_text_iter_forward_char(&cur); 3227 next = cur; 3228 } 3229 if (!gtk_text_iter_equal(&prev, &next)) { 3230 GtkTextMark *mark; 3231 3232 mark = gtk_text_buffer_get_insert(buffer); 3233 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark); 3234 if (gtk_text_iter_equal(&prev, &cur)) 3235 keep_cursor = TRUE; 3236 gtk_text_buffer_delete(buffer, &prev, &next); 3237 } 3238 iter_ = prev; 3239 3240 /* insert space if required */ 3241 gtk_text_iter_backward_char(&prev); 3242 wc1 = gtk_text_iter_get_char(&prev); 3243 wc2 = gtk_text_iter_get_char(&next); 3244 gtk_text_iter_forward_char(&next); 3245 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE); 3246 pango_default_break(str, -1, NULL, attrs, 3); 3247 if (!attrs[1].is_line_break || 3248 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) { 3249 gtk_text_buffer_insert(buffer, &iter_, " ", 1); 3250 if (keep_cursor) { 3251 gtk_text_iter_backward_char(&iter_); 3252 gtk_text_buffer_place_cursor(buffer, &iter_); 3253 } 3254 } 3255 g_free(str); 3256 3257 *iter = iter_; 3258 return TRUE; 3259 } 3260 3261 #define ADD_TXT_POS(bp_, ep_, pti_) \ 3262 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \ 3263 last = last->next; \ 3264 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \ 3265 last->next = NULL; \ 3266 } else { \ 3267 g_warning("alloc error scanning URIs"); \ 3268 } 3269 3270 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force) 3271 { 3272 GtkTextView *text = GTK_TEXT_VIEW(compose->text); 3273 GtkTextBuffer *buffer; 3274 GtkTextIter iter, break_pos, end_of_line; 3275 gchar *quote_str = NULL; 3276 gint quote_len; 3277 gboolean wrap_quote = force || prefs_common.linewrap_quote; 3278 gboolean prev_autowrap = compose->autowrap; 3279 gint startq_offset = -1, noq_offset = -1; 3280 gint uri_start = -1, uri_stop = -1; 3281 gint nouri_start = -1, nouri_stop = -1; 3282 gint num_blocks = 0; 3283 gint quotelevel = -1; 3284 gboolean modified = force; 3285 gboolean removed = FALSE; 3286 gboolean modified_before_remove = FALSE; 3287 gboolean start = TRUE; 3288 gint itemized_len = 0, rem_item_len = 0; 3289 gchar *itemized_chars = NULL; 3290 gboolean item_continuation = FALSE; 3291 3292 if (force) { 3293 modified = TRUE; 3294 } 3295 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) { 3296 modified = TRUE; 3297 } 3298 3299 compose->autowrap = FALSE; 3300 3301 buffer = gtk_text_view_get_buffer(text); 3302 undo_wrapping(compose->undostruct, TRUE); 3303 if (par_iter) { 3304 iter = *par_iter; 3305 } else { 3306 GtkTextMark *mark; 3307 mark = gtk_text_buffer_get_insert(buffer); 3308 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark); 3309 } 3310 3311 3312 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) { 3313 if (gtk_text_iter_ends_line(&iter)) { 3314 while (gtk_text_iter_ends_line(&iter) && 3315 gtk_text_iter_forward_line(&iter)) 3316 ; 3317 } else { 3318 while (gtk_text_iter_backward_line(&iter)) { 3319 if (gtk_text_iter_ends_line(&iter)) { 3320 gtk_text_iter_forward_line(&iter); 3321 break; 3322 } 3323 } 3324 } 3325 } else { 3326 /* move to line start */ 3327 gtk_text_iter_set_line_offset(&iter, 0); 3328 } 3329 3330 itemized_len = compose_itemized_length(buffer, &iter); 3331 3332 if (!itemized_len) { 3333 itemized_len = compose_left_offset_length(buffer, &iter); 3334 item_continuation = TRUE; 3335 } 3336 3337 if (itemized_len) 3338 itemized_chars = compose_get_itemized_chars(buffer, &iter); 3339 3340 /* go until paragraph end (empty line) */ 3341 while (start || !gtk_text_iter_ends_line(&iter)) { 3342 gchar *scanpos = NULL; 3343 /* parse table - in order of priority */ 3344 struct table { 3345 const gchar *needle; /* token */ 3346 3347 /* token search function */ 3348 gchar *(*search) (const gchar *haystack, 3349 const gchar *needle); 3350 /* part parsing function */ 3351 gboolean (*parse) (const gchar *start, 3352 const gchar *scanpos, 3353 const gchar **bp_, 3354 const gchar **ep_, 3355 gboolean hdr); 3356 /* part to URI function */ 3357 gchar *(*build_uri) (const gchar *bp, 3358 const gchar *ep); 3359 }; 3360 3361 static struct table parser[] = { 3362 {"http://", strcasestr, get_uri_part, make_uri_string}, 3363 {"https://", strcasestr, get_uri_part, make_uri_string}, 3364 {"ftp://", strcasestr, get_uri_part, make_uri_string}, 3365 {"ftps://", strcasestr, get_uri_part, make_uri_string}, 3366 {"sftp://", strcasestr, get_uri_part, make_uri_string}, 3367 {"gopher://",strcasestr, get_uri_part, make_uri_string}, 3368 {"www.", strcasestr, get_uri_part, make_http_string}, 3369 {"webcal://",strcasestr, get_uri_part, make_uri_string}, 3370 {"webcals://",strcasestr, get_uri_part, make_uri_string}, 3371 {"mailto:", strcasestr, get_uri_part, make_uri_string}, 3372 {"@", strcasestr, get_email_part, make_email_string} 3373 }; 3374 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0]; 3375 gint last_index = PARSE_ELEMS; 3376 gint n; 3377 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL; 3378 gint walk_pos; 3379 3380 start = FALSE; 3381 if (!prev_autowrap && num_blocks == 0) { 3382 num_blocks++; 3383 g_signal_handlers_block_by_func(G_OBJECT(buffer), 3384 G_CALLBACK(text_inserted), 3385 compose); 3386 } 3387 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force) 3388 goto colorize; 3389 3390 uri_start = uri_stop = -1; 3391 quote_len = 0; 3392 quote_str = compose_get_quote_str(buffer, &iter, "e_len); 3393 3394 if (quote_str) { 3395 if (startq_offset == -1) 3396 startq_offset = gtk_text_iter_get_offset(&iter); 3397 quotelevel = quote_depth(quote_str); 3398 if (quotelevel > 2) { 3399 /* recycle colors */ 3400 if (prefs_common.recycle_quote_colors) 3401 quotelevel %= 3; 3402 else 3403 quotelevel = 2; 3404 } 3405 if (!wrap_quote) { 3406 goto colorize; 3407 } 3408 } else { 3409 if (startq_offset == -1) 3410 noq_offset = gtk_text_iter_get_offset(&iter); 3411 quotelevel = -1; 3412 } 3413 3414 if (prev_autowrap == FALSE && !force && !wrap_quote) { 3415 goto colorize; 3416 } 3417 if (gtk_text_iter_ends_line(&iter)) { 3418 goto colorize; 3419 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos, 3420 prefs_common.linewrap_len, 3421 quote_len)) { 3422 GtkTextIter prev, next, cur; 3423 if (prev_autowrap != FALSE || force) { 3424 compose->automatic_break = TRUE; 3425 modified = TRUE; 3426 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1); 3427 compose->automatic_break = FALSE; 3428 if (itemized_len && compose->autoindent) { 3429 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1); 3430 if (!item_continuation) 3431 gtk_text_buffer_insert(buffer, &break_pos, " ", 2); 3432 } 3433 } else if (quote_str && wrap_quote) { 3434 compose->automatic_break = TRUE; 3435 modified = TRUE; 3436 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1); 3437 compose->automatic_break = FALSE; 3438 if (itemized_len && compose->autoindent) { 3439 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1); 3440 if (!item_continuation) 3441 gtk_text_buffer_insert(buffer, &break_pos, " ", 2); 3442 } 3443 } else 3444 goto colorize; 3445 /* remove trailing spaces */ 3446 cur = break_pos; 3447 rem_item_len = itemized_len; 3448 while (compose->autoindent && rem_item_len-- > 0) 3449 gtk_text_iter_backward_char(&cur); 3450 gtk_text_iter_backward_char(&cur); 3451 3452 prev = next = cur; 3453 while (!gtk_text_iter_starts_line(&cur)) { 3454 gunichar wc; 3455 3456 gtk_text_iter_backward_char(&cur); 3457 wc = gtk_text_iter_get_char(&cur); 3458 if (!g_unichar_isspace(wc)) 3459 break; 3460 prev = cur; 3461 } 3462 if (!gtk_text_iter_equal(&prev, &next)) { 3463 gtk_text_buffer_delete(buffer, &prev, &next); 3464 break_pos = next; 3465 gtk_text_iter_forward_char(&break_pos); 3466 } 3467 3468 if (quote_str) 3469 gtk_text_buffer_insert(buffer, &break_pos, 3470 quote_str, -1); 3471 3472 iter = break_pos; 3473 modified |= compose_join_next_line(compose, buffer, &iter, quote_str); 3474 3475 /* move iter to current line start */ 3476 gtk_text_iter_set_line_offset(&iter, 0); 3477 if (quote_str) { 3478 g_free(quote_str); 3479 quote_str = NULL; 3480 } 3481 continue; 3482 } else { 3483 /* move iter to next line start */ 3484 iter = break_pos; 3485 } 3486 3487 colorize: 3488 if (!prev_autowrap && num_blocks > 0) { 3489 num_blocks--; 3490 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), 3491 G_CALLBACK(text_inserted), 3492 compose); 3493 } 3494 end_of_line = iter; 3495 while (!gtk_text_iter_ends_line(&end_of_line)) { 3496 gtk_text_iter_forward_char(&end_of_line); 3497 } 3498 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE); 3499 3500 nouri_start = gtk_text_iter_get_offset(&iter); 3501 nouri_stop = gtk_text_iter_get_offset(&end_of_line); 3502 3503 walk_pos = gtk_text_iter_get_offset(&iter); 3504 /* FIXME: this looks phony. scanning for anything in the parse table */ 3505 for (n = 0; n < PARSE_ELEMS; n++) { 3506 gchar *tmp; 3507 3508 tmp = parser[n].search(walk, parser[n].needle); 3509 if (tmp) { 3510 if (scanpos == NULL || tmp < scanpos) { 3511 scanpos = tmp; 3512 last_index = n; 3513 } 3514 } 3515 } 3516 3517 bp = ep = 0; 3518 if (scanpos) { 3519 /* check if URI can be parsed */ 3520 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp, 3521 (const gchar **)&ep, FALSE) 3522 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) { 3523 walk = ep; 3524 } else 3525 walk = scanpos + 3526 strlen(parser[last_index].needle); 3527 } 3528 if (bp && ep) { 3529 uri_start = walk_pos + (bp - o_walk); 3530 uri_stop = walk_pos + (ep - o_walk); 3531 } 3532 g_free(o_walk); 3533 o_walk = NULL; 3534 gtk_text_iter_forward_line(&iter); 3535 g_free(quote_str); 3536 quote_str = NULL; 3537 if (startq_offset != -1) { 3538 GtkTextIter startquote, endquote; 3539 gtk_text_buffer_get_iter_at_offset( 3540 buffer, &startquote, startq_offset); 3541 endquote = iter; 3542 3543 switch (quotelevel) { 3544 case 0: 3545 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) || 3546 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) { 3547 gtk_text_buffer_apply_tag_by_name( 3548 buffer, "quote0", &startquote, &endquote); 3549 gtk_text_buffer_remove_tag_by_name( 3550 buffer, "quote1", &startquote, &endquote); 3551 gtk_text_buffer_remove_tag_by_name( 3552 buffer, "quote2", &startquote, &endquote); 3553 modified = TRUE; 3554 } 3555 break; 3556 case 1: 3557 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) || 3558 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) { 3559 gtk_text_buffer_apply_tag_by_name( 3560 buffer, "quote1", &startquote, &endquote); 3561 gtk_text_buffer_remove_tag_by_name( 3562 buffer, "quote0", &startquote, &endquote); 3563 gtk_text_buffer_remove_tag_by_name( 3564 buffer, "quote2", &startquote, &endquote); 3565 modified = TRUE; 3566 } 3567 break; 3568 case 2: 3569 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) || 3570 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) { 3571 gtk_text_buffer_apply_tag_by_name( 3572 buffer, "quote2", &startquote, &endquote); 3573 gtk_text_buffer_remove_tag_by_name( 3574 buffer, "quote0", &startquote, &endquote); 3575 gtk_text_buffer_remove_tag_by_name( 3576 buffer, "quote1", &startquote, &endquote); 3577 modified = TRUE; 3578 } 3579 break; 3580 } 3581 startq_offset = -1; 3582 } else if (noq_offset != -1) { 3583 GtkTextIter startnoquote, endnoquote; 3584 gtk_text_buffer_get_iter_at_offset( 3585 buffer, &startnoquote, noq_offset); 3586 endnoquote = iter; 3587 3588 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag) 3589 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) || 3590 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag) 3591 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) || 3592 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag) 3593 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) { 3594 gtk_text_buffer_remove_tag_by_name( 3595 buffer, "quote0", &startnoquote, &endnoquote); 3596 gtk_text_buffer_remove_tag_by_name( 3597 buffer, "quote1", &startnoquote, &endnoquote); 3598 gtk_text_buffer_remove_tag_by_name( 3599 buffer, "quote2", &startnoquote, &endnoquote); 3600 modified = TRUE; 3601 } 3602 noq_offset = -1; 3603 } 3604 3605 if (uri_start != nouri_start && uri_stop != nouri_stop) { 3606 GtkTextIter nouri_start_iter, nouri_end_iter; 3607 gtk_text_buffer_get_iter_at_offset( 3608 buffer, &nouri_start_iter, nouri_start); 3609 gtk_text_buffer_get_iter_at_offset( 3610 buffer, &nouri_end_iter, nouri_stop); 3611 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) && 3612 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) { 3613 gtk_text_buffer_remove_tag_by_name( 3614 buffer, "link", &nouri_start_iter, &nouri_end_iter); 3615 modified_before_remove = modified; 3616 modified = TRUE; 3617 removed = TRUE; 3618 } 3619 } 3620 if (uri_start >= 0 && uri_stop > 0) { 3621 GtkTextIter uri_start_iter, uri_end_iter, back; 3622 gtk_text_buffer_get_iter_at_offset( 3623 buffer, &uri_start_iter, uri_start); 3624 gtk_text_buffer_get_iter_at_offset( 3625 buffer, &uri_end_iter, uri_stop); 3626 back = uri_end_iter; 3627 gtk_text_iter_backward_char(&back); 3628 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) || 3629 !gtk_text_iter_has_tag(&back, compose->uri_tag)) { 3630 gtk_text_buffer_apply_tag_by_name( 3631 buffer, "link", &uri_start_iter, &uri_end_iter); 3632 modified = TRUE; 3633 if (removed && !modified_before_remove) { 3634 modified = FALSE; 3635 } 3636 } 3637 } 3638 if (!modified) { 3639 /* debug_print("not modified, out after %d lines\n", lines); */ 3640 goto end; 3641 } 3642 } 3643 /* debug_print("modified, out after %d lines\n", lines); */ 3644 end: 3645 g_free(itemized_chars); 3646 if (par_iter) 3647 *par_iter = iter; 3648 undo_wrapping(compose->undostruct, FALSE); 3649 compose->autowrap = prev_autowrap; 3650 3651 return modified; 3652 } 3653 3654 void compose_action_cb(void *data) 3655 { 3656 Compose *compose = (Compose *)data; 3657 compose_wrap_all(compose); 3658 } 3659 3660 static void compose_wrap_all(Compose *compose) 3661 { 3662 compose_wrap_all_full(compose, FALSE); 3663 } 3664 3665 static void compose_wrap_all_full(Compose *compose, gboolean force) 3666 { 3667 GtkTextView *text = GTK_TEXT_VIEW(compose->text); 3668 GtkTextBuffer *buffer; 3669 GtkTextIter iter; 3670 gboolean modified = TRUE; 3671 3672 buffer = gtk_text_view_get_buffer(text); 3673 3674 gtk_text_buffer_get_start_iter(buffer, &iter); 3675 3676 undo_wrapping(compose->undostruct, TRUE); 3677 3678 while (!gtk_text_iter_is_end(&iter) && modified) 3679 modified = compose_beautify_paragraph(compose, &iter, force); 3680 3681 undo_wrapping(compose->undostruct, FALSE); 3682 3683 } 3684 3685 static void compose_set_title(Compose *compose) 3686 { 3687 gchar *str; 3688 gchar *edited; 3689 gchar *subject; 3690 3691 edited = compose->modified ? _(" [Edited]") : ""; 3692 3693 subject = gtk_editable_get_chars( 3694 GTK_EDITABLE(compose->subject_entry), 0, -1); 3695 3696 if (subject && strlen(subject)) 3697 str = g_strdup_printf(_("%s - Write message%s"), 3698 subject, edited); 3699 else 3700 str = g_strdup_printf(_("[no subject] - Write message%s"), edited); 3701 3702 gtk_window_set_title(GTK_WINDOW(compose->window), str); 3703 g_free(str); 3704 g_free(subject); 3705 } 3706 3707 /** 3708 * compose_current_mail_account: 3709 * 3710 * Find a current mail account (the currently selected account, or the 3711 * default account, if a news account is currently selected). If a 3712 * mail account cannot be found, display an error message. 3713 * 3714 * Return value: Mail account, or NULL if not found. 3715 **/ 3716 static PrefsAccount * 3717 compose_current_mail_account(void) 3718 { 3719 PrefsAccount *ac; 3720 3721 if (cur_account) 3722 ac = cur_account; 3723 else { 3724 ac = account_get_default(); 3725 if (!ac) { 3726 alertpanel_error(_("Account for sending mail is not specified.\n" 3727 "Please select a mail account before sending.")); 3728 return NULL; 3729 } 3730 } 3731 return ac; 3732 } 3733 3734 #define QUOTE_IF_REQUIRED(out, str) \ 3735 { \ 3736 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \ 3737 gchar *__tmp; \ 3738 gint len; \ 3739 \ 3740 len = strlen(str) + 3; \ 3741 if ((__tmp = alloca(len)) == NULL) { \ 3742 g_warning("can't allocate memory"); \ 3743 g_string_free(header, TRUE); \ 3744 return NULL; \ 3745 } \ 3746 g_snprintf(__tmp, len, "\"%s\"", str); \ 3747 out = __tmp; \ 3748 } else { \ 3749 gchar *__tmp; \ 3750 \ 3751 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \ 3752 g_warning("can't allocate memory"); \ 3753 g_string_free(header, TRUE); \ 3754 return NULL; \ 3755 } else \ 3756 strcpy(__tmp, str); \ 3757 \ 3758 out = __tmp; \ 3759 } \ 3760 } 3761 3762 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \ 3763 { \ 3764 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \ 3765 gchar *__tmp; \ 3766 gint len; \ 3767 \ 3768 len = strlen(str) + 3; \ 3769 if ((__tmp = alloca(len)) == NULL) { \ 3770 g_warning("can't allocate memory"); \ 3771 errret; \ 3772 } \ 3773 g_snprintf(__tmp, len, "\"%s\"", str); \ 3774 out = __tmp; \ 3775 } else { \ 3776 gchar *__tmp; \ 3777 \ 3778 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \ 3779 g_warning("can't allocate memory"); \ 3780 errret; \ 3781 } else \ 3782 strcpy(__tmp, str); \ 3783 \ 3784 out = __tmp; \ 3785 } \ 3786 } 3787 3788 static void compose_select_account(Compose *compose, PrefsAccount *account, 3789 gboolean init) 3790 { 3791 gchar *from = NULL, *header = NULL; 3792 ComposeHeaderEntry *header_entry; 3793 GtkTreeIter iter; 3794 3795 cm_return_if_fail(account != NULL); 3796 3797 compose->account = account; 3798 if (account->name && *account->name) { 3799 gchar *buf, *qbuf; 3800 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return); 3801 qbuf = escape_internal_quotes(buf, '"'); 3802 from = g_strdup_printf("%s <%s>", 3803 qbuf, account->address); 3804 if (qbuf != buf) 3805 g_free(qbuf); 3806 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from); 3807 } else { 3808 from = g_strdup_printf("<%s>", 3809 account->address); 3810 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from); 3811 } 3812 3813 g_free(from); 3814 3815 compose_set_title(compose); 3816 3817 if (!init && compose->mode != COMPOSE_REDIRECT) { 3818 undo_block(compose->undostruct); 3819 undo_unblock(compose->undostruct); 3820 } 3821 3822 header_entry = (ComposeHeaderEntry *) compose->header_list->data; 3823 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter)) 3824 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX( 3825 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1); 3826 3827 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) { 3828 combobox_select_by_text(GTK_COMBO_BOX(header_entry->combo), "To:"); 3829 } 3830 g_free(header); 3831 } 3832 3833 gboolean compose_check_for_valid_recipient(Compose *compose) { 3834 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL}; 3835 gboolean recipient_found = FALSE; 3836 GSList *list; 3837 gchar **strptr; 3838 3839 slist_free_strings_full(compose->to_list); 3840 compose->to_list = NULL; 3841 3842 /* search header entries for to */ 3843 for (list = compose->header_list; list; list = list->next) { 3844 gchar *header; 3845 gchar *entry; 3846 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1); 3847 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1); 3848 g_strstrip(entry); 3849 g_strstrip(header); 3850 if (entry[0] != '\0') { 3851 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) { 3852 if (!g_ascii_strcasecmp(header, *strptr)) { 3853 compose->to_list = address_list_append(compose->to_list, entry); 3854 recipient_found = TRUE; 3855 } 3856 } 3857 } 3858 g_free(header); 3859 g_free(entry); 3860 } 3861 return recipient_found; 3862 } 3863 3864 static gboolean compose_check_for_set_recipients(Compose *compose) 3865 { 3866 if (compose->account->set_autocc && compose->account->auto_cc) { 3867 gboolean found_other = FALSE; 3868 GSList *list; 3869 /* search header entries for to */ 3870 for (list = compose->header_list; list; list = list->next) { 3871 gchar *entry; 3872 gchar *header; 3873 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1); 3874 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1); 3875 g_strstrip(entry); 3876 g_strstrip(header); 3877 if (strcmp(entry, compose->account->auto_cc) 3878 || strcmp(header, "Cc:")) { 3879 found_other = TRUE; 3880 g_free(entry); 3881 break; 3882 } 3883 g_free(entry); 3884 g_free(header); 3885 } 3886 if (!found_other) { 3887 AlertValue aval; 3888 gchar *text; 3889 if (compose->batch) { 3890 gtk_widget_show_all(compose->window); 3891 } 3892 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"), 3893 "Cc"); 3894 aval = alertpanel(_("Send"), 3895 text, 3896 NULL, _("_Cancel"), NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND); 3897 g_free(text); 3898 if (aval != G_ALERTALTERNATE) 3899 return FALSE; 3900 } 3901 } 3902 if (compose->account->set_autobcc && compose->account->auto_bcc) { 3903 gboolean found_other = FALSE; 3904 GSList *list; 3905 /* search header entries for to */ 3906 for (list = compose->header_list; list; list = list->next) { 3907 gchar *entry; 3908 gchar *header; 3909 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1); 3910 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1); 3911 g_strstrip(entry); 3912 g_strstrip(header); 3913 if (strcmp(entry, compose->account->auto_bcc) 3914 || strcmp(header, "Bcc:")) { 3915 found_other = TRUE; 3916 g_free(entry); 3917 g_free(header); 3918 break; 3919 } 3920 g_free(entry); 3921 g_free(header); 3922 } 3923 if (!found_other) { 3924 AlertValue aval; 3925 gchar *text; 3926 if (compose->batch) { 3927 gtk_widget_show_all(compose->window); 3928 } 3929 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"), 3930 "Bcc"); 3931 aval = alertpanel(_("Send"), 3932 text, 3933 NULL, _("_Cancel"), NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND); 3934 g_free(text); 3935 if (aval != G_ALERTALTERNATE) 3936 return FALSE; 3937 } 3938 } 3939 return TRUE; 3940 } 3941 3942 static gboolean compose_check_entries(Compose *compose, gboolean check_everything) 3943 { 3944 const gchar *str; 3945 3946 if (compose_check_for_valid_recipient(compose) == FALSE) { 3947 if (compose->batch) { 3948 gtk_widget_show_all(compose->window); 3949 } 3950 alertpanel_error(_("Recipient is not specified.")); 3951 return FALSE; 3952 } 3953 3954 if (compose_check_for_set_recipients(compose) == FALSE) { 3955 return FALSE; 3956 } 3957 3958 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) { 3959 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry)); 3960 if (*str == '\0' && check_everything == TRUE && 3961 compose->mode != COMPOSE_REDIRECT) { 3962 AlertValue aval; 3963 gchar *message; 3964 3965 message = g_strdup_printf(_("Subject is empty. %s"), 3966 compose->sending?_("Send it anyway?"): 3967 _("Queue it anyway?")); 3968 3969 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message, 3970 NULL, _("_Cancel"), NULL, compose->sending?_("_Send"):_("_Queue"), 3971 NULL, NULL, ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION); 3972 g_free(message); 3973 if (aval & G_ALERTDISABLE) { 3974 aval &= ~G_ALERTDISABLE; 3975 prefs_common.warn_empty_subj = FALSE; 3976 } 3977 if (aval != G_ALERTALTERNATE) 3978 return FALSE; 3979 } 3980 } 3981 3982 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0 3983 && check_everything == TRUE) { 3984 GSList *list; 3985 gint cnt = 0; 3986 3987 /* count To and Cc recipients */ 3988 for (list = compose->header_list; list; list = list->next) { 3989 gchar *header; 3990 gchar *entry; 3991 3992 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1); 3993 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1); 3994 g_strstrip(header); 3995 g_strstrip(entry); 3996 if ((entry[0] != '\0') && 3997 (!strcmp(header, "To:") || 3998 !strcmp(header, "Cc:"))) { 3999 cnt++; 4000 } 4001 g_free(header); 4002 g_free(entry); 4003 } 4004 if (cnt > prefs_common.warn_sending_many_recipients_num) { 4005 AlertValue aval; 4006 gchar *message; 4007 4008 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt, 4009 compose->sending?_("Send it anyway?"): 4010 _("Queue it anyway?")); 4011 4012 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message, 4013 NULL, _("_Cancel"), NULL, compose->sending?_("_Send"):_("_Queue"), 4014 NULL, NULL, ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION); 4015 g_free(message); 4016 if (aval & G_ALERTDISABLE) { 4017 aval &= ~G_ALERTDISABLE; 4018 prefs_common.warn_sending_many_recipients_num = 0; 4019 } 4020 if (aval != G_ALERTALTERNATE) 4021 return FALSE; 4022 } 4023 } 4024 4025 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose)) 4026 return FALSE; 4027 4028 return TRUE; 4029 } 4030 4031 static void _display_queue_error(ComposeQueueResult val) 4032 { 4033 switch (val) { 4034 case COMPOSE_QUEUE_SUCCESS: 4035 break; 4036 case COMPOSE_QUEUE_ERROR_NO_MSG: 4037 alertpanel_error(_("Could not queue message.")); 4038 break; 4039 case COMPOSE_QUEUE_ERROR_WITH_ERRNO: 4040 alertpanel_error(_("Could not queue message:\n\n%s."), 4041 g_strerror(errno)); 4042 break; 4043 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION: 4044 alertpanel_error(_("Could not queue message for sending:\n\n" 4045 "Charset conversion failed.")); 4046 break; 4047 default: 4048 /* unhandled error */ 4049 debug_print("oops, unhandled compose_queue() return value %d\n", 4050 val); 4051 break; 4052 } 4053 } 4054 4055 gint compose_send(Compose *compose) 4056 { 4057 gint msgnum; 4058 FolderItem *folder = NULL; 4059 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG; 4060 gchar *msgpath = NULL; 4061 gboolean discard_window = FALSE; 4062 gchar *errstr = NULL; 4063 gchar *tmsgid = NULL; 4064 MainWindow *mainwin = mainwindow_get_mainwindow(); 4065 gboolean queued_removed = FALSE; 4066 4067 if (prefs_common.send_dialog_invisible 4068 || compose->batch == TRUE) 4069 discard_window = TRUE; 4070 4071 compose_allow_user_actions (compose, FALSE); 4072 compose->sending = TRUE; 4073 4074 if (compose_check_entries(compose, TRUE) == FALSE) { 4075 if (compose->batch) { 4076 gtk_widget_show_all(compose->window); 4077 } 4078 goto bail; 4079 } 4080 4081 inc_lock(); 4082 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE); 4083 4084 if (val != COMPOSE_QUEUE_SUCCESS) { 4085 if (compose->batch) { 4086 gtk_widget_show_all(compose->window); 4087 } 4088 4089 _display_queue_error(val); 4090 4091 goto bail; 4092 } 4093 4094 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL; 4095 if (discard_window) { 4096 compose->sending = FALSE; 4097 compose_close(compose); 4098 /* No more compose access in the normal codepath 4099 * after this point! */ 4100 compose = NULL; 4101 } 4102 4103 if (msgnum == 0) { 4104 alertpanel_error(_("The message was queued but could not be " 4105 "sent.\nUse \"Send queued messages\" from " 4106 "the main window to retry.")); 4107 if (!discard_window) { 4108 goto bail; 4109 } 4110 inc_unlock(); 4111 g_free(tmsgid); 4112 return -1; 4113 } 4114 if (msgpath == NULL) { 4115 msgpath = folder_item_fetch_msg(folder, msgnum); 4116 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed); 4117 g_free(msgpath); 4118 } else { 4119 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed); 4120 unlink(msgpath); 4121 g_free(msgpath); 4122 } 4123 if (!discard_window) { 4124 if (val != 0) { 4125 if (!queued_removed) 4126 folder_item_remove_msg(folder, msgnum); 4127 folder_item_scan(folder); 4128 if (tmsgid) { 4129 /* make sure we delete that */ 4130 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid); 4131 if (tmp) { 4132 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid); 4133 folder_item_remove_msg(folder, tmp->msgnum); 4134 procmsg_msginfo_free(&tmp); 4135 } 4136 } 4137 } 4138 } 4139 4140 if (val == 0) { 4141 if (!queued_removed) 4142 folder_item_remove_msg(folder, msgnum); 4143 folder_item_scan(folder); 4144 if (tmsgid) { 4145 /* make sure we delete that */ 4146 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid); 4147 if (tmp) { 4148 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid); 4149 folder_item_remove_msg(folder, tmp->msgnum); 4150 procmsg_msginfo_free(&tmp); 4151 } 4152 } 4153 if (!discard_window) { 4154 compose->sending = FALSE; 4155 compose_allow_user_actions (compose, TRUE); 4156 compose_close(compose); 4157 } 4158 } else { 4159 if (errstr) { 4160 alertpanel_error_log(_("%s\nYou can try to \"Send\" again " 4161 "or queue the message with \"Send later\""), errstr); 4162 g_free(errstr); 4163 } else { 4164 alertpanel_error_log(_("The message was queued but could not be " 4165 "sent.\nUse \"Send queued messages\" from " 4166 "the main window to retry.")); 4167 } 4168 if (!discard_window) { 4169 goto bail; 4170 } 4171 inc_unlock(); 4172 g_free(tmsgid); 4173 return -1; 4174 } 4175 g_free(tmsgid); 4176 inc_unlock(); 4177 toolbar_main_set_sensitive(mainwin); 4178 main_window_set_menu_sensitive(mainwin); 4179 return 0; 4180 4181 bail: 4182 inc_unlock(); 4183 g_free(tmsgid); 4184 compose_allow_user_actions (compose, TRUE); 4185 compose->sending = FALSE; 4186 compose->modified = TRUE; 4187 toolbar_main_set_sensitive(mainwin); 4188 main_window_set_menu_sensitive(mainwin); 4189 4190 return -1; 4191 } 4192 4193 static gboolean compose_use_attach(Compose *compose) 4194 { 4195 GtkTreeModel *model = gtk_tree_view_get_model 4196 (GTK_TREE_VIEW(compose->attach_clist)); 4197 return gtk_tree_model_iter_n_children(model, NULL) > 0; 4198 } 4199 4200 static gint compose_redirect_write_headers_from_headerlist(Compose *compose, 4201 FILE *fp) 4202 { 4203 gchar buf[BUFFSIZE]; 4204 gchar *str; 4205 gboolean first_to_address; 4206 gboolean first_cc_address; 4207 GSList *list; 4208 ComposeHeaderEntry *headerentry; 4209 const gchar *headerentryname; 4210 const gchar *cc_hdr; 4211 const gchar *to_hdr; 4212 gboolean err = FALSE; 4213 4214 debug_print("Writing redirect header\n"); 4215 4216 cc_hdr = "Cc:"; 4217 to_hdr = "To:"; 4218 4219 first_to_address = TRUE; 4220 for (list = compose->header_list; list; list = list->next) { 4221 headerentry = ((ComposeHeaderEntry *)list->data); 4222 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))); 4223 4224 if (g_utf8_collate(headerentryname, to_hdr) == 0) { 4225 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry)); 4226 Xstrdup_a(str, entstr, return -1); 4227 g_strstrip(str); 4228 if (str[0] != '\0') { 4229 compose_convert_header 4230 (compose, buf, sizeof(buf), str, 4231 strlen("Resent-To") + 2, TRUE); 4232 4233 if (first_to_address) { 4234 err |= (fprintf(fp, "Resent-To: ") < 0); 4235 first_to_address = FALSE; 4236 } else { 4237 err |= (fprintf(fp, ",") < 0); 4238 } 4239 err |= (fprintf(fp, "%s", buf) < 0); 4240 } 4241 } 4242 } 4243 if (!first_to_address) { 4244 err |= (fprintf(fp, "\n") < 0); 4245 } 4246 4247 first_cc_address = TRUE; 4248 for (list = compose->header_list; list; list = list->next) { 4249 headerentry = ((ComposeHeaderEntry *)list->data); 4250 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))); 4251 4252 if (g_utf8_collate(headerentryname, cc_hdr) == 0) { 4253 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry)); 4254 Xstrdup_a(str, strg, return -1); 4255 g_strstrip(str); 4256 if (str[0] != '\0') { 4257 compose_convert_header 4258 (compose, buf, sizeof(buf), str, 4259 strlen("Resent-Cc") + 2, TRUE); 4260 4261 if (first_cc_address) { 4262 err |= (fprintf(fp, "Resent-Cc: ") < 0); 4263 first_cc_address = FALSE; 4264 } else { 4265 err |= (fprintf(fp, ",") < 0); 4266 } 4267 err |= (fprintf(fp, "%s", buf) < 0); 4268 } 4269 } 4270 } 4271 if (!first_cc_address) { 4272 err |= (fprintf(fp, "\n") < 0); 4273 } 4274 4275 return (err ? -1:0); 4276 } 4277 4278 static gint compose_redirect_write_headers(Compose *compose, FILE *fp) 4279 { 4280 gchar date[RFC822_DATE_BUFFSIZE]; 4281 gchar buf[BUFFSIZE]; 4282 gchar *str; 4283 const gchar *entstr; 4284 gboolean err = FALSE; 4285 4286 cm_return_val_if_fail(fp != NULL, -1); 4287 cm_return_val_if_fail(compose->account != NULL, -1); 4288 cm_return_val_if_fail(compose->account->address != NULL, -1); 4289 4290 /* Resent-Date */ 4291 if (prefs_common.hide_timezone) 4292 get_rfc822_date_hide_tz(date, sizeof(date)); 4293 else 4294 get_rfc822_date(date, sizeof(date)); 4295 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0); 4296 4297 /* Resent-From */ 4298 if (compose->account->name && *compose->account->name) { 4299 compose_convert_header 4300 (compose, buf, sizeof(buf), compose->account->name, 4301 strlen("From: "), TRUE); 4302 err |= (fprintf(fp, "Resent-From: %s <%s>\n", 4303 buf, compose->account->address) < 0); 4304 } else 4305 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0); 4306 4307 /* Subject */ 4308 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry)); 4309 if (*entstr != '\0') { 4310 Xstrdup_a(str, entstr, return -1); 4311 g_strstrip(str); 4312 if (*str != '\0') { 4313 compose_convert_header(compose, buf, sizeof(buf), str, 4314 strlen("Subject: "), FALSE); 4315 err |= (fprintf(fp, "Subject: %s\n", buf) < 0); 4316 } 4317 } 4318 4319 /* Resent-Message-ID */ 4320 if (compose->account->gen_msgid) { 4321 gchar *addr = prefs_account_generate_msgid(compose->account); 4322 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0); 4323 if (compose->msgid) 4324 g_free(compose->msgid); 4325 compose->msgid = addr; 4326 } else { 4327 compose->msgid = NULL; 4328 } 4329 4330 if (compose_redirect_write_headers_from_headerlist(compose, fp)) 4331 return -1; 4332 4333 /* separator between header and body */ 4334 err |= (fputs("\n", fp) == EOF); 4335 4336 return (err ? -1:0); 4337 } 4338 4339 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest) 4340 { 4341 FILE *fp; 4342 size_t len; 4343 gchar *buf = NULL; 4344 gchar rewrite_buf[BUFFSIZE]; 4345 int i = 0; 4346 gboolean skip = FALSE; 4347 gboolean err = FALSE; 4348 gchar *not_included[]={ 4349 "Return-Path:", "Delivered-To:", "Received:", 4350 "Subject:", "X-UIDL:", "AF:", 4351 "NF:", "PS:", "SRH:", 4352 "SFN:", "DSR:", "MID:", 4353 "CFG:", "PT:", "S:", 4354 "RQ:", "SSV:", "NSV:", 4355 "SSH:", "R:", "MAID:", 4356 "NAID:", "RMID:", "FMID:", 4357 "SCF:", "RRCPT:", "NG:", 4358 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:", 4359 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:", 4360 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:", 4361 NULL 4362 }; 4363 gint ret = 0; 4364 4365 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) { 4366 FILE_OP_ERROR(compose->redirect_filename, "g_fopen"); 4367 return -1; 4368 } 4369 4370 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) { 4371 skip = FALSE; 4372 for (i = 0; not_included[i] != NULL; i++) { 4373 if (g_ascii_strncasecmp(buf, not_included[i], 4374 strlen(not_included[i])) == 0) { 4375 skip = TRUE; 4376 break; 4377 } 4378 } 4379 if (skip) { 4380 g_free(buf); 4381 buf = NULL; 4382 continue; 4383 } 4384 if (fputs(buf, fdest) == -1) { 4385 g_free(buf); 4386 buf = NULL; 4387 goto error; 4388 } 4389 4390 if (!prefs_common.redirect_keep_from) { 4391 if (g_ascii_strncasecmp(buf, "From:", 4392 strlen("From:")) == 0) { 4393 err |= (fputs(" (by way of ", fdest) == EOF); 4394 if (compose->account->name 4395 && *compose->account->name) { 4396 gchar buffer[BUFFSIZE]; 4397 4398 compose_convert_header 4399 (compose, buffer, sizeof(buffer), 4400 compose->account->name, 4401 strlen("From: "), 4402 FALSE); 4403 err |= (fprintf(fdest, "%s <%s>", 4404 buffer, 4405 compose->account->address) < 0); 4406 } else 4407 err |= (fprintf(fdest, "%s", 4408 compose->account->address) < 0); 4409 err |= (fputs(")", fdest) == EOF); 4410 } 4411 } 4412 4413 g_free(buf); 4414 buf = NULL; 4415 if (fputs("\n", fdest) == -1) 4416 goto error; 4417 } 4418 4419 if (err) 4420 goto error; 4421 4422 if (compose_redirect_write_headers(compose, fdest)) 4423 goto error; 4424 4425 while ((len = fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) { 4426 if (fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len) 4427 goto error; 4428 } 4429 4430 fclose(fp); 4431 4432 return 0; 4433 4434 error: 4435 fclose(fp); 4436 4437 return -1; 4438 } 4439 4440 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts) 4441 { 4442 GtkTextBuffer *buffer; 4443 GtkTextIter start, end, tmp; 4444 gchar *chars, *tmp_enc_file = NULL, *content; 4445 gchar *buf, *msg; 4446 const gchar *out_codeset; 4447 EncodingType encoding = ENC_UNKNOWN; 4448 MimeInfo *mimemsg, *mimetext; 4449 gint line; 4450 const gchar *src_codeset = CS_INTERNAL; 4451 gchar *from_addr = NULL; 4452 gchar *from_name = NULL; 4453 FolderItem *outbox; 4454 4455 if (action == COMPOSE_WRITE_FOR_SEND) { 4456 attach_parts = TRUE; 4457 4458 /* We're sending the message, generate a Message-ID 4459 * if necessary. */ 4460 if (compose->msgid == NULL && 4461 compose->account->gen_msgid) { 4462 compose->msgid = prefs_account_generate_msgid(compose->account); 4463 } 4464 } 4465 4466 /* create message MimeInfo */ 4467 mimemsg = procmime_mimeinfo_new(); 4468 mimemsg->type = MIMETYPE_MESSAGE; 4469 mimemsg->subtype = g_strdup("rfc822"); 4470 mimemsg->content = MIMECONTENT_MEM; 4471 mimemsg->tmp = TRUE; /* must free content later */ 4472 mimemsg->data.mem = compose_get_header(compose); 4473 4474 /* Create text part MimeInfo */ 4475 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text)); 4476 gtk_text_buffer_get_end_iter(buffer, &end); 4477 tmp = end; 4478 4479 /* We make sure that there is a newline at the end. */ 4480 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) { 4481 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE); 4482 if (*chars != '\n') { 4483 gtk_text_buffer_insert(buffer, &end, "\n", 1); 4484 } 4485 g_free(chars); 4486 } 4487 4488 /* get all composed text */ 4489 gtk_text_buffer_get_start_iter(buffer, &start); 4490 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); 4491 4492 out_codeset = CS_UTF_8; 4493 4494 if (encoding == ENC_UNKNOWN) { 4495 if (prefs_common.encoding_method == CTE_BASE64) 4496 encoding = ENC_BASE64; 4497 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE) 4498 encoding = ENC_QUOTED_PRINTABLE; 4499 else if (prefs_common.encoding_method == CTE_8BIT) 4500 encoding = ENC_8BIT; 4501 else 4502 encoding = procmime_get_encoding_for_charset(out_codeset); 4503 } 4504 4505 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n", 4506 src_codeset, out_codeset, procmime_get_encoding_str(encoding)); 4507 4508 if (action == COMPOSE_WRITE_FOR_SEND) { 4509 codeconv_set_strict(TRUE); 4510 buf = conv_codeset_strdup(chars, src_codeset, out_codeset); 4511 codeconv_set_strict(FALSE); 4512 4513 if (!buf) { 4514 AlertValue aval; 4515 4516 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n" 4517 "to the specified %s charset.\n" 4518 "Send it as %s?"), out_codeset, src_codeset); 4519 aval = alertpanel_full(_("Error"), msg, NULL, _("_Cancel"), 4520 NULL, _("_Send"), NULL, NULL, ALERTFOCUS_SECOND, FALSE, 4521 NULL, ALERT_ERROR); 4522 g_free(msg); 4523 4524 if (aval != G_ALERTALTERNATE) { 4525 g_free(chars); 4526 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION; 4527 } else { 4528 buf = chars; 4529 out_codeset = src_codeset; 4530 chars = NULL; 4531 } 4532 } 4533 } else { 4534 buf = chars; 4535 out_codeset = src_codeset; 4536 chars = NULL; 4537 } 4538 g_free(chars); 4539 4540 /* check for line length limit */ 4541 if (action == COMPOSE_WRITE_FOR_SEND && 4542 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 && 4543 check_line_length(buf, 1000, &line) < 0) { 4544 if (encoding == ENC_8BIT) { 4545 AlertValue aval; 4546 4547 msg = g_strdup_printf 4548 (_("Line %d exceeds the line length limit (998 bytes).\n" 4549 "The contents of the message might be broken on the way " 4550 "to the recipient."), line + 1); 4551 aval = alertpanel(_("Warning"), msg, NULL, _("Send safely"), NULL, _("Send as-is"), 4552 NULL, NULL, ALERTFOCUS_FIRST); 4553 g_free(msg); 4554 if (aval != G_ALERTALTERNATE) 4555 encoding = ENC_QUOTED_PRINTABLE; 4556 } else { 4557 debug_print("Line %d exceeds the line length limit (998 bytes), " 4558 "switching to QP transfer encoding\n", line + 1); 4559 encoding = ENC_QUOTED_PRINTABLE; 4560 } 4561 } 4562 4563 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) { 4564 if (!strncmp(buf, "From ", sizeof("From ")-1) || 4565 strstr(buf, "\nFrom ") != NULL) { 4566 encoding = ENC_QUOTED_PRINTABLE; 4567 } 4568 } 4569 4570 mimetext = procmime_mimeinfo_new(); 4571 mimetext->content = MIMECONTENT_MEM; 4572 mimetext->tmp = TRUE; /* must free content later */ 4573 /* dup'ed because procmime_encode_content can turn it into a tmpfile 4574 * and free the data, which we need later. */ 4575 mimetext->data.mem = g_strdup(buf); 4576 mimetext->type = MIMETYPE_TEXT; 4577 mimetext->subtype = g_strdup("plain"); 4578 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"), 4579 g_strdup(out_codeset)); 4580 4581 debug_print("main text: %" G_GSIZE_FORMAT " bytes encoded as %s in %d\n", 4582 strlen(buf), out_codeset, encoding); 4583 4584 if (encoding != ENC_UNKNOWN) 4585 procmime_encode_content(mimetext, encoding); 4586 4587 /* append attachment parts */ 4588 if (compose_use_attach(compose) && attach_parts) { 4589 MimeInfo *mimempart; 4590 gchar *boundary = NULL; 4591 mimempart = procmime_mimeinfo_new(); 4592 mimempart->content = MIMECONTENT_EMPTY; 4593 mimempart->type = MIMETYPE_MULTIPART; 4594 mimempart->subtype = g_strdup("mixed"); 4595 4596 do { 4597 g_free(boundary); 4598 boundary = generate_mime_boundary(NULL); 4599 } while (strstr(buf, boundary) != NULL); 4600 4601 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"), 4602 boundary); 4603 4604 mimetext->disposition = DISPOSITIONTYPE_INLINE; 4605 4606 g_node_append(mimempart->node, mimetext->node); 4607 g_node_append(mimemsg->node, mimempart->node); 4608 4609 if (compose_add_attachments(compose, mimempart, action) < 0) 4610 return COMPOSE_QUEUE_ERROR_NO_MSG; 4611 } else 4612 g_node_append(mimemsg->node, mimetext->node); 4613 4614 g_free(buf); 4615 4616 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) { 4617 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1); 4618 /* extract name and address */ 4619 if (strstr(spec, " <") && strstr(spec, ">")) { 4620 from_addr = g_strdup(strrchr(spec, '<')+1); 4621 *(strrchr(from_addr, '>')) = '\0'; 4622 from_name = g_strdup(spec); 4623 *(strrchr(from_name, '<')) = '\0'; 4624 } else { 4625 from_name = NULL; 4626 from_addr = NULL; 4627 } 4628 g_free(spec); 4629 } 4630 4631 g_free(from_name); 4632 g_free(from_addr); 4633 4634 if (tmp_enc_file) 4635 g_free(tmp_enc_file); 4636 4637 procmime_write_mimeinfo(mimemsg, fp); 4638 4639 procmime_mimeinfo_free_all(&mimemsg); 4640 4641 return 0; 4642 } 4643 4644 static gint compose_write_body_to_file(Compose *compose, const gchar *file) 4645 { 4646 GtkTextBuffer *buffer; 4647 GtkTextIter start, end; 4648 FILE *fp; 4649 size_t len; 4650 gchar *chars, *tmp; 4651 4652 if ((fp = fopen(file, "wb")) == NULL) { 4653 warn("open %s", file); 4654 return -1; 4655 } 4656 4657 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text)); 4658 gtk_text_buffer_get_start_iter(buffer, &start); 4659 gtk_text_buffer_get_end_iter(buffer, &end); 4660 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); 4661 4662 chars = conv_codeset_strdup 4663 (tmp, CS_INTERNAL, conv_get_locale_charset_str()); 4664 4665 g_free(tmp); 4666 if (!chars) { 4667 fclose(fp); 4668 unlink(file); 4669 return -1; 4670 } 4671 /* write body */ 4672 len = strlen(chars); 4673 if (fwrite(chars, sizeof(gchar), len, fp) != len) { 4674 FILE_OP_ERROR(file, "fwrite"); 4675 g_free(chars); 4676 fclose(fp); 4677 unlink(file); 4678 return -1; 4679 } 4680 4681 g_free(chars); 4682 4683 if (fclose(fp) == EOF) { 4684 FILE_OP_ERROR(file, "fclose"); 4685 unlink(file); 4686 return -1; 4687 } 4688 return 0; 4689 } 4690 4691 static gint compose_remove_reedit_target(Compose *compose, gboolean force) 4692 { 4693 FolderItem *item; 4694 MsgInfo *msginfo = compose->targetinfo; 4695 4696 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1); 4697 if (!msginfo) return -1; 4698 4699 if (!force && MSG_IS_LOCKED(msginfo->flags)) 4700 return 0; 4701 4702 item = msginfo->folder; 4703 cm_return_val_if_fail(item != NULL, -1); 4704 4705 if (procmsg_msg_exist(msginfo) && 4706 (folder_has_parent_of_type(item, F_QUEUE) || 4707 folder_has_parent_of_type(item, F_DRAFT) 4708 || msginfo == compose->autosaved_draft)) { 4709 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) { 4710 g_warning("can't remove the old message"); 4711 return -1; 4712 } else { 4713 debug_print("removed reedit target %d\n", msginfo->msgnum); 4714 } 4715 } 4716 4717 return 0; 4718 } 4719 4720 static void compose_remove_draft(Compose *compose) 4721 { 4722 FolderItem *drafts; 4723 MsgInfo *msginfo = compose->targetinfo; 4724 drafts = account_get_special_folder(compose->account, F_DRAFT); 4725 4726 if (procmsg_msg_exist(msginfo)) { 4727 folder_item_remove_msg(drafts, msginfo->msgnum); 4728 } 4729 4730 } 4731 4732 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath, 4733 gboolean remove_reedit_target) 4734 { 4735 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target); 4736 } 4737 4738 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item, 4739 gchar **msgpath, gboolean perform_checks, 4740 gboolean remove_reedit_target) 4741 { 4742 FolderItem *queue; 4743 gchar *tmp; 4744 FILE *fp; 4745 GSList *cur; 4746 gint num; 4747 PrefsAccount *mailac = NULL; 4748 gboolean err = FALSE; 4749 4750 debug_print("queueing message...\n"); 4751 cm_return_val_if_fail(compose->account != NULL, -1); 4752 4753 if (compose_check_entries(compose, perform_checks) == FALSE) { 4754 if (compose->batch) { 4755 gtk_widget_show_all(compose->window); 4756 } 4757 return COMPOSE_QUEUE_ERROR_NO_MSG; 4758 } 4759 4760 if (!compose->to_list) { 4761 g_warning("can't get recipient list"); 4762 return COMPOSE_QUEUE_ERROR_NO_MSG; 4763 } 4764 4765 if (compose->to_list) { 4766 mailac = compose->account; 4767 if (!mailac && cur_account) 4768 mailac = cur_account; 4769 else if (!mailac && !(mailac = compose_current_mail_account())) { 4770 alertpanel_error(_("No account for sending mails available!")); 4771 return COMPOSE_QUEUE_ERROR_NO_MSG; 4772 } 4773 } 4774 4775 /* write queue header */ 4776 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(), 4777 G_DIR_SEPARATOR, compose, (guint) rand()); 4778 debug_print("queuing to %s\n", tmp); 4779 if ((fp = g_fopen(tmp, "w+b")) == NULL) { 4780 warn("open %s", tmp); 4781 g_free(tmp); 4782 return COMPOSE_QUEUE_ERROR_WITH_ERRNO; 4783 } 4784 4785 /* queueing variables */ 4786 err |= (fprintf(fp, "AF:\n") < 0); 4787 err |= (fprintf(fp, "NF:0\n") < 0); 4788 err |= (fprintf(fp, "PS:10\n") < 0); 4789 err |= (fprintf(fp, "SRH:1\n") < 0); 4790 err |= (fprintf(fp, "SFN:\n") < 0); 4791 err |= (fprintf(fp, "DSR:\n") < 0); 4792 if (compose->msgid) 4793 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0); 4794 else 4795 err |= (fprintf(fp, "MID:\n") < 0); 4796 err |= (fprintf(fp, "CFG:\n") < 0); 4797 err |= (fprintf(fp, "PT:0\n") < 0); 4798 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0); 4799 err |= (fprintf(fp, "RQ:\n") < 0); 4800 if (mailac) 4801 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0); 4802 else 4803 err |= (fprintf(fp, "SSV:\n") < 0); 4804 err |= (fprintf(fp, "NSV:\n") < 0); 4805 err |= (fprintf(fp, "SSH:\n") < 0); 4806 /* write recipient list */ 4807 if (compose->to_list) { 4808 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0); 4809 for (cur = compose->to_list->next; cur != NULL; 4810 cur = cur->next) 4811 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0); 4812 err |= (fprintf(fp, "\n") < 0); 4813 } 4814 /* account IDs */ 4815 if (mailac) 4816 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0); 4817 4818 /* Save copy folder */ 4819 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) { 4820 gchar *savefolderid; 4821 4822 savefolderid = compose_get_save_to(compose); 4823 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0); 4824 g_free(savefolderid); 4825 } 4826 /* Message-ID of message replying to */ 4827 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) { 4828 gchar *folderid = NULL; 4829 4830 if (compose->replyinfo->folder) 4831 folderid = folder_item_get_identifier(compose->replyinfo->folder); 4832 if (folderid == NULL) 4833 folderid = g_strdup("NULL"); 4834 4835 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0); 4836 g_free(folderid); 4837 } 4838 /* Message-ID of message forwarding to */ 4839 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) { 4840 gchar *folderid = NULL; 4841 4842 if (compose->fwdinfo->folder) 4843 folderid = folder_item_get_identifier(compose->fwdinfo->folder); 4844 if (folderid == NULL) 4845 folderid = g_strdup("NULL"); 4846 4847 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0); 4848 g_free(folderid); 4849 } 4850 4851 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0); 4852 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0); 4853 4854 /* end of headers */ 4855 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0); 4856 4857 if (compose->redirect_filename != NULL) { 4858 if (compose_redirect_write_to_file(compose, fp) < 0) { 4859 fclose(fp); 4860 unlink(tmp); 4861 g_free(tmp); 4862 return COMPOSE_QUEUE_ERROR_WITH_ERRNO; 4863 } 4864 } else { 4865 gint result = 0; 4866 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) { 4867 fclose(fp); 4868 unlink(tmp); 4869 g_free(tmp); 4870 return result; 4871 } 4872 } 4873 if (err == TRUE) { 4874 g_warning("failed to write queue message"); 4875 fclose(fp); 4876 unlink(tmp); 4877 g_free(tmp); 4878 return COMPOSE_QUEUE_ERROR_WITH_ERRNO; 4879 } 4880 if (fclose(fp) == EOF) { 4881 FILE_OP_ERROR(tmp, "fclose"); 4882 unlink(tmp); 4883 g_free(tmp); 4884 return COMPOSE_QUEUE_ERROR_WITH_ERRNO; 4885 } 4886 4887 if (item && *item) { 4888 queue = *item; 4889 } else { 4890 queue = account_get_special_folder(compose->account, F_QUEUE); 4891 } 4892 if (!queue) { 4893 g_warning("can't find queue folder"); 4894 unlink(tmp); 4895 g_free(tmp); 4896 return COMPOSE_QUEUE_ERROR_NO_MSG; 4897 } 4898 folder_item_scan(queue); 4899 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) { 4900 g_warning("can't queue the message"); 4901 unlink(tmp); 4902 g_free(tmp); 4903 return COMPOSE_QUEUE_ERROR_NO_MSG; 4904 } 4905 4906 if (msgpath == NULL) { 4907 unlink(tmp); 4908 g_free(tmp); 4909 } else 4910 *msgpath = tmp; 4911 4912 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) { 4913 MsgInfo *mi = folder_item_get_msginfo(queue, num); 4914 if (mi) { 4915 procmsg_msginfo_change_flags(mi, 4916 compose->targetinfo->flags.perm_flags, 4917 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE), 4918 0, 0); 4919 4920 g_slist_free(mi->tags); 4921 mi->tags = g_slist_copy(compose->targetinfo->tags); 4922 procmsg_msginfo_free(&mi); 4923 } 4924 } 4925 4926 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) { 4927 compose_remove_reedit_target(compose, FALSE); 4928 } 4929 4930 if ((msgnum != NULL) && (item != NULL)) { 4931 *msgnum = num; 4932 *item = queue; 4933 } 4934 4935 return COMPOSE_QUEUE_SUCCESS; 4936 } 4937 4938 static int compose_add_attachments(Compose *compose, MimeInfo *parent, gint action) 4939 { 4940 AttachInfo *ainfo; 4941 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist); 4942 MimeInfo *mimepart; 4943 GStatBuf statbuf; 4944 goffset size; 4945 gchar *type, *subtype; 4946 GtkTreeModel *model; 4947 GtkTreeIter iter; 4948 4949 model = gtk_tree_view_get_model(tree_view); 4950 4951 if (!gtk_tree_model_get_iter_first(model, &iter)) 4952 return 0; 4953 do { 4954 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1); 4955 4956 if (!is_file_exist(ainfo->file)) { 4957 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file); 4958 AlertValue val = alertpanel_full(_("Warning"), msg, NULL, 4959 action == COMPOSE_WRITE_FOR_STORE? _("Cancel drafting"): _("Cancel sending"), 4960 NULL, _("Ignore attachment"), NULL, NULL, 4961 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING); 4962 g_free(msg); 4963 if (val == G_ALERTDEFAULT) { 4964 return -1; 4965 } 4966 continue; 4967 } 4968 if (g_stat(ainfo->file, &statbuf) < 0) 4969 return -1; 4970 size = statbuf.st_size; 4971 4972 mimepart = procmime_mimeinfo_new(); 4973 mimepart->content = MIMECONTENT_FILE; 4974 mimepart->data.filename = g_strdup(ainfo->file); 4975 mimepart->tmp = FALSE; /* or we destroy our attachment */ 4976 mimepart->offset = 0; 4977 mimepart->length = size; 4978 4979 type = g_strdup(ainfo->content_type); 4980 4981 if (!strchr(type, '/')) { 4982 g_free(type); 4983 type = g_strdup("application/octet-stream"); 4984 } 4985 4986 subtype = strchr(type, '/') + 1; 4987 *(subtype - 1) = '\0'; 4988 mimepart->type = procmime_get_media_type(type); 4989 mimepart->subtype = g_strdup(subtype); 4990 g_free(type); 4991 4992 if (mimepart->type == MIMETYPE_MESSAGE && 4993 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) { 4994 mimepart->disposition = DISPOSITIONTYPE_INLINE; 4995 } else if (mimepart->type == MIMETYPE_TEXT) { 4996 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) { 4997 /* Text parts with no name come from multipart/alternative 4998 * forwards. Make sure the recipient won't look at the 4999 * original HTML part by mistake. */ 5000 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT; 5001 ainfo->name = g_strdup_printf(_("Original %s part"), 5002 mimepart->subtype); 5003 } 5004 if (ainfo->charset) 5005 g_hash_table_insert(mimepart->typeparameters, 5006 g_strdup("charset"), g_strdup(ainfo->charset)); 5007 } 5008 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) { 5009 if (mimepart->type == MIMETYPE_APPLICATION && 5010 !g_strcmp0(mimepart->subtype, "octet-stream")) 5011 g_hash_table_insert(mimepart->typeparameters, 5012 g_strdup("name"), g_strdup(ainfo->name)); 5013 g_hash_table_insert(mimepart->dispositionparameters, 5014 g_strdup("filename"), g_strdup(ainfo->name)); 5015 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT; 5016 } 5017 5018 if (mimepart->type == MIMETYPE_MESSAGE 5019 || mimepart->type == MIMETYPE_MULTIPART) 5020 ainfo->encoding = ENC_BINARY; 5021 else if (compose->fwdinfo != NULL) { 5022 if (ainfo->encoding == ENC_7BIT) 5023 ainfo->encoding = ENC_QUOTED_PRINTABLE; 5024 else if (ainfo->encoding == ENC_8BIT) 5025 ainfo->encoding = ENC_BASE64; 5026 } 5027 5028 procmime_encode_content(mimepart, ainfo->encoding); 5029 5030 g_node_append(parent->node, mimepart->node); 5031 } while (gtk_tree_model_iter_next(model, &iter)); 5032 5033 return 0; 5034 } 5035 5036 static gchar *compose_quote_list_of_addresses(gchar *str) 5037 { 5038 GSList *list = NULL, *item = NULL; 5039 gchar *qname = NULL, *faddr = NULL, *result = NULL; 5040 5041 list = address_list_append_with_comments(list, str); 5042 for (item = list; item != NULL; item = item->next) { 5043 gchar *spec = item->data; 5044 gchar *endofname = strstr(spec, " <"); 5045 if (endofname != NULL) { 5046 gchar * qqname; 5047 *endofname = '\0'; 5048 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL); 5049 qqname = escape_internal_quotes(qname, '"'); 5050 *endofname = ' '; 5051 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */ 5052 gchar *addr = g_strdup(endofname); 5053 gchar *name = (qqname != qname)? qqname: g_strdup(qname); 5054 faddr = g_strconcat(name, addr, NULL); 5055 g_free(name); 5056 g_free(addr); 5057 debug_print("new auto-quoted address: '%s'\n", faddr); 5058 } 5059 } 5060 if (result == NULL) 5061 result = g_strdup((faddr != NULL)? faddr: spec); 5062 else { 5063 gchar *tmp = g_strconcat(result, 5064 ", ", 5065 (faddr != NULL)? faddr: spec, 5066 NULL); 5067 g_free(result); 5068 result = tmp; 5069 } 5070 if (faddr != NULL) { 5071 g_free(faddr); 5072 faddr = NULL; 5073 } 5074 } 5075 slist_free_strings_full(list); 5076 5077 return result; 5078 } 5079 5080 #define IS_IN_CUSTOM_HEADER(header) \ 5081 (compose->account->add_customhdr && \ 5082 custom_header_find(compose->account->customhdr_list, header) != NULL) 5083 5084 static void compose_add_headerfield_from_headerlist(Compose *compose, 5085 GString *header, 5086 const gchar *fieldname, 5087 const gchar *seperator) 5088 { 5089 gchar *str, *fieldname_w_colon; 5090 gboolean add_field = FALSE; 5091 GSList *list; 5092 ComposeHeaderEntry *headerentry; 5093 const gchar *headerentryname; 5094 const gchar *trans_fieldname; 5095 GString *fieldstr; 5096 5097 if (IS_IN_CUSTOM_HEADER(fieldname)) 5098 return; 5099 5100 debug_print("Adding %s-fields\n", fieldname); 5101 5102 fieldstr = g_string_sized_new(64); 5103 5104 fieldname_w_colon = g_strconcat(fieldname, ":", NULL); 5105 trans_fieldname = fieldname_w_colon; 5106 5107 for (list = compose->header_list; list; list = list->next) { 5108 headerentry = ((ComposeHeaderEntry *)list->data); 5109 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))); 5110 5111 if (!g_utf8_collate(trans_fieldname, headerentryname)) { 5112 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1); 5113 g_strstrip(ustr); 5114 str = compose_quote_list_of_addresses(ustr); 5115 g_free(ustr); 5116 if (str != NULL && str[0] != '\0') { 5117 if (add_field) 5118 g_string_append(fieldstr, seperator); 5119 g_string_append(fieldstr, str); 5120 add_field = TRUE; 5121 } 5122 g_free(str); 5123 } 5124 } 5125 if (add_field) { 5126 gchar *buf; 5127 5128 buf = g_new0(gchar, fieldstr->len * 4 + 256); 5129 compose_convert_header 5130 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str, 5131 strlen(fieldname) + 2, TRUE); 5132 g_string_append_printf(header, "%s: %s\n", fieldname, buf); 5133 g_free(buf); 5134 } 5135 5136 g_free(fieldname_w_colon); 5137 g_string_free(fieldstr, TRUE); 5138 5139 return; 5140 } 5141 5142 static gchar *compose_get_manual_headers_info(Compose *compose) 5143 { 5144 GString *sh_header = g_string_new(" "); 5145 GSList *list; 5146 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL}; 5147 5148 for (list = compose->header_list; list; list = list->next) { 5149 ComposeHeaderEntry *headerentry; 5150 gchar *tmp; 5151 gchar *headername; 5152 gchar *headername_wcolon; 5153 gchar **string; 5154 gboolean standard_header = FALSE; 5155 5156 headerentry = ((ComposeHeaderEntry *)list->data); 5157 5158 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))))); 5159 g_strstrip(tmp); 5160 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) { 5161 g_free(tmp); 5162 continue; 5163 } 5164 5165 if (!strstr(tmp, ":")) { 5166 headername_wcolon = g_strconcat(tmp, ":", NULL); 5167 headername = g_strdup(tmp); 5168 } else { 5169 headername_wcolon = g_strdup(tmp); 5170 headername = g_strdup(strtok(tmp, ":")); 5171 } 5172 g_free(tmp); 5173 5174 string = std_headers; 5175 while (*string != NULL) { 5176 if (!strcmp(*string, headername_wcolon)) 5177 standard_header = TRUE; 5178 string++; 5179 } 5180 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) 5181 g_string_append_printf(sh_header, "%s ", headername); 5182 g_free(headername); 5183 g_free(headername_wcolon); 5184 } 5185 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */ 5186 return g_string_free(sh_header, FALSE); 5187 } 5188 5189 static gchar *compose_get_header(Compose *compose) 5190 { 5191 gchar date[RFC822_DATE_BUFFSIZE]; 5192 gchar buf[BUFFSIZE]; 5193 const gchar *entry_str; 5194 gchar *str; 5195 gchar *name; 5196 GSList *list; 5197 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL}; 5198 GString *header; 5199 gchar *from_name = NULL, *from_address = NULL; 5200 gchar *tmp; 5201 5202 cm_return_val_if_fail(compose->account != NULL, NULL); 5203 cm_return_val_if_fail(compose->account->address != NULL, NULL); 5204 5205 header = g_string_sized_new(64); 5206 5207 /* Date */ 5208 if (prefs_common.hide_timezone) 5209 get_rfc822_date_hide_tz(date, sizeof(date)); 5210 else 5211 get_rfc822_date(date, sizeof(date)); 5212 g_string_append_printf(header, "Date: %s\n", date); 5213 5214 /* From */ 5215 5216 if (compose->account->name && *compose->account->name) { 5217 gchar *buf; 5218 QUOTE_IF_REQUIRED(buf, compose->account->name); 5219 tmp = g_strdup_printf("%s <%s>", 5220 buf, compose->account->address); 5221 } else { 5222 tmp = g_strdup_printf("%s", 5223 compose->account->address); 5224 } 5225 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp) 5226 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) { 5227 /* use default */ 5228 from_name = compose->account->name ? g_strdup(compose->account->name):NULL; 5229 from_address = g_strdup(compose->account->address); 5230 } else { 5231 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1); 5232 /* extract name and address */ 5233 if (strstr(spec, " <") && strstr(spec, ">")) { 5234 from_address = g_strdup(strrchr(spec, '<')+1); 5235 *(strrchr(from_address, '>')) = '\0'; 5236 from_name = g_strdup(spec); 5237 *(strrchr(from_name, '<')) = '\0'; 5238 } else { 5239 from_name = NULL; 5240 from_address = g_strdup(spec); 5241 } 5242 g_free(spec); 5243 } 5244 g_free(tmp); 5245 5246 5247 if (from_name && *from_name) { 5248 gchar *qname; 5249 compose_convert_header 5250 (compose, buf, sizeof(buf), from_name, 5251 strlen("From: "), TRUE); 5252 QUOTE_IF_REQUIRED(name, buf); 5253 qname = escape_internal_quotes(name, '"'); 5254 5255 g_string_append_printf(header, "From: %s <%s>\n", 5256 qname, from_address); 5257 if (qname != name) 5258 g_free(qname); 5259 } else { 5260 g_string_append_printf(header, "From: %s\n", from_address); 5261 } 5262 g_free(from_name); 5263 g_free(from_address); 5264 5265 compose_add_headerfield_from_headerlist(compose, header, "To", ", "); 5266 5267 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", "); 5268 5269 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1); 5270 5271 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) { 5272 g_strstrip(str); 5273 if (*str != '\0') { 5274 compose_convert_header(compose, buf, sizeof(buf), str, 5275 strlen("Subject: "), FALSE); 5276 g_string_append_printf(header, "Subject: %s\n", buf); 5277 } 5278 } 5279 g_free(str); 5280 5281 if (compose->msgid != NULL && strlen(compose->msgid) > 0) { 5282 g_string_append_printf(header, "Message-ID: <%s>\n", 5283 compose->msgid); 5284 } 5285 5286 if (compose->remove_references == FALSE) { 5287 if (compose->inreplyto && compose->to_list) 5288 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto); 5289 if (compose->references) 5290 g_string_append_printf(header, "References: %s\n", compose->references); 5291 } 5292 5293 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ","); 5294 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", "); 5295 5296 /* Program version and system info */ 5297 if (compose->account->gen_xmailer && 5298 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer")) { 5299 g_string_append_printf(header, "X-Mailer: Talons (https://git.olowe.co/talons)\n"); 5300 } 5301 5302 /* custom headers */ 5303 if (compose->account->add_customhdr) { 5304 GSList *cur; 5305 5306 for (cur = compose->account->customhdr_list; cur != NULL; 5307 cur = cur->next) { 5308 CustomHeader *chdr = (CustomHeader *)cur->data; 5309 5310 if (custom_header_is_allowed(chdr->name) 5311 && chdr->value != NULL 5312 && *(chdr->value) != '\0') { 5313 compose_convert_header 5314 (compose, buf, sizeof(buf), 5315 chdr->value, 5316 strlen(chdr->name) + 2, FALSE); 5317 g_string_append_printf(header, "%s: %s\n", chdr->name, buf); 5318 } 5319 } 5320 } 5321 5322 /* get special headers */ 5323 for (list = compose->header_list; list; list = list->next) { 5324 ComposeHeaderEntry *headerentry; 5325 gchar *tmp; 5326 gchar *headername; 5327 gchar *headername_wcolon; 5328 gchar *headervalue; 5329 gchar **string; 5330 gboolean standard_header = FALSE; 5331 5332 headerentry = ((ComposeHeaderEntry *)list->data); 5333 5334 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))))); 5335 g_strstrip(tmp); 5336 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) { 5337 g_free(tmp); 5338 continue; 5339 } 5340 5341 if (!strstr(tmp, ":")) { 5342 headername_wcolon = g_strconcat(tmp, ":", NULL); 5343 headername = g_strdup(tmp); 5344 } else { 5345 headername_wcolon = g_strdup(tmp); 5346 headername = g_strdup(strtok(tmp, ":")); 5347 } 5348 g_free(tmp); 5349 5350 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry)); 5351 Xstrdup_a(headervalue, entry_str, { 5352 g_free(headername); 5353 g_free(headername_wcolon); 5354 g_string_free(header, TRUE); 5355 return NULL; 5356 }); 5357 subst_char(headervalue, '\r', ' '); 5358 subst_char(headervalue, '\n', ' '); 5359 g_strstrip(headervalue); 5360 if (*headervalue != '\0') { 5361 string = std_headers; 5362 while (*string != NULL && !standard_header) { 5363 if (!strcmp(*string, headername_wcolon)) 5364 standard_header = TRUE; 5365 string++; 5366 } 5367 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) { 5368 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue); 5369 } 5370 } 5371 g_free(headername); 5372 g_free(headername_wcolon); 5373 } 5374 5375 return g_string_free(header, FALSE); 5376 } 5377 5378 #undef IS_IN_CUSTOM_HEADER 5379 5380 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src, 5381 gint header_len, gboolean addr_field) 5382 { 5383 gchar *tmpstr = NULL; 5384 const gchar *out_codeset = NULL; 5385 5386 cm_return_if_fail(src != NULL); 5387 cm_return_if_fail(dest != NULL); 5388 5389 if (len < 1) return; 5390 5391 tmpstr = g_strdup(src); 5392 5393 subst_char(tmpstr, '\n', ' '); 5394 subst_char(tmpstr, '\r', ' '); 5395 g_strchomp(tmpstr); 5396 5397 if (!g_utf8_validate(tmpstr, -1, NULL)) { 5398 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1); 5399 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr); 5400 g_free(tmpstr); 5401 tmpstr = mybuf; 5402 } 5403 5404 codeconv_set_strict(TRUE); 5405 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field, 5406 conv_get_charset_str(compose->out_encoding)); 5407 codeconv_set_strict(FALSE); 5408 5409 if (!dest || *dest == '\0') { 5410 gchar *test_conv_global_out = NULL; 5411 gchar *test_conv_reply = NULL; 5412 5413 /* automatic mode. be automatic. */ 5414 codeconv_set_strict(TRUE); 5415 5416 out_codeset = conv_get_outgoing_charset_str(); 5417 if (out_codeset) { 5418 debug_print("trying to convert to %s\n", out_codeset); 5419 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset); 5420 } 5421 5422 if (!test_conv_global_out && compose->orig_charset 5423 && strcmp(compose->orig_charset, CS_US_ASCII)) { 5424 out_codeset = compose->orig_charset; 5425 debug_print("failure; trying to convert to %s\n", out_codeset); 5426 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset); 5427 } 5428 5429 if (!test_conv_global_out && !test_conv_reply) { 5430 /* we're lost */ 5431 out_codeset = CS_INTERNAL; 5432 debug_print("finally using %s\n", out_codeset); 5433 } 5434 g_free(test_conv_global_out); 5435 g_free(test_conv_reply); 5436 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field, 5437 out_codeset); 5438 codeconv_set_strict(FALSE); 5439 } 5440 g_free(tmpstr); 5441 } 5442 5443 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data) 5444 { 5445 gchar *address; 5446 5447 cm_return_if_fail(user_data != NULL); 5448 5449 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data))); 5450 g_strstrip(address); 5451 if (*address != '\0') { 5452 gchar *name = procheader_get_fromname(address); 5453 extract_address(address); 5454 addressbook_add_contact(name, address, NULL, NULL); 5455 } 5456 g_free(address); 5457 } 5458 5459 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data) 5460 { 5461 GtkWidget *menuitem; 5462 gchar *address; 5463 5464 cm_return_if_fail(menu != NULL); 5465 cm_return_if_fail(GTK_IS_MENU_SHELL(menu)); 5466 5467 menuitem = gtk_separator_menu_item_new(); 5468 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem); 5469 gtk_widget_show(menuitem); 5470 5471 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book")); 5472 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem); 5473 5474 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry))); 5475 g_strstrip(address); 5476 if (*address == '\0') { 5477 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE); 5478 } 5479 5480 g_signal_connect(G_OBJECT(menuitem), "activate", 5481 G_CALLBACK(compose_add_to_addressbook_cb), entry); 5482 gtk_widget_show(menuitem); 5483 } 5484 5485 void compose_add_extra_header(gchar *header, GtkListStore *model) 5486 { 5487 GtkTreeIter iter; 5488 if (strcmp(header, "")) { 5489 COMBOBOX_ADD(model, header, COMPOSE_TO); 5490 } 5491 } 5492 5493 void compose_add_extra_header_entries(GtkListStore *model) 5494 { 5495 FILE *exh; 5496 gchar *exhrc; 5497 gchar buf[BUFFSIZE]; 5498 gint lastc; 5499 5500 if (extra_headers == NULL) { 5501 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL); 5502 if ((exh = g_fopen(exhrc, "rb")) == NULL) { 5503 debug_print("extra headers file not found\n"); 5504 goto extra_headers_done; 5505 } 5506 while (fgets(buf, BUFFSIZE, exh) != NULL) { 5507 lastc = strlen(buf) - 1; /* remove trailing control chars */ 5508 while (lastc >= 0 && buf[lastc] != ':') 5509 buf[lastc--] = '\0'; 5510 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') { 5511 buf[lastc] = '\0'; /* remove trailing : for comparison */ 5512 if (custom_header_is_allowed(buf)) { 5513 buf[lastc] = ':'; 5514 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf)); 5515 } 5516 else 5517 g_message("disallowed extra header line: %s\n", buf); 5518 } 5519 else { 5520 if (buf[0] != '#') 5521 g_message("invalid extra header line: %s\n", buf); 5522 } 5523 } 5524 fclose(exh); 5525 extra_headers_done: 5526 g_free(exhrc); 5527 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */ 5528 extra_headers = g_slist_reverse(extra_headers); 5529 } 5530 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model); 5531 } 5532 5533 static void compose_create_header_entry(Compose *compose) 5534 { 5535 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL}; 5536 5537 GtkWidget *combo; 5538 GtkWidget *entry; 5539 GtkWidget *button; 5540 GtkWidget *hbox; 5541 gchar **string; 5542 const gchar *header = NULL; 5543 ComposeHeaderEntry *headerentry; 5544 gboolean standard_header = FALSE; 5545 GtkListStore *model; 5546 GtkTreeIter iter; 5547 5548 headerentry = g_new0(ComposeHeaderEntry, 1); 5549 5550 /* Combo box model */ 5551 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN); 5552 COMBOBOX_ADD(model, "To:", 5553 COMPOSE_TO); 5554 COMBOBOX_ADD(model, "Cc:", 5555 COMPOSE_CC); 5556 COMBOBOX_ADD(model, "Bcc:", 5557 COMPOSE_BCC); 5558 COMBOBOX_ADD(model, "Reply-To:", 5559 COMPOSE_REPLYTO); 5560 COMBOBOX_ADD(model, "Followup-To:", 5561 COMPOSE_FOLLOWUPTO); 5562 compose_add_extra_header_entries(model); 5563 5564 /* Combo box */ 5565 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model)); 5566 GtkCellRenderer *cell = gtk_cell_renderer_text_new(); 5567 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5); 5568 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE); 5569 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0); 5570 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0); 5571 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus", 5572 G_CALLBACK(compose_grab_focus_cb), compose); 5573 gtk_widget_show(combo); 5574 5575 gtk_grid_attach(GTK_GRID(compose->header_table), combo, 0, compose->header_nextrow, 5576 1, 1); 5577 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) { 5578 const gchar *last_header_entry = gtk_entry_get_text( 5579 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo))))); 5580 string = headers; 5581 while (*string != NULL) { 5582 if (!strcmp(*string, last_header_entry)) 5583 standard_header = TRUE; 5584 string++; 5585 } 5586 if (standard_header) 5587 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo))))); 5588 } 5589 if (!compose->header_last || !standard_header) { 5590 switch(compose->account->protocol) { 5591 default: 5592 header = "To:"; 5593 break; 5594 } 5595 } 5596 if (header) 5597 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header); 5598 5599 gtk_editable_set_editable( 5600 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))), 5601 prefs_common.type_any_header); 5602 5603 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus", 5604 G_CALLBACK(compose_grab_focus_cb), compose); 5605 5606 /* Entry field with cleanup button */ 5607 button = gtk_button_new_from_icon_name("edit-clear", GTK_ICON_SIZE_MENU); 5608 gtk_widget_show(button); 5609 CLAWS_SET_TIP(button, 5610 _("Delete entry contents")); 5611 entry = gtk_entry_new(); 5612 gtk_widget_show(entry); 5613 CLAWS_SET_TIP(entry, 5614 _("Use <tab> to autocomplete from addressbook")); 5615 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); 5616 gtk_widget_show(hbox); 5617 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0); 5618 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); 5619 gtk_grid_attach(GTK_GRID(compose->header_table), hbox, 1, compose->header_nextrow, 5620 1, 1); 5621 gtk_widget_set_hexpand(hbox, TRUE); 5622 gtk_widget_set_halign(hbox, GTK_ALIGN_FILL); 5623 5624 g_signal_connect(G_OBJECT(entry), "key-press-event", 5625 G_CALLBACK(compose_headerentry_key_press_event_cb), 5626 headerentry); 5627 g_signal_connect(G_OBJECT(entry), "changed", 5628 G_CALLBACK(compose_headerentry_changed_cb), 5629 headerentry); 5630 g_signal_connect_after(G_OBJECT(entry), "grab_focus", 5631 G_CALLBACK(compose_grab_focus_cb), compose); 5632 5633 g_signal_connect(G_OBJECT(button), "clicked", 5634 G_CALLBACK(compose_headerentry_button_clicked_cb), 5635 headerentry); 5636 5637 /* email dnd */ 5638 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types, 5639 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]), 5640 GDK_ACTION_COPY | GDK_ACTION_MOVE); 5641 g_signal_connect(G_OBJECT(entry), "drag_data_received", 5642 G_CALLBACK(compose_header_drag_received_cb), 5643 entry); 5644 g_signal_connect(G_OBJECT(entry), "drag-drop", 5645 G_CALLBACK(compose_drag_drop), 5646 compose); 5647 g_signal_connect(G_OBJECT(entry), "populate-popup", 5648 G_CALLBACK(compose_entry_popup_extend), 5649 NULL); 5650 5651 address_completion_register_entry(GTK_ENTRY(entry), TRUE); 5652 5653 headerentry->compose = compose; 5654 headerentry->combo = combo; 5655 headerentry->entry = entry; 5656 headerentry->button = button; 5657 headerentry->hbox = hbox; 5658 headerentry->headernum = compose->header_nextrow; 5659 headerentry->type = PREF_NONE; 5660 5661 compose->header_nextrow++; 5662 compose->header_last = headerentry; 5663 compose->header_list = 5664 g_slist_append(compose->header_list, 5665 headerentry); 5666 } 5667 5668 static void compose_add_header_entry(Compose *compose, const gchar *header, 5669 gchar *text, ComposePrefType pref_type) 5670 { 5671 ComposeHeaderEntry *last_header = compose->header_last; 5672 gchar *tmp = g_strdup(text), *email; 5673 gboolean replyto_hdr; 5674 5675 replyto_hdr = (!strcasecmp(header, "Reply-To:") || 5676 !strcasecmp(header, "Followup-To:") || 5677 !strcasecmp(header, "In-Reply-To:")); 5678 5679 extract_address(tmp); 5680 email = g_utf8_strdown(tmp, -1); 5681 5682 if (replyto_hdr == FALSE && 5683 g_hash_table_lookup(compose->email_hashtable, email) != NULL) 5684 { 5685 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n", 5686 header, text, (gint) pref_type); 5687 g_free(email); 5688 g_free(tmp); 5689 return; 5690 } 5691 5692 if (!strcasecmp(header, "In-Reply-To:")) 5693 gtk_entry_set_text(GTK_ENTRY( 5694 gtk_bin_get_child(GTK_BIN(last_header->combo))), header); 5695 else 5696 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header); 5697 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text); 5698 last_header->type = pref_type; 5699 5700 if (replyto_hdr == FALSE) 5701 g_hash_table_insert(compose->email_hashtable, email, 5702 GUINT_TO_POINTER(1)); 5703 else 5704 g_free(email); 5705 5706 g_free(tmp); 5707 } 5708 5709 static void compose_destroy_headerentry(Compose *compose, 5710 ComposeHeaderEntry *headerentry) 5711 { 5712 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1); 5713 gchar *email; 5714 5715 extract_address(text); 5716 email = g_utf8_strdown(text, -1); 5717 g_hash_table_remove(compose->email_hashtable, email); 5718 g_free(text); 5719 g_free(email); 5720 5721 gtk_widget_destroy(headerentry->combo); 5722 gtk_widget_destroy(headerentry->entry); 5723 gtk_widget_destroy(headerentry->button); 5724 gtk_widget_destroy(headerentry->hbox); 5725 g_free(headerentry); 5726 } 5727 5728 static void compose_remove_header_entries(Compose *compose) 5729 { 5730 GSList *list; 5731 for (list = compose->header_list; list; list = list->next) 5732 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data); 5733 5734 compose->header_last = NULL; 5735 g_slist_free(compose->header_list); 5736 compose->header_list = NULL; 5737 compose->header_nextrow = 1; 5738 compose_create_header_entry(compose); 5739 } 5740 5741 static GtkWidget *compose_create_header(Compose *compose) 5742 { 5743 GtkWidget *from_optmenu_hbox; 5744 GtkWidget *header_table_main; 5745 GtkWidget *header_scrolledwin; 5746 GtkWidget *header_table; 5747 5748 /* parent with account selection and from header */ 5749 header_table_main = gtk_grid_new(); 5750 gtk_widget_show(header_table_main); 5751 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH); 5752 5753 from_optmenu_hbox = compose_account_option_menu_create(compose); 5754 gtk_grid_attach(GTK_GRID(header_table_main),from_optmenu_hbox, 0, 0, 1, 1); 5755 gtk_widget_set_hexpand(from_optmenu_hbox, TRUE); 5756 gtk_widget_set_halign(from_optmenu_hbox, GTK_ALIGN_FILL); 5757 5758 /* child with header labels and entries */ 5759 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL); 5760 gtk_widget_show(header_scrolledwin); 5761 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); 5762 5763 header_table = gtk_grid_new(); 5764 gtk_widget_show(header_table); 5765 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0); 5766 gtk_container_add(GTK_CONTAINER(header_scrolledwin), header_table); 5767 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table), 5768 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin))); 5769 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE); 5770 5771 gtk_grid_attach(GTK_GRID(header_table_main), header_scrolledwin, 0, 1, 1, 1); 5772 gtk_widget_set_vexpand(header_scrolledwin, TRUE); 5773 gtk_widget_set_valign(header_scrolledwin, GTK_ALIGN_FILL); 5774 5775 compose->header_table = header_table; 5776 compose->header_list = NULL; 5777 compose->header_nextrow = 0; 5778 5779 compose_create_header_entry(compose); 5780 5781 compose->table = NULL; 5782 5783 return header_table_main; 5784 } 5785 5786 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data) 5787 { 5788 Compose *compose = (Compose *)data; 5789 GdkEventButton event; 5790 5791 event.button = 3; 5792 event.time = gtk_get_current_event_time(); 5793 5794 return attach_button_pressed(compose->attach_clist, &event, compose); 5795 } 5796 5797 static GtkWidget *compose_create_attach(Compose *compose) 5798 { 5799 GtkWidget *attach_scrwin; 5800 GtkWidget *attach_clist; 5801 5802 GtkListStore *store; 5803 GtkCellRenderer *renderer; 5804 GtkTreeViewColumn *column; 5805 GtkTreeSelection *selection; 5806 5807 /* attachment list */ 5808 attach_scrwin = gtk_scrolled_window_new(NULL, NULL); 5809 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin), 5810 GTK_POLICY_AUTOMATIC, 5811 GTK_POLICY_AUTOMATIC); 5812 gtk_widget_set_size_request(attach_scrwin, -1, 80); 5813 5814 store = gtk_list_store_new(N_ATTACH_COLS, 5815 G_TYPE_STRING, 5816 G_TYPE_STRING, 5817 G_TYPE_STRING, 5818 G_TYPE_STRING, 5819 G_TYPE_POINTER, 5820 G_TYPE_AUTO_POINTER, 5821 -1); 5822 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model 5823 (GTK_TREE_MODEL(store))); 5824 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist); 5825 g_object_unref(store); 5826 5827 renderer = gtk_cell_renderer_text_new(); 5828 column = gtk_tree_view_column_new_with_attributes 5829 (_("Mime type"), renderer, "text", 5830 COL_MIMETYPE, NULL); 5831 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column); 5832 5833 renderer = gtk_cell_renderer_text_new(); 5834 column = gtk_tree_view_column_new_with_attributes 5835 (_("Size"), renderer, "text", 5836 COL_SIZE, NULL); 5837 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column); 5838 5839 renderer = gtk_cell_renderer_text_new(); 5840 column = gtk_tree_view_column_new_with_attributes 5841 (_("Name"), renderer, "text", 5842 COL_NAME, NULL); 5843 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column); 5844 5845 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist)); 5846 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); 5847 5848 g_signal_connect(G_OBJECT(attach_clist), "row_activated", 5849 G_CALLBACK(attach_selected), compose); 5850 g_signal_connect(G_OBJECT(attach_clist), "button_press_event", 5851 G_CALLBACK(attach_button_pressed), compose); 5852 g_signal_connect(G_OBJECT(attach_clist), "popup-menu", 5853 G_CALLBACK(popup_attach_button_pressed), compose); 5854 g_signal_connect(G_OBJECT(attach_clist), "key_press_event", 5855 G_CALLBACK(attach_key_pressed), compose); 5856 5857 /* drag and drop */ 5858 gtk_drag_dest_set(attach_clist, 5859 GTK_DEST_DEFAULT_ALL, compose_mime_types, 5860 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]), 5861 GDK_ACTION_COPY | GDK_ACTION_MOVE); 5862 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received", 5863 G_CALLBACK(compose_attach_drag_received_cb), 5864 compose); 5865 g_signal_connect(G_OBJECT(attach_clist), "drag-drop", 5866 G_CALLBACK(compose_drag_drop), 5867 compose); 5868 5869 compose->attach_scrwin = attach_scrwin; 5870 compose->attach_clist = attach_clist; 5871 5872 return attach_scrwin; 5873 } 5874 5875 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose); 5876 5877 static GtkWidget *compose_create_others(Compose *compose) 5878 { 5879 GtkWidget *table; 5880 GtkWidget *savemsg_checkbtn; 5881 GtkWidget *savemsg_combo; 5882 GtkWidget *savemsg_select; 5883 5884 guint rowcount = 0; 5885 gchar *folderidentifier; 5886 5887 /* Table for settings */ 5888 table = gtk_grid_new(); 5889 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH); 5890 gtk_widget_show(table); 5891 gtk_grid_set_row_spacing(GTK_GRID(table), VSPACING_NARROW); 5892 rowcount = 0; 5893 5894 /* Save Message to folder */ 5895 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to ")); 5896 gtk_widget_show(savemsg_checkbtn); 5897 gtk_grid_attach(GTK_GRID(table), savemsg_checkbtn, 0, rowcount, 1, 1); 5898 if (account_get_special_folder(compose->account, F_OUTBOX)) { 5899 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg); 5900 } 5901 5902 savemsg_combo = gtk_combo_box_text_new_with_entry(); 5903 compose->savemsg_checkbtn = savemsg_checkbtn; 5904 compose->savemsg_combo = savemsg_combo; 5905 gtk_widget_show(savemsg_combo); 5906 5907 if (prefs_common.compose_save_to_history) 5908 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo), 5909 prefs_common.compose_save_to_history); 5910 gtk_grid_attach(GTK_GRID(table), savemsg_combo, 1, rowcount, 1, 1); 5911 gtk_widget_set_hexpand(savemsg_combo, TRUE); 5912 gtk_widget_set_halign(savemsg_combo, GTK_ALIGN_FILL); 5913 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg); 5914 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus", 5915 G_CALLBACK(compose_grab_focus_cb), compose); 5916 if (account_get_special_folder(compose->account, F_OUTBOX)) { 5917 if (compose->account->set_sent_folder || prefs_common.savemsg) 5918 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), TRUE); 5919 else 5920 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), FALSE); 5921 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), TRUE); 5922 folderidentifier = folder_item_get_identifier(account_get_special_folder 5923 (compose->account, F_OUTBOX)); 5924 compose_set_save_to(compose, folderidentifier); 5925 g_free(folderidentifier); 5926 } 5927 5928 savemsg_select = gtkut_get_browse_file_btn(_("_Browse")); 5929 gtk_widget_show(savemsg_select); 5930 gtk_grid_attach(GTK_GRID(table), savemsg_select, 2, rowcount, 1, 1); 5931 g_signal_connect(G_OBJECT(savemsg_select), "clicked", 5932 G_CALLBACK(compose_savemsg_select_cb), 5933 compose); 5934 5935 return table; 5936 } 5937 5938 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose) 5939 { 5940 FolderItem *dest; 5941 gchar * path; 5942 5943 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE, 5944 _("Select the folder where you want to save the sent message")); 5945 if (!dest) return; 5946 5947 path = folder_item_get_identifier(dest); 5948 5949 compose_set_save_to(compose, path); 5950 g_free(path); 5951 } 5952 5953 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, GdkAtom clip, GtkTextIter *insert_place); 5954 5955 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event, 5956 Compose *compose) 5957 { 5958 if (event->button != 2) 5959 return FALSE; 5960 5961 gint prev_autowrap; 5962 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); 5963 5964 GtkTextIter iter; 5965 gint x, y; 5966 BLOCK_WRAP(); 5967 /* get the middle-click position to paste at the correct place */ 5968 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text), 5969 GTK_TEXT_WINDOW_TEXT, event->x, event->y, 5970 &x, &y); 5971 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text), &iter, x, y); 5972 5973 entry_paste_clipboard(compose, text, GDK_SELECTION_PRIMARY, &iter); 5974 UNBLOCK_WRAP(); 5975 return TRUE; 5976 } 5977 5978 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data) 5979 { 5980 Compose *compose = (Compose *)data; 5981 GdkEventButton event; 5982 5983 event.button = 3; 5984 event.time = gtk_get_current_event_time(); 5985 event.x = 0; 5986 event.y = 0; 5987 5988 return text_clicked(compose->text, &event, compose); 5989 } 5990 5991 static gboolean compose_force_window_origin = TRUE; 5992 static Compose *compose_create(PrefsAccount *account, 5993 FolderItem *folder, 5994 ComposeMode mode, 5995 gboolean batch) 5996 { 5997 Compose *compose; 5998 GtkWidget *window; 5999 GtkWidget *vbox; 6000 GtkWidget *menubar; 6001 GtkWidget *handlebox; 6002 6003 GtkWidget *notebook; 6004 6005 GtkWidget *attach_hbox; 6006 GtkWidget *attach_lab1; 6007 GtkWidget *attach_lab2; 6008 6009 GtkWidget *vbox2; 6010 6011 GtkWidget *label; 6012 GtkWidget *subject_hbox; 6013 GtkWidget *subject_frame; 6014 GtkWidget *subject_entry; 6015 GtkWidget *subject; 6016 GtkWidget *paned; 6017 6018 GtkWidget *edit_vbox; 6019 GtkWidget *scrolledwin; 6020 GtkWidget *text; 6021 GtkTextBuffer *buffer; 6022 GtkClipboard *clipboard; 6023 6024 UndoMain *undostruct; 6025 6026 GtkWidget *popupmenu; 6027 GtkActionGroup *action_group = NULL; 6028 6029 static GdkGeometry geometry; 6030 GdkRectangle workarea = {0}; 6031 6032 cm_return_val_if_fail(account != NULL, NULL); 6033 6034 default_header_bgcolor = prefs_common.color[COL_DEFAULT_HEADER_BG], 6035 default_header_color = prefs_common.color[COL_DEFAULT_HEADER], 6036 6037 debug_print("Creating write window...\n"); 6038 compose = g_new0(Compose, 1); 6039 6040 compose->batch = batch; 6041 compose->account = account; 6042 compose->folder = folder; 6043 6044 g_mutex_init(&compose->mutex); 6045 compose->set_cursor_pos = -1; 6046 6047 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose"); 6048 6049 gtk_window_set_resizable(GTK_WINDOW(window), TRUE); 6050 gtk_window_set_default_size(GTK_WINDOW(window), prefs_common.compose_width, 6051 prefs_common.compose_height); 6052 6053 gdk_monitor_get_workarea(gdk_display_get_primary_monitor(gdk_display_get_default()), 6054 &workarea); 6055 6056 if (!geometry.max_width) { 6057 geometry.max_width = workarea.width; 6058 geometry.max_height = workarea.height; 6059 } 6060 6061 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL, 6062 &geometry, GDK_HINT_MAX_SIZE); 6063 if (!geometry.min_width) { 6064 geometry.min_width = 600; 6065 geometry.min_height = 440; 6066 } 6067 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL, 6068 &geometry, GDK_HINT_MIN_SIZE); 6069 6070 if (compose_force_window_origin) 6071 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x, 6072 prefs_common.compose_y); 6073 g_signal_connect(G_OBJECT(window), "delete_event", 6074 G_CALLBACK(compose_delete_cb), compose); 6075 MANAGE_WINDOW_SIGNALS_CONNECT(window); 6076 gtk_widget_realize(window); 6077 6078 gtkut_widget_set_composer_icon(window); 6079 6080 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); 6081 gtk_container_add(GTK_CONTAINER(window), vbox); 6082 6083 compose->ui_manager = gtk_ui_manager_new(); 6084 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries, 6085 G_N_ELEMENTS(compose_entries), (gpointer)compose); 6086 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries, 6087 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose); 6088 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries, 6089 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose); 6090 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries, 6091 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose); 6092 6093 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR) 6094 6095 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU) 6096 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU) 6097 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU) 6098 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU) 6099 6100 /* Compose menu */ 6101 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM) 6102 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM) 6103 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR) 6104 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM) 6105 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM) 6106 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR) 6107 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM) 6108 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR) 6109 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR) 6110 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM) 6111 6112 /* Edit menu */ 6113 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM) 6114 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM) 6115 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR) 6116 6117 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM) 6118 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM) 6119 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM) 6120 6121 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU) 6122 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM) 6123 6124 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM) 6125 6126 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU) 6127 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM) 6128 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM) 6129 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM) 6130 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM) 6131 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM) 6132 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM) 6133 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM) 6134 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM) 6135 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM) 6136 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM) 6137 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM) 6138 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM) 6139 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM) 6140 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM) 6141 6142 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR) 6143 6144 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM) 6145 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM) 6146 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM) 6147 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM) 6148 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM) 6149 6150 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR) 6151 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM) 6152 6153 /* Options menu */ 6154 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU) 6155 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM) 6156 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM) 6157 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM) 6158 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM) 6159 6160 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR) 6161 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM) 6162 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR) 6163 6164 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU) 6165 6166 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM) 6167 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR) 6168 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM) 6169 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM) 6170 6171 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM) 6172 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU) 6173 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM) 6174 6175 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu"); 6176 gtk_widget_show_all(menubar); 6177 6178 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager)); 6179 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0); 6180 6181 handlebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); 6182 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0); 6183 6184 gtk_widget_realize(handlebox); 6185 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox, 6186 (gpointer)compose); 6187 6188 vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2); 6189 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0); 6190 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0); 6191 6192 /* Notebook */ 6193 notebook = gtk_notebook_new(); 6194 gtk_widget_show(notebook); 6195 6196 /* header labels and entries */ 6197 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), 6198 compose_create_header(compose), 6199 gtk_label_new_with_mnemonic(_("Hea_der"))); 6200 /* attachment list */ 6201 attach_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); 6202 gtk_widget_show(attach_hbox); 6203 6204 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments")); 6205 gtk_widget_show(attach_lab1); 6206 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0); 6207 6208 attach_lab2 = gtk_label_new(""); 6209 gtk_widget_show(attach_lab2); 6210 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0); 6211 6212 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), 6213 compose_create_attach(compose), 6214 attach_hbox); 6215 /* Others Tab */ 6216 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), 6217 compose_create_others(compose), 6218 gtk_label_new_with_mnemonic(_("Othe_rs"))); 6219 6220 /* Subject */ 6221 subject_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); 6222 gtk_widget_show(subject_hbox); 6223 6224 subject_frame = gtk_frame_new(NULL); 6225 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE); 6226 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0); 6227 gtk_widget_show(subject_frame); 6228 6229 subject = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, HSPACING_NARROW); 6230 gtk_container_set_border_width(GTK_CONTAINER(subject), 0); 6231 gtk_widget_show(subject); 6232 6233 label = gtk_label_new_with_mnemonic(_("S_ubject:")); 6234 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, HSPACING_NARROW); 6235 gtk_widget_show(label); 6236 subject_entry = gtk_entry_new(); 6237 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, HSPACING_NARROW); 6238 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus", 6239 G_CALLBACK(compose_grab_focus_cb), compose); 6240 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry); 6241 gtk_widget_show(subject_entry); 6242 compose->subject_entry = subject_entry; 6243 gtk_container_add(GTK_CONTAINER(subject_frame), subject); 6244 6245 edit_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); 6246 6247 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0); 6248 6249 /* text widget */ 6250 scrolledwin = gtk_scrolled_window_new(NULL, NULL); 6251 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin), 6252 GTK_POLICY_AUTOMATIC, 6253 GTK_POLICY_AUTOMATIC); 6254 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin), 6255 GTK_SHADOW_IN); 6256 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0); 6257 6258 text = gtk_text_view_new(); 6259 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 4); 6260 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 4); 6261 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); 6262 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR); 6263 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE); 6264 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); 6265 gtk_text_buffer_add_selection_clipboard(buffer, clipboard); 6266 6267 gtk_container_add(GTK_CONTAINER(scrolledwin), text); 6268 g_signal_connect(G_OBJECT(buffer), "changed", 6269 G_CALLBACK(compose_changed_cb), compose); 6270 g_signal_connect(G_OBJECT(text), "grab_focus", 6271 G_CALLBACK(compose_grab_focus_cb), compose); 6272 g_signal_connect(G_OBJECT(buffer), "insert_text", 6273 G_CALLBACK(text_inserted), compose); 6274 g_signal_connect(G_OBJECT(text), "button_press_event", 6275 G_CALLBACK(text_clicked), compose); 6276 g_signal_connect(G_OBJECT(text), "popup-menu", 6277 G_CALLBACK(compose_popup_menu), compose); 6278 g_signal_connect(G_OBJECT(subject_entry), "changed", 6279 G_CALLBACK(compose_changed_cb), compose); 6280 g_signal_connect(G_OBJECT(subject_entry), "activate", 6281 G_CALLBACK(compose_subject_entry_activated), compose); 6282 6283 /* drag and drop */ 6284 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types, 6285 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]), 6286 GDK_ACTION_COPY | GDK_ACTION_MOVE); 6287 g_signal_connect(G_OBJECT(text), "drag_data_received", 6288 G_CALLBACK(compose_insert_drag_received_cb), 6289 compose); 6290 g_signal_connect(G_OBJECT(text), "drag-drop", 6291 G_CALLBACK(compose_drag_drop), 6292 compose); 6293 g_signal_connect(G_OBJECT(text), "key-press-event", 6294 G_CALLBACK(completion_set_focus_to_subject), 6295 compose); 6296 gtk_widget_show_all(vbox); 6297 6298 /* pane between attach clist and text */ 6299 paned = gtk_paned_new(GTK_ORIENTATION_VERTICAL); 6300 gtk_box_pack_start(GTK_BOX(vbox2), paned, TRUE, TRUE, 0); 6301 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE); 6302 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE); 6303 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height); 6304 g_signal_connect(G_OBJECT(notebook), "size_allocate", 6305 G_CALLBACK(compose_notebook_size_alloc), paned); 6306 6307 gtk_widget_show_all(paned); 6308 6309 gtk_action_group_add_actions(action_group, compose_popup_entries, 6310 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose); 6311 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR) 6312 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU) 6313 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM) 6314 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM) 6315 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR) 6316 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM) 6317 6318 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose"))); 6319 6320 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE); 6321 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE); 6322 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE); 6323 6324 undostruct = undo_init(text); 6325 undo_set_change_state_func(undostruct, &compose_undo_state_changed, 6326 compose); 6327 6328 address_completion_start(window); 6329 6330 compose->window = window; 6331 compose->vbox = vbox; 6332 compose->menubar = menubar; 6333 compose->handlebox = handlebox; 6334 6335 compose->vbox2 = vbox2; 6336 6337 compose->paned = paned; 6338 6339 compose->attach_label = attach_lab2; 6340 6341 compose->notebook = notebook; 6342 compose->edit_vbox = edit_vbox; 6343 compose->scrolledwin = scrolledwin; 6344 compose->text = text; 6345 6346 compose->focused_editable = NULL; 6347 6348 compose->popupmenu = popupmenu; 6349 6350 compose->mode = mode; 6351 compose->rmode = mode; 6352 6353 compose->targetinfo = NULL; 6354 compose->replyinfo = NULL; 6355 compose->fwdinfo = NULL; 6356 6357 compose->email_hashtable = g_hash_table_new_full(g_str_hash, 6358 g_str_equal, (GDestroyNotify) g_free, NULL); 6359 6360 compose->replyto = NULL; 6361 compose->cc = NULL; 6362 compose->bcc = NULL; 6363 compose->followup_to = NULL; 6364 6365 compose->ml_post = NULL; 6366 6367 compose->inreplyto = NULL; 6368 compose->references = NULL; 6369 compose->msgid = NULL; 6370 compose->boundary = NULL; 6371 6372 compose->autowrap = prefs_common.autowrap; 6373 compose->autoindent = prefs_common.auto_indent; 6374 compose->encdata = NULL; 6375 6376 compose->modified = FALSE; 6377 6378 compose->to_list = NULL; 6379 6380 compose->undostruct = undostruct; 6381 6382 compose->sig_str = NULL; 6383 6384 compose->exteditor_file = NULL; 6385 compose->exteditor_pid = INVALID_PID; 6386 compose->exteditor_tag = -1; 6387 compose->exteditor_socket = NULL; 6388 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */ 6389 6390 compose->folder_update_callback_id = 6391 hooks_register_hook(FOLDER_UPDATE_HOOKLIST, 6392 compose_update_folder_hook, 6393 (gpointer) compose); 6394 compose_select_account(compose, account, TRUE); 6395 6396 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap); 6397 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent); 6398 6399 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT) 6400 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT); 6401 6402 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT) 6403 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT); 6404 6405 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT) 6406 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT); 6407 6408 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY); 6409 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))), "To:"); 6410 6411 addressbook_set_target_compose(compose); 6412 6413 compose_list = g_list_append(compose_list, compose); 6414 6415 compose_set_out_encoding(compose); 6416 6417 /* Actions menu */ 6418 compose_update_actions_menu(compose); 6419 6420 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style); 6421 if (batch) { 6422 gtk_widget_realize(window); 6423 } else { 6424 gtk_widget_show(window); 6425 } 6426 6427 return compose; 6428 } 6429 6430 static GtkWidget *compose_account_option_menu_create(Compose *compose) 6431 { 6432 GList *accounts; 6433 GtkWidget *hbox; 6434 GtkWidget *optmenu; 6435 GtkWidget *optmenubox; 6436 GtkWidget *fromlabel; 6437 GtkListStore *menu; 6438 GtkTreeIter iter; 6439 GtkWidget *from_name = NULL; 6440 6441 gint num = 0, def_menu = 0; 6442 6443 accounts = account_get_list(); 6444 cm_return_val_if_fail(accounts != NULL, NULL); 6445 6446 optmenubox = gtk_event_box_new(); 6447 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE); 6448 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu))); 6449 6450 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, HSPACING_NARROW); 6451 from_name = gtk_entry_new(); 6452 6453 g_signal_connect_after(G_OBJECT(from_name), "grab_focus", 6454 G_CALLBACK(compose_grab_focus_cb), compose); 6455 g_signal_connect_after(G_OBJECT(from_name), "activate", 6456 G_CALLBACK(from_name_activate_cb), optmenu); 6457 6458 for (; accounts != NULL; accounts = accounts->next, num++) { 6459 PrefsAccount *ac = (PrefsAccount *)accounts->data; 6460 gchar *name, *from = NULL; 6461 6462 if (ac == compose->account) def_menu = num; 6463 6464 name = g_markup_printf_escaped("<i>%s</i>", 6465 ac->account_name); 6466 6467 if (ac == compose->account) { 6468 if (ac->name && *ac->name) { 6469 gchar *buf; 6470 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL); 6471 from = g_strdup_printf("%s <%s>", 6472 buf, ac->address); 6473 gtk_entry_set_text(GTK_ENTRY(from_name), from); 6474 } else { 6475 from = g_strdup_printf("%s", 6476 ac->address); 6477 gtk_entry_set_text(GTK_ENTRY(from_name), from); 6478 } 6479 if (cur_account != compose->account) { 6480 GdkColor color; 6481 6482 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_bgcolor, color); 6483 gtk_widget_modify_base( 6484 GTK_WIDGET(from_name), 6485 GTK_STATE_NORMAL, &color); 6486 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_color, color); 6487 gtk_widget_modify_text( 6488 GTK_WIDGET(from_name), 6489 GTK_STATE_NORMAL, &color); 6490 } 6491 } 6492 COMBOBOX_ADD(menu, name, ac->account_id); 6493 g_free(name); 6494 g_free(from); 6495 } 6496 6497 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu); 6498 6499 g_signal_connect(G_OBJECT(optmenu), "changed", 6500 G_CALLBACK(account_activated), 6501 compose); 6502 g_signal_connect(G_OBJECT(from_name), "populate-popup", 6503 G_CALLBACK(compose_entry_popup_extend), 6504 NULL); 6505 6506 fromlabel = gtk_label_new_with_mnemonic(_("_From:")); 6507 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name); 6508 6509 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4); 6510 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0); 6511 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0); 6512 6513 CLAWS_SET_TIP(optmenubox, 6514 _("Account to use for this email")); 6515 CLAWS_SET_TIP(from_name, 6516 _("Sender address to be used")); 6517 6518 compose->account_combo = optmenu; 6519 compose->from_name = from_name; 6520 6521 return hbox; 6522 } 6523 6524 static void compose_reply_change_mode(Compose *compose, 6525 ComposeMode action) 6526 { 6527 gboolean was_modified = compose->modified; 6528 6529 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE; 6530 6531 cm_return_if_fail(compose->replyinfo != NULL); 6532 6533 if (action == COMPOSE_REPLY && prefs_common.default_reply_list) 6534 ml = TRUE; 6535 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO) 6536 followup = TRUE; 6537 if (action == COMPOSE_REPLY_TO_ALL) 6538 all = TRUE; 6539 if (action == COMPOSE_REPLY_TO_SENDER) 6540 sender = TRUE; 6541 if (action == COMPOSE_REPLY_TO_LIST) 6542 ml = TRUE; 6543 6544 compose_remove_header_entries(compose); 6545 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup); 6546 if (compose->account->set_autocc && compose->account->auto_cc) 6547 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT); 6548 6549 if (compose->account->set_autobcc && compose->account->auto_bcc) 6550 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT); 6551 6552 if (compose->account->set_autoreplyto && compose->account->auto_replyto) 6553 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT); 6554 compose_show_first_last_header(compose, TRUE); 6555 compose->modified = was_modified; 6556 compose_set_title(compose); 6557 } 6558 6559 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data) 6560 { 6561 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current)); 6562 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current)); 6563 Compose *compose = (Compose *) data; 6564 6565 if (active) 6566 compose_reply_change_mode(compose, value); 6567 } 6568 6569 static void compose_set_out_encoding(Compose *compose) 6570 { 6571 CharSet out_encoding; 6572 const gchar *branch = NULL; 6573 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset); 6574 6575 switch(out_encoding) { 6576 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break; 6577 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break; 6578 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break; 6579 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break; 6580 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break; 6581 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break; 6582 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break; 6583 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break; 6584 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break; 6585 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break; 6586 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break; 6587 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break; 6588 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break; 6589 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break; 6590 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break; 6591 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break; 6592 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break; 6593 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break; 6594 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break; 6595 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break; 6596 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break; 6597 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break; 6598 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break; 6599 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break; 6600 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break; 6601 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break; 6602 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break; 6603 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break; 6604 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break; 6605 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break; 6606 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break; 6607 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break; 6608 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break; 6609 default: branch = "Menu/Options/Encoding/" CS_AUTO; break; 6610 } 6611 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE); 6612 } 6613 6614 void compose_update_actions_menu(Compose *compose) 6615 { 6616 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose); 6617 } 6618 6619 void compose_reflect_prefs_pixmap_theme(void) 6620 { 6621 GList *cur; 6622 Compose *compose; 6623 6624 for (cur = compose_list; cur != NULL; cur = cur->next) { 6625 compose = (Compose *)cur->data; 6626 toolbar_update(TOOLBAR_COMPOSE, compose); 6627 } 6628 } 6629 6630 static void compose_destroy(Compose *compose) 6631 { 6632 GtkAllocation allocation; 6633 GtkTextBuffer *buffer; 6634 GtkClipboard *clipboard; 6635 6636 compose_list = g_list_remove(compose_list, compose); 6637 6638 if (compose->updating) { 6639 debug_print("danger, not destroying anything now\n"); 6640 compose->deferred_destroy = TRUE; 6641 return; 6642 } 6643 6644 /* NOTE: address_completion_end() does nothing with the window 6645 * however this may change. */ 6646 address_completion_end(compose->window); 6647 6648 slist_free_strings_full(compose->to_list); 6649 slist_free_strings_full(compose->header_list); 6650 6651 slist_free_strings_full(extra_headers); 6652 extra_headers = NULL; 6653 6654 compose->header_list = compose->to_list = NULL; 6655 6656 g_hash_table_destroy(compose->email_hashtable); 6657 6658 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST, 6659 compose->folder_update_callback_id); 6660 6661 procmsg_msginfo_free(&(compose->targetinfo)); 6662 procmsg_msginfo_free(&(compose->replyinfo)); 6663 procmsg_msginfo_free(&(compose->fwdinfo)); 6664 6665 g_free(compose->replyto); 6666 g_free(compose->cc); 6667 g_free(compose->bcc); 6668 g_free(compose->followup_to); 6669 6670 g_free(compose->ml_post); 6671 6672 g_free(compose->inreplyto); 6673 g_free(compose->references); 6674 g_free(compose->msgid); 6675 g_free(compose->boundary); 6676 6677 g_free(compose->redirect_filename); 6678 if (compose->undostruct) 6679 undo_destroy(compose->undostruct); 6680 6681 g_free(compose->sig_str); 6682 g_free(compose->exteditor_file); 6683 g_free(compose->orig_charset); 6684 g_free(compose->encdata); 6685 6686 if (addressbook_get_target_compose() == compose) 6687 addressbook_set_target_compose(NULL); 6688 6689 if (!compose->batch) { 6690 gtk_window_get_size(GTK_WINDOW(compose->window), 6691 &allocation.width, &allocation.height); 6692 prefs_common.compose_width = allocation.width; 6693 prefs_common.compose_height = allocation.height; 6694 } 6695 6696 if (!gtk_widget_get_parent(compose->paned)) 6697 gtk_widget_destroy(compose->paned); 6698 gtk_widget_destroy(compose->popupmenu); 6699 6700 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text)); 6701 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); 6702 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard); 6703 6704 message_search_close(compose); 6705 gtk_widget_destroy(compose->window); 6706 toolbar_destroy(compose->toolbar); 6707 g_free(compose->toolbar); 6708 g_mutex_clear(&compose->mutex); 6709 g_free(compose); 6710 } 6711 6712 static void compose_attach_info_free(AttachInfo *ainfo) 6713 { 6714 g_free(ainfo->file); 6715 g_free(ainfo->content_type); 6716 g_free(ainfo->name); 6717 g_free(ainfo->charset); 6718 g_free(ainfo); 6719 } 6720 6721 static void compose_attach_update_label(Compose *compose) 6722 { 6723 GtkTreeIter iter; 6724 gint i = 1; 6725 gchar *text; 6726 GtkTreeModel *model; 6727 goffset total_size; 6728 AttachInfo *ainfo; 6729 6730 if (compose == NULL) 6731 return; 6732 6733 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist)); 6734 if (!gtk_tree_model_get_iter_first(model, &iter)) { 6735 gtk_label_set_text(GTK_LABEL(compose->attach_label), ""); 6736 return; 6737 } 6738 6739 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1); 6740 total_size = ainfo->size; 6741 while(gtk_tree_model_iter_next(model, &iter)) { 6742 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1); 6743 total_size += ainfo->size; 6744 i++; 6745 } 6746 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size)); 6747 gtk_label_set_text(GTK_LABEL(compose->attach_label), text); 6748 g_free(text); 6749 } 6750 6751 static void compose_attach_remove_selected(GtkAction *action, gpointer data) 6752 { 6753 Compose *compose = (Compose *)data; 6754 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist); 6755 GtkTreeSelection *selection; 6756 GList *sel, *cur; 6757 GtkTreeModel *model; 6758 6759 selection = gtk_tree_view_get_selection(tree_view); 6760 sel = gtk_tree_selection_get_selected_rows(selection, &model); 6761 cm_return_if_fail(sel); 6762 6763 for (cur = sel; cur != NULL; cur = cur->next) { 6764 GtkTreePath *path = cur->data; 6765 GtkTreeRowReference *ref = gtk_tree_row_reference_new 6766 (model, cur->data); 6767 cur->data = ref; 6768 gtk_tree_path_free(path); 6769 } 6770 6771 for (cur = sel; cur != NULL; cur = cur->next) { 6772 GtkTreeRowReference *ref = cur->data; 6773 GtkTreePath *path = gtk_tree_row_reference_get_path(ref); 6774 GtkTreeIter iter; 6775 6776 if (gtk_tree_model_get_iter(model, &iter, path)) 6777 gtk_list_store_remove(GTK_LIST_STORE(model), &iter); 6778 6779 gtk_tree_path_free(path); 6780 gtk_tree_row_reference_free(ref); 6781 } 6782 6783 g_list_free(sel); 6784 compose_attach_update_label(compose); 6785 } 6786 6787 static struct _AttachProperty 6788 { 6789 GtkWidget *window; 6790 GtkWidget *mimetype_entry; 6791 GtkWidget *encoding_optmenu; 6792 GtkWidget *path_entry; 6793 GtkWidget *filename_entry; 6794 GtkWidget *ok_btn; 6795 GtkWidget *cancel_btn; 6796 } attach_prop; 6797 6798 static void gtk_tree_path_free_(gpointer ptr, gpointer data) 6799 { 6800 gtk_tree_path_free((GtkTreePath *)ptr); 6801 } 6802 6803 static void compose_attach_property(GtkAction *action, gpointer data) 6804 { 6805 Compose *compose = (Compose *)data; 6806 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist); 6807 AttachInfo *ainfo; 6808 GtkComboBox *optmenu; 6809 GtkTreeSelection *selection; 6810 GList *sel; 6811 GtkTreeModel *model; 6812 GtkTreeIter iter; 6813 GtkTreePath *path; 6814 static gboolean cancelled; 6815 6816 /* only if one selected */ 6817 selection = gtk_tree_view_get_selection(tree_view); 6818 if (gtk_tree_selection_count_selected_rows(selection) != 1) 6819 return; 6820 6821 sel = gtk_tree_selection_get_selected_rows(selection, &model); 6822 cm_return_if_fail(sel); 6823 6824 path = (GtkTreePath *) sel->data; 6825 gtk_tree_model_get_iter(model, &iter, path); 6826 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1); 6827 6828 if (!ainfo) { 6829 g_list_foreach(sel, gtk_tree_path_free_, NULL); 6830 g_list_free(sel); 6831 return; 6832 } 6833 g_list_free(sel); 6834 6835 if (!attach_prop.window) 6836 compose_attach_property_create(&cancelled); 6837 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE); 6838 gtk_widget_grab_focus(attach_prop.ok_btn); 6839 gtk_widget_show(attach_prop.window); 6840 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window), 6841 GTK_WINDOW(compose->window)); 6842 6843 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu); 6844 if (ainfo->encoding == ENC_UNKNOWN) 6845 combobox_select_by_data(optmenu, ENC_BASE64); 6846 else 6847 combobox_select_by_data(optmenu, ainfo->encoding); 6848 6849 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry), 6850 ainfo->content_type ? ainfo->content_type : ""); 6851 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry), 6852 ainfo->file ? ainfo->file : ""); 6853 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry), 6854 ainfo->name ? ainfo->name : ""); 6855 6856 for (;;) { 6857 const gchar *entry_text; 6858 gchar *text; 6859 gchar *cnttype = NULL; 6860 gchar *file = NULL; 6861 off_t size = 0; 6862 6863 cancelled = FALSE; 6864 gtk_main(); 6865 6866 gtk_widget_hide(attach_prop.window); 6867 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE); 6868 6869 if (cancelled) 6870 break; 6871 6872 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry)); 6873 if (*entry_text != '\0') { 6874 gchar *p; 6875 6876 text = g_strstrip(g_strdup(entry_text)); 6877 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) { 6878 cnttype = g_strdup(text); 6879 g_free(text); 6880 } else { 6881 alertpanel_error(_("Invalid MIME type.")); 6882 g_free(text); 6883 continue; 6884 } 6885 } 6886 6887 ainfo->encoding = combobox_get_active_data(optmenu); 6888 6889 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry)); 6890 if (*entry_text != '\0') { 6891 if (is_file_exist(entry_text) && 6892 (size = get_file_size(entry_text)) > 0) 6893 file = g_strdup(entry_text); 6894 else { 6895 alertpanel_error 6896 (_("File doesn't exist or is empty.")); 6897 g_free(cnttype); 6898 continue; 6899 } 6900 } 6901 6902 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry)); 6903 if (*entry_text != '\0') { 6904 g_free(ainfo->name); 6905 ainfo->name = g_strdup(entry_text); 6906 } 6907 6908 if (cnttype) { 6909 g_free(ainfo->content_type); 6910 ainfo->content_type = cnttype; 6911 } 6912 if (file) { 6913 g_free(ainfo->file); 6914 ainfo->file = file; 6915 } 6916 if (size) 6917 ainfo->size = (goffset)size; 6918 6919 /* update tree store */ 6920 text = to_human_readable(ainfo->size); 6921 gtk_tree_model_get_iter(model, &iter, path); 6922 gtk_list_store_set(GTK_LIST_STORE(model), &iter, 6923 COL_MIMETYPE, ainfo->content_type, 6924 COL_SIZE, text, 6925 COL_NAME, ainfo->name, 6926 COL_CHARSET, ainfo->charset, 6927 -1); 6928 6929 break; 6930 } 6931 6932 gtk_tree_path_free(path); 6933 } 6934 6935 #define SET_LABEL_AND_ENTRY(str, entry, top) \ 6936 { \ 6937 label = gtk_label_new(str); \ 6938 gtk_grid_attach(GTK_GRID(table), label, 0, top, 1, 1); \ 6939 gtk_label_set_xalign(GTK_LABEL(label), 0.0); \ 6940 entry = gtk_entry_new(); \ 6941 gtk_grid_attach(GTK_GRID(table), entry, 1, top, 1, 1); \ 6942 } 6943 6944 static void compose_attach_property_create(gboolean *cancelled) 6945 { 6946 GtkWidget *window; 6947 GtkWidget *vbox; 6948 GtkWidget *table; 6949 GtkWidget *label; 6950 GtkWidget *mimetype_entry; 6951 GtkWidget *hbox; 6952 GtkWidget *optmenu; 6953 GtkListStore *optmenu_menu; 6954 GtkWidget *path_entry; 6955 GtkWidget *filename_entry; 6956 GtkWidget *hbbox; 6957 GtkWidget *ok_btn; 6958 GtkWidget *cancel_btn; 6959 GList *mime_type_list, *strlist; 6960 GtkTreeIter iter; 6961 6962 debug_print("Creating attach_property window...\n"); 6963 6964 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property"); 6965 gtk_widget_set_size_request(window, 480, -1); 6966 gtk_container_set_border_width(GTK_CONTAINER(window), 8); 6967 gtk_window_set_title(GTK_WINDOW(window), _("Properties")); 6968 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); 6969 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG); 6970 g_signal_connect(G_OBJECT(window), "delete_event", 6971 G_CALLBACK(attach_property_delete_event), 6972 cancelled); 6973 g_signal_connect(G_OBJECT(window), "key_press_event", 6974 G_CALLBACK(attach_property_key_pressed), 6975 cancelled); 6976 6977 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8); 6978 gtk_container_add(GTK_CONTAINER(window), vbox); 6979 6980 table = gtk_grid_new(); 6981 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); 6982 gtk_grid_set_row_spacing(GTK_GRID(table), 8); 6983 gtk_grid_set_column_spacing(GTK_GRID(table), 8); 6984 6985 label = gtk_label_new(_("MIME type")); 6986 gtk_grid_attach(GTK_GRID(table), label, 0, 0, 1, 1); 6987 gtk_label_set_xalign(GTK_LABEL(label), 0.0); 6988 mimetype_entry = gtk_combo_box_text_new_with_entry(); 6989 gtk_grid_attach(GTK_GRID(table), mimetype_entry, 1, 0, 1, 1); 6990 gtk_widget_set_hexpand(mimetype_entry, TRUE); 6991 gtk_widget_set_halign(mimetype_entry, GTK_ALIGN_FILL); 6992 6993 /* stuff with list */ 6994 mime_type_list = procmime_get_mime_type_list(); 6995 strlist = NULL; 6996 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) { 6997 MimeType *type = (MimeType *) mime_type_list->data; 6998 gchar *tmp; 6999 7000 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type); 7001 7002 if (g_list_find_custom(strlist, tmp, (GCompareFunc)g_strcmp0)) 7003 g_free(tmp); 7004 else 7005 strlist = g_list_insert_sorted(strlist, (gpointer)tmp, 7006 (GCompareFunc)g_strcmp0); 7007 } 7008 7009 for (mime_type_list = strlist; mime_type_list != NULL; 7010 mime_type_list = mime_type_list->next) { 7011 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data); 7012 g_free(mime_type_list->data); 7013 } 7014 g_list_free(strlist); 7015 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0); 7016 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry))); 7017 7018 label = gtk_label_new(_("Encoding")); 7019 gtk_grid_attach(GTK_GRID(table), label, 0, 1, 1, 1); 7020 gtk_label_set_xalign(GTK_LABEL(label), 0.0); 7021 7022 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); 7023 gtk_grid_attach(GTK_GRID(table), hbox, 1, 1, 1, 1); 7024 gtk_widget_set_hexpand(hbox, TRUE); 7025 gtk_widget_set_halign(hbox, GTK_ALIGN_FILL); 7026 7027 optmenu = gtkut_sc_combobox_create(NULL, TRUE); 7028 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu))); 7029 7030 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT); 7031 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT); 7032 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE); 7033 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64); 7034 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0); 7035 7036 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0); 7037 7038 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2); 7039 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3); 7040 7041 gtkut_stock_button_set_create(&hbbox, &cancel_btn, NULL, _("_Cancel"), 7042 &ok_btn, NULL, _("_OK"), 7043 NULL, NULL, NULL); 7044 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0); 7045 gtk_widget_grab_default(ok_btn); 7046 7047 g_signal_connect(G_OBJECT(ok_btn), "clicked", 7048 G_CALLBACK(attach_property_ok), 7049 cancelled); 7050 g_signal_connect(G_OBJECT(cancel_btn), "clicked", 7051 G_CALLBACK(attach_property_cancel), 7052 cancelled); 7053 7054 gtk_widget_show_all(vbox); 7055 7056 attach_prop.window = window; 7057 attach_prop.mimetype_entry = mimetype_entry; 7058 attach_prop.encoding_optmenu = optmenu; 7059 attach_prop.path_entry = path_entry; 7060 attach_prop.filename_entry = filename_entry; 7061 attach_prop.ok_btn = ok_btn; 7062 attach_prop.cancel_btn = cancel_btn; 7063 } 7064 7065 #undef SET_LABEL_AND_ENTRY 7066 7067 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled) 7068 { 7069 *cancelled = FALSE; 7070 gtk_main_quit(); 7071 } 7072 7073 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled) 7074 { 7075 *cancelled = TRUE; 7076 gtk_main_quit(); 7077 } 7078 7079 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event, 7080 gboolean *cancelled) 7081 { 7082 *cancelled = TRUE; 7083 gtk_main_quit(); 7084 7085 return TRUE; 7086 } 7087 7088 static gboolean attach_property_key_pressed(GtkWidget *widget, 7089 GdkEventKey *event, 7090 gboolean *cancelled) 7091 { 7092 if (event && event->keyval == GDK_KEY_Escape) { 7093 *cancelled = TRUE; 7094 gtk_main_quit(); 7095 } 7096 if (event && (event->keyval == GDK_KEY_KP_Enter || 7097 event->keyval == GDK_KEY_Return)) { 7098 *cancelled = FALSE; 7099 gtk_main_quit(); 7100 return TRUE; 7101 } 7102 return FALSE; 7103 } 7104 7105 /** 7106 * compose_exec_ext_editor: 7107 * 7108 * Open (and optionally embed) external editor 7109 **/ 7110 static void compose_exec_ext_editor(Compose *compose) 7111 { 7112 gchar *tmp; 7113 #ifdef GDK_WINDOWING_X11 7114 GtkWidget *socket; 7115 Window socket_wid = 0; 7116 gchar *p, *s; 7117 #endif /* GDK_WINDOWING_X11 */ 7118 GPid pid; 7119 GError *error = NULL; 7120 gchar *cmd = NULL; 7121 gchar **argv; 7122 7123 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(), 7124 G_DIR_SEPARATOR, compose); 7125 7126 if (compose_write_body_to_file(compose, tmp) < 0) { 7127 alertpanel_error(_("Could not write the body to file:\n%s"), 7128 tmp); 7129 g_free(tmp); 7130 return; 7131 } 7132 7133 #ifdef GDK_WINDOWING_X11 7134 if (compose_get_ext_editor_uses_socket()) { 7135 if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) { 7136 /* Only allow one socket */ 7137 if (compose->exteditor_socket != NULL) { 7138 if (gtk_widget_is_focus(compose->exteditor_socket)) { 7139 /* Move the focus off of the socket */ 7140 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD); 7141 } 7142 g_free(tmp); 7143 return; 7144 } 7145 /* Create the receiving GtkSocket */ 7146 socket = gtk_socket_new (); 7147 g_signal_connect (G_OBJECT(socket), "plug-removed", 7148 G_CALLBACK(compose_ext_editor_plug_removed_cb), 7149 compose); 7150 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0); 7151 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1); 7152 /* Realize the socket so that we can use its ID */ 7153 gtk_widget_realize(socket); 7154 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket)); 7155 compose->exteditor_socket = socket; 7156 } else 7157 debug_print("Socket communication with an external editor is only available on X11.\n"); 7158 } 7159 #else 7160 if (compose_get_ext_editor_uses_socket()) { 7161 alertpanel_error(_("Socket communication with an external editor is only available on X11.")); 7162 g_free(tmp); 7163 return; 7164 } 7165 #endif /* GDK_WINDOWING_X11 */ 7166 7167 if (compose_get_ext_editor_cmd_valid()) { 7168 #ifdef GDK_WINDOWING_X11 7169 if (compose_get_ext_editor_uses_socket() && GDK_IS_X11_DISPLAY(gdk_display_get_default())) { 7170 p = g_strdup(prefs_common_get_ext_editor_cmd()); 7171 s = strstr(p, "%w"); 7172 s[1] = 'u'; 7173 if (strstr(p, "%s") < s) 7174 cmd = g_strdup_printf(p, tmp, socket_wid); 7175 else 7176 cmd = g_strdup_printf(p, socket_wid, tmp); 7177 g_free(p); 7178 } else { 7179 cmd = g_strdup_printf(prefs_common_get_ext_editor_cmd(), tmp); 7180 } 7181 #else 7182 cmd = g_strdup_printf(prefs_common_get_ext_editor_cmd(), tmp); 7183 #endif /* GDK_WINDOWING_X11 */ 7184 } else { 7185 if (prefs_common_get_ext_editor_cmd()) 7186 g_warning("external editor command-line is invalid: '%s'", 7187 prefs_common_get_ext_editor_cmd()); 7188 cmd = g_strdup_printf(DEFAULT_EDITOR_CMD, tmp); 7189 } 7190 7191 argv = strsplit_with_quote(cmd, " ", 0); 7192 7193 if (!g_spawn_async(NULL, argv, NULL, 7194 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, 7195 NULL, NULL, &pid, &error)) { 7196 alertpanel_error(_("Could not spawn the following " 7197 "external editor command:\n%s\n%s"), 7198 cmd, error ? error->message : _("Unknown error")); 7199 if (error) 7200 g_error_free(error); 7201 g_free(tmp); 7202 g_free(cmd); 7203 g_strfreev(argv); 7204 return; 7205 } 7206 g_free(cmd); 7207 g_strfreev(argv); 7208 7209 compose->exteditor_file = g_strdup(tmp); 7210 compose->exteditor_pid = pid; 7211 compose->exteditor_tag = g_child_watch_add(pid, 7212 compose_ext_editor_closed_cb, 7213 compose); 7214 7215 compose_set_ext_editor_sensitive(compose, FALSE); 7216 7217 g_free(tmp); 7218 } 7219 7220 /** 7221 * compose_ext_editor_cb: 7222 * 7223 * External editor has closed (called by g_child_watch) 7224 **/ 7225 static void compose_ext_editor_closed_cb(GPid pid, gint exit_status, gpointer data) 7226 { 7227 Compose *compose = (Compose *)data; 7228 GError *error = NULL; 7229 GtkTextView *text = GTK_TEXT_VIEW(compose->text); 7230 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text); 7231 GtkTextIter iter; 7232 gboolean modified = TRUE; 7233 7234 #if GLIB_CHECK_VERSION(2,70,0) 7235 if (!g_spawn_check_wait_status(exit_status, &error)) { 7236 #else 7237 if (!g_spawn_check_exit_status(exit_status, &error)) { 7238 #endif 7239 alertpanel_error( 7240 _("External editor stopped with an error: %s"), 7241 error ? error->message : _("Unknown error")); 7242 if (error) 7243 g_error_free(error); 7244 } 7245 g_spawn_close_pid(compose->exteditor_pid); 7246 7247 gtk_text_buffer_set_text(buffer, "", -1); 7248 compose_insert_file(compose, compose->exteditor_file); 7249 7250 /* Check if we should save the draft or not */ 7251 if (prefs_common.autosave) 7252 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE); 7253 7254 if (remove(compose->exteditor_file) < 0) 7255 warn("remove %s", compose->exteditor_file); 7256 7257 gtk_text_buffer_get_start_iter(buffer, &iter); 7258 7259 while (!gtk_text_iter_is_end(&iter) && modified) 7260 modified = compose_beautify_paragraph(compose, &iter, compose->autowrap); 7261 7262 compose_set_ext_editor_sensitive(compose, TRUE); 7263 7264 g_free(compose->exteditor_file); 7265 compose->exteditor_file = NULL; 7266 compose->exteditor_pid = INVALID_PID; 7267 compose->exteditor_tag = -1; 7268 #ifdef GDK_WINDOWING_X11 7269 if (compose->exteditor_socket && GDK_IS_X11_DISPLAY(gdk_display_get_default())) { 7270 gtk_widget_destroy(compose->exteditor_socket); 7271 compose->exteditor_socket = NULL; 7272 } 7273 #endif /* GDK_WINDOWING_X11 */ 7274 7275 } 7276 7277 static gboolean compose_get_ext_editor_cmd_valid() 7278 { 7279 gboolean has_s = FALSE; 7280 gboolean has_w = FALSE; 7281 const gchar *p = prefs_common_get_ext_editor_cmd(); 7282 if (!p) 7283 return FALSE; 7284 while ((p = strchr(p, '%'))) { 7285 p++; 7286 if (*p == 's') { 7287 if (has_s) 7288 return FALSE; 7289 has_s = TRUE; 7290 } else if (*p == 'w') { 7291 if (has_w) 7292 return FALSE; 7293 has_w = TRUE; 7294 } else { 7295 return FALSE; 7296 } 7297 } 7298 return TRUE; 7299 } 7300 7301 static gboolean compose_ext_editor_kill(Compose *compose) 7302 { 7303 GPid pid = compose->exteditor_pid; 7304 gchar *pidmsg = NULL; 7305 7306 if (pid > 0) { 7307 AlertValue val; 7308 gchar *msg; 7309 7310 pidmsg = g_strdup_printf(_("process id: %" G_PID_FORMAT), pid); 7311 7312 msg = g_strdup_printf 7313 (_("The external editor is still working.\n" 7314 "Force terminating the process?\n" 7315 "%s"), pidmsg); 7316 val = alertpanel_full(_("Notice"), msg, NULL, _("_No"), NULL, _("_Yes"), 7317 NULL, NULL, ALERTFOCUS_FIRST, FALSE, NULL, 7318 ALERT_WARNING); 7319 g_free(msg); 7320 7321 if (val == G_ALERTALTERNATE) { 7322 g_source_remove(compose->exteditor_tag); 7323 7324 if (kill(pid, SIGTERM) < 0) perror("kill"); 7325 waitpid(compose->exteditor_pid, NULL, 0); 7326 7327 g_warning("terminated %s, temporary file: %s", 7328 pidmsg, compose->exteditor_file); 7329 g_spawn_close_pid(compose->exteditor_pid); 7330 7331 compose_set_ext_editor_sensitive(compose, TRUE); 7332 7333 g_free(compose->exteditor_file); 7334 compose->exteditor_file = NULL; 7335 compose->exteditor_pid = INVALID_PID; 7336 compose->exteditor_tag = -1; 7337 } else { 7338 g_free(pidmsg); 7339 return FALSE; 7340 } 7341 } 7342 7343 if (pidmsg) 7344 g_free(pidmsg); 7345 return TRUE; 7346 } 7347 7348 static char *ext_editor_menu_entries[] = { 7349 "Menu/Message/Send", 7350 "Menu/Message/SendLater", 7351 "Menu/Message/InsertFile", 7352 "Menu/Message/Save", 7353 "Menu/Edit", 7354 "Menu/Tools/Actions", 7355 NULL 7356 }; 7357 7358 static void compose_set_ext_editor_sensitive(Compose *compose, 7359 gboolean sensitive) 7360 { 7361 int i; 7362 7363 for (i = 0; ext_editor_menu_entries[i]; ++i) { 7364 cm_menu_set_sensitive_full(compose->ui_manager, 7365 ext_editor_menu_entries[i], sensitive); 7366 } 7367 7368 #ifdef GDK_WINDOWING_X11 7369 if (compose_get_ext_editor_uses_socket() && GDK_IS_X11_DISPLAY(gdk_display_get_default())) { 7370 if (sensitive) { 7371 if (compose->exteditor_socket) 7372 gtk_widget_hide(compose->exteditor_socket); 7373 gtk_widget_show(compose->scrolledwin); 7374 /* Fix the focus, as it doesn't go anywhere when the 7375 * socket is hidden or destroyed */ 7376 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD); 7377 } else { 7378 g_assert (compose->exteditor_socket != NULL); 7379 /* Fix the focus, as it doesn't go anywhere when the 7380 * edit box is hidden */ 7381 if (gtk_widget_is_focus(compose->text)) 7382 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD); 7383 gtk_widget_hide(compose->scrolledwin); 7384 gtk_widget_show(compose->exteditor_socket); 7385 } 7386 } else { 7387 gtk_widget_set_sensitive(compose->text, sensitive); 7388 } 7389 #else 7390 gtk_widget_set_sensitive(compose->text, sensitive); 7391 #endif /* GDK_WINDOWING_X11 */ 7392 if (compose->toolbar->send_btn) 7393 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive); 7394 if (compose->toolbar->sendl_btn) 7395 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive); 7396 if (compose->toolbar->draft_btn) 7397 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive); 7398 if (compose->toolbar->insert_btn) 7399 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive); 7400 if (compose->toolbar->sig_btn) 7401 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive); 7402 if (compose->toolbar->exteditor_btn) 7403 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive); 7404 if (compose->toolbar->linewrap_current_btn) 7405 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive); 7406 if (compose->toolbar->linewrap_all_btn) 7407 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive); 7408 } 7409 7410 static gboolean compose_get_ext_editor_uses_socket() 7411 { 7412 return (prefs_common_get_ext_editor_cmd() && 7413 strstr(prefs_common_get_ext_editor_cmd(), "%w")); 7414 } 7415 7416 #ifdef GDK_WINDOWING_X11 7417 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose) 7418 { 7419 compose->exteditor_socket = NULL; 7420 /* returning FALSE allows destruction of the socket */ 7421 return FALSE; 7422 } 7423 #endif /* GDK_WINDOWING_X11 */ 7424 7425 /** 7426 * compose_undo_state_changed: 7427 * 7428 * Change the sensivity of the menuentries undo and redo 7429 **/ 7430 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state, 7431 gint redo_state, gpointer data) 7432 { 7433 Compose *compose = (Compose *)data; 7434 7435 switch (undo_state) { 7436 case UNDO_STATE_TRUE: 7437 if (!undostruct->undo_state) { 7438 undostruct->undo_state = 1; 7439 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE); 7440 } 7441 break; 7442 case UNDO_STATE_FALSE: 7443 if (undostruct->undo_state) { 7444 undostruct->undo_state = FALSE; 7445 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE); 7446 } 7447 break; 7448 case UNDO_STATE_UNCHANGED: 7449 break; 7450 case UNDO_STATE_REFRESH: 7451 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state); 7452 break; 7453 default: 7454 g_warning("undo state not recognized"); 7455 break; 7456 } 7457 7458 switch (redo_state) { 7459 case UNDO_STATE_TRUE: 7460 if (!undostruct->redo_state) { 7461 undostruct->redo_state = 1; 7462 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE); 7463 } 7464 break; 7465 case UNDO_STATE_FALSE: 7466 if (undostruct->redo_state) { 7467 undostruct->redo_state = FALSE; 7468 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE); 7469 } 7470 break; 7471 case UNDO_STATE_UNCHANGED: 7472 break; 7473 case UNDO_STATE_REFRESH: 7474 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state); 7475 break; 7476 default: 7477 g_warning("redo state not recognized"); 7478 break; 7479 } 7480 } 7481 7482 /* callback functions */ 7483 7484 static void compose_notebook_size_alloc(GtkNotebook *notebook, 7485 GtkAllocation *allocation, 7486 GtkPaned *paned) 7487 { 7488 prefs_common.compose_notebook_height = gtk_paned_get_position(paned); 7489 } 7490 7491 typedef struct { 7492 gchar *header; 7493 gchar *entry; 7494 ComposePrefType type; 7495 gboolean entry_marked; 7496 } HeaderEntryState; 7497 7498 static void account_activated(GtkComboBox *optmenu, gpointer data) 7499 { 7500 Compose *compose = (Compose *)data; 7501 7502 PrefsAccount *ac; 7503 gchar *folderidentifier; 7504 gint account_id = 0; 7505 GtkTreeModel *menu; 7506 GtkTreeIter iter; 7507 GSList *list, *saved_list = NULL; 7508 HeaderEntryState *state; 7509 7510 /* Get ID of active account in the combo box */ 7511 menu = gtk_combo_box_get_model(optmenu); 7512 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter)); 7513 gtk_tree_model_get(menu, &iter, 1, &account_id, -1); 7514 7515 ac = account_find_from_id(account_id); 7516 cm_return_if_fail(ac != NULL); 7517 7518 if (ac != compose->account) { 7519 compose_select_account(compose, ac, FALSE); 7520 7521 for (list = compose->header_list; list; list = list->next) { 7522 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data; 7523 7524 if (hentry->type == PREF_ACCOUNT || !list->next) { 7525 compose_destroy_headerentry(compose, hentry); 7526 continue; 7527 } 7528 state = g_malloc0(sizeof(HeaderEntryState)); 7529 state->header = gtk_editable_get_chars(GTK_EDITABLE( 7530 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1); 7531 state->entry = gtk_editable_get_chars( 7532 GTK_EDITABLE(hentry->entry), 0, -1); 7533 state->type = hentry->type; 7534 7535 saved_list = g_slist_append(saved_list, state); 7536 compose_destroy_headerentry(compose, hentry); 7537 } 7538 7539 compose->header_last = NULL; 7540 g_slist_free(compose->header_list); 7541 compose->header_list = NULL; 7542 compose->header_nextrow = 1; 7543 compose_create_header_entry(compose); 7544 7545 if (ac->set_autocc && ac->auto_cc) 7546 compose_entry_append(compose, ac->auto_cc, 7547 COMPOSE_CC, PREF_ACCOUNT); 7548 if (ac->set_autobcc && ac->auto_bcc) 7549 compose_entry_append(compose, ac->auto_bcc, 7550 COMPOSE_BCC, PREF_ACCOUNT); 7551 if (ac->set_autoreplyto && ac->auto_replyto) 7552 compose_entry_append(compose, ac->auto_replyto, 7553 COMPOSE_REPLYTO, PREF_ACCOUNT); 7554 7555 for (list = saved_list; list; list = list->next) { 7556 state = (HeaderEntryState *) list->data; 7557 7558 compose_add_header_entry(compose, state->header, 7559 state->entry, state->type); 7560 7561 g_free(state->header); 7562 g_free(state->entry); 7563 g_free(state); 7564 } 7565 g_slist_free(saved_list); 7566 7567 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),COMPOSE_TO); 7568 } 7569 7570 /* Set message save folder */ 7571 compose_set_save_to(compose, NULL); 7572 if (compose->folder && compose->folder->prefs && compose->folder->prefs->save_copy_to_folder) { 7573 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE); 7574 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE); 7575 folderidentifier = folder_item_get_identifier(compose->folder); 7576 compose_set_save_to(compose, folderidentifier); 7577 g_free(folderidentifier); 7578 } else if (account_get_special_folder(compose->account, F_OUTBOX)) { 7579 if (compose->account->set_sent_folder || prefs_common.savemsg) 7580 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE); 7581 else 7582 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE); 7583 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE); 7584 folderidentifier = folder_item_get_identifier(account_get_special_folder 7585 (compose->account, F_OUTBOX)); 7586 compose_set_save_to(compose, folderidentifier); 7587 g_free(folderidentifier); 7588 } 7589 } 7590 7591 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path, 7592 GtkTreeViewColumn *column, Compose *compose) 7593 { 7594 compose_attach_property(NULL, compose); 7595 } 7596 7597 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event, 7598 gpointer data) 7599 { 7600 Compose *compose = (Compose *)data; 7601 GtkTreeSelection *attach_selection; 7602 gint attach_nr_selected; 7603 GtkTreePath *path; 7604 7605 if (!event || compose->redirect_filename != NULL) 7606 return FALSE; 7607 7608 if (event->button == 3) { 7609 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)); 7610 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection); 7611 7612 /* If no rows, or just one row is selected, right-click should 7613 * open menu relevant to the row being right-clicked on. We 7614 * achieve that by selecting the clicked row first. If more 7615 * than one row is selected, we shouldn't modify the selection, 7616 * as user may want to remove selected rows (attachments). */ 7617 if (attach_nr_selected < 2) { 7618 gtk_tree_selection_unselect_all(attach_selection); 7619 attach_nr_selected = 0; 7620 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), 7621 event->x, event->y, &path, NULL, NULL, NULL); 7622 if (path != NULL) { 7623 gtk_tree_selection_select_path(attach_selection, path); 7624 gtk_tree_path_free(path); 7625 attach_nr_selected++; 7626 } 7627 } 7628 7629 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0)); 7630 /* Properties menu item makes no sense with more than one row 7631 * selected, the properties dialog can only edit one attachment. */ 7632 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1)); 7633 7634 gtk_menu_popup_at_pointer(GTK_MENU(compose->popupmenu), NULL); 7635 7636 return TRUE; 7637 } 7638 7639 return FALSE; 7640 } 7641 7642 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event, 7643 gpointer data) 7644 { 7645 Compose *compose = (Compose *)data; 7646 7647 if (!event) return FALSE; 7648 7649 switch (event->keyval) { 7650 case GDK_KEY_Delete: 7651 compose_attach_remove_selected(NULL, compose); 7652 break; 7653 } 7654 return FALSE; 7655 } 7656 7657 static void compose_allow_user_actions (Compose *compose, gboolean allow) 7658 { 7659 toolbar_comp_set_sensitive(compose, allow); 7660 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow); 7661 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow); 7662 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow); 7663 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow); 7664 7665 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow); 7666 7667 } 7668 7669 static void compose_send_cb(GtkAction *action, gpointer data) 7670 { 7671 Compose *compose = (Compose *)data; 7672 7673 #ifdef G_OS_UNIX 7674 if (compose->exteditor_tag != -1) { 7675 debug_print("ignoring send: external editor still open\n"); 7676 return; 7677 } 7678 #endif 7679 if (prefs_common.work_offline && 7680 !inc_offline_should_override(TRUE, 7681 _("Claws Mail needs network access in order " 7682 "to send this email."))) 7683 return; 7684 7685 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */ 7686 g_source_remove(compose->draft_timeout_tag); 7687 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; 7688 } 7689 7690 compose_send(compose); 7691 } 7692 7693 static void compose_send_later_cb(GtkAction *action, gpointer data) 7694 { 7695 Compose *compose = (Compose *)data; 7696 ComposeQueueResult val; 7697 7698 inc_lock(); 7699 compose_allow_user_actions(compose, FALSE); 7700 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE); 7701 compose_allow_user_actions(compose, TRUE); 7702 inc_unlock(); 7703 7704 if (val == COMPOSE_QUEUE_SUCCESS) { 7705 compose_close(compose); 7706 } else { 7707 _display_queue_error(val); 7708 } 7709 7710 toolbar_main_set_sensitive(mainwindow_get_mainwindow()); 7711 } 7712 7713 #define DRAFTED_AT_EXIT "drafted_at_exit" 7714 static void compose_register_draft(MsgInfo *info) 7715 { 7716 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 7717 DRAFTED_AT_EXIT, NULL); 7718 FILE *fp = g_fopen(filepath, "ab"); 7719 7720 if (fp) { 7721 gchar *name = folder_item_get_identifier(info->folder); 7722 fprintf(fp, "%s\t%d\n", name, info->msgnum); 7723 g_free(name); 7724 fclose(fp); 7725 } 7726 7727 g_free(filepath); 7728 } 7729 7730 gboolean compose_draft (gpointer data, guint action) 7731 { 7732 Compose *compose = (Compose *)data; 7733 FolderItem *draft; 7734 FolderItemPrefs *prefs; 7735 gchar *tmp; 7736 gchar *sheaders; 7737 gint msgnum; 7738 MsgFlags flag = {0, 0}; 7739 static gboolean lock = FALSE; 7740 MsgInfo *newmsginfo; 7741 FILE *fp; 7742 gboolean target_locked = FALSE; 7743 gboolean err = FALSE; 7744 gint filemode = 0; 7745 7746 if (lock) return FALSE; 7747 7748 if (compose->sending) 7749 return TRUE; 7750 7751 draft = account_get_special_folder(compose->account, F_DRAFT); 7752 cm_return_val_if_fail(draft != NULL, FALSE); 7753 7754 if (!g_mutex_trylock(&compose->mutex)) { 7755 /* we don't want to lock the mutex once it's available, 7756 * because as the only other part of compose.c locking 7757 * it is compose_close - which means once unlocked, 7758 * the compose struct will be freed */ 7759 debug_print("couldn't lock mutex, probably sending\n"); 7760 return FALSE; 7761 } 7762 7763 lock = TRUE; 7764 7765 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(), 7766 G_DIR_SEPARATOR, compose); 7767 if ((fp = g_fopen(tmp, "wb")) == NULL) { 7768 FILE_OP_ERROR(tmp, "g_fopen"); 7769 goto warn_err; 7770 } 7771 7772 prefs = draft->prefs; 7773 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) { 7774 filemode = prefs->folder_chmod; 7775 if (filemode & S_IRGRP) filemode |= S_IWGRP; 7776 if (filemode & S_IROTH) filemode |= S_IWOTH; 7777 if (chmod(tmp, filemode) < 0) 7778 FILE_OP_ERROR(tmp, "chmod"); 7779 } 7780 7781 /* Save draft infos */ 7782 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0); 7783 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0); 7784 7785 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) { 7786 gchar *savefolderid; 7787 7788 savefolderid = compose_get_save_to(compose); 7789 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0); 7790 g_free(savefolderid); 7791 } 7792 7793 /* Message-ID of message replying to */ 7794 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) { 7795 gchar *folderid = NULL; 7796 7797 if (compose->replyinfo->folder) 7798 folderid = folder_item_get_identifier(compose->replyinfo->folder); 7799 if (folderid == NULL) 7800 folderid = g_strdup("NULL"); 7801 7802 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0); 7803 g_free(folderid); 7804 } 7805 /* Message-ID of message forwarding to */ 7806 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) { 7807 gchar *folderid = NULL; 7808 7809 if (compose->fwdinfo->folder) 7810 folderid = folder_item_get_identifier(compose->fwdinfo->folder); 7811 if (folderid == NULL) 7812 folderid = g_strdup("NULL"); 7813 7814 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0); 7815 g_free(folderid); 7816 } 7817 7818 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0); 7819 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0); 7820 7821 sheaders = compose_get_manual_headers_info(compose); 7822 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0); 7823 g_free(sheaders); 7824 7825 /* end of headers */ 7826 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0); 7827 7828 if (err) { 7829 fclose(fp); 7830 goto warn_err; 7831 } 7832 7833 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) { 7834 fclose(fp); 7835 goto warn_err; 7836 } 7837 if (fclose(fp) == EOF) { 7838 goto warn_err; 7839 } 7840 7841 flag.perm_flags = MSG_NEW|MSG_UNREAD; 7842 if (compose->targetinfo) { 7843 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags); 7844 if (target_locked) 7845 flag.perm_flags |= MSG_LOCKED; 7846 } 7847 flag.tmp_flags = MSG_DRAFT; 7848 7849 folder_item_scan(draft); 7850 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) { 7851 MsgInfo *tmpinfo = NULL; 7852 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid"); 7853 if (compose->msgid) { 7854 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid); 7855 } 7856 if (tmpinfo) { 7857 msgnum = tmpinfo->msgnum; 7858 procmsg_msginfo_free(&tmpinfo); 7859 debug_print("got draft msgnum %d from scanning\n", msgnum); 7860 } else { 7861 debug_print("didn't get draft msgnum after scanning\n"); 7862 } 7863 } else { 7864 debug_print("got draft msgnum %d from adding\n", msgnum); 7865 } 7866 if (msgnum < 0) { 7867 warn_err: 7868 unlink(tmp); 7869 g_free(tmp); 7870 if (action != COMPOSE_AUTO_SAVE) { 7871 if (action != COMPOSE_DRAFT_FOR_EXIT) 7872 alertpanel_error(_("Could not save draft.")); 7873 else { 7874 AlertValue val; 7875 gtkut_window_popup(compose->window); 7876 val = alertpanel_full(_("Could not save draft"), 7877 _("Could not save draft.\n" 7878 "Do you want to cancel exit or discard this email?"), 7879 NULL, _("_Cancel exit"), NULL, _("_Discard email"), 7880 NULL, NULL, ALERTFOCUS_FIRST, FALSE, NULL, ALERT_QUESTION); 7881 if (val == G_ALERTALTERNATE) { 7882 lock = FALSE; 7883 g_mutex_unlock(&compose->mutex); /* must be done before closing */ 7884 compose_close(compose); 7885 return TRUE; 7886 } else { 7887 lock = FALSE; 7888 g_mutex_unlock(&compose->mutex); /* must be done before closing */ 7889 return FALSE; 7890 } 7891 } 7892 } 7893 goto unlock; 7894 } 7895 g_free(tmp); 7896 7897 if (compose->mode == COMPOSE_REEDIT) { 7898 compose_remove_reedit_target(compose, TRUE); 7899 } 7900 7901 newmsginfo = folder_item_get_msginfo(draft, msgnum); 7902 7903 if (newmsginfo) { 7904 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0); 7905 if (target_locked) 7906 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT); 7907 else 7908 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT); 7909 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE) 7910 procmsg_msginfo_set_flags(newmsginfo, 0, 7911 MSG_HAS_ATTACHMENT); 7912 7913 if (action == COMPOSE_DRAFT_FOR_EXIT) { 7914 compose_register_draft(newmsginfo); 7915 } 7916 procmsg_msginfo_free(&newmsginfo); 7917 } 7918 7919 folder_item_scan(draft); 7920 7921 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) { 7922 lock = FALSE; 7923 g_mutex_unlock(&compose->mutex); /* must be done before closing */ 7924 compose_close(compose); 7925 return TRUE; 7926 } else { 7927 GStatBuf s; 7928 gchar *path; 7929 goffset size, mtime; 7930 7931 path = folder_item_fetch_msg(draft, msgnum); 7932 if (path == NULL) { 7933 debug_print("can't fetch %s:%d\n", draft->path, msgnum); 7934 goto unlock; 7935 } 7936 if (g_stat(path, &s) < 0) { 7937 FILE_OP_ERROR(path, "stat"); 7938 g_free(path); 7939 goto unlock; 7940 } 7941 size = s.st_size; 7942 mtime = s.st_mtime; 7943 g_free(path); 7944 7945 procmsg_msginfo_free(&(compose->targetinfo)); 7946 compose->targetinfo = procmsg_msginfo_new(); 7947 compose->targetinfo->msgnum = msgnum; 7948 compose->targetinfo->size = size; 7949 compose->targetinfo->mtime = mtime; 7950 compose->targetinfo->folder = draft; 7951 if (target_locked) 7952 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0); 7953 compose->mode = COMPOSE_REEDIT; 7954 7955 if (action == COMPOSE_AUTO_SAVE) { 7956 compose->modified = FALSE; 7957 compose->autosaved_draft = compose->targetinfo; 7958 } 7959 compose_set_title(compose); 7960 } 7961 unlock: 7962 lock = FALSE; 7963 g_mutex_unlock(&compose->mutex); 7964 return TRUE; 7965 } 7966 7967 void compose_clear_exit_drafts(void) 7968 { 7969 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 7970 DRAFTED_AT_EXIT, NULL); 7971 if (is_file_exist(filepath)) 7972 unlink(filepath); 7973 7974 g_free(filepath); 7975 } 7976 7977 void compose_reopen_exit_drafts(void) 7978 { 7979 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, 7980 DRAFTED_AT_EXIT, NULL); 7981 FILE *fp = g_fopen(filepath, "rb"); 7982 gchar buf[1024]; 7983 7984 if (fp) { 7985 while (fgets(buf, sizeof(buf), fp)) { 7986 gchar **parts = g_strsplit(buf, "\t", 2); 7987 const gchar *folder = parts[0]; 7988 int msgnum = parts[1] ? atoi(parts[1]):-1; 7989 7990 if (folder && *folder && msgnum > -1) { 7991 FolderItem *item = folder_find_item_from_identifier(folder); 7992 MsgInfo *info = folder_item_get_msginfo(item, msgnum); 7993 if (info) 7994 compose_reedit(info, FALSE); 7995 } 7996 g_strfreev(parts); 7997 } 7998 fclose(fp); 7999 } 8000 g_free(filepath); 8001 compose_clear_exit_drafts(); 8002 } 8003 8004 static void compose_save_cb(GtkAction *action, gpointer data) 8005 { 8006 Compose *compose = (Compose *)data; 8007 compose_draft(compose, COMPOSE_KEEP_EDITING); 8008 compose->rmode = COMPOSE_REEDIT; 8009 compose->modified = FALSE; 8010 compose_set_title(compose); 8011 } 8012 8013 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data) 8014 { 8015 if (compose && file_list) { 8016 GList *tmp; 8017 8018 for ( tmp = file_list; tmp; tmp = tmp->next) { 8019 gchar *file = (gchar *) tmp->data; 8020 gchar *utf8_filename = conv_filename_to_utf8(file); 8021 compose_attach_append(compose, file, utf8_filename, NULL, NULL); 8022 compose_changed_cb(NULL, compose); 8023 if (free_data) { 8024 g_free(file); 8025 tmp->data = NULL; 8026 } 8027 g_free(utf8_filename); 8028 } 8029 } 8030 } 8031 8032 static void compose_attach_cb(GtkAction *action, gpointer data) 8033 { 8034 Compose *compose = (Compose *)data; 8035 GList *file_list; 8036 8037 if (compose->redirect_filename != NULL) 8038 return; 8039 8040 /* Set focus_window properly, in case we were called via popup menu, 8041 * which unsets it (via focus_out_event callback on compose window). */ 8042 manage_window_focus_in(compose->window, NULL, NULL); 8043 8044 file_list = filesel_select_multiple_files_open(_("Select file"), NULL); 8045 8046 if (file_list) { 8047 compose_attach_from_list(compose, file_list, TRUE); 8048 g_list_free(file_list); 8049 } 8050 } 8051 8052 static void compose_insert_file_cb(GtkAction *action, gpointer data) 8053 { 8054 Compose *compose = (Compose *)data; 8055 GList *file_list; 8056 8057 file_list = filesel_select_multiple_files_open(_("Select file"), NULL); 8058 8059 if (file_list) { 8060 GList *tmp; 8061 8062 for ( tmp = file_list; tmp; tmp = tmp->next) { 8063 gchar *file = (gchar *) tmp->data; 8064 gchar *filedup = g_strdup(file); 8065 gchar *shortfile = g_path_get_basename(filedup); 8066 ComposeInsertResult res; 8067 /* insert the file if the file is short or if the user confirmed that 8068 he/she wants to insert the large file */ 8069 res = compose_insert_file(compose, file); 8070 if (res == COMPOSE_INSERT_READ_ERROR) { 8071 alertpanel_error(_("File '%s' could not be read."), shortfile); 8072 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) { 8073 alertpanel_error(_("File '%s' contained invalid characters\n" 8074 "for the current encoding, insertion may be incorrect."), 8075 shortfile); 8076 } 8077 8078 g_free(shortfile); 8079 g_free(filedup); 8080 g_free(file); 8081 } 8082 g_list_free(file_list); 8083 } 8084 } 8085 8086 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event, 8087 gpointer data) 8088 { 8089 gint x, y; 8090 Compose *compose = (Compose *)data; 8091 8092 gtk_window_get_position(GTK_WINDOW(widget), &x, &y); 8093 if (!compose->batch) { 8094 prefs_common.compose_x = x; 8095 prefs_common.compose_y = y; 8096 } 8097 if (compose->sending || compose->updating) 8098 return TRUE; 8099 compose_close_cb(NULL, compose); 8100 return TRUE; 8101 } 8102 8103 void compose_close_toolbar(Compose *compose) 8104 { 8105 compose_close_cb(NULL, compose); 8106 } 8107 8108 static void compose_close_cb(GtkAction *action, gpointer data) 8109 { 8110 Compose *compose = (Compose *)data; 8111 AlertValue val; 8112 8113 if (compose->exteditor_tag != -1) { 8114 if (!compose_ext_editor_kill(compose)) 8115 return; 8116 } 8117 8118 if (compose->modified) { 8119 gboolean reedit = (compose->rmode == COMPOSE_REEDIT); 8120 if (!g_mutex_trylock(&compose->mutex)) { 8121 /* we don't want to lock the mutex once it's available, 8122 * because as the only other part of compose.c locking 8123 * it is compose_close - which means once unlocked, 8124 * the compose struct will be freed */ 8125 debug_print("couldn't lock mutex, probably sending\n"); 8126 return; 8127 } 8128 if (!reedit || (compose->folder != NULL && compose->folder->stype == F_DRAFT)) { 8129 val = alertpanel(_("Discard message"), 8130 _("This message has been modified. Discard it?"), 8131 NULL, _("_Discard"), NULL, _("_Save to Drafts"), NULL, _("_Cancel"), 8132 ALERTFOCUS_FIRST); 8133 } else { 8134 val = alertpanel(_("Save changes"), 8135 _("This message has been modified. Save the latest changes?"), 8136 NULL, _("_Don't save"), NULL, _("_Save to Drafts"), NULL, _("_Cancel"), 8137 ALERTFOCUS_SECOND); 8138 } 8139 g_mutex_unlock(&compose->mutex); 8140 switch (val) { 8141 case G_ALERTDEFAULT: 8142 if (prefs_common.autosave && !reedit) 8143 compose_remove_draft(compose); 8144 break; 8145 case G_ALERTALTERNATE: 8146 compose_draft(data, COMPOSE_QUIT_EDITING); 8147 return; 8148 default: 8149 return; 8150 } 8151 } 8152 8153 compose_close(compose); 8154 } 8155 8156 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data) 8157 { 8158 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current)); 8159 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current)); 8160 Compose *compose = (Compose *) data; 8161 8162 if (active) 8163 compose->out_encoding = (CharSet)value; 8164 } 8165 8166 static void compose_address_cb(GtkAction *action, gpointer data) 8167 { 8168 Compose *compose = (Compose *)data; 8169 addressbook_open(compose); 8170 } 8171 8172 static void compose_ext_editor_cb(GtkAction *action, gpointer data) 8173 { 8174 Compose *compose = (Compose *)data; 8175 8176 if (compose->exteditor_tag != -1) { 8177 debug_print("ignoring open external editor: external editor still open\n"); 8178 return; 8179 } 8180 compose_exec_ext_editor(compose); 8181 } 8182 8183 static void compose_undo_cb(GtkAction *action, gpointer data) 8184 { 8185 Compose *compose = (Compose *)data; 8186 gboolean prev_autowrap = compose->autowrap; 8187 8188 compose->autowrap = FALSE; 8189 undo_undo(compose->undostruct); 8190 compose->autowrap = prev_autowrap; 8191 } 8192 8193 static void compose_redo_cb(GtkAction *action, gpointer data) 8194 { 8195 Compose *compose = (Compose *)data; 8196 gboolean prev_autowrap = compose->autowrap; 8197 8198 compose->autowrap = FALSE; 8199 undo_redo(compose->undostruct); 8200 compose->autowrap = prev_autowrap; 8201 } 8202 8203 static void entry_cut_clipboard(GtkWidget *entry) 8204 { 8205 if (GTK_IS_EDITABLE(entry)) 8206 gtk_editable_cut_clipboard (GTK_EDITABLE(entry)); 8207 else if (GTK_IS_TEXT_VIEW(entry)) 8208 gtk_text_buffer_cut_clipboard( 8209 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)), 8210 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), 8211 TRUE); 8212 } 8213 8214 static void entry_copy_clipboard(GtkWidget *entry) 8215 { 8216 if (GTK_IS_EDITABLE(entry)) 8217 gtk_editable_copy_clipboard (GTK_EDITABLE(entry)); 8218 else if (GTK_IS_TEXT_VIEW(entry)) 8219 gtk_text_buffer_copy_clipboard( 8220 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)), 8221 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)); 8222 } 8223 8224 static void paste_text(Compose *compose, GtkWidget *entry, GdkAtom clip, GtkTextIter *insert_place, const gchar *contents) 8225 { 8226 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)); 8227 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer); 8228 GtkTextIter start_iter, end_iter; 8229 gint start, end; 8230 8231 if (contents == NULL) 8232 return; 8233 8234 /* we shouldn't delete the selection when middle-click-pasting, or we 8235 * can't mid-click-paste our own selection */ 8236 if (clip != GDK_SELECTION_PRIMARY) { 8237 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct); 8238 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE); 8239 } 8240 8241 if (insert_place == NULL) { 8242 /* if insert_place isn't specified, insert at the cursor. 8243 * used for Ctrl-V pasting */ 8244 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start); 8245 start = gtk_text_iter_get_offset(&start_iter); 8246 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents)); 8247 } else { 8248 /* if insert_place is specified, paste here. 8249 * used for mid-click-pasting */ 8250 start = gtk_text_iter_get_offset(insert_place); 8251 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents)); 8252 if (prefs_common.primary_paste_unselects) 8253 gtk_text_buffer_select_range(buffer, insert_place, insert_place); 8254 } 8255 8256 end = start + strlen(contents); 8257 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start); 8258 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end); 8259 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter); 8260 compose->modified = TRUE; 8261 } 8262 8263 static void attach_uri_list(Compose *compose, GtkSelectionData *data) 8264 { 8265 GList *list, *tmp; 8266 gchar *warn_files = NULL; 8267 8268 list = uri_list_extract_filenames( 8269 (const gchar *)gtk_selection_data_get_data(data)); 8270 for (tmp = list; tmp != NULL; tmp = tmp->next) { 8271 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data); 8272 gchar *tmp_f = g_strdup_printf("%s%s\n", 8273 warn_files?warn_files:"", 8274 utf8_filename); 8275 g_free(warn_files); 8276 warn_files = tmp_f; 8277 compose_attach_append 8278 (compose, (const gchar *)tmp->data, 8279 utf8_filename, NULL, NULL); 8280 g_free(utf8_filename); 8281 } 8282 if (list) { 8283 AlertValue val = 0; 8284 compose_changed_cb(NULL, compose); 8285 if (prefs_common.notify_pasted_attachments) { 8286 gchar *msg; 8287 8288 msg = g_strdup_printf("Attached:\n%s", warn_files); 8289 val = alertpanel_full(_("Notice"), msg, 8290 NULL, _("_Close"), NULL, NULL, NULL, NULL, 8291 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_NOTICE); 8292 g_free(msg); 8293 } 8294 g_free(warn_files); 8295 if (val & G_ALERTDISABLE) 8296 prefs_common.notify_pasted_attachments = FALSE; 8297 } 8298 list_free_strings_full(list); 8299 } 8300 8301 int attach_image(Compose *compose, GtkSelectionData *data, const gchar *subtype) 8302 { 8303 FILE *fp; 8304 const guchar *contents; 8305 gchar *tmpf; 8306 gchar *file; 8307 gchar *type; 8308 size_t len; 8309 int r; 8310 8311 cm_return_val_if_fail(data != NULL, -1); 8312 8313 contents = gtk_selection_data_get_data(data); 8314 len = gtk_selection_data_get_length(data); 8315 8316 tmpf = get_tmp_file(); 8317 file = g_strconcat(tmpf, "-image.", subtype, NULL); 8318 g_free(tmpf); 8319 8320 debug_print("writing image to %s\n", file); 8321 8322 if ((fp = g_fopen(file, "wb")) == NULL) { 8323 FILE_OP_ERROR(file, "g_fopen"); 8324 g_free(file); 8325 return -1; 8326 } 8327 8328 if (fwrite(contents, 1, len, fp) != len) { 8329 FILE_OP_ERROR(file, "fwrite"); 8330 fclose(fp); 8331 if (unlink(file) < 0) 8332 FILE_OP_ERROR(file, "unlink"); 8333 g_free(file); 8334 return -1; 8335 } 8336 8337 r = fclose(fp); 8338 8339 if (r == EOF) { 8340 FILE_OP_ERROR(file, "fclose"); 8341 if (unlink(file) < 0) 8342 FILE_OP_ERROR(file, "unlink"); 8343 g_free(file); 8344 return -1; 8345 } 8346 8347 type = g_strconcat("image/", subtype, NULL); 8348 8349 compose_attach_append(compose, (const gchar *)file, 8350 (const gchar *)file, type, NULL); 8351 8352 alertpanel_notice(_("The pasted image has been attached as: \n%s"), file); 8353 8354 g_free(file); 8355 g_free(type); 8356 8357 return 0; 8358 } 8359 8360 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, 8361 GdkAtom clip, GtkTextIter *insert_place) 8362 { 8363 if (GTK_IS_TEXT_VIEW(entry)) { 8364 GdkAtom types = gdk_atom_intern ("TARGETS", FALSE); 8365 GdkAtom *targets = NULL; 8366 int n_targets = 0, i; 8367 gboolean paste_done = FALSE; 8368 GtkClipboard *clipboard = gtk_clipboard_get(clip); 8369 8370 GtkSelectionData *contents = gtk_clipboard_wait_for_contents( 8371 clipboard, types); 8372 8373 if (contents != NULL) { 8374 gtk_selection_data_get_targets(contents, &targets, &n_targets); 8375 gtk_selection_data_free(contents); 8376 } 8377 8378 for (i = 0; i < n_targets; i++) { 8379 GdkAtom atom = targets[i]; 8380 gchar *atom_type = gdk_atom_name(atom); 8381 8382 if (atom_type != NULL && strchr(atom_type, '/')) { 8383 GtkSelectionData *data = gtk_clipboard_wait_for_contents( 8384 clipboard, atom); 8385 debug_print("got contents of type %s\n", atom_type); 8386 if (!strcmp(atom_type, "text/plain")) { 8387 /* let the default text handler handle it */ 8388 break; 8389 } else if (!strcmp(atom_type, "text/uri-list")) { 8390 attach_uri_list(compose, data); 8391 8392 paste_done = TRUE; 8393 break; 8394 } else if (!strncmp(atom_type, "image/", strlen("image/"))) { 8395 gchar *subtype = g_strdup((gchar *)(strstr(atom_type, "/")+1)); 8396 debug_print("image of type %s\n", subtype); 8397 attach_image(compose, data, subtype); 8398 g_free(subtype); 8399 8400 paste_done = TRUE; 8401 break; 8402 } 8403 } 8404 } 8405 if (!paste_done) { 8406 gchar *def_text = gtk_clipboard_wait_for_text(clipboard); 8407 paste_text(compose, entry, clip, insert_place, def_text); 8408 g_free(def_text); 8409 } 8410 g_free(targets); 8411 } else if (GTK_IS_EDITABLE(entry)) { 8412 gtk_editable_paste_clipboard (GTK_EDITABLE(entry)); 8413 compose->modified = TRUE; 8414 } 8415 } 8416 8417 static void entry_allsel(GtkWidget *entry) 8418 { 8419 if (GTK_IS_EDITABLE(entry)) 8420 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1); 8421 else if (GTK_IS_TEXT_VIEW(entry)) { 8422 GtkTextIter startiter, enditer; 8423 GtkTextBuffer *textbuf; 8424 8425 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)); 8426 gtk_text_buffer_get_start_iter(textbuf, &startiter); 8427 gtk_text_buffer_get_end_iter(textbuf, &enditer); 8428 8429 gtk_text_buffer_move_mark_by_name(textbuf, 8430 "selection_bound", &startiter); 8431 gtk_text_buffer_move_mark_by_name(textbuf, 8432 "insert", &enditer); 8433 } 8434 } 8435 8436 static void compose_cut_cb(GtkAction *action, gpointer data) 8437 { 8438 Compose *compose = (Compose *)data; 8439 if (compose->focused_editable && gtk_widget_has_focus(compose->focused_editable)) 8440 entry_cut_clipboard(compose->focused_editable); 8441 } 8442 8443 static void compose_copy_cb(GtkAction *action, gpointer data) 8444 { 8445 Compose *compose = (Compose *)data; 8446 if (compose->focused_editable && gtk_widget_has_focus(compose->focused_editable)) 8447 entry_copy_clipboard(compose->focused_editable); 8448 } 8449 8450 static void compose_paste_cb(GtkAction *action, gpointer data) 8451 { 8452 Compose *compose = (Compose *)data; 8453 if (compose->focused_editable && gtk_widget_has_focus(compose->focused_editable)) 8454 entry_paste_clipboard(compose, compose->focused_editable, GDK_SELECTION_CLIPBOARD, NULL); 8455 } 8456 8457 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data) 8458 { 8459 Compose *compose = (Compose *)data; 8460 if (compose->focused_editable && gtk_widget_has_focus(compose->focused_editable)) { 8461 /* let text_insert() (called directly or at a later time 8462 * after the gtk_editable_paste_clipboard) know that 8463 * text is to be inserted as a quotation. implemented 8464 * by using a simple refcount... */ 8465 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data( 8466 G_OBJECT(compose->focused_editable), 8467 "paste_as_quotation")); 8468 g_object_set_data(G_OBJECT(compose->focused_editable), 8469 "paste_as_quotation", 8470 GINT_TO_POINTER(paste_as_quotation + 1)); 8471 entry_paste_clipboard(compose, compose->focused_editable, GDK_SELECTION_CLIPBOARD, NULL); 8472 } 8473 } 8474 8475 static void compose_allsel_cb(GtkAction *action, gpointer data) 8476 { 8477 Compose *compose = (Compose *)data; 8478 if (compose->focused_editable && gtk_widget_has_focus(compose->focused_editable)) 8479 entry_allsel(compose->focused_editable); 8480 } 8481 8482 static void textview_move_beginning_of_line (GtkTextView *text) 8483 { 8484 GtkTextBuffer *buffer; 8485 GtkTextMark *mark; 8486 GtkTextIter ins; 8487 8488 cm_return_if_fail(GTK_IS_TEXT_VIEW(text)); 8489 8490 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); 8491 mark = gtk_text_buffer_get_insert(buffer); 8492 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark); 8493 gtk_text_iter_set_line_offset(&ins, 0); 8494 gtk_text_buffer_place_cursor(buffer, &ins); 8495 } 8496 8497 static void textview_move_forward_character (GtkTextView *text) 8498 { 8499 GtkTextBuffer *buffer; 8500 GtkTextMark *mark; 8501 GtkTextIter ins; 8502 8503 cm_return_if_fail(GTK_IS_TEXT_VIEW(text)); 8504 8505 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); 8506 mark = gtk_text_buffer_get_insert(buffer); 8507 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark); 8508 if (gtk_text_iter_forward_cursor_position(&ins)) 8509 gtk_text_buffer_place_cursor(buffer, &ins); 8510 } 8511 8512 static void textview_move_backward_character (GtkTextView *text) 8513 { 8514 GtkTextBuffer *buffer; 8515 GtkTextMark *mark; 8516 GtkTextIter ins; 8517 8518 cm_return_if_fail(GTK_IS_TEXT_VIEW(text)); 8519 8520 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); 8521 mark = gtk_text_buffer_get_insert(buffer); 8522 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark); 8523 if (gtk_text_iter_backward_cursor_position(&ins)) 8524 gtk_text_buffer_place_cursor(buffer, &ins); 8525 } 8526 8527 static void textview_move_forward_word (GtkTextView *text) 8528 { 8529 GtkTextBuffer *buffer; 8530 GtkTextMark *mark; 8531 GtkTextIter ins; 8532 gint count; 8533 8534 cm_return_if_fail(GTK_IS_TEXT_VIEW(text)); 8535 8536 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); 8537 mark = gtk_text_buffer_get_insert(buffer); 8538 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark); 8539 count = gtk_text_iter_inside_word (&ins) ? 2 : 1; 8540 if (gtk_text_iter_forward_word_ends(&ins, count)) { 8541 gtk_text_iter_backward_word_start(&ins); 8542 gtk_text_buffer_place_cursor(buffer, &ins); 8543 } 8544 } 8545 8546 static void textview_move_backward_word (GtkTextView *text) 8547 { 8548 GtkTextBuffer *buffer; 8549 GtkTextMark *mark; 8550 GtkTextIter ins; 8551 8552 cm_return_if_fail(GTK_IS_TEXT_VIEW(text)); 8553 8554 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); 8555 mark = gtk_text_buffer_get_insert(buffer); 8556 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark); 8557 if (gtk_text_iter_backward_word_starts(&ins, 1)) 8558 gtk_text_buffer_place_cursor(buffer, &ins); 8559 } 8560 8561 static void textview_move_end_of_line (GtkTextView *text) 8562 { 8563 GtkTextBuffer *buffer; 8564 GtkTextMark *mark; 8565 GtkTextIter ins; 8566 8567 cm_return_if_fail(GTK_IS_TEXT_VIEW(text)); 8568 8569 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); 8570 mark = gtk_text_buffer_get_insert(buffer); 8571 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark); 8572 if (gtk_text_iter_forward_to_line_end(&ins)) 8573 gtk_text_buffer_place_cursor(buffer, &ins); 8574 } 8575 8576 static void textview_move_next_line (GtkTextView *text) 8577 { 8578 GtkTextBuffer *buffer; 8579 GtkTextMark *mark; 8580 GtkTextIter ins; 8581 gint offset; 8582 8583 cm_return_if_fail(GTK_IS_TEXT_VIEW(text)); 8584 8585 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); 8586 mark = gtk_text_buffer_get_insert(buffer); 8587 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark); 8588 offset = gtk_text_iter_get_line_offset(&ins); 8589 if (gtk_text_iter_forward_line(&ins)) { 8590 gtk_text_iter_set_line_offset(&ins, offset); 8591 gtk_text_buffer_place_cursor(buffer, &ins); 8592 } 8593 } 8594 8595 static void textview_move_previous_line (GtkTextView *text) 8596 { 8597 GtkTextBuffer *buffer; 8598 GtkTextMark *mark; 8599 GtkTextIter ins; 8600 gint offset; 8601 8602 cm_return_if_fail(GTK_IS_TEXT_VIEW(text)); 8603 8604 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); 8605 mark = gtk_text_buffer_get_insert(buffer); 8606 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark); 8607 offset = gtk_text_iter_get_line_offset(&ins); 8608 if (gtk_text_iter_backward_line(&ins)) { 8609 gtk_text_iter_set_line_offset(&ins, offset); 8610 gtk_text_buffer_place_cursor(buffer, &ins); 8611 } 8612 } 8613 8614 static void textview_delete_forward_character (GtkTextView *text) 8615 { 8616 GtkTextBuffer *buffer; 8617 GtkTextMark *mark; 8618 GtkTextIter ins, end_iter; 8619 8620 cm_return_if_fail(GTK_IS_TEXT_VIEW(text)); 8621 8622 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); 8623 mark = gtk_text_buffer_get_insert(buffer); 8624 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark); 8625 end_iter = ins; 8626 if (gtk_text_iter_forward_char(&end_iter)) { 8627 gtk_text_buffer_delete(buffer, &ins, &end_iter); 8628 } 8629 } 8630 8631 static void textview_delete_backward_character (GtkTextView *text) 8632 { 8633 GtkTextBuffer *buffer; 8634 GtkTextMark *mark; 8635 GtkTextIter ins, end_iter; 8636 8637 cm_return_if_fail(GTK_IS_TEXT_VIEW(text)); 8638 8639 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); 8640 mark = gtk_text_buffer_get_insert(buffer); 8641 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark); 8642 end_iter = ins; 8643 if (gtk_text_iter_backward_char(&end_iter)) { 8644 gtk_text_buffer_delete(buffer, &end_iter, &ins); 8645 } 8646 } 8647 8648 static void textview_delete_forward_word (GtkTextView *text) 8649 { 8650 GtkTextBuffer *buffer; 8651 GtkTextMark *mark; 8652 GtkTextIter ins, end_iter; 8653 8654 cm_return_if_fail(GTK_IS_TEXT_VIEW(text)); 8655 8656 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); 8657 mark = gtk_text_buffer_get_insert(buffer); 8658 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark); 8659 end_iter = ins; 8660 if (gtk_text_iter_forward_word_end(&end_iter)) { 8661 gtk_text_buffer_delete(buffer, &ins, &end_iter); 8662 } 8663 } 8664 8665 static void textview_delete_backward_word (GtkTextView *text) 8666 { 8667 GtkTextBuffer *buffer; 8668 GtkTextMark *mark; 8669 GtkTextIter ins, end_iter; 8670 8671 cm_return_if_fail(GTK_IS_TEXT_VIEW(text)); 8672 8673 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); 8674 mark = gtk_text_buffer_get_insert(buffer); 8675 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark); 8676 end_iter = ins; 8677 if (gtk_text_iter_backward_word_start(&end_iter)) { 8678 gtk_text_buffer_delete(buffer, &end_iter, &ins); 8679 } 8680 } 8681 8682 static void textview_delete_line (GtkTextView *text) 8683 { 8684 GtkTextBuffer *buffer; 8685 GtkTextMark *mark; 8686 GtkTextIter ins, start_iter, end_iter; 8687 8688 cm_return_if_fail(GTK_IS_TEXT_VIEW(text)); 8689 8690 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); 8691 mark = gtk_text_buffer_get_insert(buffer); 8692 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark); 8693 8694 start_iter = ins; 8695 gtk_text_iter_set_line_offset(&start_iter, 0); 8696 8697 end_iter = ins; 8698 if (gtk_text_iter_ends_line(&end_iter)){ 8699 if (!gtk_text_iter_forward_char(&end_iter)) 8700 gtk_text_iter_backward_char(&start_iter); 8701 } 8702 else 8703 gtk_text_iter_forward_to_line_end(&end_iter); 8704 gtk_text_buffer_delete(buffer, &start_iter, &end_iter); 8705 } 8706 8707 static void textview_delete_to_line_end (GtkTextView *text) 8708 { 8709 GtkTextBuffer *buffer; 8710 GtkTextMark *mark; 8711 GtkTextIter ins, end_iter; 8712 8713 cm_return_if_fail(GTK_IS_TEXT_VIEW(text)); 8714 8715 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)); 8716 mark = gtk_text_buffer_get_insert(buffer); 8717 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark); 8718 end_iter = ins; 8719 if (gtk_text_iter_ends_line(&end_iter)) 8720 gtk_text_iter_forward_char(&end_iter); 8721 else 8722 gtk_text_iter_forward_to_line_end(&end_iter); 8723 gtk_text_buffer_delete(buffer, &ins, &end_iter); 8724 } 8725 8726 #define DO_ACTION(name, act) { \ 8727 if(!strcmp(name, a_name)) { \ 8728 return act; \ 8729 } \ 8730 } 8731 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action) 8732 { 8733 const gchar *a_name = gtk_action_get_name(action); 8734 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER); 8735 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER); 8736 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD); 8737 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD); 8738 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE); 8739 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE); 8740 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE); 8741 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE); 8742 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER); 8743 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER); 8744 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD); 8745 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD); 8746 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE); 8747 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END); 8748 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED; 8749 } 8750 8751 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data) 8752 { 8753 Compose *compose = (Compose *)data; 8754 GtkTextView *text = GTK_TEXT_VIEW(compose->text); 8755 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED; 8756 8757 action = compose_call_advanced_action_from_path(gaction); 8758 8759 static struct { 8760 void (*do_action) (GtkTextView *text); 8761 } action_table[] = { 8762 {textview_move_beginning_of_line}, 8763 {textview_move_forward_character}, 8764 {textview_move_backward_character}, 8765 {textview_move_forward_word}, 8766 {textview_move_backward_word}, 8767 {textview_move_end_of_line}, 8768 {textview_move_next_line}, 8769 {textview_move_previous_line}, 8770 {textview_delete_forward_character}, 8771 {textview_delete_backward_character}, 8772 {textview_delete_forward_word}, 8773 {textview_delete_backward_word}, 8774 {textview_delete_line}, 8775 {textview_delete_to_line_end} 8776 }; 8777 8778 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return; 8779 8780 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE && 8781 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) { 8782 if (action_table[action].do_action) 8783 action_table[action].do_action(text); 8784 else 8785 g_warning("not implemented yet"); 8786 } 8787 } 8788 8789 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose) 8790 { 8791 GtkAllocation allocation; 8792 GtkWidget *parent; 8793 gchar *str = NULL; 8794 8795 if (GTK_IS_EDITABLE(widget)) { 8796 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1); 8797 gtk_editable_set_position(GTK_EDITABLE(widget), 8798 strlen(str)); 8799 g_free(str); 8800 if ((parent = gtk_widget_get_parent(widget)) 8801 && (parent = gtk_widget_get_parent(parent)) 8802 && (parent = gtk_widget_get_parent(parent))) { 8803 if (GTK_IS_SCROLLED_WINDOW(parent)) { 8804 gtk_widget_get_allocation(widget, &allocation); 8805 gint y = allocation.y; 8806 gint height = allocation.height; 8807 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment 8808 (GTK_SCROLLED_WINDOW(parent)); 8809 8810 gfloat value = gtk_adjustment_get_value(shown); 8811 gfloat upper = gtk_adjustment_get_upper(shown); 8812 gfloat page_size = gtk_adjustment_get_page_size(shown); 8813 if (y < (int)value) { 8814 gtk_adjustment_set_value(shown, y - 1); 8815 } 8816 if ((y + height) > ((int)value + (int)page_size)) { 8817 if ((y - height - 1) < ((int)upper - (int)page_size)) { 8818 gtk_adjustment_set_value(shown, 8819 y + height - (int)page_size - 1); 8820 } else { 8821 gtk_adjustment_set_value(shown, 8822 (int)upper - (int)page_size - 1); 8823 } 8824 } 8825 } 8826 } 8827 } 8828 8829 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget)) 8830 compose->focused_editable = widget; 8831 } 8832 8833 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose) 8834 { 8835 compose->modified = TRUE; 8836 compose_set_title(compose); 8837 } 8838 8839 static void compose_wrap_cb(GtkAction *action, gpointer data) 8840 { 8841 Compose *compose = (Compose *)data; 8842 compose_beautify_paragraph(compose, NULL, TRUE); 8843 } 8844 8845 static void compose_wrap_all_cb(GtkAction *action, gpointer data) 8846 { 8847 Compose *compose = (Compose *)data; 8848 compose_wrap_all_full(compose, TRUE); 8849 } 8850 8851 static void compose_find_cb(GtkAction *action, gpointer data) 8852 { 8853 Compose *compose = (Compose *)data; 8854 8855 message_search_compose(compose); 8856 } 8857 8858 static void compose_toggle_autowrap_cb(GtkToggleAction *action, 8859 gpointer data) 8860 { 8861 Compose *compose = (Compose *)data; 8862 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); 8863 if (compose->autowrap) 8864 compose_wrap_all_full(compose, TRUE); 8865 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); 8866 } 8867 8868 static void compose_toggle_autoindent_cb(GtkToggleAction *action, 8869 gpointer data) 8870 { 8871 Compose *compose = (Compose *)data; 8872 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); 8873 } 8874 8875 static void compose_attach_drag_received_cb (GtkWidget *widget, 8876 GdkDragContext *context, 8877 gint x, 8878 gint y, 8879 GtkSelectionData *data, 8880 guint info, 8881 guint time, 8882 gpointer user_data) 8883 { 8884 Compose *compose = (Compose *)user_data; 8885 GdkAtom type; 8886 8887 type = gtk_selection_data_get_data_type(data); 8888 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) 8889 && gtk_drag_get_source_widget(context) != 8890 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) { 8891 attach_uri_list(compose, data); 8892 } else if (gtk_drag_get_source_widget(context) 8893 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) { 8894 /* comes from our summaryview */ 8895 SummaryView * summaryview = NULL; 8896 GSList * list = NULL, *cur = NULL; 8897 8898 if (mainwindow_get_mainwindow()) 8899 summaryview = mainwindow_get_mainwindow()->summaryview; 8900 8901 if (summaryview) 8902 list = summary_get_selected_msg_list(summaryview); 8903 8904 for (cur = list; cur; cur = cur->next) { 8905 MsgInfo *msginfo = (MsgInfo *)cur->data; 8906 gchar *file = NULL; 8907 if (msginfo) 8908 file = procmsg_get_message_file_full(msginfo, 8909 TRUE, TRUE); 8910 if (file) { 8911 compose_attach_append(compose, (const gchar *)file, 8912 (const gchar *)file, "message/rfc822", NULL); 8913 g_free(file); 8914 } 8915 } 8916 g_slist_free(list); 8917 } 8918 } 8919 8920 static gboolean compose_drag_drop(GtkWidget *widget, 8921 GdkDragContext *drag_context, 8922 gint x, gint y, 8923 guint time, gpointer user_data) 8924 { 8925 /* not handling this signal makes compose_insert_drag_received_cb 8926 * called twice */ 8927 return TRUE; 8928 } 8929 8930 static gboolean completion_set_focus_to_subject 8931 (GtkWidget *widget, 8932 GdkEventKey *event, 8933 Compose *compose) 8934 { 8935 cm_return_val_if_fail(compose != NULL, FALSE); 8936 8937 /* make backtab move to subject field */ 8938 if(event->keyval == GDK_KEY_ISO_Left_Tab) { 8939 gtk_widget_grab_focus(compose->subject_entry); 8940 return TRUE; 8941 } 8942 return FALSE; 8943 } 8944 8945 static void compose_insert_drag_received_cb (GtkWidget *widget, 8946 GdkDragContext *drag_context, 8947 gint x, 8948 gint y, 8949 GtkSelectionData *data, 8950 guint info, 8951 guint time, 8952 gpointer user_data) 8953 { 8954 Compose *compose = (Compose *)user_data; 8955 GList *list, *tmp; 8956 GdkAtom type; 8957 gchar *msg; 8958 8959 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE) 8960 * does not work */ 8961 type = gtk_selection_data_get_data_type(data); 8962 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) { 8963 AlertValue val = G_ALERTDEFAULT; 8964 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data); 8965 8966 list = uri_list_extract_filenames(ddata); 8967 if (list == NULL && strstr(ddata, "://")) { 8968 /* Assume a list of no files, and data has ://, is a remote link */ 8969 gchar *tmpdata = g_strstrip(g_strdup(ddata)); 8970 gchar *tmpfile = get_tmp_file(); 8971 str_write_to_file(tmpdata, tmpfile); 8972 g_free(tmpdata); 8973 compose_insert_file(compose, tmpfile); 8974 unlink(tmpfile); 8975 g_free(tmpfile); 8976 gtk_drag_finish(drag_context, TRUE, FALSE, time); 8977 compose_beautify_paragraph(compose, NULL, compose->autowrap); 8978 return; 8979 } 8980 switch (prefs_common.compose_dnd_mode) { 8981 case COMPOSE_DND_ASK: 8982 msg = g_strdup_printf("Insert to the message body or attach to the email?"); 8983 val = alertpanel_full(_("Insert or attach?"), msg, 8984 NULL, _("_Cancel"), NULL, _("_Insert"), NULL, _("_Attach"), 8985 ALERTFOCUS_SECOND, TRUE, NULL, ALERT_QUESTION); 8986 g_free(msg); 8987 break; 8988 case COMPOSE_DND_INSERT: 8989 val = G_ALERTALTERNATE; 8990 break; 8991 case COMPOSE_DND_ATTACH: 8992 val = G_ALERTOTHER; 8993 break; 8994 default: 8995 /* unexpected case */ 8996 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()"); 8997 } 8998 8999 if (val & G_ALERTDISABLE) { 9000 val &= ~G_ALERTDISABLE; 9001 /* remember what action to perform by default, only if we don't click Cancel */ 9002 if (val == G_ALERTALTERNATE) 9003 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT; 9004 else if (val == G_ALERTOTHER) 9005 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH; 9006 } 9007 9008 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) { 9009 gtk_drag_finish(drag_context, FALSE, FALSE, time); 9010 list_free_strings_full(list); 9011 return; 9012 } else if (val == G_ALERTOTHER) { 9013 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data); 9014 list_free_strings_full(list); 9015 return; 9016 } 9017 9018 for (tmp = list; tmp != NULL; tmp = tmp->next) { 9019 compose_insert_file(compose, (const gchar *)tmp->data); 9020 } 9021 list_free_strings_full(list); 9022 gtk_drag_finish(drag_context, TRUE, FALSE, time); 9023 return; 9024 } 9025 } 9026 9027 static void compose_header_drag_received_cb (GtkWidget *widget, 9028 GdkDragContext *drag_context, 9029 gint x, 9030 gint y, 9031 GtkSelectionData *data, 9032 guint info, 9033 guint time, 9034 gpointer user_data) 9035 { 9036 GtkEditable *entry = (GtkEditable *)user_data; 9037 const gchar *email = (const gchar *)gtk_selection_data_get_data(data); 9038 9039 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE) 9040 * does not work */ 9041 9042 if (!strncmp(email, "mailto:", strlen("mailto:"))) { 9043 gchar *decoded=g_new(gchar, strlen(email)+1); 9044 int start = 0; 9045 9046 decode_uri(decoded, email + strlen("mailto:")); /* will fit */ 9047 gtk_editable_delete_text(entry, 0, -1); 9048 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start); 9049 gtk_drag_finish(drag_context, TRUE, FALSE, time); 9050 g_free(decoded); 9051 return; 9052 } 9053 gtk_drag_finish(drag_context, TRUE, FALSE, time); 9054 } 9055 9056 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data) 9057 { 9058 Compose *compose = (Compose *)data; 9059 9060 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) 9061 compose->remove_references = TRUE; 9062 else 9063 compose->remove_references = FALSE; 9064 } 9065 9066 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button, 9067 ComposeHeaderEntry *headerentry) 9068 { 9069 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), ""); 9070 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL); 9071 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL); 9072 return FALSE; 9073 } 9074 9075 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry, 9076 GdkEventKey *event, 9077 ComposeHeaderEntry *headerentry) 9078 { 9079 if ((g_slist_length(headerentry->compose->header_list) > 0) && 9080 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) && 9081 !(event->state & GDK_MODIFIER_MASK) && 9082 (event->keyval == GDK_KEY_BackSpace) && 9083 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) { 9084 gtk_container_remove 9085 (GTK_CONTAINER(headerentry->compose->header_table), 9086 headerentry->combo); 9087 gtk_container_remove 9088 (GTK_CONTAINER(headerentry->compose->header_table), 9089 headerentry->entry); 9090 headerentry->compose->header_list = 9091 g_slist_remove(headerentry->compose->header_list, 9092 headerentry); 9093 g_free(headerentry); 9094 } else if (event->keyval == GDK_KEY_Tab) { 9095 if (headerentry->compose->header_last == headerentry) { 9096 /* Override default next focus, and give it to subject_entry 9097 * instead of notebook tabs 9098 */ 9099 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event"); 9100 gtk_widget_grab_focus(headerentry->compose->subject_entry); 9101 return TRUE; 9102 } 9103 } 9104 return FALSE; 9105 } 9106 9107 static gboolean scroll_postpone(gpointer data) 9108 { 9109 Compose *compose = (Compose *)data; 9110 9111 if (compose->batch) 9112 return FALSE; 9113 9114 GTK_EVENTS_FLUSH(); 9115 compose_show_first_last_header(compose, FALSE); 9116 return FALSE; 9117 } 9118 9119 static void compose_headerentry_changed_cb(GtkWidget *entry, 9120 ComposeHeaderEntry *headerentry) 9121 { 9122 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) { 9123 compose_create_header_entry(headerentry->compose); 9124 g_signal_handlers_disconnect_matched 9125 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA, 9126 0, 0, NULL, NULL, headerentry); 9127 9128 if (!headerentry->compose->batch) 9129 g_timeout_add(0, scroll_postpone, headerentry->compose); 9130 } 9131 } 9132 9133 static gboolean compose_defer_auto_save_draft(Compose *compose) 9134 { 9135 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; 9136 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE); 9137 return FALSE; 9138 } 9139 9140 static void compose_show_first_last_header(Compose *compose, gboolean show_first) 9141 { 9142 GtkAdjustment *vadj; 9143 9144 cm_return_if_fail(compose); 9145 9146 if(compose->batch) 9147 return; 9148 9149 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table)); 9150 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table))); 9151 vadj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(gtk_widget_get_parent(compose->header_table))); 9152 gtk_adjustment_set_value(vadj, (show_first ? 9153 gtk_adjustment_get_lower(vadj) : 9154 (gtk_adjustment_get_upper(vadj) - 9155 gtk_adjustment_get_page_size(vadj)))); 9156 } 9157 9158 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter, 9159 const gchar *text, gint len, Compose *compose) 9160 { 9161 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data 9162 (G_OBJECT(compose->text), "paste_as_quotation")); 9163 GtkTextMark *mark; 9164 9165 cm_return_if_fail(text != NULL); 9166 9167 g_signal_handlers_block_by_func(G_OBJECT(buffer), 9168 G_CALLBACK(text_inserted), 9169 compose); 9170 if (paste_as_quotation) { 9171 // TODO delete 9172 } else { 9173 if (strcmp(text, "\n") || compose->automatic_break 9174 || gtk_text_iter_starts_line(iter)) { 9175 GtkTextIter before_ins; 9176 gtk_text_buffer_insert(buffer, iter, text, len); 9177 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) { 9178 before_ins = *iter; 9179 gtk_text_iter_backward_chars(&before_ins, len); 9180 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter); 9181 } 9182 } else { 9183 /* check if the preceding is just whitespace or quote */ 9184 GtkTextIter start_line; 9185 gchar *tmp = NULL, *quote = NULL; 9186 gint quote_len = 0, is_normal = 0; 9187 start_line = *iter; 9188 gtk_text_iter_set_line_offset(&start_line, 0); 9189 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE); 9190 g_strstrip(tmp); 9191 9192 if (*tmp == '\0') { 9193 is_normal = 1; 9194 } else { 9195 quote = compose_get_quote_str(buffer, &start_line, "e_len); 9196 if (quote) 9197 is_normal = 1; 9198 g_free(quote); 9199 } 9200 g_free(tmp); 9201 9202 if (is_normal) { 9203 gtk_text_buffer_insert(buffer, iter, text, len); 9204 } else { 9205 gtk_text_buffer_insert_with_tags_by_name(buffer, 9206 iter, text, len, "no_join", NULL); 9207 } 9208 } 9209 } 9210 9211 if (!paste_as_quotation) { 9212 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE); 9213 compose_beautify_paragraph(compose, iter, FALSE); 9214 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark); 9215 gtk_text_buffer_delete_mark(buffer, mark); 9216 } 9217 9218 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), 9219 G_CALLBACK(text_inserted), 9220 compose); 9221 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text"); 9222 9223 if (prefs_common.autosave && 9224 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 && 9225 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */) 9226 compose->draft_timeout_tag = g_timeout_add 9227 (500, (GSourceFunc) compose_defer_auto_save_draft, compose); 9228 } 9229 9230 /*! 9231 *\brief Guess originating forward account from MsgInfo and several 9232 * "common preference" settings. Return NULL if no guess. 9233 */ 9234 static PrefsAccount *compose_find_account(MsgInfo *msginfo) 9235 { 9236 PrefsAccount *account = NULL; 9237 9238 cm_return_val_if_fail(msginfo, NULL); 9239 cm_return_val_if_fail(msginfo->folder, NULL); 9240 cm_return_val_if_fail(msginfo->folder->prefs, NULL); 9241 9242 if (msginfo->folder->prefs->enable_default_account) 9243 account = account_find_from_id(msginfo->folder->prefs->default_account); 9244 9245 if (!account && msginfo->to && prefs_common.forward_account_autosel) { 9246 gchar *to; 9247 Xstrdup_a(to, msginfo->to, return NULL); 9248 extract_address(to); 9249 account = account_find_from_address(to); 9250 } 9251 9252 if (!account && prefs_common.forward_account_autosel) { 9253 gchar *cc = NULL; 9254 if (!procheader_get_header_from_msginfo 9255 (msginfo, &cc, "Cc:")) { 9256 gchar *buf = cc + strlen("Cc:"); 9257 extract_address(buf); 9258 account = account_find_from_address(buf); 9259 g_free(cc); 9260 } 9261 } 9262 9263 if (!account && prefs_common.forward_account_autosel) { 9264 gchar *deliveredto = NULL; 9265 if (!procheader_get_header_from_msginfo 9266 (msginfo, &deliveredto, "Delivered-To:")) { 9267 gchar *buf = deliveredto + strlen("Delivered-To:"); 9268 extract_address(buf); 9269 account = account_find_from_address(buf); 9270 g_free(deliveredto); 9271 } 9272 } 9273 9274 if (!account) 9275 account = msginfo->folder->folder->account; 9276 9277 return account; 9278 } 9279 9280 gboolean compose_close(Compose *compose) 9281 { 9282 gint x, y; 9283 9284 cm_return_val_if_fail(compose, FALSE); 9285 9286 if (!g_mutex_trylock(&compose->mutex)) { 9287 /* we have to wait for the (possibly deferred by auto-save) 9288 * drafting to be done, before destroying the compose under 9289 * it. */ 9290 debug_print("waiting for drafting to finish...\n"); 9291 compose_allow_user_actions(compose, FALSE); 9292 if (compose->close_timeout_tag == 0) { 9293 compose->close_timeout_tag = 9294 g_timeout_add (500, (GSourceFunc) compose_close, 9295 compose); 9296 } 9297 return TRUE; 9298 } 9299 9300 if (compose->draft_timeout_tag >= 0) { 9301 g_source_remove(compose->draft_timeout_tag); 9302 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; 9303 } 9304 9305 gtk_window_get_position(GTK_WINDOW(compose->window), &x, &y); 9306 if (!compose->batch) { 9307 prefs_common.compose_x = x; 9308 prefs_common.compose_y = y; 9309 } 9310 g_mutex_unlock(&compose->mutex); 9311 compose_destroy(compose); 9312 return FALSE; 9313 } 9314 9315 /** 9316 * Add entry field for each address in list. 9317 * \param compose E-Mail composition object. 9318 * \param listAddress List of (formatted) E-Mail addresses. 9319 */ 9320 static void compose_add_field_list( Compose *compose, GList *listAddress ) { 9321 GList *node; 9322 gchar *addr; 9323 node = listAddress; 9324 while( node ) { 9325 addr = ( gchar * ) node->data; 9326 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE ); 9327 node = g_list_next( node ); 9328 } 9329 } 9330 9331 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list, 9332 guint action, gboolean opening_multiple) 9333 { 9334 gchar *body = NULL; 9335 GSList *new_msglist = NULL; 9336 MsgInfo *tmp_msginfo = NULL; 9337 Compose *compose = NULL; 9338 gchar *s_system = NULL; 9339 9340 cm_return_if_fail(msginfo_list != NULL); 9341 9342 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) { 9343 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview); 9344 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data; 9345 9346 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE && 9347 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) { 9348 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo( 9349 orig_msginfo, mimeinfo); 9350 if (tmp_msginfo != NULL) { 9351 new_msglist = g_slist_append(NULL, tmp_msginfo); 9352 9353 tmp_msginfo->folder = orig_msginfo->folder; 9354 tmp_msginfo->msgnum = orig_msginfo->msgnum; 9355 if (orig_msginfo->tags) { 9356 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags); 9357 tmp_msginfo->folder->tags_dirty = TRUE; 9358 } 9359 } 9360 } 9361 } 9362 9363 if (!opening_multiple && msgview != NULL) 9364 body = messageview_get_selection(msgview); 9365 9366 if (new_msglist) { 9367 compose = compose_reply_mode((ComposeMode)action, new_msglist, body); 9368 procmsg_msginfo_free(&tmp_msginfo); 9369 g_slist_free(new_msglist); 9370 } else 9371 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body); 9372 9373 g_free(s_system); 9374 g_free(body); 9375 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose); 9376 } 9377 9378 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list, 9379 guint action) 9380 { 9381 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD) 9382 && msginfo_list != NULL 9383 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) { 9384 GSList *cur = msginfo_list; 9385 gchar *msg = g_strdup_printf(_("You are about to reply to %d " 9386 "messages. Opening the windows " 9387 "could take some time. Do you " 9388 "want to continue?"), 9389 g_slist_length(msginfo_list)); 9390 if (g_slist_length(msginfo_list) > 9 9391 && alertpanel(_("Warning"), msg, NULL, _("_Cancel"), NULL, _("_Yes"), 9392 NULL, NULL, ALERTFOCUS_SECOND) != G_ALERTALTERNATE) { 9393 g_free(msg); 9394 return; 9395 } 9396 g_free(msg); 9397 /* We'll open multiple compose windows */ 9398 /* let the WM place the next windows */ 9399 compose_force_window_origin = FALSE; 9400 for (; cur; cur = cur->next) { 9401 GSList tmplist; 9402 tmplist.data = cur->data; 9403 tmplist.next = NULL; 9404 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE); 9405 } 9406 compose_force_window_origin = TRUE; 9407 } else { 9408 /* forwarding multiple mails as attachments is done via a 9409 * single compose window */ 9410 if (msginfo_list != NULL) { 9411 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE); 9412 } else if (msgview != NULL) { 9413 GSList tmplist; 9414 tmplist.data = msgview->msginfo; 9415 tmplist.next = NULL; 9416 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE); 9417 } else { 9418 debug_print("Nothing to reply to\n"); 9419 } 9420 } 9421 } 9422 9423 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo, 9424 const gchar *address) 9425 { 9426 GSList *msginfo_list = NULL; 9427 gchar *body = messageview_get_selection(msgview); 9428 Compose *compose; 9429 9430 msginfo_list = g_slist_prepend(msginfo_list, msginfo); 9431 9432 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body); 9433 compose_set_folder_prefs(compose, msginfo->folder, FALSE); 9434 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE); 9435 compose_reply_set_subject(compose, msginfo); 9436 9437 g_free(body); 9438 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose); 9439 } 9440 9441 void compose_set_position(Compose *compose, gint pos) 9442 { 9443 GtkTextView *text = GTK_TEXT_VIEW(compose->text); 9444 9445 gtkut_text_view_set_position(text, pos); 9446 } 9447 9448 gboolean compose_search_string(Compose *compose, 9449 const gchar *str, gboolean case_sens) 9450 { 9451 GtkTextView *text = GTK_TEXT_VIEW(compose->text); 9452 9453 return gtkut_text_view_search_string(text, str, case_sens); 9454 } 9455 9456 gboolean compose_search_string_backward(Compose *compose, 9457 const gchar *str, gboolean case_sens) 9458 { 9459 GtkTextView *text = GTK_TEXT_VIEW(compose->text); 9460 9461 return gtkut_text_view_search_string_backward(text, str, case_sens); 9462 } 9463 9464 /* allocate a msginfo structure and populate its data from a compose data structure */ 9465 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose) 9466 { 9467 MsgInfo *newmsginfo; 9468 GSList *list; 9469 gchar date[RFC822_DATE_BUFFSIZE]; 9470 9471 cm_return_val_if_fail( compose != NULL, NULL ); 9472 9473 newmsginfo = procmsg_msginfo_new(); 9474 9475 /* date is now */ 9476 get_rfc822_date(date, sizeof(date)); 9477 newmsginfo->date = g_strdup(date); 9478 9479 /* from */ 9480 if (compose->from_name) { 9481 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1); 9482 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from); 9483 } 9484 9485 /* subject */ 9486 if (compose->subject_entry) 9487 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1); 9488 9489 /* to, cc, reply-to */ 9490 for (list = compose->header_list; list; list = list->next) { 9491 gchar *header = gtk_editable_get_chars( 9492 GTK_EDITABLE( 9493 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1); 9494 gchar *entry = gtk_editable_get_chars( 9495 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1); 9496 9497 if ( strcasecmp(header, "To:") == 0 ) { 9498 if ( newmsginfo->to == NULL ) { 9499 newmsginfo->to = g_strdup(entry); 9500 } else if (entry && *entry) { 9501 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL); 9502 g_free(newmsginfo->to); 9503 newmsginfo->to = tmp; 9504 } 9505 } else 9506 if ( strcasecmp(header, "Cc:") == 0 ) { 9507 if ( newmsginfo->cc == NULL ) { 9508 newmsginfo->cc = g_strdup(entry); 9509 } else if (entry && *entry) { 9510 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL); 9511 g_free(newmsginfo->cc); 9512 newmsginfo->cc = tmp; 9513 } 9514 } 9515 9516 g_free(header); 9517 g_free(entry); 9518 } 9519 9520 /* other data is unset */ 9521 9522 return newmsginfo; 9523 } 9524 9525 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data) 9526 { 9527 Compose *compose = (Compose *)data; 9528 9529 cm_return_if_fail(compose != NULL); 9530 9531 gtk_widget_grab_focus(compose->text); 9532 } 9533 9534 static void from_name_activate_cb(GtkWidget *widget, gpointer data) 9535 { 9536 gtk_combo_box_popup(GTK_COMBO_BOX(data)); 9537 }