1 /*
2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2017 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 2 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, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
28 #endif
29
30 #include <glib.h>
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtkmain.h>
34 #include <gtk/gtkmenu.h>
35 #include <gtk/gtkmenuitem.h>
36 #include <gtk/gtkitemfactory.h>
37 #include <gtk/gtkcheckmenuitem.h>
38 #include <gtk/gtkoptionmenu.h>
39 #include <gtk/gtkwidget.h>
40 #include <gtk/gtkliststore.h>
41 #include <gtk/gtktreeview.h>
42 #include <gtk/gtktreeselection.h>
43 #include <gtk/gtkcellrenderertext.h>
44 #include <gtk/gtkvpaned.h>
45 #include <gtk/gtkentry.h>
46 #include <gtk/gtkeditable.h>
47 #include <gtk/gtktextview.h>
48 #include <gtk/gtkwindow.h>
49 #include <gtk/gtksignal.h>
50 #include <gtk/gtkvbox.h>
51 #include <gtk/gtkcontainer.h>
52 #include <gtk/gtktoolbar.h>
53 #include <gtk/gtktoolitem.h>
54 #include <gtk/gtktoolbutton.h>
55 #include <gtk/gtkseparatortoolitem.h>
56 #include <gtk/gtktable.h>
57 #include <gtk/gtkhbox.h>
58 #include <gtk/gtklabel.h>
59 #include <gtk/gtkcheckbutton.h>
60 #include <gtk/gtkscrolledwindow.h>
61 #include <gtk/gtktreeview.h>
62 #include <gtk/gtkdnd.h>
63 #include <gtk/gtkclipboard.h>
64 #include <gtk/gtkstock.h>
65 #include <gtk/gtkdialog.h>
66 #include <gtk/gtkimage.h>
67 #include <gtk/gtktooltips.h>
68 #include <pango/pango-break.h>
69
70 #if USE_GTKSPELL
71 # include <gtk/gtkradiomenuitem.h>
72 # include <gtkspell/gtkspell.h>
73 #if USE_ENCHANT
74 # include <enchant/enchant.h>
75 #else
76 # include <aspell.h>
77 #endif
78 #endif
79
80 #include <stdio.h>
81 #include <stdlib.h>
82 #include <string.h>
83 #include <ctype.h>
84 #include <sys/types.h>
85 #include <sys/stat.h>
86 #include <unistd.h>
87 #include <time.h>
88 #include <stdlib.h>
89 #if HAVE_SYS_WAIT_H
90 # include <sys/wait.h>
91 #endif
92 #include <signal.h>
93 #include <errno.h>
94 #ifdef G_OS_WIN32
95 # include <windows.h>
96 #endif
97
98 #include "main.h"
99 #include "mainwindow.h"
100 #include "compose.h"
101 #include "addressbook.h"
102 #include "folderview.h"
103 #include "procmsg.h"
104 #include "menu.h"
105 #include "stock_pixmap.h"
106 #include "send_message.h"
107 #include "imap.h"
108 #include "news.h"
109 #include "customheader.h"
110 #include "prefs_common.h"
111 #include "prefs_common_dialog.h"
112 #include "prefs_account.h"
113 #include "prefs_toolbar.h"
114 #include "action.h"
115 #include "account.h"
116 #include "account_dialog.h"
117 #include "filesel.h"
118 #include "procheader.h"
119 #include "procmime.h"
120 #include "statusbar.h"
121 #include "about.h"
122 #include "base64.h"
123 #include "quoted-printable.h"
124 #include "codeconv.h"
125 #include "utils.h"
126 #include "gtkutils.h"
127 #include "socket.h"
128 #include "alertpanel.h"
129 #include "manage_window.h"
130 #include "gtkshruler.h"
131 #include "folder.h"
132 #include "filter.h"
133 #include "addr_compl.h"
134 #include "quote_fmt.h"
135 #include "template.h"
136 #include "undo.h"
137 #include "plugin.h"
138 #include "md5.h"
139 #include "inc.h"
140
141 #if USE_GPGME
142 # include "rfc2015.h"
143 #endif
144
145 enum
146 {
147 COL_MIMETYPE,
148 COL_SIZE,
149 COL_NAME,
150 COL_ATTACH_INFO,
151 N_ATTACH_COLS
152 };
153
154 typedef enum
155 {
156 COMPOSE_ACTION_MOVE_BEGINNING_OF_LINE,
157 COMPOSE_ACTION_MOVE_FORWARD_CHARACTER,
158 COMPOSE_ACTION_MOVE_BACKWARD_CHARACTER,
159 COMPOSE_ACTION_MOVE_FORWARD_WORD,
160 COMPOSE_ACTION_MOVE_BACKWARD_WORD,
161 COMPOSE_ACTION_MOVE_END_OF_LINE,
162 COMPOSE_ACTION_MOVE_NEXT_LINE,
163 COMPOSE_ACTION_MOVE_PREVIOUS_LINE,
164 COMPOSE_ACTION_DELETE_FORWARD_CHARACTER,
165 COMPOSE_ACTION_DELETE_BACKWARD_CHARACTER,
166 COMPOSE_ACTION_DELETE_FORWARD_WORD,
167 COMPOSE_ACTION_DELETE_BACKWARD_WORD,
168 COMPOSE_ACTION_DELETE_LINE,
169 COMPOSE_ACTION_DELETE_LINE_N,
170 COMPOSE_ACTION_DELETE_TO_LINE_END
171 } ComposeAction;
172
173 #define B64_LINE_SIZE 57
174 #define B64_BUFFSIZE 77
175
176 #define MAX_REFERENCES_LEN 999
177
178 #define TEXTVIEW_MARGIN 6
179
180 static GdkColor quote_color = {0, 0, 0, 0xbfff};
181
182 static GList *compose_list = NULL;
183
184 static Compose *compose_create (PrefsAccount *account,
185 ComposeMode mode);
186 static Compose *compose_find_window_by_target (MsgInfo *msginfo);
187 static gboolean compose_window_exist (gint x,
188 gint y);
189 static void compose_connect_changed_callbacks (Compose *compose);
190
191 static GtkWidget *compose_toolbar_create (Compose *compose);
192 static GtkWidget *compose_toolbar_create_from_list
193 (Compose *compose,
194 GList *item_list);
195 static void compose_set_toolbar_button_visibility
196 (Compose *compose);
197
198 static GtkWidget *compose_account_option_menu_create
199 (Compose *compose,
200 GtkWidget *hbox);
201 static GtkWidget *compose_signature_menu_create(Compose *compose,
202 GtkWidget *hbox);
203 static void compose_set_out_encoding (Compose *compose);
204 static void compose_set_template_menu (Compose *compose);
205 static void compose_template_apply (Compose *compose,
206 Template *tmpl,
207 gboolean replace);
208 static void compose_destroy (Compose *compose);
209
210 static void compose_entry_show (Compose *compose,
211 ComposeEntryType type);
212 static GtkEntry *compose_get_entry (Compose *compose,
213 ComposeEntryType type);
214 static void compose_entries_set (Compose *compose,
215 const gchar *mailto);
216 static void compose_entries_set_from_item (Compose *compose,
217 FolderItem *item,
218 ComposeMode mode);
219 static gint compose_parse_header (Compose *compose,
220 MsgInfo *msginfo);
221 static gchar *compose_parse_references (const gchar *ref,
222 const gchar *msgid);
223 static gint compose_parse_source_msg (Compose *compose,
224 MsgInfo *msginfo);
225
226 static gchar *compose_quote_fmt (Compose *compose,
227 MsgInfo *msginfo,
228 const gchar *fmt,
229 const gchar *qmark,
230 const gchar *body);
231
232 static void compose_reply_set_entry (Compose *compose,
233 MsgInfo *msginfo,
234 ComposeMode mode);
235 static void compose_reedit_set_entry (Compose *compose,
236 MsgInfo *msginfo);
237
238 static void compose_insert_sig (Compose *compose,
239 gboolean append,
240 gboolean replace,
241 gboolean scroll);
242 static void compose_enable_sig (Compose *compose);
243 static gchar *compose_get_signature_str (Compose *compose);
244
245 static void compose_insert_file (Compose *compose,
246 const gchar *file,
247 gboolean scroll);
248
249 static void compose_attach_parts (Compose *compose,
250 MsgInfo *msginfo);
251
252 static void compose_wrap_paragraph (Compose *compose,
253 GtkTextIter *par_iter);
254 static void compose_wrap_all (Compose *compose);
255 static void compose_wrap_all_full (Compose *compose,
256 gboolean autowrap);
257
258 static void compose_set_title (Compose *compose);
259 static void compose_select_account (Compose *compose,
260 PrefsAccount *account,
261 gboolean init);
262 static void compose_update_signature_menu (Compose *compose);
263
264 static gboolean compose_check_for_valid_recipient
265 (Compose *compose);
266 static gboolean compose_check_entries (Compose *compose);
267 static gboolean compose_check_attachments (Compose *compose);
268 static gboolean compose_check_recipients (Compose *compose);
269 static gboolean compose_check_activities (Compose *compose);
270
271 static void compose_add_new_recipients_to_addressbook
272 (Compose *compose);
273
274 static gint compose_send_real (Compose *compose);
275 static gint compose_write_to_file (Compose *compose,
276 const gchar *file,
277 gboolean is_draft);
278 static gint compose_write_body_to_file (Compose *compose,
279 const gchar *file);
280 static gint compose_redirect_write_to_file (Compose *compose,
281 const gchar *file);
282 static gint compose_remove_reedit_target (Compose *compose);
283 static gint compose_queue (Compose *compose,
284 const gchar *file);
285 static gint compose_write_attach (Compose *compose,
286 FILE *fp,
287 const gchar *charset);
288 static gint compose_write_headers (Compose *compose,
289 FILE *fp,
290 const gchar *charset,
291 const gchar *body_charset,
292 EncodingType encoding,
293 gboolean is_draft);
294 static gint compose_redirect_write_headers (Compose *compose,
295 FILE *fp);
296
297 static void compose_convert_header (Compose *compose,
298 gchar *dest,
299 gint len,
300 const gchar *src,
301 gint header_len,
302 gboolean addr_field,
303 const gchar *encoding);
304 static gchar *compose_convert_filename (Compose *compose,
305 const gchar *src,
306 const gchar *param_name,
307 const gchar *encoding);
308 static void compose_generate_msgid (Compose *compose,
309 gchar *buf,
310 gint len);
311
312 static void compose_attach_info_free (AttachInfo *ainfo);
313 static void compose_attach_remove_selected (Compose *compose);
314
315 static void compose_attach_property (Compose *compose);
316 static void compose_attach_property_create (gboolean *cancelled);
317 static void attach_property_ok (GtkWidget *widget,
318 gboolean *cancelled);
319 static void attach_property_cancel (GtkWidget *widget,
320 gboolean *cancelled);
321 static gint attach_property_delete_event (GtkWidget *widget,
322 GdkEventAny *event,
323 gboolean *cancelled);
324 static gboolean attach_property_key_pressed (GtkWidget *widget,
325 GdkEventKey *event,
326 gboolean *cancelled);
327
328 static void compose_attach_open (Compose *compose);
329
330 static void compose_exec_ext_editor (Compose *compose);
331 static gboolean compose_ext_editor_kill (Compose *compose);
332 static void compose_ext_editor_child_exit (GPid pid,
333 gint status,
334 gpointer data);
335 static void compose_set_ext_editor_sensitive (Compose *compose,
336 gboolean sensitive);
337
338 static void compose_undo_state_changed (UndoMain *undostruct,
339 gint undo_state,
340 gint redo_state,
341 gpointer data);
342
343 static gint calc_cursor_xpos (GtkTextView *text,
344 gint extra,
345 gint char_width);
346
347 /* callback functions */
348
349 static gboolean compose_edit_size_alloc (GtkEditable *widget,
350 GtkAllocation *allocation,
351 GtkSHRuler *shruler);
352
353 static void toolbar_send_cb (GtkWidget *widget,
354 gpointer data);
355 static void toolbar_send_later_cb (GtkWidget *widget,
356 gpointer data);
357 static void toolbar_draft_cb (GtkWidget *widget,
358 gpointer data);
359 static void toolbar_insert_cb (GtkWidget *widget,
360 gpointer data);
361 static void toolbar_attach_cb (GtkWidget *widget,
362 gpointer data);
363 static void toolbar_sig_cb (GtkWidget *widget,
364 gpointer data);
365 static void toolbar_ext_editor_cb (GtkWidget *widget,
366 gpointer data);
367 static void toolbar_linewrap_cb (GtkWidget *widget,
368 gpointer data);
369 static void toolbar_address_cb (GtkWidget *widget,
370 gpointer data);
371 static void toolbar_prefs_common_cb (GtkWidget *widget,
372 gpointer data);
373 static void toolbar_prefs_account_cb (GtkWidget *widget,
374 gpointer data);
375
376 static gboolean toolbar_button_pressed (GtkWidget *widget,
377 GdkEventButton *event,
378 gpointer data);
379
380 static void account_activated (GtkMenuItem *menuitem,
381 gpointer data);
382 static void sig_combo_changed (GtkComboBox *combo,
383 gpointer data);
384
385 static void attach_selection_changed (GtkTreeSelection *selection,
386 gpointer data);
387
388 static gboolean attach_button_pressed (GtkWidget *widget,
389 GdkEventButton *event,
390 gpointer data);
391 static gboolean attach_key_pressed (GtkWidget *widget,
392 GdkEventKey *event,
393 gpointer data);
394
395 static void compose_send_cb (gpointer data,
396 guint action,
397 GtkWidget *widget);
398 static void compose_send_later_cb (gpointer data,
399 guint action,
400 GtkWidget *widget);
401
402 static void compose_draft_cb (gpointer data,
403 guint action,
404 GtkWidget *widget);
405
406 static void compose_attach_open_cb (gpointer data,
407 guint action,
408 GtkWidget *widget);
409 static void compose_attach_cb (gpointer data,
410 guint action,
411 GtkWidget *widget);
412 static void compose_insert_file_cb (gpointer data,
413 guint action,
414 GtkWidget *widget);
415 static void compose_insert_sig_cb (gpointer data,
416 guint action,
417 GtkWidget *widget);
418
419 static void compose_close_cb (gpointer data,
420 guint action,
421 GtkWidget *widget);
422
423 static void compose_set_encoding_cb (gpointer data,
424 guint action,
425 GtkWidget *widget);
426
427 static void compose_address_cb (gpointer data,
428 guint action,
429 GtkWidget *widget);
430 static void compose_template_activate_cb(GtkWidget *widget,
431 gpointer data);
432
433 static void compose_ext_editor_cb (gpointer data,
434 guint action,
435 GtkWidget *widget);
436
437 static gint compose_delete_cb (GtkWidget *widget,
438 GdkEventAny *event,
439 gpointer data);
440
441 static gint compose_window_state_cb (GtkWidget *widget,
442 GdkEventWindowState *event,
443 gpointer data);
444
445 static void compose_undo_cb (Compose *compose);
446 static void compose_redo_cb (Compose *compose);
447 static void compose_cut_cb (Compose *compose);
448 static void compose_copy_cb (Compose *compose);
449 static void compose_paste_cb (Compose *compose);
450 static void compose_paste_as_quote_cb (Compose *compose);
451 static void compose_allsel_cb (Compose *compose);
452
453 static void compose_grab_focus_cb (GtkWidget *widget,
454 Compose *compose);
455
456 #if USE_GPGME
457 static void compose_signing_toggled (GtkWidget *widget,
458 Compose *compose);
459 static void compose_encrypt_toggled (GtkWidget *widget,
460 Compose *compose);
461 #endif
462
463 #if 0
464 static void compose_attach_toggled (GtkWidget *widget,
465 Compose *compose);
466 #endif
467
468 static void compose_buffer_changed_cb (GtkTextBuffer *textbuf,
469 Compose *compose);
470 static void compose_changed_cb (GtkEditable *editable,
471 Compose *compose);
472
473 static void compose_wrap_cb (gpointer data,
474 guint action,
475 GtkWidget *widget);
476 static void compose_toggle_autowrap_cb (gpointer data,
477 guint action,
478 GtkWidget *widget);
479
480 static void compose_toggle_to_cb (gpointer data,
481 guint action,
482 GtkWidget *widget);
483 static void compose_toggle_cc_cb (gpointer data,
484 guint action,
485 GtkWidget *widget);
486 static void compose_toggle_bcc_cb (gpointer data,
487 guint action,
488 GtkWidget *widget);
489 static void compose_toggle_replyto_cb (gpointer data,
490 guint action,
491 GtkWidget *widget);
492 static void compose_toggle_followupto_cb(gpointer data,
493 guint action,
494 GtkWidget *widget);
495 static void compose_toggle_ruler_cb (gpointer data,
496 guint action,
497 GtkWidget *widget);
498 static void compose_toggle_attach_cb (gpointer data,
499 guint action,
500 GtkWidget *widget);
501 static void compose_customize_toolbar_cb(gpointer data,
502 guint action,
503 GtkWidget *widget);
504
505 static void compose_toggle_mdn_cb (gpointer data,
506 guint action,
507 GtkWidget *widget);
508
509 #if USE_GPGME
510 static void compose_toggle_sign_cb (gpointer data,
511 guint action,
512 GtkWidget *widget);
513 static void compose_toggle_encrypt_cb (gpointer data,
514 guint action,
515 GtkWidget *widget);
516 #endif
517
518 #if USE_GTKSPELL
519 static void compose_set_spell_lang_menu (Compose *compose);
520 static void compose_change_spell_lang_menu
521 (Compose *compose,
522 const gchar *lang);
523 static void compose_toggle_spell_cb (gpointer data,
524 guint action,
525 GtkWidget *widget);
526 static void compose_set_spell_lang_cb (GtkWidget *widget,
527 gpointer data);
528 #endif
529
530 static void compose_attach_drag_received_cb (GtkWidget *widget,
531 GdkDragContext *drag_context,
532 gint x,
533 gint y,
534 GtkSelectionData *data,
535 guint info,
536 guint time,
537 gpointer user_data);
538 static void compose_insert_drag_received_cb (GtkWidget *widget,
539 GdkDragContext *drag_context,
540 gint x,
541 gint y,
542 GtkSelectionData *data,
543 guint info,
544 guint time,
545 gpointer user_data);
546
547 static void to_activated (GtkWidget *widget,
548 Compose *compose);
549 static void newsgroups_activated (GtkWidget *widget,
550 Compose *compose);
551 static void cc_activated (GtkWidget *widget,
552 Compose *compose);
553 static void bcc_activated (GtkWidget *widget,
554 Compose *compose);
555 static void replyto_activated (GtkWidget *widget,
556 Compose *compose);
557 static void followupto_activated (GtkWidget *widget,
558 Compose *compose);
559 static void subject_activated (GtkWidget *widget,
560 Compose *compose);
561
562 static void text_inserted (GtkTextBuffer *buffer,
563 GtkTextIter *iter,
564 const gchar *text,
565 gint len,
566 Compose *compose);
567
568 static gboolean autosave_timeout (gpointer data);
569
570
571 static GtkItemFactoryEntry compose_popup_entries[] =
572 {
573 {N_("/_Open"), NULL, compose_attach_open_cb, 0, NULL},
574 {N_("/---"), NULL, NULL, 0, "<Separator>"},
575 {N_("/_Add..."), NULL, compose_attach_cb, 0, NULL},
576 {N_("/_Remove"), NULL, compose_attach_remove_selected, 0, NULL},
577 {N_("/---"), NULL, NULL, 0, "<Separator>"},
578 {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
579 };
580
581 static GtkItemFactoryEntry compose_entries[] =
582 {
583 {N_("/_File"), NULL, NULL, 0, "<Branch>"},
584 {N_("/_File/_Send"), "<shift><control>E",
585 compose_send_cb, 0, NULL},
586 {N_("/_File/Send _later"), "<shift><control>S",
587 compose_send_later_cb, 0, NULL},
588 {N_("/_File/---"), NULL, NULL, 0, "<Separator>"},
589 {N_("/_File/Save to _draft folder"),
590 "<shift><control>D", compose_draft_cb, 0, NULL},
591 {N_("/_File/Save and _keep editing"),
592 "<control>S", compose_draft_cb, 1, NULL},
593 {N_("/_File/---"), NULL, NULL, 0, "<Separator>"},
594 {N_("/_File/_Attach file"), "<control>M", compose_attach_cb, 0, NULL},
595 {N_("/_File/_Insert file"), "<control>I", compose_insert_file_cb, 0, NULL},
596 {N_("/_File/---"), NULL, NULL, 0, "<Separator>"},
597 {N_("/_File/Insert si_gnature"), "<control>G", compose_insert_sig_cb, 0, NULL},
598 {N_("/_File/A_ppend signature"), "<shift><control>G", compose_insert_sig_cb, 1, NULL},
599 {N_("/_File/---"), NULL, NULL, 0, "<Separator>"},
600 {N_("/_File/_Close"), "<control>W", compose_close_cb, 0, NULL},
601
602 {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
603 {N_("/_Edit/_Undo"), "<control>Z", compose_undo_cb, 0, NULL},
604 {N_("/_Edit/_Redo"), "<control>Y", compose_redo_cb, 0, NULL},
605 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
606 {N_("/_Edit/Cu_t"), "<control>X", compose_cut_cb, 0, NULL},
607 {N_("/_Edit/_Copy"), "<control>C", compose_copy_cb, 0, NULL},
608 {N_("/_Edit/_Paste"), "<control>V", compose_paste_cb, 0, NULL},
609 {N_("/_Edit/Paste as _quotation"),
610 NULL, compose_paste_as_quote_cb, 0, NULL},
611 {N_("/_Edit/Select _all"), "<control>A", compose_allsel_cb, 0, NULL},
612 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
613 {N_("/_Edit/_Wrap current paragraph"),
614 "<control>L", compose_wrap_cb, 0, NULL},
615 {N_("/_Edit/Wrap all long _lines"),
616 "<control><alt>L", compose_wrap_cb, 1, NULL},
617 {N_("/_Edit/Aut_o wrapping"), "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
618 {N_("/_View"), NULL, NULL, 0, "<Branch>"},
619 {N_("/_View/_To"), NULL, compose_toggle_to_cb , 0, "<ToggleItem>"},
620 {N_("/_View/_Cc"), NULL, compose_toggle_cc_cb , 0, "<ToggleItem>"},
621 {N_("/_View/_Bcc"), NULL, compose_toggle_bcc_cb , 0, "<ToggleItem>"},
622 {N_("/_View/_Reply-To"), NULL, compose_toggle_replyto_cb, 0, "<ToggleItem>"},
623 {N_("/_View/---"), NULL, NULL, 0, "<Separator>"},
624 {N_("/_View/_Followup-To"), NULL, compose_toggle_followupto_cb, 0, "<ToggleItem>"},
625 {N_("/_View/---"), NULL, NULL, 0, "<Separator>"},
626 {N_("/_View/R_uler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
627 {N_("/_View/---"), NULL, NULL, 0, "<Separator>"},
628 {N_("/_View/_Attachment"), NULL, compose_toggle_attach_cb, 0, "<ToggleItem>"},
629 {N_("/_View/---"), NULL, NULL, 0, "<Separator>"},
630 {N_("/_View/Cu_stomize toolbar..."),
631 NULL, compose_customize_toolbar_cb, 0, NULL},
632 {N_("/_View/---"), NULL, NULL, 0, "<Separator>"},
633
634 #define ENC_ACTION(action) \
635 NULL, compose_set_encoding_cb, action, \
636 "/View/Character encoding/Automatic"
637
638 {N_("/_View/Character _encoding"), NULL, NULL, 0, "<Branch>"},
639 {N_("/_View/Character _encoding/_Automatic"),
640 NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
641 {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
642
643 {N_("/_View/Character _encoding/7bit ascii (US-ASC_II)"),
644 ENC_ACTION(C_US_ASCII)},
645 {N_("/_View/Character _encoding/Unicode (_UTF-8)"),
646 ENC_ACTION(C_UTF_8)},
647 {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
648
649 {N_("/_View/Character _encoding/Western European (ISO-8859-_1)"),
650 ENC_ACTION(C_ISO_8859_1)},
651 {N_("/_View/Character _encoding/Western European (ISO-8859-15)"),
652 ENC_ACTION(C_ISO_8859_15)},
653 {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
654
655 {N_("/_View/Character _encoding/Central European (ISO-8859-_2)"),
656 ENC_ACTION(C_ISO_8859_2)},
657 {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
658
659 {N_("/_View/Character _encoding/_Baltic (ISO-8859-13)"),
660 ENC_ACTION(C_ISO_8859_13)},
661 {N_("/_View/Character _encoding/Baltic (ISO-8859-_4)"),
662 ENC_ACTION(C_ISO_8859_4)},
663 {N_("/_View/Character _encoding/Baltic (Windows-1257)"),
664 ENC_ACTION(C_WINDOWS_1257)},
665 {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
666
667 {N_("/_View/Character _encoding/Greek (ISO-8859-_7)"),
668 ENC_ACTION(C_ISO_8859_7)},
669 {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
670
671 {N_("/_View/Character _encoding/Arabic (ISO-8859-_6)"),
672 ENC_ACTION(C_ISO_8859_6)},
673 {N_("/_View/Character _encoding/Arabic (Windows-1256)"),
674 ENC_ACTION(C_WINDOWS_1256)},
675 {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
676
677 {N_("/_View/Character _encoding/Hebrew (ISO-8859-_8)"),
678 ENC_ACTION(C_ISO_8859_8)},
679 {N_("/_View/Character _encoding/Hebrew (Windows-1255)"),
680 ENC_ACTION(C_WINDOWS_1255)},
681 {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
682
683 {N_("/_View/Character _encoding/Turkish (ISO-8859-_9)"),
684 ENC_ACTION(C_ISO_8859_9)},
685 {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
686
687 {N_("/_View/Character _encoding/Cyrillic (ISO-8859-_5)"),
688 ENC_ACTION(C_ISO_8859_5)},
689 {N_("/_View/Character _encoding/Cyrillic (KOI8-_R)"),
690 ENC_ACTION(C_KOI8_R)},
691 {N_("/_View/Character _encoding/Cyrillic (KOI8-U)"),
692 ENC_ACTION(C_KOI8_U)},
693 {N_("/_View/Character _encoding/Cyrillic (Windows-1251)"),
694 ENC_ACTION(C_WINDOWS_1251)},
695 {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
696
697 {N_("/_View/Character _encoding/Japanese (ISO-2022-_JP)"),
698 ENC_ACTION(C_ISO_2022_JP)},
699 {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
700
701 {N_("/_View/Character _encoding/Simplified Chinese (_GB2312)"),
702 ENC_ACTION(C_GB2312)},
703 {N_("/_View/Character _encoding/Simplified Chinese (GBK)"),
704 ENC_ACTION(C_GBK)},
705 {N_("/_View/Character _encoding/Traditional Chinese (_Big5)"),
706 ENC_ACTION(C_BIG5)},
707 {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
708
709 {N_("/_View/Character _encoding/Korean (EUC-_KR)"),
710 ENC_ACTION(C_EUC_KR)},
711 {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
712
713 {N_("/_View/Character _encoding/Thai (TIS-620)"),
714 ENC_ACTION(C_TIS_620)},
715 {N_("/_View/Character _encoding/Thai (Windows-874)"),
716 ENC_ACTION(C_WINDOWS_874)},
717
718 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
719 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
720 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
721 #ifndef G_OS_WIN32
722 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
723 #endif
724 {N_("/_Tools/---"), NULL, NULL, 0, "<Separator>"},
725 {N_("/_Tools/Edit with e_xternal editor"),
726 "<shift><control>X", compose_ext_editor_cb, 0, NULL},
727 {N_("/_Tools/---"), NULL, NULL, 0, "<Separator>"},
728 {N_("/_Tools/Request _disposition notification"), NULL, compose_toggle_mdn_cb , 0, "<ToggleItem>"},
729
730 #if USE_GPGME
731 {N_("/_Tools/---"), NULL, NULL, 0, "<Separator>"},
732 {N_("/_Tools/PGP Si_gn"), NULL, compose_toggle_sign_cb , 0, "<ToggleItem>"},
733 {N_("/_Tools/PGP _Encrypt"), NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
734 #endif /* USE_GPGME */
735
736 #if USE_GTKSPELL
737 {N_("/_Tools/---"), NULL, NULL, 0, "<Separator>"},
738 {N_("/_Tools/_Check spell"), NULL, compose_toggle_spell_cb, 0, "<ToggleItem>"},
739 {N_("/_Tools/_Set spell language"), NULL, NULL, 0, "<Branch>"},
740 #endif /* USE_GTKSPELL */
741
742 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
743 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
744 };
745
746 enum
747 {
748 DRAG_TYPE_RFC822,
749 DRAG_TYPE_URI_LIST,
750
751 N_DRAG_TYPES
752 };
753
754 static GtkTargetEntry compose_drag_types[] =
755 {
756 {"message/rfc822", GTK_TARGET_SAME_APP, DRAG_TYPE_RFC822},
757 {"text/uri-list", 0, DRAG_TYPE_URI_LIST}
758 };
759
760
compose_new(PrefsAccount * account,FolderItem * item,const gchar * mailto,GPtrArray * attach_files)761 Compose *compose_new(PrefsAccount *account, FolderItem *item,
762 const gchar *mailto, GPtrArray *attach_files)
763 {
764 Compose *compose;
765 GtkTextView *text;
766 GtkTextBuffer *buffer;
767 GtkTextIter iter;
768
769 if (!account) account = cur_account;
770 g_return_val_if_fail(account != NULL, NULL);
771
772 compose = compose_create(account, COMPOSE_NEW);
773
774 undo_block(compose->undostruct);
775
776 if (prefs_common.auto_sig)
777 compose_insert_sig(compose, TRUE, FALSE, FALSE);
778
779 text = GTK_TEXT_VIEW(compose->text);
780 buffer = gtk_text_view_get_buffer(text);
781 gtk_text_buffer_get_start_iter(buffer, &iter);
782 gtk_text_buffer_place_cursor(buffer, &iter);
783
784 if (account->protocol != A_NNTP) {
785 if (mailto && *mailto != '\0') {
786 compose_entries_set(compose, mailto);
787 gtk_widget_grab_focus(compose->subject_entry);
788 } else if (item) {
789 compose_entries_set_from_item
790 (compose, item, COMPOSE_NEW);
791 if (item->auto_to)
792 gtk_widget_grab_focus(compose->subject_entry);
793 else
794 gtk_widget_grab_focus(compose->to_entry);
795 } else
796 gtk_widget_grab_focus(compose->to_entry);
797 } else {
798 if (mailto && *mailto != '\0') {
799 compose_entry_append(compose, mailto,
800 COMPOSE_ENTRY_NEWSGROUPS);
801 gtk_widget_grab_focus(compose->subject_entry);
802 } else
803 gtk_widget_grab_focus(compose->newsgroups_entry);
804 }
805
806 if (attach_files) {
807 gint i;
808 gchar *file;
809
810 for (i = 0; i < attach_files->len; i++) {
811 gchar *utf8file;
812
813 file = g_ptr_array_index(attach_files, i);
814 utf8file = conv_filename_to_utf8(file);
815 compose_attach_append(compose, file, utf8file, NULL);
816 g_free(utf8file);
817 }
818 }
819
820 undo_unblock(compose->undostruct);
821
822 compose_connect_changed_callbacks(compose);
823 compose_set_title(compose);
824
825 syl_plugin_signal_emit("compose-created", compose);
826
827 if (prefs_common.enable_autosave && prefs_common.autosave_itv > 0)
828 compose->autosave_tag =
829 g_timeout_add_full(G_PRIORITY_LOW,
830 prefs_common.autosave_itv * 60 * 1000,
831 autosave_timeout, compose, NULL);
832 if (prefs_common.auto_exteditor)
833 compose_exec_ext_editor(compose);
834
835 return compose;
836 }
837
compose_reply(MsgInfo * msginfo,FolderItem * item,ComposeMode mode,const gchar * body)838 Compose *compose_reply(MsgInfo *msginfo, FolderItem *item, ComposeMode mode,
839 const gchar *body)
840 {
841 Compose *compose;
842 PrefsAccount *account = NULL;
843 MsgInfo *replyinfo;
844 GtkTextBuffer *buffer;
845 GtkTextIter iter;
846 gboolean quote = FALSE;
847
848 g_return_val_if_fail(msginfo != NULL, NULL);
849
850 if (COMPOSE_QUOTE_MODE(mode) == COMPOSE_WITH_QUOTE)
851 quote = TRUE;
852
853 if (msginfo->folder)
854 account = account_find_from_item_property(msginfo->folder);
855 if (!account && msginfo->to && prefs_common.reply_account_autosel) {
856 gchar *to;
857 to = g_strdup(msginfo->to);
858 extract_address(to);
859 account = account_find_from_address(to);
860 g_free(to);
861 }
862 if (!account && msginfo->folder && msginfo->folder->folder)
863 account = msginfo->folder->folder->account;
864 if (!account)
865 account = cur_account;
866 g_return_val_if_fail(account != NULL, NULL);
867
868 compose = compose_create(account, COMPOSE_REPLY);
869
870 replyinfo = procmsg_msginfo_get_full_info(msginfo);
871 if (!replyinfo)
872 replyinfo = procmsg_msginfo_copy(msginfo);
873 if (msginfo->folder) {
874 gchar *id;
875 id = folder_item_get_identifier(msginfo->folder);
876 if (id) {
877 compose->reply_target = g_strdup_printf
878 ("%s/%u", id, msginfo->msgnum);
879 g_free(id);
880 }
881 }
882
883 if (compose_parse_header(compose, msginfo) < 0)
884 return compose;
885
886 undo_block(compose->undostruct);
887
888 compose_reply_set_entry(compose, msginfo, mode);
889 if (item)
890 compose_entries_set_from_item(compose, item, COMPOSE_REPLY);
891
892 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
893
894 if (account->sig_before_quote && prefs_common.auto_sig) {
895 GtkTextMark *mark;
896 compose_insert_sig(compose, TRUE, FALSE, FALSE);
897 mark = gtk_text_buffer_get_insert(buffer);
898 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
899 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
900 }
901
902 if (quote) {
903 compose_quote_fmt(compose, replyinfo,
904 prefs_common.quotefmt,
905 prefs_common.quotemark, body);
906 }
907
908 if (!account->sig_before_quote && prefs_common.auto_sig)
909 compose_insert_sig(compose, TRUE, FALSE, FALSE);
910
911 if (quote && prefs_common.linewrap_quote)
912 compose_wrap_all(compose);
913
914 gtk_text_buffer_get_start_iter(buffer, &iter);
915 gtk_text_buffer_place_cursor(buffer, &iter);
916
917 gtk_widget_grab_focus(compose->text);
918
919 undo_unblock(compose->undostruct);
920
921 compose_connect_changed_callbacks(compose);
922 compose_set_title(compose);
923
924 #if USE_GPGME
925 if (rfc2015_is_available() && account->encrypt_reply &&
926 MSG_IS_ENCRYPTED(replyinfo->flags)) {
927 GtkItemFactory *ifactory;
928
929 ifactory = gtk_item_factory_from_widget(compose->menubar);
930 menu_set_active(ifactory, "/Tools/PGP Encrypt", TRUE);
931 }
932 #endif
933
934 procmsg_msginfo_free(replyinfo);
935
936 syl_plugin_signal_emit("compose-created", compose);
937
938 if (prefs_common.enable_autosave && prefs_common.autosave_itv > 0)
939 compose->autosave_tag =
940 g_timeout_add_full(G_PRIORITY_LOW,
941 prefs_common.autosave_itv * 60 * 1000,
942 autosave_timeout, compose, NULL);
943 if (prefs_common.auto_exteditor)
944 compose_exec_ext_editor(compose);
945
946 return compose;
947 }
948
compose_forward(GSList * mlist,FolderItem * item,gboolean as_attach,const gchar * body)949 Compose *compose_forward(GSList *mlist, FolderItem *item, gboolean as_attach,
950 const gchar *body)
951 {
952 Compose *compose;
953 PrefsAccount *account = NULL;
954 GtkTextView *text;
955 GtkTextBuffer *buffer;
956 GtkTextIter iter;
957 GSList *cur;
958 MsgInfo *msginfo;
959 GString *forward_targets;
960
961 g_return_val_if_fail(mlist != NULL, NULL);
962
963 msginfo = (MsgInfo *)mlist->data;
964
965 if (msginfo->folder)
966 account = account_find_from_item(msginfo->folder);
967 if (!account) account = cur_account;
968 g_return_val_if_fail(account != NULL, NULL);
969
970 compose = compose_create(account, COMPOSE_FORWARD);
971
972 forward_targets = g_string_new("");
973 for (cur = mlist; cur != NULL; cur = cur->next) {
974 msginfo = (MsgInfo *)cur->data;
975 if (msginfo->folder) {
976 gchar *id;
977 id = folder_item_get_identifier(msginfo->folder);
978 if (id) {
979 if (forward_targets->len > 0)
980 g_string_append(forward_targets,
981 "\n ");
982 g_string_append_printf(forward_targets, "%s/%u",
983 id, msginfo->msgnum);
984 g_free(id);
985 }
986 }
987 }
988 if (forward_targets->len > 0)
989 compose->forward_targets = g_string_free(forward_targets,
990 FALSE);
991 else
992 g_string_free(forward_targets, TRUE);
993
994 undo_block(compose->undostruct);
995
996 compose_entry_set(compose, "Fw: ", COMPOSE_ENTRY_SUBJECT);
997 if (mlist->next == NULL && msginfo->subject && *msginfo->subject)
998 compose_entry_append(compose, msginfo->subject,
999 COMPOSE_ENTRY_SUBJECT);
1000 if (item)
1001 compose_entries_set_from_item(compose, item, COMPOSE_FORWARD);
1002
1003 text = GTK_TEXT_VIEW(compose->text);
1004 buffer = gtk_text_view_get_buffer(text);
1005
1006 if (account->sig_before_quote && prefs_common.auto_sig) {
1007 GtkTextMark *mark;
1008 compose_insert_sig(compose, TRUE, FALSE, FALSE);
1009 mark = gtk_text_buffer_get_insert(buffer);
1010 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1011 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
1012 }
1013
1014 for (cur = mlist; cur != NULL; cur = cur->next) {
1015 msginfo = (MsgInfo *)cur->data;
1016
1017 if (as_attach) {
1018 gchar *msgfile;
1019
1020 msgfile = procmsg_get_message_file_path(msginfo);
1021 if (!is_file_exist(msgfile))
1022 g_warning(_("%s: file not exist\n"), msgfile);
1023 else
1024 compose_attach_append(compose, msgfile, msgfile,
1025 "message/rfc822");
1026
1027 g_free(msgfile);
1028 } else {
1029 MsgInfo *full_msginfo;
1030
1031 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1032 if (!full_msginfo)
1033 full_msginfo = procmsg_msginfo_copy(msginfo);
1034
1035 if (cur != mlist) {
1036 GtkTextMark *mark;
1037 mark = gtk_text_buffer_get_insert(buffer);
1038 gtk_text_buffer_get_iter_at_mark
1039 (buffer, &iter, mark);
1040 gtk_text_buffer_insert
1041 (buffer, &iter, "\n\n", 2);
1042 }
1043
1044 compose_quote_fmt(compose, full_msginfo,
1045 prefs_common.fw_quotefmt,
1046 prefs_common.fw_quotemark, body);
1047 compose_attach_parts(compose, msginfo);
1048
1049 procmsg_msginfo_free(full_msginfo);
1050
1051 if (body) break;
1052 }
1053 }
1054
1055 if (!account->sig_before_quote && prefs_common.auto_sig)
1056 compose_insert_sig(compose, TRUE, FALSE, FALSE);
1057
1058 if (prefs_common.linewrap_quote)
1059 compose_wrap_all(compose);
1060
1061 gtk_text_buffer_get_start_iter(buffer, &iter);
1062 gtk_text_buffer_place_cursor(buffer, &iter);
1063
1064 undo_unblock(compose->undostruct);
1065
1066 compose_connect_changed_callbacks(compose);
1067 compose_set_title(compose);
1068
1069 if (account->protocol != A_NNTP)
1070 gtk_widget_grab_focus(compose->to_entry);
1071 else
1072 gtk_widget_grab_focus(compose->newsgroups_entry);
1073
1074 syl_plugin_signal_emit("compose-created", compose);
1075
1076 if (prefs_common.enable_autosave && prefs_common.autosave_itv > 0)
1077 compose->autosave_tag =
1078 g_timeout_add_full(G_PRIORITY_LOW,
1079 prefs_common.autosave_itv * 60 * 1000,
1080 autosave_timeout, compose, NULL);
1081 if (prefs_common.auto_exteditor)
1082 compose_exec_ext_editor(compose);
1083
1084 return compose;
1085 }
1086
compose_redirect(MsgInfo * msginfo,FolderItem * item)1087 Compose *compose_redirect(MsgInfo *msginfo, FolderItem *item)
1088 {
1089 Compose *compose;
1090 PrefsAccount *account;
1091 GtkTextView *text;
1092 GtkTextBuffer *buffer;
1093 GtkTextMark *mark;
1094 GtkTextIter iter;
1095 FILE *fp;
1096 gchar buf[BUFFSIZE];
1097
1098 g_return_val_if_fail(msginfo != NULL, NULL);
1099 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1100
1101 account = account_find_from_item(msginfo->folder);
1102 if (!account) account = cur_account;
1103 g_return_val_if_fail(account != NULL, NULL);
1104
1105 compose = compose_create(account, COMPOSE_REDIRECT);
1106 compose->targetinfo = procmsg_msginfo_copy(msginfo);
1107
1108 if (compose_parse_header(compose, msginfo) < 0)
1109 return compose;
1110
1111 undo_block(compose->undostruct);
1112
1113 if (msginfo->subject)
1114 compose_entry_set(compose, msginfo->subject,
1115 COMPOSE_ENTRY_SUBJECT);
1116 compose_entries_set_from_item(compose, item, COMPOSE_REDIRECT);
1117
1118 text = GTK_TEXT_VIEW(compose->text);
1119 buffer = gtk_text_view_get_buffer(text);
1120 mark = gtk_text_buffer_get_insert(buffer);
1121 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1122
1123 if ((fp = procmime_get_first_text_content(msginfo, NULL)) == NULL)
1124 g_warning(_("Can't get text part\n"));
1125 else {
1126 gboolean prev_autowrap = compose->autowrap;
1127
1128 compose->autowrap = FALSE;
1129 while (fgets(buf, sizeof(buf), fp) != NULL) {
1130 strcrchomp(buf);
1131 gtk_text_buffer_insert(buffer, &iter, buf, -1);
1132 }
1133 compose->autowrap = prev_autowrap;
1134 fclose(fp);
1135 }
1136 compose_attach_parts(compose, msginfo);
1137
1138 if (account->protocol != A_NNTP)
1139 gtk_widget_grab_focus(compose->to_entry);
1140 else
1141 gtk_widget_grab_focus(compose->newsgroups_entry);
1142
1143 gtk_text_view_set_editable(text, FALSE);
1144
1145 undo_unblock(compose->undostruct);
1146
1147 compose_connect_changed_callbacks(compose);
1148 compose_set_title(compose);
1149
1150 syl_plugin_signal_emit("compose-created", compose);
1151
1152 return compose;
1153 }
1154
compose_reedit(MsgInfo * msginfo)1155 Compose *compose_reedit(MsgInfo *msginfo)
1156 {
1157 Compose *compose;
1158 PrefsAccount *account;
1159 GtkTextView *text;
1160 GtkTextBuffer *buffer;
1161 GtkTextMark *mark;
1162 GtkTextIter iter;
1163 FILE *fp;
1164 gchar buf[BUFFSIZE];
1165 const gchar *str;
1166 GtkWidget *focus_widget = NULL;
1167 GtkItemFactory *ifactory;
1168
1169 g_return_val_if_fail(msginfo != NULL, NULL);
1170 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1171
1172 account = account_find_from_msginfo(msginfo);
1173 if (!account) account = cur_account;
1174 g_return_val_if_fail(account != NULL, NULL);
1175
1176 if (msginfo->folder->stype == F_DRAFT ||
1177 msginfo->folder->stype == F_QUEUE) {
1178 compose = compose_find_window_by_target(msginfo);
1179 if (compose) {
1180 debug_print
1181 ("compose_reedit(): existing window found.\n");
1182 gtk_window_present(GTK_WINDOW(compose->window));
1183 return compose;
1184 }
1185 }
1186
1187 compose = compose_create(account, COMPOSE_REEDIT);
1188 compose->targetinfo = procmsg_msginfo_copy(msginfo);
1189
1190 if (compose_parse_header(compose, msginfo) < 0)
1191 return compose;
1192 compose_parse_source_msg(compose, msginfo);
1193
1194 undo_block(compose->undostruct);
1195
1196 compose_reedit_set_entry(compose, msginfo);
1197
1198 text = GTK_TEXT_VIEW(compose->text);
1199 buffer = gtk_text_view_get_buffer(text);
1200 mark = gtk_text_buffer_get_insert(buffer);
1201 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1202
1203 if ((fp = procmime_get_first_text_content(msginfo, NULL)) == NULL)
1204 g_warning(_("Can't get text part\n"));
1205 else {
1206 gboolean prev_autowrap = compose->autowrap;
1207
1208 compose->autowrap = FALSE;
1209 while (fgets(buf, sizeof(buf), fp) != NULL) {
1210 strcrchomp(buf);
1211 gtk_text_buffer_insert(buffer, &iter, buf, -1);
1212 }
1213 compose_enable_sig(compose);
1214 compose->autowrap = prev_autowrap;
1215 fclose(fp);
1216 }
1217 compose_attach_parts(compose, msginfo);
1218
1219 gtk_text_buffer_get_start_iter(buffer, &iter);
1220 gtk_text_buffer_place_cursor(buffer, &iter);
1221
1222 if (account->protocol != A_NNTP) {
1223 str = gtk_entry_get_text(GTK_ENTRY(compose->to_entry));
1224 if (!str || *str == '\0')
1225 focus_widget = compose->to_entry;
1226 } else {
1227 str = gtk_entry_get_text(GTK_ENTRY(compose->newsgroups_entry));
1228 if (!str || *str == '\0')
1229 focus_widget = compose->newsgroups_entry;
1230 }
1231 if (!focus_widget) {
1232 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
1233 if (!str || *str == '\0')
1234 focus_widget = compose->subject_entry;
1235 }
1236 if (focus_widget)
1237 gtk_widget_grab_focus(focus_widget);
1238 else
1239 gtk_widget_grab_focus(compose->text);
1240
1241 undo_unblock(compose->undostruct);
1242
1243 compose_connect_changed_callbacks(compose);
1244 compose_set_title(compose);
1245
1246 ifactory = gtk_item_factory_from_widget(compose->menubar);
1247 if (compose->use_mdn) {
1248 menu_set_active(ifactory, "/Tools/Request disposition notification", TRUE);
1249 }
1250 menu_set_active(ifactory, "/Edit/Auto wrapping", compose->autowrap);
1251 #if USE_GTKSPELL
1252 menu_set_active(ifactory, "/Tools/Check spell", compose->check_spell);
1253 compose_change_spell_lang_menu(compose, compose->spell_lang);
1254 #endif
1255 #if USE_GPGME
1256 menu_set_active(ifactory, "/Tools/PGP Sign", compose->use_signing);
1257 menu_set_active(ifactory, "/Tools/PGP Encrypt", compose->use_encryption);
1258 #endif
1259
1260 syl_plugin_signal_emit("compose-created", compose);
1261
1262 if (prefs_common.enable_autosave && prefs_common.autosave_itv > 0)
1263 compose->autosave_tag =
1264 g_timeout_add_full(G_PRIORITY_LOW,
1265 prefs_common.autosave_itv * 60 * 1000,
1266 autosave_timeout, compose, NULL);
1267 if (prefs_common.auto_exteditor)
1268 compose_exec_ext_editor(compose);
1269
1270 return compose;
1271 }
1272
compose_get_compose_list(void)1273 GList *compose_get_compose_list(void)
1274 {
1275 return compose_list;
1276 }
1277
compose_entry_show(Compose * compose,ComposeEntryType type)1278 static void compose_entry_show(Compose *compose, ComposeEntryType type)
1279 {
1280 GtkItemFactory *ifactory;
1281
1282 ifactory = gtk_item_factory_from_widget(compose->menubar);
1283
1284 switch (type) {
1285 case COMPOSE_ENTRY_CC:
1286 menu_set_active(ifactory, "/View/Cc", TRUE);
1287 break;
1288 case COMPOSE_ENTRY_BCC:
1289 menu_set_active(ifactory, "/View/Bcc", TRUE);
1290 break;
1291 case COMPOSE_ENTRY_REPLY_TO:
1292 menu_set_active(ifactory, "/View/Reply-To", TRUE);
1293 break;
1294 case COMPOSE_ENTRY_FOLLOWUP_TO:
1295 menu_set_active(ifactory, "/View/Followup-To", TRUE);
1296 break;
1297 case COMPOSE_ENTRY_TO:
1298 menu_set_active(ifactory, "/View/To", TRUE);
1299 break;
1300 default:
1301 break;
1302 }
1303 }
1304
compose_get_entry(Compose * compose,ComposeEntryType type)1305 static GtkEntry *compose_get_entry(Compose *compose, ComposeEntryType type)
1306 {
1307 GtkEntry *entry;
1308
1309 switch (type) {
1310 case COMPOSE_ENTRY_CC:
1311 entry = GTK_ENTRY(compose->cc_entry);
1312 break;
1313 case COMPOSE_ENTRY_BCC:
1314 entry = GTK_ENTRY(compose->bcc_entry);
1315 break;
1316 case COMPOSE_ENTRY_REPLY_TO:
1317 entry = GTK_ENTRY(compose->reply_entry);
1318 break;
1319 case COMPOSE_ENTRY_SUBJECT:
1320 entry = GTK_ENTRY(compose->subject_entry);
1321 break;
1322 case COMPOSE_ENTRY_NEWSGROUPS:
1323 entry = GTK_ENTRY(compose->newsgroups_entry);
1324 break;
1325 case COMPOSE_ENTRY_FOLLOWUP_TO:
1326 entry = GTK_ENTRY(compose->followup_entry);
1327 break;
1328 case COMPOSE_ENTRY_TO:
1329 default:
1330 entry = GTK_ENTRY(compose->to_entry);
1331 break;
1332 }
1333
1334 return entry;
1335 }
1336
compose_entry_set(Compose * compose,const gchar * text,ComposeEntryType type)1337 void compose_entry_set(Compose *compose, const gchar *text,
1338 ComposeEntryType type)
1339 {
1340 GtkEntry *entry;
1341
1342 if (!text) return;
1343
1344 compose_entry_show(compose, type);
1345 entry = compose_get_entry(compose, type);
1346
1347 gtk_entry_set_text(entry, text);
1348 }
1349
compose_entry_append(Compose * compose,const gchar * text,ComposeEntryType type)1350 void compose_entry_append(Compose *compose, const gchar *text,
1351 ComposeEntryType type)
1352 {
1353 GtkEntry *entry;
1354 const gchar *str;
1355 gint pos;
1356
1357 if (!text || *text == '\0') return;
1358
1359 compose_entry_show(compose, type);
1360 entry = compose_get_entry(compose, type);
1361
1362 if (type != COMPOSE_ENTRY_SUBJECT) {
1363 str = gtk_entry_get_text(entry);
1364 if (*str != '\0') {
1365 pos = entry->text_length;
1366 gtk_editable_insert_text(GTK_EDITABLE(entry),
1367 ", ", -1, &pos);
1368 }
1369 }
1370
1371 pos = entry->text_length;
1372 gtk_editable_insert_text(GTK_EDITABLE(entry), text, -1, &pos);
1373 }
1374
compose_entry_get_text(Compose * compose,ComposeEntryType type)1375 gchar *compose_entry_get_text(Compose *compose, ComposeEntryType type)
1376 {
1377 GtkEntry *entry;
1378
1379 entry = compose_get_entry(compose, type);
1380 return gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
1381 }
1382
compose_entries_set(Compose * compose,const gchar * mailto)1383 static void compose_entries_set(Compose *compose, const gchar *mailto)
1384 {
1385 gchar *to = NULL;
1386 gchar *cc = NULL;
1387 gchar *subject = NULL;
1388 gchar *inreplyto = NULL;
1389 gchar *body = NULL;
1390
1391 scan_mailto_url(mailto, &to, &cc, NULL, &subject, &inreplyto, &body);
1392
1393 if (to)
1394 compose_entry_set(compose, to, COMPOSE_ENTRY_TO);
1395 if (cc)
1396 compose_entry_set(compose, cc, COMPOSE_ENTRY_CC);
1397 if (subject)
1398 compose_entry_set(compose, subject, COMPOSE_ENTRY_SUBJECT);
1399 if (inreplyto) {
1400 if (strchr(inreplyto, '<'))
1401 extract_parenthesis(inreplyto, '<', '>');
1402 remove_space(inreplyto);
1403 compose->inreplyto = g_strdup(inreplyto);
1404 }
1405 if (body) {
1406 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
1407 GtkTextBuffer *buffer;
1408 GtkTextMark *mark;
1409 GtkTextIter iter;
1410 gboolean prev_autowrap = compose->autowrap;
1411
1412 compose->autowrap = FALSE;
1413
1414 buffer = gtk_text_view_get_buffer(text);
1415 mark = gtk_text_buffer_get_insert(buffer);
1416 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1417
1418 gtk_text_buffer_insert(buffer, &iter, body, -1);
1419 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
1420
1421 compose->autowrap = prev_autowrap;
1422 if (compose->autowrap)
1423 compose_wrap_all(compose);
1424 }
1425
1426 g_free(to);
1427 g_free(cc);
1428 g_free(subject);
1429 g_free(inreplyto);
1430 g_free(body);
1431 }
1432
compose_entries_set_from_item(Compose * compose,FolderItem * item,ComposeMode mode)1433 static void compose_entries_set_from_item(Compose *compose, FolderItem *item,
1434 ComposeMode mode)
1435 {
1436 g_return_if_fail(item != NULL);
1437
1438 if (item->auto_to) {
1439 if (mode != COMPOSE_REPLY || item->use_auto_to_on_reply)
1440 compose_entry_set(compose, item->auto_to,
1441 COMPOSE_ENTRY_TO);
1442 }
1443 if (item->auto_cc)
1444 compose_entry_set(compose, item->auto_cc, COMPOSE_ENTRY_CC);
1445 if (item->auto_bcc)
1446 compose_entry_set(compose, item->auto_bcc, COMPOSE_ENTRY_BCC);
1447 if (item->auto_replyto)
1448 compose_entry_set(compose, item->auto_replyto,
1449 COMPOSE_ENTRY_REPLY_TO);
1450 }
1451
1452 #undef ACTIVATE_MENU
1453
compose_parse_header(Compose * compose,MsgInfo * msginfo)1454 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
1455 {
1456 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
1457 {"Cc:", NULL, TRUE},
1458 {"References:", NULL, FALSE},
1459 {"Bcc:", NULL, TRUE},
1460 {"Newsgroups:", NULL, TRUE},
1461 {"Followup-To:", NULL, TRUE},
1462 {"List-Post:", NULL, FALSE},
1463 {"Content-Type:",NULL, FALSE},
1464 {NULL, NULL, FALSE}};
1465
1466 enum
1467 {
1468 H_REPLY_TO = 0,
1469 H_CC = 1,
1470 H_REFERENCES = 2,
1471 H_BCC = 3,
1472 H_NEWSGROUPS = 4,
1473 H_FOLLOWUP_TO = 5,
1474 H_LIST_POST = 6,
1475 H_CONTENT_TYPE = 7
1476 };
1477
1478 FILE *fp;
1479 gchar *charset = NULL;
1480
1481 g_return_val_if_fail(msginfo != NULL, -1);
1482
1483 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
1484 procheader_get_header_fields(fp, hentry);
1485 fclose(fp);
1486
1487 if (hentry[H_CONTENT_TYPE].body != NULL) {
1488 procmime_scan_content_type_str(hentry[H_CONTENT_TYPE].body,
1489 NULL, &charset, NULL, NULL);
1490 g_free(hentry[H_CONTENT_TYPE].body);
1491 hentry[H_CONTENT_TYPE].body = NULL;
1492 }
1493 if (hentry[H_REPLY_TO].body != NULL) {
1494 if (hentry[H_REPLY_TO].body[0] != '\0') {
1495 compose->replyto =
1496 conv_unmime_header(hentry[H_REPLY_TO].body,
1497 charset);
1498 }
1499 g_free(hentry[H_REPLY_TO].body);
1500 hentry[H_REPLY_TO].body = NULL;
1501 }
1502 if (hentry[H_CC].body != NULL) {
1503 compose->cc = conv_unmime_header(hentry[H_CC].body, charset);
1504 g_free(hentry[H_CC].body);
1505 hentry[H_CC].body = NULL;
1506 }
1507 if (hentry[H_REFERENCES].body != NULL) {
1508 if (compose->mode == COMPOSE_REEDIT)
1509 compose->references = hentry[H_REFERENCES].body;
1510 else {
1511 compose->references = compose_parse_references
1512 (hentry[H_REFERENCES].body, msginfo->msgid);
1513 g_free(hentry[H_REFERENCES].body);
1514 }
1515 hentry[H_REFERENCES].body = NULL;
1516 }
1517 if (hentry[H_BCC].body != NULL) {
1518 if (compose->mode == COMPOSE_REEDIT)
1519 compose->bcc =
1520 conv_unmime_header(hentry[H_BCC].body, charset);
1521 g_free(hentry[H_BCC].body);
1522 hentry[H_BCC].body = NULL;
1523 }
1524 if (hentry[H_NEWSGROUPS].body != NULL) {
1525 compose->newsgroups = hentry[H_NEWSGROUPS].body;
1526 hentry[H_NEWSGROUPS].body = NULL;
1527 }
1528 if (hentry[H_FOLLOWUP_TO].body != NULL) {
1529 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
1530 compose->followup_to =
1531 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
1532 charset);
1533 }
1534 g_free(hentry[H_FOLLOWUP_TO].body);
1535 hentry[H_FOLLOWUP_TO].body = NULL;
1536 }
1537 if (hentry[H_LIST_POST].body != NULL) {
1538 gchar *to = NULL;
1539
1540 extract_address(hentry[H_LIST_POST].body);
1541 if (hentry[H_LIST_POST].body[0] != '\0') {
1542 scan_mailto_url(hentry[H_LIST_POST].body,
1543 &to, NULL, NULL, NULL, NULL, NULL);
1544 if (to) {
1545 g_free(compose->ml_post);
1546 compose->ml_post = to;
1547 }
1548 }
1549 g_free(hentry[H_LIST_POST].body);
1550 hentry[H_LIST_POST].body = NULL;
1551 }
1552
1553 g_free(charset);
1554
1555 if (compose->mode == COMPOSE_REEDIT) {
1556 if (msginfo->inreplyto && *msginfo->inreplyto)
1557 compose->inreplyto = g_strdup(msginfo->inreplyto);
1558 return 0;
1559 }
1560
1561 if (msginfo->msgid && *msginfo->msgid)
1562 compose->inreplyto = g_strdup(msginfo->msgid);
1563
1564 if (!compose->references) {
1565 if (msginfo->msgid && *msginfo->msgid) {
1566 if (msginfo->inreplyto && *msginfo->inreplyto)
1567 compose->references =
1568 g_strdup_printf("<%s>\n\t<%s>",
1569 msginfo->inreplyto,
1570 msginfo->msgid);
1571 else
1572 compose->references =
1573 g_strconcat("<", msginfo->msgid, ">",
1574 NULL);
1575 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
1576 compose->references =
1577 g_strconcat("<", msginfo->inreplyto, ">",
1578 NULL);
1579 }
1580 }
1581
1582 return 0;
1583 }
1584
compose_parse_references(const gchar * ref,const gchar * msgid)1585 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
1586 {
1587 GSList *ref_id_list, *cur;
1588 GString *new_ref;
1589 gchar *new_ref_str;
1590
1591 ref_id_list = references_list_append(NULL, ref);
1592 if (!ref_id_list) return NULL;
1593 if (msgid && *msgid)
1594 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
1595
1596 for (;;) {
1597 gint len = 0;
1598
1599 for (cur = ref_id_list; cur != NULL; cur = cur->next)
1600 /* "<" + Message-ID + ">" + CR+LF+TAB */
1601 len += strlen((gchar *)cur->data) + 5;
1602
1603 if (len > MAX_REFERENCES_LEN) {
1604 /* remove second message-ID */
1605 if (ref_id_list && ref_id_list->next &&
1606 ref_id_list->next->next) {
1607 g_free(ref_id_list->next->data);
1608 ref_id_list = g_slist_remove
1609 (ref_id_list, ref_id_list->next->data);
1610 } else {
1611 slist_free_strings(ref_id_list);
1612 g_slist_free(ref_id_list);
1613 return NULL;
1614 }
1615 } else
1616 break;
1617 }
1618
1619 new_ref = g_string_new("");
1620 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
1621 if (new_ref->len > 0)
1622 g_string_append(new_ref, "\n\t");
1623 g_string_sprintfa(new_ref, "<%s>", (gchar *)cur->data);
1624 }
1625
1626 slist_free_strings(ref_id_list);
1627 g_slist_free(ref_id_list);
1628
1629 new_ref_str = new_ref->str;
1630 g_string_free(new_ref, FALSE);
1631
1632 return new_ref_str;
1633 }
1634
compose_parse_source_msg(Compose * compose,MsgInfo * msginfo)1635 static gint compose_parse_source_msg(Compose *compose, MsgInfo *msginfo)
1636 {
1637 static HeaderEntry hentry[] = {{"X-Sylpheed-Reply:", NULL, FALSE},
1638 {"X-Sylpheed-Forward:", NULL, FALSE},
1639 {"REP:", NULL, FALSE},
1640 {"FWD:", NULL, FALSE},
1641 {"Disposition-Notification-To:", NULL, FALSE},
1642 {"X-Sylpheed-Compose-AutoWrap:", NULL, FALSE},
1643 {"X-Sylpheed-Compose-CheckSpell:", NULL, FALSE},
1644 {"X-Sylpheed-Compose-SpellLang:", NULL, FALSE},
1645 {"X-Sylpheed-Compose-UseSigning:", NULL, FALSE},
1646 {"X-Sylpheed-Compose-UseEncryption:", NULL, FALSE},
1647 {NULL, NULL, FALSE}};
1648
1649 enum
1650 {
1651 H_X_SYLPHEED_REPLY = 0,
1652 H_X_SYLPHEED_FORWARD = 1,
1653 H_REP = 2,
1654 H_FWD = 3,
1655 H_MDN = 4,
1656 H_X_SYLPHEED_COMPOSE_AUTOWRAP = 5,
1657 H_X_SYLPHEED_COMPOSE_CHECKSPELL = 6,
1658 H_X_SYLPHEED_COMPOSE_SPELLLANG = 7,
1659 H_X_SYLPHEED_COMPOSE_USESIGNING = 8,
1660 H_X_SYLPHEED_COMPOSE_USEENCRYPTION = 9
1661 };
1662
1663 gchar *file;
1664 FILE *fp;
1665 gchar *str;
1666 gchar buf[BUFFSIZE];
1667 gint hnum;
1668
1669 g_return_val_if_fail(msginfo != NULL, -1);
1670
1671 file = procmsg_get_message_file(msginfo);
1672
1673 if ((fp = g_fopen(file, "rb")) == NULL) {
1674 FILE_OP_ERROR(file, "fopen");
1675 return -1;
1676 }
1677
1678 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry))
1679 != -1) {
1680 str = buf + strlen(hentry[hnum].name);
1681 while (g_ascii_isspace(*str))
1682 ++str;
1683 if ((hnum == H_X_SYLPHEED_REPLY || hnum == H_REP) &&
1684 !compose->reply_target) {
1685 compose->reply_target = g_strdup(str);
1686 } else if ((hnum == H_X_SYLPHEED_FORWARD || hnum == H_FWD) &&
1687 !compose->forward_targets) {
1688 compose->forward_targets = g_strdup(str);
1689 } else if (hnum == H_MDN) {
1690 compose->use_mdn = TRUE;
1691 } else if (hnum == H_X_SYLPHEED_COMPOSE_AUTOWRAP) {
1692 if (g_ascii_strcasecmp(str, "TRUE") == 0)
1693 compose->autowrap = TRUE;
1694 else
1695 compose->autowrap = FALSE;
1696 #if USE_GTKSPELL
1697 } else if (hnum == H_X_SYLPHEED_COMPOSE_CHECKSPELL) {
1698 if (g_ascii_strcasecmp(str, "TRUE") == 0)
1699 compose->check_spell = TRUE;
1700 else
1701 compose->check_spell = FALSE;
1702 } else if (hnum == H_X_SYLPHEED_COMPOSE_SPELLLANG) {
1703 g_free(compose->spell_lang);
1704 compose->spell_lang = g_strdup(str);
1705 #endif
1706 #if USE_GPGME
1707 } else if (hnum == H_X_SYLPHEED_COMPOSE_USESIGNING) {
1708 if (g_ascii_strcasecmp(str, "TRUE") == 0)
1709 compose->use_signing = TRUE;
1710 else
1711 compose->use_signing = FALSE;
1712 } else if (hnum == H_X_SYLPHEED_COMPOSE_USEENCRYPTION) {
1713 if (g_ascii_strcasecmp(str, "TRUE") == 0)
1714 compose->use_encryption = TRUE;
1715 else
1716 compose->use_encryption = FALSE;
1717 #endif
1718 }
1719 }
1720
1721 fclose(fp);
1722 g_free(file);
1723
1724 return 0;
1725 }
1726
compose_quote_fmt(Compose * compose,MsgInfo * msginfo,const gchar * fmt,const gchar * qmark,const gchar * body)1727 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
1728 const gchar *fmt, const gchar *qmark,
1729 const gchar *body)
1730 {
1731 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
1732 GtkTextBuffer *buffer;
1733 GtkTextMark *mark;
1734 GtkTextIter iter;
1735 static MsgInfo dummyinfo;
1736 gchar *quote_str = NULL;
1737 gchar *buf;
1738 gchar *p, *lastp;
1739 gint len;
1740 gboolean prev_autowrap;
1741
1742 if (!msginfo)
1743 msginfo = &dummyinfo;
1744
1745 if (qmark == NULL || *qmark == '\0')
1746 qmark = "> ";
1747
1748 quote_fmt_init(msginfo, NULL, NULL);
1749 quote_fmt_scan_string(qmark);
1750 quote_fmt_parse();
1751
1752 buf = quote_fmt_get_buffer();
1753 if (buf == NULL)
1754 alertpanel_error(_("Quote mark format error."));
1755 else
1756 quote_str = g_strdup(buf);
1757
1758 if (fmt && *fmt != '\0') {
1759 quote_fmt_init(msginfo, quote_str, body);
1760 quote_fmt_scan_string(fmt);
1761 quote_fmt_parse();
1762
1763 buf = quote_fmt_get_buffer();
1764 if (buf == NULL) {
1765 alertpanel_error(_("Message reply/forward format error."));
1766 g_free(quote_str);
1767 return NULL;
1768 }
1769 } else
1770 buf = "";
1771
1772 g_free(quote_str);
1773
1774 prev_autowrap = compose->autowrap;
1775 compose->autowrap = FALSE;
1776
1777 buffer = gtk_text_view_get_buffer(text);
1778 mark = gtk_text_buffer_get_insert(buffer);
1779 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1780
1781 for (p = buf; *p != '\0'; ) {
1782 lastp = strchr(p, '\n');
1783 len = lastp ? lastp - p + 1 : -1;
1784 gtk_text_buffer_insert(buffer, &iter, p, len);
1785 if (lastp)
1786 p = lastp + 1;
1787 else
1788 break;
1789 }
1790
1791 compose->autowrap = prev_autowrap;
1792 if (compose->autowrap)
1793 compose_wrap_all(compose);
1794
1795 return buf;
1796 }
1797
compose_reply_set_entry(Compose * compose,MsgInfo * msginfo,ComposeMode mode)1798 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
1799 ComposeMode mode)
1800 {
1801 GSList *cc_list = NULL;
1802 GSList *cur;
1803 GHashTable *to_table;
1804 gboolean to_all = FALSE, to_ml = FALSE, ignore_replyto = FALSE;
1805 gchar *from_str = NULL, *to_str = NULL, *cc_str = NULL,
1806 *replyto_str = NULL;
1807 gboolean address_only = prefs_common.reply_address_only;
1808
1809 g_return_if_fail(compose->account != NULL);
1810 g_return_if_fail(msginfo != NULL);
1811
1812 switch (COMPOSE_MODE(mode)) {
1813 case COMPOSE_REPLY_TO_SENDER:
1814 ignore_replyto = TRUE;
1815 break;
1816 case COMPOSE_REPLY_TO_ALL:
1817 to_all = TRUE;
1818 break;
1819 case COMPOSE_REPLY_TO_LIST:
1820 to_ml = TRUE;
1821 break;
1822 default:
1823 break;
1824 }
1825
1826 if (address_only) {
1827 from_str = extract_addresses(msginfo->from);
1828 to_str = extract_addresses(msginfo->to);
1829 cc_str = extract_addresses(compose->cc);
1830 replyto_str = extract_addresses(compose->replyto);
1831 } else {
1832 from_str = g_strdup(msginfo->from);
1833 to_str = g_strdup(msginfo->to);
1834 cc_str = g_strdup(compose->cc);
1835 replyto_str = g_strdup(compose->replyto);
1836 }
1837
1838 if (compose->account->protocol != A_NNTP) {
1839 if (to_ml && compose->ml_post) {
1840 /* don't reply to list for confirmation request etc. */
1841 if ((!to_str ||
1842 !strcasestr_with_skip_quote(to_str,
1843 compose->ml_post)) &&
1844 (!cc_str ||
1845 !strcasestr_with_skip_quote(cc_str,
1846 compose->ml_post)))
1847 to_ml = FALSE;
1848 }
1849 if (to_ml && compose->ml_post) {
1850 compose_entry_set(compose, compose->ml_post,
1851 COMPOSE_ENTRY_TO);
1852 if (replyto_str &&
1853 !address_equal(replyto_str, compose->ml_post))
1854 compose_entry_set(compose, replyto_str,
1855 COMPOSE_ENTRY_CC);
1856 } else if (prefs_common.inherit_recipient_on_self_reply &&
1857 address_equal(compose->account->address, from_str)) {
1858 compose_entry_set(compose, to_str, COMPOSE_ENTRY_TO);
1859 if (to_all) {
1860 compose_entry_set(compose, cc_str,
1861 COMPOSE_ENTRY_CC);
1862 to_all = FALSE;
1863 }
1864 } else {
1865 compose_entry_set(compose,
1866 (replyto_str && !ignore_replyto)
1867 ? replyto_str
1868 : from_str ? from_str : "",
1869 COMPOSE_ENTRY_TO);
1870 }
1871 } else {
1872 if (ignore_replyto) {
1873 compose_entry_set(compose, from_str ? from_str : "",
1874 COMPOSE_ENTRY_TO);
1875 } else {
1876 if (to_all) {
1877 compose_entry_set
1878 (compose,
1879 (replyto_str && !ignore_replyto)
1880 ? replyto_str
1881 : from_str ? from_str : "",
1882 COMPOSE_ENTRY_TO);
1883 }
1884 compose_entry_set(compose,
1885 compose->followup_to ? compose->followup_to
1886 : compose->newsgroups ? compose->newsgroups
1887 : "",
1888 COMPOSE_ENTRY_NEWSGROUPS);
1889 }
1890 }
1891
1892 if (msginfo->subject && *msginfo->subject) {
1893 gchar *buf;
1894 gchar *p;
1895
1896 buf = g_strdup(msginfo->subject);
1897
1898 if (msginfo->folder && msginfo->folder->trim_compose_subject)
1899 trim_subject(buf);
1900
1901 while (!g_ascii_strncasecmp(buf, "Re:", 3)) {
1902 p = buf + 3;
1903 while (g_ascii_isspace(*p)) p++;
1904 memmove(buf, p, strlen(p) + 1);
1905 }
1906
1907 compose_entry_set(compose, "Re: ", COMPOSE_ENTRY_SUBJECT);
1908 compose_entry_append(compose, buf, COMPOSE_ENTRY_SUBJECT);
1909
1910 g_free(buf);
1911 } else
1912 compose_entry_set(compose, "Re: ", COMPOSE_ENTRY_SUBJECT);
1913
1914 if (!compose->replyto && to_ml && compose->ml_post)
1915 goto done;
1916 if (!to_all)
1917 goto done;
1918
1919 if (replyto_str && from_str)
1920 cc_list = address_list_append_orig(cc_list, from_str);
1921 cc_list = address_list_append_orig(cc_list, to_str);
1922 cc_list = address_list_append_orig(cc_list, cc_str);
1923
1924 to_table = g_hash_table_new(g_str_hash, g_str_equal);
1925 if (replyto_str) {
1926 gchar *replyto;
1927
1928 replyto = g_strdup(replyto_str);
1929 extract_address(replyto);
1930 g_hash_table_insert(to_table, replyto, GINT_TO_POINTER(1));
1931 } else if (from_str) {
1932 gchar *from;
1933
1934 from = g_strdup(from_str);
1935 extract_address(from);
1936 g_hash_table_insert(to_table, from, GINT_TO_POINTER(1));
1937 }
1938 if (compose->account->address)
1939 g_hash_table_insert(to_table,
1940 g_strdup(compose->account->address),
1941 GINT_TO_POINTER(1));
1942
1943 /* remove duplicate addresses */
1944 for (cur = cc_list; cur != NULL; ) {
1945 gchar *addr = (gchar *)cur->data;
1946 GSList *next = cur->next;
1947 gchar *addr_;
1948
1949 addr_ = g_strdup(addr);
1950 extract_address(addr_);
1951 if (g_hash_table_lookup(to_table, addr_) != NULL) {
1952 cc_list = g_slist_remove(cc_list, addr);
1953 g_free(addr_);
1954 g_free(addr);
1955 } else
1956 g_hash_table_insert(to_table, addr_, cur);
1957
1958 cur = next;
1959 }
1960 hash_free_strings(to_table);
1961 g_hash_table_destroy(to_table);
1962
1963 if (cc_list) {
1964 for (cur = cc_list; cur != NULL; cur = cur->next)
1965 compose_entry_append(compose, (gchar *)cur->data,
1966 COMPOSE_ENTRY_CC);
1967 slist_free_strings(cc_list);
1968 g_slist_free(cc_list);
1969 }
1970
1971 done:
1972 g_free(from_str);
1973 g_free(to_str);
1974 g_free(cc_str);
1975 g_free(replyto_str);
1976 }
1977
compose_reedit_set_entry(Compose * compose,MsgInfo * msginfo)1978 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
1979 {
1980 g_return_if_fail(msginfo != NULL);
1981 g_return_if_fail(compose->account != NULL);
1982
1983 compose_entry_set(compose, msginfo->to , COMPOSE_ENTRY_TO);
1984 compose_entry_set(compose, compose->cc , COMPOSE_ENTRY_CC);
1985 compose_entry_set(compose, compose->bcc , COMPOSE_ENTRY_BCC);
1986 compose_entry_set(compose, compose->replyto, COMPOSE_ENTRY_REPLY_TO);
1987 if (compose->account->protocol == A_NNTP) {
1988 compose_entry_set(compose, compose->newsgroups,
1989 COMPOSE_ENTRY_NEWSGROUPS);
1990 compose_entry_set(compose, compose->followup_to,
1991 COMPOSE_ENTRY_FOLLOWUP_TO);
1992 }
1993 compose_entry_set(compose, msginfo->subject, COMPOSE_ENTRY_SUBJECT);
1994 }
1995
compose_insert_sig(Compose * compose,gboolean append,gboolean replace,gboolean scroll)1996 static void compose_insert_sig(Compose *compose, gboolean append,
1997 gboolean replace, gboolean scroll)
1998 {
1999 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2000 GtkTextBuffer *buffer;
2001 GtkTextMark *mark;
2002 GtkTextIter iter;
2003 gchar *sig_str;
2004 gboolean prev_autowrap;
2005
2006 debug_print("compose_insert_sig: append:%d replace:%d scroll:%d\n",
2007 append, replace, scroll);
2008
2009 g_return_if_fail(compose->account != NULL);
2010
2011 prev_autowrap = compose->autowrap;
2012 compose->autowrap = FALSE;
2013
2014 buffer = gtk_text_view_get_buffer(text);
2015 mark = gtk_text_buffer_get_insert(buffer);
2016 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2017
2018 if (replace) {
2019 GtkTextIter start_iter, end_iter;
2020
2021 gtk_text_buffer_get_start_iter(buffer, &start_iter);
2022 gtk_text_buffer_get_end_iter(buffer, &iter);
2023
2024 while (gtk_text_iter_begins_tag
2025 (&start_iter, compose->sig_tag) ||
2026 gtk_text_iter_forward_to_tag_toggle
2027 (&start_iter, compose->sig_tag)) {
2028 end_iter = start_iter;
2029 if (gtk_text_iter_forward_to_tag_toggle
2030 (&end_iter, compose->sig_tag)) {
2031 gtk_text_buffer_delete
2032 (buffer, &start_iter, &end_iter);
2033 iter = start_iter;
2034 }
2035 }
2036 }
2037
2038 if (scroll) {
2039 if (append)
2040 gtk_text_buffer_get_end_iter(buffer, &iter);
2041 else
2042 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2043 }
2044
2045 sig_str = compose_get_signature_str(compose);
2046 if (sig_str) {
2047 if (!replace)
2048 gtk_text_buffer_insert(buffer, &iter, "\n\n", 2);
2049 else if (!gtk_text_iter_starts_line(&iter))
2050 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2051 gtk_text_buffer_insert_with_tags
2052 (buffer, &iter, sig_str, -1, compose->sig_tag, NULL);
2053 g_free(sig_str);
2054 if (scroll)
2055 gtk_text_buffer_place_cursor(buffer, &iter);
2056 }
2057
2058 compose->autowrap = prev_autowrap;
2059 if (compose->autowrap)
2060 compose_wrap_all(compose);
2061
2062 if (scroll)
2063 gtk_text_view_scroll_mark_onscreen(text, mark);
2064 }
2065
compose_enable_sig(Compose * compose)2066 static void compose_enable_sig(Compose *compose)
2067 {
2068 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2069 GtkTextBuffer *buffer;
2070 GtkTextIter iter, start, end;
2071 gchar *sig_str;
2072
2073 g_return_if_fail(compose->account != NULL);
2074
2075 buffer = gtk_text_view_get_buffer(text);
2076 gtk_text_buffer_get_start_iter(buffer, &iter);
2077
2078 sig_str = compose_get_signature_str(compose);
2079 if (!sig_str)
2080 return;
2081
2082 if (gtkut_text_buffer_find(buffer, &iter, sig_str, TRUE, &start)) {
2083 end = start;
2084 gtk_text_iter_forward_chars(&end, g_utf8_strlen(sig_str, -1));
2085 gtk_text_buffer_apply_tag(buffer, compose->sig_tag,
2086 &start, &end);
2087 }
2088
2089 g_free(sig_str);
2090 }
2091
compose_get_signature_str(Compose * compose)2092 static gchar *compose_get_signature_str(Compose *compose)
2093 {
2094 gchar *sig_path;
2095 gchar *sig_body = NULL;
2096 gchar *utf8_sig_body = NULL;
2097 gchar *utf8_sig_str = NULL;
2098
2099 g_return_val_if_fail(compose->account != NULL, NULL);
2100
2101 if (compose->account->sig_type == SIG_DIRECT) {
2102 gchar *sig_text;
2103 gchar *p, *sp;
2104 gint sig_index;
2105
2106 sig_index = gtk_combo_box_get_active(GTK_COMBO_BOX(compose->sig_combo));
2107 if (sig_index > 0)
2108 sig_text = compose->account->sig_texts[sig_index];
2109 else
2110 sig_text = compose->account->sig_text;
2111 if (!sig_text)
2112 return NULL;
2113
2114 sp = sig_text;
2115 p = sig_body = g_malloc(strlen(sig_text) + 1);
2116 while (*sp) {
2117 if (*sp == '\\' && *(sp + 1) == 'n') {
2118 *p++ = '\n';
2119 sp += 2;
2120 } else
2121 *p++ = *sp++;
2122 }
2123 *p = '\0';
2124
2125 if (prefs_common.sig_sep) {
2126 utf8_sig_str = g_strconcat(prefs_common.sig_sep, "\n",
2127 sig_body, NULL);
2128 g_free(sig_body);
2129 } else
2130 utf8_sig_str = sig_body;
2131
2132 return utf8_sig_str;
2133 }
2134
2135 if (!compose->account->sig_path)
2136 return NULL;
2137
2138 if (g_path_is_absolute(compose->account->sig_path) ||
2139 compose->account->sig_type == SIG_COMMAND)
2140 sig_path = g_strdup(compose->account->sig_path);
2141 else {
2142 #ifdef G_OS_WIN32
2143 sig_path = g_strconcat(get_rc_dir(),
2144 #else
2145 sig_path = g_strconcat(get_home_dir(),
2146 #endif
2147 G_DIR_SEPARATOR_S,
2148 compose->account->sig_path, NULL);
2149 }
2150
2151 if (compose->account->sig_type == SIG_FILE) {
2152 if (!is_file_or_fifo_exist(sig_path)) {
2153 debug_print("can't open signature file: %s\n",
2154 sig_path);
2155 g_free(sig_path);
2156 return NULL;
2157 }
2158 }
2159
2160 if (compose->account->sig_type == SIG_COMMAND)
2161 sig_body = get_command_output(sig_path);
2162 else {
2163 gchar *tmp;
2164
2165 tmp = file_read_to_str(sig_path);
2166 if (!tmp)
2167 return NULL;
2168 sig_body = normalize_newlines(tmp);
2169 g_free(tmp);
2170 }
2171 g_free(sig_path);
2172
2173 if (sig_body) {
2174 gint error = 0;
2175
2176 utf8_sig_body = conv_codeset_strdup_full
2177 (sig_body, conv_get_locale_charset_str(),
2178 CS_INTERNAL, &error);
2179 if (!utf8_sig_body || error != 0) {
2180 if (g_utf8_validate(sig_body, -1, NULL) == TRUE) {
2181 g_free(utf8_sig_body);
2182 utf8_sig_body = conv_utf8todisp(sig_body, NULL);
2183 }
2184 } else {
2185 g_free(sig_body);
2186 sig_body = utf8_sig_body;
2187 utf8_sig_body = conv_utf8todisp(sig_body, NULL);
2188 }
2189 g_free(sig_body);
2190 }
2191
2192 if (prefs_common.sig_sep) {
2193 utf8_sig_str = g_strconcat(prefs_common.sig_sep, "\n",
2194 utf8_sig_body, NULL);
2195 g_free(utf8_sig_body);
2196 } else
2197 utf8_sig_str = utf8_sig_body;
2198
2199 return utf8_sig_str;
2200 }
2201
compose_insert_file(Compose * compose,const gchar * file,gboolean scroll)2202 static void compose_insert_file(Compose *compose, const gchar *file,
2203 gboolean scroll)
2204 {
2205 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2206 GtkTextBuffer *buffer;
2207 GtkTextMark *mark;
2208 GtkTextIter iter;
2209 const gchar *cur_encoding;
2210 gchar buf[BUFFSIZE];
2211 gint len;
2212 FILE *fp;
2213 gboolean prev_autowrap;
2214 CharSet enc;
2215 gchar *tmp_file = NULL;
2216
2217 g_return_if_fail(file != NULL);
2218
2219 enc = conv_check_file_encoding(file);
2220 if (enc == C_UTF_16 || enc == C_UTF_16BE || enc == C_UTF_16LE) {
2221 tmp_file = get_tmp_file();
2222 if (conv_copy_file(file, tmp_file, conv_get_charset_str(enc)) < 0) {
2223 g_warning("compose_insert_file: Cannot convert UTF-16 file %s to UTF-8\n", file);
2224 g_free(tmp_file);
2225 tmp_file = NULL;
2226 }
2227 }
2228
2229 if (tmp_file) {
2230 if ((fp = g_fopen(tmp_file, "rb")) == NULL) {
2231 FILE_OP_ERROR(tmp_file, "fopen");
2232 g_unlink(tmp_file);
2233 g_free(tmp_file);
2234 return;
2235 }
2236 } else {
2237 if ((fp = g_fopen(file, "rb")) == NULL) {
2238 FILE_OP_ERROR(file, "fopen");
2239 return;
2240 }
2241 }
2242
2243 prev_autowrap = compose->autowrap;
2244 compose->autowrap = FALSE;
2245
2246 buffer = gtk_text_view_get_buffer(text);
2247 mark = gtk_text_buffer_get_insert(buffer);
2248 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2249
2250 cur_encoding = conv_get_locale_charset_str();
2251
2252 while (fgets(buf, sizeof(buf), fp) != NULL) {
2253 gchar *str;
2254 gint error = 0;
2255
2256 if (enc == C_UTF_8) {
2257 str = conv_utf8todisp(buf, NULL);
2258 } else {
2259 str = conv_codeset_strdup_full(buf, cur_encoding,
2260 CS_INTERNAL, &error);
2261 if (!str || error != 0) {
2262 if (g_utf8_validate(buf, -1, NULL) == TRUE) {
2263 g_free(str);
2264 str = g_strdup(buf);
2265 }
2266 }
2267 if (!str) continue;
2268 }
2269
2270 /* strip <CR> if DOS/Windows file,
2271 replace <CR> with <LF> if Macintosh file. */
2272 strcrchomp(str);
2273 len = strlen(str);
2274 if (len > 0 && str[len - 1] != '\n') {
2275 while (--len >= 0)
2276 if (str[len] == '\r') str[len] = '\n';
2277 }
2278
2279 gtk_text_buffer_insert(buffer, &iter, str, -1);
2280 g_free(str);
2281 }
2282
2283 fclose(fp);
2284 if (tmp_file) {
2285 g_unlink(tmp_file);
2286 g_free(tmp_file);
2287 }
2288
2289 compose->autowrap = prev_autowrap;
2290 if (compose->autowrap)
2291 compose_wrap_all(compose);
2292
2293 if (scroll)
2294 gtk_text_view_scroll_mark_onscreen(text, mark);
2295 }
2296
compose_attach_append(Compose * compose,const gchar * file,const gchar * filename,const gchar * content_type)2297 void compose_attach_append(Compose *compose, const gchar *file,
2298 const gchar *filename,
2299 const gchar *content_type)
2300 {
2301 GtkTreeIter iter;
2302 AttachInfo *ainfo;
2303 FILE *fp;
2304 off_t size;
2305
2306 g_return_if_fail(file != NULL);
2307 g_return_if_fail(*file != '\0');
2308
2309 if (!is_file_exist(file)) {
2310 g_warning(_("File %s doesn't exist\n"), file);
2311 return;
2312 }
2313 if ((size = get_file_size(file)) < 0) {
2314 g_warning(_("Can't get file size of %s\n"), file);
2315 return;
2316 }
2317 if (size == 0) {
2318 manage_window_focus_in(compose->window, NULL, NULL);
2319 alertpanel_notice(_("File %s is empty."), file);
2320 return;
2321 }
2322 if ((fp = g_fopen(file, "rb")) == NULL) {
2323 manage_window_focus_in(compose->window, NULL, NULL);
2324 alertpanel_error(_("Can't read %s."), file);
2325 return;
2326 }
2327 fclose(fp);
2328
2329 if (!compose->use_attach) {
2330 GtkItemFactory *ifactory;
2331
2332 ifactory = gtk_item_factory_from_widget(compose->menubar);
2333 menu_set_active(ifactory, "/View/Attachment", TRUE);
2334 }
2335
2336 ainfo = g_new0(AttachInfo, 1);
2337 ainfo->file = g_strdup(file);
2338
2339 if (content_type) {
2340 ainfo->content_type = g_strdup(content_type);
2341 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
2342 MsgInfo *msginfo;
2343 MsgFlags flags = {0, 0};
2344 const gchar *name;
2345
2346 if (procmime_get_encoding_for_text_file(file) == ENC_7BIT)
2347 ainfo->encoding = ENC_7BIT;
2348 else
2349 ainfo->encoding = ENC_8BIT;
2350
2351 msginfo = procheader_parse_file(file, flags, FALSE);
2352 if (msginfo && msginfo->subject)
2353 name = msginfo->subject;
2354 else
2355 name = g_basename(filename ? filename : file);
2356
2357 ainfo->name = g_strdup_printf(_("Message: %s"), name);
2358
2359 procmsg_msginfo_free(msginfo);
2360 } else {
2361 if (!g_ascii_strncasecmp(content_type, "text", 4))
2362 ainfo->encoding = procmime_get_encoding_for_text_file(file);
2363 else
2364 ainfo->encoding = ENC_BASE64;
2365 ainfo->name = g_strdup
2366 (g_basename(filename ? filename : file));
2367 }
2368 } else {
2369 ainfo->content_type = procmime_get_mime_type(file);
2370 if (!ainfo->content_type) {
2371 ainfo->content_type =
2372 g_strdup("application/octet-stream");
2373 ainfo->encoding = ENC_BASE64;
2374 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
2375 ainfo->encoding =
2376 procmime_get_encoding_for_text_file(file);
2377 else
2378 ainfo->encoding = ENC_BASE64;
2379 ainfo->name = g_strdup(g_basename(filename ? filename : file));
2380 }
2381 ainfo->size = size;
2382
2383 gtk_list_store_append(compose->attach_store, &iter);
2384 gtk_list_store_set(compose->attach_store, &iter,
2385 COL_MIMETYPE, ainfo->content_type,
2386 COL_SIZE, to_human_readable(ainfo->size),
2387 COL_NAME, ainfo->name,
2388 COL_ATTACH_INFO, ainfo,
2389 -1);
2390
2391 syl_plugin_signal_emit("compose-attach-changed", compose);
2392 }
2393
compose_attach_parts(Compose * compose,MsgInfo * msginfo)2394 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
2395 {
2396 MimeInfo *mimeinfo;
2397 MimeInfo *child;
2398 gchar *infile;
2399 gchar *outfile;
2400
2401 mimeinfo = procmime_scan_message(msginfo);
2402 if (!mimeinfo) return;
2403
2404 infile = procmsg_get_message_file_path(msginfo);
2405
2406 child = mimeinfo;
2407 while (child != NULL) {
2408 if (child->children || child->mime_type == MIME_MULTIPART)
2409 goto next;
2410 if (child->mime_type != MIME_MESSAGE_RFC822 &&
2411 child->mime_type != MIME_IMAGE &&
2412 child->mime_type != MIME_AUDIO &&
2413 child->mime_type != MIME_VIDEO &&
2414 !child->filename && !child->name)
2415 goto next;
2416
2417 outfile = procmime_get_tmp_file_name(child);
2418 if (procmime_get_part(outfile, infile, child) < 0) {
2419 g_warning(_("Can't get the part of multipart message."));
2420 g_free(outfile);
2421 goto next;
2422 }
2423
2424 compose_attach_append
2425 (compose, outfile,
2426 child->filename ? child->filename : child->name,
2427 child->content_type);
2428
2429 g_free(outfile);
2430
2431 next:
2432 if (child->mime_type == MIME_MESSAGE_RFC822)
2433 child = child->next;
2434 else
2435 child = procmime_mimeinfo_next(child);
2436 }
2437
2438 g_free(infile);
2439 procmime_mimeinfo_free_all(mimeinfo);
2440 }
2441
2442 #define INDENT_CHARS ">|#"
2443
2444 typedef enum {
2445 WAIT_FOR_INDENT_CHAR,
2446 WAIT_FOR_INDENT_CHAR_OR_SPACE,
2447 } IndentState;
2448
2449 /* return indent length, we allow:
2450 indent characters followed by indent characters or spaces/tabs,
2451 alphabets and numbers immediately followed by indent characters,
2452 and the repeating sequences of the above
2453 If quote ends with multiple spaces, only the first one is included. */
compose_get_quote_str(GtkTextBuffer * buffer,const GtkTextIter * start,gint * len)2454 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
2455 const GtkTextIter *start, gint *len)
2456 {
2457 GtkTextIter iter = *start;
2458 gunichar wc;
2459 gchar ch[6];
2460 gint clen;
2461 IndentState state = WAIT_FOR_INDENT_CHAR;
2462 gboolean is_space;
2463 gboolean is_indent;
2464 gint alnum_count = 0;
2465 gint space_count = 0;
2466 gint quote_len = 0;
2467
2468 while (!gtk_text_iter_ends_line(&iter)) {
2469 wc = gtk_text_iter_get_char(&iter);
2470 if (g_unichar_iswide(wc))
2471 break;
2472 clen = g_unichar_to_utf8(wc, ch);
2473 if (clen != 1)
2474 break;
2475
2476 is_indent = strchr(INDENT_CHARS, ch[0]) ? TRUE : FALSE;
2477 is_space = g_unichar_isspace(wc);
2478
2479 if (state == WAIT_FOR_INDENT_CHAR) {
2480 if (!is_indent && !g_unichar_isalnum(wc))
2481 break;
2482 if (is_indent) {
2483 quote_len += alnum_count + space_count + 1;
2484 alnum_count = space_count = 0;
2485 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
2486 } else
2487 alnum_count++;
2488 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
2489 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
2490 break;
2491 if (is_space)
2492 space_count++;
2493 else if (is_indent) {
2494 quote_len += alnum_count + space_count + 1;
2495 alnum_count = space_count = 0;
2496 } else {
2497 alnum_count++;
2498 state = WAIT_FOR_INDENT_CHAR;
2499 }
2500 }
2501
2502 gtk_text_iter_forward_char(&iter);
2503 }
2504
2505 if (quote_len > 0 && space_count > 0)
2506 quote_len++;
2507
2508 if (len)
2509 *len = quote_len;
2510
2511 if (quote_len > 0) {
2512 iter = *start;
2513 gtk_text_iter_forward_chars(&iter, quote_len);
2514 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
2515 }
2516
2517 return NULL;
2518 }
2519
2520 /* return TRUE if the line is itemized */
compose_is_itemized(GtkTextBuffer * buffer,const GtkTextIter * start)2521 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
2522 const GtkTextIter *start)
2523 {
2524 GtkTextIter iter = *start;
2525 gunichar wc;
2526 gchar ch[6];
2527 gint clen;
2528
2529 if (gtk_text_iter_ends_line(&iter))
2530 return FALSE;
2531
2532 while (1) {
2533 wc = gtk_text_iter_get_char(&iter);
2534 if (!g_unichar_isspace(wc))
2535 break;
2536 gtk_text_iter_forward_char(&iter);
2537 if (gtk_text_iter_ends_line(&iter))
2538 return FALSE;
2539 }
2540
2541 clen = g_unichar_to_utf8(wc, ch);
2542
2543 /* (1), 2), 3. etc. */
2544 if ((clen == 1 && ch[0] == '(') || g_unichar_isdigit(wc)) {
2545 gboolean digit_appeared = FALSE;
2546
2547 if (ch[0] == '(')
2548 gtk_text_iter_forward_char(&iter);
2549
2550 while (1) {
2551 wc = gtk_text_iter_get_char(&iter);
2552 clen = g_unichar_to_utf8(wc, ch);
2553 if (g_unichar_isdigit(wc)) {
2554 digit_appeared = TRUE;
2555 gtk_text_iter_forward_char(&iter);
2556 if (gtk_text_iter_ends_line(&iter))
2557 return FALSE;
2558 } else if (clen == 1 &&
2559 (ch[0] == ')' || ch[0] == '.')) {
2560 if (!digit_appeared)
2561 return FALSE;
2562 gtk_text_iter_forward_char(&iter);
2563 if (gtk_text_iter_ends_line(&iter))
2564 return TRUE;
2565 wc = gtk_text_iter_get_char(&iter);
2566 if (g_unichar_isspace(wc))
2567 return TRUE;
2568 else
2569 return FALSE;
2570 } else
2571 return FALSE;
2572 }
2573 }
2574
2575 if (clen != 1)
2576 return FALSE;
2577 if (!strchr("*-+", ch[0]))
2578 return FALSE;
2579
2580 gtk_text_iter_forward_char(&iter);
2581 if (gtk_text_iter_ends_line(&iter))
2582 return FALSE;
2583 wc = gtk_text_iter_get_char(&iter);
2584 if (g_unichar_isspace(wc))
2585 return TRUE;
2586 else if (ch[0] == '-') {
2587 /* -- */
2588 clen = g_unichar_to_utf8(wc, ch);
2589 if (clen == 1 && ch[0] == '-')
2590 return TRUE;
2591 }
2592
2593 return FALSE;
2594 }
2595
compose_get_line_break_pos(GtkTextBuffer * buffer,const GtkTextIter * start,GtkTextIter * break_pos,gint max_col,gint quote_len)2596 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
2597 const GtkTextIter *start,
2598 GtkTextIter *break_pos,
2599 gint max_col,
2600 gint quote_len)
2601 {
2602 GtkTextIter iter = *start, line_end = *start;
2603 PangoLogAttr *attrs;
2604 gchar *str;
2605 gchar *p;
2606 gint len;
2607 gint i;
2608 gint col = 0;
2609 gint pos = 0;
2610 gboolean can_break = FALSE;
2611 gboolean do_break = FALSE;
2612 gboolean prev_dont_break = FALSE;
2613
2614 gtk_text_iter_forward_to_line_end(&line_end);
2615 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
2616 len = g_utf8_strlen(str, -1);
2617 /* g_print("breaking line: %d: %s (len = %d)\n",
2618 gtk_text_iter_get_line(&iter), str, len); */
2619 attrs = g_new(PangoLogAttr, len + 1);
2620
2621 pango_default_break(str, -1, NULL, attrs, len + 1);
2622
2623 p = str;
2624
2625 /* skip quote and leading spaces */
2626 for (i = 0; *p != '\0' && i < len; i++) {
2627 gunichar wc;
2628
2629 wc = g_utf8_get_char(p);
2630 if (i >= quote_len && !g_unichar_isspace(wc))
2631 break;
2632 if (g_unichar_iswide(wc))
2633 col += 2;
2634 else if (*p == '\t')
2635 col += 8;
2636 else
2637 col++;
2638 p = g_utf8_next_char(p);
2639 }
2640
2641 for (; *p != '\0' && i < len; i++) {
2642 PangoLogAttr *attr = attrs + i;
2643 gunichar wc;
2644 gint uri_len;
2645
2646 if (attr->is_line_break && can_break && !prev_dont_break)
2647 pos = i;
2648
2649 /* don't wrap URI */
2650 if ((uri_len = get_uri_len(p)) > 0) {
2651 col += uri_len;
2652 if (pos > 0 && col > max_col) {
2653 do_break = TRUE;
2654 break;
2655 }
2656 i += uri_len - 1;
2657 p += uri_len;
2658 can_break = TRUE;
2659 continue;
2660 }
2661
2662 wc = g_utf8_get_char(p);
2663 if (g_unichar_iswide(wc)) {
2664 col += 2;
2665 if (prev_dont_break && can_break && attr->is_line_break)
2666 pos = i;
2667 } else if (*p == '\t')
2668 col += 8;
2669 else
2670 col++;
2671 if (pos > 0 && col > max_col) {
2672 do_break = TRUE;
2673 break;
2674 }
2675
2676 if (*p == '-' || *p == '/')
2677 prev_dont_break = TRUE;
2678 else
2679 prev_dont_break = FALSE;
2680
2681 p = g_utf8_next_char(p);
2682 can_break = TRUE;
2683 }
2684
2685 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
2686
2687 g_free(attrs);
2688 g_free(str);
2689
2690 *break_pos = *start;
2691 gtk_text_iter_set_line_offset(break_pos, pos);
2692
2693 return do_break;
2694 }
2695
compose_join_next_line(GtkTextBuffer * buffer,GtkTextIter * iter,const gchar * quote_str)2696 static gboolean compose_join_next_line(GtkTextBuffer *buffer,
2697 GtkTextIter *iter,
2698 const gchar *quote_str)
2699 {
2700 GtkTextIter iter_ = *iter, cur, prev, next, end;
2701 PangoLogAttr attrs[3];
2702 gchar *str;
2703 gchar *next_quote_str;
2704 gunichar wc1, wc2;
2705 gint quote_len;
2706 gboolean keep_cursor = FALSE;
2707
2708 if (!gtk_text_iter_forward_line(&iter_) ||
2709 gtk_text_iter_ends_line(&iter_))
2710 return FALSE;
2711
2712 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
2713
2714 if ((quote_str || next_quote_str) &&
2715 strcmp2(quote_str, next_quote_str) != 0) {
2716 g_free(next_quote_str);
2717 return FALSE;
2718 }
2719 g_free(next_quote_str);
2720
2721 end = iter_;
2722 if (quote_len > 0) {
2723 gtk_text_iter_forward_chars(&end, quote_len);
2724 if (gtk_text_iter_ends_line(&end))
2725 return FALSE;
2726 }
2727
2728 /* don't join itemized lines */
2729 if (compose_is_itemized(buffer, &end))
2730 return FALSE;
2731
2732 /* delete quote str */
2733 if (quote_len > 0)
2734 gtk_text_buffer_delete(buffer, &iter_, &end);
2735
2736 /* delete linebreak and extra spaces */
2737 prev = cur = iter_;
2738 while (gtk_text_iter_backward_char(&cur)) {
2739 wc1 = gtk_text_iter_get_char(&cur);
2740 if (!g_unichar_isspace(wc1))
2741 break;
2742 prev = cur;
2743 }
2744 next = cur = iter_;
2745 while (!gtk_text_iter_ends_line(&cur)) {
2746 wc1 = gtk_text_iter_get_char(&cur);
2747 if (!g_unichar_isspace(wc1))
2748 break;
2749 gtk_text_iter_forward_char(&cur);
2750 next = cur;
2751 }
2752 if (!gtk_text_iter_equal(&prev, &next)) {
2753 GtkTextMark *mark;
2754
2755 mark = gtk_text_buffer_get_insert(buffer);
2756 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
2757 if (gtk_text_iter_equal(&prev, &cur))
2758 keep_cursor = TRUE;
2759 gtk_text_buffer_delete(buffer, &prev, &next);
2760 }
2761 iter_ = prev;
2762
2763 /* insert space if required */
2764 gtk_text_iter_backward_char(&prev);
2765 wc1 = gtk_text_iter_get_char(&prev);
2766 wc2 = gtk_text_iter_get_char(&next);
2767 gtk_text_iter_forward_char(&next);
2768 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
2769 pango_default_break(str, -1, NULL, attrs, 3);
2770 if (!attrs[1].is_line_break ||
2771 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
2772 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
2773 if (keep_cursor) {
2774 gtk_text_iter_backward_char(&iter_);
2775 gtk_text_buffer_place_cursor(buffer, &iter_);
2776 }
2777 }
2778 g_free(str);
2779
2780 *iter = iter_;
2781 return TRUE;
2782 }
2783
compose_wrap_paragraph(Compose * compose,GtkTextIter * par_iter)2784 static void compose_wrap_paragraph(Compose *compose, GtkTextIter *par_iter)
2785 {
2786 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2787 GtkTextBuffer *buffer;
2788 GtkTextIter iter, break_pos;
2789 gchar *quote_str = NULL;
2790 gint quote_len;
2791 gboolean wrap_quote = prefs_common.linewrap_quote;
2792 gboolean prev_autowrap = compose->autowrap;
2793
2794 compose->autowrap = FALSE;
2795
2796 buffer = gtk_text_view_get_buffer(text);
2797
2798 if (par_iter) {
2799 iter = *par_iter;
2800 } else {
2801 GtkTextMark *mark;
2802 mark = gtk_text_buffer_get_insert(buffer);
2803 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2804 }
2805
2806 /* move to paragraph start */
2807 gtk_text_iter_set_line_offset(&iter, 0);
2808 if (gtk_text_iter_ends_line(&iter)) {
2809 while (gtk_text_iter_ends_line(&iter) &&
2810 gtk_text_iter_forward_line(&iter))
2811 ;
2812 } else {
2813 while (gtk_text_iter_backward_line(&iter)) {
2814 if (gtk_text_iter_ends_line(&iter)) {
2815 gtk_text_iter_forward_line(&iter);
2816 break;
2817 }
2818 }
2819 }
2820
2821 /* go until paragraph end (empty line) */
2822 while (!gtk_text_iter_ends_line(&iter)) {
2823 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
2824 if (quote_str) {
2825 if (!wrap_quote) {
2826 gtk_text_iter_forward_line(&iter);
2827 g_free(quote_str);
2828 continue;
2829 }
2830 debug_print("compose_wrap_paragraph(): quote_str = '%s'\n", quote_str);
2831 }
2832
2833 if (compose_get_line_break_pos(buffer, &iter, &break_pos,
2834 prefs_common.linewrap_len,
2835 quote_len)) {
2836 GtkTextIter prev, next, cur;
2837
2838 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
2839
2840 /* remove trailing spaces */
2841 cur = break_pos;
2842 gtk_text_iter_backward_char(&cur);
2843 prev = next = cur;
2844 while (!gtk_text_iter_starts_line(&cur)) {
2845 gunichar wc;
2846
2847 gtk_text_iter_backward_char(&cur);
2848 wc = gtk_text_iter_get_char(&cur);
2849 if (!g_unichar_isspace(wc))
2850 break;
2851 prev = cur;
2852 }
2853 if (!gtk_text_iter_equal(&prev, &next)) {
2854 gtk_text_buffer_delete(buffer, &prev, &next);
2855 break_pos = next;
2856 gtk_text_iter_forward_char(&break_pos);
2857 }
2858
2859 if (quote_str)
2860 gtk_text_buffer_insert(buffer, &break_pos,
2861 quote_str, -1);
2862
2863 iter = break_pos;
2864 compose_join_next_line(buffer, &iter, quote_str);
2865
2866 /* move iter to current line start */
2867 gtk_text_iter_set_line_offset(&iter, 0);
2868 } else {
2869 /* move iter to next line start */
2870 iter = break_pos;
2871 gtk_text_iter_forward_line(&iter);
2872 }
2873
2874 g_free(quote_str);
2875 }
2876
2877 if (par_iter)
2878 *par_iter = iter;
2879
2880 compose->autowrap = prev_autowrap;
2881 }
2882
compose_wrap_all(Compose * compose)2883 static void compose_wrap_all(Compose *compose)
2884 {
2885 compose_wrap_all_full(compose, FALSE);
2886 }
2887
compose_wrap_all_full(Compose * compose,gboolean autowrap)2888 static void compose_wrap_all_full(Compose *compose, gboolean autowrap)
2889 {
2890 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2891 GtkTextBuffer *buffer;
2892 GtkTextIter iter;
2893
2894 buffer = gtk_text_view_get_buffer(text);
2895
2896 gtk_text_buffer_get_start_iter(buffer, &iter);
2897 while (!gtk_text_iter_is_end(&iter))
2898 compose_wrap_paragraph(compose, &iter);
2899 }
2900
compose_set_title(Compose * compose)2901 static void compose_set_title(Compose *compose)
2902 {
2903 gchar *str;
2904 gchar *edited;
2905 const gchar *subject;
2906
2907 subject = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
2908 if (!subject || subject[0] == '\0')
2909 subject = _("(No Subject)");
2910
2911 edited = compose->modified ? " *" : "";
2912 str = g_strdup_printf(_("%s - Compose%s"), subject, edited);
2913 gtk_window_set_title(GTK_WINDOW(compose->window), str);
2914 g_free(str);
2915 }
2916
compose_select_account(Compose * compose,PrefsAccount * account,gboolean init)2917 static void compose_select_account(Compose *compose, PrefsAccount *account,
2918 gboolean init)
2919 {
2920 GtkItemFactory *ifactory;
2921 PrefsAccount *prev_account;
2922
2923 g_return_if_fail(account != NULL);
2924
2925 prev_account = compose->account;
2926 compose->account = account;
2927
2928 compose_set_title(compose);
2929
2930 ifactory = gtk_item_factory_from_widget(compose->menubar);
2931
2932 if (account->protocol == A_NNTP &&
2933 (init || prev_account->protocol != A_NNTP)) {
2934 gtk_widget_show(compose->newsgroups_hbox);
2935 gtk_widget_show(compose->newsgroups_entry);
2936 gtk_table_set_row_spacing(GTK_TABLE(compose->table), 2, 4);
2937 compose->use_newsgroups = TRUE;
2938
2939 menu_set_active(ifactory, "/View/To", FALSE);
2940 menu_set_sensitive(ifactory, "/View/To", TRUE);
2941 menu_set_active(ifactory, "/View/Cc", FALSE);
2942 menu_set_sensitive(ifactory, "/View/Followup-To", TRUE);
2943 } else if (account->protocol != A_NNTP &&
2944 (init || prev_account->protocol == A_NNTP)) {
2945 gtk_widget_hide(compose->newsgroups_hbox);
2946 gtk_widget_hide(compose->newsgroups_entry);
2947 gtk_table_set_row_spacing(GTK_TABLE(compose->table), 2, 0);
2948 gtk_widget_queue_resize(compose->table_vbox);
2949 compose->use_newsgroups = FALSE;
2950
2951 menu_set_active(ifactory, "/View/To", TRUE);
2952 menu_set_sensitive(ifactory, "/View/To", FALSE);
2953 menu_set_active(ifactory, "/View/Cc", TRUE);
2954 menu_set_active(ifactory, "/View/Followup-To", FALSE);
2955 menu_set_sensitive(ifactory, "/View/Followup-To", FALSE);
2956 }
2957
2958 if (account->set_autocc) {
2959 compose_entry_show(compose, COMPOSE_ENTRY_CC);
2960 if (account->auto_cc && compose->mode != COMPOSE_REEDIT)
2961 compose_entry_set(compose, account->auto_cc,
2962 COMPOSE_ENTRY_CC);
2963 }
2964 if (account->set_autobcc) {
2965 compose_entry_show(compose, COMPOSE_ENTRY_BCC);
2966 if (account->auto_bcc && compose->mode != COMPOSE_REEDIT)
2967 compose_entry_set(compose, account->auto_bcc,
2968 COMPOSE_ENTRY_BCC);
2969 }
2970 if (account->set_autoreplyto) {
2971 compose_entry_show(compose, COMPOSE_ENTRY_REPLY_TO);
2972 if (account->auto_replyto && compose->mode != COMPOSE_REEDIT)
2973 compose_entry_set(compose, account->auto_replyto,
2974 COMPOSE_ENTRY_REPLY_TO);
2975 }
2976
2977 #if USE_GPGME
2978 if (rfc2015_is_available()) {
2979 if (account->default_sign)
2980 menu_set_active(ifactory, "/Tools/PGP Sign", TRUE);
2981 if (account->default_encrypt)
2982 menu_set_active(ifactory, "/Tools/PGP Encrypt", TRUE);
2983 }
2984 #endif /* USE_GPGME */
2985
2986 compose_update_signature_menu(compose);
2987
2988 if (!init && compose->mode != COMPOSE_REDIRECT && prefs_common.auto_sig)
2989 compose_insert_sig(compose, TRUE, TRUE, FALSE);
2990 }
2991
compose_update_signature_menu(Compose * compose)2992 static void compose_update_signature_menu(Compose *compose)
2993 {
2994 GtkTreeModel *model;
2995 GtkListStore *store;
2996 GtkTreeIter iter;
2997 gboolean valid;
2998 gint i;
2999 gchar *name;
3000
3001 if (!compose->account)
3002 return;
3003
3004 model = gtk_combo_box_get_model(GTK_COMBO_BOX(compose->sig_combo));
3005 store = GTK_LIST_STORE(model);
3006 valid = gtk_tree_model_get_iter_first(model, &iter);
3007
3008 for (i = 0; valid && i < sizeof(compose->account->sig_names) /
3009 sizeof(compose->account->sig_names[0]); i++) {
3010 if (compose->account->sig_names[i] &&
3011 compose->account->sig_names[i][0] != '\0') {
3012 name = g_strdup_printf
3013 ("%s", compose->account->sig_names[i]);
3014 } else {
3015 name = g_strdup_printf(_("Signature %d"), i + 1);
3016 }
3017 gtk_list_store_set(store, &iter, 0, name, -1);
3018 g_free(name);
3019 valid = gtk_tree_model_iter_next(model, &iter);
3020 }
3021
3022 g_signal_handlers_block_by_func(G_OBJECT(compose->sig_combo),
3023 G_CALLBACK(sig_combo_changed), compose);
3024 gtk_combo_box_set_active(GTK_COMBO_BOX(compose->sig_combo), 0);
3025 g_signal_handlers_unblock_by_func(G_OBJECT(compose->sig_combo),
3026 G_CALLBACK(sig_combo_changed),
3027 compose);
3028
3029 if (compose->account->sig_type != SIG_DIRECT) {
3030 gtk_widget_set_sensitive(compose->sig_combo, FALSE);
3031 } else {
3032 gtk_widget_set_sensitive(compose->sig_combo, TRUE);
3033 }
3034 }
3035
compose_check_for_valid_recipient(Compose * compose)3036 static gboolean compose_check_for_valid_recipient(Compose *compose)
3037 {
3038 const gchar *to_raw = "", *cc_raw = "", *bcc_raw = "";
3039 const gchar *newsgroups_raw = "";
3040 gchar *to, *cc, *bcc;
3041 gchar *newsgroups;
3042 gboolean valid;
3043
3044 if (compose->use_to)
3045 to_raw = gtk_entry_get_text(GTK_ENTRY(compose->to_entry));
3046 if (compose->use_cc)
3047 cc_raw = gtk_entry_get_text(GTK_ENTRY(compose->cc_entry));
3048 if (compose->use_bcc)
3049 bcc_raw = gtk_entry_get_text(GTK_ENTRY(compose->bcc_entry));
3050 if (compose->use_newsgroups)
3051 newsgroups_raw = gtk_entry_get_text
3052 (GTK_ENTRY(compose->newsgroups_entry));
3053
3054 if (*to_raw == '\0' && *cc_raw == '\0' && *bcc_raw == '\0' &&
3055 *newsgroups_raw == '\0')
3056 return FALSE;
3057
3058 to = g_strstrip(g_strdup(to_raw));
3059 cc = g_strstrip(g_strdup(cc_raw));
3060 bcc = g_strstrip(g_strdup(bcc_raw));
3061 newsgroups = g_strstrip(g_strdup(newsgroups_raw));
3062
3063 if (*to == '\0' && *cc == '\0' && *bcc == '\0' && *newsgroups == '\0')
3064 valid = FALSE;
3065 else
3066 valid = TRUE;
3067
3068 g_free(newsgroups);
3069 g_free(bcc);
3070 g_free(cc);
3071 g_free(to);
3072
3073 return valid;
3074 }
3075
compose_check_entries(Compose * compose)3076 static gboolean compose_check_entries(Compose *compose)
3077 {
3078 const gchar *str;
3079
3080 if (compose_check_for_valid_recipient(compose) == FALSE) {
3081 alertpanel_error(_("Recipient is not specified."));
3082 return FALSE;
3083 }
3084
3085 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
3086 if (*str == '\0') {
3087 AlertValue aval;
3088
3089 aval = alertpanel(_("Empty subject"),
3090 _("Subject is empty. Send it anyway?"),
3091 GTK_STOCK_YES, GTK_STOCK_NO, NULL);
3092 if (aval != G_ALERTDEFAULT)
3093 return FALSE;
3094 }
3095
3096 return TRUE;
3097 }
3098
compose_check_attachments(Compose * compose)3099 static gboolean compose_check_attachments(Compose *compose)
3100 {
3101 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3102 GtkTextBuffer *buffer;
3103 GtkTextIter iter, line_end;
3104 gchar *line;
3105 gchar **strv;
3106 gint i;
3107 gboolean attach_found = FALSE;
3108 gboolean valid = TRUE;
3109
3110 if (!prefs_common.check_attach)
3111 return TRUE;
3112 if (!prefs_common.check_attach_str)
3113 return TRUE;
3114
3115 if (compose->use_attach &&
3116 gtk_tree_model_iter_n_children
3117 (GTK_TREE_MODEL(compose->attach_store), NULL) > 0)
3118 return TRUE;
3119
3120 buffer = gtk_text_view_get_buffer(text);
3121 gtk_text_buffer_get_start_iter(buffer, &iter);
3122 line_end = iter;
3123
3124 strv = g_strsplit(prefs_common.check_attach_str, ",", -1);
3125 for (i = 0; strv[i] != NULL; i++)
3126 g_strstrip(strv[i]);
3127
3128 while (valid) {
3129 valid = gtk_text_iter_forward_to_line_end(&line_end);
3130 line = gtk_text_buffer_get_text(buffer, &iter, &line_end,
3131 FALSE);
3132 iter = line_end;
3133 if (get_quote_level(line) != -1)
3134 continue;
3135
3136 for (i = 0; strv[i] != NULL; i++) {
3137 if (strv[i][0] == '\0')
3138 continue;
3139 if (strcasestr(line, strv[i])) {
3140 attach_found = TRUE;
3141 valid = FALSE;
3142 break;
3143 }
3144 }
3145
3146 g_free(line);
3147 }
3148
3149 g_strfreev(strv);
3150
3151 if (attach_found) {
3152 AlertValue aval;
3153
3154 aval = alertpanel(_("Attachment is missing"),
3155 _("There is no attachment. Send it without attachments?"),
3156 GTK_STOCK_YES, GTK_STOCK_NO, NULL);
3157 if (aval != G_ALERTDEFAULT)
3158 return FALSE;
3159 }
3160
3161 return TRUE;
3162 }
3163
check_recp_delete_event(GtkWidget * widget,GdkEventAny * event,gint * state)3164 static gint check_recp_delete_event(GtkWidget *widget, GdkEventAny *event,
3165 gint *state)
3166 {
3167 *state = GTK_RESPONSE_CANCEL;
3168 return TRUE;
3169 }
3170
check_recp_key_pressed(GtkWidget * widget,GdkEventKey * event,gint * state)3171 static gboolean check_recp_key_pressed(GtkWidget *widget, GdkEventKey *event,
3172 gint *state)
3173 {
3174 if (event && event->keyval == GDK_Escape) {
3175 *state = GTK_RESPONSE_CANCEL;
3176 return TRUE;
3177 }
3178 return FALSE;
3179 }
3180
check_recp_ok(GtkWidget * widget,gint * state)3181 static void check_recp_ok(GtkWidget *widget, gint *state)
3182 {
3183 *state = GTK_RESPONSE_OK;
3184 }
3185
check_recp_cancel(GtkWidget * widget,gint * state)3186 static void check_recp_cancel(GtkWidget *widget, gint *state)
3187 {
3188 *state = GTK_RESPONSE_CANCEL;
3189 }
3190
compose_check_recipients(Compose * compose)3191 static gboolean compose_check_recipients(Compose *compose)
3192 {
3193 GtkWidget *window;
3194 GtkWidget *vbox;
3195 GtkWidget *hbox;
3196 GtkWidget *image;
3197 GtkWidget *vbox2;
3198 GtkWidget *label;
3199 GtkWidget *table;
3200 GtkWidget *entry;
3201 gchar buf[1024];
3202 const gchar *text;
3203 GtkWidget *scrwin;
3204 GtkWidget *treeview;
3205 GtkTreeStore *store;
3206 GtkTreeViewColumn *column;
3207 GtkCellRenderer *renderer;
3208 GtkTreeIter iter, parent;
3209 GtkWidget *hbbox;
3210 GtkWidget *ok_btn;
3211 GtkWidget *cancel_btn;
3212 static PangoFontDescription *font_desc;
3213 GtkStyle *style;
3214
3215 GSList *cur, *to_list = NULL;
3216 gboolean check_recp = FALSE;
3217 gint state = 0;
3218
3219 g_return_val_if_fail(compose->account != NULL, FALSE);
3220 g_return_val_if_fail(compose->account->address != NULL, FALSE);
3221
3222 if (!prefs_common.check_recipients)
3223 return TRUE;
3224
3225 if (prefs_common.check_recp_exclude) {
3226 gchar **strv;
3227 gint i;
3228
3229 strv = g_strsplit(prefs_common.check_recp_exclude, ",", -1);
3230 for (i = 0; strv[i] != NULL; i++)
3231 g_strstrip(strv[i]);
3232
3233 if (compose->use_to) {
3234 text = gtk_entry_get_text(GTK_ENTRY(compose->to_entry));
3235 to_list = address_list_append_orig(NULL, text);
3236 }
3237 if (compose->use_cc) {
3238 text = gtk_entry_get_text(GTK_ENTRY(compose->cc_entry));
3239 to_list = address_list_append_orig(to_list, text);
3240 }
3241 if (compose->use_bcc) {
3242 text = gtk_entry_get_text(GTK_ENTRY(compose->bcc_entry));
3243 to_list = address_list_append_orig(to_list, text);
3244 }
3245
3246 for (cur = to_list; cur != NULL; cur = cur->next) {
3247 for (i = 0; strv[i] != NULL; i++) {
3248 if (strv[i][0] == '\0')
3249 continue;
3250 if (strcasestr((gchar *)cur->data, strv[i]))
3251 break;
3252 }
3253 if (!strv[i]) {
3254 /* not found in exclude list */
3255 check_recp = TRUE;
3256 break;
3257 }
3258 }
3259
3260 slist_free_strings(to_list);
3261 g_slist_free(to_list);
3262 to_list = NULL;
3263 g_strfreev(strv);
3264 } else
3265 check_recp = TRUE;
3266
3267 if (!check_recp)
3268 return TRUE;
3269
3270 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3271 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
3272 gtk_window_set_title(GTK_WINDOW(window), _("Check recipients"));
3273 gtk_window_set_position(GTK_WINDOW(window),
3274 GTK_WIN_POS_CENTER_ON_PARENT);
3275 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
3276 gtk_widget_set_size_request(window, 480 * gtkut_get_dpi_multiplier(), -1);
3277 gtk_widget_realize(window);
3278 g_signal_connect(G_OBJECT(window), "delete_event",
3279 G_CALLBACK(check_recp_delete_event), &state);
3280 g_signal_connect(G_OBJECT(window), "key_press_event",
3281 G_CALLBACK(check_recp_key_pressed), &state);
3282
3283 vbox = gtk_vbox_new(FALSE, 8);
3284 gtk_container_add(GTK_CONTAINER(window), vbox);
3285
3286 hbox = gtk_hbox_new(FALSE, 12);
3287 gtk_container_set_border_width(GTK_CONTAINER(hbox), 12);
3288 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
3289
3290 image = gtk_image_new_from_stock
3291 (GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
3292 gtk_misc_set_alignment(GTK_MISC(image), 0.5, 0.0);
3293 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
3294
3295 vbox2 = gtk_vbox_new(FALSE, 12);
3296 gtk_box_pack_start(GTK_BOX(hbox), vbox2, TRUE, TRUE, 0);
3297
3298 label = gtk_label_new(_("Check recipients"));
3299 gtk_box_pack_start(GTK_BOX(vbox2), label, TRUE, TRUE, 0);
3300 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
3301 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
3302
3303 if (!font_desc) {
3304 gint size;
3305
3306 size = pango_font_description_get_size
3307 (label->style->font_desc);
3308 font_desc = pango_font_description_new();
3309 pango_font_description_set_weight
3310 (font_desc, PANGO_WEIGHT_BOLD);
3311 pango_font_description_set_size
3312 (font_desc, size * PANGO_SCALE_LARGE);
3313 }
3314 if (font_desc)
3315 gtk_widget_modify_font(label, font_desc);
3316
3317 label = gtk_label_new
3318 (_("Really send this mail to the following addresses?"));
3319 gtk_box_pack_start(GTK_BOX(vbox2), label, TRUE, TRUE, 0);
3320 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
3321 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
3322 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
3323 GTK_WIDGET_UNSET_FLAGS(label, GTK_CAN_FOCUS);
3324
3325 table = gtk_table_new(2, 2, FALSE);
3326 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
3327 gtk_table_set_row_spacings(GTK_TABLE(table), 4);
3328 gtk_table_set_col_spacings(GTK_TABLE(table), 4);
3329
3330 hbox = gtk_hbox_new(FALSE, 0);
3331 label = gtk_label_new(prefs_common.trans_hdr ? _("From:")
3332 : "From:");
3333 gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0);
3334 gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, 0, 1,
3335 GTK_FILL, 0, 2, 0);
3336 entry = gtk_entry_new();
3337 gtk_entry_set_max_length(GTK_ENTRY(entry), MAX_ENTRY_LENGTH);
3338 gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
3339 style = gtk_widget_get_style(window);
3340 gtk_widget_modify_base(entry, GTK_STATE_NORMAL,
3341 &style->bg[GTK_STATE_NORMAL]);
3342 gtk_table_attach_defaults
3343 (GTK_TABLE(table), entry, 1, 2, 0, 1);
3344
3345 if (compose->account->name && *compose->account->name) {
3346 g_snprintf(buf, sizeof(buf), "%s <%s>",
3347 compose->account->name, compose->account->address);
3348 gtk_entry_set_text(GTK_ENTRY(entry), buf);
3349 } else
3350 gtk_entry_set_text(GTK_ENTRY(entry), compose->account->address);
3351
3352 hbox = gtk_hbox_new(FALSE, 0);
3353 label = gtk_label_new(prefs_common.trans_hdr ? _("Subject:")
3354 : "Subject:");
3355 gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0);
3356 gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, 1, 2,
3357 GTK_FILL, 0, 2, 0);
3358 entry = gtk_entry_new();
3359 gtk_entry_set_max_length(GTK_ENTRY(entry), MAX_ENTRY_LENGTH);
3360 gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
3361 style = gtk_widget_get_style(window);
3362 gtk_widget_modify_base(entry, GTK_STATE_NORMAL,
3363 &style->bg[GTK_STATE_NORMAL]);
3364 gtk_table_attach_defaults
3365 (GTK_TABLE(table), entry, 1, 2, 1, 2);
3366
3367 text = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
3368 gtk_entry_set_text(GTK_ENTRY(entry), text);
3369
3370 scrwin = gtk_scrolled_window_new(NULL, NULL);
3371 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrwin),
3372 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
3373 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrwin),
3374 GTK_SHADOW_IN);
3375 gtk_widget_set_size_request(scrwin, -1, 180 * gtkut_get_dpi_multiplier());
3376 gtk_box_pack_start(GTK_BOX(vbox), scrwin, TRUE, TRUE, 0);
3377
3378 store = gtk_tree_store_new(1, G_TYPE_STRING);
3379 if (compose->use_to) {
3380 text = gtk_entry_get_text(GTK_ENTRY(compose->to_entry));
3381 to_list = address_list_append_orig(NULL, text);
3382 if (to_list) {
3383 gtk_tree_store_append(store, &parent, NULL);
3384 gtk_tree_store_set(store, &parent, 0,
3385 prefs_common.trans_hdr ?
3386 _("To:") : "To:", -1);
3387 for (cur = to_list; cur != NULL; cur = cur->next) {
3388 gtk_tree_store_append(store, &iter, &parent);
3389 gtk_tree_store_set(store, &iter, 0,
3390 (gchar *)cur->data, -1);
3391 }
3392 slist_free_strings(to_list);
3393 g_slist_free(to_list);
3394 }
3395 }
3396 if (compose->use_cc) {
3397 text = gtk_entry_get_text(GTK_ENTRY(compose->cc_entry));
3398 to_list = address_list_append_orig(NULL, text);
3399 if (to_list) {
3400 gtk_tree_store_append(store, &parent, NULL);
3401 gtk_tree_store_set(store, &parent, 0,
3402 prefs_common.trans_hdr ?
3403 _("Cc:") : "Cc:", -1);
3404 for (cur = to_list; cur != NULL; cur = cur->next) {
3405 gtk_tree_store_append(store, &iter, &parent);
3406 gtk_tree_store_set(store, &iter, 0,
3407 (gchar *)cur->data, -1);
3408 }
3409 slist_free_strings(to_list);
3410 g_slist_free(to_list);
3411 }
3412 }
3413 if (compose->use_bcc) {
3414 text = gtk_entry_get_text(GTK_ENTRY(compose->bcc_entry));
3415 to_list = address_list_append_orig(NULL, text);
3416 if (to_list) {
3417 gtk_tree_store_append(store, &parent, NULL);
3418 gtk_tree_store_set(store, &parent, 0,
3419 prefs_common.trans_hdr ?
3420 _("Bcc:") : "Bcc:", -1);
3421 for (cur = to_list; cur != NULL; cur = cur->next) {
3422 gtk_tree_store_append(store, &iter, &parent);
3423 gtk_tree_store_set(store, &iter, 0,
3424 (gchar *)cur->data, -1);
3425 }
3426 slist_free_strings(to_list);
3427 g_slist_free(to_list);
3428 }
3429 }
3430
3431 treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
3432 g_object_unref(G_OBJECT(store));
3433 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
3434 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
3435
3436 gtk_container_add(GTK_CONTAINER(scrwin), treeview);
3437
3438 renderer = gtk_cell_renderer_text_new();
3439 g_object_set(renderer, "ypad", 0, NULL);
3440 column = gtk_tree_view_column_new_with_attributes
3441 (_("Address"), renderer, "text", 0, NULL);
3442 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
3443
3444 gtk_tree_view_expand_all(GTK_TREE_VIEW(treeview));
3445
3446 gtkut_stock_button_set_create(&hbbox, &ok_btn, _("_Send"),
3447 &cancel_btn, GTK_STOCK_CANCEL,
3448 NULL, NULL);
3449 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
3450 gtk_widget_grab_default(ok_btn);
3451 gtk_widget_grab_focus(ok_btn);
3452
3453 g_signal_connect(G_OBJECT(ok_btn), "clicked",
3454 G_CALLBACK(check_recp_ok), &state);
3455 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
3456 G_CALLBACK(check_recp_cancel), &state);
3457
3458 manage_window_set_transient(GTK_WINDOW(window));
3459
3460 gtk_widget_show_all(window);
3461
3462 while (state == 0)
3463 gtk_main_iteration();
3464
3465 gtk_widget_destroy(window);
3466
3467 if (state == GTK_RESPONSE_OK)
3468 return TRUE;
3469
3470 return FALSE;
3471 }
3472
compose_check_activities(Compose * compose)3473 static gboolean compose_check_activities(Compose *compose)
3474 {
3475 if (inc_is_active()) {
3476 alertpanel_notice(_("Checking for new messages is currently running.\n"
3477 "Please try again later."));
3478 return FALSE;
3479 }
3480
3481 return TRUE;
3482 }
3483
compose_add_new_recipients_to_addressbook(Compose * compose)3484 static void compose_add_new_recipients_to_addressbook(Compose *compose)
3485 {
3486 GSList *to_list = NULL, *cur;
3487 const gchar *text;
3488
3489 if (compose->use_to) {
3490 text = gtk_entry_get_text(GTK_ENTRY(compose->to_entry));
3491 to_list = address_list_append_orig(NULL, text);
3492 }
3493 if (compose->use_cc) {
3494 text = gtk_entry_get_text(GTK_ENTRY(compose->cc_entry));
3495 to_list = address_list_append_orig(to_list, text);
3496 }
3497 if (compose->use_bcc) {
3498 text = gtk_entry_get_text(GTK_ENTRY(compose->bcc_entry));
3499 to_list = address_list_append_orig(to_list, text);
3500 }
3501
3502 for (cur = to_list; cur != NULL; cur = cur->next) {
3503 gchar *orig_addr = cur->data;
3504 gchar *name, *addr;
3505
3506 name = procheader_get_fromname(orig_addr);
3507 addr = g_strdup(orig_addr);
3508 extract_address(addr);
3509 if (!g_ascii_strcasecmp(name, addr)) {
3510 g_free(name);
3511 name = NULL;
3512 }
3513
3514 if (addressbook_has_address(addr))
3515 debug_print("compose_add_new_recipients_to_addressbook: address <%s> already registered.\n", addr);
3516 else
3517 addressbook_add_contact_autoreg(name, addr, NULL);
3518
3519 g_free(addr);
3520 g_free(name);
3521 }
3522
3523 slist_free_strings(to_list);
3524 g_slist_free(to_list);
3525 }
3526
compose_lock(Compose * compose)3527 void compose_lock(Compose *compose)
3528 {
3529 compose->lock_count++;
3530 }
3531
compose_unlock(Compose * compose)3532 void compose_unlock(Compose *compose)
3533 {
3534 if (compose->lock_count > 0)
3535 compose->lock_count--;
3536 }
3537
compose_block_modified(Compose * compose)3538 void compose_block_modified(Compose *compose)
3539 {
3540 compose->block_modified = TRUE;
3541 }
3542
compose_unblock_modified(Compose * compose)3543 void compose_unblock_modified(Compose *compose)
3544 {
3545 compose->block_modified = FALSE;
3546 }
3547
3548 #define C_LOCK() \
3549 { \
3550 inc_lock(); \
3551 compose_lock(compose); \
3552 }
3553
3554 #define C_UNLOCK() \
3555 { \
3556 compose_unlock(compose); \
3557 inc_unlock(); \
3558 }
3559
compose_send_real(Compose * compose)3560 static gint compose_send_real(Compose *compose)
3561 {
3562 gchar tmp[MAXPATHLEN + 1];
3563 gint ok = 0;
3564 gboolean cancel = FALSE;
3565
3566 if (compose->lock_count > 0)
3567 return 1;
3568
3569 g_return_val_if_fail(compose->account != NULL, -1);
3570
3571 C_LOCK();
3572
3573 if (compose_check_entries(compose) == FALSE) {
3574 C_UNLOCK();
3575 return 1;
3576 }
3577 if (compose_check_attachments(compose) == FALSE) {
3578 C_UNLOCK();
3579 return 1;
3580 }
3581 if (compose_check_recipients(compose) == FALSE) {
3582 C_UNLOCK();
3583 return 1;
3584 }
3585 if (compose_check_activities(compose) == FALSE) {
3586 C_UNLOCK();
3587 return 1;
3588 }
3589
3590 if (!main_window_toggle_online_if_offline(main_window_get())) {
3591 C_UNLOCK();
3592 return 1;
3593 }
3594
3595 /* write to temporary file */
3596 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.%p",
3597 get_tmp_dir(), G_DIR_SEPARATOR, compose);
3598
3599 if (compose->mode == COMPOSE_REDIRECT) {
3600 if (compose_redirect_write_to_file(compose, tmp) < 0) {
3601 C_UNLOCK();
3602 return -1;
3603 }
3604 } else {
3605 if (compose_write_to_file(compose, tmp, FALSE) < 0) {
3606 C_UNLOCK();
3607 return -1;
3608 }
3609 }
3610
3611 if (!compose->to_list && !compose->newsgroup_list) {
3612 g_warning(_("can't get recipient list."));
3613 g_unlink(tmp);
3614 C_UNLOCK();
3615 return 1;
3616 }
3617
3618 syl_plugin_signal_emit("compose-send", compose, compose->mode, 0,
3619 tmp, compose->to_list, &cancel);
3620 if (cancel) {
3621 g_unlink(tmp);
3622 C_UNLOCK();
3623 return -1;
3624 }
3625
3626 if (compose->to_list) {
3627 PrefsAccount *ac;
3628
3629 if (compose->account->protocol != A_NNTP)
3630 ac = compose->account;
3631 else {
3632 ac = account_find_from_address(compose->account->address);
3633 if (!ac) {
3634 if (cur_account && cur_account->protocol != A_NNTP)
3635 ac = cur_account;
3636 else
3637 ac = account_get_default();
3638 }
3639 if (!ac || ac->protocol == A_NNTP) {
3640 alertpanel_error(_("Account for sending mail is not specified.\n"
3641 "Please select a mail account before sending."));
3642 g_unlink(tmp);
3643 C_UNLOCK();
3644 return -1;
3645 }
3646 }
3647
3648 /* POP before SMTP requires inc to be unlocked.
3649 send_message() also locks inc internally. */
3650 inc_unlock();
3651 ok = send_message(tmp, ac, compose->to_list);
3652 inc_lock();
3653
3654 statusbar_pop_all();
3655 }
3656
3657 if (ok == 0 && compose->newsgroup_list) {
3658 ok = news_post(FOLDER(compose->account->folder), tmp);
3659 if (ok < 0) {
3660 alertpanel_error(_("Error occurred while posting the message to %s ."),
3661 compose->account->nntp_server);
3662 g_unlink(tmp);
3663 C_UNLOCK();
3664 return -1;
3665 }
3666 }
3667
3668 if (ok == 0) {
3669 if (compose->mode == COMPOSE_REEDIT) {
3670 compose_remove_reedit_target(compose);
3671 if (compose->targetinfo)
3672 folderview_update_item
3673 (compose->targetinfo->folder, TRUE);
3674 }
3675
3676 if (compose->reply_target)
3677 send_message_set_reply_flag(compose->reply_target,
3678 compose->inreplyto);
3679 else if (compose->forward_targets)
3680 send_message_set_forward_flags
3681 (compose->forward_targets);
3682
3683 /* save message to outbox */
3684 if (prefs_common.savemsg) {
3685 FolderItem *outbox;
3686 gboolean drop_done = FALSE;
3687
3688 /* filter sent message */
3689 if (prefs_common.filter_sent) {
3690 FilterInfo *fltinfo;
3691
3692 fltinfo = filter_info_new();
3693 fltinfo->account = compose->account;
3694 fltinfo->flags.perm_flags = 0;
3695 fltinfo->flags.tmp_flags = MSG_RECEIVED;
3696
3697 filter_apply(prefs_common.fltlist, tmp,
3698 fltinfo);
3699
3700 drop_done = fltinfo->drop_done;
3701 folderview_update_all_updated(TRUE);
3702 filter_info_free(fltinfo);
3703 }
3704
3705 if (!drop_done) {
3706 outbox = account_get_special_folder
3707 (compose->account, F_OUTBOX);
3708 if (procmsg_save_to_outbox(outbox, tmp) < 0) {
3709 alertpanel_error
3710 (_("Sending of message was completed, but the message could not be saved to outbox."));
3711 ok = -2;
3712 } else
3713 folderview_update_item(outbox, TRUE);
3714 }
3715 }
3716
3717 /* Add recipients to addressbook automatically */
3718 if (prefs_common.recipients_autoreg) {
3719 compose_add_new_recipients_to_addressbook(compose);
3720 }
3721 }
3722
3723 g_unlink(tmp);
3724 C_UNLOCK();
3725
3726 return ok;
3727 }
3728
3729 #if USE_GPGME
compose_get_self_key_id(Compose * compose)3730 static const gchar *compose_get_self_key_id(Compose *compose)
3731 {
3732 const gchar *key_id = NULL;
3733
3734 switch (compose->account->sign_key) {
3735 case SIGN_KEY_DEFAULT:
3736 break;
3737 case SIGN_KEY_BY_FROM:
3738 key_id = compose->account->address;
3739 break;
3740 case SIGN_KEY_CUSTOM:
3741 key_id = compose->account->sign_key_id;
3742 break;
3743 default:
3744 break;
3745 }
3746
3747 return key_id;
3748 }
3749
3750 /* interfaces to rfc2015 to keep out the prefs stuff there.
3751 * returns 0 on success and -1 on error. */
compose_create_signers_list(Compose * compose,GSList ** pkey_list)3752 static gint compose_create_signers_list(Compose *compose, GSList **pkey_list)
3753 {
3754 const gchar *key_id = NULL;
3755 GSList *key_list;
3756
3757 key_id = compose_get_self_key_id(compose);
3758 if (!key_id) {
3759 *pkey_list = NULL;
3760 return 0;
3761 }
3762
3763 key_list = rfc2015_create_signers_list(key_id);
3764 if (!key_list) {
3765 alertpanel_error(_("Could not find any key associated with "
3766 "currently selected key id `%s'."), key_id);
3767 return -1;
3768 }
3769
3770 *pkey_list = key_list;
3771 return 0;
3772 }
3773
compose_create_encrypt_recipients_list(Compose * compose)3774 static GSList *compose_create_encrypt_recipients_list(Compose *compose)
3775 {
3776 GSList *recp_list = NULL;
3777 const gchar *key_id = NULL;
3778
3779 g_return_val_if_fail(compose->to_list != NULL, NULL);
3780
3781 recp_list = g_slist_copy(compose->to_list);
3782 if (compose->account->encrypt_to_self) {
3783 key_id = compose_get_self_key_id(compose);
3784 recp_list = g_slist_append(recp_list, (gpointer)key_id);
3785 }
3786
3787 return recp_list;
3788 }
3789
3790 /* clearsign message body text */
compose_clearsign_text(Compose * compose,gchar ** text)3791 static gint compose_clearsign_text(Compose *compose, gchar **text)
3792 {
3793 GSList *key_list;
3794 gchar *tmp_file;
3795
3796 tmp_file = get_tmp_file();
3797 if (str_write_to_file(*text, tmp_file) < 0) {
3798 g_free(tmp_file);
3799 return -1;
3800 }
3801
3802 if (compose_create_signers_list(compose, &key_list) < 0) {
3803 g_unlink(tmp_file);
3804 g_free(tmp_file);
3805 return -1;
3806 }
3807 if (rfc2015_clearsign(tmp_file, key_list) < 0) {
3808 alertpanel_error(_("Can't sign the message."));
3809 g_unlink(tmp_file);
3810 g_free(tmp_file);
3811 return -1;
3812 }
3813
3814 g_free(*text);
3815 *text = file_read_to_str(tmp_file);
3816 g_unlink(tmp_file);
3817 g_free(tmp_file);
3818 if (*text == NULL)
3819 return -1;
3820
3821 return 0;
3822 }
3823
compose_encrypt_armored(Compose * compose,gchar ** text)3824 static gint compose_encrypt_armored(Compose *compose, gchar **text)
3825 {
3826 gchar *tmp_file;
3827 GSList *recp_list;
3828
3829 tmp_file = get_tmp_file();
3830 if (str_write_to_file(*text, tmp_file) < 0) {
3831 g_free(tmp_file);
3832 return -1;
3833 }
3834
3835 recp_list = compose_create_encrypt_recipients_list(compose);
3836
3837 if (rfc2015_encrypt_armored(tmp_file, recp_list) < 0) {
3838 alertpanel_error(_("Can't encrypt the message."));
3839 g_slist_free(recp_list);
3840 g_unlink(tmp_file);
3841 g_free(tmp_file);
3842 return -1;
3843 }
3844
3845 g_slist_free(recp_list);
3846 g_free(*text);
3847 *text = file_read_to_str(tmp_file);
3848 g_unlink(tmp_file);
3849 g_free(tmp_file);
3850 if (*text == NULL)
3851 return -1;
3852
3853 return 0;
3854 }
3855
compose_encrypt_sign_armored(Compose * compose,gchar ** text)3856 static gint compose_encrypt_sign_armored(Compose *compose, gchar **text)
3857 {
3858 GSList *key_list;
3859 GSList *recp_list;
3860 gchar *tmp_file;
3861
3862 tmp_file = get_tmp_file();
3863 if (str_write_to_file(*text, tmp_file) < 0) {
3864 g_free(tmp_file);
3865 return -1;
3866 }
3867
3868 if (compose_create_signers_list(compose, &key_list) < 0) {
3869 g_unlink(tmp_file);
3870 g_free(tmp_file);
3871 return -1;
3872 }
3873
3874 recp_list = compose_create_encrypt_recipients_list(compose);
3875
3876 if (rfc2015_encrypt_sign_armored(tmp_file, recp_list, key_list) < 0) {
3877 alertpanel_error(_("Can't encrypt or sign the message."));
3878 g_slist_free(recp_list);
3879 g_unlink(tmp_file);
3880 g_free(tmp_file);
3881 return -1;
3882 }
3883
3884 g_slist_free(recp_list);
3885 g_free(*text);
3886 *text = file_read_to_str(tmp_file);
3887 g_unlink(tmp_file);
3888 g_free(tmp_file);
3889 if (*text == NULL)
3890 return -1;
3891
3892 return 0;
3893 }
3894 #endif /* USE_GPGME */
3895
compose_write_to_file(Compose * compose,const gchar * file,gboolean is_draft)3896 static gint compose_write_to_file(Compose *compose, const gchar *file,
3897 gboolean is_draft)
3898 {
3899 GtkTextBuffer *buffer;
3900 GtkTextIter start, end;
3901 GtkTreeModel *model = GTK_TREE_MODEL(compose->attach_store);
3902 FILE *fp;
3903 size_t len;
3904 gchar *chars;
3905 gchar *buf;
3906 gchar *canon_buf;
3907 const gchar *out_charset;
3908 const gchar *body_charset;
3909 const gchar *src_charset = CS_INTERNAL;
3910 EncodingType encoding;
3911 gint line;
3912 #if USE_GPGME
3913 gboolean use_pgpmime_encryption = FALSE;
3914 gboolean use_pgpmime_signing = FALSE;
3915 #endif
3916
3917 if ((fp = g_fopen(file, "wb")) == NULL) {
3918 FILE_OP_ERROR(file, "fopen");
3919 return -1;
3920 }
3921
3922 /* chmod for security */
3923 if (change_file_mode_rw(fp, file) < 0) {
3924 FILE_OP_ERROR(file, "chmod");
3925 g_warning(_("can't change file mode\n"));
3926 }
3927
3928 /* get outgoing charset */
3929 out_charset = conv_get_charset_str(compose->out_encoding);
3930 if (!out_charset)
3931 out_charset = conv_get_outgoing_charset_str();
3932 if (!g_ascii_strcasecmp(out_charset, CS_US_ASCII))
3933 out_charset = CS_ISO_8859_1;
3934 body_charset = out_charset;
3935
3936 /* get all composed text */
3937 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
3938 gtk_text_buffer_get_start_iter(buffer, &start);
3939 gtk_text_buffer_get_end_iter(buffer, &end);
3940 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
3941 if (is_ascii_str(chars)) {
3942 buf = chars;
3943 chars = NULL;
3944 body_charset = CS_US_ASCII;
3945 encoding = ENC_7BIT;
3946 } else {
3947 gint error = 0;
3948
3949 buf = conv_codeset_strdup_full
3950 (chars, src_charset, body_charset, &error);
3951 if (!buf || error != 0) {
3952 AlertValue aval = G_ALERTDEFAULT;
3953 gchar *msg;
3954
3955 g_free(buf);
3956
3957 if (!is_draft) {
3958 msg = g_strdup_printf(_("Can't convert the character encoding of the message body from %s to %s.\n"
3959 "\n"
3960 "Send it as %s anyway?"),
3961 src_charset, body_charset,
3962 src_charset);
3963 aval = alertpanel_full
3964 (_("Code conversion error"), msg, ALERT_ERROR,
3965 G_ALERTALTERNATE,
3966 FALSE, GTK_STOCK_YES, GTK_STOCK_NO, NULL);
3967 g_free(msg);
3968 }
3969
3970 if (aval != G_ALERTDEFAULT) {
3971 g_free(chars);
3972 fclose(fp);
3973 g_unlink(file);
3974 return -1;
3975 } else {
3976 buf = chars;
3977 out_charset = body_charset = src_charset;
3978 chars = NULL;
3979 }
3980 }
3981
3982 if (prefs_common.encoding_method == CTE_BASE64)
3983 encoding = ENC_BASE64;
3984 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
3985 encoding = ENC_QUOTED_PRINTABLE;
3986 else if (prefs_common.encoding_method == CTE_8BIT)
3987 encoding = ENC_8BIT;
3988 else
3989 encoding = procmime_get_encoding_for_charset
3990 (body_charset);
3991 }
3992 g_free(chars);
3993
3994 canon_buf = canonicalize_str(buf);
3995 g_free(buf);
3996 buf = canon_buf;
3997
3998 #if USE_GPGME
3999 if (compose->use_signing && !compose->account->clearsign)
4000 use_pgpmime_signing = TRUE;
4001 if (compose->use_encryption && compose->account->ascii_armored) {
4002 use_pgpmime_encryption = FALSE;
4003 use_pgpmime_signing = FALSE;
4004 }
4005 if (compose->use_encryption && !compose->account->ascii_armored)
4006 use_pgpmime_encryption = TRUE;
4007
4008 /* protect trailing spaces */
4009 if (rfc2015_is_available() && !is_draft && use_pgpmime_signing) {
4010 if (encoding == ENC_7BIT) {
4011 if (!g_ascii_strcasecmp(body_charset, CS_ISO_2022_JP)) {
4012 gchar *tmp;
4013 tmp = strchomp_all(buf);
4014 g_free(buf);
4015 buf = tmp;
4016 } else
4017 encoding = ENC_QUOTED_PRINTABLE;
4018 } else if (encoding == ENC_8BIT) {
4019 encoding = procmime_get_encoding_for_str(buf);
4020 if (encoding == ENC_7BIT)
4021 encoding = ENC_QUOTED_PRINTABLE;
4022 }
4023 }
4024
4025 if (rfc2015_is_available() && !is_draft) {
4026 if ((compose->use_encryption &&
4027 compose->account->ascii_armored) ||
4028 (compose->use_signing && compose->account->clearsign)) {
4029 /* MIME encoding doesn't fit with cleartext signature */
4030 if (encoding == ENC_QUOTED_PRINTABLE || encoding == ENC_BASE64)
4031 encoding = ENC_8BIT;
4032
4033 }
4034 }
4035 #endif
4036
4037 debug_print("src encoding = %s, out encoding = %s, "
4038 "body encoding = %s, transfer encoding = %s\n",
4039 src_charset, out_charset, body_charset,
4040 procmime_get_encoding_str(encoding));
4041
4042 /* check for line length limit */
4043 if (!is_draft &&
4044 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
4045 check_line_length(buf, 1000, &line) < 0) {
4046 AlertValue aval;
4047 gchar *msg;
4048
4049 msg = g_strdup_printf
4050 (_("Line %d exceeds the line length limit (998 bytes).\n"
4051 "The contents of the message might be broken on the way to the delivery.\n"
4052 "\n"
4053 "Send it anyway?"), line + 1);
4054 aval = alertpanel_full(_("Line length limit"),
4055 msg, ALERT_WARNING,
4056 G_ALERTALTERNATE, FALSE,
4057 GTK_STOCK_YES, GTK_STOCK_NO, NULL);
4058 if (aval != G_ALERTDEFAULT) {
4059 g_free(msg);
4060 fclose(fp);
4061 g_unlink(file);
4062 g_free(buf);
4063 return -1;
4064 }
4065 }
4066
4067 /* write headers */
4068 if (compose_write_headers(compose, fp, out_charset,
4069 body_charset, encoding, is_draft) < 0) {
4070 g_warning("can't write headers\n");
4071 fclose(fp);
4072 g_unlink(file);
4073 g_free(buf);
4074 return -1;
4075 }
4076
4077 #if USE_GPGME
4078 /* do ascii-armor encryption and/or clearsign */
4079 if (rfc2015_is_available() && !is_draft) {
4080 gint ret;
4081
4082 if (compose->use_encryption && compose->account->ascii_armored) {
4083 if (compose->use_signing)
4084 ret = compose_encrypt_sign_armored(compose, &buf);
4085 else
4086 ret = compose_encrypt_armored(compose, &buf);
4087 if (ret < 0) {
4088 g_warning("ascii-armored encryption failed\n");
4089 fclose(fp);
4090 g_unlink(file);
4091 g_free(buf);
4092 return -1;
4093 }
4094 } else if (compose->use_signing && compose->account->clearsign) {
4095 if (compose_clearsign_text(compose, &buf) < 0) {
4096 g_warning("clearsign failed\n");
4097 fclose(fp);
4098 g_unlink(file);
4099 g_free(buf);
4100 return -1;
4101 }
4102 }
4103 }
4104 #endif
4105
4106 if (compose->use_attach &&
4107 gtk_tree_model_iter_n_children(model, NULL) > 0) {
4108 #if USE_GPGME
4109 /* This prolog message is ignored by mime software and
4110 * because it would make our signing/encryption task
4111 * tougher, we don't emit it in that case */
4112 if (!rfc2015_is_available() ||
4113 (!compose->use_signing && !compose->use_encryption))
4114 #endif
4115 fputs("This is a multi-part message in MIME format.\n", fp);
4116
4117 fprintf(fp, "\n--%s\n", compose->boundary);
4118 fprintf(fp, "Content-Type: text/plain; charset=%s\n",
4119 body_charset);
4120 #if USE_GPGME
4121 if (rfc2015_is_available() && use_pgpmime_signing)
4122 fprintf(fp, "Content-Disposition: inline\n");
4123 #endif
4124 fprintf(fp, "Content-Transfer-Encoding: %s\n",
4125 procmime_get_encoding_str(encoding));
4126 fputc('\n', fp);
4127 }
4128
4129 /* write body */
4130 len = strlen(buf);
4131 if (encoding == ENC_BASE64) {
4132 gchar outbuf[B64_BUFFSIZE];
4133 gint i, l;
4134
4135 for (i = 0; i < len; i += B64_LINE_SIZE) {
4136 l = MIN(B64_LINE_SIZE, len - i);
4137 base64_encode(outbuf, (guchar *)buf + i, l);
4138 fputs(outbuf, fp);
4139 fputc('\n', fp);
4140 }
4141 } else if (encoding == ENC_QUOTED_PRINTABLE) {
4142 gchar *outbuf;
4143 size_t outlen;
4144
4145 outbuf = g_malloc(len * 4);
4146 qp_encode_line(outbuf, (guchar *)buf);
4147 outlen = strlen(outbuf);
4148 if (fwrite(outbuf, sizeof(gchar), outlen, fp) != outlen) {
4149 FILE_OP_ERROR(file, "fwrite");
4150 fclose(fp);
4151 g_unlink(file);
4152 g_free(outbuf);
4153 g_free(buf);
4154 return -1;
4155 }
4156 g_free(outbuf);
4157 } else if (fwrite(buf, sizeof(gchar), len, fp) != len) {
4158 FILE_OP_ERROR(file, "fwrite");
4159 fclose(fp);
4160 g_unlink(file);
4161 g_free(buf);
4162 return -1;
4163 }
4164 g_free(buf);
4165
4166 if (compose->use_attach &&
4167 gtk_tree_model_iter_n_children(model, NULL) > 0) {
4168 if (compose_write_attach(compose, fp, out_charset) < 0) {
4169 fclose(fp);
4170 g_unlink(file);
4171 return -1;
4172 }
4173 }
4174
4175 if (fclose(fp) == EOF) {
4176 FILE_OP_ERROR(file, "fclose");
4177 g_unlink(file);
4178 return -1;
4179 }
4180
4181 #if USE_GPGME
4182 if (!rfc2015_is_available() || is_draft) {
4183 uncanonicalize_file_replace(file);
4184 return 0;
4185 }
4186
4187 if (use_pgpmime_signing || use_pgpmime_encryption) {
4188 if (canonicalize_file_replace(file) < 0) {
4189 g_unlink(file);
4190 return -1;
4191 }
4192 }
4193
4194 if (use_pgpmime_signing && !use_pgpmime_encryption) {
4195 GSList *key_list;
4196
4197 if (compose_create_signers_list(compose, &key_list) < 0) {
4198 g_unlink(file);
4199 return -1;
4200 }
4201 if (rfc2015_sign(file, key_list) < 0) {
4202 alertpanel_error(_("Can't sign the message."));
4203 g_unlink(file);
4204 return -1;
4205 }
4206 } else if (use_pgpmime_encryption) {
4207 GSList *key_list;
4208 GSList *recp_list;
4209
4210 if (compose->use_bcc) {
4211 const gchar *text;
4212 gchar *bcc;
4213 AlertValue aval;
4214
4215 text = gtk_entry_get_text(GTK_ENTRY(compose->bcc_entry));
4216 if (*text != '\0') {
4217 bcc = g_strdup(text);
4218 g_strstrip(bcc);
4219 if (*bcc != '\0') {
4220 aval = alertpanel_full
4221 (_("Encrypting with Bcc"),
4222 _("This message has Bcc recipients. If this message is encrypted, all Bcc recipients will be visible by examing the encryption key list, leading to loss of confidentiality.\n"
4223 "\n"
4224 "Send it anyway?"),
4225 ALERT_WARNING, G_ALERTDEFAULT, FALSE,
4226 GTK_STOCK_YES, GTK_STOCK_NO, NULL);
4227 if (aval != G_ALERTDEFAULT) {
4228 g_free(bcc);
4229 g_unlink(file);
4230 return -1;
4231 }
4232 }
4233 g_free(bcc);
4234 }
4235 }
4236
4237 recp_list = compose_create_encrypt_recipients_list(compose);
4238
4239 if (use_pgpmime_signing) {
4240 if (compose_create_signers_list
4241 (compose, &key_list) < 0) {
4242 g_unlink(file);
4243 return -1;
4244 }
4245
4246 if (rfc2015_encrypt_sign(file, recp_list, key_list) < 0) {
4247 alertpanel_error(_("Can't encrypt or sign the message."));
4248 g_slist_free(recp_list);
4249 g_unlink(file);
4250 return -1;
4251 }
4252 } else if (rfc2015_encrypt(file, recp_list) < 0) {
4253 alertpanel_error(_("Can't encrypt the message."));
4254 g_slist_free(recp_list);
4255 g_unlink(file);
4256 return -1;
4257 }
4258
4259 g_slist_free(recp_list);
4260 }
4261 #endif /* USE_GPGME */
4262
4263 uncanonicalize_file_replace(file);
4264
4265 return 0;
4266 }
4267
compose_write_body_to_file(Compose * compose,const gchar * file)4268 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
4269 {
4270 GtkTextBuffer *buffer;
4271 GtkTextIter start, end;
4272 FILE *fp;
4273 size_t len;
4274 gchar *chars, *tmp;
4275
4276 if ((fp = g_fopen(file, "wb")) == NULL) {
4277 FILE_OP_ERROR(file, "fopen");
4278 return -1;
4279 }
4280
4281 /* chmod for security */
4282 if (change_file_mode_rw(fp, file) < 0) {
4283 FILE_OP_ERROR(file, "chmod");
4284 g_warning(_("can't change file mode\n"));
4285 }
4286
4287 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
4288 gtk_text_buffer_get_start_iter(buffer, &start);
4289 gtk_text_buffer_get_end_iter(buffer, &end);
4290 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
4291
4292 chars = conv_codeset_strdup
4293 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
4294
4295 g_free(tmp);
4296
4297 if (!chars) {
4298 fclose(fp);
4299 g_unlink(file);
4300 return -1;
4301 }
4302
4303 /* write body */
4304 len = strlen(chars);
4305 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
4306 FILE_OP_ERROR(file, "fwrite");
4307 g_free(chars);
4308 fclose(fp);
4309 g_unlink(file);
4310 return -1;
4311 }
4312
4313 g_free(chars);
4314
4315 if (fclose(fp) == EOF) {
4316 FILE_OP_ERROR(file, "fclose");
4317 g_unlink(file);
4318 return -1;
4319 }
4320 return 0;
4321 }
4322
compose_redirect_write_to_file(Compose * compose,const gchar * file)4323 static gint compose_redirect_write_to_file(Compose *compose, const gchar *file)
4324 {
4325 FILE *fp;
4326 FILE *fdest;
4327 size_t len;
4328 gchar buf[BUFFSIZE];
4329
4330 g_return_val_if_fail(file != NULL, -1);
4331 g_return_val_if_fail(compose->account != NULL, -1);
4332 g_return_val_if_fail(compose->account->address != NULL, -1);
4333 g_return_val_if_fail(compose->mode == COMPOSE_REDIRECT, -1);
4334 g_return_val_if_fail(compose->targetinfo != NULL, -1);
4335
4336 if ((fp = procmsg_open_message(compose->targetinfo)) == NULL)
4337 return -1;
4338
4339 if ((fdest = g_fopen(file, "wb")) == NULL) {
4340 FILE_OP_ERROR(file, "fopen");
4341 fclose(fp);
4342 return -1;
4343 }
4344
4345 if (change_file_mode_rw(fdest, file) < 0) {
4346 FILE_OP_ERROR(file, "chmod");
4347 g_warning(_("can't change file mode\n"));
4348 }
4349
4350 while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) == 0) {
4351 if (g_ascii_strncasecmp(buf, "Return-Path:",
4352 strlen("Return-Path:")) == 0 ||
4353 g_ascii_strncasecmp(buf, "Delivered-To:",
4354 strlen("Delivered-To:")) == 0 ||
4355 g_ascii_strncasecmp(buf, "Received:",
4356 strlen("Received:")) == 0 ||
4357 g_ascii_strncasecmp(buf, "Subject:",
4358 strlen("Subject:")) == 0 ||
4359 g_ascii_strncasecmp(buf, "X-UIDL:",
4360 strlen("X-UIDL:")) == 0)
4361 continue;
4362
4363 if (fputs(buf, fdest) == EOF)
4364 goto error;
4365
4366 #if 0
4367 if (g_ascii_strncasecmp(buf, "From:", strlen("From:")) == 0) {
4368 fputs("\n (by way of ", fdest);
4369 if (compose->account->name) {
4370 compose_convert_header(compose,
4371 buf, sizeof(buf),
4372 compose->account->name,
4373 strlen(" (by way of "),
4374 FALSE, NULL);
4375 fprintf(fdest, "%s <%s>", buf,
4376 compose->account->address);
4377 } else
4378 fputs(compose->account->address, fdest);
4379 fputs(")", fdest);
4380 }
4381 #endif
4382
4383 if (fputs("\n", fdest) == EOF)
4384 goto error;
4385 }
4386
4387 compose_redirect_write_headers(compose, fdest);
4388
4389 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
4390 if (fwrite(buf, sizeof(gchar), len, fdest) != len) {
4391 FILE_OP_ERROR(file, "fwrite");
4392 goto error;
4393 }
4394 }
4395
4396 fclose(fp);
4397 if (fclose(fdest) == EOF) {
4398 FILE_OP_ERROR(file, "fclose");
4399 g_unlink(file);
4400 return -1;
4401 }
4402
4403 return 0;
4404 error:
4405 fclose(fp);
4406 fclose(fdest);
4407 g_unlink(file);
4408
4409 return -1;
4410 }
4411
compose_remove_reedit_target(Compose * compose)4412 static gint compose_remove_reedit_target(Compose *compose)
4413 {
4414 FolderItem *item;
4415 MsgInfo *msginfo = compose->targetinfo;
4416
4417 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
4418 if (!msginfo) return -1;
4419
4420 item = msginfo->folder;
4421 g_return_val_if_fail(item != NULL, -1);
4422
4423 folder_item_scan(item);
4424 if (procmsg_msg_exist(msginfo) &&
4425 (item->stype == F_DRAFT || item->stype == F_QUEUE)) {
4426 if (folder_item_remove_msg(item, msginfo) < 0) {
4427 g_warning(_("can't remove the old message\n"));
4428 return -1;
4429 }
4430 }
4431
4432 return 0;
4433 }
4434
compose_queue(Compose * compose,const gchar * file)4435 static gint compose_queue(Compose *compose, const gchar *file)
4436 {
4437 FolderItem *queue;
4438 gchar *tmp;
4439 FILE *fp, *src_fp;
4440 GSList *cur;
4441 gchar buf[BUFFSIZE];
4442 gint num;
4443 MsgFlags flag = {0, MSG_QUEUED};
4444
4445 debug_print(_("queueing message...\n"));
4446 g_return_val_if_fail(compose->to_list != NULL ||
4447 compose->newsgroup_list != NULL,
4448 -1);
4449 g_return_val_if_fail(compose->account != NULL, -1);
4450
4451 tmp = g_strdup_printf("%s%cqueue.%p", get_tmp_dir(),
4452 G_DIR_SEPARATOR, compose);
4453 if ((fp = g_fopen(tmp, "wb")) == NULL) {
4454 FILE_OP_ERROR(tmp, "fopen");
4455 g_free(tmp);
4456 return -1;
4457 }
4458 if ((src_fp = g_fopen(file, "rb")) == NULL) {
4459 FILE_OP_ERROR(file, "fopen");
4460 fclose(fp);
4461 g_unlink(tmp);
4462 g_free(tmp);
4463 return -1;
4464 }
4465 if (change_file_mode_rw(fp, tmp) < 0) {
4466 FILE_OP_ERROR(tmp, "chmod");
4467 g_warning(_("can't change file mode\n"));
4468 }
4469
4470 /* queueing variables */
4471 fprintf(fp, "AF:\n");
4472 fprintf(fp, "NF:0\n");
4473 fprintf(fp, "PS:10\n");
4474 fprintf(fp, "SRH:1\n");
4475 fprintf(fp, "SFN:\n");
4476 fprintf(fp, "DSR:\n");
4477 if (compose->msgid)
4478 fprintf(fp, "MID:<%s>\n", compose->msgid);
4479 else
4480 fprintf(fp, "MID:\n");
4481 fprintf(fp, "CFG:\n");
4482 fprintf(fp, "PT:0\n");
4483 fprintf(fp, "S:%s\n", compose->account->address);
4484 fprintf(fp, "RQ:\n");
4485 if (compose->account->smtp_server)
4486 fprintf(fp, "SSV:%s\n", compose->account->smtp_server);
4487 else
4488 fprintf(fp, "SSV:\n");
4489 if (compose->account->nntp_server)
4490 fprintf(fp, "NSV:%s\n", compose->account->nntp_server);
4491 else
4492 fprintf(fp, "NSV:\n");
4493 fprintf(fp, "SSH:\n");
4494 if (compose->to_list) {
4495 fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data);
4496 for (cur = compose->to_list->next; cur != NULL;
4497 cur = cur->next)
4498 fprintf(fp, ",<%s>", (gchar *)cur->data);
4499 fprintf(fp, "\n");
4500 } else
4501 fprintf(fp, "R:\n");
4502 /* Sylpheed account ID */
4503 fprintf(fp, "AID:%d\n", compose->account->account_id);
4504 /* Reply target */
4505 if (compose->reply_target)
4506 fprintf(fp, "REP:%s\n", compose->reply_target);
4507 /* Forward target */
4508 if (compose->forward_targets)
4509 fprintf(fp, "FWD:%s\n", compose->forward_targets);
4510 fprintf(fp, "\n");
4511
4512 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
4513 if (fputs(buf, fp) == EOF) {
4514 FILE_OP_ERROR(tmp, "fputs");
4515 fclose(fp);
4516 fclose(src_fp);
4517 g_unlink(tmp);
4518 g_free(tmp);
4519 return -1;
4520 }
4521 }
4522
4523 fclose(src_fp);
4524 if (fclose(fp) == EOF) {
4525 FILE_OP_ERROR(tmp, "fclose");
4526 g_unlink(tmp);
4527 g_free(tmp);
4528 return -1;
4529 }
4530
4531 queue = account_get_special_folder(compose->account, F_QUEUE);
4532 if (!queue) {
4533 g_warning(_("can't find queue folder\n"));
4534 g_unlink(tmp);
4535 g_free(tmp);
4536 return -1;
4537 }
4538 folder_item_scan(queue);
4539 if ((num = folder_item_add_msg(queue, tmp, &flag, TRUE)) < 0) {
4540 g_warning(_("can't queue the message\n"));
4541 g_unlink(tmp);
4542 g_free(tmp);
4543 return -1;
4544 }
4545 g_free(tmp);
4546
4547 if (compose->mode == COMPOSE_REEDIT) {
4548 compose_remove_reedit_target(compose);
4549 if (compose->targetinfo &&
4550 compose->targetinfo->folder != queue)
4551 folderview_update_item
4552 (compose->targetinfo->folder, TRUE);
4553 }
4554
4555 folder_item_scan(queue);
4556 folderview_update_item(queue, TRUE);
4557
4558 /* Add recipients to addressbook automatically */
4559 if (prefs_common.recipients_autoreg) {
4560 compose_add_new_recipients_to_addressbook(compose);
4561 }
4562
4563 main_window_set_menu_sensitive(main_window_get());
4564 main_window_set_toolbar_sensitive(main_window_get());
4565
4566 return 0;
4567 }
4568
compose_write_attach(Compose * compose,FILE * fp,const gchar * charset)4569 static gint compose_write_attach(Compose *compose, FILE *fp,
4570 const gchar *charset)
4571 {
4572 GtkTreeModel *model = GTK_TREE_MODEL(compose->attach_store);
4573 GtkTreeIter iter;
4574 gboolean valid;
4575 AttachInfo *ainfo;
4576 FILE *attach_fp;
4577 gint len;
4578 EncodingType encoding;
4579 ContentType content_type;
4580 gchar *tmp_file = NULL;
4581 const gchar *src_file;
4582 FILE *src_fp;
4583 FILE *tmp_fp = NULL;
4584
4585 for (valid = gtk_tree_model_get_iter_first(model, &iter); valid;
4586 valid = gtk_tree_model_iter_next(model, &iter)) {
4587 gtk_tree_model_get(model, &iter, COL_ATTACH_INFO, &ainfo, -1);
4588
4589 if (!is_file_exist(ainfo->file)) {
4590 alertpanel_error(_("File %s doesn't exist."),
4591 ainfo->file);
4592 return -1;
4593 }
4594 if (get_file_size(ainfo->file) <= 0) {
4595 alertpanel_error(_("File %s is empty."), ainfo->file);
4596 return -1;
4597 }
4598 if ((attach_fp = g_fopen(ainfo->file, "rb")) == NULL) {
4599 alertpanel_error(_("Can't open file %s."), ainfo->file);
4600 return -1;
4601 }
4602
4603 fprintf(fp, "\n--%s\n", compose->boundary);
4604
4605 encoding = ainfo->encoding;
4606
4607 if (!g_ascii_strncasecmp(ainfo->content_type, "message/", 8)) {
4608 fprintf(fp, "Content-Type: %s\n", ainfo->content_type);
4609 fprintf(fp, "Content-Disposition: inline\n");
4610
4611 /* message/... shouldn't be encoded */
4612 if (encoding == ENC_QUOTED_PRINTABLE ||
4613 encoding == ENC_BASE64)
4614 encoding = ENC_8BIT;
4615 } else {
4616 if (prefs_common.mime_fencoding_method ==
4617 FENC_RFC2231) {
4618 gchar *param;
4619
4620 param = compose_convert_filename
4621 (compose, ainfo->name, "name", charset);
4622 fprintf(fp, "Content-Type: %s;\n"
4623 "%s\n",
4624 ainfo->content_type, param);
4625 g_free(param);
4626 param = compose_convert_filename
4627 (compose, ainfo->name, "filename",
4628 charset);
4629 fprintf(fp, "Content-Disposition: attachment;\n"
4630 "%s\n", param);
4631 g_free(param);
4632 } else {
4633 gchar filename[BUFFSIZE];
4634
4635 compose_convert_header(compose, filename,
4636 sizeof(filename),
4637 ainfo->name, 12, FALSE,
4638 charset);
4639 fprintf(fp, "Content-Type: %s;\n"
4640 " name=\"%s\"\n",
4641 ainfo->content_type, filename);
4642 fprintf(fp, "Content-Disposition: attachment;\n"
4643 " filename=\"%s\"\n", filename);
4644 }
4645
4646 #if USE_GPGME
4647 /* force encoding to protect trailing spaces */
4648 if (rfc2015_is_available() && compose->use_signing &&
4649 !compose->account->clearsign) {
4650 if (encoding == ENC_7BIT)
4651 encoding = ENC_QUOTED_PRINTABLE;
4652 else if (encoding == ENC_8BIT)
4653 encoding = ENC_BASE64;
4654 }
4655 #endif
4656 }
4657
4658 fprintf(fp, "Content-Transfer-Encoding: %s\n\n",
4659 procmime_get_encoding_str(encoding));
4660
4661 content_type = procmime_scan_mime_type(ainfo->content_type);
4662
4663 if (content_type == MIME_TEXT || content_type == MIME_TEXT_HTML) {
4664 CharSet enc;
4665
4666 enc = conv_check_file_encoding(ainfo->file);
4667 if (enc == C_UTF_16 || enc == C_UTF_16BE || enc == C_UTF_16LE) {
4668 tmp_file = get_tmp_file();
4669 if (conv_copy_file(ainfo->file, tmp_file, conv_get_charset_str(enc)) < 0) {
4670 g_warning("compose_write_attach: Cannot convert UTF-16 file %s to UTF-8", ainfo->file);
4671 g_free(tmp_file);
4672 tmp_file = NULL;
4673 }
4674 }
4675 }
4676
4677 if (tmp_file) {
4678 src_file = tmp_file;
4679 } else {
4680 src_file = ainfo->file;
4681 }
4682
4683 if (encoding == ENC_BASE64) {
4684 gchar inbuf[B64_LINE_SIZE], outbuf[B64_BUFFSIZE];
4685 gchar *canon_file = NULL;
4686
4687 if (content_type == MIME_TEXT ||
4688 content_type == MIME_TEXT_HTML ||
4689 content_type == MIME_MESSAGE_RFC822) {
4690 canon_file = get_tmp_file();
4691 if (canonicalize_file(src_file, canon_file) < 0) {
4692 g_free(canon_file);
4693 if (tmp_file) {
4694 g_unlink(tmp_file);
4695 g_free(tmp_file);
4696 }
4697 fclose(attach_fp);
4698 return -1;
4699 }
4700 if ((tmp_fp = g_fopen(canon_file, "rb")) == NULL) {
4701 FILE_OP_ERROR(canon_file, "fopen");
4702 g_unlink(canon_file);
4703 g_free(canon_file);
4704 if (tmp_file) {
4705 g_unlink(tmp_file);
4706 g_free(tmp_file);
4707 }
4708 fclose(attach_fp);
4709 return -1;
4710 }
4711 }
4712
4713 if (tmp_fp) {
4714 src_fp = tmp_fp;
4715 } else {
4716 src_fp = attach_fp;
4717 }
4718
4719 while ((len = fread(inbuf, sizeof(gchar),
4720 B64_LINE_SIZE, src_fp))
4721 == B64_LINE_SIZE) {
4722 base64_encode(outbuf, (guchar *)inbuf,
4723 B64_LINE_SIZE);
4724 fputs(outbuf, fp);
4725 fputc('\n', fp);
4726 }
4727 if (len > 0 && feof(src_fp)) {
4728 base64_encode(outbuf, (guchar *)inbuf, len);
4729 fputs(outbuf, fp);
4730 fputc('\n', fp);
4731 }
4732
4733 if (tmp_fp) {
4734 fclose(tmp_fp);
4735 tmp_fp = NULL;
4736 }
4737 if (canon_file) {
4738 g_unlink(canon_file);
4739 g_free(canon_file);
4740 }
4741 } else {
4742 if (tmp_file) {
4743 if ((tmp_fp = g_fopen(tmp_file, "rb")) == NULL) {
4744 FILE_OP_ERROR(tmp_file, "fopen");
4745 g_unlink(tmp_file);
4746 g_free(tmp_file);
4747 fclose(attach_fp);
4748 return -1;
4749 }
4750 src_fp = tmp_fp;
4751 } else {
4752 src_fp = attach_fp;
4753 }
4754
4755 if (encoding == ENC_QUOTED_PRINTABLE) {
4756 gchar inbuf[BUFFSIZE], outbuf[BUFFSIZE * 4];
4757
4758 while (fgets(inbuf, sizeof(inbuf), src_fp) != NULL) {
4759 qp_encode_line(outbuf, (guchar *)inbuf);
4760 fputs(outbuf, fp);
4761 }
4762 } else {
4763 gchar buf[BUFFSIZE];
4764
4765 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
4766 strcrchomp(buf);
4767 fputs(buf, fp);
4768 }
4769 }
4770
4771 if (tmp_fp) {
4772 fclose(tmp_fp);
4773 tmp_fp = NULL;
4774 }
4775 }
4776
4777 if (tmp_file) {
4778 g_unlink(tmp_file);
4779 g_free(tmp_file);
4780 tmp_file = NULL;
4781 }
4782 fclose(attach_fp);
4783 }
4784
4785 fprintf(fp, "\n--%s--\n", compose->boundary);
4786 return 0;
4787 }
4788
4789 #define QUOTE_REQUIRED(str) \
4790 (*str != '"' && strpbrk(str, ",.[]<>") != NULL)
4791
4792 #define PUT_RECIPIENT_HEADER(header, str) \
4793 { \
4794 if (*str != '\0') { \
4795 gchar *dest; \
4796 \
4797 dest = g_strdup(str); \
4798 g_strstrip(dest); \
4799 if (*dest != '\0') { \
4800 compose->to_list = address_list_append \
4801 (compose->to_list, dest); \
4802 compose_convert_header \
4803 (compose, buf, sizeof(buf), dest, \
4804 strlen(header) + 2, TRUE, charset); \
4805 fprintf(fp, "%s: %s\n", header, buf); \
4806 } \
4807 g_free(dest); \
4808 } \
4809 }
4810
4811 #define IS_IN_CUSTOM_HEADER(header) \
4812 (compose->account->add_customhdr && \
4813 custom_header_find(compose->account->customhdr_list, header) != NULL)
4814
compose_write_headers(Compose * compose,FILE * fp,const gchar * charset,const gchar * body_charset,EncodingType encoding,gboolean is_draft)4815 static gint compose_write_headers(Compose *compose, FILE *fp,
4816 const gchar *charset,
4817 const gchar *body_charset,
4818 EncodingType encoding, gboolean is_draft)
4819 {
4820 gchar buf[BUFFSIZE];
4821 const gchar *entry_str;
4822 gchar *str;
4823
4824 g_return_val_if_fail(fp != NULL, -1);
4825 g_return_val_if_fail(charset != NULL, -1);
4826 g_return_val_if_fail(compose->account != NULL, -1);
4827 g_return_val_if_fail(compose->account->address != NULL, -1);
4828
4829 /* Date */
4830 if (compose->account->add_date) {
4831 get_rfc822_date(buf, sizeof(buf));
4832 fprintf(fp, "Date: %s\n", buf);
4833 }
4834
4835 /* From */
4836 if (compose->account->name && *compose->account->name) {
4837 compose_convert_header
4838 (compose, buf, sizeof(buf), compose->account->name,
4839 strlen("From: "), TRUE, charset);
4840 if (QUOTE_REQUIRED(buf))
4841 fprintf(fp, "From: \"%s\" <%s>\n",
4842 buf, compose->account->address);
4843 else
4844 fprintf(fp, "From: %s <%s>\n",
4845 buf, compose->account->address);
4846 } else
4847 fprintf(fp, "From: %s\n", compose->account->address);
4848
4849 slist_free_strings(compose->to_list);
4850 g_slist_free(compose->to_list);
4851 compose->to_list = NULL;
4852
4853 /* To */
4854 if (compose->use_to) {
4855 entry_str = gtk_entry_get_text(GTK_ENTRY(compose->to_entry));
4856 PUT_RECIPIENT_HEADER("To", entry_str);
4857 }
4858
4859 slist_free_strings(compose->newsgroup_list);
4860 g_slist_free(compose->newsgroup_list);
4861 compose->newsgroup_list = NULL;
4862
4863 /* Newsgroups */
4864 if (compose->use_newsgroups) {
4865 entry_str = gtk_entry_get_text
4866 (GTK_ENTRY(compose->newsgroups_entry));
4867 if (*entry_str != '\0') {
4868 str = g_strdup(entry_str);
4869 g_strstrip(str);
4870 remove_space(str);
4871 if (*str != '\0') {
4872 compose->newsgroup_list =
4873 newsgroup_list_append
4874 (compose->newsgroup_list, str);
4875 compose_convert_header(compose,
4876 buf, sizeof(buf), str,
4877 strlen("Newsgroups: "),
4878 FALSE, charset);
4879 fprintf(fp, "Newsgroups: %s\n", buf);
4880 }
4881 g_free(str);
4882 }
4883 }
4884
4885 /* Cc */
4886 if (compose->use_cc) {
4887 entry_str = gtk_entry_get_text(GTK_ENTRY(compose->cc_entry));
4888 PUT_RECIPIENT_HEADER("Cc", entry_str);
4889 }
4890
4891 /* Bcc */
4892 if (compose->use_bcc) {
4893 entry_str = gtk_entry_get_text(GTK_ENTRY(compose->bcc_entry));
4894 PUT_RECIPIENT_HEADER("Bcc", entry_str);
4895 }
4896
4897 if (!is_draft && !compose->to_list && !compose->newsgroup_list)
4898 return -1;
4899
4900 /* Subject */
4901 entry_str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4902 if (*entry_str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
4903 str = g_strdup(entry_str);
4904 g_strstrip(str);
4905 if (*str != '\0') {
4906 compose_convert_header(compose, buf, sizeof(buf), str,
4907 strlen("Subject: "), FALSE,
4908 charset);
4909 fprintf(fp, "Subject: %s\n", buf);
4910 }
4911 g_free(str);
4912 }
4913
4914 /* Message-ID */
4915 if (compose->account->gen_msgid) {
4916 compose_generate_msgid(compose, buf, sizeof(buf));
4917 fprintf(fp, "Message-Id: <%s>\n", buf);
4918 compose->msgid = g_strdup(buf);
4919 }
4920
4921 /* In-Reply-To */
4922 if (compose->inreplyto && compose->to_list)
4923 fprintf(fp, "In-Reply-To: <%s>\n", compose->inreplyto);
4924
4925 /* References */
4926 if (compose->references)
4927 fprintf(fp, "References: %s\n", compose->references);
4928
4929 /* Followup-To */
4930 if (compose->use_followupto && !IS_IN_CUSTOM_HEADER("Followup-To")) {
4931 entry_str = gtk_entry_get_text
4932 (GTK_ENTRY(compose->followup_entry));
4933 if (*entry_str != '\0') {
4934 str = g_strdup(entry_str);
4935 g_strstrip(str);
4936 remove_space(str);
4937 if (*str != '\0') {
4938 compose_convert_header(compose,
4939 buf, sizeof(buf), str,
4940 strlen("Followup-To: "),
4941 FALSE, charset);
4942 fprintf(fp, "Followup-To: %s\n", buf);
4943 }
4944 g_free(str);
4945 }
4946 }
4947
4948 /* Reply-To */
4949 if (compose->use_replyto && !IS_IN_CUSTOM_HEADER("Reply-To")) {
4950 entry_str = gtk_entry_get_text(GTK_ENTRY(compose->reply_entry));
4951 if (*entry_str != '\0') {
4952 str = g_strdup(entry_str);
4953 g_strstrip(str);
4954 if (*str != '\0') {
4955 compose_convert_header(compose,
4956 buf, sizeof(buf), str,
4957 strlen("Reply-To: "),
4958 TRUE, charset);
4959 fprintf(fp, "Reply-To: %s\n", buf);
4960 }
4961 g_free(str);
4962 }
4963 }
4964
4965 /* Disposition-Notification-To */
4966 if (compose->use_mdn &&
4967 !IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
4968 fprintf(fp, "Disposition-Notification-To: %s\n",
4969 compose->account->address);
4970 }
4971
4972 /* Organization */
4973 if (compose->account->organization &&
4974 !IS_IN_CUSTOM_HEADER("Organization")) {
4975 compose_convert_header(compose, buf, sizeof(buf),
4976 compose->account->organization,
4977 strlen("Organization: "), FALSE,
4978 charset);
4979 fprintf(fp, "Organization: %s\n", buf);
4980 }
4981
4982 /* Program version and system info */
4983 if (prefs_common.user_agent_str) {
4984 if (compose->to_list && !IS_IN_CUSTOM_HEADER("X-Mailer")) {
4985 fprintf(fp, "X-Mailer: %s\n",
4986 prefs_common.user_agent_str);
4987 }
4988 if (compose->newsgroup_list && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
4989 fprintf(fp, "X-Newsreader: %s\n",
4990 prefs_common.user_agent_str);
4991 }
4992 }
4993
4994 /* custom headers */
4995 if (compose->account->add_customhdr) {
4996 GSList *cur;
4997
4998 for (cur = compose->account->customhdr_list; cur != NULL;
4999 cur = cur->next) {
5000 CustomHeader *chdr = (CustomHeader *)cur->data;
5001
5002 if (g_ascii_strcasecmp(chdr->name, "Date") != 0 &&
5003 g_ascii_strcasecmp(chdr->name, "From") != 0 &&
5004 g_ascii_strcasecmp(chdr->name, "To") != 0 &&
5005 /* g_ascii_strcasecmp(chdr->name, "Sender") != 0 && */
5006 g_ascii_strcasecmp(chdr->name, "Message-Id") != 0 &&
5007 g_ascii_strcasecmp(chdr->name, "In-Reply-To") != 0 &&
5008 g_ascii_strcasecmp(chdr->name, "References") != 0 &&
5009 g_ascii_strcasecmp(chdr->name, "Mime-Version") != 0 &&
5010 g_ascii_strcasecmp(chdr->name, "Content-Type") != 0 &&
5011 g_ascii_strcasecmp(chdr->name, "Content-Transfer-Encoding") != 0) {
5012 compose_convert_header
5013 (compose, buf, sizeof(buf),
5014 chdr->value ? chdr->value : "",
5015 strlen(chdr->name) + 2, FALSE,
5016 charset);
5017 fprintf(fp, "%s: %s\n", chdr->name, buf);
5018 }
5019 }
5020 }
5021
5022 /* MIME */
5023 fprintf(fp, "Mime-Version: 1.0\n");
5024 if (compose->use_attach &&
5025 gtk_tree_model_iter_n_children
5026 (GTK_TREE_MODEL(compose->attach_store), NULL) > 0) {
5027 compose->boundary = generate_mime_boundary(NULL);
5028 fprintf(fp,
5029 "Content-Type: multipart/mixed;\n"
5030 " boundary=\"%s\"\n", compose->boundary);
5031 } else {
5032 fprintf(fp, "Content-Type: text/plain; charset=%s\n",
5033 body_charset);
5034 #if USE_GPGME
5035 if (rfc2015_is_available() &&
5036 compose->use_signing && !compose->account->clearsign)
5037 fprintf(fp, "Content-Disposition: inline\n");
5038 #endif
5039 fprintf(fp, "Content-Transfer-Encoding: %s\n",
5040 procmime_get_encoding_str(encoding));
5041 }
5042
5043 /* X-Sylpheed headers */
5044 if (is_draft) {
5045 fprintf(fp, "X-Sylpheed-Account-Id: %d\n",
5046 compose->account->account_id);
5047 if (compose->reply_target)
5048 fprintf(fp, "X-Sylpheed-Reply: %s\n",
5049 compose->reply_target);
5050 else if (compose->forward_targets)
5051 fprintf(fp, "X-Sylpheed-Forward: %s\n",
5052 compose->forward_targets);
5053 fprintf(fp, "X-Sylpheed-Compose-AutoWrap: %s\n",
5054 compose->autowrap ? "TRUE" : "FALSE");
5055 #if USE_GTKSPELL
5056 fprintf(fp, "X-Sylpheed-Compose-CheckSpell: %s\n",
5057 compose->check_spell ? "TRUE" : "FALSE");
5058 if (compose->spell_lang)
5059 fprintf(fp, "X-Sylpheed-Compose-SpellLang: %s\n",
5060 compose->spell_lang);
5061 #endif
5062 #if USE_GPGME
5063 fprintf(fp, "X-Sylpheed-Compose-UseSigning: %s\n",
5064 compose->use_signing ? "TRUE" : "FALSE");
5065 fprintf(fp, "X-Sylpheed-Compose-UseEncryption: %s\n",
5066 compose->use_encryption ? "TRUE" : "FALSE");
5067 #endif
5068 }
5069
5070 /* separator between header and body */
5071 fputs("\n", fp);
5072
5073 return 0;
5074 }
5075
compose_redirect_write_headers(Compose * compose,FILE * fp)5076 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5077 {
5078 gchar buf[BUFFSIZE];
5079 const gchar *entry_str;
5080 gchar *str;
5081 const gchar *charset = NULL;
5082
5083 g_return_val_if_fail(fp != NULL, -1);
5084 g_return_val_if_fail(compose->account != NULL, -1);
5085 g_return_val_if_fail(compose->account->address != NULL, -1);
5086
5087 /* Resent-Date */
5088 get_rfc822_date(buf, sizeof(buf));
5089 fprintf(fp, "Resent-Date: %s\n", buf);
5090
5091 /* Resent-From */
5092 if (compose->account->name) {
5093 compose_convert_header
5094 (compose, buf, sizeof(buf), compose->account->name,
5095 strlen("Resent-From: "), TRUE, NULL);
5096 fprintf(fp, "Resent-From: %s <%s>\n",
5097 buf, compose->account->address);
5098 } else
5099 fprintf(fp, "Resent-From: %s\n", compose->account->address);
5100
5101 slist_free_strings(compose->to_list);
5102 g_slist_free(compose->to_list);
5103 compose->to_list = NULL;
5104
5105 /* Resent-To */
5106 if (compose->use_to) {
5107 entry_str = gtk_entry_get_text(GTK_ENTRY(compose->to_entry));
5108 PUT_RECIPIENT_HEADER("Resent-To", entry_str);
5109 }
5110 if (compose->use_cc) {
5111 entry_str = gtk_entry_get_text(GTK_ENTRY(compose->cc_entry));
5112 PUT_RECIPIENT_HEADER("Resent-Cc", entry_str);
5113 }
5114 if (compose->use_bcc) {
5115 entry_str = gtk_entry_get_text(GTK_ENTRY(compose->bcc_entry));
5116 PUT_RECIPIENT_HEADER("Bcc", entry_str);
5117 }
5118
5119 slist_free_strings(compose->newsgroup_list);
5120 g_slist_free(compose->newsgroup_list);
5121 compose->newsgroup_list = NULL;
5122
5123 /* Newsgroups */
5124 if (compose->use_newsgroups) {
5125 entry_str = gtk_entry_get_text
5126 (GTK_ENTRY(compose->newsgroups_entry));
5127 if (*entry_str != '\0') {
5128 str = g_strdup(entry_str);
5129 g_strstrip(str);
5130 remove_space(str);
5131 if (*str != '\0') {
5132 compose->newsgroup_list =
5133 newsgroup_list_append
5134 (compose->newsgroup_list, str);
5135 compose_convert_header(compose,
5136 buf, sizeof(buf), str,
5137 strlen("Newsgroups: "),
5138 FALSE, NULL);
5139 fprintf(fp, "Newsgroups: %s\n", buf);
5140 }
5141 g_free(str);
5142 }
5143 }
5144
5145 if (!compose->to_list && !compose->newsgroup_list)
5146 return -1;
5147
5148 /* Subject */
5149 entry_str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5150 if (*entry_str != '\0') {
5151 str = g_strdup(entry_str);
5152 g_strstrip(str);
5153 if (*str != '\0') {
5154 compose_convert_header(compose, buf, sizeof(buf), str,
5155 strlen("Subject: "), FALSE,
5156 NULL);
5157 fprintf(fp, "Subject: %s\n", buf);
5158 }
5159 g_free(str);
5160 }
5161
5162 /* Resent-Message-Id */
5163 if (compose->account->gen_msgid) {
5164 compose_generate_msgid(compose, buf, sizeof(buf));
5165 fprintf(fp, "Resent-Message-Id: <%s>\n", buf);
5166 compose->msgid = g_strdup(buf);
5167 }
5168
5169 /* Followup-To */
5170 if (compose->use_followupto) {
5171 entry_str = gtk_entry_get_text
5172 (GTK_ENTRY(compose->followup_entry));
5173 if (*entry_str != '\0') {
5174 str = g_strdup(entry_str);
5175 g_strstrip(str);
5176 remove_space(str);
5177 if (*str != '\0') {
5178 compose_convert_header(compose,
5179 buf, sizeof(buf), str,
5180 strlen("Followup-To: "),
5181 FALSE, NULL);
5182 fprintf(fp, "Followup-To: %s\n", buf);
5183 }
5184 g_free(str);
5185 }
5186 }
5187
5188 /* Resent-Reply-To */
5189 if (compose->use_replyto) {
5190 entry_str = gtk_entry_get_text(GTK_ENTRY(compose->reply_entry));
5191 if (*entry_str != '\0') {
5192 str = g_strdup(entry_str);
5193 g_strstrip(str);
5194 if (*str != '\0') {
5195 compose_convert_header
5196 (compose, buf, sizeof(buf), str,
5197 strlen("Resent-Reply-To: "), TRUE,
5198 NULL);
5199 fprintf(fp, "Resent-Reply-To: %s\n", buf);
5200 }
5201 g_free(str);
5202 }
5203 }
5204
5205 fputs("\n", fp);
5206
5207 return 0;
5208 }
5209
5210 #undef IS_IN_CUSTOM_HEADER
5211
compose_convert_header(Compose * compose,gchar * dest,gint len,const gchar * src,gint header_len,gboolean addr_field,const gchar * encoding)5212 static void compose_convert_header(Compose *compose, gchar *dest, gint len,
5213 const gchar *src, gint header_len,
5214 gboolean addr_field, const gchar *encoding)
5215 {
5216 gchar *src_;
5217
5218 g_return_if_fail(src != NULL);
5219 g_return_if_fail(dest != NULL);
5220
5221 if (len < 1) return;
5222
5223 if (addr_field)
5224 src_ = normalize_address_field(src);
5225 else
5226 src_ = g_strdup(src);
5227 g_strchomp(src_);
5228 if (!encoding)
5229 encoding = conv_get_charset_str(compose->out_encoding);
5230
5231 conv_encode_header(dest, len, src_, header_len, addr_field, encoding);
5232
5233 g_free(src_);
5234 }
5235
compose_convert_filename(Compose * compose,const gchar * src,const gchar * param_name,const gchar * encoding)5236 static gchar *compose_convert_filename(Compose *compose, const gchar *src,
5237 const gchar *param_name,
5238 const gchar *encoding)
5239 {
5240 gchar *str;
5241
5242 g_return_val_if_fail(src != NULL, NULL);
5243
5244 if (!encoding)
5245 encoding = conv_get_charset_str(compose->out_encoding);
5246
5247 str = conv_encode_filename(src, param_name, encoding);
5248
5249 return str;
5250 }
5251
compose_generate_msgid(Compose * compose,gchar * buf,gint len)5252 static void compose_generate_msgid(Compose *compose, gchar *buf, gint len)
5253 {
5254 struct tm *lt;
5255 time_t t;
5256 const gchar *addr, *p;
5257 gchar *addr_left;
5258 gchar *addr_right;
5259 gchar hash_str[64];
5260 SMD5 *md5;
5261 gchar *md5str;
5262
5263 t = time(NULL);
5264 lt = localtime(&t);
5265
5266 if (compose->account && compose->account->address &&
5267 *compose->account->address) {
5268 addr = compose->account->address;
5269 if ((p = strchr(addr, '@'))) {
5270 addr_left = g_strndup(addr, p - addr);
5271 addr_right = g_strdup(p + 1);
5272 } else {
5273 addr_left = g_strdup(addr);
5274 addr_right = g_strdup(get_domain_name());
5275 }
5276 } else {
5277 addr_left = g_strdup(g_get_user_name());
5278 addr_right = g_strdup(get_domain_name());
5279 }
5280
5281 g_snprintf(hash_str, sizeof(hash_str), "%08x%s",
5282 g_random_int(), addr_left);
5283 md5 = s_gnet_md5_new((guchar *)hash_str, strlen(hash_str));
5284 md5str = s_gnet_md5_get_string(md5);
5285
5286 g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%.24s@%s",
5287 lt->tm_year + 1900, lt->tm_mon + 1,
5288 lt->tm_mday, lt->tm_hour,
5289 lt->tm_min, lt->tm_sec,
5290 md5str, addr_right);
5291
5292 g_free(md5str);
5293 s_gnet_md5_delete(md5);
5294 g_free(addr_right);
5295 g_free(addr_left);
5296
5297 debug_print("generated Message-ID: %s\n", buf);
5298 }
5299
compose_add_entry_field(GtkWidget * table,GtkWidget ** hbox,GtkWidget ** entry,gint * count,const gchar * label_str,gboolean is_addr_entry)5300 static void compose_add_entry_field(GtkWidget *table, GtkWidget **hbox,
5301 GtkWidget **entry, gint *count,
5302 const gchar *label_str,
5303 gboolean is_addr_entry)
5304 {
5305 GtkWidget *label;
5306
5307 if (GTK_TABLE(table)->nrows < (*count) + 1)
5308 gtk_table_resize(GTK_TABLE(table), (*count) + 1, 2);
5309
5310 *hbox = gtk_hbox_new(FALSE, 0);
5311 label = gtk_label_new
5312 (prefs_common.trans_hdr ? gettext(label_str) : label_str);
5313 gtk_box_pack_end(GTK_BOX(*hbox), label, FALSE, FALSE, 0);
5314 gtk_table_attach(GTK_TABLE(table), *hbox, 0, 1, *count, (*count) + 1,
5315 GTK_FILL, 0, 2, 0);
5316 *entry = gtk_entry_new();
5317 gtk_entry_set_max_length(GTK_ENTRY(*entry), MAX_ENTRY_LENGTH);
5318 gtk_table_attach_defaults
5319 (GTK_TABLE(table), *entry, 1, 2, *count, (*count) + 1);
5320 if (GTK_TABLE(table)->nrows > (*count) + 1)
5321 gtk_table_set_row_spacing(GTK_TABLE(table), *count, 4);
5322
5323 if (is_addr_entry && prefs_common.enable_address_completion)
5324 address_completion_register_entry(GTK_ENTRY(*entry));
5325
5326 (*count)++;
5327 }
5328
compose_create(PrefsAccount * account,ComposeMode mode)5329 static Compose *compose_create(PrefsAccount *account, ComposeMode mode)
5330 {
5331 Compose *compose;
5332 GtkWidget *window;
5333 GtkWidget *vbox;
5334 GtkWidget *menubar;
5335 GtkWidget *toolbar;
5336
5337 GtkWidget *vbox2;
5338
5339 GtkWidget *table_vbox;
5340 GtkWidget *table;
5341 GtkWidget *hbox;
5342 GtkWidget *label;
5343 GtkWidget *from_optmenu_hbox;
5344 GtkWidget *sig_combo;
5345 GtkWidget *to_entry;
5346 GtkWidget *to_hbox;
5347 GtkWidget *newsgroups_entry;
5348 GtkWidget *newsgroups_hbox;
5349 GtkWidget *subject_entry;
5350 GtkWidget *cc_entry;
5351 GtkWidget *cc_hbox;
5352 GtkWidget *bcc_entry;
5353 GtkWidget *bcc_hbox;
5354 GtkWidget *reply_entry;
5355 GtkWidget *reply_hbox;
5356 GtkWidget *followup_entry;
5357 GtkWidget *followup_hbox;
5358
5359 #if USE_GPGME
5360 GtkWidget *misc_hbox;
5361 GtkWidget *signing_chkbtn;
5362 GtkWidget *encrypt_chkbtn;
5363 #endif /* USE_GPGME */
5364 #if 0
5365 GtkWidget *attach_img;
5366 GtkWidget *attach_toggle;
5367 #endif
5368
5369 GtkWidget *paned;
5370
5371 GtkWidget *attach_scrwin;
5372 GtkWidget *attach_treeview;
5373 GtkListStore *store;
5374 GtkTreeSelection *selection;
5375 GtkTreeViewColumn *column;
5376 GtkCellRenderer *renderer;
5377
5378 GtkWidget *edit_vbox;
5379 GtkWidget *ruler_hbox;
5380 GtkWidget *ruler;
5381 GtkWidget *scrolledwin;
5382 GtkWidget *text;
5383
5384 GtkTextBuffer *buffer;
5385 GtkClipboard *clipboard;
5386 GtkTextTag *sig_tag;
5387
5388 #if USE_GTKSPELL
5389 GtkWidget *spell_menu;
5390 #endif /* USE_GTKSPELL */
5391
5392 UndoMain *undostruct;
5393
5394 guint n_menu_entries;
5395 GdkColormap *cmap;
5396 GdkColor color[1];
5397 gboolean success[1];
5398 GtkWidget *popupmenu;
5399 GtkItemFactory *popupfactory;
5400 GtkItemFactory *ifactory;
5401 GtkWidget *tmpl_menu;
5402 gint n_entries;
5403 gint count = 0;
5404
5405 #ifndef G_OS_WIN32
5406 static GdkGeometry geometry;
5407 #endif
5408
5409 g_return_val_if_fail(account != NULL, NULL);
5410
5411 debug_print(_("Creating compose window...\n"));
5412 compose = g_new0(Compose, 1);
5413
5414 compose->account = account;
5415
5416 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
5417 gtk_window_set_wmclass(GTK_WINDOW(window), "compose", "Sylpheed");
5418 gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE);
5419 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
5420 gtkut_window_move(GTK_WINDOW(window), prefs_common.compose_x,
5421 prefs_common.compose_y);
5422
5423 #ifndef G_OS_WIN32
5424 if (!geometry.max_width) {
5425 geometry.max_width = gdk_screen_width();
5426 geometry.max_height = gdk_screen_height();
5427 }
5428 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
5429 &geometry, GDK_HINT_MAX_SIZE);
5430 #endif
5431
5432 g_signal_connect(G_OBJECT(window), "delete_event",
5433 G_CALLBACK(compose_delete_cb), compose);
5434 MANAGE_WINDOW_SIGNALS_CONNECT(window);
5435
5436 vbox = gtk_vbox_new(FALSE, 0);
5437 gtk_container_add(GTK_CONTAINER(window), vbox);
5438
5439 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
5440 menubar = menubar_create(window, compose_entries,
5441 n_menu_entries, "<Compose>", compose);
5442 gtk_widget_set_size_request(menubar, 300, -1);
5443 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
5444
5445 compose->toolbar_tip = gtk_tooltips_new();
5446 g_object_ref_sink(compose->toolbar_tip);
5447 toolbar = compose_toolbar_create(compose);
5448 gtk_widget_set_size_request(toolbar, 300, -1);
5449 gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
5450
5451 vbox2 = gtk_vbox_new(FALSE, 2);
5452 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
5453 gtk_container_set_border_width(GTK_CONTAINER(vbox2), BORDER_WIDTH);
5454
5455 table_vbox = gtk_vbox_new(FALSE, 0);
5456 gtk_box_pack_start(GTK_BOX(vbox2), table_vbox, FALSE, TRUE, 0);
5457 gtk_container_set_border_width(GTK_CONTAINER(table_vbox), BORDER_WIDTH);
5458
5459 table = gtk_table_new(8, 2, FALSE);
5460 gtk_box_pack_start(GTK_BOX(table_vbox), table, FALSE, TRUE, 0);
5461
5462 /* option menu for selecting accounts */
5463 hbox = gtk_hbox_new(FALSE, 0);
5464 label = gtk_label_new(prefs_common.trans_hdr ? _("From:") : "From:");
5465 gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0);
5466 gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, count, count + 1,
5467 GTK_FILL, 0, 2, 0);
5468
5469 from_optmenu_hbox = gtk_hbox_new(FALSE, 0);
5470 gtk_table_attach_defaults(GTK_TABLE(table), from_optmenu_hbox,
5471 1, 2, count, count + 1);
5472 //gtk_table_attach(GTK_TABLE(table), from_optmenu_hbox,
5473 //1, 2, count, count + 1, GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
5474 gtk_widget_set_size_request(from_optmenu_hbox, 300, -1);
5475 gtk_table_set_row_spacing(GTK_TABLE(table), 0, 4);
5476 count++;
5477 compose_account_option_menu_create(compose, from_optmenu_hbox);
5478 sig_combo = compose_signature_menu_create(compose, from_optmenu_hbox);
5479
5480 /* header labels and entries */
5481 compose_add_entry_field(table, &to_hbox, &to_entry, &count,
5482 "To:", TRUE);
5483 compose_add_entry_field(table, &newsgroups_hbox, &newsgroups_entry,
5484 &count, "Newsgroups:", FALSE);
5485 compose_add_entry_field(table, &cc_hbox, &cc_entry, &count,
5486 "Cc:", TRUE);
5487 compose_add_entry_field(table, &bcc_hbox, &bcc_entry, &count,
5488 "Bcc:", TRUE);
5489 compose_add_entry_field(table, &reply_hbox, &reply_entry, &count,
5490 "Reply-To:", TRUE);
5491 compose_add_entry_field(table, &followup_hbox, &followup_entry, &count,
5492 "Followup-To:", FALSE);
5493 compose_add_entry_field(table, &hbox, &subject_entry, &count,
5494 "Subject:", FALSE);
5495
5496 gtk_table_set_col_spacings(GTK_TABLE(table), 4);
5497
5498 #ifndef __APPLE__
5499 g_signal_connect(G_OBJECT(to_entry), "activate",
5500 G_CALLBACK(to_activated), compose);
5501 g_signal_connect(G_OBJECT(newsgroups_entry), "activate",
5502 G_CALLBACK(newsgroups_activated), compose);
5503 g_signal_connect(G_OBJECT(cc_entry), "activate",
5504 G_CALLBACK(cc_activated), compose);
5505 g_signal_connect(G_OBJECT(bcc_entry), "activate",
5506 G_CALLBACK(bcc_activated), compose);
5507 g_signal_connect(G_OBJECT(reply_entry), "activate",
5508 G_CALLBACK(replyto_activated), compose);
5509 g_signal_connect(G_OBJECT(followup_entry), "activate",
5510 G_CALLBACK(followupto_activated), compose);
5511 g_signal_connect(G_OBJECT(subject_entry), "activate",
5512 G_CALLBACK(subject_activated), compose);
5513 #endif
5514
5515 g_signal_connect(G_OBJECT(to_entry), "grab_focus",
5516 G_CALLBACK(compose_grab_focus_cb), compose);
5517 g_signal_connect(G_OBJECT(newsgroups_entry), "grab_focus",
5518 G_CALLBACK(compose_grab_focus_cb), compose);
5519 g_signal_connect(G_OBJECT(cc_entry), "grab_focus",
5520 G_CALLBACK(compose_grab_focus_cb), compose);
5521 g_signal_connect(G_OBJECT(bcc_entry), "grab_focus",
5522 G_CALLBACK(compose_grab_focus_cb), compose);
5523 g_signal_connect(G_OBJECT(reply_entry), "grab_focus",
5524 G_CALLBACK(compose_grab_focus_cb), compose);
5525 g_signal_connect(G_OBJECT(followup_entry), "grab_focus",
5526 G_CALLBACK(compose_grab_focus_cb), compose);
5527 g_signal_connect(G_OBJECT(subject_entry), "grab_focus",
5528 G_CALLBACK(compose_grab_focus_cb), compose);
5529
5530 #if 0
5531 attach_img = stock_pixbuf_widget(window, STOCK_PIXMAP_CLIP);
5532 attach_toggle = gtk_toggle_button_new();
5533 GTK_WIDGET_UNSET_FLAGS(attach_toggle, GTK_CAN_FOCUS);
5534 gtk_container_add(GTK_CONTAINER(attach_toggle), attach_img);
5535 gtk_box_pack_start(GTK_BOX(misc_hbox), attach_toggle, FALSE, FALSE, 8);
5536 g_signal_connect(G_OBJECT(attach_toggle), "toggled",
5537 G_CALLBACK(compose_attach_toggled), compose);
5538 #endif
5539
5540 #if USE_GPGME
5541 misc_hbox = gtk_hbox_new(FALSE, 0);
5542 gtk_box_pack_start(GTK_BOX(vbox2), misc_hbox, FALSE, FALSE, 0);
5543
5544 signing_chkbtn = gtk_check_button_new_with_label(_("PGP Sign"));
5545 GTK_WIDGET_UNSET_FLAGS(signing_chkbtn, GTK_CAN_FOCUS);
5546 gtk_box_pack_start(GTK_BOX(misc_hbox), signing_chkbtn, FALSE, FALSE, 8);
5547 encrypt_chkbtn = gtk_check_button_new_with_label(_("PGP Encrypt"));
5548 GTK_WIDGET_UNSET_FLAGS(encrypt_chkbtn, GTK_CAN_FOCUS);
5549 gtk_box_pack_start(GTK_BOX(misc_hbox), encrypt_chkbtn, FALSE, FALSE, 8);
5550
5551 g_signal_connect(G_OBJECT(signing_chkbtn), "toggled",
5552 G_CALLBACK(compose_signing_toggled), compose);
5553 g_signal_connect(G_OBJECT(encrypt_chkbtn), "toggled",
5554 G_CALLBACK(compose_encrypt_toggled), compose);
5555 #endif /* USE_GPGME */
5556
5557 /* attachment list */
5558 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
5559 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
5560 GTK_POLICY_AUTOMATIC,
5561 GTK_POLICY_ALWAYS);
5562 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(attach_scrwin),
5563 GTK_SHADOW_IN);
5564 gtk_widget_set_size_request(attach_scrwin, -1, 80 * gtkut_get_dpi_multiplier());
5565
5566 store = gtk_list_store_new(N_ATTACH_COLS, G_TYPE_STRING, G_TYPE_STRING,
5567 G_TYPE_STRING, G_TYPE_POINTER);
5568
5569 attach_treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
5570 g_object_unref(G_OBJECT(store));
5571 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(attach_treeview), TRUE);
5572 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_treeview), TRUE);
5573 gtk_tree_view_set_search_column(GTK_TREE_VIEW(attach_treeview),
5574 COL_NAME);
5575 gtk_tree_view_set_reorderable(GTK_TREE_VIEW(attach_treeview), FALSE);
5576
5577 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_treeview));
5578 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
5579
5580 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_treeview);
5581
5582 renderer = gtk_cell_renderer_text_new();
5583 g_object_set(renderer, "ypad", 0, NULL);
5584 column = gtk_tree_view_column_new_with_attributes
5585 (_("Data type"), renderer, "text", COL_MIMETYPE, NULL);
5586 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
5587 gtk_tree_view_column_set_fixed_width(column, 240);
5588 gtk_tree_view_column_set_resizable(column, TRUE);
5589 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_treeview), column);
5590
5591 renderer = gtk_cell_renderer_text_new();
5592 g_object_set(renderer, "xalign", 1.0, "ypad", 0, NULL);
5593 column = gtk_tree_view_column_new_with_attributes
5594 (_("Size"), renderer, "text", COL_SIZE, NULL);
5595 gtk_tree_view_column_set_alignment(column, 1.0);
5596 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
5597 gtk_tree_view_column_set_fixed_width(column, 64);
5598 gtk_tree_view_column_set_resizable(column, TRUE);
5599 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_treeview), column);
5600
5601 renderer = gtk_cell_renderer_text_new();
5602 g_object_set(renderer, "ypad", 0, NULL);
5603 column = gtk_tree_view_column_new_with_attributes
5604 (_("Name"), renderer, "text", COL_NAME, NULL);
5605 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
5606 gtk_tree_view_column_set_resizable(column, TRUE);
5607 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_treeview), column);
5608
5609 g_signal_connect(G_OBJECT(selection), "changed",
5610 G_CALLBACK(attach_selection_changed), compose);
5611 g_signal_connect(G_OBJECT(attach_treeview), "button_press_event",
5612 G_CALLBACK(attach_button_pressed), compose);
5613 g_signal_connect(G_OBJECT(attach_treeview), "key_press_event",
5614 G_CALLBACK(attach_key_pressed), compose);
5615
5616 /* drag and drop */
5617 gtk_drag_dest_set(window,
5618 GTK_DEST_DEFAULT_ALL, compose_drag_types,
5619 N_DRAG_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE);
5620 g_signal_connect(G_OBJECT(window), "drag-data-received",
5621 G_CALLBACK(compose_attach_drag_received_cb),
5622 compose);
5623
5624 /* pane between attach tree view and text */
5625 paned = gtk_vpaned_new();
5626 gtk_paned_add1(GTK_PANED(paned), attach_scrwin);
5627 gtk_widget_ref(paned);
5628 gtk_widget_show_all(paned);
5629
5630 edit_vbox = gtk_vbox_new(FALSE, 0);
5631 gtk_box_pack_start(GTK_BOX(vbox2), edit_vbox, TRUE, TRUE, 0);
5632
5633 /* ruler */
5634 ruler_hbox = gtk_hbox_new(FALSE, 0);
5635 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
5636
5637 ruler = gtk_shruler_new();
5638 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
5639 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE, 0);
5640
5641 /* text widget */
5642 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
5643 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
5644 GTK_POLICY_AUTOMATIC,
5645 GTK_POLICY_ALWAYS);
5646 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
5647 GTK_SHADOW_IN);
5648 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
5649 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width,
5650 -1);
5651
5652 text = gtk_text_view_new();
5653 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
5654 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
5655 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
5656 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), TEXTVIEW_MARGIN);
5657 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), TEXTVIEW_MARGIN);
5658 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
5659 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
5660 sig_tag = gtk_text_buffer_create_tag(buffer, "signature", NULL);
5661 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
5662
5663 gtk_shruler_set_start_pos(GTK_SHRULER(ruler),
5664 text->style->xthickness + TEXTVIEW_MARGIN);
5665
5666 g_signal_connect(G_OBJECT(text), "grab_focus",
5667 G_CALLBACK(compose_grab_focus_cb), compose);
5668 g_signal_connect(G_OBJECT(buffer), "insert_text",
5669 G_CALLBACK(text_inserted), compose);
5670 g_signal_connect_after(G_OBJECT(text), "size_allocate",
5671 G_CALLBACK(compose_edit_size_alloc),
5672 ruler);
5673
5674 /* drag and drop */
5675 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_drag_types,
5676 N_DRAG_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE);
5677 g_signal_connect(G_OBJECT(text), "drag-data-received",
5678 G_CALLBACK(compose_insert_drag_received_cb),
5679 compose);
5680
5681 gtk_widget_show_all(vbox);
5682
5683 if (prefs_common.textfont) {
5684 PangoFontDescription *font_desc;
5685
5686 font_desc = pango_font_description_from_string
5687 (prefs_common.textfont);
5688 if (font_desc) {
5689 gtk_widget_modify_font(text, font_desc);
5690 pango_font_description_free(font_desc);
5691 }
5692 }
5693
5694 gtk_text_view_set_pixels_above_lines
5695 (GTK_TEXT_VIEW(text),
5696 prefs_common.line_space - prefs_common.line_space / 2);
5697 gtk_text_view_set_pixels_below_lines
5698 (GTK_TEXT_VIEW(text), prefs_common.line_space / 2);
5699 gtk_text_view_set_pixels_inside_wrap
5700 (GTK_TEXT_VIEW(text), prefs_common.line_space);
5701
5702 n_entries = sizeof(compose_popup_entries) /
5703 sizeof(compose_popup_entries[0]);
5704 popupmenu = menu_create_items(compose_popup_entries, n_entries,
5705 "<Compose>", &popupfactory,
5706 compose);
5707
5708 ifactory = gtk_item_factory_from_widget(menubar);
5709 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
5710 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
5711
5712 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
5713
5714 #if USE_GTKSPELL
5715 spell_menu = gtk_item_factory_get_item
5716 (ifactory, "/Tools/Set spell language");
5717 #endif
5718
5719 gtk_widget_hide(bcc_hbox);
5720 gtk_widget_hide(bcc_entry);
5721 gtk_widget_hide(reply_hbox);
5722 gtk_widget_hide(reply_entry);
5723 gtk_widget_hide(followup_hbox);
5724 gtk_widget_hide(followup_entry);
5725 gtk_widget_hide(ruler_hbox);
5726 gtk_table_set_row_spacing(GTK_TABLE(table), 4, 0);
5727 gtk_table_set_row_spacing(GTK_TABLE(table), 5, 0);
5728 gtk_table_set_row_spacing(GTK_TABLE(table), 6, 0);
5729
5730 if (account->protocol == A_NNTP) {
5731 gtk_widget_hide(to_hbox);
5732 gtk_widget_hide(to_entry);
5733 gtk_widget_hide(cc_hbox);
5734 gtk_widget_hide(cc_entry);
5735 gtk_table_set_row_spacing(GTK_TABLE(table), 1, 0);
5736 gtk_table_set_row_spacing(GTK_TABLE(table), 3, 0);
5737 } else {
5738 gtk_widget_hide(newsgroups_hbox);
5739 gtk_widget_hide(newsgroups_entry);
5740 gtk_table_set_row_spacing(GTK_TABLE(table), 2, 0);
5741 }
5742
5743 #if USE_GPGME
5744 if (!rfc2015_is_available())
5745 gtk_widget_hide(misc_hbox);
5746 #endif
5747
5748 undostruct = undo_init(text);
5749 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
5750 menubar);
5751
5752 address_completion_start(window);
5753
5754 compose->window = window;
5755 compose->vbox = vbox;
5756 compose->menubar = menubar;
5757 compose->toolbar = toolbar;
5758
5759 compose->vbox2 = vbox2;
5760
5761 compose->table_vbox = table_vbox;
5762 compose->table = table;
5763 compose->to_hbox = to_hbox;
5764 compose->to_entry = to_entry;
5765 compose->newsgroups_hbox = newsgroups_hbox;
5766 compose->newsgroups_entry = newsgroups_entry;
5767 compose->subject_entry = subject_entry;
5768 compose->cc_hbox = cc_hbox;
5769 compose->cc_entry = cc_entry;
5770 compose->bcc_hbox = bcc_hbox;
5771 compose->bcc_entry = bcc_entry;
5772 compose->reply_hbox = reply_hbox;
5773 compose->reply_entry = reply_entry;
5774 compose->followup_hbox = followup_hbox;
5775 compose->followup_entry = followup_entry;
5776
5777 compose->sig_combo = sig_combo;
5778
5779 /* compose->attach_toggle = attach_toggle; */
5780 #if USE_GPGME
5781 compose->misc_hbox = misc_hbox;
5782 compose->signing_chkbtn = signing_chkbtn;
5783 compose->encrypt_chkbtn = encrypt_chkbtn;
5784 #endif /* USE_GPGME */
5785
5786 compose->paned = paned;
5787
5788 compose->attach_scrwin = attach_scrwin;
5789 compose->attach_treeview = attach_treeview;
5790 compose->attach_store = store;
5791
5792 compose->edit_vbox = edit_vbox;
5793 compose->ruler_hbox = ruler_hbox;
5794 compose->ruler = ruler;
5795 compose->scrolledwin = scrolledwin;
5796 compose->text = text;
5797
5798 #ifdef USE_GTKSPELL
5799 compose->check_spell = prefs_common.check_spell;
5800 compose->spell_lang = g_strdup(prefs_common.spell_lang);
5801 compose->spell_menu = spell_menu;
5802 compose->dict_list = NULL;
5803 #endif /* USE_GTKSPELL */
5804
5805 compose->focused_editable = NULL;
5806
5807 compose->popupmenu = popupmenu;
5808 compose->popupfactory = popupfactory;
5809
5810 compose->tmpl_menu = tmpl_menu;
5811
5812 compose->mode = mode;
5813
5814 compose->targetinfo = NULL;
5815 compose->reply_target = NULL;
5816 compose->forward_targets = NULL;
5817
5818 compose->replyto = NULL;
5819 compose->cc = NULL;
5820 compose->bcc = NULL;
5821 compose->followup_to = NULL;
5822
5823 compose->ml_post = NULL;
5824
5825 compose->inreplyto = NULL;
5826 compose->references = NULL;
5827 compose->msgid = NULL;
5828 compose->boundary = NULL;
5829
5830 compose->autowrap = prefs_common.autowrap;
5831
5832 compose->use_to = FALSE;
5833 compose->use_cc = FALSE;
5834 compose->use_bcc = FALSE;
5835 compose->use_replyto = FALSE;
5836 compose->use_newsgroups = FALSE;
5837 compose->use_followupto = FALSE;
5838 compose->use_attach = FALSE;
5839
5840 compose->out_encoding = C_AUTO;
5841
5842 compose->use_mdn = FALSE;
5843
5844 #if USE_GPGME
5845 compose->use_signing = FALSE;
5846 compose->use_encryption = FALSE;
5847 #endif /* USE_GPGME */
5848
5849 compose->modified = FALSE;
5850
5851 compose->to_list = NULL;
5852 compose->newsgroup_list = NULL;
5853
5854 compose->undostruct = undostruct;
5855
5856 compose->sig_tag = sig_tag;
5857
5858 compose->exteditor_file = NULL;
5859 compose->exteditor_pid = 0;
5860 compose->exteditor_tag = 0;
5861
5862 compose->autosave_tag = 0;
5863
5864 compose->window_maximized = prefs_common.compose_maximized;
5865
5866 compose->block_modified = FALSE;
5867
5868 compose_set_toolbar_button_visibility(compose);
5869
5870 compose_select_account(compose, account, TRUE);
5871
5872 if (prefs_common.compose_maximized)
5873 gtk_window_maximize(GTK_WINDOW(window));
5874
5875 g_signal_connect(G_OBJECT(window), "window_state_event",
5876 G_CALLBACK(compose_window_state_cb), compose);
5877
5878 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
5879 menu_set_active(ifactory, "/View/Ruler", prefs_common.show_ruler);
5880
5881 if (mode == COMPOSE_REDIRECT) {
5882 menu_set_sensitive(ifactory, "/File/Save to draft folder", FALSE);
5883 menu_set_sensitive(ifactory, "/File/Save and keep editing", FALSE);
5884 menu_set_sensitive(ifactory, "/File/Attach file", FALSE);
5885 menu_set_sensitive(ifactory, "/File/Insert file", FALSE);
5886 menu_set_sensitive(ifactory, "/File/Insert signature", FALSE);
5887 menu_set_sensitive(ifactory, "/Edit/Cut", FALSE);
5888 menu_set_sensitive(ifactory, "/Edit/Paste", FALSE);
5889 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", FALSE);
5890 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", FALSE);
5891 menu_set_sensitive(ifactory, "/Edit/Auto wrapping", FALSE);
5892 menu_set_sensitive(ifactory, "/View/Attachment", FALSE);
5893 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
5894 #ifndef G_OS_WIN32
5895 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
5896 #endif
5897 menu_set_sensitive(ifactory, "/Tools/Edit with external editor", FALSE);
5898 #if USE_GPGME
5899 menu_set_sensitive(ifactory, "/Tools/PGP Sign", FALSE);
5900 menu_set_sensitive(ifactory, "/Tools/PGP Encrypt", FALSE);
5901 #endif /* USE_GPGME */
5902 #if USE_GTKSPELL
5903 menu_set_sensitive(ifactory, "/Tools/Check spell", FALSE);
5904 menu_set_sensitive(ifactory, "/Tools/Set spell language",
5905 FALSE);
5906 #endif
5907
5908 if (compose->insert_btn)
5909 gtk_widget_set_sensitive(compose->insert_btn, FALSE);
5910 if (compose->attach_btn)
5911 gtk_widget_set_sensitive(compose->attach_btn, FALSE);
5912 if (compose->sig_btn)
5913 gtk_widget_set_sensitive(compose->sig_btn, FALSE);
5914 if (compose->exteditor_btn)
5915 gtk_widget_set_sensitive(compose->exteditor_btn, FALSE);
5916 if (compose->linewrap_btn)
5917 gtk_widget_set_sensitive(compose->linewrap_btn, FALSE);
5918
5919 /* gtk_widget_set_sensitive(compose->attach_toggle, FALSE); */
5920
5921 menu_set_sensitive_all(GTK_MENU_SHELL(compose->popupmenu),
5922 FALSE);
5923 }
5924
5925 #if USE_GPGME
5926 if (!rfc2015_is_available()) {
5927 menu_set_sensitive(ifactory, "/Tools/PGP Sign", FALSE);
5928 menu_set_sensitive(ifactory, "/Tools/PGP Encrypt", FALSE);
5929 }
5930 #endif /* USE_GPGME */
5931
5932 compose_set_out_encoding(compose);
5933 addressbook_set_target_compose(compose);
5934 #ifndef G_OS_WIN32
5935 action_update_compose_menu(ifactory, compose);
5936 #endif
5937 compose_set_template_menu(compose);
5938
5939 #if USE_GTKSPELL
5940 compose_set_spell_lang_menu(compose);
5941 if (mode != COMPOSE_REDIRECT)
5942 menu_set_active(ifactory, "/Tools/Check spell",
5943 prefs_common.check_spell);
5944 #endif
5945
5946 compose_list = g_list_append(compose_list, compose);
5947
5948 gtk_widget_show(window);
5949
5950 color[0] = quote_color;
5951 cmap = gdk_window_get_colormap(window->window);
5952 gdk_colormap_alloc_colors(cmap, color, 1, FALSE, TRUE, success);
5953 if (success[0] == FALSE) {
5954 GtkStyle *style;
5955
5956 g_warning("Compose: color allocation failed.\n");
5957 style = gtk_widget_get_style(text);
5958 quote_color = style->black;
5959 }
5960
5961 return compose;
5962 }
5963
compose_find_window_by_target(MsgInfo * msginfo)5964 static Compose *compose_find_window_by_target(MsgInfo *msginfo)
5965 {
5966 GList *cur;
5967 Compose *compose;
5968
5969 g_return_val_if_fail(msginfo != NULL, NULL);
5970
5971 for (cur = compose_list; cur != NULL; cur = cur->next) {
5972 compose = cur->data;
5973 if (procmsg_msginfo_equal(compose->targetinfo, msginfo))
5974 return compose;
5975 }
5976
5977 return NULL;
5978 }
5979
compose_window_exist(gint x,gint y)5980 static gboolean compose_window_exist(gint x, gint y)
5981 {
5982 GList *cur;
5983 Compose *compose;
5984 gint x_, y_;
5985
5986 for (cur = compose_list; cur != NULL; cur = cur->next) {
5987 compose = cur->data;
5988 gtkut_widget_get_uposition(compose->window, &x_, &y_);
5989 if (x == x_ && y == y_)
5990 return TRUE;
5991 }
5992
5993 return FALSE;
5994 }
5995
compose_connect_changed_callbacks(Compose * compose)5996 static void compose_connect_changed_callbacks(Compose *compose)
5997 {
5998 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
5999 GtkTextBuffer *buffer;
6000
6001 buffer = gtk_text_view_get_buffer(text);
6002
6003 g_signal_connect(G_OBJECT(buffer), "changed",
6004 G_CALLBACK(compose_buffer_changed_cb), compose);
6005 g_signal_connect(G_OBJECT(compose->to_entry), "changed",
6006 G_CALLBACK(compose_changed_cb), compose);
6007 g_signal_connect(G_OBJECT(compose->newsgroups_entry), "changed",
6008 G_CALLBACK(compose_changed_cb), compose);
6009 g_signal_connect(G_OBJECT(compose->cc_entry), "changed",
6010 G_CALLBACK(compose_changed_cb), compose);
6011 g_signal_connect(G_OBJECT(compose->bcc_entry), "changed",
6012 G_CALLBACK(compose_changed_cb), compose);
6013 g_signal_connect(G_OBJECT(compose->reply_entry), "changed",
6014 G_CALLBACK(compose_changed_cb), compose);
6015 g_signal_connect(G_OBJECT(compose->followup_entry), "changed",
6016 G_CALLBACK(compose_changed_cb), compose);
6017 g_signal_connect(G_OBJECT(compose->subject_entry), "changed",
6018 G_CALLBACK(compose_changed_cb), compose);
6019 }
6020
6021 static PrefsToolbarItem items[] =
6022 {
6023 {T_SEND, TRUE, toolbar_send_cb},
6024 {T_SEND_LATER, TRUE, toolbar_send_later_cb},
6025 {T_DRAFT, TRUE, toolbar_draft_cb},
6026 {T_INSERT_FILE, FALSE, toolbar_insert_cb},
6027 {T_ATTACH_FILE, FALSE, toolbar_attach_cb},
6028 {T_SIGNATURE, FALSE, toolbar_sig_cb},
6029 {T_EDITOR, FALSE, toolbar_ext_editor_cb},
6030 {T_LINEWRAP, FALSE, toolbar_linewrap_cb},
6031 {T_ADDRESS_BOOK, FALSE, toolbar_address_cb},
6032 {T_COMMON_PREFS, FALSE, toolbar_prefs_common_cb},
6033 {T_ACCOUNT_PREFS, FALSE, toolbar_prefs_account_cb},
6034
6035 {-1, FALSE, NULL}
6036 };
6037
compose_toolbar_create(Compose * compose)6038 static GtkWidget *compose_toolbar_create(Compose *compose)
6039 {
6040 GtkWidget *toolbar;
6041 const gchar *setting;
6042 GList *item_list;
6043
6044 if (prefs_common.compose_toolbar_setting &&
6045 *prefs_common.compose_toolbar_setting != '\0')
6046 setting = prefs_common.compose_toolbar_setting;
6047 else
6048 setting = prefs_toolbar_get_default_compose_setting_name_list();
6049
6050 item_list = prefs_toolbar_get_item_list_from_name_list(setting);
6051 toolbar = compose_toolbar_create_from_list(compose, item_list);
6052 g_list_free(item_list);
6053
6054 return toolbar;
6055 }
6056
compose_toolbar_create_from_list(Compose * compose,GList * item_list)6057 static GtkWidget *compose_toolbar_create_from_list(Compose *compose,
6058 GList *item_list)
6059 {
6060 GtkWidget *toolbar;
6061 GtkWidget *icon_wid;
6062 GtkToolItem *toolitem;
6063 gint i;
6064 GList *cur;
6065
6066 toolbar = gtk_toolbar_new();
6067 gtk_toolbar_set_orientation(GTK_TOOLBAR(toolbar),
6068 GTK_ORIENTATION_HORIZONTAL);
6069 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH);
6070 g_signal_connect(G_OBJECT(toolbar), "button_press_event",
6071 G_CALLBACK(toolbar_button_pressed), compose);
6072
6073 items[0].data = &compose->send_btn;
6074 items[1].data = &compose->sendl_btn;
6075 items[2].data = &compose->draft_btn;
6076 items[3].data = &compose->insert_btn;
6077 items[4].data = &compose->attach_btn;
6078 items[5].data = &compose->sig_btn;
6079 items[6].data = &compose->exteditor_btn;
6080 items[7].data = &compose->linewrap_btn;
6081 items[8].data = &compose->addrbook_btn;
6082 items[9].data = &compose->prefs_common_btn;
6083 items[10].data = &compose->prefs_account_btn;
6084 for (i = 0; i <= 10; i++)
6085 *(GtkWidget **)items[i].data = NULL;
6086
6087 for (cur = item_list; cur != NULL; cur = cur->next) {
6088 const PrefsDisplayItem *ditem = cur->data;
6089 PrefsToolbarItem *item;
6090 gint width;
6091
6092 if (ditem->id == T_SEPARATOR) {
6093 toolitem = gtk_separator_tool_item_new();
6094 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
6095 continue;
6096 }
6097
6098 for (item = items; item->id != -1; item++) {
6099 if (ditem->id == item->id)
6100 break;
6101 }
6102 if (item->id == -1)
6103 continue;
6104
6105 icon_wid = stock_pixbuf_widget_for_toolbar(ditem->icon);
6106 toolitem = gtk_tool_button_new(icon_wid, gettext(ditem->label));
6107 if (ditem->description) {
6108 gtk_tool_item_set_tooltip(toolitem,
6109 compose->toolbar_tip,
6110 gettext(ditem->description),
6111 ditem->name);
6112 }
6113
6114 gtkut_get_str_size(GTK_WIDGET(toolitem), gettext(ditem->label),
6115 &width, NULL);
6116 gtk_tool_item_set_homogeneous
6117 (toolitem, width < 52 ? TRUE : FALSE);
6118 gtk_tool_item_set_is_important(toolitem, item->is_important);
6119
6120 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
6121
6122 g_signal_connect(G_OBJECT(toolitem), "clicked",
6123 G_CALLBACK(item->callback), compose);
6124 g_signal_connect(G_OBJECT(GTK_BIN(toolitem)->child),
6125 "button_press_event",
6126 G_CALLBACK(toolbar_button_pressed), compose);
6127
6128 *(GtkWidget **)item->data = GTK_WIDGET(toolitem);
6129 }
6130
6131 gtk_widget_show_all(toolbar);
6132
6133 return toolbar;
6134 }
6135
compose_set_toolbar_button_visibility(Compose * compose)6136 static void compose_set_toolbar_button_visibility(Compose *compose)
6137 {
6138 GtkToolbarStyle style = GTK_TOOLBAR_BOTH_HORIZ;
6139
6140 if (prefs_common.toolbar_style == TOOLBAR_NONE)
6141 style = -1;
6142 else if (prefs_common.toolbar_style == TOOLBAR_ICON)
6143 style = GTK_TOOLBAR_ICONS;
6144 else if (prefs_common.toolbar_style == TOOLBAR_TEXT)
6145 style = GTK_TOOLBAR_TEXT;
6146 else if (prefs_common.toolbar_style == TOOLBAR_BOTH)
6147 style = GTK_TOOLBAR_BOTH;
6148 else if (prefs_common.toolbar_style == TOOLBAR_BOTH_HORIZ)
6149 style = GTK_TOOLBAR_BOTH_HORIZ;
6150
6151 if (style != -1) {
6152 gtk_toolbar_set_style(GTK_TOOLBAR(compose->toolbar), style);
6153 gtk_widget_show(compose->toolbar);
6154 gtk_widget_queue_resize(compose->toolbar);
6155 } else
6156 gtk_widget_hide(compose->toolbar);
6157 }
6158
compose_account_option_menu_create(Compose * compose,GtkWidget * hbox)6159 static GtkWidget *compose_account_option_menu_create(Compose *compose,
6160 GtkWidget *hbox)
6161 {
6162 GList *accounts;
6163 GtkWidget *optmenu;
6164 GtkWidget *menu;
6165 gint num = 0, def_menu = 0;
6166
6167 accounts = account_get_list();
6168 g_return_val_if_fail(accounts != NULL, NULL);
6169
6170 optmenu = gtk_option_menu_new();
6171 gtk_box_pack_start(GTK_BOX(hbox), optmenu, FALSE, FALSE, 0);
6172 menu = gtk_menu_new();
6173
6174 for (; accounts != NULL; accounts = accounts->next, num++) {
6175 PrefsAccount *ac = (PrefsAccount *)accounts->data;
6176 GtkWidget *menuitem;
6177 gchar *name;
6178
6179 if (ac == compose->account) def_menu = num;
6180
6181 if (ac->name)
6182 name = g_strdup_printf("%s: %s <%s>",
6183 ac->account_name,
6184 ac->name, ac->address);
6185 else
6186 name = g_strdup_printf("%s: %s",
6187 ac->account_name, ac->address);
6188 MENUITEM_ADD(menu, menuitem, name, ac);
6189 g_free(name);
6190 g_signal_connect(G_OBJECT(menuitem), "activate",
6191 G_CALLBACK(account_activated),
6192 compose);
6193 }
6194
6195 gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), menu);
6196 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), def_menu);
6197
6198 return optmenu;
6199 }
6200
compose_signature_menu_create(Compose * compose,GtkWidget * hbox)6201 static GtkWidget *compose_signature_menu_create(Compose *compose,
6202 GtkWidget *hbox)
6203 {
6204 GtkWidget *label;
6205 GtkWidget *combo;
6206 gint i;
6207
6208 label = gtk_label_new(_("Signature:"));
6209 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 4);
6210
6211 combo = gtk_combo_box_new_text();
6212 gtk_box_pack_start(GTK_BOX(hbox), combo, FALSE, FALSE, 0);
6213
6214 for (i = 0; i < 10; i++) {
6215 gchar buf[256];
6216 g_snprintf(buf, sizeof(buf), _("Signature %d"), i + 1);
6217 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), buf);
6218 }
6219 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6220 g_signal_connect(GTK_COMBO_BOX(combo), "changed",
6221 G_CALLBACK(sig_combo_changed), compose);
6222
6223 gtk_widget_set_tooltip_text(combo, _("Change signature"));
6224
6225 return combo;
6226 }
6227
compose_set_out_encoding(Compose * compose)6228 static void compose_set_out_encoding(Compose *compose)
6229 {
6230 GtkItemFactoryEntry *entry;
6231 GtkItemFactory *ifactory;
6232 CharSet out_encoding;
6233 gchar *path, *p, *q;
6234 GtkWidget *item;
6235
6236 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
6237 ifactory = gtk_item_factory_from_widget(compose->menubar);
6238
6239 for (entry = compose_entries; entry->callback != compose_address_cb;
6240 entry++) {
6241 if (entry->callback == compose_set_encoding_cb &&
6242 (CharSet)entry->callback_action == out_encoding) {
6243 p = q = path = g_strdup(entry->path);
6244 while (*p) {
6245 if (*p == '_') {
6246 if (p[1] == '_') {
6247 p++;
6248 *q++ = '_';
6249 }
6250 } else
6251 *q++ = *p;
6252 p++;
6253 }
6254 *q = '\0';
6255 item = gtk_item_factory_get_item(ifactory, path);
6256 gtk_widget_activate(item);
6257 g_free(path);
6258 break;
6259 }
6260 }
6261 }
6262
6263 #if USE_GTKSPELL
6264 #if USE_ENCHANT
ench_dict_desc_cb(const char * const lang_tag,const char * const provider_name,const char * const provider_desc,const char * const provider_file,void * user_data)6265 static void ench_dict_desc_cb(const char *const lang_tag,
6266 const char *const provider_name,
6267 const char *const provider_desc,
6268 const char *const provider_file,
6269 void *user_data)
6270 {
6271 GSList **dict_list = (GSList **)user_data;
6272 *dict_list = g_slist_append(*dict_list, g_strdup((gchar*)lang_tag));
6273 }
6274
compose_set_spell_lang_menu(Compose * compose)6275 static void compose_set_spell_lang_menu(Compose *compose)
6276 {
6277 EnchantBroker *eb;
6278 GSList *dict_list = NULL, *menu_list = NULL, *cur;
6279 GtkWidget *menu;
6280 gboolean lang_set = FALSE;
6281
6282 eb = enchant_broker_init();
6283 enchant_broker_list_dicts(eb, ench_dict_desc_cb, &dict_list);
6284 enchant_broker_free(eb);
6285
6286 for (cur = dict_list; cur != NULL; cur = cur->next) {
6287 if (compose->spell_lang != NULL &&
6288 g_ascii_strcasecmp(compose->spell_lang,
6289 (gchar *)cur->data) == 0)
6290 lang_set = TRUE;
6291 }
6292 #else /* !USE_ENCHANT */
6293 static void compose_set_spell_lang_menu(Compose *compose)
6294 {
6295 AspellConfig *config;
6296 AspellDictInfoList *dlist;
6297 AspellDictInfoEnumeration *dels;
6298 const AspellDictInfo *entry;
6299 GSList *dict_list = NULL, *menu_list = NULL, *cur;
6300 GtkWidget *menu;
6301 gboolean lang_set = FALSE;
6302
6303 config = new_aspell_config();
6304 dlist = get_aspell_dict_info_list(config);
6305 delete_aspell_config(config);
6306
6307 dels = aspell_dict_info_list_elements(dlist);
6308 while ((entry = aspell_dict_info_enumeration_next(dels)) != 0) {
6309 dict_list = g_slist_append(dict_list, g_strdup(entry->name));
6310 if (compose->spell_lang != NULL &&
6311 g_ascii_strcasecmp(compose->spell_lang, entry->name) == 0)
6312 lang_set = TRUE;
6313 }
6314 delete_aspell_dict_info_enumeration(dels);
6315 #endif /* USE_ENCHANT */
6316
6317 compose->dict_list = dict_list;
6318
6319 menu = gtk_menu_new();
6320
6321 for (cur = dict_list; cur != NULL; cur = cur->next) {
6322 gchar *dict = (gchar *)cur->data;
6323 GtkWidget *item;
6324
6325 if (dict == NULL) continue;
6326
6327 item = gtk_radio_menu_item_new_with_label(menu_list, dict);
6328 menu_list = gtk_radio_menu_item_get_group
6329 (GTK_RADIO_MENU_ITEM(item));
6330 if (compose->spell_lang != NULL &&
6331 g_ascii_strcasecmp(compose->spell_lang, dict) == 0)
6332 gtk_check_menu_item_set_active
6333 (GTK_CHECK_MENU_ITEM(item), TRUE);
6334 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
6335 g_signal_connect(G_OBJECT(item), "activate",
6336 G_CALLBACK(compose_set_spell_lang_cb),
6337 compose);
6338 g_object_set_data(G_OBJECT(item), "spell-lang", dict);
6339 gtk_widget_show(item);
6340
6341 if (!lang_set && g_ascii_strcasecmp("en", dict) == 0) {
6342 g_free(compose->spell_lang);
6343 compose->spell_lang = g_strdup("en");
6344 gtk_check_menu_item_set_active
6345 (GTK_CHECK_MENU_ITEM(item), TRUE);
6346 }
6347 }
6348
6349 gtk_widget_show(menu);
6350 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->spell_menu), menu);
6351 }
6352
6353 static void compose_change_spell_lang_menu(Compose *compose, const gchar *lang)
6354 {
6355 GtkWidget *menu;
6356 GtkWidget *def_item = NULL;
6357 GList *cur_item;
6358 const gchar *dict;
6359
6360 if (!lang)
6361 return;
6362
6363 menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(compose->spell_menu));
6364 for (cur_item = GTK_MENU_SHELL(menu)->children; cur_item != NULL;
6365 cur_item = cur_item->next) {
6366 if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(cur_item->data)))
6367 def_item = GTK_WIDGET(cur_item->data);
6368 dict = g_object_get_data(G_OBJECT(cur_item->data), "spell-lang");
6369 if (dict && !g_ascii_strcasecmp(dict, lang)) {
6370 gtk_check_menu_item_set_active
6371 (GTK_CHECK_MENU_ITEM(cur_item->data), TRUE);
6372 return;
6373 }
6374 }
6375
6376 if (def_item) {
6377 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(def_item),
6378 TRUE);
6379 compose_set_spell_lang_cb(def_item, compose);
6380 }
6381 }
6382 #endif /* USE_GTKSPELL */
6383
6384 static void compose_set_template_menu(Compose *compose)
6385 {
6386 GSList *tmpl_list, *cur;
6387 GtkWidget *menu;
6388 GtkWidget *item;
6389
6390 tmpl_list = template_get_config();
6391
6392 menu = gtk_menu_new();
6393
6394 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
6395 Template *tmpl = (Template *)cur->data;
6396
6397 item = gtk_menu_item_new_with_label(tmpl->name);
6398 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
6399 g_signal_connect(G_OBJECT(item), "activate",
6400 G_CALLBACK(compose_template_activate_cb),
6401 compose);
6402 g_object_set_data(G_OBJECT(item), "template", tmpl);
6403 gtk_widget_show(item);
6404 }
6405
6406 gtk_widget_show(menu);
6407 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
6408 }
6409
6410 void compose_reflect_prefs_all(void)
6411 {
6412 GList *cur;
6413 Compose *compose;
6414
6415 for (cur = compose_list; cur != NULL; cur = cur->next) {
6416 compose = (Compose *)cur->data;
6417
6418 if (compose->autosave_tag > 0) {
6419 g_source_remove(compose->autosave_tag);
6420 compose->autosave_tag = 0;
6421 }
6422
6423 compose_set_template_menu(compose);
6424
6425 if (prefs_common.enable_autosave &&
6426 prefs_common.autosave_itv > 0 &&
6427 compose->mode != COMPOSE_REDIRECT)
6428 compose->autosave_tag =
6429 g_timeout_add_full
6430 (G_PRIORITY_LOW,
6431 prefs_common.autosave_itv * 60 * 1000,
6432 autosave_timeout, compose, NULL);
6433 }
6434 }
6435
6436 GtkWidget *compose_get_toolbar(Compose *compose)
6437 {
6438 g_return_val_if_fail(compose != NULL, NULL);
6439 return compose->toolbar;
6440 }
6441
6442 GtkWidget *compose_get_misc_hbox(Compose *compose)
6443 {
6444 g_return_val_if_fail(compose != NULL, NULL);
6445 return compose->misc_hbox;
6446 }
6447
6448 GtkWidget *compose_get_textview(Compose *compose)
6449 {
6450 g_return_val_if_fail(compose != NULL, NULL);
6451 return compose->text;
6452 }
6453
6454 static void compose_template_apply(Compose *compose, Template *tmpl,
6455 gboolean replace)
6456 {
6457 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
6458 GtkTextBuffer *buffer;
6459 GtkTextMark *mark;
6460 GtkTextIter iter;
6461 gchar *parsed_str = NULL;
6462
6463 if (!tmpl || !tmpl->value) return;
6464
6465 buffer = gtk_text_view_get_buffer(text);
6466
6467 if (tmpl->to && *tmpl->to != '\0')
6468 compose_entry_set(compose, tmpl->to, COMPOSE_ENTRY_TO);
6469 if (tmpl->cc && *tmpl->cc != '\0')
6470 compose_entry_set(compose, tmpl->cc, COMPOSE_ENTRY_CC);
6471 if (tmpl->bcc && *tmpl->bcc != '\0')
6472 compose_entry_set(compose, tmpl->bcc, COMPOSE_ENTRY_BCC);
6473 if (tmpl->replyto && *tmpl->replyto != '\0')
6474 compose_entry_set(compose, tmpl->replyto,
6475 COMPOSE_ENTRY_REPLY_TO);
6476 if (tmpl->subject && *tmpl->subject != '\0')
6477 compose_entry_set(compose, tmpl->subject,
6478 COMPOSE_ENTRY_SUBJECT);
6479
6480 if (replace)
6481 gtk_text_buffer_set_text(buffer, "", 0);
6482
6483 mark = gtk_text_buffer_get_insert(buffer);
6484 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6485
6486 if (compose->reply_target) {
6487 FolderItem *item;
6488 gint num;
6489 MsgInfo *msginfo = NULL;
6490 MsgInfo *full_msginfo;
6491
6492 item = folder_find_item_and_num_from_id(compose->reply_target,
6493 &num);
6494 if (item && num > 0) {
6495 msginfo = folder_item_get_msginfo(item, num);
6496 if (msginfo) {
6497 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
6498 if (full_msginfo) {
6499 procmsg_msginfo_free(msginfo);
6500 msginfo = full_msginfo;
6501 }
6502 }
6503 }
6504 parsed_str = compose_quote_fmt(compose, msginfo,
6505 tmpl->value,
6506 prefs_common.quotemark, NULL);
6507 procmsg_msginfo_free(msginfo);
6508 } else if (compose->forward_targets) {
6509 FolderItem *item;
6510 gint num;
6511 gchar **targets;
6512 gint i;
6513 MsgInfo *msginfo;
6514 MsgInfo *full_msginfo;
6515
6516 targets = g_strsplit(compose->forward_targets, "\n", 0);
6517
6518 for (i = 0; targets[i] != NULL; i++) {
6519 g_strstrip(targets[i]);
6520 item = folder_find_item_and_num_from_id(targets[i], &num);
6521 if (!item || num <= 0)
6522 continue;
6523 msginfo = procmsg_get_msginfo(item, num);
6524 if (!msginfo)
6525 continue;
6526 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
6527 parsed_str = compose_quote_fmt(compose, full_msginfo ? full_msginfo : msginfo,
6528 tmpl->value,
6529 prefs_common.fw_quotemark, NULL);
6530 procmsg_msginfo_free(full_msginfo);
6531 procmsg_msginfo_free(msginfo);
6532 }
6533 g_strfreev(targets);
6534 } else {
6535 parsed_str = compose_quote_fmt(compose, NULL, tmpl->value,
6536 NULL, NULL);
6537 }
6538
6539 if (replace && parsed_str && prefs_common.auto_sig)
6540 compose_insert_sig(compose, TRUE, FALSE, FALSE);
6541
6542 if (replace && parsed_str) {
6543 gtk_text_buffer_get_start_iter(buffer, &iter);
6544 gtk_text_buffer_place_cursor(buffer, &iter);
6545 }
6546
6547 if (parsed_str)
6548 compose_changed_cb(NULL, compose);
6549 }
6550
6551 static void compose_destroy(Compose *compose)
6552 {
6553 GtkTreeModel *model = GTK_TREE_MODEL(compose->attach_store);
6554 GtkTreeIter iter;
6555 gboolean valid;
6556 AttachInfo *ainfo;
6557 GtkTextBuffer *buffer;
6558 GtkClipboard *clipboard;
6559
6560 compose_list = g_list_remove(compose_list, compose);
6561
6562 if (compose->autosave_tag > 0)
6563 g_source_remove(compose->autosave_tag);
6564
6565 syl_plugin_signal_emit("compose-destroy", compose);
6566
6567 /* NOTE: address_completion_end() does nothing with the window
6568 * however this may change. */
6569 address_completion_end(compose->window);
6570
6571 #if USE_GTKSPELL
6572 slist_free_strings(compose->dict_list);
6573 g_slist_free(compose->dict_list);
6574 g_free(compose->spell_lang);
6575 #endif
6576
6577 slist_free_strings(compose->to_list);
6578 g_slist_free(compose->to_list);
6579 slist_free_strings(compose->newsgroup_list);
6580 g_slist_free(compose->newsgroup_list);
6581
6582 procmsg_msginfo_free(compose->targetinfo);
6583 g_free(compose->reply_target);
6584 g_free(compose->forward_targets);
6585 g_free(compose->replyto);
6586 g_free(compose->cc);
6587 g_free(compose->bcc);
6588 g_free(compose->newsgroups);
6589 g_free(compose->followup_to);
6590
6591 g_free(compose->ml_post);
6592
6593 g_free(compose->inreplyto);
6594 g_free(compose->references);
6595 g_free(compose->msgid);
6596 g_free(compose->boundary);
6597
6598 if (compose->undostruct)
6599 undo_destroy(compose->undostruct);
6600
6601 if (compose->exteditor_file) {
6602 g_unlink(compose->exteditor_file);
6603 g_free(compose->exteditor_file);
6604 }
6605
6606 for (valid = gtk_tree_model_get_iter_first(model, &iter); valid;
6607 valid = gtk_tree_model_iter_next(model, &iter)) {
6608 gtk_tree_model_get(model, &iter, COL_ATTACH_INFO, &ainfo, -1);
6609 compose_attach_info_free(ainfo);
6610 }
6611
6612 if (addressbook_get_target_compose() == compose)
6613 addressbook_set_target_compose(NULL);
6614
6615 prefs_common.compose_maximized = compose->window_maximized;
6616 if (!prefs_common.compose_maximized) {
6617 gtkut_widget_get_uposition(compose->window,
6618 &prefs_common.compose_x,
6619 &prefs_common.compose_y);
6620 prefs_common.compose_width =
6621 compose->scrolledwin->allocation.width;
6622 prefs_common.compose_height =
6623 compose->window->allocation.height;
6624 }
6625
6626 if (!gtk_widget_get_parent(compose->paned))
6627 gtk_widget_destroy(compose->paned);
6628 gtk_widget_destroy(compose->popupmenu);
6629
6630 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
6631 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6632 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
6633
6634 g_object_unref(compose->toolbar_tip);
6635
6636 gtk_widget_destroy(compose->window);
6637
6638 g_free(compose);
6639 }
6640
6641 static void compose_attach_info_free(AttachInfo *ainfo)
6642 {
6643 g_free(ainfo->file);
6644 g_free(ainfo->content_type);
6645 g_free(ainfo->name);
6646 g_free(ainfo);
6647 }
6648
6649 static void compose_attach_remove_selected(Compose *compose)
6650 {
6651 GtkTreeModel *model = GTK_TREE_MODEL(compose->attach_store);
6652 GtkTreeSelection *selection;
6653 GtkTreeIter iter;
6654 GList *rows, *cur;
6655 AttachInfo *ainfo;
6656
6657 selection = gtk_tree_view_get_selection
6658 (GTK_TREE_VIEW(compose->attach_treeview));
6659
6660 rows = gtk_tree_selection_get_selected_rows(selection, NULL);
6661
6662 /* delete from below so that GtkTreePath doesn't point wrong row */
6663 rows = g_list_reverse(rows);
6664
6665 for (cur = rows; cur != NULL; cur = cur->next) {
6666 gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)cur->data);
6667 gtk_tree_model_get(model, &iter, COL_ATTACH_INFO, &ainfo, -1);
6668 compose_attach_info_free(ainfo);
6669 gtk_list_store_remove(compose->attach_store, &iter);
6670 gtk_tree_path_free((GtkTreePath *)cur->data);
6671 }
6672
6673 g_list_free(rows);
6674
6675 syl_plugin_signal_emit("compose-attach-changed", compose);
6676 }
6677
6678 void compose_attach_remove_all(Compose *compose)
6679 {
6680 GtkTreeModel *model = GTK_TREE_MODEL(compose->attach_store);
6681 GtkTreeIter iter;
6682 AttachInfo *ainfo;
6683
6684 g_return_if_fail(compose != NULL);
6685
6686 if (gtk_tree_model_get_iter_first(model, &iter) == FALSE)
6687 return;
6688
6689 do {
6690 gtk_tree_model_get(model, &iter, COL_ATTACH_INFO, &ainfo, -1);
6691 compose_attach_info_free(ainfo);
6692 } while (gtk_tree_model_iter_next(model, &iter) == TRUE);
6693
6694 gtk_list_store_clear(GTK_LIST_STORE(model));
6695
6696 syl_plugin_signal_emit("compose-attach-changed", compose);
6697 }
6698
6699 GSList *compose_get_attach_list(Compose *compose)
6700 {
6701 GtkTreeModel *model = GTK_TREE_MODEL(compose->attach_store);
6702 GtkTreeIter iter;
6703 gboolean valid;
6704 AttachInfo *ainfo;
6705 GSList *alist = NULL;
6706
6707 if (!compose->use_attach)
6708 return NULL;
6709
6710 for (valid = gtk_tree_model_get_iter_first(model, &iter); valid;
6711 valid = gtk_tree_model_iter_next(model, &iter)) {
6712 gtk_tree_model_get(model, &iter, COL_ATTACH_INFO, &ainfo, -1);
6713 alist = g_slist_append(alist, ainfo);
6714 }
6715
6716 return alist;
6717 }
6718
6719 static struct _AttachProperty
6720 {
6721 GtkWidget *window;
6722 GtkWidget *mimetype_entry;
6723 GtkWidget *encoding_optmenu;
6724 GtkWidget *path_entry;
6725 GtkWidget *filename_entry;
6726 GtkWidget *ok_btn;
6727 GtkWidget *cancel_btn;
6728 } attach_prop;
6729
6730 static void compose_attach_property(Compose *compose)
6731 {
6732 GtkTreeModel *model = GTK_TREE_MODEL(compose->attach_store);
6733 GtkTreeSelection *selection;
6734 GtkTreeIter iter;
6735 GList *rows;
6736 AttachInfo *ainfo;
6737 gchar *path = NULL;
6738 GtkOptionMenu *optmenu;
6739 static gboolean cancelled;
6740
6741 selection = gtk_tree_view_get_selection
6742 (GTK_TREE_VIEW(compose->attach_treeview));
6743
6744 rows = gtk_tree_selection_get_selected_rows(selection, NULL);
6745
6746 if (!rows)
6747 return;
6748
6749 gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)rows->data);
6750 gtk_tree_model_get(model, &iter, COL_ATTACH_INFO, &ainfo, -1);
6751
6752 g_list_foreach(rows, (GFunc)gtk_tree_path_free, NULL);
6753 g_list_free(rows);
6754
6755 compose_attach_property_create(&cancelled);
6756 gtk_widget_grab_focus(attach_prop.ok_btn);
6757 gtk_widget_show(attach_prop.window);
6758 manage_window_focus_in(compose->window, NULL, NULL);
6759 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
6760
6761 optmenu = GTK_OPTION_MENU(attach_prop.encoding_optmenu);
6762 if (ainfo->encoding == ENC_UNKNOWN)
6763 gtk_option_menu_set_history(optmenu, ENC_BASE64);
6764 else
6765 gtk_option_menu_set_history(optmenu, ainfo->encoding);
6766
6767 if (ainfo->file)
6768 path = conv_filename_to_utf8(ainfo->file);
6769
6770 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
6771 ainfo->content_type ? ainfo->content_type : "");
6772 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry), path ? path : "");
6773 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
6774 ainfo->name ? ainfo->name : "");
6775
6776 g_free(path);
6777
6778 for (;;) {
6779 const gchar *entry_text;
6780 gchar *text;
6781 gchar *cnttype = NULL;
6782 gchar *file = NULL;
6783 off_t size = 0;
6784 GtkWidget *menu;
6785 GtkWidget *menuitem;
6786
6787 cancelled = FALSE;
6788 gtk_main();
6789
6790 if (cancelled == TRUE)
6791 break;
6792
6793 entry_text = gtk_entry_get_text
6794 (GTK_ENTRY(attach_prop.mimetype_entry));
6795 if (*entry_text != '\0') {
6796 gchar *p;
6797
6798 text = g_strstrip(g_strdup(entry_text));
6799 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
6800 cnttype = g_strdup(text);
6801 g_free(text);
6802 } else {
6803 alertpanel_error(_("Invalid MIME type."));
6804 g_free(text);
6805 continue;
6806 }
6807 }
6808
6809 menu = gtk_option_menu_get_menu(optmenu);
6810 menuitem = gtk_menu_get_active(GTK_MENU(menu));
6811 ainfo->encoding = GPOINTER_TO_INT
6812 (g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID));
6813
6814 entry_text = gtk_entry_get_text
6815 (GTK_ENTRY(attach_prop.path_entry));
6816 if (*entry_text != '\0') {
6817 file = conv_filename_from_utf8(entry_text);
6818 if (!is_file_exist(file) ||
6819 (size = get_file_size(file)) <= 0) {
6820 alertpanel_error
6821 (_("File doesn't exist or is empty."));
6822 g_free(file);
6823 g_free(cnttype);
6824 continue;
6825 }
6826 g_free(ainfo->file);
6827 ainfo->file = file;
6828 }
6829
6830 entry_text = gtk_entry_get_text
6831 (GTK_ENTRY(attach_prop.filename_entry));
6832 if (*entry_text != '\0') {
6833 g_free(ainfo->name);
6834 ainfo->name = g_strdup(entry_text);
6835 }
6836
6837 if (cnttype) {
6838 g_free(ainfo->content_type);
6839 ainfo->content_type = cnttype;
6840 }
6841 if (size)
6842 ainfo->size = size;
6843
6844 gtk_list_store_set(compose->attach_store, &iter,
6845 COL_MIMETYPE, ainfo->content_type,
6846 COL_SIZE, to_human_readable(ainfo->size),
6847 COL_NAME, ainfo->name,
6848 -1);
6849 break;
6850 }
6851
6852 gtk_widget_destroy(attach_prop.window);
6853 memset(&attach_prop, 0, sizeof(attach_prop));
6854 }
6855
6856 #define SET_LABEL_AND_ENTRY(str, entry, top) \
6857 { \
6858 label = gtk_label_new(str); \
6859 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
6860 GTK_FILL, 0, 0, 0); \
6861 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
6862 \
6863 entry = gtk_entry_new(); \
6864 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
6865 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
6866 }
6867
6868 static void compose_attach_property_create(gboolean *cancelled)
6869 {
6870 GtkWidget *window;
6871 GtkWidget *vbox;
6872 GtkWidget *table;
6873 GtkWidget *label;
6874 GtkWidget *mimetype_entry;
6875 GtkWidget *hbox;
6876 GtkWidget *optmenu;
6877 GtkWidget *optmenu_menu;
6878 GtkWidget *menuitem;
6879 GtkWidget *path_entry;
6880 GtkWidget *filename_entry;
6881 GtkWidget *hbbox;
6882 GtkWidget *ok_btn;
6883 GtkWidget *cancel_btn;
6884
6885 debug_print("Creating attach property window...\n");
6886
6887 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
6888 gtk_widget_set_size_request(window, 480 * gtkut_get_dpi_multiplier(), -1);
6889 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
6890 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
6891 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
6892 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
6893 g_signal_connect(G_OBJECT(window), "delete_event",
6894 G_CALLBACK(attach_property_delete_event),
6895 cancelled);
6896 g_signal_connect(G_OBJECT(window), "key_press_event",
6897 G_CALLBACK(attach_property_key_pressed),
6898 cancelled);
6899
6900 vbox = gtk_vbox_new(FALSE, 8);
6901 gtk_container_add(GTK_CONTAINER(window), vbox);
6902
6903 table = gtk_table_new(4, 2, FALSE);
6904 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
6905 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
6906 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
6907
6908 SET_LABEL_AND_ENTRY(_("MIME type"), mimetype_entry, 0);
6909
6910 label = gtk_label_new(_("Encoding"));
6911 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
6912 GTK_FILL, 0, 0, 0);
6913 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
6914
6915 hbox = gtk_hbox_new(FALSE, 0);
6916 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
6917 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
6918
6919 optmenu = gtk_option_menu_new();
6920 gtk_box_pack_start(GTK_BOX(hbox), optmenu, FALSE, FALSE, 0);
6921
6922 optmenu_menu = gtk_menu_new();
6923 MENUITEM_ADD(optmenu_menu, menuitem, "7bit", ENC_7BIT);
6924 gtk_widget_set_sensitive(menuitem, FALSE);
6925 MENUITEM_ADD(optmenu_menu, menuitem, "8bit", ENC_8BIT);
6926 gtk_widget_set_sensitive(menuitem, FALSE);
6927 MENUITEM_ADD(optmenu_menu, menuitem, "quoted-printable",
6928 ENC_QUOTED_PRINTABLE);
6929 MENUITEM_ADD(optmenu_menu, menuitem, "base64", ENC_BASE64);
6930
6931 gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), optmenu_menu);
6932
6933 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
6934 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
6935
6936 gtkut_stock_button_set_create(&hbbox, &ok_btn, GTK_STOCK_OK,
6937 &cancel_btn, GTK_STOCK_CANCEL,
6938 NULL, NULL);
6939 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
6940 gtk_widget_grab_default(ok_btn);
6941
6942 g_signal_connect(G_OBJECT(ok_btn), "clicked",
6943 G_CALLBACK(attach_property_ok),
6944 cancelled);
6945 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
6946 G_CALLBACK(attach_property_cancel),
6947 cancelled);
6948
6949 gtk_widget_show_all(vbox);
6950
6951 attach_prop.window = window;
6952 attach_prop.mimetype_entry = mimetype_entry;
6953 attach_prop.encoding_optmenu = optmenu;
6954 attach_prop.path_entry = path_entry;
6955 attach_prop.filename_entry = filename_entry;
6956 attach_prop.ok_btn = ok_btn;
6957 attach_prop.cancel_btn = cancel_btn;
6958 }
6959
6960 #undef SET_LABEL_AND_ENTRY
6961
6962 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
6963 {
6964 *cancelled = FALSE;
6965 gtk_main_quit();
6966 }
6967
6968 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
6969 {
6970 *cancelled = TRUE;
6971 gtk_main_quit();
6972 }
6973
6974 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
6975 gboolean *cancelled)
6976 {
6977 *cancelled = TRUE;
6978 gtk_main_quit();
6979
6980 return TRUE;
6981 }
6982
6983 static gboolean attach_property_key_pressed(GtkWidget *widget,
6984 GdkEventKey *event,
6985 gboolean *cancelled)
6986 {
6987 if (event && event->keyval == GDK_Escape) {
6988 *cancelled = TRUE;
6989 gtk_main_quit();
6990 }
6991 return FALSE;
6992 }
6993
6994 static void compose_attach_open(Compose *compose)
6995 {
6996 GtkTreeModel *model = GTK_TREE_MODEL(compose->attach_store);
6997 GtkTreeSelection *selection;
6998 GtkTreeIter iter;
6999 GList *rows;
7000 AttachInfo *ainfo = NULL;
7001 #ifdef G_OS_WIN32
7002 DWORD dwtype;
7003 #endif
7004
7005 selection = gtk_tree_view_get_selection
7006 (GTK_TREE_VIEW(compose->attach_treeview));
7007
7008 rows = gtk_tree_selection_get_selected_rows(selection, NULL);
7009
7010 if (!rows)
7011 return;
7012
7013 gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)rows->data);
7014 gtk_tree_model_get(model, &iter, COL_ATTACH_INFO, &ainfo, -1);
7015
7016 g_list_foreach(rows, (GFunc)gtk_tree_path_free, NULL);
7017 g_list_free(rows);
7018
7019 if (!ainfo || !ainfo->file)
7020 return;
7021
7022 if (!is_file_exist(ainfo->file)) {
7023 alertpanel_error(_("File not exist."));
7024 return;
7025 }
7026
7027 #ifdef G_OS_WIN32
7028 if (g_file_test(ainfo->file, G_FILE_TEST_IS_EXECUTABLE) ||
7029 str_has_suffix_case(ainfo->file, ".scr") ||
7030 str_has_suffix_case(ainfo->file, ".pif") ||
7031 GetBinaryType(ainfo->file, &dwtype)) {
7032 alertpanel_full
7033 (_("Opening executable file"),
7034 _("This is an executable file. Opening executable file is restricted for security.\n"
7035 "If you want to launch it, save it to somewhere and make sure it is not an virus or something like a malicious program."),
7036 ALERT_WARNING, G_ALERTDEFAULT, FALSE,
7037 GTK_STOCK_OK, NULL, NULL);
7038 return;
7039 }
7040 execute_open_file(ainfo->file, ainfo->content_type);
7041 #else
7042 procmime_execute_open_file(ainfo->file, ainfo->content_type);
7043 #endif
7044 }
7045
7046 static void compose_exec_ext_editor(Compose *compose)
7047 {
7048 gchar *tmp;
7049 GPid pid;
7050 static gchar *def_cmd = "emacs %s";
7051 gchar buf[1024];
7052 gchar **cmdline;
7053 GError *error = NULL;
7054
7055 tmp = g_strdup_printf("%s%ctmpmsg-%p.txt", get_tmp_dir(),
7056 G_DIR_SEPARATOR, compose);
7057
7058 if (compose_write_body_to_file(compose, tmp) < 0) {
7059 g_warning("Coundn't write to file: %s\n", tmp);
7060 g_free(tmp);
7061 return;
7062 }
7063 #ifdef G_OS_WIN32
7064 if (canonicalize_file_replace(tmp) < 0) {
7065 g_warning("Coundn't write to file: %s\n", tmp);
7066 g_free(tmp);
7067 return;
7068 }
7069 #endif
7070
7071 if (prefs_common.ext_editor_cmd &&
7072 str_find_format_times(prefs_common.ext_editor_cmd, 's') == 1)
7073 g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, tmp);
7074 else {
7075 if (prefs_common.ext_editor_cmd)
7076 g_warning(_("External editor command line is invalid: `%s'\n"),
7077 prefs_common.ext_editor_cmd);
7078 g_snprintf(buf, sizeof(buf), def_cmd, tmp);
7079 }
7080
7081 cmdline = strsplit_with_quote(buf, " ", 1024);
7082
7083 if (g_spawn_async(NULL, cmdline, NULL,
7084 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
7085 NULL, NULL, &pid, &error) == FALSE) {
7086 g_warning("Couldn't execute external editor: %s\n", buf);
7087 if (error) {
7088 g_warning("g_spawn_async(): %s\n", error->message);
7089 g_error_free(error);
7090 }
7091 g_strfreev(cmdline);
7092 g_unlink(tmp);
7093 g_free(tmp);
7094 return;
7095 }
7096 if (pid == 0) {
7097 g_warning("Couldn't get PID of external editor\n");
7098 g_strfreev(cmdline);
7099 g_unlink(tmp);
7100 g_free(tmp);
7101 return;
7102 }
7103
7104 g_strfreev(cmdline);
7105
7106 compose_set_ext_editor_sensitive(compose, FALSE);
7107
7108 debug_print("compose_exec_ext_editor(): pid: %d file: %s\n", pid, tmp);
7109
7110 compose->exteditor_file = tmp;
7111 compose->exteditor_pid = pid;
7112 compose->exteditor_tag =
7113 g_child_watch_add(pid, compose_ext_editor_child_exit, compose);
7114 }
7115
7116 static gboolean compose_ext_editor_kill(Compose *compose)
7117 {
7118 #ifdef G_OS_WIN32
7119 DWORD exitcode;
7120 #endif
7121 gint ret;
7122
7123 g_return_val_if_fail(compose->exteditor_pid != 0, TRUE);
7124
7125 #ifdef G_OS_WIN32
7126 ret = GetExitCodeProcess(compose->exteditor_pid, &exitcode);
7127
7128 if (ret && exitcode == STILL_ACTIVE) {
7129 #else
7130 ret = kill(compose->exteditor_pid, 0);
7131
7132 if (ret == 0 || (ret == -1 && EPERM == errno)) {
7133 #endif
7134 AlertValue val;
7135 gchar *msg;
7136
7137 msg = g_strdup_printf
7138 (_("The external editor is still working.\n"
7139 "Force terminating the process (pid: %d)?\n"),
7140 compose->exteditor_pid);
7141 val = alertpanel_full(_("Notice"), msg, ALERT_NOTICE,
7142 G_ALERTALTERNATE, FALSE,
7143 GTK_STOCK_YES, GTK_STOCK_NO, NULL);
7144 g_free(msg);
7145
7146 if (val != G_ALERTDEFAULT)
7147 return FALSE;
7148 }
7149
7150 if (compose->exteditor_tag != 0) {
7151 g_source_remove(compose->exteditor_tag);
7152 compose->exteditor_tag = 0;
7153 }
7154
7155 if (compose->exteditor_pid != 0) {
7156 #ifdef G_OS_WIN32
7157 if (TerminateProcess(compose->exteditor_pid, 1) == 0)
7158 g_warning("TerminateProcess() failed: %d\n",
7159 GetLastError());
7160 #else
7161 if (kill(compose->exteditor_pid, SIGTERM) < 0)
7162 perror("kill");
7163 #endif
7164 g_message("Terminated process group id: %d\n",
7165 compose->exteditor_pid);
7166 g_message("Temporary file: %s\n",
7167 compose->exteditor_file);
7168 compose->exteditor_pid = 0;
7169 }
7170
7171 if (compose->exteditor_file) {
7172 g_unlink(compose->exteditor_file);
7173 g_free(compose->exteditor_file);
7174 compose->exteditor_file = NULL;
7175 }
7176
7177 compose_set_ext_editor_sensitive(compose, TRUE);
7178
7179 return TRUE;
7180 }
7181
7182 static void compose_ext_editor_child_exit(GPid pid, gint status, gpointer data)
7183 {
7184 Compose *compose = (Compose *)data;
7185 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
7186 GtkTextBuffer *buffer;
7187 GtkTextMark *mark;
7188 GtkTextIter iter;
7189
7190 debug_print("Compose: child exit (pid: %d status: %d)\n", pid, status);
7191
7192 compose_lock(compose);
7193
7194 g_spawn_close_pid(pid);
7195
7196 buffer = gtk_text_view_get_buffer(text);
7197
7198 gtk_text_buffer_set_text(buffer, "", 0);
7199 compose_insert_file(compose, compose->exteditor_file, FALSE);
7200 compose_enable_sig(compose);
7201
7202 gtk_text_buffer_get_start_iter(buffer, &iter);
7203 gtk_text_buffer_place_cursor(buffer, &iter);
7204 mark = gtk_text_buffer_get_insert(buffer);
7205 gtk_text_view_scroll_mark_onscreen(text, mark);
7206
7207 compose_changed_cb(NULL, compose);
7208
7209 if (g_unlink(compose->exteditor_file) < 0)
7210 FILE_OP_ERROR(compose->exteditor_file, "unlink");
7211
7212 compose_set_ext_editor_sensitive(compose, TRUE);
7213 gtk_window_present(GTK_WINDOW(compose->window));
7214
7215 g_free(compose->exteditor_file);
7216 compose->exteditor_file = NULL;
7217 compose->exteditor_pid = 0;
7218 compose->exteditor_tag = 0;
7219
7220 compose_unlock(compose);
7221 }
7222
7223 static void compose_set_ext_editor_sensitive(Compose *compose,
7224 gboolean sensitive)
7225 {
7226 GtkItemFactory *ifactory;
7227
7228 ifactory = gtk_item_factory_from_widget(compose->menubar);
7229
7230 menu_set_sensitive(ifactory, "/File/Send", sensitive);
7231 menu_set_sensitive(ifactory, "/File/Send later", sensitive);
7232 menu_set_sensitive(ifactory, "/File/Save to draft folder",
7233 sensitive);
7234 menu_set_sensitive(ifactory, "/File/Insert file", sensitive);
7235 menu_set_sensitive(ifactory, "/File/Insert signature", sensitive);
7236 menu_set_sensitive(ifactory, "/File/Append signature", sensitive);
7237 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
7238 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
7239 menu_set_sensitive(ifactory, "/Tools/Edit with external editor",
7240 sensitive);
7241
7242 #define SET_SENS(w) \
7243 if (compose->w) \
7244 gtk_widget_set_sensitive(compose->w, sensitive);
7245
7246 SET_SENS(text);
7247 SET_SENS(send_btn);
7248 SET_SENS(sendl_btn);
7249 SET_SENS(draft_btn);
7250 SET_SENS(insert_btn);
7251 SET_SENS(sig_btn);
7252 SET_SENS(exteditor_btn);
7253 SET_SENS(linewrap_btn);
7254
7255 #undef SET_SENS
7256 }
7257
7258 /**
7259 * compose_undo_state_changed:
7260 *
7261 * Change the sensivity of the menuentries undo and redo
7262 **/
7263 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
7264 gint redo_state, gpointer data)
7265 {
7266 GtkWidget *widget = GTK_WIDGET(data);
7267 GtkItemFactory *ifactory;
7268
7269 g_return_if_fail(widget != NULL);
7270
7271 ifactory = gtk_item_factory_from_widget(widget);
7272
7273 switch (undo_state) {
7274 case UNDO_STATE_TRUE:
7275 if (!undostruct->undo_state) {
7276 debug_print ("Set_undo - Testpoint\n");
7277 undostruct->undo_state = TRUE;
7278 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
7279 }
7280 break;
7281 case UNDO_STATE_FALSE:
7282 if (undostruct->undo_state) {
7283 undostruct->undo_state = FALSE;
7284 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
7285 }
7286 break;
7287 case UNDO_STATE_UNCHANGED:
7288 break;
7289 case UNDO_STATE_REFRESH:
7290 menu_set_sensitive(ifactory, "/Edit/Undo",
7291 undostruct->undo_state);
7292 break;
7293 default:
7294 g_warning("Undo state not recognized");
7295 break;
7296 }
7297
7298 switch (redo_state) {
7299 case UNDO_STATE_TRUE:
7300 if (!undostruct->redo_state) {
7301 undostruct->redo_state = TRUE;
7302 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
7303 }
7304 break;
7305 case UNDO_STATE_FALSE:
7306 if (undostruct->redo_state) {
7307 undostruct->redo_state = FALSE;
7308 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
7309 }
7310 break;
7311 case UNDO_STATE_UNCHANGED:
7312 break;
7313 case UNDO_STATE_REFRESH:
7314 menu_set_sensitive(ifactory, "/Edit/Redo",
7315 undostruct->redo_state);
7316 break;
7317 default:
7318 g_warning("Redo state not recognized");
7319 break;
7320 }
7321 }
7322
7323 static gint calc_cursor_xpos(GtkTextView *text, gint extra, gint char_width)
7324 {
7325 #if 0
7326 gint cursor_pos;
7327
7328 cursor_pos = (text->cursor_pos_x - extra) / char_width;
7329 cursor_pos = MAX(cursor_pos, 0);
7330
7331 return cursor_pos;
7332 #endif
7333 return 0;
7334 }
7335
7336 /* callback functions */
7337
7338 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
7339 * includes "non-client" (windows-izm) in calculation, so this calculation
7340 * may not be accurate.
7341 */
7342 static gboolean compose_edit_size_alloc(GtkEditable *widget,
7343 GtkAllocation *allocation,
7344 GtkSHRuler *shruler)
7345 {
7346 if (prefs_common.show_ruler) {
7347 gint char_width = 0, char_height = 0;
7348 gint line_width_in_chars;
7349
7350 gtkut_get_font_size(GTK_WIDGET(widget),
7351 &char_width, &char_height);
7352 line_width_in_chars =
7353 (allocation->width - allocation->x) / char_width;
7354
7355 /* got the maximum */
7356 gtk_ruler_set_range(GTK_RULER(shruler),
7357 0.0, line_width_in_chars,
7358 calc_cursor_xpos(GTK_TEXT_VIEW(widget),
7359 allocation->x,
7360 char_width),
7361 /*line_width_in_chars*/ char_width);
7362 }
7363
7364 return TRUE;
7365 }
7366
7367 static void toolbar_send_cb(GtkWidget *widget, gpointer data)
7368 {
7369 compose_send((Compose *)data, TRUE);
7370 }
7371
7372 static void toolbar_send_later_cb(GtkWidget *widget, gpointer data)
7373 {
7374 compose_send_later_cb(data, 0, NULL);
7375 }
7376
7377 static void toolbar_draft_cb(GtkWidget *widget, gpointer data)
7378 {
7379 compose_draft_cb(data, 0, NULL);
7380 }
7381
7382 static void toolbar_insert_cb(GtkWidget *widget, gpointer data)
7383 {
7384 compose_insert_file_cb(data, 0, NULL);
7385 }
7386
7387 static void toolbar_attach_cb(GtkWidget *widget, gpointer data)
7388 {
7389 compose_attach_cb(data, 0, NULL);
7390 }
7391
7392 static void toolbar_sig_cb(GtkWidget *widget, gpointer data)
7393 {
7394 Compose *compose = (Compose *)data;
7395
7396 compose_insert_sig(compose, TRUE, TRUE, TRUE);
7397 }
7398
7399 static void toolbar_ext_editor_cb(GtkWidget *widget, gpointer data)
7400 {
7401 Compose *compose = (Compose *)data;
7402
7403 compose_exec_ext_editor(compose);
7404 }
7405
7406 static void toolbar_linewrap_cb(GtkWidget *widget, gpointer data)
7407 {
7408 Compose *compose = (Compose *)data;
7409
7410 compose_wrap_all(compose);
7411 }
7412
7413 static void toolbar_address_cb(GtkWidget *widget, gpointer data)
7414 {
7415 compose_address_cb(data, 0, NULL);
7416 }
7417
7418 static void toolbar_prefs_common_cb(GtkWidget *widget, gpointer data)
7419 {
7420 prefs_common_open();
7421 }
7422
7423 static void toolbar_prefs_account_cb(GtkWidget *widget, gpointer data)
7424 {
7425 account_open(cur_account);
7426 }
7427
7428 static void toolbar_customize(GtkWidget *widget, gpointer data)
7429 {
7430 Compose *compose = (Compose *)data;
7431 gint *visible_items;
7432 GList *item_list = NULL;
7433 GtkWidget *toolbar;
7434 gint ret;
7435 const gchar *setting;
7436
7437 if (prefs_common.compose_toolbar_setting &&
7438 *prefs_common.compose_toolbar_setting != '\0')
7439 setting = prefs_common.compose_toolbar_setting;
7440 else
7441 setting = prefs_toolbar_get_default_compose_setting_name_list();
7442 visible_items = prefs_toolbar_get_id_list_from_name_list(setting);
7443 ret = prefs_toolbar_open(TOOLBAR_COMPOSE, visible_items, &item_list);
7444 g_free(visible_items);
7445
7446 if (ret == 0) {
7447 gtk_widget_destroy(compose->toolbar);
7448 toolbar = compose_toolbar_create_from_list(compose, item_list);
7449 gtk_widget_set_size_request(toolbar, 300, -1);
7450 gtk_box_pack_start(GTK_BOX(compose->vbox), toolbar,
7451 FALSE, FALSE, 0);
7452 gtk_box_reorder_child(GTK_BOX(compose->vbox), toolbar, 1);
7453 compose->toolbar = toolbar;
7454 compose_set_toolbar_button_visibility(compose);
7455 g_free(prefs_common.compose_toolbar_setting);
7456 prefs_common.compose_toolbar_setting =
7457 prefs_toolbar_get_name_list_from_item_list(item_list);
7458 g_list_free(item_list);
7459 prefs_common_write_config();
7460
7461 syl_plugin_signal_emit("compose-toolbar-changed", compose);
7462 }
7463 }
7464
7465 static gboolean toolbar_button_pressed(GtkWidget *widget, GdkEventButton *event,
7466 gpointer data)
7467 {
7468 Compose *compose = (Compose *)data;
7469 GtkWidget *menu;
7470 GtkWidget *menuitem;
7471
7472 if (!event) return FALSE;
7473 if (event->button != 3) return FALSE;
7474
7475 menu = gtk_menu_new();
7476 gtk_widget_show(menu);
7477
7478 MENUITEM_ADD_WITH_MNEMONIC(menu, menuitem, _("_Customize toolbar..."),
7479 0);
7480 g_signal_connect(G_OBJECT(menuitem), "activate",
7481 G_CALLBACK(toolbar_customize), compose);
7482 g_signal_connect(G_OBJECT(menu), "selection_done",
7483 G_CALLBACK(gtk_widget_destroy), NULL);
7484
7485 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
7486 event->button, event->time);
7487
7488 return TRUE;
7489 }
7490
7491 static void account_activated(GtkMenuItem *menuitem, gpointer data)
7492 {
7493 Compose *compose = (Compose *)data;
7494
7495 PrefsAccount *ac;
7496
7497 ac = (PrefsAccount *)g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID);
7498 g_return_if_fail(ac != NULL);
7499
7500 if (ac != compose->account)
7501 compose_select_account(compose, ac, FALSE);
7502 }
7503
7504 static void sig_combo_changed(GtkComboBox *combo, gpointer data)
7505 {
7506 Compose *compose = (Compose *)data;
7507
7508 if (compose->mode != COMPOSE_REDIRECT && prefs_common.auto_sig)
7509 compose_insert_sig(compose, TRUE, TRUE, FALSE);
7510 }
7511
7512 static void attach_selection_changed(GtkTreeSelection *selection, gpointer data)
7513 {
7514 }
7515
7516 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
7517 gpointer data)
7518 {
7519 Compose *compose = (Compose *)data;
7520 GtkTreeView *treeview = GTK_TREE_VIEW(compose->attach_treeview);
7521 GtkTreeSelection *selection;
7522 GtkTreePath *path = NULL;
7523
7524 if (!event) return FALSE;
7525
7526 gtk_tree_view_get_path_at_pos(treeview, event->x, event->y,
7527 &path, NULL, NULL, NULL);
7528
7529 if (event->button == 2 && path)
7530 gtk_tree_view_set_cursor(treeview, path, NULL, FALSE);
7531
7532 if (event->button == 2 ||
7533 (event->button == 1 && event->type == GDK_2BUTTON_PRESS)) {
7534 compose_attach_property(compose);
7535 } else if (event->button == 3) {
7536 GList *rows;
7537 gboolean has_selection = FALSE;
7538
7539 selection = gtk_tree_view_get_selection(treeview);
7540 rows = gtk_tree_selection_get_selected_rows(selection, NULL);
7541 if (rows) {
7542 has_selection = TRUE;
7543 g_list_free(rows);
7544 }
7545 if (path)
7546 has_selection = TRUE;
7547 menu_set_sensitive(compose->popupfactory, "/Open", has_selection);
7548 menu_set_sensitive(compose->popupfactory, "/Add...", TRUE);
7549 menu_set_sensitive(compose->popupfactory, "/Remove", has_selection);
7550 menu_set_sensitive(compose->popupfactory, "/Properties...", has_selection);
7551 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
7552 NULL, NULL, event->button, event->time);
7553
7554 if (path &&
7555 gtk_tree_selection_path_is_selected(selection, path)) {
7556 gtk_tree_path_free(path);
7557 return TRUE;
7558 }
7559 }
7560
7561 gtk_tree_path_free(path);
7562 return FALSE;
7563 }
7564
7565 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
7566 gpointer data)
7567 {
7568 Compose *compose = (Compose *)data;
7569
7570 if (!event) return FALSE;
7571
7572 switch (event->keyval) {
7573 case GDK_Delete:
7574 compose_attach_remove_selected(compose);
7575 break;
7576 }
7577
7578 return FALSE;
7579 }
7580
7581 gint compose_send(Compose *compose, gboolean close_on_success)
7582 {
7583 gint val;
7584
7585 if (compose->lock_count > 0)
7586 return 1;
7587
7588 gtk_widget_set_sensitive(compose->vbox, FALSE);
7589 val = compose_send_real(compose);
7590 gtk_widget_set_sensitive(compose->vbox, TRUE);
7591
7592 if (val == 0 && close_on_success)
7593 compose_destroy(compose);
7594
7595 return val;
7596 }
7597
7598 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
7599 {
7600 Compose *compose = (Compose *)data;
7601
7602 compose_send(compose, TRUE);
7603 }
7604
7605 static void compose_send_later_cb(gpointer data, guint action,
7606 GtkWidget *widget)
7607 {
7608 Compose *compose = (Compose *)data;
7609 FolderItem *queue;
7610 gchar tmp[MAXPATHLEN + 1];
7611 gboolean cancel = FALSE;
7612
7613 if (compose->lock_count > 0)
7614 return;
7615
7616 C_LOCK();
7617
7618 if (compose_check_entries(compose) == FALSE) {
7619 C_UNLOCK();
7620 return;
7621 }
7622 if (compose_check_attachments(compose) == FALSE) {
7623 C_UNLOCK();
7624 return;
7625 }
7626 if (compose_check_recipients(compose) == FALSE) {
7627 C_UNLOCK();
7628 return;
7629 }
7630
7631 queue = account_get_special_folder(compose->account, F_QUEUE);
7632 if (!queue) {
7633 g_warning("can't find queue folder\n");
7634 C_UNLOCK();
7635 return;
7636 }
7637
7638 if (!FOLDER_IS_LOCAL(queue->folder)) {
7639 if (compose_check_activities(compose) == FALSE) {
7640 C_UNLOCK();
7641 return;
7642 }
7643 if (!main_window_toggle_online_if_offline(main_window_get())) {
7644 C_UNLOCK();
7645 return;
7646 }
7647 }
7648
7649 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.%p",
7650 get_tmp_dir(), G_DIR_SEPARATOR, compose);
7651
7652 if (compose->mode == COMPOSE_REDIRECT) {
7653 if (compose_redirect_write_to_file(compose, tmp) < 0) {
7654 alertpanel_error(_("Can't queue the message."));
7655 C_UNLOCK();
7656 return;
7657 }
7658 } else {
7659 if (compose_write_to_file(compose, tmp, FALSE) < 0) {
7660 alertpanel_error(_("Can't queue the message."));
7661 C_UNLOCK();
7662 return;
7663 }
7664 }
7665
7666 if (!compose->to_list && !compose->newsgroup_list) {
7667 g_warning("can't get recipient list.");
7668 g_unlink(tmp);
7669 C_UNLOCK();
7670 return;
7671 }
7672
7673 syl_plugin_signal_emit("compose-send", compose, compose->mode, 1,
7674 tmp, compose->to_list, &cancel);
7675 if (cancel) {
7676 g_unlink(tmp);
7677 C_UNLOCK();
7678 return;
7679 }
7680
7681 if (compose_queue(compose, tmp) < 0) {
7682 alertpanel_error(_("Can't queue the message."));
7683 g_unlink(tmp);
7684 C_UNLOCK();
7685 return;
7686 }
7687
7688 if (g_unlink(tmp) < 0)
7689 FILE_OP_ERROR(tmp, "unlink");
7690
7691 C_UNLOCK();
7692 compose_destroy(compose);
7693 }
7694
7695 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
7696 {
7697 Compose *compose = (Compose *)data;
7698 FolderItem *draft;
7699 gchar *tmp;
7700 gint msgnum;
7701 MsgFlags flag = {0, 0};
7702
7703 if (compose->lock_count > 0)
7704 return;
7705
7706 draft = account_get_special_folder(compose->account, F_DRAFT);
7707 g_return_if_fail(draft != NULL);
7708
7709 C_LOCK();
7710
7711 if (!FOLDER_IS_LOCAL(draft->folder)) {
7712 if (compose_check_activities(compose) == FALSE) {
7713 C_UNLOCK();
7714 return;
7715 }
7716 if (!main_window_toggle_online_if_offline(main_window_get())) {
7717 C_UNLOCK();
7718 return;
7719 }
7720 }
7721
7722 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
7723 G_DIR_SEPARATOR, compose);
7724
7725 if (compose_write_to_file(compose, tmp, TRUE) < 0) {
7726 g_free(tmp);
7727 C_UNLOCK();
7728 return;
7729 }
7730
7731 folder_item_scan(draft);
7732 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
7733 g_unlink(tmp);
7734 g_free(tmp);
7735 C_UNLOCK();
7736 return;
7737 }
7738 g_free(tmp);
7739
7740 if (compose->mode == COMPOSE_REEDIT) {
7741 compose_remove_reedit_target(compose);
7742 if (compose->targetinfo &&
7743 compose->targetinfo->folder != draft)
7744 folderview_update_item(compose->targetinfo->folder,
7745 TRUE);
7746 }
7747
7748 folder_item_scan(draft);
7749 folderview_update_item(draft, TRUE);
7750
7751 /* 0: quit editing 1: keep editing */
7752 if (action == 0) {
7753 C_UNLOCK();
7754 compose_destroy(compose);
7755 } else {
7756 GStatBuf s;
7757 gchar *path;
7758
7759 path = folder_item_fetch_msg(draft, msgnum);
7760 C_UNLOCK();
7761 g_return_if_fail(path != NULL);
7762 if (g_stat(path, &s) < 0) {
7763 FILE_OP_ERROR(path, "stat");
7764 g_free(path);
7765 return;
7766 }
7767 g_free(path);
7768
7769 procmsg_msginfo_free(compose->targetinfo);
7770 compose->targetinfo = g_new0(MsgInfo, 1);
7771 compose->targetinfo->msgnum = msgnum;
7772 compose->targetinfo->size = s.st_size;
7773 compose->targetinfo->mtime = s.st_mtime;
7774 compose->targetinfo->folder = draft;
7775 compose->mode = COMPOSE_REEDIT;
7776 compose->modified = FALSE;
7777 compose_set_title(compose);
7778 }
7779 }
7780
7781 static void compose_attach_open_cb(gpointer data, guint action,
7782 GtkWidget *widget)
7783 {
7784 Compose *compose = (Compose *)data;
7785
7786 compose_attach_open(compose);
7787 }
7788
7789 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
7790 {
7791 Compose *compose = (Compose *)data;
7792 GSList *files;
7793 GSList *cur;
7794
7795 files = filesel_select_files(_("Select files"), NULL,
7796 GTK_FILE_CHOOSER_ACTION_OPEN);
7797
7798 for (cur = files; cur != NULL; cur = cur->next) {
7799 gchar *file = (gchar *)cur->data;
7800 gchar *utf8_filename;
7801
7802 utf8_filename = conv_filename_to_utf8(file);
7803 compose_attach_append(compose, file, utf8_filename, NULL);
7804 compose_changed_cb(NULL, compose);
7805 g_free(utf8_filename);
7806 g_free(file);
7807 }
7808
7809 g_slist_free(files);
7810 }
7811
7812 static void compose_insert_file_cb(gpointer data, guint action,
7813 GtkWidget *widget)
7814 {
7815 Compose *compose = (Compose *)data;
7816 gchar *file;
7817
7818 file = filesel_select_file(_("Select file"), NULL,
7819 GTK_FILE_CHOOSER_ACTION_OPEN);
7820
7821 if (file && *file)
7822 compose_insert_file(compose, file, TRUE);
7823
7824 g_free(file);
7825 }
7826
7827 static void compose_insert_sig_cb(gpointer data, guint action,
7828 GtkWidget *widget)
7829 {
7830 Compose *compose = (Compose *)data;
7831
7832 compose_insert_sig(compose, action, TRUE, TRUE);
7833 }
7834
7835 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
7836 gpointer data)
7837 {
7838 compose_close_cb(data, 0, NULL);
7839 return TRUE;
7840 }
7841
7842 static gint compose_window_state_cb(GtkWidget *widget,
7843 GdkEventWindowState *event,
7844 gpointer data)
7845 {
7846 Compose *compose = (Compose *)data;
7847
7848 if ((event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) != 0) {
7849 if ((event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0)
7850 compose->window_maximized = TRUE;
7851 else
7852 compose->window_maximized = FALSE;
7853 }
7854
7855 return FALSE;
7856 }
7857
7858 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
7859 {
7860 Compose *compose = (Compose *)data;
7861 AlertValue val;
7862
7863 if (compose->lock_count > 0)
7864 return;
7865
7866 if (compose->exteditor_pid != 0) {
7867 if (!compose_ext_editor_kill(compose))
7868 return;
7869 }
7870
7871 if (compose->modified) {
7872 val = alertpanel(_("Save message"),
7873 _("This message has been modified. Save it to draft folder?"),
7874 #ifdef G_OS_WIN32
7875 GTK_STOCK_SAVE, _("Close _without saving"),
7876 GTK_STOCK_CANCEL);
7877 #else
7878 GTK_STOCK_SAVE, GTK_STOCK_CANCEL,
7879 _("Close _without saving"));
7880 #endif
7881
7882 switch (val) {
7883 case G_ALERTDEFAULT:
7884 compose_draft_cb(data, 0, NULL);
7885 return;
7886 #ifdef G_OS_WIN32
7887 case G_ALERTALTERNATE:
7888 #else
7889 case G_ALERTOTHER:
7890 #endif
7891 break;
7892 default:
7893 return;
7894 }
7895 }
7896
7897 compose_destroy(compose);
7898 }
7899
7900 static void compose_set_encoding_cb(gpointer data, guint action,
7901 GtkWidget *widget)
7902 {
7903 Compose *compose = (Compose *)data;
7904
7905 if (GTK_CHECK_MENU_ITEM(widget)->active)
7906 compose->out_encoding = (CharSet)action;
7907 }
7908
7909 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
7910 {
7911 Compose *compose = (Compose *)data;
7912
7913 addressbook_open(compose);
7914 }
7915
7916 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
7917 {
7918 Compose *compose = (Compose *)data;
7919 Template *tmpl;
7920 gchar *msg;
7921 AlertValue val;
7922
7923 tmpl = g_object_get_data(G_OBJECT(widget), "template");
7924 g_return_if_fail(tmpl != NULL);
7925
7926 msg = g_strdup_printf(_("Do you want to apply the template `%s' ?"),
7927 tmpl->name);
7928 val = alertpanel(_("Apply template"), msg,
7929 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
7930 g_free(msg);
7931
7932 if (val == G_ALERTDEFAULT)
7933 compose_template_apply(compose, tmpl, TRUE);
7934 else if (val == G_ALERTALTERNATE)
7935 compose_template_apply(compose, tmpl, FALSE);
7936 }
7937
7938 static void compose_ext_editor_cb(gpointer data, guint action,
7939 GtkWidget *widget)
7940 {
7941 Compose *compose = (Compose *)data;
7942
7943 compose_exec_ext_editor(compose);
7944 }
7945
7946 static void compose_undo_cb(Compose *compose)
7947 {
7948 gboolean prev_autowrap = compose->autowrap;
7949
7950 compose->autowrap = FALSE;
7951 undo_undo(compose->undostruct);
7952 compose->autowrap = prev_autowrap;
7953 }
7954
7955 static void compose_redo_cb(Compose *compose)
7956 {
7957 gboolean prev_autowrap = compose->autowrap;
7958
7959 compose->autowrap = FALSE;
7960 undo_redo(compose->undostruct);
7961 compose->autowrap = prev_autowrap;
7962 }
7963
7964 static void compose_cut_cb(Compose *compose)
7965 {
7966 if (compose->focused_editable &&
7967 GTK_WIDGET_HAS_FOCUS(compose->focused_editable)) {
7968 if (GTK_IS_EDITABLE(compose->focused_editable)) {
7969 gtk_editable_cut_clipboard
7970 (GTK_EDITABLE(compose->focused_editable));
7971 } else if (GTK_IS_TEXT_VIEW(compose->focused_editable)) {
7972 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
7973 GtkTextBuffer *buffer;
7974 GtkClipboard *clipboard;
7975
7976 buffer = gtk_text_view_get_buffer(text);
7977 clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
7978
7979 gtk_text_buffer_cut_clipboard(buffer, clipboard, TRUE);
7980 }
7981 }
7982 }
7983
7984 static void compose_copy_cb(Compose *compose)
7985 {
7986 if (compose->focused_editable &&
7987 GTK_WIDGET_HAS_FOCUS(compose->focused_editable)) {
7988 if (GTK_IS_EDITABLE(compose->focused_editable)) {
7989 gtk_editable_copy_clipboard
7990 (GTK_EDITABLE(compose->focused_editable));
7991 } else if (GTK_IS_TEXT_VIEW(compose->focused_editable)) {
7992 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
7993 GtkTextBuffer *buffer;
7994 GtkClipboard *clipboard;
7995
7996 buffer = gtk_text_view_get_buffer(text);
7997 clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
7998
7999 gtk_text_buffer_copy_clipboard(buffer, clipboard);
8000 }
8001 }
8002 }
8003
8004 static void compose_paste_cb(Compose *compose)
8005 {
8006 if (compose->focused_editable &&
8007 GTK_WIDGET_HAS_FOCUS(compose->focused_editable)) {
8008 if (GTK_IS_EDITABLE(compose->focused_editable)) {
8009 gtk_editable_paste_clipboard
8010 (GTK_EDITABLE(compose->focused_editable));
8011 } else if (GTK_IS_TEXT_VIEW(compose->focused_editable)) {
8012 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8013 GtkTextBuffer *buffer;
8014 GtkTextMark *mark;
8015 GtkClipboard *clipboard;
8016
8017 buffer = gtk_text_view_get_buffer(text);
8018 mark = gtk_text_buffer_get_insert(buffer);
8019 clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
8020
8021 gtk_text_buffer_paste_clipboard(buffer, clipboard,
8022 NULL, TRUE);
8023
8024 gtk_text_view_scroll_mark_onscreen(text, mark);
8025 }
8026 }
8027 }
8028
8029 static void compose_paste_as_quote_cb(Compose *compose)
8030 {
8031 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8032 GtkTextBuffer *buffer;
8033 GtkTextMark *mark;
8034 GtkClipboard *clipboard;
8035 gchar *str = NULL;
8036
8037 if (!compose->focused_editable ||
8038 !GTK_WIDGET_HAS_FOCUS(compose->focused_editable) ||
8039 !GTK_IS_TEXT_VIEW(compose->focused_editable))
8040 return;
8041
8042 buffer = gtk_text_view_get_buffer(text);
8043 mark = gtk_text_buffer_get_insert(buffer);
8044 clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
8045 str = gtk_clipboard_wait_for_text(clipboard);
8046 if (!str)
8047 return;
8048
8049 compose_quote_fmt(compose, NULL, "%Q", prefs_common.quotemark, str);
8050
8051 g_free(str);
8052
8053 gtk_text_view_scroll_mark_onscreen(text, mark);
8054 }
8055
8056 static void compose_allsel_cb(Compose *compose)
8057 {
8058 if (compose->focused_editable &&
8059 GTK_WIDGET_HAS_FOCUS(compose->focused_editable)) {
8060 if (GTK_IS_EDITABLE(compose->focused_editable)) {
8061 gtk_editable_select_region
8062 (GTK_EDITABLE(compose->focused_editable),
8063 0, -1);
8064 } else if (GTK_IS_TEXT_VIEW(compose->focused_editable)) {
8065 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8066 GtkTextBuffer *buffer;
8067 GtkTextIter iter;
8068
8069 buffer = gtk_text_view_get_buffer(text);
8070 gtk_text_buffer_get_start_iter(buffer, &iter);
8071 gtk_text_buffer_place_cursor(buffer, &iter);
8072 gtk_text_buffer_get_end_iter(buffer, &iter);
8073 gtk_text_buffer_move_mark_by_name
8074 (buffer, "selection_bound", &iter);
8075 }
8076 }
8077 }
8078
8079 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
8080 {
8081 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
8082 compose->focused_editable = widget;
8083 }
8084
8085 #if USE_GPGME
8086 static void compose_signing_toggled(GtkWidget *widget, Compose *compose)
8087 {
8088 GtkItemFactory *ifactory;
8089
8090 if (!rfc2015_is_available())
8091 return;
8092
8093 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
8094 compose->use_signing = TRUE;
8095 else
8096 compose->use_signing = FALSE;
8097
8098 ifactory = gtk_item_factory_from_widget(compose->menubar);
8099 menu_set_active(ifactory, "/Tools/PGP Sign", compose->use_signing);
8100 }
8101
8102 static void compose_encrypt_toggled(GtkWidget *widget, Compose *compose)
8103 {
8104 GtkItemFactory *ifactory;
8105
8106 if (!rfc2015_is_available())
8107 return;
8108
8109 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
8110 compose->use_encryption = TRUE;
8111 else
8112 compose->use_encryption = FALSE;
8113
8114 ifactory = gtk_item_factory_from_widget(compose->menubar);
8115 menu_set_active(ifactory, "/Tools/PGP Encrypt",
8116 compose->use_encryption);
8117 }
8118 #endif /* USE_GPGME */
8119
8120 #if 0
8121 static void compose_attach_toggled(GtkWidget *widget, Compose *compose)
8122 {
8123 GtkItemFactory *ifactory;
8124
8125 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
8126 compose->use_attach = TRUE;
8127 else
8128 compose->use_attach = FALSE;
8129
8130 ifactory = gtk_item_factory_from_widget(compose->menubar);
8131 menu_set_active(ifactory, "/View/Attachment", compose->use_attach);
8132 }
8133 #endif
8134
8135 static void compose_buffer_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
8136 {
8137 if (compose->modified == FALSE && compose->block_modified == FALSE) {
8138 compose->modified = TRUE;
8139 compose_set_title(compose);
8140 }
8141 }
8142
8143 static void compose_changed_cb(GtkEditable *editable, Compose *compose)
8144 {
8145 if (compose->block_modified == FALSE &&
8146 (compose->modified == FALSE ||
8147 editable == GTK_EDITABLE(compose->subject_entry))) {
8148 compose->modified = TRUE;
8149 compose_set_title(compose);
8150 }
8151 }
8152
8153 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
8154 {
8155 Compose *compose = (Compose *)data;
8156
8157 if (action == 1)
8158 compose_wrap_all(compose);
8159 else
8160 compose_wrap_paragraph(compose, NULL);
8161 }
8162
8163 static void compose_toggle_autowrap_cb(gpointer data, guint action,
8164 GtkWidget *widget)
8165 {
8166 Compose *compose = (Compose *)data;
8167
8168 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
8169 if (compose->autowrap)
8170 compose_wrap_all_full(compose, TRUE);
8171 }
8172
8173 static void compose_toggle_to_cb(gpointer data, guint action,
8174 GtkWidget *widget)
8175 {
8176 Compose *compose = (Compose *)data;
8177
8178 if (GTK_CHECK_MENU_ITEM(widget)->active) {
8179 gtk_widget_show(compose->to_hbox);
8180 gtk_widget_show(compose->to_entry);
8181 gtk_table_set_row_spacing(GTK_TABLE(compose->table), 1, 4);
8182 compose->use_to = TRUE;
8183 } else {
8184 gtk_widget_hide(compose->to_hbox);
8185 gtk_widget_hide(compose->to_entry);
8186 gtk_table_set_row_spacing(GTK_TABLE(compose->table), 1, 0);
8187 gtk_widget_queue_resize(compose->table_vbox);
8188 compose->use_to = FALSE;
8189 }
8190 }
8191
8192 static void compose_toggle_cc_cb(gpointer data, guint action,
8193 GtkWidget *widget)
8194 {
8195 Compose *compose = (Compose *)data;
8196
8197 if (GTK_CHECK_MENU_ITEM(widget)->active) {
8198 gtk_widget_show(compose->cc_hbox);
8199 gtk_widget_show(compose->cc_entry);
8200 gtk_table_set_row_spacing(GTK_TABLE(compose->table), 3, 4);
8201 compose->use_cc = TRUE;
8202 } else {
8203 gtk_widget_hide(compose->cc_hbox);
8204 gtk_widget_hide(compose->cc_entry);
8205 gtk_table_set_row_spacing(GTK_TABLE(compose->table), 3, 0);
8206 gtk_widget_queue_resize(compose->table_vbox);
8207 compose->use_cc = FALSE;
8208 }
8209 }
8210
8211 static void compose_toggle_bcc_cb(gpointer data, guint action,
8212 GtkWidget *widget)
8213 {
8214 Compose *compose = (Compose *)data;
8215
8216 if (GTK_CHECK_MENU_ITEM(widget)->active) {
8217 gtk_widget_show(compose->bcc_hbox);
8218 gtk_widget_show(compose->bcc_entry);
8219 gtk_table_set_row_spacing(GTK_TABLE(compose->table), 4, 4);
8220 compose->use_bcc = TRUE;
8221 } else {
8222 gtk_widget_hide(compose->bcc_hbox);
8223 gtk_widget_hide(compose->bcc_entry);
8224 gtk_table_set_row_spacing(GTK_TABLE(compose->table), 4, 0);
8225 gtk_widget_queue_resize(compose->table_vbox);
8226 compose->use_bcc = FALSE;
8227 }
8228 }
8229
8230 static void compose_toggle_replyto_cb(gpointer data, guint action,
8231 GtkWidget *widget)
8232 {
8233 Compose *compose = (Compose *)data;
8234
8235 if (GTK_CHECK_MENU_ITEM(widget)->active) {
8236 gtk_widget_show(compose->reply_hbox);
8237 gtk_widget_show(compose->reply_entry);
8238 gtk_table_set_row_spacing(GTK_TABLE(compose->table), 5, 4);
8239 compose->use_replyto = TRUE;
8240 } else {
8241 gtk_widget_hide(compose->reply_hbox);
8242 gtk_widget_hide(compose->reply_entry);
8243 gtk_table_set_row_spacing(GTK_TABLE(compose->table), 5, 0);
8244 gtk_widget_queue_resize(compose->table_vbox);
8245 compose->use_replyto = FALSE;
8246 }
8247 }
8248
8249 static void compose_toggle_followupto_cb(gpointer data, guint action,
8250 GtkWidget *widget)
8251 {
8252 Compose *compose = (Compose *)data;
8253
8254 if (GTK_CHECK_MENU_ITEM(widget)->active) {
8255 gtk_widget_show(compose->followup_hbox);
8256 gtk_widget_show(compose->followup_entry);
8257 gtk_table_set_row_spacing(GTK_TABLE(compose->table), 6, 4);
8258 compose->use_followupto = TRUE;
8259 } else {
8260 gtk_widget_hide(compose->followup_hbox);
8261 gtk_widget_hide(compose->followup_entry);
8262 gtk_table_set_row_spacing(GTK_TABLE(compose->table), 6, 0);
8263 gtk_widget_queue_resize(compose->table_vbox);
8264 compose->use_followupto = FALSE;
8265 }
8266 }
8267
8268 static void compose_toggle_attach_cb(gpointer data, guint action,
8269 GtkWidget *widget)
8270 {
8271 Compose *compose = (Compose *)data;
8272
8273 if (GTK_CHECK_MENU_ITEM(widget)->active) {
8274 gtk_widget_ref(compose->edit_vbox);
8275
8276 gtkut_container_remove(GTK_CONTAINER(compose->vbox2),
8277 compose->edit_vbox);
8278 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
8279 gtk_box_pack_start(GTK_BOX(compose->vbox2), compose->paned,
8280 TRUE, TRUE, 0);
8281 gtk_widget_show(compose->paned);
8282
8283 gtk_widget_unref(compose->edit_vbox);
8284 gtk_widget_unref(compose->paned);
8285
8286 compose->use_attach = TRUE;
8287 } else {
8288 gtk_widget_ref(compose->paned);
8289 gtk_widget_ref(compose->edit_vbox);
8290
8291 gtkut_container_remove(GTK_CONTAINER(compose->vbox2),
8292 compose->paned);
8293 gtkut_container_remove(GTK_CONTAINER(compose->paned),
8294 compose->edit_vbox);
8295 gtk_box_pack_start(GTK_BOX(compose->vbox2),
8296 compose->edit_vbox, TRUE, TRUE, 0);
8297
8298 gtk_widget_unref(compose->edit_vbox);
8299
8300 compose->use_attach = FALSE;
8301 }
8302
8303 #if 0
8304 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->attach_toggle),
8305 compose->use_attach);
8306 #endif
8307
8308 syl_plugin_signal_emit("compose-attach-changed", compose);
8309 }
8310
8311 static void compose_customize_toolbar_cb(gpointer data, guint action,
8312 GtkWidget *widget)
8313 {
8314 toolbar_customize(widget, data);
8315 }
8316
8317 static void compose_toggle_mdn_cb(gpointer data, guint action,
8318 GtkWidget *widget)
8319 {
8320 Compose *compose = (Compose *)data;
8321
8322 if (GTK_CHECK_MENU_ITEM(widget)->active)
8323 compose->use_mdn = TRUE;
8324 else
8325 compose->use_mdn = FALSE;
8326 }
8327
8328 #if USE_GPGME
8329 static void compose_toggle_sign_cb(gpointer data, guint action,
8330 GtkWidget *widget)
8331 {
8332 Compose *compose = (Compose *)data;
8333
8334 if (!rfc2015_is_available())
8335 return;
8336
8337 if (GTK_CHECK_MENU_ITEM(widget)->active)
8338 compose->use_signing = TRUE;
8339 else
8340 compose->use_signing = FALSE;
8341
8342 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->signing_chkbtn),
8343 compose->use_signing);
8344 }
8345
8346 static void compose_toggle_encrypt_cb(gpointer data, guint action,
8347 GtkWidget *widget)
8348 {
8349 Compose *compose = (Compose *)data;
8350
8351 if (!rfc2015_is_available())
8352 return;
8353
8354 if (GTK_CHECK_MENU_ITEM(widget)->active)
8355 compose->use_encryption = TRUE;
8356 else
8357 compose->use_encryption = FALSE;
8358
8359 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->encrypt_chkbtn),
8360 compose->use_encryption);
8361 }
8362 #endif /* USE_GPGME */
8363
8364 #if USE_GTKSPELL
8365 static void compose_toggle_spell_cb(gpointer data, guint action,
8366 GtkWidget *widget)
8367 {
8368 Compose *compose = (Compose *)data;
8369 GtkSpell *speller;
8370
8371 if (GTK_CHECK_MENU_ITEM(widget)->active) {
8372 debug_print("Spell checking enabled: %s\n",
8373 compose->spell_lang ? compose->spell_lang : "(none)");
8374 speller = gtkspell_new_attach(GTK_TEXT_VIEW(compose->text),
8375 compose->spell_lang, NULL);
8376 compose->check_spell = TRUE;
8377 } else {
8378 debug_print("Spell checking disabled\n");
8379 speller = gtkspell_get_from_text_view
8380 (GTK_TEXT_VIEW(compose->text));
8381 if (speller != NULL)
8382 gtkspell_detach(speller);
8383 compose->check_spell = FALSE;
8384 }
8385 }
8386
8387 static void compose_set_spell_lang_cb(GtkWidget *widget,
8388 gpointer data)
8389 {
8390 Compose *compose = (Compose *)data;
8391 gchar *dict;
8392 GtkSpell *speller;
8393
8394 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8395 return;
8396
8397 dict = g_object_get_data(G_OBJECT(widget), "spell-lang");
8398
8399 g_free(compose->spell_lang);
8400 compose->spell_lang = g_strdup(dict);
8401
8402 speller = gtkspell_get_from_text_view(GTK_TEXT_VIEW(compose->text));
8403 if (speller != NULL)
8404 gtkspell_set_language(speller, dict, NULL);
8405
8406 debug_print("Spell lang set to \"%s\"\n", dict);
8407 }
8408 #endif /* USE_GTKSPELL */
8409
8410 static void compose_toggle_ruler_cb(gpointer data, guint action,
8411 GtkWidget *widget)
8412 {
8413 Compose *compose = (Compose *)data;
8414
8415 if (GTK_CHECK_MENU_ITEM(widget)->active) {
8416 gtk_widget_show(compose->ruler_hbox);
8417 prefs_common.show_ruler = TRUE;
8418 } else {
8419 gtk_widget_hide(compose->ruler_hbox);
8420 gtk_widget_queue_resize(compose->edit_vbox);
8421 prefs_common.show_ruler = FALSE;
8422 }
8423 }
8424
8425 static void compose_attach_drag_received_cb (GtkWidget *widget,
8426 GdkDragContext *drag_context,
8427 gint x,
8428 gint y,
8429 GtkSelectionData *data,
8430 guint info,
8431 guint time,
8432 gpointer user_data)
8433 {
8434 Compose *compose = (Compose *)user_data;
8435 GList *list, *cur;
8436 gchar *path, *filename;
8437 gchar *content_type = NULL;
8438
8439 if (info == DRAG_TYPE_RFC822)
8440 content_type = "message/rfc822";
8441
8442 debug_print("compose_attach_drag_received_cb(): received %s\n",
8443 (const gchar *)data->data);
8444
8445 list = uri_list_extract_filenames((const gchar *)data->data);
8446 for (cur = list; cur != NULL; cur = cur->next) {
8447 path = (gchar *)cur->data;
8448 filename = conv_filename_to_utf8(path);
8449 compose_attach_append(compose, path, filename, content_type);
8450 compose_changed_cb(NULL, compose);
8451 g_free(filename);
8452 g_free(path);
8453 }
8454 if (list) compose_changed_cb(NULL, compose);
8455 g_list_free(list);
8456
8457 if ((drag_context->actions & GDK_ACTION_MOVE) != 0)
8458 drag_context->action = 0;
8459 gtk_drag_finish(drag_context, TRUE, FALSE, time);
8460 }
8461
8462 static void compose_insert_drag_received_cb (GtkWidget *widget,
8463 GdkDragContext *drag_context,
8464 gint x,
8465 gint y,
8466 GtkSelectionData *data,
8467 guint info,
8468 guint time,
8469 gpointer user_data)
8470 {
8471 static GdkDragContext *context_ = NULL;
8472 static gint x_ = -1, y_ = -1;
8473 static guint info_ = N_DRAG_TYPES;
8474 static guint time_ = G_MAXUINT;
8475
8476 debug_print("compose_insert_drag_received_cb(): received %s\n",
8477 (const gchar *)data->data);
8478
8479 /* FIXME: somehow drag-data-received signal is emitted twice.
8480 * This hack prevents duplicated insertion. */
8481 if (context_ == drag_context && x_ == x && y_ == y && info_ == info &&
8482 time_ == time) {
8483 debug_print("dup drag-data-received event\n");
8484 context_ = NULL;
8485 x_ = y_ = -1;
8486 info_ = N_DRAG_TYPES;
8487 time_ = G_MAXUINT;
8488 return;
8489 }
8490 context_ = drag_context;
8491 x_ = x;
8492 y_ = y;
8493 info_ = info;
8494 time_ = time;
8495
8496 compose_attach_drag_received_cb(widget, drag_context, x, y, data,
8497 info, time, user_data);
8498 }
8499
8500 static void to_activated(GtkWidget *widget, Compose *compose)
8501 {
8502 if (GTK_WIDGET_VISIBLE(compose->newsgroups_entry))
8503 gtk_widget_grab_focus(compose->newsgroups_entry);
8504 else if (GTK_WIDGET_VISIBLE(compose->cc_entry))
8505 gtk_widget_grab_focus(compose->cc_entry);
8506 else if (GTK_WIDGET_VISIBLE(compose->bcc_entry))
8507 gtk_widget_grab_focus(compose->bcc_entry);
8508 else if (GTK_WIDGET_VISIBLE(compose->reply_entry))
8509 gtk_widget_grab_focus(compose->reply_entry);
8510 else if (GTK_WIDGET_VISIBLE(compose->followup_entry))
8511 gtk_widget_grab_focus(compose->followup_entry);
8512 else
8513 gtk_widget_grab_focus(compose->subject_entry);
8514 }
8515
8516 static void newsgroups_activated(GtkWidget *widget, Compose *compose)
8517 {
8518 if (GTK_WIDGET_VISIBLE(compose->cc_entry))
8519 gtk_widget_grab_focus(compose->cc_entry);
8520 else if (GTK_WIDGET_VISIBLE(compose->bcc_entry))
8521 gtk_widget_grab_focus(compose->bcc_entry);
8522 else if (GTK_WIDGET_VISIBLE(compose->reply_entry))
8523 gtk_widget_grab_focus(compose->reply_entry);
8524 else if (GTK_WIDGET_VISIBLE(compose->followup_entry))
8525 gtk_widget_grab_focus(compose->followup_entry);
8526 else
8527 gtk_widget_grab_focus(compose->subject_entry);
8528 }
8529
8530 static void cc_activated(GtkWidget *widget, Compose *compose)
8531 {
8532 if (GTK_WIDGET_VISIBLE(compose->bcc_entry))
8533 gtk_widget_grab_focus(compose->bcc_entry);
8534 else if (GTK_WIDGET_VISIBLE(compose->reply_entry))
8535 gtk_widget_grab_focus(compose->reply_entry);
8536 else if (GTK_WIDGET_VISIBLE(compose->followup_entry))
8537 gtk_widget_grab_focus(compose->followup_entry);
8538 else
8539 gtk_widget_grab_focus(compose->subject_entry);
8540 }
8541
8542 static void bcc_activated(GtkWidget *widget, Compose *compose)
8543 {
8544 if (GTK_WIDGET_VISIBLE(compose->reply_entry))
8545 gtk_widget_grab_focus(compose->reply_entry);
8546 else if (GTK_WIDGET_VISIBLE(compose->followup_entry))
8547 gtk_widget_grab_focus(compose->followup_entry);
8548 else
8549 gtk_widget_grab_focus(compose->subject_entry);
8550 }
8551
8552 static void replyto_activated(GtkWidget *widget, Compose *compose)
8553 {
8554 if (GTK_WIDGET_VISIBLE(compose->followup_entry))
8555 gtk_widget_grab_focus(compose->followup_entry);
8556 else
8557 gtk_widget_grab_focus(compose->subject_entry);
8558 }
8559
8560 static void followupto_activated(GtkWidget *widget, Compose *compose)
8561 {
8562 gtk_widget_grab_focus(compose->subject_entry);
8563 }
8564
8565 static void subject_activated(GtkWidget *widget, Compose *compose)
8566 {
8567 gtk_widget_grab_focus(compose->text);
8568 }
8569
8570 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
8571 const gchar *text, gint len, Compose *compose)
8572 {
8573 GtkTextMark *mark;
8574
8575 /* pass to the default handler */
8576 if (!compose->autowrap)
8577 return;
8578
8579 g_return_if_fail(text != NULL);
8580
8581 g_signal_handlers_block_by_func(G_OBJECT(buffer),
8582 G_CALLBACK(text_inserted),
8583 compose);
8584
8585 gtk_text_buffer_insert(buffer, iter, text, len);
8586
8587 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
8588 compose_wrap_all_full(compose, TRUE);
8589 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
8590 gtk_text_buffer_delete_mark(buffer, mark);
8591
8592 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
8593 G_CALLBACK(text_inserted),
8594 compose);
8595 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
8596 }
8597
8598 static gboolean autosave_timeout(gpointer data)
8599 {
8600 Compose *compose = (Compose *)data;
8601
8602 gdk_threads_enter();
8603
8604 debug_print("auto-saving...\n");
8605
8606 if (compose->modified)
8607 compose_draft_cb(data, 1, NULL);
8608
8609 gdk_threads_leave();
8610
8611 return TRUE;
8612 }
8613