Statistics
| Revision:

root / src / compose.c @ 1514

History | View | Annotate | Download (187.8 kB)

1
/*
2
 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3
 * Copyright (C) 1999-2007 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 <pango/pango-break.h>
66
67
#if USE_GTKSPELL
68
#  include <gtk/gtkradiomenuitem.h>
69
#  include <gtkspell/gtkspell.h>
70
#  include <aspell.h>
71
#endif
72
73
#include <stdio.h>
74
#include <stdlib.h>
75
#include <string.h>
76
#include <ctype.h>
77
#include <sys/types.h>
78
#include <sys/stat.h>
79
#include <unistd.h>
80
#include <time.h>
81
#include <stdlib.h>
82
#if HAVE_SYS_WAIT_H
83
#  include <sys/wait.h>
84
#endif
85
#include <signal.h>
86
#include <errno.h>
87
#ifdef G_OS_WIN32
88
#  include <windows.h>
89
#endif
90
91
#include "main.h"
92
#include "mainwindow.h"
93
#include "compose.h"
94
#include "addressbook.h"
95
#include "folderview.h"
96
#include "procmsg.h"
97
#include "menu.h"
98
#include "stock_pixmap.h"
99
#include "send_message.h"
100
#include "imap.h"
101
#include "news.h"
102
#include "customheader.h"
103
#include "prefs_common.h"
104
#include "prefs_account.h"
105
#include "prefs_toolbar.h"
106
#include "action.h"
107
#include "account.h"
108
#include "filesel.h"
109
#include "procheader.h"
110
#include "procmime.h"
111
#include "statusbar.h"
112
#include "about.h"
113
#include "base64.h"
114
#include "quoted-printable.h"
115
#include "codeconv.h"
116
#include "utils.h"
117
#include "gtkutils.h"
118
#include "socket.h"
119
#include "alertpanel.h"
120
#include "manage_window.h"
121
#include "gtkshruler.h"
122
#include "folder.h"
123
#include "filter.h"
124
#include "addr_compl.h"
125
#include "quote_fmt.h"
126
#include "template.h"
127
#include "undo.h"
128
129
#if USE_GPGME
130
#  include "rfc2015.h"
131
#endif
132
133
enum
134
{
135
        COL_MIMETYPE,
136
        COL_SIZE,
137
        COL_NAME,
138
        COL_ATTACH_INFO,
139
        N_ATTACH_COLS
140
};
141
142
typedef enum
143
{
144
        COMPOSE_ACTION_MOVE_BEGINNING_OF_LINE,
145
        COMPOSE_ACTION_MOVE_FORWARD_CHARACTER,
146
        COMPOSE_ACTION_MOVE_BACKWARD_CHARACTER,
147
        COMPOSE_ACTION_MOVE_FORWARD_WORD,
148
        COMPOSE_ACTION_MOVE_BACKWARD_WORD,
149
        COMPOSE_ACTION_MOVE_END_OF_LINE,
150
        COMPOSE_ACTION_MOVE_NEXT_LINE,
151
        COMPOSE_ACTION_MOVE_PREVIOUS_LINE,
152
        COMPOSE_ACTION_DELETE_FORWARD_CHARACTER,
153
        COMPOSE_ACTION_DELETE_BACKWARD_CHARACTER,
154
        COMPOSE_ACTION_DELETE_FORWARD_WORD,
155
        COMPOSE_ACTION_DELETE_BACKWARD_WORD,
156
        COMPOSE_ACTION_DELETE_LINE,
157
        COMPOSE_ACTION_DELETE_LINE_N,
158
        COMPOSE_ACTION_DELETE_TO_LINE_END
159
} ComposeAction;
160
161
#define B64_LINE_SIZE                57
162
#define B64_BUFFSIZE                77
163
164
#define MAX_REFERENCES_LEN        999
165
166
static GdkColor quote_color = {0, 0, 0, 0xbfff};
167
168
static GList *compose_list = NULL;
169
170
static Compose *compose_create                        (PrefsAccount        *account,
171
                                                 ComposeMode         mode);
172
static Compose *compose_find_window_by_target        (MsgInfo        *msginfo);
173
static gboolean compose_window_exist                (gint                 x,
174
                                                 gint                 y);
175
static void compose_connect_changed_callbacks        (Compose        *compose);
176
177
static GtkWidget *compose_toolbar_create        (Compose        *compose);
178
static GtkWidget *compose_toolbar_create_from_list
179
                                                (Compose        *compose,
180
                                                 GList                *item_list);
181
static void compose_set_toolbar_button_visibility
182
                                                (Compose        *compose);
183
184
static GtkWidget *compose_account_option_menu_create
185
                                                (Compose        *compose,
186
                                                 GtkWidget        *hbox);
187
static void compose_set_out_encoding                (Compose        *compose);
188
static void compose_set_template_menu                (Compose        *compose);
189
static void compose_template_apply                (Compose        *compose,
190
                                                 Template        *tmpl,
191
                                                 gboolean         replace);
192
static void compose_destroy                        (Compose        *compose);
193
194
static void compose_entry_show                        (Compose        *compose,
195
                                                 ComposeEntryType type);
196
static GtkEntry *compose_get_entry                (Compose        *compose,
197
                                                 ComposeEntryType type);
198
static void compose_entries_set                        (Compose        *compose,
199
                                                 const gchar        *mailto);
200
static void compose_entries_set_from_item        (Compose        *compose,
201
                                                 FolderItem        *item,
202
                                                 ComposeMode         mode);
203
static gint compose_parse_header                (Compose        *compose,
204
                                                 MsgInfo        *msginfo);
205
static gchar *compose_parse_references                (const gchar        *ref,
206
                                                 const gchar        *msgid);
207
208
static gchar *compose_quote_fmt                        (Compose        *compose,
209
                                                 MsgInfo        *msginfo,
210
                                                 const gchar        *fmt,
211
                                                 const gchar        *qmark,
212
                                                 const gchar        *body);
213
214
static void compose_reply_set_entry                (Compose        *compose,
215
                                                 MsgInfo        *msginfo,
216
                                                 ComposeMode         mode);
217
static void compose_reedit_set_entry                (Compose        *compose,
218
                                                 MsgInfo        *msginfo);
219
220
static void compose_insert_sig                        (Compose        *compose,
221
                                                 gboolean         append,
222
                                                 gboolean         replace,
223
                                                 gboolean         scroll);
224
static void compose_enable_sig                        (Compose        *compose);
225
static gchar *compose_get_signature_str                (Compose        *compose);
226
227
static void compose_insert_file                        (Compose        *compose,
228
                                                 const gchar        *file,
229
                                                 gboolean         scroll);
230
231
static void compose_attach_append                (Compose        *compose,
232
                                                 const gchar        *file,
233
                                                 const gchar        *filename,
234
                                                 const gchar        *content_type);
235
static void compose_attach_parts                (Compose        *compose,
236
                                                 MsgInfo        *msginfo);
237
238
static void compose_wrap_paragraph                (Compose        *compose,
239
                                                 GtkTextIter        *par_iter);
240
static void compose_wrap_all                        (Compose        *compose);
241
static void compose_wrap_all_full                (Compose        *compose,
242
                                                 gboolean         autowrap);
243
244
static void compose_set_title                        (Compose        *compose);
245
static void compose_select_account                (Compose        *compose,
246
                                                 PrefsAccount        *account,
247
                                                 gboolean         init);
248
249
static gboolean compose_check_for_valid_recipient
250
                                                (Compose        *compose);
251
static gboolean compose_check_entries                (Compose        *compose);
252
253
static gint compose_send                        (Compose        *compose);
254
static gint compose_write_to_file                (Compose        *compose,
255
                                                 const gchar        *file,
256
                                                 gboolean         is_draft);
257
static gint compose_write_body_to_file                (Compose        *compose,
258
                                                 const gchar        *file);
259
static gint compose_redirect_write_to_file        (Compose        *compose,
260
                                                 const gchar        *file);
261
static gint compose_remove_reedit_target        (Compose        *compose);
262
static gint compose_queue                        (Compose        *compose,
263
                                                 const gchar        *file);
264
static void compose_write_attach                (Compose        *compose,
265
                                                 FILE                *fp,
266
                                                 const gchar        *charset);
267
static gint compose_write_headers                (Compose        *compose,
268
                                                 FILE                *fp,
269
                                                 const gchar        *charset,
270
                                                 const gchar        *body_charset,
271
                                                 EncodingType         encoding,
272
                                                 gboolean         is_draft);
273
static gint compose_redirect_write_headers        (Compose        *compose,
274
                                                 FILE                *fp);
275
276
static void compose_convert_header                (Compose        *compose,
277
                                                 gchar                *dest,
278
                                                 gint                 len,
279
                                                 const gchar        *src,
280
                                                 gint                 header_len,
281
                                                 gboolean         addr_field,
282
                                                 const gchar        *encoding);
283
static gchar *compose_convert_filename                (Compose        *compose,
284
                                                 const gchar        *src,
285
                                                 const gchar        *param_name,
286
                                                 const gchar        *encoding);
287
static void compose_generate_msgid                (Compose        *compose,
288
                                                 gchar                *buf,
289
                                                 gint                 len);
290
291
static void compose_attach_info_free                (AttachInfo        *ainfo);
292
static void compose_attach_remove_selected        (Compose        *compose);
293
294
static void compose_attach_property                (Compose        *compose);
295
static void compose_attach_property_create        (gboolean        *cancelled);
296
static void attach_property_ok                        (GtkWidget        *widget,
297
                                                 gboolean        *cancelled);
298
static void attach_property_cancel                (GtkWidget        *widget,
299
                                                 gboolean        *cancelled);
300
static gint attach_property_delete_event        (GtkWidget        *widget,
301
                                                 GdkEventAny        *event,
302
                                                 gboolean        *cancelled);
303
static gboolean attach_property_key_pressed        (GtkWidget        *widget,
304
                                                 GdkEventKey        *event,
305
                                                 gboolean        *cancelled);
306
307
static void compose_exec_ext_editor                (Compose        *compose);
308
static gboolean compose_ext_editor_kill                (Compose        *compose);
309
static void compose_ext_editor_child_exit        (GPid                 pid,
310
                                                 gint                 status,
311
                                                 gpointer         data);
312
static void compose_set_ext_editor_sensitive        (Compose        *compose,
313
                                                 gboolean         sensitive);
314
315
static void compose_undo_state_changed                (UndoMain        *undostruct,
316
                                                 gint                 undo_state,
317
                                                 gint                 redo_state,
318
                                                 gpointer         data);
319
320
static gint calc_cursor_xpos        (GtkTextView        *text,
321
                                 gint                 extra,
322
                                 gint                 char_width);
323
324
/* callback functions */
325
326
static gboolean compose_edit_size_alloc (GtkEditable        *widget,
327
                                         GtkAllocation        *allocation,
328
                                         GtkSHRuler        *shruler);
329
330
static void toolbar_send_cb                (GtkWidget        *widget,
331
                                         gpointer         data);
332
static void toolbar_send_later_cb        (GtkWidget        *widget,
333
                                         gpointer         data);
334
static void toolbar_draft_cb                (GtkWidget        *widget,
335
                                         gpointer         data);
336
static void toolbar_insert_cb                (GtkWidget        *widget,
337
                                         gpointer         data);
338
static void toolbar_attach_cb                (GtkWidget        *widget,
339
                                         gpointer         data);
340
static void toolbar_sig_cb                (GtkWidget        *widget,
341
                                         gpointer         data);
342
static void toolbar_ext_editor_cb        (GtkWidget        *widget,
343
                                         gpointer         data);
344
static void toolbar_linewrap_cb                (GtkWidget        *widget,
345
                                         gpointer         data);
346
static void toolbar_address_cb                (GtkWidget        *widget,
347
                                         gpointer         data);
348
349
static gboolean toolbar_button_pressed        (GtkWidget        *widget,
350
                                         GdkEventButton        *event,
351
                                         gpointer         data);
352
353
static void account_activated                (GtkMenuItem        *menuitem,
354
                                         gpointer         data);
355
356
static void attach_selection_changed        (GtkTreeSelection        *selection,
357
                                         gpointer                 data);
358
359
static gboolean attach_button_pressed        (GtkWidget        *widget,
360
                                         GdkEventButton        *event,
361
                                         gpointer         data);
362
static gboolean attach_key_pressed        (GtkWidget        *widget,
363
                                         GdkEventKey        *event,
364
                                         gpointer         data);
365
366
static void compose_send_cb                (gpointer         data,
367
                                         guint                 action,
368
                                         GtkWidget        *widget);
369
static void compose_send_later_cb        (gpointer         data,
370
                                         guint                 action,
371
                                         GtkWidget        *widget);
372
373
static void compose_draft_cb                (gpointer         data,
374
                                         guint                 action,
375
                                         GtkWidget        *widget);
376
377
static void compose_attach_cb                (gpointer         data,
378
                                         guint                 action,
379
                                         GtkWidget        *widget);
380
static void compose_insert_file_cb        (gpointer         data,
381
                                         guint                 action,
382
                                         GtkWidget        *widget);
383
static void compose_insert_sig_cb        (gpointer         data,
384
                                         guint                 action,
385
                                         GtkWidget        *widget);
386
387
static void compose_close_cb                (gpointer         data,
388
                                         guint                 action,
389
                                         GtkWidget        *widget);
390
391
static void compose_set_encoding_cb        (gpointer         data,
392
                                         guint                 action,
393
                                         GtkWidget        *widget);
394
395
static void compose_address_cb                (gpointer         data,
396
                                         guint                 action,
397
                                         GtkWidget        *widget);
398
static void compose_template_activate_cb(GtkWidget        *widget,
399
                                         gpointer         data);
400
401
static void compose_ext_editor_cb        (gpointer         data,
402
                                         guint                 action,
403
                                         GtkWidget        *widget);
404
405
static gint compose_delete_cb                (GtkWidget        *widget,
406
                                         GdkEventAny        *event,
407
                                         gpointer         data);
408
409
static void compose_undo_cb                (Compose        *compose);
410
static void compose_redo_cb                (Compose        *compose);
411
static void compose_cut_cb                (Compose        *compose);
412
static void compose_copy_cb                (Compose        *compose);
413
static void compose_paste_cb                (Compose        *compose);
414
static void compose_paste_as_quote_cb        (Compose        *compose);
415
static void compose_allsel_cb                (Compose        *compose);
416
417
static void compose_grab_focus_cb        (GtkWidget        *widget,
418
                                         Compose        *compose);
419
420
#if USE_GPGME
421
static void compose_signing_toggled        (GtkWidget        *widget,
422
                                         Compose        *compose);
423
static void compose_encrypt_toggled        (GtkWidget        *widget,
424
                                         Compose        *compose);
425
#endif
426
427
#if 0
428
static void compose_attach_toggled        (GtkWidget        *widget,
429
                                         Compose        *compose);
430
#endif
431
432
static void compose_buffer_changed_cb        (GtkTextBuffer        *textbuf,
433
                                         Compose        *compose);
434
static void compose_changed_cb                (GtkEditable        *editable,
435
                                         Compose        *compose);
436
437
static void compose_wrap_cb                (gpointer         data,
438
                                         guint                 action,
439
                                         GtkWidget        *widget);
440
static void compose_toggle_autowrap_cb        (gpointer         data,
441
                                         guint                 action,
442
                                         GtkWidget        *widget);
443
444
static void compose_toggle_to_cb        (gpointer         data,
445
                                         guint                 action,
446
                                         GtkWidget        *widget);
447
static void compose_toggle_cc_cb        (gpointer         data,
448
                                         guint                 action,
449
                                         GtkWidget        *widget);
450
static void compose_toggle_bcc_cb        (gpointer         data,
451
                                         guint                 action,
452
                                         GtkWidget        *widget);
453
static void compose_toggle_replyto_cb        (gpointer         data,
454
                                         guint                 action,
455
                                         GtkWidget        *widget);
456
static void compose_toggle_followupto_cb(gpointer         data,
457
                                         guint                 action,
458
                                         GtkWidget        *widget);
459
static void compose_toggle_ruler_cb        (gpointer         data,
460
                                         guint                 action,
461
                                         GtkWidget        *widget);
462
static void compose_toggle_attach_cb        (gpointer         data,
463
                                         guint                 action,
464
                                         GtkWidget        *widget);
465
static void compose_customize_toolbar_cb(gpointer         data,
466
                                         guint                 action,
467
                                         GtkWidget        *widget);
468
469
#if USE_GPGME
470
static void compose_toggle_sign_cb        (gpointer         data,
471
                                         guint                 action,
472
                                         GtkWidget        *widget);
473
static void compose_toggle_encrypt_cb        (gpointer         data,
474
                                         guint                 action,
475
                                         GtkWidget        *widget);
476
#endif
477
478
#if USE_GTKSPELL
479
static void compose_set_spell_lang_menu (Compose        *compose);
480
static void compose_toggle_spell_cb        (gpointer         data,
481
                                         guint                 action,
482
                                         GtkWidget        *widget);
483
static void compose_set_spell_lang_cb        (GtkWidget        *widget,
484
                                         gpointer         data);
485
#endif
486
487
static void compose_attach_drag_received_cb (GtkWidget                *widget,
488
                                             GdkDragContext        *drag_context,
489
                                             gint                 x,
490
                                             gint                 y,
491
                                             GtkSelectionData        *data,
492
                                             guint                 info,
493
                                             guint                 time,
494
                                             gpointer                 user_data);
495
static void compose_insert_drag_received_cb (GtkWidget                *widget,
496
                                             GdkDragContext        *drag_context,
497
                                             gint                 x,
498
                                             gint                 y,
499
                                             GtkSelectionData        *data,
500
                                             guint                 info,
501
                                             guint                 time,
502
                                             gpointer                 user_data);
503
504
static void to_activated                (GtkWidget        *widget,
505
                                         Compose        *compose);
506
static void newsgroups_activated        (GtkWidget        *widget,
507
                                         Compose        *compose);
508
static void cc_activated                (GtkWidget        *widget,
509
                                         Compose        *compose);
510
static void bcc_activated                (GtkWidget        *widget,
511
                                         Compose        *compose);
512
static void replyto_activated                (GtkWidget        *widget,
513
                                         Compose        *compose);
514
static void followupto_activated        (GtkWidget        *widget,
515
                                         Compose        *compose);
516
static void subject_activated                (GtkWidget        *widget,
517
                                         Compose        *compose);
518
519
static void text_inserted                (GtkTextBuffer        *buffer,
520
                                         GtkTextIter        *iter,
521
                                         const gchar        *text,
522
                                         gint                 len,
523
                                         Compose        *compose);
524
525
static gboolean autosave_timeout        (gpointer         data);
526
527
528
static GtkItemFactoryEntry compose_popup_entries[] =
529
{
530
        {N_("/_Add..."),        NULL, compose_attach_cb, 0, NULL},
531
        {N_("/_Remove"),        NULL, compose_attach_remove_selected, 0, NULL},
532
        {N_("/---"),                NULL, NULL, 0, "<Separator>"},
533
        {N_("/_Properties..."),        NULL, compose_attach_property, 0, NULL}
534
};
535
536
static GtkItemFactoryEntry compose_entries[] =
537
{
538
        {N_("/_File"),                                NULL, NULL, 0, "<Branch>"},
539
        {N_("/_File/_Send"),                        "<control>Return",
540
                                                compose_send_cb, 0, NULL},
541
        {N_("/_File/Send _later"),                "<shift><control>S",
542
                                                compose_send_later_cb,  0, NULL},
543
        {N_("/_File/---"),                        NULL, NULL, 0, "<Separator>"},
544
        {N_("/_File/Save to _draft folder"),
545
                                                "<shift><control>D", compose_draft_cb, 0, NULL},
546
        {N_("/_File/Save and _keep editing"),
547
                                                "<control>S", compose_draft_cb, 1, NULL},
548
        {N_("/_File/---"),                        NULL, NULL, 0, "<Separator>"},
549
        {N_("/_File/_Attach file"),                "<control>M", compose_attach_cb,      0, NULL},
550
        {N_("/_File/_Insert file"),                "<control>I", compose_insert_file_cb, 0, NULL},
551
        {N_("/_File/---"),                        NULL, NULL, 0, "<Separator>"},
552
        {N_("/_File/Insert si_gnature"),        "<control>G", compose_insert_sig_cb,  0, NULL},
553
        {N_("/_File/A_ppend signature"),        "<shift><control>G", compose_insert_sig_cb,  1, NULL},
554
        {N_("/_File/---"),                        NULL, NULL, 0, "<Separator>"},
555
        {N_("/_File/_Close"),                        "<control>W", compose_close_cb, 0, NULL},
556
557
        {N_("/_Edit"),                        NULL, NULL, 0, "<Branch>"},
558
        {N_("/_Edit/_Undo"),                "<control>Z", compose_undo_cb, 0, NULL},
559
        {N_("/_Edit/_Redo"),                "<control>Y", compose_redo_cb, 0, NULL},
560
        {N_("/_Edit/---"),                NULL, NULL, 0, "<Separator>"},
561
        {N_("/_Edit/Cu_t"),                "<control>X", compose_cut_cb,    0, NULL},
562
        {N_("/_Edit/_Copy"),                "<control>C", compose_copy_cb,   0, NULL},
563
        {N_("/_Edit/_Paste"),                "<control>V", compose_paste_cb,  0, NULL},
564
        {N_("/_Edit/Paste as _quotation"),
565
                                        NULL, compose_paste_as_quote_cb, 0, NULL},
566
        {N_("/_Edit/Select _all"),        "<control>A", compose_allsel_cb, 0, NULL},
567
        {N_("/_Edit/---"),                NULL, NULL, 0, "<Separator>"},
568
        {N_("/_Edit/_Wrap current paragraph"),
569
                                        "<control>L", compose_wrap_cb, 0, NULL},
570
        {N_("/_Edit/Wrap all long _lines"),
571
                                        "<control><alt>L", compose_wrap_cb, 1, NULL},
572
        {N_("/_Edit/Aut_o wrapping"),        "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
573
        {N_("/_View"),                        NULL, NULL, 0, "<Branch>"},
574
        {N_("/_View/_To"),                NULL, compose_toggle_to_cb     , 0, "<ToggleItem>"},
575
        {N_("/_View/_Cc"),                NULL, compose_toggle_cc_cb     , 0, "<ToggleItem>"},
576
        {N_("/_View/_Bcc"),                NULL, compose_toggle_bcc_cb    , 0, "<ToggleItem>"},
577
        {N_("/_View/_Reply to"),        NULL, compose_toggle_replyto_cb, 0, "<ToggleItem>"},
578
        {N_("/_View/---"),                NULL, NULL, 0, "<Separator>"},
579
        {N_("/_View/_Followup to"),        NULL, compose_toggle_followupto_cb, 0, "<ToggleItem>"},
580
        {N_("/_View/---"),                NULL, NULL, 0, "<Separator>"},
581
        {N_("/_View/R_uler"),                NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
582
        {N_("/_View/---"),                NULL, NULL, 0, "<Separator>"},
583
        {N_("/_View/_Attachment"),        NULL, compose_toggle_attach_cb, 0, "<ToggleItem>"},
584
        {N_("/_View/---"),                NULL, NULL, 0, "<Separator>"},
585
        {N_("/_View/Cu_stomize toolbar..."),
586
                                        NULL, compose_customize_toolbar_cb, 0, "<ToggleItem>"},
587
        {N_("/_View/---"),                NULL, NULL, 0, "<Separator>"},
588
589
#define ENC_ACTION(action) \
590
        NULL, compose_set_encoding_cb, action, \
591
        "/View/Character encoding/Automatic"
592
593
        {N_("/_View/Character _encoding"), NULL, NULL, 0, "<Branch>"},
594
        {N_("/_View/Character _encoding/_Automatic"),
595
                        NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
596
        {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
597
598
        {N_("/_View/Character _encoding/7bit ascii (US-ASC_II)"),
599
         ENC_ACTION(C_US_ASCII)},
600
        {N_("/_View/Character _encoding/Unicode (_UTF-8)"),
601
         ENC_ACTION(C_UTF_8)},
602
        {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
603
604
        {N_("/_View/Character _encoding/Western European (ISO-8859-_1)"),
605
         ENC_ACTION(C_ISO_8859_1)},
606
        {N_("/_View/Character _encoding/Western European (ISO-8859-15)"),
607
         ENC_ACTION(C_ISO_8859_15)},
608
        {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
609
610
        {N_("/_View/Character _encoding/Central European (ISO-8859-_2)"),
611
         ENC_ACTION(C_ISO_8859_2)},
612
        {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
613
614
        {N_("/_View/Character _encoding/_Baltic (ISO-8859-13)"),
615
         ENC_ACTION(C_ISO_8859_13)},
616
        {N_("/_View/Character _encoding/Baltic (ISO-8859-_4)"),
617
         ENC_ACTION(C_ISO_8859_4)},
618
        {N_("/_View/Character _encoding/Baltic (Windows-1257)"),
619
         ENC_ACTION(C_WINDOWS_1257)},
620
        {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
621
622
        {N_("/_View/Character _encoding/Greek (ISO-8859-_7)"),
623
         ENC_ACTION(C_ISO_8859_7)},
624
        {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
625
626
        {N_("/_View/Character _encoding/Arabic (ISO-8859-_6)"),
627
         ENC_ACTION(C_ISO_8859_6)},
628
        {N_("/_View/Character _encoding/Arabic (Windows-1256)"),
629
         ENC_ACTION(C_WINDOWS_1256)},
630
        {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
631
632
        {N_("/_View/Character _encoding/Hebrew (ISO-8859-_8)"),
633
         ENC_ACTION(C_ISO_8859_8)},
634
        {N_("/_View/Character _encoding/Hebrew (Windows-1255)"),
635
         ENC_ACTION(C_WINDOWS_1255)},
636
        {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
637
638
        {N_("/_View/Character _encoding/Turkish (ISO-8859-_9)"),
639
         ENC_ACTION(C_ISO_8859_9)},
640
        {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
641
642
        {N_("/_View/Character _encoding/Cyrillic (ISO-8859-_5)"),
643
         ENC_ACTION(C_ISO_8859_5)},
644
        {N_("/_View/Character _encoding/Cyrillic (KOI8-_R)"),
645
         ENC_ACTION(C_KOI8_R)},
646
        {N_("/_View/Character _encoding/Cyrillic (KOI8-U)"),
647
         ENC_ACTION(C_KOI8_U)},
648
        {N_("/_View/Character _encoding/Cyrillic (Windows-1251)"),
649
         ENC_ACTION(C_WINDOWS_1251)},
650
        {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
651
652
        {N_("/_View/Character _encoding/Japanese (ISO-2022-_JP)"),
653
         ENC_ACTION(C_ISO_2022_JP)},
654
        {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
655
656
        {N_("/_View/Character _encoding/Simplified Chinese (_GB2312)"),
657
         ENC_ACTION(C_GB2312)},
658
        {N_("/_View/Character _encoding/Simplified Chinese (GBK)"),
659
         ENC_ACTION(C_GBK)},
660
        {N_("/_View/Character _encoding/Traditional Chinese (_Big5)"),
661
         ENC_ACTION(C_BIG5)},
662
        {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
663
664
        {N_("/_View/Character _encoding/Korean (EUC-_KR)"),
665
         ENC_ACTION(C_EUC_KR)},
666
        {N_("/_View/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
667
668
        {N_("/_View/Character _encoding/Thai (TIS-620)"),
669
         ENC_ACTION(C_TIS_620)},
670
        {N_("/_View/Character _encoding/Thai (Windows-874)"),
671
         ENC_ACTION(C_WINDOWS_874)},
672
673
        {N_("/_Tools"),                        NULL, NULL, 0, "<Branch>"},
674
        {N_("/_Tools/_Address book"),        "<shift><control>A", compose_address_cb , 0, NULL},
675
        {N_("/_Tools/_Template"),        NULL, NULL, 0, "<Branch>"},
676
#ifndef G_OS_WIN32
677
        {N_("/_Tools/Actio_ns"),        NULL, NULL, 0, "<Branch>"},
678
#endif
679
        {N_("/_Tools/---"),                NULL, NULL, 0, "<Separator>"},
680
        {N_("/_Tools/Edit with e_xternal editor"),
681
                                        "<shift><control>X", compose_ext_editor_cb, 0, NULL},
682
#if USE_GPGME
683
        {N_("/_Tools/---"),                NULL, NULL, 0, "<Separator>"},
684
        {N_("/_Tools/PGP Si_gn"),           NULL, compose_toggle_sign_cb   , 0, "<ToggleItem>"},
685
        {N_("/_Tools/PGP _Encrypt"),        NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
686
#endif /* USE_GPGME */
687
688
#if USE_GTKSPELL
689
        {N_("/_Tools/---"),                        NULL, NULL, 0, "<Separator>"},
690
        {N_("/_Tools/_Check spell"),                NULL, compose_toggle_spell_cb, 0, "<ToggleItem>"},
691
        {N_("/_Tools/_Set spell language"),        NULL, NULL, 0, "<Branch>"},
692
#endif /* USE_GTKSPELL */
693
694
        {N_("/_Help"),                        NULL, NULL, 0, "<Branch>"},
695
        {N_("/_Help/_About"),                NULL, about_show, 0, NULL}
696
};
697
698
enum
699
{
700
        DRAG_TYPE_RFC822,
701
        DRAG_TYPE_URI_LIST,
702
703
        N_DRAG_TYPES
704
};
705
706
static GtkTargetEntry compose_drag_types[] =
707
{
708
        {"message/rfc822", GTK_TARGET_SAME_APP, DRAG_TYPE_RFC822},
709
        {"text/uri-list", 0, DRAG_TYPE_URI_LIST}
710
};
711
712
713
Compose *compose_new(PrefsAccount *account, FolderItem *item,
714
                     const gchar *mailto, GPtrArray *attach_files)
715
{
716
        Compose *compose;
717
        GtkTextView *text;
718
        GtkTextBuffer *buffer;
719
        GtkTextIter iter;
720
721
        if (!account) account = cur_account;
722
        g_return_val_if_fail(account != NULL, NULL);
723
724
        compose = compose_create(account, COMPOSE_NEW);
725
726
        undo_block(compose->undostruct);
727
728
        if (prefs_common.auto_sig)
729
                compose_insert_sig(compose, TRUE, FALSE, FALSE);
730
731
        text = GTK_TEXT_VIEW(compose->text);
732
        buffer = gtk_text_view_get_buffer(text);
733
        gtk_text_buffer_get_start_iter(buffer, &iter);
734
        gtk_text_buffer_place_cursor(buffer, &iter);
735
736
        if (account->protocol != A_NNTP) {
737
                if (mailto && *mailto != '\0') {
738
                        compose_entries_set(compose, mailto);
739
                        gtk_widget_grab_focus(compose->subject_entry);
740
                } else if (item) {
741
                        compose_entries_set_from_item
742
                                (compose, item, COMPOSE_NEW);
743
                        if (item->auto_to)
744
                                gtk_widget_grab_focus(compose->subject_entry);
745
                        else
746
                                gtk_widget_grab_focus(compose->to_entry);
747
                } else
748
                        gtk_widget_grab_focus(compose->to_entry);
749
        } else {
750
                if (mailto && *mailto != '\0') {
751
                        compose_entry_append(compose, mailto,
752
                                             COMPOSE_ENTRY_NEWSGROUPS);
753
                        gtk_widget_grab_focus(compose->subject_entry);
754
                } else
755
                        gtk_widget_grab_focus(compose->newsgroups_entry);
756
        }
757
758
        if (attach_files) {
759
                gint i;
760
                gchar *file;
761
762
                for (i = 0; i < attach_files->len; i++) {
763
                        gchar *utf8file;
764
765
                        file = g_ptr_array_index(attach_files, i);
766
                        utf8file = conv_filename_to_utf8(file);
767
                        compose_attach_append(compose, file, utf8file, NULL);
768
                        g_free(utf8file);
769
                }
770
        }
771
772
        undo_unblock(compose->undostruct);
773
774
        compose_connect_changed_callbacks(compose);
775
        compose_set_title(compose);
776
777
        if (prefs_common.enable_autosave && prefs_common.autosave_itv > 0)
778
                compose->autosave_tag =
779
                        g_timeout_add(prefs_common.autosave_itv * 60 * 1000,
780
                                      autosave_timeout, compose);
781
        if (prefs_common.auto_exteditor)
782
                compose_exec_ext_editor(compose);
783
784
        return compose;
785
}
786
787
void compose_reply(MsgInfo *msginfo, FolderItem *item, ComposeMode mode,
788
                   const gchar *body)
789
{
790
        Compose *compose;
791
        PrefsAccount *account;
792
        GtkTextBuffer *buffer;
793
        GtkTextIter iter;
794
        gboolean quote = FALSE;
795
796
        g_return_if_fail(msginfo != NULL);
797
        g_return_if_fail(msginfo->folder != NULL);
798
799
        if (COMPOSE_QUOTE_MODE(mode) == COMPOSE_WITH_QUOTE)
800
                quote = TRUE;
801
802
        account = account_find_from_item_property(msginfo->folder);
803
        if (!account && msginfo->to && prefs_common.reply_account_autosel) {
804
                gchar *to;
805
                Xstrdup_a(to, msginfo->to, return);
806
                extract_address(to);
807
                account = account_find_from_address(to);
808
        }
809
        if (!account && msginfo->folder->folder)
810
                account = msginfo->folder->folder->account;
811
        if (!account)
812
                account = cur_account;
813
        g_return_if_fail(account != NULL);
814
815
        MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_FORWARDED);
816
        MSG_SET_PERM_FLAGS(msginfo->flags, MSG_REPLIED);
817
        MSG_SET_TMP_FLAGS(msginfo->flags, MSG_FLAG_CHANGED);
818
        if (item)
819
                item->mark_dirty = TRUE;
820
        if (MSG_IS_IMAP(msginfo->flags))
821
                imap_msg_set_perm_flags(msginfo, MSG_REPLIED);
822
823
        compose = compose_create(account, COMPOSE_REPLY);
824
825
        compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
826
        if (!compose->replyinfo)
827
                compose->replyinfo = procmsg_msginfo_copy(msginfo);
828
829
        if (compose_parse_header(compose, msginfo) < 0) return;
830
831
        undo_block(compose->undostruct);
832
833
        compose_reply_set_entry(compose, msginfo, mode);
834
        if (item)
835
                compose_entries_set_from_item(compose, item, COMPOSE_REPLY);
836
837
        if (quote) {
838
                gchar *qmark;
839
                gchar *quote_str;
840
841
                if (prefs_common.quotemark && *prefs_common.quotemark)
842
                        qmark = prefs_common.quotemark;
843
                else
844
                        qmark = "> ";
845
846
                quote_str = compose_quote_fmt(compose, compose->replyinfo,
847
                                              prefs_common.quotefmt,
848
                                              qmark, body);
849
        }
850
851
        if (prefs_common.auto_sig)
852
                compose_insert_sig(compose, TRUE, FALSE, FALSE);
853
854
        if (quote && prefs_common.linewrap_quote)
855
                compose_wrap_all(compose);
856
857
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
858
        gtk_text_buffer_get_start_iter(buffer, &iter);
859
        gtk_text_buffer_place_cursor(buffer, &iter);
860
861
        gtk_widget_grab_focus(compose->text);
862
863
        undo_unblock(compose->undostruct);
864
865
        compose_connect_changed_callbacks(compose);
866
        compose_set_title(compose);
867
868
#if USE_GPGME
869
        if (rfc2015_is_available() && account->encrypt_reply &&
870
            MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
871
                GtkItemFactory *ifactory;
872
873
                ifactory = gtk_item_factory_from_widget(compose->menubar);
874
                menu_set_active(ifactory, "/Tools/PGP Encrypt", TRUE);
875
        }
876
#endif
877
878
        if (prefs_common.enable_autosave && prefs_common.autosave_itv > 0)
879
                compose->autosave_tag =
880
                        g_timeout_add(prefs_common.autosave_itv * 60 * 1000,
881
                                      autosave_timeout, compose);
882
        if (prefs_common.auto_exteditor)
883
                compose_exec_ext_editor(compose);
884
}
885
886
void compose_forward(GSList *mlist, FolderItem *item, gboolean as_attach,
887
                     const gchar *body)
888
{
889
        Compose *compose;
890
        PrefsAccount *account;
891
        GtkTextView *text;
892
        GtkTextBuffer *buffer;
893
        GtkTextIter iter;
894
        GSList *cur;
895
        MsgInfo *msginfo;
896
897
        g_return_if_fail(mlist != NULL);
898
899
        msginfo = (MsgInfo *)mlist->data;
900
        g_return_if_fail(msginfo->folder != NULL);
901
902
        account = account_find_from_item(msginfo->folder);
903
        if (!account) account = cur_account;
904
        g_return_if_fail(account != NULL);
905
906
        for (cur = mlist; cur != NULL; cur = cur->next) {
907
                msginfo = (MsgInfo *)cur->data;
908
                MSG_UNSET_PERM_FLAGS(msginfo->flags, MSG_REPLIED);
909
                MSG_SET_PERM_FLAGS(msginfo->flags, MSG_FORWARDED);
910
                MSG_SET_TMP_FLAGS(msginfo->flags, MSG_FLAG_CHANGED);
911
                if (item)
912
                        item->mark_dirty = TRUE;
913
        }
914
        msginfo = (MsgInfo *)mlist->data;
915
        if (MSG_IS_IMAP(msginfo->flags))
916
                imap_msg_list_unset_perm_flags(mlist, MSG_REPLIED);
917
918
        compose = compose_create(account, COMPOSE_FORWARD);
919
920
        undo_block(compose->undostruct);
921
922
        compose_entry_set(compose, "Fw: ", COMPOSE_ENTRY_SUBJECT);
923
        if (mlist->next == NULL && msginfo->subject && *msginfo->subject)
924
                compose_entry_append(compose, msginfo->subject,
925
                                     COMPOSE_ENTRY_SUBJECT);
926
        if (item)
927
                compose_entries_set_from_item(compose, item, COMPOSE_FORWARD);
928
929
        text = GTK_TEXT_VIEW(compose->text);
930
        buffer = gtk_text_view_get_buffer(text);
931
932
        for (cur = mlist; cur != NULL; cur = cur->next) {
933
                msginfo = (MsgInfo *)cur->data;
934
935
                if (as_attach) {
936
                        gchar *msgfile;
937
938
                        msgfile = procmsg_get_message_file_path(msginfo);
939
                        if (!is_file_exist(msgfile))
940
                                g_warning(_("%s: file not exist\n"), msgfile);
941
                        else
942
                                compose_attach_append(compose, msgfile, msgfile,
943
                                                      "message/rfc822");
944
945
                        g_free(msgfile);
946
                } else {
947
                        gchar *qmark;
948
                        gchar *quote_str;
949
                        MsgInfo *full_msginfo;
950
951
                        full_msginfo = procmsg_msginfo_get_full_info(msginfo);
952
                        if (!full_msginfo)
953
                                full_msginfo = procmsg_msginfo_copy(msginfo);
954
955
                        if (cur != mlist) {
956
                                GtkTextMark *mark;
957
                                mark = gtk_text_buffer_get_insert(buffer);
958
                                gtk_text_buffer_get_iter_at_mark
959
                                        (buffer, &iter, mark);
960
                                gtk_text_buffer_insert
961
                                        (buffer, &iter, "\n\n", 2);
962
                        }
963
964
                        if (prefs_common.fw_quotemark &&
965
                            *prefs_common.fw_quotemark)
966
                                qmark = prefs_common.fw_quotemark;
967
                        else
968
                                qmark = "> ";
969
970
                        quote_str = compose_quote_fmt(compose, full_msginfo,
971
                                                      prefs_common.fw_quotefmt,
972
                                                      qmark, body);
973
                        compose_attach_parts(compose, msginfo);
974
975
                        procmsg_msginfo_free(full_msginfo);
976
977
                        if (body) break;
978
                }
979
        }
980
981
        if (prefs_common.auto_sig)
982
                compose_insert_sig(compose, TRUE, FALSE, FALSE);
983
984
        if (prefs_common.linewrap_quote)
985
                compose_wrap_all(compose);
986
987
        gtk_text_buffer_get_start_iter(buffer, &iter);
988
        gtk_text_buffer_place_cursor(buffer, &iter);
989
990
        undo_unblock(compose->undostruct);
991
992
        compose_connect_changed_callbacks(compose);
993
        compose_set_title(compose);
994
995
        if (account->protocol != A_NNTP)
996
                gtk_widget_grab_focus(compose->to_entry);
997
        else
998
                gtk_widget_grab_focus(compose->newsgroups_entry);
999
1000
        if (prefs_common.enable_autosave && prefs_common.autosave_itv > 0)
1001
                compose->autosave_tag =
1002
                        g_timeout_add(prefs_common.autosave_itv * 60 * 1000,
1003
                                      autosave_timeout, compose);
1004
        if (prefs_common.auto_exteditor)
1005
                compose_exec_ext_editor(compose);
1006
}
1007
1008
void compose_redirect(MsgInfo *msginfo, FolderItem *item)
1009
{
1010
        Compose *compose;
1011
        PrefsAccount *account;
1012
        GtkTextView *text;
1013
        GtkTextBuffer *buffer;
1014
        GtkTextMark *mark;
1015
        GtkTextIter iter;
1016
        FILE *fp;
1017
        gchar buf[BUFFSIZE];
1018
1019
        g_return_if_fail(msginfo != NULL);
1020
        g_return_if_fail(msginfo->folder != NULL);
1021
1022
        account = account_find_from_item(msginfo->folder);
1023
        if (!account) account = cur_account;
1024
        g_return_if_fail(account != NULL);
1025
1026
        compose = compose_create(account, COMPOSE_REDIRECT);
1027
        compose->targetinfo = procmsg_msginfo_copy(msginfo);
1028
1029
        if (compose_parse_header(compose, msginfo) < 0) return;
1030
1031
        undo_block(compose->undostruct);
1032
1033
        if (msginfo->subject)
1034
                compose_entry_set(compose, msginfo->subject,
1035
                                  COMPOSE_ENTRY_SUBJECT);
1036
        compose_entries_set_from_item(compose, item, COMPOSE_REDIRECT);
1037
1038
        text = GTK_TEXT_VIEW(compose->text);
1039
        buffer = gtk_text_view_get_buffer(text);
1040
        mark = gtk_text_buffer_get_insert(buffer);
1041
        gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1042
1043
        if ((fp = procmime_get_first_text_content(msginfo, NULL)) == NULL)
1044
                g_warning(_("Can't get text part\n"));
1045
        else {
1046
                gboolean prev_autowrap = compose->autowrap;
1047
1048
                compose->autowrap = FALSE;
1049
                while (fgets(buf, sizeof(buf), fp) != NULL) {
1050
                        strcrchomp(buf);
1051
                        gtk_text_buffer_insert(buffer, &iter, buf, -1);
1052
                }
1053
                compose->autowrap = prev_autowrap;
1054
                fclose(fp);
1055
        }
1056
        compose_attach_parts(compose, msginfo);
1057
1058
        if (account->protocol != A_NNTP)
1059
                gtk_widget_grab_focus(compose->to_entry);
1060
        else
1061
                gtk_widget_grab_focus(compose->newsgroups_entry);
1062
1063
        gtk_text_view_set_editable(text, FALSE);
1064
1065
        undo_unblock(compose->undostruct);
1066
1067
        compose_connect_changed_callbacks(compose);
1068
        compose_set_title(compose);
1069
}
1070
1071
void compose_reedit(MsgInfo *msginfo)
1072
{
1073
        Compose *compose;
1074
        PrefsAccount *account;
1075
        GtkTextView *text;
1076
        GtkTextBuffer *buffer;
1077
        GtkTextMark *mark;
1078
        GtkTextIter iter;
1079
        FILE *fp;
1080
        gchar buf[BUFFSIZE];
1081
        const gchar *str;
1082
        GtkWidget *focus_widget = NULL;
1083
1084
        g_return_if_fail(msginfo != NULL);
1085
        g_return_if_fail(msginfo->folder != NULL);
1086
1087
        account = account_find_from_msginfo(msginfo);
1088
        if (!account) account = cur_account;
1089
        g_return_if_fail(account != NULL);
1090
1091
        if (msginfo->folder->stype == F_DRAFT ||
1092
            msginfo->folder->stype == F_QUEUE) {
1093
                compose = compose_find_window_by_target(msginfo);
1094
                if (compose) {
1095
                        debug_print
1096
                                ("compose_reedit(): existing window found.\n");
1097
                        gtk_window_present(GTK_WINDOW(compose->window));
1098
                        return;
1099
                }
1100
        }
1101
1102
        compose = compose_create(account, COMPOSE_REEDIT);
1103
        compose->targetinfo = procmsg_msginfo_copy(msginfo);
1104
1105
        if (compose_parse_header(compose, msginfo) < 0) return;
1106
1107
        undo_block(compose->undostruct);
1108
1109
        compose_reedit_set_entry(compose, msginfo);
1110
1111
        text = GTK_TEXT_VIEW(compose->text);
1112
        buffer = gtk_text_view_get_buffer(text);
1113
        mark = gtk_text_buffer_get_insert(buffer);
1114
        gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1115
1116
        if ((fp = procmime_get_first_text_content(msginfo, NULL)) == NULL)
1117
                g_warning(_("Can't get text part\n"));
1118
        else {
1119
                gboolean prev_autowrap = compose->autowrap;
1120
1121
                compose->autowrap = FALSE;
1122
                while (fgets(buf, sizeof(buf), fp) != NULL) {
1123
                        strcrchomp(buf);
1124
                        gtk_text_buffer_insert(buffer, &iter, buf, -1);
1125
                }
1126
                compose_enable_sig(compose);
1127
                compose->autowrap = prev_autowrap;
1128
                fclose(fp);
1129
        }
1130
        compose_attach_parts(compose, msginfo);
1131
1132
        gtk_text_buffer_get_start_iter(buffer, &iter);
1133
        gtk_text_buffer_place_cursor(buffer, &iter);
1134
1135
        if (account->protocol != A_NNTP) {
1136
                str = gtk_entry_get_text(GTK_ENTRY(compose->to_entry));
1137
                if (!str || *str == '\0')
1138
                        focus_widget = compose->to_entry;
1139
        } else {
1140
                str = gtk_entry_get_text(GTK_ENTRY(compose->newsgroups_entry));
1141
                if (!str || *str == '\0')
1142
                        focus_widget = compose->newsgroups_entry;
1143
        }
1144
        if (!focus_widget) {
1145
                str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
1146
                if (!str || *str == '\0')
1147
                        focus_widget = compose->subject_entry;
1148
        }
1149
        if (focus_widget)
1150
                gtk_widget_grab_focus(focus_widget);
1151
        else
1152
                gtk_widget_grab_focus(compose->text);
1153
1154
        undo_unblock(compose->undostruct);
1155
1156
        compose_connect_changed_callbacks(compose);
1157
        compose_set_title(compose);
1158
1159
        if (prefs_common.enable_autosave && prefs_common.autosave_itv > 0)
1160
                compose->autosave_tag =
1161
                        g_timeout_add(prefs_common.autosave_itv * 60 * 1000,
1162
                                      autosave_timeout, compose);
1163
        if (prefs_common.auto_exteditor)
1164
                compose_exec_ext_editor(compose);
1165
}
1166
1167
GList *compose_get_compose_list(void)
1168
{
1169
        return compose_list;
1170
}
1171
1172
static void compose_entry_show(Compose *compose, ComposeEntryType type)
1173
{
1174
        GtkItemFactory *ifactory;
1175
1176
        ifactory = gtk_item_factory_from_widget(compose->menubar);
1177
1178
        switch (type) {
1179
        case COMPOSE_ENTRY_CC:
1180
                menu_set_active(ifactory, "/View/Cc", TRUE);
1181
                break;
1182
        case COMPOSE_ENTRY_BCC:
1183
                menu_set_active(ifactory, "/View/Bcc", TRUE);
1184
                break;
1185
        case COMPOSE_ENTRY_REPLY_TO:
1186
                menu_set_active(ifactory, "/View/Reply to", TRUE);
1187
                break;
1188
        case COMPOSE_ENTRY_FOLLOWUP_TO:
1189
                menu_set_active(ifactory, "/View/Followup to", TRUE);
1190
                break;
1191
        case COMPOSE_ENTRY_TO:
1192
                menu_set_active(ifactory, "/View/To", TRUE);
1193
                break;
1194
        default:
1195
                break;
1196
        }
1197
}
1198
1199
static GtkEntry *compose_get_entry(Compose *compose, ComposeEntryType type)
1200
{
1201
        GtkEntry *entry;
1202
1203
        switch (type) {
1204
        case COMPOSE_ENTRY_CC:
1205
                entry = GTK_ENTRY(compose->cc_entry);
1206
                break;
1207
        case COMPOSE_ENTRY_BCC:
1208
                entry = GTK_ENTRY(compose->bcc_entry);
1209
                break;
1210
        case COMPOSE_ENTRY_REPLY_TO:
1211
                entry = GTK_ENTRY(compose->reply_entry);
1212
                break;
1213
        case COMPOSE_ENTRY_SUBJECT:
1214
                entry = GTK_ENTRY(compose->subject_entry);
1215
                break;
1216
        case COMPOSE_ENTRY_NEWSGROUPS:
1217
                entry = GTK_ENTRY(compose->newsgroups_entry);
1218
                break;
1219
        case COMPOSE_ENTRY_FOLLOWUP_TO:
1220
                entry = GTK_ENTRY(compose->followup_entry);
1221
                break;
1222
        case COMPOSE_ENTRY_TO:
1223
        default:
1224
                entry = GTK_ENTRY(compose->to_entry);
1225
                break;
1226
        }
1227
1228
        return entry;
1229
}
1230
1231
void compose_entry_set(Compose *compose, const gchar *text,
1232
                       ComposeEntryType type)
1233
{
1234
        GtkEntry *entry;
1235
1236
        if (!text) return;
1237
1238
        compose_entry_show(compose, type);
1239
        entry = compose_get_entry(compose, type);
1240
1241
        gtk_entry_set_text(entry, text);
1242
}
1243
1244
void compose_entry_append(Compose *compose, const gchar *text,
1245
                          ComposeEntryType type)
1246
{
1247
        GtkEntry *entry;
1248
        const gchar *str;
1249
        gint pos;
1250
1251
        if (!text || *text == '\0') return;
1252
1253
        compose_entry_show(compose, type);
1254
        entry = compose_get_entry(compose, type);
1255
1256
        if (type != COMPOSE_ENTRY_SUBJECT) {
1257
                str = gtk_entry_get_text(entry);
1258
                if (*str != '\0') {
1259
                        pos = entry->text_length;
1260
                        gtk_editable_insert_text(GTK_EDITABLE(entry),
1261
                                                 ", ", -1, &pos);
1262
                }
1263
        }
1264
1265
        pos = entry->text_length;
1266
        gtk_editable_insert_text(GTK_EDITABLE(entry), text, -1, &pos);
1267
}
1268
1269
static void compose_entries_set(Compose *compose, const gchar *mailto)
1270
{
1271
        gchar *to = NULL;
1272
        gchar *cc = NULL;
1273
        gchar *subject = NULL;
1274
        gchar *body = NULL;
1275
1276
        scan_mailto_url(mailto, &to, &cc, NULL, &subject, &body);
1277
1278
        if (to)
1279
                compose_entry_set(compose, to, COMPOSE_ENTRY_TO);
1280
        if (cc)
1281
                compose_entry_set(compose, cc, COMPOSE_ENTRY_CC);
1282
        if (subject)
1283
                compose_entry_set(compose, subject, COMPOSE_ENTRY_SUBJECT);
1284
        if (body) {
1285
                GtkTextView *text = GTK_TEXT_VIEW(compose->text);
1286
                GtkTextBuffer *buffer;
1287
                GtkTextMark *mark;
1288
                GtkTextIter iter;
1289
                gboolean prev_autowrap = compose->autowrap;
1290
1291
                compose->autowrap = FALSE;
1292
1293
                buffer = gtk_text_view_get_buffer(text);
1294
                mark = gtk_text_buffer_get_insert(buffer);
1295
                gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1296
1297
                gtk_text_buffer_insert(buffer, &iter, body, -1);
1298
                gtk_text_buffer_insert(buffer, &iter, "\n", 1);
1299
1300
                compose->autowrap = prev_autowrap;
1301
                if (compose->autowrap)
1302
                        compose_wrap_all(compose);
1303
        }
1304
1305
        g_free(to);
1306
        g_free(cc);
1307
        g_free(subject);
1308
        g_free(body);
1309
}
1310
1311
static void compose_entries_set_from_item(Compose *compose, FolderItem *item,
1312
                                          ComposeMode mode)
1313
{
1314
        g_return_if_fail(item != NULL);
1315
1316
        if (item->auto_to) {
1317
                if (mode != COMPOSE_REPLY || item->use_auto_to_on_reply)
1318
                        compose_entry_set(compose, item->auto_to,
1319
                                          COMPOSE_ENTRY_TO);
1320
        }
1321
        if (item->auto_cc)
1322
                compose_entry_set(compose, item->auto_cc, COMPOSE_ENTRY_CC);
1323
        if (item->auto_bcc)
1324
                compose_entry_set(compose, item->auto_bcc, COMPOSE_ENTRY_BCC);
1325
        if (item->auto_replyto)
1326
                compose_entry_set(compose, item->auto_replyto,
1327
                                  COMPOSE_ENTRY_REPLY_TO);
1328
}
1329
1330
#undef ACTIVATE_MENU
1331
1332
static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
1333
{
1334
        static HeaderEntry hentry[] = {{"Reply-To:",        NULL, TRUE},
1335
                                       {"Cc:",                NULL, TRUE},
1336
                                       {"References:",        NULL, FALSE},
1337
                                       {"Bcc:",                NULL, TRUE},
1338
                                       {"Newsgroups:",  NULL, TRUE},
1339
                                       {"Followup-To:", NULL, TRUE},
1340
                                       {"List-Post:",   NULL, FALSE},
1341
                                       {"Content-Type:",NULL, FALSE},
1342
                                       {NULL,                NULL, FALSE}};
1343
1344
        enum
1345
        {
1346
                H_REPLY_TO        = 0,
1347
                H_CC                = 1,
1348
                H_REFERENCES        = 2,
1349
                H_BCC                = 3,
1350
                H_NEWSGROUPS    = 4,
1351
                H_FOLLOWUP_TO        = 5,
1352
                H_LIST_POST     = 6,
1353
                H_CONTENT_TYPE  = 7
1354
        };
1355
1356
        FILE *fp;
1357
        gchar *charset = NULL;
1358
1359
        g_return_val_if_fail(msginfo != NULL, -1);
1360
1361
        if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
1362
        procheader_get_header_fields(fp, hentry);
1363
        fclose(fp);
1364
1365
        if (hentry[H_CONTENT_TYPE].body != NULL) {
1366
                procmime_scan_content_type_str(hentry[H_CONTENT_TYPE].body,
1367
                                               NULL, &charset, NULL, NULL);
1368
                g_free(hentry[H_CONTENT_TYPE].body);
1369
                hentry[H_CONTENT_TYPE].body = NULL;
1370
        }
1371
        if (hentry[H_REPLY_TO].body != NULL) {
1372
                if (hentry[H_REPLY_TO].body[0] != '\0') {
1373
                        compose->replyto =
1374
                                conv_unmime_header(hentry[H_REPLY_TO].body,
1375
                                                   charset);
1376
                }
1377
                g_free(hentry[H_REPLY_TO].body);
1378
                hentry[H_REPLY_TO].body = NULL;
1379
        }
1380
        if (hentry[H_CC].body != NULL) {
1381
                compose->cc = conv_unmime_header(hentry[H_CC].body, charset);
1382
                g_free(hentry[H_CC].body);
1383
                hentry[H_CC].body = NULL;
1384
        }
1385
        if (hentry[H_REFERENCES].body != NULL) {
1386
                if (compose->mode == COMPOSE_REEDIT)
1387
                        compose->references = hentry[H_REFERENCES].body;
1388
                else {
1389
                        compose->references = compose_parse_references
1390
                                (hentry[H_REFERENCES].body, msginfo->msgid);
1391
                        g_free(hentry[H_REFERENCES].body);
1392
                }
1393
                hentry[H_REFERENCES].body = NULL;
1394
        }
1395
        if (hentry[H_BCC].body != NULL) {
1396
                if (compose->mode == COMPOSE_REEDIT)
1397
                        compose->bcc =
1398
                                conv_unmime_header(hentry[H_BCC].body, charset);
1399
                g_free(hentry[H_BCC].body);
1400
                hentry[H_BCC].body = NULL;
1401
        }
1402
        if (hentry[H_NEWSGROUPS].body != NULL) {
1403
                compose->newsgroups = hentry[H_NEWSGROUPS].body;
1404
                hentry[H_NEWSGROUPS].body = NULL;
1405
        }
1406
        if (hentry[H_FOLLOWUP_TO].body != NULL) {
1407
                if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
1408
                        compose->followup_to =
1409
                                conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
1410
                                                   charset);
1411
                }
1412
                g_free(hentry[H_FOLLOWUP_TO].body);
1413
                hentry[H_FOLLOWUP_TO].body = NULL;
1414
        }
1415
        if (hentry[H_LIST_POST].body != NULL) {
1416
                gchar *to = NULL;
1417
1418
                extract_address(hentry[H_LIST_POST].body);
1419
                if (hentry[H_LIST_POST].body[0] != '\0') {
1420
                        scan_mailto_url(hentry[H_LIST_POST].body,
1421
                                        &to, NULL, NULL, NULL, NULL);
1422
                        if (to) {
1423
                                g_free(compose->ml_post);
1424
                                compose->ml_post = to;
1425
                        }
1426
                }
1427
                g_free(hentry[H_LIST_POST].body);
1428
                hentry[H_LIST_POST].body = NULL;
1429
        }
1430
1431
        g_free(charset);
1432
1433
        if (compose->mode == COMPOSE_REEDIT) {
1434
                if (msginfo->inreplyto && *msginfo->inreplyto)
1435
                        compose->inreplyto = g_strdup(msginfo->inreplyto);
1436
                return 0;
1437
        }
1438
1439
        if (msginfo->msgid && *msginfo->msgid)
1440
                compose->inreplyto = g_strdup(msginfo->msgid);
1441
1442
        if (!compose->references) {
1443
                if (msginfo->msgid && *msginfo->msgid) {
1444
                        if (msginfo->inreplyto && *msginfo->inreplyto)
1445
                                compose->references =
1446
                                        g_strdup_printf("<%s>\n\t<%s>",
1447
                                                        msginfo->inreplyto,
1448
                                                        msginfo->msgid);
1449
                        else
1450
                                compose->references =
1451
                                        g_strconcat("<", msginfo->msgid, ">",
1452
                                                    NULL);
1453
                } else if (msginfo->inreplyto && *msginfo->inreplyto) {
1454
                        compose->references =
1455
                                g_strconcat("<", msginfo->inreplyto, ">",
1456
                                            NULL);
1457
                }
1458
        }
1459
1460
        return 0;
1461
}
1462
1463
static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
1464
{
1465
        GSList *ref_id_list, *cur;
1466
        GString *new_ref;
1467
        gchar *new_ref_str;
1468
1469
        ref_id_list = references_list_append(NULL, ref);
1470
        if (!ref_id_list) return NULL;
1471
        if (msgid && *msgid)
1472
                ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
1473
1474
        for (;;) {
1475
                gint len = 0;
1476
1477
                for (cur = ref_id_list; cur != NULL; cur = cur->next)
1478
                        /* "<" + Message-ID + ">" + CR+LF+TAB */
1479
                        len += strlen((gchar *)cur->data) + 5;
1480
1481
                if (len > MAX_REFERENCES_LEN) {
1482
                        /* remove second message-ID */
1483
                        if (ref_id_list && ref_id_list->next &&
1484
                            ref_id_list->next->next) {
1485
                                g_free(ref_id_list->next->data);
1486
                                ref_id_list = g_slist_remove
1487
                                        (ref_id_list, ref_id_list->next->data);
1488
                        } else {
1489
                                slist_free_strings(ref_id_list);
1490
                                g_slist_free(ref_id_list);
1491
                                return NULL;
1492
                        }
1493
                } else
1494
                        break;
1495
        }
1496
1497
        new_ref = g_string_new("");
1498
        for (cur = ref_id_list; cur != NULL; cur = cur->next) {
1499
                if (new_ref->len > 0)
1500
                        g_string_append(new_ref, "\n\t");
1501
                g_string_sprintfa(new_ref, "<%s>", (gchar *)cur->data);
1502
        }
1503
1504
        slist_free_strings(ref_id_list);
1505
        g_slist_free(ref_id_list);
1506
1507
        new_ref_str = new_ref->str;
1508
        g_string_free(new_ref, FALSE);
1509
1510
        return new_ref_str;
1511
}
1512
1513
static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
1514
                                const gchar *fmt, const gchar *qmark,
1515
                                const gchar *body)
1516
{
1517
        GtkTextView *text = GTK_TEXT_VIEW(compose->text);
1518
        GtkTextBuffer *buffer;
1519
        GtkTextMark *mark;
1520
        GtkTextIter iter;
1521
        static MsgInfo dummyinfo;
1522
        gchar *quote_str = NULL;
1523
        gchar *buf;
1524
        gchar *p, *lastp;
1525
        gint len;
1526
        gboolean prev_autowrap;
1527
1528
        if (!msginfo)
1529
                msginfo = &dummyinfo;
1530
1531
        if (qmark != NULL) {
1532
                quote_fmt_init(msginfo, NULL, NULL);
1533
                quote_fmt_scan_string(qmark);
1534
                quote_fmt_parse();
1535
1536
                buf = quote_fmt_get_buffer();
1537
                if (buf == NULL)
1538
                        alertpanel_error(_("Quote mark format error."));
1539
                else
1540
                        Xstrdup_a(quote_str, buf, return NULL)
1541
        }
1542
1543
        if (fmt && *fmt != '\0') {
1544
                quote_fmt_init(msginfo, quote_str, body);
1545
                quote_fmt_scan_string(fmt);
1546
                quote_fmt_parse();
1547
1548
                buf = quote_fmt_get_buffer();
1549
                if (buf == NULL) {
1550
                        alertpanel_error(_("Message reply/forward format error."));
1551
                        return NULL;
1552
                }
1553
        } else
1554
                buf = "";
1555
1556
        prev_autowrap = compose->autowrap;
1557
        compose->autowrap = FALSE;
1558
1559
        buffer = gtk_text_view_get_buffer(text);
1560
        mark = gtk_text_buffer_get_insert(buffer);
1561
        gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1562
1563
        for (p = buf; *p != '\0'; ) {
1564
                lastp = strchr(p, '\n');
1565
                len = lastp ? lastp - p + 1 : -1;
1566
                gtk_text_buffer_insert(buffer, &iter, p, len);
1567
                if (lastp)
1568
                        p = lastp + 1;
1569
                else
1570
                        break;
1571
        }
1572
1573
        compose->autowrap = prev_autowrap;
1574
        if (compose->autowrap)
1575
                compose_wrap_all(compose);
1576
1577
        return buf;
1578
}
1579
1580
static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
1581
                                    ComposeMode mode)
1582
{
1583
        GSList *cc_list = NULL;
1584
        GSList *cur;
1585
        gchar *from = NULL;
1586
        gchar *replyto = NULL;
1587
        GHashTable *to_table;
1588
        gboolean to_all = FALSE, to_ml = FALSE, ignore_replyto = FALSE;
1589
1590
        g_return_if_fail(compose->account != NULL);
1591
        g_return_if_fail(msginfo != NULL);
1592
1593
        switch (COMPOSE_MODE(mode)) {
1594
        case COMPOSE_REPLY_TO_SENDER:
1595
                ignore_replyto = TRUE;
1596
                break;
1597
        case COMPOSE_REPLY_TO_ALL:
1598
                to_all = TRUE;
1599
                break;
1600
        case COMPOSE_REPLY_TO_LIST:
1601
                to_ml = TRUE;
1602
                break;
1603
        default:
1604
                break;
1605
        }
1606
1607
        if (compose->account->protocol != A_NNTP) {
1608
                if (to_ml && compose->ml_post) {
1609
                        /* don't reply to list for confirmation request etc. */
1610
                        if ((!msginfo->to ||
1611
                             !strstr_with_skip_quote(msginfo->to,
1612
                                                     compose->ml_post)) &&
1613
                            (!compose->cc ||
1614
                             !strstr_with_skip_quote(compose->cc,
1615
                                                     compose->ml_post)))
1616
                                to_ml = FALSE;
1617
                }
1618
                if (to_ml && compose->ml_post) {
1619
                        compose_entry_set(compose, compose->ml_post,
1620
                                          COMPOSE_ENTRY_TO);
1621
                        if (compose->replyto &&
1622
                            !address_equal(compose->replyto, compose->ml_post))
1623
                                compose_entry_set(compose, compose->replyto,
1624
                                                  COMPOSE_ENTRY_CC);
1625
                } else if (prefs_common.inherit_recipient_on_self_reply &&
1626
                           address_equal(compose->account->address,
1627
                                         msginfo->from)) {
1628
                        compose_entry_set(compose, msginfo->to,
1629
                                          COMPOSE_ENTRY_TO);
1630
                        if (to_all) {
1631
                                compose_entry_set(compose, compose->cc,
1632
                                                  COMPOSE_ENTRY_CC);
1633
                                to_all = FALSE;
1634
                        }
1635
                } else {
1636
                        compose_entry_set(compose, 
1637
                                          (compose->replyto && !ignore_replyto)
1638
                                          ? compose->replyto
1639
                                          : msginfo->from ? msginfo->from : "",
1640
                                          COMPOSE_ENTRY_TO);
1641
                }
1642
        } else {
1643
                if (ignore_replyto) {
1644
                        compose_entry_set(compose,
1645
                                          msginfo->from ? msginfo->from : "",
1646
                                          COMPOSE_ENTRY_TO);
1647
                } else {
1648
                        if (to_all) {
1649
                                compose_entry_set
1650
                                        (compose, 
1651
                                         (compose->replyto && !ignore_replyto)
1652
                                         ? compose->replyto
1653
                                         : msginfo->from ? msginfo->from : "",
1654
                                         COMPOSE_ENTRY_TO);
1655
                        }
1656
                        compose_entry_set(compose,
1657
                                          compose->followup_to ? compose->followup_to
1658
                                          : compose->newsgroups ? compose->newsgroups
1659
                                          : "",
1660
                                          COMPOSE_ENTRY_NEWSGROUPS);
1661
                }
1662
        }
1663
1664
        if (msginfo->subject && *msginfo->subject) {
1665
                gchar *buf;
1666
                gchar *p;
1667
1668
                buf = g_strdup(msginfo->subject);
1669
1670
                if (msginfo->folder && msginfo->folder->trim_compose_subject)
1671
                        trim_subject(buf);
1672
1673
                while (!g_ascii_strncasecmp(buf, "Re:", 3)) {
1674
                        p = buf + 3;
1675
                        while (g_ascii_isspace(*p)) p++;
1676
                        memmove(buf, p, strlen(p) + 1);
1677
                }
1678
1679
                compose_entry_set(compose, "Re: ", COMPOSE_ENTRY_SUBJECT);
1680
                compose_entry_append(compose, buf, COMPOSE_ENTRY_SUBJECT);
1681
1682
                g_free(buf);
1683
        } else
1684
                compose_entry_set(compose, "Re: ", COMPOSE_ENTRY_SUBJECT);
1685
1686
        if (!compose->replyto && to_ml && compose->ml_post) return;
1687
        if (!to_all) return;
1688
1689
        if (compose->replyto && msginfo->from)
1690
                cc_list = address_list_append_orig(cc_list, msginfo->from);
1691
        cc_list = address_list_append_orig(cc_list, msginfo->to);
1692
        cc_list = address_list_append_orig(cc_list, compose->cc);
1693
1694
        to_table = g_hash_table_new(g_str_hash, g_str_equal);
1695
        if (compose->replyto) {
1696
                replyto = g_strdup(compose->replyto);
1697
                extract_address(replyto);
1698
                g_hash_table_insert(to_table, replyto, GINT_TO_POINTER(1));
1699
        } else if (msginfo->from) {
1700
                from = g_strdup(msginfo->from);
1701
                extract_address(from);
1702
                g_hash_table_insert(to_table, from, GINT_TO_POINTER(1));
1703
        }
1704
        if (compose->account)
1705
                g_hash_table_insert(to_table,
1706
                                    g_strdup(compose->account->address),
1707
                                    GINT_TO_POINTER(1));
1708
1709
        /* remove duplicate addresses */
1710
        for (cur = cc_list; cur != NULL; ) {
1711
                gchar *addr = (gchar *)cur->data;
1712
                GSList *next = cur->next;
1713
                gchar *addr_;
1714
1715
                addr_ = g_strdup(addr);
1716
                extract_address(addr_);
1717
                if (g_hash_table_lookup(to_table, addr_) != NULL) {
1718
                        cc_list = g_slist_remove(cc_list, addr);
1719
                        g_free(addr_);
1720
                        g_free(addr);
1721
                } else
1722
                        g_hash_table_insert(to_table, addr_, cur);
1723
1724
                cur = next;
1725
        }
1726
        hash_free_strings(to_table);
1727
        g_hash_table_destroy(to_table);
1728
1729
        if (cc_list) {
1730
                for (cur = cc_list; cur != NULL; cur = cur->next)
1731
                        compose_entry_append(compose, (gchar *)cur->data,
1732
                                             COMPOSE_ENTRY_CC);
1733
                slist_free_strings(cc_list);
1734
                g_slist_free(cc_list);
1735
        }
1736
}
1737
1738
static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
1739
{
1740
        g_return_if_fail(msginfo != NULL);
1741
        g_return_if_fail(compose->account != NULL);
1742
1743
        compose_entry_set(compose, msginfo->to     , COMPOSE_ENTRY_TO);
1744
        compose_entry_set(compose, compose->cc     , COMPOSE_ENTRY_CC);
1745
        compose_entry_set(compose, compose->bcc    , COMPOSE_ENTRY_BCC);
1746
        compose_entry_set(compose, compose->replyto, COMPOSE_ENTRY_REPLY_TO);
1747
        if (compose->account->protocol == A_NNTP) {
1748
                compose_entry_set(compose, compose->newsgroups,
1749
                                  COMPOSE_ENTRY_NEWSGROUPS);
1750
                compose_entry_set(compose, compose->followup_to,
1751
                                  COMPOSE_ENTRY_FOLLOWUP_TO);
1752
        }
1753
        compose_entry_set(compose, msginfo->subject, COMPOSE_ENTRY_SUBJECT);
1754
}
1755
1756
static void compose_insert_sig(Compose *compose, gboolean append,
1757
                               gboolean replace, gboolean scroll)
1758
{
1759
        GtkTextView *text = GTK_TEXT_VIEW(compose->text);
1760
        GtkTextBuffer *buffer;
1761
        GtkTextMark *mark;
1762
        GtkTextIter iter;
1763
        gchar *sig_str;
1764
        gboolean prev_autowrap;
1765
1766
        g_return_if_fail(compose->account != NULL);
1767
1768
        prev_autowrap = compose->autowrap;
1769
        compose->autowrap = FALSE;
1770
1771
        buffer = gtk_text_view_get_buffer(text);
1772
        mark = gtk_text_buffer_get_insert(buffer);
1773
        gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1774
1775
        if (replace) {
1776
                GtkTextIter start_iter, end_iter;
1777
1778
                gtk_text_buffer_get_start_iter(buffer, &start_iter);
1779
                gtk_text_buffer_get_end_iter(buffer, &iter);
1780
1781
                while (gtk_text_iter_begins_tag
1782
                        (&start_iter, compose->sig_tag) ||
1783
                       gtk_text_iter_forward_to_tag_toggle
1784
                        (&start_iter, compose->sig_tag)) {
1785
                        end_iter = start_iter;
1786
                        if (gtk_text_iter_forward_to_tag_toggle
1787
                                (&end_iter, compose->sig_tag)) {
1788
                                gtk_text_buffer_delete
1789
                                        (buffer, &start_iter, &end_iter);
1790
                                iter = start_iter;
1791
                        }
1792
                }
1793
        }
1794
1795
        if (scroll) {
1796
                if (append)
1797
                        gtk_text_buffer_get_end_iter(buffer, &iter);
1798
                else
1799
                        gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1800
        }
1801
1802
        sig_str = compose_get_signature_str(compose);
1803
        if (sig_str) {
1804
                if (!replace)
1805
                        gtk_text_buffer_insert(buffer, &iter, "\n\n", 2);
1806
                else if (!gtk_text_iter_starts_line(&iter))
1807
                        gtk_text_buffer_insert(buffer, &iter, "\n", 1);
1808
                gtk_text_buffer_insert_with_tags
1809
                        (buffer, &iter, sig_str, -1, compose->sig_tag, NULL);
1810
                g_free(sig_str);
1811
                if (scroll)
1812
                        gtk_text_buffer_place_cursor(buffer, &iter);
1813
        }
1814
1815
        compose->autowrap = prev_autowrap;
1816
        if (compose->autowrap)
1817
                compose_wrap_all(compose);
1818
1819
        if (scroll)
1820
                gtk_text_view_scroll_mark_onscreen(text, mark);
1821
}
1822
1823
static void compose_enable_sig(Compose *compose)
1824
{
1825
        GtkTextView *text = GTK_TEXT_VIEW(compose->text);
1826
        GtkTextBuffer *buffer;
1827
        GtkTextIter iter, start, end;
1828
        gchar *sig_str;
1829
1830
        g_return_if_fail(compose->account != NULL);
1831
1832
        buffer = gtk_text_view_get_buffer(text);
1833
        gtk_text_buffer_get_start_iter(buffer, &iter);
1834
1835
        sig_str = compose_get_signature_str(compose);
1836
        if (!sig_str)
1837
                return;
1838
1839
        if (gtkut_text_buffer_find(buffer, &iter, sig_str, TRUE, &start)) {
1840
                end = start;
1841
                gtk_text_iter_forward_chars(&end, g_utf8_strlen(sig_str, -1));
1842
                gtk_text_buffer_apply_tag(buffer, compose->sig_tag,
1843
                                          &start, &end);
1844
        }
1845
1846
        g_free(sig_str);
1847
}
1848
1849
static gchar *compose_get_signature_str(Compose *compose)
1850
{
1851
        gchar *sig_path;
1852
        gchar *sig_body = NULL;
1853
        gchar *sig_str = NULL;
1854
        gchar *utf8_sig_str = NULL;
1855
1856
        g_return_val_if_fail(compose->account != NULL, NULL);
1857
1858
        if (compose->account->sig_type == SIG_DIRECT) {
1859
                gchar *p, *sp;
1860
1861
                if (!compose->account->sig_text)
1862
                        return NULL;
1863
1864
                sp = compose->account->sig_text;
1865
                p = sig_body = g_malloc(strlen(compose->account->sig_text) + 1);
1866
                while (*sp) {
1867
                        if (*sp == '\\' && *(sp + 1) == 'n') {
1868
                                *p++ = '\n';
1869
                                sp += 2;
1870
                        } else
1871
                                *p++ = *sp++;
1872
                }
1873
                *p = '\0';
1874
1875
                if (prefs_common.sig_sep) {
1876
                        utf8_sig_str = g_strconcat(prefs_common.sig_sep, "\n",
1877
                                                   sig_body, NULL);
1878
                        g_free(sig_body);
1879
                } else
1880
                        utf8_sig_str = sig_body;
1881
1882
                return utf8_sig_str;
1883
        }
1884
1885
        if (!compose->account->sig_path)
1886
                return NULL;
1887
1888
        if (g_path_is_absolute(compose->account->sig_path) ||
1889
            compose->account->sig_type == SIG_COMMAND)
1890
                sig_path = g_strdup(compose->account->sig_path);
1891
        else {
1892
#ifdef G_OS_WIN32
1893
                sig_path = g_strconcat(get_rc_dir(),
1894
#else
1895
                sig_path = g_strconcat(get_home_dir(),
1896
#endif
1897
                                       G_DIR_SEPARATOR_S,
1898
                                       compose->account->sig_path, NULL);
1899
        }
1900
1901
        if (compose->account->sig_type == SIG_FILE) {
1902
                if (!is_file_or_fifo_exist(sig_path)) {
1903
                        debug_print("can't open signature file: %s\n",
1904
                                    sig_path);
1905
                        g_free(sig_path);
1906
                        return NULL;
1907
                }
1908
        }
1909
1910
        if (compose->account->sig_type == SIG_COMMAND)
1911
                sig_body = get_command_output(sig_path);
1912
        else {
1913
                gchar *tmp;
1914
1915
                tmp = file_read_to_str(sig_path);
1916
                if (!tmp)
1917
                        return NULL;
1918
                sig_body = normalize_newlines(tmp);
1919
                g_free(tmp);
1920
        }
1921
        g_free(sig_path);
1922
1923
        if (prefs_common.sig_sep) {
1924
                sig_str = g_strconcat(prefs_common.sig_sep, "\n", sig_body,
1925
                                      NULL);
1926
                g_free(sig_body);
1927
        } else
1928
                sig_str = sig_body;
1929
1930
        if (sig_str) {
1931
                gint error = 0;
1932
1933
                utf8_sig_str = conv_codeset_strdup_full
1934
                        (sig_str, conv_get_locale_charset_str(),
1935
                         CS_INTERNAL, &error);
1936
                if (!utf8_sig_str || error != 0) {
1937
                        if (g_utf8_validate(sig_str, -1, NULL) == TRUE) {
1938
                                g_free(utf8_sig_str);
1939
                                utf8_sig_str = sig_str;
1940
                        } else
1941
                                g_free(sig_str);
1942
                } else
1943
                        g_free(sig_str);
1944
        }
1945
1946
        return utf8_sig_str;
1947
}
1948
1949
static void compose_insert_file(Compose *compose, const gchar *file,
1950
                                gboolean scroll)
1951
{
1952
        GtkTextView *text = GTK_TEXT_VIEW(compose->text);
1953
        GtkTextBuffer *buffer;
1954
        GtkTextMark *mark;
1955
        GtkTextIter iter;
1956
        const gchar *cur_encoding;
1957
        gchar buf[BUFFSIZE];
1958
        gint len;
1959
        FILE *fp;
1960
        gboolean prev_autowrap;
1961
        CharSet enc;
1962
1963
        g_return_if_fail(file != NULL);
1964
1965
        enc = conv_check_file_encoding(file);
1966
1967
        if ((fp = g_fopen(file, "rb")) == NULL) {
1968
                FILE_OP_ERROR(file, "fopen");
1969
                return;
1970
        }
1971
1972
        prev_autowrap = compose->autowrap;
1973
        compose->autowrap = FALSE;
1974
1975
        buffer = gtk_text_view_get_buffer(text);
1976
        mark = gtk_text_buffer_get_insert(buffer);
1977
        gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1978
1979
        cur_encoding = conv_get_locale_charset_str();
1980
1981
        while (fgets(buf, sizeof(buf), fp) != NULL) {
1982
                gchar *str;
1983
                gint error = 0;
1984
1985
                if (enc == C_UTF_8) {
1986
                        str = conv_utf8todisp(buf, NULL);
1987
                } else {
1988
                        str = conv_codeset_strdup_full(buf, cur_encoding,
1989
                                                       CS_INTERNAL, &error);
1990
                        if (!str || error != 0) {
1991
                                if (g_utf8_validate(buf, -1, NULL) == TRUE) {
1992
                                        g_free(str);
1993
                                        str = g_strdup(buf);
1994
                                }
1995
                        }
1996
                        if (!str) continue;
1997
                }
1998
1999
                /* strip <CR> if DOS/Windows file,
2000
                   replace <CR> with <LF> if Macintosh file. */
2001
                strcrchomp(str);
2002
                len = strlen(str);
2003
                if (len > 0 && str[len - 1] != '\n') {
2004
                        while (--len >= 0)
2005
                                if (str[len] == '\r') str[len] = '\n';
2006
                }
2007
2008
                gtk_text_buffer_insert(buffer, &iter, str, -1);
2009
                g_free(str);
2010
        }
2011
2012
        fclose(fp);
2013
2014
        compose->autowrap = prev_autowrap;
2015
        if (compose->autowrap)
2016
                compose_wrap_all(compose);
2017
2018
        if (scroll)
2019
                gtk_text_view_scroll_mark_onscreen(text, mark);
2020
}
2021
2022
static void compose_attach_append(Compose *compose, const gchar *file,
2023
                                  const gchar *filename,
2024
                                  const gchar *content_type)
2025
{
2026
        GtkTreeIter iter;
2027
        AttachInfo *ainfo;
2028
        FILE *fp;
2029
        off_t size;
2030
2031
        g_return_if_fail(file != NULL);
2032
        g_return_if_fail(*file != '\0');
2033
2034
        if (!is_file_exist(file)) {
2035
                g_warning(_("File %s doesn't exist\n"), file);
2036
                return;
2037
        }
2038
        if ((size = get_file_size(file)) < 0) {
2039
                g_warning(_("Can't get file size of %s\n"), file);
2040
                return;
2041
        }
2042
        if (size == 0) {
2043
                alertpanel_notice(_("File %s is empty."), file);
2044
                return;
2045
        }
2046
        if ((fp = g_fopen(file, "rb")) == NULL) {
2047
                alertpanel_error(_("Can't read %s."), file);
2048
                return;
2049
        }
2050
        fclose(fp);
2051
2052
        if (!compose->use_attach) {
2053
                GtkItemFactory *ifactory;
2054
2055
                ifactory = gtk_item_factory_from_widget(compose->menubar);
2056
                menu_set_active(ifactory, "/View/Attachment", TRUE);
2057
        }
2058
2059
        ainfo = g_new0(AttachInfo, 1);
2060
        ainfo->file = g_strdup(file);
2061
2062
        if (content_type) {
2063
                ainfo->content_type = g_strdup(content_type);
2064
                if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
2065
                        MsgInfo *msginfo;
2066
                        MsgFlags flags = {0, 0};
2067
                        const gchar *name;
2068
2069
                        if (procmime_get_encoding_for_text_file(file) == ENC_7BIT)
2070
                                ainfo->encoding = ENC_7BIT;
2071
                        else
2072
                                ainfo->encoding = ENC_8BIT;
2073
2074
                        msginfo = procheader_parse_file(file, flags, FALSE);
2075
                        if (msginfo && msginfo->subject)
2076
                                name = msginfo->subject;
2077
                        else
2078
                                name = g_basename(filename ? filename : file);
2079
2080
                        ainfo->name = g_strdup_printf(_("Message: %s"), name);
2081
2082
                        procmsg_msginfo_free(msginfo);
2083
                } else {
2084
                        if (!g_ascii_strncasecmp(content_type, "text", 4))
2085
                                ainfo->encoding = procmime_get_encoding_for_text_file(file);
2086
                        else
2087
                                ainfo->encoding = ENC_BASE64;
2088
                        ainfo->name = g_strdup
2089
                                (g_basename(filename ? filename : file));
2090
                }
2091
        } else {
2092
                ainfo->content_type = procmime_get_mime_type(file);
2093
                if (!ainfo->content_type) {
2094
                        ainfo->content_type =
2095
                                g_strdup("application/octet-stream");
2096
                        ainfo->encoding = ENC_BASE64;
2097
                } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
2098
                        ainfo->encoding =
2099
                                procmime_get_encoding_for_text_file(file);
2100
                else
2101
                        ainfo->encoding = ENC_BASE64;
2102
                ainfo->name = g_strdup(g_basename(filename ? filename : file));        
2103
        }
2104
        ainfo->size = size;
2105
2106
        gtk_list_store_append(compose->attach_store, &iter);
2107
        gtk_list_store_set(compose->attach_store, &iter,
2108
                           COL_MIMETYPE, ainfo->content_type,
2109
                           COL_SIZE, to_human_readable(ainfo->size),
2110
                           COL_NAME, ainfo->name,
2111
                           COL_ATTACH_INFO, ainfo,
2112
                           -1);
2113
}
2114
2115
static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
2116
{
2117
        MimeInfo *mimeinfo;
2118
        MimeInfo *child;
2119
        gchar *infile;
2120
        gchar *outfile;
2121
2122
        mimeinfo = procmime_scan_message(msginfo);
2123
        if (!mimeinfo) return;
2124
2125
        infile = procmsg_get_message_file_path(msginfo);
2126
2127
        child = mimeinfo;
2128
        while (child != NULL) {
2129
                if (child->children || child->mime_type == MIME_MULTIPART)
2130
                        goto next;
2131
                if (child->mime_type != MIME_MESSAGE_RFC822 &&
2132
                    child->mime_type != MIME_IMAGE &&
2133
                    child->mime_type != MIME_AUDIO &&
2134
                    child->mime_type != MIME_VIDEO &&
2135
                    !child->filename && !child->name)
2136
                        goto next;
2137
2138
                outfile = procmime_get_tmp_file_name(child);
2139
                if (procmime_get_part(outfile, infile, child) < 0) {
2140
                        g_warning(_("Can't get the part of multipart message."));
2141
                        g_free(outfile);
2142
                        goto next;
2143
                }
2144
2145
                compose_attach_append
2146
                        (compose, outfile,
2147
                         child->filename ? child->filename : child->name,
2148
                         child->content_type);
2149
2150
                g_free(outfile);
2151
2152
next:
2153
                if (child->mime_type == MIME_MESSAGE_RFC822)
2154
                        child = child->next;
2155
                else
2156
                        child = procmime_mimeinfo_next(child);
2157
        }
2158
2159
        g_free(infile);
2160
        procmime_mimeinfo_free_all(mimeinfo);
2161
}
2162
2163
#define INDENT_CHARS        ">|#"
2164
2165
typedef enum {
2166
        WAIT_FOR_INDENT_CHAR,
2167
        WAIT_FOR_INDENT_CHAR_OR_SPACE,
2168
} IndentState;
2169
2170
/* return indent length, we allow:
2171
   indent characters followed by indent characters or spaces/tabs,
2172
   alphabets and numbers immediately followed by indent characters,
2173
   and the repeating sequences of the above
2174
   If quote ends with multiple spaces, only the first one is included. */
2175
static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
2176
                                    const GtkTextIter *start, gint *len)
2177
{
2178
        GtkTextIter iter = *start;
2179
        gunichar wc;
2180
        gchar ch[6];
2181
        gint clen;
2182
        IndentState state = WAIT_FOR_INDENT_CHAR;
2183
        gboolean is_space;
2184
        gboolean is_indent;
2185
        gint alnum_count = 0;
2186
        gint space_count = 0;
2187
        gint quote_len = 0;
2188
2189
        while (!gtk_text_iter_ends_line(&iter)) {
2190
                wc = gtk_text_iter_get_char(&iter);
2191
                if (g_unichar_iswide(wc))
2192
                        break;
2193
                clen = g_unichar_to_utf8(wc, ch);
2194
                if (clen != 1)
2195
                        break;
2196
2197
                is_indent = strchr(INDENT_CHARS, ch[0]) ? TRUE : FALSE;
2198
                is_space = g_unichar_isspace(wc);
2199
2200
                if (state == WAIT_FOR_INDENT_CHAR) {
2201
                        if (!is_indent && !g_unichar_isalnum(wc))
2202
                                break;
2203
                        if (is_indent) {
2204
                                quote_len += alnum_count + space_count + 1;
2205
                                alnum_count = space_count = 0;
2206
                                state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
2207
                        } else
2208
                                alnum_count++;
2209
                } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
2210
                        if (!is_indent && !is_space && !g_unichar_isalnum(wc))
2211
                                break;
2212
                        if (is_space)
2213
                                space_count++;
2214
                        else if (is_indent) {
2215
                                quote_len += alnum_count + space_count + 1;
2216
                                alnum_count = space_count = 0;
2217
                        } else {
2218
                                alnum_count++;
2219
                                state = WAIT_FOR_INDENT_CHAR;
2220
                        }
2221
                }
2222
2223
                gtk_text_iter_forward_char(&iter);
2224
        }
2225
2226
        if (quote_len > 0 && space_count > 0)
2227
                quote_len++;
2228
2229
        if (len)
2230
                *len = quote_len;
2231
2232
        if (quote_len > 0) {
2233
                iter = *start;
2234
                gtk_text_iter_forward_chars(&iter, quote_len);
2235
                return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
2236
        }
2237
2238
        return NULL;
2239
}
2240
2241
/* return TRUE if the line is itemized */
2242
static gboolean compose_is_itemized(GtkTextBuffer *buffer,
2243
                                    const GtkTextIter *start)
2244
{
2245
        GtkTextIter iter = *start;
2246
        gunichar wc;
2247
        gchar ch[6];
2248
        gint clen;
2249
2250
        if (gtk_text_iter_ends_line(&iter))
2251
                return FALSE;
2252
2253
        while (1) {
2254
                wc = gtk_text_iter_get_char(&iter);
2255
                if (!g_unichar_isspace(wc))
2256
                        break;
2257
                gtk_text_iter_forward_char(&iter);
2258
                if (gtk_text_iter_ends_line(&iter))
2259
                        return FALSE;
2260
        }
2261
2262
        clen = g_unichar_to_utf8(wc, ch);
2263
2264
        /* (1), 2), 3. etc. */
2265
        if ((clen == 1 && ch[0] == '(') || g_unichar_isdigit(wc)) {
2266
                gboolean digit_appeared = FALSE;
2267
2268
                if (ch[0] == '(')
2269
                        gtk_text_iter_forward_char(&iter);
2270
2271
                while (1) {
2272
                        wc = gtk_text_iter_get_char(&iter);
2273
                        clen = g_unichar_to_utf8(wc, ch);
2274
                        if (g_unichar_isdigit(wc)) {
2275
                                digit_appeared = TRUE;
2276
                                gtk_text_iter_forward_char(&iter);
2277
                                if (gtk_text_iter_ends_line(&iter))
2278
                                        return FALSE;
2279
                        } else if (clen == 1 &&
2280
                                   (ch[0] == ')' || ch[0] == '.')) {
2281
                                if (!digit_appeared)
2282
                                        return FALSE;
2283
                                gtk_text_iter_forward_char(&iter);
2284
                                if (gtk_text_iter_ends_line(&iter))
2285
                                        return TRUE;
2286
                                wc = gtk_text_iter_get_char(&iter);
2287
                                if (g_unichar_isspace(wc))
2288
                                        return TRUE;
2289
                                else
2290
                                        return FALSE;
2291
                        } else
2292
                                return FALSE;
2293
                }
2294
        }
2295
2296
        if (clen != 1)
2297
                return FALSE;
2298
        if (!strchr("*-+", ch[0]))
2299
                return FALSE;
2300
2301
        gtk_text_iter_forward_char(&iter);
2302
        if (gtk_text_iter_ends_line(&iter))
2303
                return FALSE;
2304
        wc = gtk_text_iter_get_char(&iter);
2305
        if (g_unichar_isspace(wc))
2306
                return TRUE;
2307
2308
        return FALSE;
2309
}
2310
2311
static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
2312
                                           const GtkTextIter *start,
2313
                                           GtkTextIter *break_pos,
2314
                                           gint max_col,
2315
                                           gint quote_len)
2316
{
2317
        GtkTextIter iter = *start, line_end = *start;
2318
        PangoLogAttr *attrs;
2319
        gchar *str;
2320
        gchar *p;
2321
        gint len;
2322
        gint i;
2323
        gint col = 0;
2324
        gint pos = 0;
2325
        gboolean can_break = FALSE;
2326
        gboolean do_break = FALSE;
2327
        gboolean prev_dont_break = FALSE;
2328
2329
        gtk_text_iter_forward_to_line_end(&line_end);
2330
        str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
2331
        len = g_utf8_strlen(str, -1);
2332
        /* g_print("breaking line: %d: %s (len = %d)\n",
2333
                gtk_text_iter_get_line(&iter), str, len); */
2334
        attrs = g_new(PangoLogAttr, len + 1);
2335
2336
        pango_default_break(str, -1, NULL, attrs, len + 1);
2337
2338
        p = str;
2339
2340
        /* skip quote and leading spaces */
2341
        for (i = 0; *p != '\0' && i < len; i++) {
2342
                gunichar wc;
2343
2344
                wc = g_utf8_get_char(p);
2345
                if (i >= quote_len && !g_unichar_isspace(wc))
2346
                        break;
2347
                if (g_unichar_iswide(wc))
2348
                        col += 2;
2349
                else if (*p == '\t')
2350
                        col += 8;
2351
                else
2352
                        col++;
2353
                p = g_utf8_next_char(p);
2354
        }
2355
2356
        for (; *p != '\0' && i < len; i++) {
2357
                PangoLogAttr *attr = attrs + i;
2358
                gunichar wc;
2359
                gint uri_len;
2360
2361
                if (attr->is_line_break && can_break && !prev_dont_break)
2362
                        pos = i;
2363
2364
                /* don't wrap URI */
2365
                if ((uri_len = get_uri_len(p)) > 0) {
2366
                        col += uri_len;
2367
                        if (pos > 0 && col > max_col) {
2368
                                do_break = TRUE;
2369
                                break;
2370
                        }
2371
                        i += uri_len - 1;
2372
                        p += uri_len;
2373
                        can_break = TRUE;
2374
                        continue;
2375
                }
2376
2377
                wc = g_utf8_get_char(p);
2378
                if (g_unichar_iswide(wc)) {
2379
                        col += 2;
2380
                        if (prev_dont_break && can_break && attr->is_line_break)
2381
                                pos = i;
2382
                } else if (*p == '\t')
2383
                        col += 8;
2384
                else
2385
                        col++;
2386
                if (pos > 0 && col > max_col) {
2387
                        do_break = TRUE;
2388
                        break;
2389
                }
2390
2391
                if (*p == '-' || *p == '/')
2392
                        prev_dont_break = TRUE;
2393
                else
2394
                        prev_dont_break = FALSE;
2395
2396
                p = g_utf8_next_char(p);
2397
                can_break = TRUE;
2398
        }
2399
2400
        debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
2401
2402
        g_free(attrs);
2403
        g_free(str);
2404
2405
        *break_pos = *start;
2406
        gtk_text_iter_set_line_offset(break_pos, pos);
2407
2408
        return do_break;
2409
}
2410
2411
static gboolean compose_join_next_line(GtkTextBuffer *buffer,
2412
                                       GtkTextIter *iter,
2413
                                       const gchar *quote_str)
2414
{
2415
        GtkTextIter iter_ = *iter, cur, prev, next, end;
2416
        PangoLogAttr attrs[3];
2417
        gchar *str;
2418
        gchar *next_quote_str;
2419
        gunichar wc1, wc2;
2420
        gint quote_len;
2421
        gboolean keep_cursor = FALSE;
2422
2423
        if (!gtk_text_iter_forward_line(&iter_) ||
2424
            gtk_text_iter_ends_line(&iter_))
2425
                return FALSE;
2426
2427
        next_quote_str = compose_get_quote_str(buffer, &iter_, &quote_len);
2428
2429
        if ((quote_str || next_quote_str) &&
2430
            strcmp2(quote_str, next_quote_str) != 0) {
2431
                g_free(next_quote_str);
2432
                return FALSE;
2433
        }
2434
        g_free(next_quote_str);
2435
2436
        end = iter_;
2437
        if (quote_len > 0) {
2438
                gtk_text_iter_forward_chars(&end, quote_len);
2439
                if (gtk_text_iter_ends_line(&end))
2440
                        return FALSE;
2441
        }
2442
2443
        /* don't join itemized lines */
2444
        if (compose_is_itemized(buffer, &end))
2445
                return FALSE;
2446
2447
        /* delete quote str */
2448
        if (quote_len > 0)
2449
                gtk_text_buffer_delete(buffer, &iter_, &end);
2450
2451
        /* delete linebreak and extra spaces */
2452
        prev = cur = iter_;
2453
        while (gtk_text_iter_backward_char(&cur)) {
2454
                wc1 = gtk_text_iter_get_char(&cur);
2455
                if (!g_unichar_isspace(wc1))
2456
                        break;
2457
                prev = cur;
2458
        }
2459
        next = cur = iter_;
2460
        while (!gtk_text_iter_ends_line(&cur)) {
2461
                wc1 = gtk_text_iter_get_char(&cur);
2462
                if (!g_unichar_isspace(wc1))
2463
                        break;
2464
                gtk_text_iter_forward_char(&cur);
2465
                next = cur;
2466
        }
2467
        if (!gtk_text_iter_equal(&prev, &next)) {
2468
                GtkTextMark *mark;
2469
2470
                mark = gtk_text_buffer_get_insert(buffer);
2471
                gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
2472
                if (gtk_text_iter_equal(&prev, &cur))
2473
                        keep_cursor = TRUE;
2474
                gtk_text_buffer_delete(buffer, &prev, &next);
2475
        }
2476
        iter_ = prev;
2477
2478
        /* insert space if required */
2479
        gtk_text_iter_backward_char(&prev);
2480
        wc1 = gtk_text_iter_get_char(&prev);
2481
        wc2 = gtk_text_iter_get_char(&next);
2482
        gtk_text_iter_forward_char(&next);
2483
        str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
2484
        pango_default_break(str, -1, NULL, attrs, 3);
2485
        if (!attrs[1].is_line_break ||
2486
            (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
2487
                gtk_text_buffer_insert(buffer, &iter_, " ", 1);
2488
                if (keep_cursor) {
2489
                        gtk_text_iter_backward_char(&iter_);
2490
                        gtk_text_buffer_place_cursor(buffer, &iter_);
2491
                }
2492
        }
2493
        g_free(str);
2494
2495
        *iter = iter_;
2496
        return TRUE;
2497
}
2498
2499
static void compose_wrap_paragraph(Compose *compose, GtkTextIter *par_iter)
2500
{
2501
        GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2502
        GtkTextBuffer *buffer;
2503
        GtkTextIter iter, break_pos;
2504
        gchar *quote_str = NULL;
2505
        gint quote_len;
2506
        gboolean wrap_quote = prefs_common.linewrap_quote;
2507
        gboolean prev_autowrap = compose->autowrap;
2508
2509
        compose->autowrap = FALSE;
2510
2511
        buffer = gtk_text_view_get_buffer(text);
2512
2513
        if (par_iter) {
2514
                iter = *par_iter;
2515
        } else {
2516
                GtkTextMark *mark;
2517
                mark = gtk_text_buffer_get_insert(buffer);
2518
                gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2519
        }
2520
2521
        /* move to paragraph start */
2522
        gtk_text_iter_set_line_offset(&iter, 0);
2523
        if (gtk_text_iter_ends_line(&iter)) {
2524
                while (gtk_text_iter_ends_line(&iter) &&
2525
                       gtk_text_iter_forward_line(&iter))
2526
                        ;
2527
        } else {
2528
                while (gtk_text_iter_backward_line(&iter)) {
2529
                        if (gtk_text_iter_ends_line(&iter)) {
2530
                                gtk_text_iter_forward_line(&iter);
2531
                                break;
2532
                        }
2533
                }
2534
        }
2535
2536
        /* go until paragraph end (empty line) */
2537
        while (!gtk_text_iter_ends_line(&iter)) {
2538
                quote_str = compose_get_quote_str(buffer, &iter, &quote_len);
2539
                if (quote_str) {
2540
                        if (!wrap_quote) {
2541
                                gtk_text_iter_forward_line(&iter);
2542
                                g_free(quote_str);
2543
                                continue;
2544
                        }
2545
                        debug_print("compose_wrap_paragraph(): quote_str = '%s'\n", quote_str);
2546
                }
2547
2548
                if (compose_get_line_break_pos(buffer, &iter, &break_pos,
2549
                                               prefs_common.linewrap_len,
2550
                                               quote_len)) {
2551
                        GtkTextIter prev, next, cur;
2552
2553
                        gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
2554
2555
                        /* remove trailing spaces */
2556
                        cur = break_pos;
2557
                        gtk_text_iter_backward_char(&cur);
2558
                        prev = next = cur;
2559
                        while (!gtk_text_iter_starts_line(&cur)) {
2560
                                gunichar wc;
2561
2562
                                gtk_text_iter_backward_char(&cur);
2563
                                wc = gtk_text_iter_get_char(&cur);
2564
                                if (!g_unichar_isspace(wc))
2565
                                        break;
2566
                                prev = cur;
2567
                        }
2568
                        if (!gtk_text_iter_equal(&prev, &next)) {
2569
                                gtk_text_buffer_delete(buffer, &prev, &next);
2570
                                break_pos = next;
2571
                                gtk_text_iter_forward_char(&break_pos);
2572
                        }
2573
2574
                        if (quote_str)
2575
                                gtk_text_buffer_insert(buffer, &break_pos,
2576
                                                       quote_str, -1);
2577
2578
                        iter = break_pos;
2579
                        compose_join_next_line(buffer, &iter, quote_str);
2580
2581
                        /* move iter to current line start */
2582
                        gtk_text_iter_set_line_offset(&iter, 0);
2583
                } else {
2584
                        /* move iter to next line start */
2585
                        iter = break_pos;
2586
                        gtk_text_iter_forward_line(&iter);
2587
                }
2588
2589
                g_free(quote_str);
2590
        }
2591
2592
        if (par_iter)
2593
                *par_iter = iter;
2594
2595
        compose->autowrap = prev_autowrap;
2596
}
2597
2598
static void compose_wrap_all(Compose *compose)
2599
{
2600
        compose_wrap_all_full(compose, FALSE);
2601
}
2602
2603
static void compose_wrap_all_full(Compose *compose, gboolean autowrap)
2604
{
2605
        GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2606
        GtkTextBuffer *buffer;
2607
        GtkTextIter iter;
2608
2609
        buffer = gtk_text_view_get_buffer(text);
2610
2611
        gtk_text_buffer_get_start_iter(buffer, &iter);
2612
        while (!gtk_text_iter_is_end(&iter))
2613
                compose_wrap_paragraph(compose, &iter);
2614
}
2615
2616
static void compose_set_title(Compose *compose)
2617
{
2618
        gchar *str;
2619
        gchar *edited;
2620
        const gchar *subject;
2621
2622
        subject = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
2623
        if (!subject || subject[0] == '\0')
2624
                subject = _("(No Subject)");
2625
2626
        edited = compose->modified ? " *" : "";
2627
        str = g_strdup_printf(_("%s - Compose%s"), subject, edited);
2628
        gtk_window_set_title(GTK_WINDOW(compose->window), str);
2629
        g_free(str);
2630
}
2631
2632
static void compose_select_account(Compose *compose, PrefsAccount *account,
2633
                                   gboolean init)
2634
{
2635
        GtkItemFactory *ifactory;
2636
        PrefsAccount *prev_account;
2637
2638
        g_return_if_fail(account != NULL);
2639
2640
        prev_account = compose->account;
2641
        compose->account = account;
2642
2643
        compose_set_title(compose);
2644
2645
        ifactory = gtk_item_factory_from_widget(compose->menubar);
2646
2647
        if (account->protocol == A_NNTP &&
2648
            (init || prev_account->protocol != A_NNTP)) {
2649
                gtk_widget_show(compose->newsgroups_hbox);
2650
                gtk_widget_show(compose->newsgroups_entry);
2651
                gtk_table_set_row_spacing(GTK_TABLE(compose->table), 2, 4);
2652
                compose->use_newsgroups = TRUE;
2653
2654
                menu_set_active(ifactory, "/View/To", FALSE);
2655
                menu_set_sensitive(ifactory, "/View/To", TRUE);
2656
                menu_set_active(ifactory, "/View/Cc", FALSE);
2657
                menu_set_sensitive(ifactory, "/View/Followup to", TRUE);
2658
        } else if (account->protocol != A_NNTP &&
2659
                   (init || prev_account->protocol == A_NNTP)) {
2660
                gtk_widget_hide(compose->newsgroups_hbox);
2661
                gtk_widget_hide(compose->newsgroups_entry);
2662
                gtk_table_set_row_spacing(GTK_TABLE(compose->table), 2, 0);
2663
                gtk_widget_queue_resize(compose->table_vbox);
2664
                compose->use_newsgroups = FALSE;
2665
2666
                menu_set_active(ifactory, "/View/To", TRUE);
2667
                menu_set_sensitive(ifactory, "/View/To", FALSE);
2668
                menu_set_active(ifactory, "/View/Cc", TRUE);
2669
                menu_set_active(ifactory, "/View/Followup to", FALSE);
2670
                menu_set_sensitive(ifactory, "/View/Followup to", FALSE);
2671
        }
2672
2673
        if (account->set_autocc) {
2674
                compose_entry_show(compose, COMPOSE_ENTRY_CC);
2675
                if (account->auto_cc && compose->mode != COMPOSE_REEDIT)
2676
                        compose_entry_set(compose, account->auto_cc,
2677
                                          COMPOSE_ENTRY_CC);
2678
        }
2679
        if (account->set_autobcc) {
2680
                compose_entry_show(compose, COMPOSE_ENTRY_BCC);
2681
                if (account->auto_bcc && compose->mode != COMPOSE_REEDIT)
2682
                        compose_entry_set(compose, account->auto_bcc,
2683
                                          COMPOSE_ENTRY_BCC);
2684
        }
2685
        if (account->set_autoreplyto) {
2686
                compose_entry_show(compose, COMPOSE_ENTRY_REPLY_TO);
2687
                if (account->auto_replyto && compose->mode != COMPOSE_REEDIT)
2688
                        compose_entry_set(compose, account->auto_replyto,
2689
                                          COMPOSE_ENTRY_REPLY_TO);
2690
        }
2691
2692
#if USE_GPGME
2693
        if (rfc2015_is_available()) {
2694
                if (account->default_sign)
2695
                        menu_set_active(ifactory, "/Tools/PGP Sign", TRUE);
2696
                if (account->default_encrypt)
2697
                        menu_set_active(ifactory, "/Tools/PGP Encrypt", TRUE);
2698
        }
2699
#endif /* USE_GPGME */
2700
2701
        if (!init && compose->mode != COMPOSE_REDIRECT && prefs_common.auto_sig)
2702
                compose_insert_sig(compose, TRUE, TRUE, FALSE);
2703
}
2704
2705
static gboolean compose_check_for_valid_recipient(Compose *compose)
2706
{
2707
        const gchar *to_raw = "", *cc_raw = "", *bcc_raw = "";
2708
        const gchar *newsgroups_raw = "";
2709
        gchar *to, *cc, *bcc;
2710
        gchar *newsgroups;
2711
2712
        if (compose->use_to)
2713
                to_raw = gtk_entry_get_text(GTK_ENTRY(compose->to_entry));
2714
        if (compose->use_cc)
2715
                cc_raw = gtk_entry_get_text(GTK_ENTRY(compose->cc_entry));
2716
        if (compose->use_bcc)
2717
                bcc_raw = gtk_entry_get_text(GTK_ENTRY(compose->bcc_entry));
2718
        if (compose->use_newsgroups)
2719
                newsgroups_raw = gtk_entry_get_text
2720
                        (GTK_ENTRY(compose->newsgroups_entry));
2721
2722
        Xstrdup_a(to, to_raw, return FALSE);
2723
        Xstrdup_a(cc, cc_raw, return FALSE);
2724
        Xstrdup_a(bcc, bcc_raw, return FALSE);
2725
        Xstrdup_a(newsgroups, newsgroups_raw, return FALSE);
2726
        g_strstrip(to);
2727
        g_strstrip(cc);
2728
        g_strstrip(bcc);
2729
        g_strstrip(newsgroups);
2730
2731
        if (*to == '\0' && *cc == '\0' && *bcc == '\0' && *newsgroups == '\0')
2732
                return FALSE;
2733
        else
2734
                return TRUE;
2735
}
2736
2737
static gboolean compose_check_entries(Compose *compose)
2738
{
2739
        const gchar *str;
2740
2741
        if (compose_check_for_valid_recipient(compose) == FALSE) {
2742
                alertpanel_error(_("Recipient is not specified."));
2743
                return FALSE;
2744
        }
2745
2746
        str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
2747
        if (*str == '\0') {
2748
                AlertValue aval;
2749
2750
                aval = alertpanel(_("Empty subject"),
2751
                                  _("Subject is empty. Send it anyway?"),
2752
                                  GTK_STOCK_YES, GTK_STOCK_NO, NULL);
2753
                if (aval != G_ALERTDEFAULT)
2754
                        return FALSE;
2755
        }
2756
2757
        return TRUE;
2758
}
2759
2760
void compose_lock(Compose *compose)
2761
{
2762
        compose->lock_count++;
2763
}
2764
2765
void compose_unlock(Compose *compose)
2766
{
2767
        if (compose->lock_count > 0)
2768
                compose->lock_count--;
2769
}
2770
2771
static gint compose_send(Compose *compose)
2772
{
2773
        gchar tmp[MAXPATHLEN + 1];
2774
        gint ok = 0;
2775
2776
        if (compose->lock_count > 0)
2777
                return 1;
2778
2779
        g_return_val_if_fail(compose->account != NULL, -1);
2780
2781
        compose_lock(compose);
2782
2783
        if (compose_check_entries(compose) == FALSE) {
2784
                compose_unlock(compose);
2785
                return 1;
2786
        }
2787
2788
        if (!main_window_toggle_online_if_offline(main_window_get())) {
2789
                compose_unlock(compose);
2790
                return 1;
2791
        }
2792
2793
        /* write to temporary file */
2794
        g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.%p",
2795
                   get_tmp_dir(), G_DIR_SEPARATOR, compose);
2796
2797
        if (compose->mode == COMPOSE_REDIRECT) {
2798
                if (compose_redirect_write_to_file(compose, tmp) < 0) {
2799
                        compose_unlock(compose);
2800
                        return -1;
2801
                }
2802
        } else {
2803
                if (compose_write_to_file(compose, tmp, FALSE) < 0) {
2804
                        compose_unlock(compose);
2805
                        return -1;
2806
                }
2807
        }
2808
2809
        if (!compose->to_list && !compose->newsgroup_list) {
2810
                g_warning(_("can't get recipient list."));
2811
                g_unlink(tmp);
2812
                compose_unlock(compose);
2813
                return -1;
2814
        }
2815
2816
        if (compose->to_list) {
2817
                PrefsAccount *ac;
2818
2819
                if (compose->account->protocol != A_NNTP)
2820
                        ac = compose->account;
2821
                else {
2822
                        ac = account_find_from_address(compose->account->address);
2823
                        if (!ac) {
2824
                                if (cur_account && cur_account->protocol != A_NNTP)
2825
                                        ac = cur_account;
2826
                                else
2827
                                        ac = account_get_default();
2828
                        }
2829
                        if (!ac || ac->protocol == A_NNTP) {
2830
                                alertpanel_error(_("Account for sending mail is not specified.\n"
2831
                                                   "Please select a mail account before sending."));
2832
                                g_unlink(tmp);
2833
                                compose_unlock(compose);
2834
                                return -1;
2835
                        }
2836
                }
2837
                ok = send_message(tmp, ac, compose->to_list);
2838
                statusbar_pop_all();
2839
        }
2840
2841
        if (ok == 0 && compose->newsgroup_list) {
2842
                ok = news_post(FOLDER(compose->account->folder), tmp);
2843
                if (ok < 0) {
2844
                        alertpanel_error(_("Error occurred while posting the message to %s ."),
2845
                                         compose->account->nntp_server);
2846
                        g_unlink(tmp);
2847
                        compose_unlock(compose);
2848
                        return -1;
2849
                }
2850
        }
2851
2852
        if (ok == 0) {
2853
                if (compose->mode == COMPOSE_REEDIT) {
2854
                        compose_remove_reedit_target(compose);
2855
                        if (compose->targetinfo)
2856
                                folderview_update_item
2857
                                        (compose->targetinfo->folder, TRUE);
2858
                }
2859
                /* save message to outbox */
2860
                if (prefs_common.savemsg) {
2861
                        FolderItem *outbox;
2862
                        gboolean drop_done = FALSE;
2863
2864
                        /* filter sent message */
2865
                        if (prefs_common.filter_sent) {
2866
                                FilterInfo *fltinfo;
2867
2868
                                fltinfo = filter_info_new();
2869
                                fltinfo->account = compose->account;
2870
                                fltinfo->flags.perm_flags = 0;
2871
                                fltinfo->flags.tmp_flags = MSG_RECEIVED;
2872
2873
                                filter_apply(prefs_common.fltlist, tmp,
2874
                                             fltinfo);
2875
2876
                                drop_done = fltinfo->drop_done;
2877
                                folderview_update_all_updated(TRUE);
2878
                                filter_info_free(fltinfo);
2879
                        }
2880
2881
                        if (!drop_done) {
2882
                                outbox = account_get_special_folder
2883
                                        (compose->account, F_OUTBOX);
2884
                                if (procmsg_save_to_outbox(outbox, tmp) < 0)
2885
                                        alertpanel_error
2886
                                                (_("Can't save the message to outbox."));
2887
                                else
2888
                                        folderview_update_item(outbox, TRUE);
2889
                        }
2890
                }
2891
        }
2892
2893
        g_unlink(tmp);
2894
        compose_unlock(compose);
2895
2896
        return ok;
2897
}
2898
2899
#if USE_GPGME
2900
/* interfaces to rfc2015 to keep out the prefs stuff there.
2901
 * returns 0 on success and -1 on error. */
2902
static gint compose_create_signers_list(Compose *compose, GSList **pkey_list)
2903
{
2904
        const gchar *key_id = NULL;
2905
        GSList *key_list;
2906
2907
        switch (compose->account->sign_key) {
2908
        case SIGN_KEY_DEFAULT:
2909
                *pkey_list = NULL;
2910
                return 0;
2911
        case SIGN_KEY_BY_FROM:
2912
                key_id = compose->account->address;
2913
                break;
2914
        case SIGN_KEY_CUSTOM:
2915
                key_id = compose->account->sign_key_id;
2916
                break;
2917
        default:
2918
                break;
2919
        }
2920
2921
        key_list = rfc2015_create_signers_list(key_id);
2922
2923
        if (!key_list) {
2924
                alertpanel_error(_("Could not find any key associated with "
2925
                                   "currently selected key id `%s'."), key_id);
2926
                return -1;
2927
        }
2928
2929
        *pkey_list = key_list;
2930
        return 0;
2931
}
2932
2933
/* clearsign message body text */
2934
static gint compose_clearsign_text(Compose *compose, gchar **text)
2935
{
2936
        GSList *key_list;
2937
        gchar *tmp_file;
2938
2939
        tmp_file = get_tmp_file();
2940
        if (str_write_to_file(*text, tmp_file) < 0) {
2941
                g_free(tmp_file);
2942
                return -1;
2943
        }
2944
2945
        if (compose_create_signers_list(compose, &key_list) < 0 ||
2946
            rfc2015_clearsign(tmp_file, key_list) < 0) {
2947
                g_unlink(tmp_file);
2948
                g_free(tmp_file);
2949
                return -1;
2950
        }
2951
2952
        g_free(*text);
2953
        *text = file_read_to_str(tmp_file);
2954
        g_unlink(tmp_file);
2955
        g_free(tmp_file);
2956
        if (*text == NULL)
2957
                return -1;
2958
2959
        return 0;
2960
}
2961
#endif /* USE_GPGME */
2962
2963
static gint compose_write_to_file(Compose *compose, const gchar *file,
2964
                                  gboolean is_draft)
2965
{
2966
        GtkTextBuffer *buffer;
2967
        GtkTextIter start, end;
2968
        GtkTreeModel *model = GTK_TREE_MODEL(compose->attach_store);
2969
        FILE *fp;
2970
        size_t len;
2971
        gchar *chars;
2972
        gchar *buf;
2973
        gchar *canon_buf;
2974
        const gchar *out_charset;
2975
        const gchar *body_charset;
2976
        const gchar *src_charset = CS_INTERNAL;
2977
        EncodingType encoding;
2978
        gint line;
2979
2980
        if ((fp = g_fopen(file, "wb")) == NULL) {
2981
                FILE_OP_ERROR(file, "fopen");
2982
                return -1;
2983
        }
2984
2985
        /* chmod for security */
2986
        if (change_file_mode_rw(fp, file) < 0) {
2987
                FILE_OP_ERROR(file, "chmod");
2988
                g_warning(_("can't change file mode\n"));
2989
        }
2990
2991
        /* get outgoing charset */
2992
        out_charset = conv_get_charset_str(compose->out_encoding);
2993
        if (!out_charset)
2994
                out_charset = conv_get_outgoing_charset_str();
2995
        if (!g_ascii_strcasecmp(out_charset, CS_US_ASCII))
2996
                out_charset = CS_ISO_8859_1;
2997
        body_charset = out_charset;
2998
2999
        /* get all composed text */
3000
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
3001
        gtk_text_buffer_get_start_iter(buffer, &start);
3002
        gtk_text_buffer_get_end_iter(buffer, &end);
3003
        chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
3004
        if (is_ascii_str(chars)) {
3005
                buf = chars;
3006
                chars = NULL;
3007
                body_charset = CS_US_ASCII;
3008
                encoding = ENC_7BIT;
3009
        } else {
3010
                gint error = 0;
3011
3012
                buf = conv_codeset_strdup_full
3013
                        (chars, src_charset, body_charset, &error);
3014
                if (!buf || error != 0) {
3015
                        AlertValue aval = G_ALERTDEFAULT;
3016
                        gchar *msg;
3017
3018
                        g_free(buf);
3019
3020
                        if (!is_draft) {
3021
                                msg = g_strdup_printf(_("Can't convert the character encoding of the message body from %s to %s.\n"
3022
                                                        "\n"
3023
                                                        "Send it as %s anyway?"),
3024
                                                      src_charset, body_charset,
3025
                                                      src_charset);
3026
                                aval = alertpanel_full
3027
                                        (_("Code conversion error"), msg, ALERT_ERROR,
3028
                                         G_ALERTALTERNATE,
3029
                                         FALSE, GTK_STOCK_YES, GTK_STOCK_NO, NULL);
3030
                                g_free(msg);
3031
                        }
3032
3033
                        if (aval != G_ALERTDEFAULT) {
3034
                                g_free(chars);
3035
                                fclose(fp);
3036
                                g_unlink(file);
3037
                                return -1;
3038
                        } else {
3039
                                buf = chars;
3040
                                out_charset = body_charset = src_charset;
3041
                                chars = NULL;
3042
                        }
3043
                }
3044
3045
                if (prefs_common.encoding_method == CTE_BASE64)
3046
                        encoding = ENC_BASE64;
3047
                else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
3048
                        encoding = ENC_QUOTED_PRINTABLE;
3049
                else if (prefs_common.encoding_method == CTE_8BIT)
3050
                        encoding = ENC_8BIT;
3051
                else
3052
                        encoding = procmime_get_encoding_for_charset
3053
                                (body_charset);
3054
        }
3055
        g_free(chars);
3056
3057
        canon_buf = canonicalize_str(buf);
3058
        g_free(buf);
3059
        buf = canon_buf;
3060
3061
#if USE_GPGME
3062
        /* chomp all trailing spaces */
3063
        if (rfc2015_is_available() && !is_draft &&
3064
            compose->use_signing && !compose->account->clearsign) {
3065
                gchar *tmp;
3066
                tmp = strchomp_all(buf);
3067
                g_free(buf);
3068
                buf = tmp;
3069
#if 0
3070
                if (encoding == ENC_7BIT)
3071
                        encoding = ENC_QUOTED_PRINTABLE;
3072
                else if (encoding == ENC_8BIT)
3073
                        encoding = ENC_BASE64;
3074
#endif
3075
        }
3076
3077
        if (rfc2015_is_available() && !is_draft &&
3078
            compose->use_signing && compose->account->clearsign) {
3079
                /* MIME encoding doesn't fit with cleartext signature */
3080
                if (encoding == ENC_QUOTED_PRINTABLE || encoding == ENC_BASE64)
3081
                        encoding = ENC_8BIT;
3082
3083
                if (compose_clearsign_text(compose, &buf) < 0) {
3084
                        g_warning("clearsign failed\n");
3085
                        fclose(fp);
3086
                        g_unlink(file);
3087
                        g_free(buf);
3088
                        return -1;
3089
                }
3090
        }
3091
#endif
3092
3093
        debug_print("src encoding = %s, out encoding = %s, "
3094
                    "body encoding = %s, transfer encoding = %s\n",
3095
                    src_charset, out_charset, body_charset,
3096
                    procmime_get_encoding_str(encoding));
3097
3098
        /* check for line length limit */
3099
        if (!is_draft &&
3100
            encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
3101
            check_line_length(buf, 1000, &line) < 0) {
3102
                AlertValue aval;
3103
                gchar *msg;
3104
3105
                msg = g_strdup_printf
3106
                        (_("Line %d exceeds the line length limit (998 bytes).\n"
3107
                           "The contents of the message might be broken on the way to the delivery.\n"
3108
                           "\n"
3109
                           "Send it anyway?"), line + 1);
3110
                aval = alertpanel_full(_("Line length limit"),
3111
                                       msg, ALERT_WARNING,
3112
                                       G_ALERTALTERNATE, FALSE,
3113
                                       GTK_STOCK_YES, GTK_STOCK_NO, NULL);
3114
                if (aval != G_ALERTDEFAULT) {
3115
                        g_free(msg);
3116
                        fclose(fp);
3117
                        g_unlink(file);
3118
                        g_free(buf);
3119
                        return -1;
3120
                }
3121
        }
3122
3123
        /* write headers */
3124
        if (compose_write_headers(compose, fp, out_charset,
3125
                                  body_charset, encoding, is_draft) < 0) {
3126
                g_warning("can't write headers\n");
3127
                fclose(fp);
3128
                g_unlink(file);
3129
                g_free(buf);
3130
                return -1;
3131
        }
3132
3133
        if (compose->use_attach &&
3134
            gtk_tree_model_iter_n_children(model, NULL) > 0) {
3135
#if USE_GPGME
3136
            /* This prolog message is ignored by mime software and
3137
             * because it would make our signing/encryption task
3138
             * tougher, we don't emit it in that case */
3139
            if (!rfc2015_is_available() ||
3140
                (!compose->use_signing && !compose->use_encryption))
3141
#endif
3142
                fputs("This is a multi-part message in MIME format.\n", fp);
3143
3144
                fprintf(fp, "\n--%s\n", compose->boundary);
3145
                fprintf(fp, "Content-Type: text/plain; charset=%s\n",
3146
                        body_charset);
3147
#if USE_GPGME
3148
                if (rfc2015_is_available() &&
3149
                    compose->use_signing && !compose->account->clearsign)
3150
                        fprintf(fp, "Content-Disposition: inline\n");
3151
#endif
3152
                fprintf(fp, "Content-Transfer-Encoding: %s\n",
3153
                        procmime_get_encoding_str(encoding));
3154
                fputc('\n', fp);
3155
        }
3156
3157
        /* write body */
3158
        len = strlen(buf);
3159
        if (encoding == ENC_BASE64) {
3160
                gchar outbuf[B64_BUFFSIZE];
3161
                gint i, l;
3162
3163
                for (i = 0; i < len; i += B64_LINE_SIZE) {
3164
                        l = MIN(B64_LINE_SIZE, len - i);
3165
                        base64_encode(outbuf, (guchar *)buf + i, l);
3166
                        fputs(outbuf, fp);
3167
                        fputc('\n', fp);
3168
                }
3169
        } else if (encoding == ENC_QUOTED_PRINTABLE) {
3170
                gchar *outbuf;
3171
                size_t outlen;
3172
3173
                outbuf = g_malloc(len * 4);
3174
                qp_encode_line(outbuf, (guchar *)buf);
3175
                outlen = strlen(outbuf);
3176
                if (fwrite(outbuf, sizeof(gchar), outlen, fp) != outlen) {
3177
                        FILE_OP_ERROR(file, "fwrite");
3178
                        fclose(fp);
3179
                        g_unlink(file);
3180
                        g_free(outbuf);
3181
                        g_free(buf);
3182
                        return -1;
3183
                }
3184
                g_free(outbuf);
3185
        } else if (fwrite(buf, sizeof(gchar), len, fp) != len) {
3186
                FILE_OP_ERROR(file, "fwrite");
3187
                fclose(fp);
3188
                g_unlink(file);
3189
                g_free(buf);
3190
                return -1;
3191
        }
3192
        g_free(buf);
3193
3194
        if (compose->use_attach &&
3195
            gtk_tree_model_iter_n_children(model, NULL) > 0)
3196
                compose_write_attach(compose, fp, out_charset);
3197
3198
        if (fclose(fp) == EOF) {
3199
                FILE_OP_ERROR(file, "fclose");
3200
                g_unlink(file);
3201
                return -1;
3202
        }
3203
3204
#if USE_GPGME
3205
        if (!rfc2015_is_available() || is_draft) {
3206
                uncanonicalize_file_replace(file);
3207
                return 0;
3208
        }
3209
3210
        if ((compose->use_signing && !compose->account->clearsign) ||
3211
            compose->use_encryption) {
3212
                if (canonicalize_file_replace(file) < 0) {
3213
                        g_unlink(file);
3214
                        return -1;
3215
                }
3216
        }
3217
3218
        if (compose->use_signing && !compose->account->clearsign) {
3219
                GSList *key_list;
3220
3221
                if (compose_create_signers_list(compose, &key_list) < 0 ||
3222
                    rfc2015_sign(file, key_list) < 0) {
3223
                        g_unlink(file);
3224
                        return -1;
3225
                }
3226
        }
3227
        if (compose->use_encryption) {
3228
                if (compose->use_bcc) {
3229
                        const gchar *text;
3230
                        gchar *bcc;
3231
                        AlertValue aval;
3232
3233
                        text = gtk_entry_get_text
3234
                                (GTK_ENTRY(compose->bcc_entry));
3235
                        Xstrdup_a(bcc, text, { g_unlink(file); return -1; });
3236
                        g_strstrip(bcc);
3237
                        if (*bcc != '\0') {
3238
                                aval = alertpanel_full
3239
                                        (_("Encrypting with Bcc"),
3240
                                         _("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"
3241
                                           "\n"
3242
                                           "Send it anyway?"),
3243
                                         ALERT_WARNING, G_ALERTDEFAULT, FALSE,
3244
                                         GTK_STOCK_YES, GTK_STOCK_NO, NULL);
3245
                                if (aval != G_ALERTDEFAULT) {
3246
                                        g_unlink(file);
3247
                                        return -1;
3248
                                }
3249
                        }
3250
                }
3251
                if (rfc2015_encrypt(file, compose->to_list,
3252
                                    compose->account->ascii_armored) < 0) {
3253
                        g_unlink(file);
3254
                        return -1;
3255
                }
3256
        }
3257
#endif /* USE_GPGME */
3258
3259
        uncanonicalize_file_replace(file);
3260
3261
        return 0;
3262
}
3263
3264
static gint compose_write_body_to_file(Compose *compose, const gchar *file)
3265
{
3266
        GtkTextBuffer *buffer;
3267
        GtkTextIter start, end;
3268
        FILE *fp;
3269
        size_t len;
3270
        gchar *chars, *tmp;
3271
3272
        if ((fp = g_fopen(file, "wb")) == NULL) {
3273
                FILE_OP_ERROR(file, "fopen");
3274
                return -1;
3275
        }
3276
3277
        /* chmod for security */
3278
        if (change_file_mode_rw(fp, file) < 0) {
3279
                FILE_OP_ERROR(file, "chmod");
3280
                g_warning(_("can't change file mode\n"));
3281
        }
3282
3283
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
3284
        gtk_text_buffer_get_start_iter(buffer, &start);
3285
        gtk_text_buffer_get_end_iter(buffer, &end);
3286
        tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
3287
3288
        chars = conv_codeset_strdup
3289
                (tmp, CS_INTERNAL, conv_get_locale_charset_str());
3290
3291
        g_free(tmp);
3292
3293
        if (!chars) {
3294
                fclose(fp);
3295
                g_unlink(file);
3296
                return -1;
3297
        }
3298
3299
        /* write body */
3300
        len = strlen(chars);
3301
        if (fwrite(chars, sizeof(gchar), len, fp) != len) {
3302
                FILE_OP_ERROR(file, "fwrite");
3303
                g_free(chars);
3304
                fclose(fp);
3305
                g_unlink(file);
3306
                return -1;
3307
        }
3308
3309
        g_free(chars);
3310
3311
        if (fclose(fp) == EOF) {
3312
                FILE_OP_ERROR(file, "fclose");
3313
                g_unlink(file);
3314
                return -1;
3315
        }
3316
        return 0;
3317
}
3318
3319
static gint compose_redirect_write_to_file(Compose *compose, const gchar *file)
3320
{
3321
        FILE *fp;
3322
        FILE *fdest;
3323
        size_t len;
3324
        gchar buf[BUFFSIZE];
3325
3326
        g_return_val_if_fail(file != NULL, -1);
3327
        g_return_val_if_fail(compose->account != NULL, -1);
3328
        g_return_val_if_fail(compose->account->address != NULL, -1);
3329
        g_return_val_if_fail(compose->mode == COMPOSE_REDIRECT, -1);
3330
        g_return_val_if_fail(compose->targetinfo != NULL, -1);
3331
3332
        if ((fp = procmsg_open_message(compose->targetinfo)) == NULL)
3333
                return -1;
3334
3335
        if ((fdest = g_fopen(file, "wb")) == NULL) {
3336
                FILE_OP_ERROR(file, "fopen");
3337
                fclose(fp);
3338
                return -1;
3339
        }
3340
3341
        if (change_file_mode_rw(fdest, file) < 0) {
3342
                FILE_OP_ERROR(file, "chmod");
3343
                g_warning(_("can't change file mode\n"));
3344
        }
3345
3346
        while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) == 0) {
3347
                if (g_ascii_strncasecmp(buf, "Return-Path:",
3348
                                        strlen("Return-Path:")) == 0 ||
3349
                    g_ascii_strncasecmp(buf, "Delivered-To:",
3350
                                        strlen("Delivered-To:")) == 0 ||
3351
                    g_ascii_strncasecmp(buf, "Received:",
3352
                                        strlen("Received:")) == 0 ||
3353
                    g_ascii_strncasecmp(buf, "Subject:",
3354
                                        strlen("Subject:")) == 0 ||
3355
                    g_ascii_strncasecmp(buf, "X-UIDL:",
3356
                                        strlen("X-UIDL:")) == 0)
3357
                        continue;
3358
3359
                if (fputs(buf, fdest) == EOF)
3360
                        goto error;
3361
3362
#if 0
3363
                if (g_ascii_strncasecmp(buf, "From:", strlen("From:")) == 0) {
3364
                        fputs("\n (by way of ", fdest);
3365
                        if (compose->account->name) {
3366
                                compose_convert_header(compose,
3367
                                                       buf, sizeof(buf),
3368
                                                       compose->account->name,
3369
                                                       strlen(" (by way of "),
3370
                                                       FALSE, NULL);
3371
                                fprintf(fdest, "%s <%s>", buf,
3372
                                        compose->account->address);
3373
                        } else
3374
                                fputs(compose->account->address, fdest);
3375
                        fputs(")", fdest);
3376
                }
3377
#endif
3378
3379
                if (fputs("\n", fdest) == EOF)
3380
                        goto error;
3381
        }
3382
3383
        compose_redirect_write_headers(compose, fdest);
3384
3385
        while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3386
                if (fwrite(buf, sizeof(gchar), len, fdest) != len) {
3387
                        FILE_OP_ERROR(file, "fwrite");
3388
                        goto error;
3389
                }
3390
        }
3391
3392
        fclose(fp);
3393
        if (fclose(fdest) == EOF) {
3394
                FILE_OP_ERROR(file, "fclose");
3395
                g_unlink(file);
3396
                return -1;
3397
        }
3398
3399
        return 0;
3400
error:
3401
        fclose(fp);
3402
        fclose(fdest);
3403
        g_unlink(file);
3404
3405
        return -1;
3406
}
3407
3408
static gint compose_remove_reedit_target(Compose *compose)
3409
{
3410
        FolderItem *item;
3411
        MsgInfo *msginfo = compose->targetinfo;
3412
3413
        g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
3414
        if (!msginfo) return -1;
3415
3416
        item = msginfo->folder;
3417
        g_return_val_if_fail(item != NULL, -1);
3418
3419
        folder_item_scan(item);
3420
        if (procmsg_msg_exist(msginfo) &&
3421
            (item->stype == F_DRAFT || item->stype == F_QUEUE)) {
3422
                if (folder_item_remove_msg(item, msginfo) < 0) {
3423
                        g_warning(_("can't remove the old message\n"));
3424
                        return -1;
3425
                }
3426
        }
3427
3428
        return 0;
3429
}
3430
3431
static gint compose_queue(Compose *compose, const gchar *file)
3432
{
3433
        FolderItem *queue;
3434
        gchar *tmp;
3435
        FILE *fp, *src_fp;
3436
        GSList *cur;
3437
        gchar buf[BUFFSIZE];
3438
        gint num;
3439
        MsgFlags flag = {0, 0};
3440
3441
        debug_print(_("queueing message...\n"));
3442
        g_return_val_if_fail(compose->to_list != NULL ||
3443
                             compose->newsgroup_list != NULL,
3444
                             -1);
3445
        g_return_val_if_fail(compose->account != NULL, -1);
3446
3447
        tmp = g_strdup_printf("%s%cqueue.%p", get_tmp_dir(),
3448
                              G_DIR_SEPARATOR, compose);
3449
        if ((fp = g_fopen(tmp, "wb")) == NULL) {
3450
                FILE_OP_ERROR(tmp, "fopen");
3451
                g_free(tmp);
3452
                return -1;
3453
        }
3454
        if ((src_fp = g_fopen(file, "rb")) == NULL) {
3455
                FILE_OP_ERROR(file, "fopen");
3456
                fclose(fp);
3457
                g_unlink(tmp);
3458
                g_free(tmp);
3459
                return -1;
3460
        }
3461
        if (change_file_mode_rw(fp, tmp) < 0) {
3462
                FILE_OP_ERROR(tmp, "chmod");
3463
                g_warning(_("can't change file mode\n"));
3464
        }
3465
3466
        /* queueing variables */
3467
        fprintf(fp, "AF:\n");
3468
        fprintf(fp, "NF:0\n");
3469
        fprintf(fp, "PS:10\n");
3470
        fprintf(fp, "SRH:1\n");
3471
        fprintf(fp, "SFN:\n");
3472
        fprintf(fp, "DSR:\n");
3473
        if (compose->msgid)
3474
                fprintf(fp, "MID:<%s>\n", compose->msgid);
3475
        else
3476
                fprintf(fp, "MID:\n");
3477
        fprintf(fp, "CFG:\n");
3478
        fprintf(fp, "PT:0\n");
3479
        fprintf(fp, "S:%s\n", compose->account->address);
3480
        fprintf(fp, "RQ:\n");
3481
        if (compose->account->smtp_server)
3482
                fprintf(fp, "SSV:%s\n", compose->account->smtp_server);
3483
        else
3484
                fprintf(fp, "SSV:\n");
3485
        if (compose->account->nntp_server)
3486
                fprintf(fp, "NSV:%s\n", compose->account->nntp_server);
3487
        else
3488
                fprintf(fp, "NSV:\n");
3489
        fprintf(fp, "SSH:\n");
3490
        if (compose->to_list) {
3491
                fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data);
3492
                for (cur = compose->to_list->next; cur != NULL;
3493
                     cur = cur->next)
3494
                        fprintf(fp, ",<%s>", (gchar *)cur->data);
3495
                fprintf(fp, "\n");
3496
        } else
3497
                fprintf(fp, "R:\n");
3498
        /* Sylpheed account ID */
3499
        fprintf(fp, "AID:%d\n", compose->account->account_id);
3500
        fprintf(fp, "\n");
3501
3502
        while (fgets(buf, sizeof(buf), src_fp) != NULL) {
3503
                if (fputs(buf, fp) == EOF) {
3504
                        FILE_OP_ERROR(tmp, "fputs");
3505
                        fclose(fp);
3506
                        fclose(src_fp);
3507
                        g_unlink(tmp);
3508
                        g_free(tmp);
3509
                        return -1;
3510
                }
3511
        }
3512
3513
        fclose(src_fp);
3514
        if (fclose(fp) == EOF) {
3515
                FILE_OP_ERROR(tmp, "fclose");
3516
                g_unlink(tmp);
3517
                g_free(tmp);
3518
                return -1;
3519
        }
3520
3521
        queue = account_get_special_folder(compose->account, F_QUEUE);
3522
        if (!queue) {
3523
                g_warning(_("can't find queue folder\n"));
3524
                g_unlink(tmp);
3525
                g_free(tmp);
3526
                return -1;
3527
        }
3528
        folder_item_scan(queue);
3529
        if ((num = folder_item_add_msg(queue, tmp, &flag, TRUE)) < 0) {
3530
                g_warning(_("can't queue the message\n"));
3531
                g_unlink(tmp);
3532
                g_free(tmp);
3533
                return -1;
3534
        }
3535
        g_free(tmp);
3536
3537
        if (compose->mode == COMPOSE_REEDIT) {
3538
                compose_remove_reedit_target(compose);
3539
                if (compose->targetinfo &&
3540
                    compose->targetinfo->folder != queue)
3541
                        folderview_update_item
3542
                                (compose->targetinfo->folder, TRUE);
3543
        }
3544
3545
        folder_item_scan(queue);
3546
        folderview_update_item(queue, TRUE);
3547
3548
        return 0;
3549
}
3550
3551
static void compose_write_attach(Compose *compose, FILE *fp,
3552
                                 const gchar *charset)
3553
{
3554
        GtkTreeModel *model = GTK_TREE_MODEL(compose->attach_store);
3555
        GtkTreeIter iter;
3556
        gboolean valid;
3557
        AttachInfo *ainfo;
3558
        FILE *attach_fp;
3559
        gint len;
3560
        EncodingType encoding;
3561
3562
        for (valid = gtk_tree_model_get_iter_first(model, &iter); valid;
3563
             valid = gtk_tree_model_iter_next(model, &iter)) {
3564
                gtk_tree_model_get(model, &iter, COL_ATTACH_INFO, &ainfo, -1);
3565
3566
                if ((attach_fp = g_fopen(ainfo->file, "rb")) == NULL) {
3567
                        g_warning("Can't open file %s\n", ainfo->file);
3568
                        continue;
3569
                }
3570
3571
                fprintf(fp, "\n--%s\n", compose->boundary);
3572
3573
                encoding = ainfo->encoding;
3574
3575
                if (!g_ascii_strncasecmp(ainfo->content_type, "message/", 8)) {
3576
                        fprintf(fp, "Content-Type: %s\n", ainfo->content_type);
3577
                        fprintf(fp, "Content-Disposition: inline\n");
3578
3579
                        /* message/... shouldn't be encoded */
3580
                        if (encoding == ENC_QUOTED_PRINTABLE ||
3581
                            encoding == ENC_BASE64)
3582
                                encoding = ENC_8BIT;
3583
                } else {
3584
                        if (prefs_common.mime_fencoding_method ==
3585
                            FENC_RFC2231) {
3586
                                gchar *param;
3587
3588
                                param = compose_convert_filename
3589
                                        (compose, ainfo->name, "name", charset);
3590
                                fprintf(fp, "Content-Type: %s;\n"
3591
                                            "%s\n",
3592
                                        ainfo->content_type, param);
3593
                                g_free(param);
3594
                                param = compose_convert_filename
3595
                                        (compose, ainfo->name, "filename",
3596
                                         charset);
3597
                                fprintf(fp, "Content-Disposition: attachment;\n"
3598
                                            "%s\n", param);
3599
                                g_free(param);
3600
                        } else {
3601
                                gchar filename[BUFFSIZE];
3602
3603
                                compose_convert_header(compose, filename,
3604
                                                       sizeof(filename),
3605
                                                       ainfo->name, 12, FALSE,
3606
                                                       charset);
3607
                                fprintf(fp, "Content-Type: %s;\n"
3608
                                            " name=\"%s\"\n",
3609
                                        ainfo->content_type, filename);
3610
                                fprintf(fp, "Content-Disposition: attachment;\n"
3611
                                            " filename=\"%s\"\n", filename);
3612
                        }
3613
3614
#if USE_GPGME
3615
                        /* force encoding to protect trailing spaces */
3616
                        if (rfc2015_is_available() && compose->use_signing &&
3617
                            !compose->account->clearsign) {
3618
                                if (encoding == ENC_7BIT)
3619
                                        encoding = ENC_QUOTED_PRINTABLE;
3620
                                else if (encoding == ENC_8BIT)
3621
                                        encoding = ENC_BASE64;
3622
                        }
3623
#endif
3624
                }
3625
3626
                fprintf(fp, "Content-Transfer-Encoding: %s\n\n",
3627
                        procmime_get_encoding_str(encoding));
3628
3629
                if (encoding == ENC_BASE64) {
3630
                        gchar inbuf[B64_LINE_SIZE], outbuf[B64_BUFFSIZE];
3631
                        FILE *tmp_fp = attach_fp;
3632
                        gchar *tmp_file = NULL;
3633
                        ContentType content_type;
3634
3635
                        content_type =
3636
                                procmime_scan_mime_type(ainfo->content_type);
3637
                        if (content_type == MIME_TEXT ||
3638
                            content_type == MIME_TEXT_HTML ||
3639
                            content_type == MIME_MESSAGE_RFC822) {
3640
                                tmp_file = get_tmp_file();
3641
                                if (canonicalize_file(ainfo->file, tmp_file) < 0) {
3642
                                        g_free(tmp_file);
3643
                                        fclose(attach_fp);
3644
                                        continue;
3645
                                }
3646
                                if ((tmp_fp = g_fopen(tmp_file, "rb")) == NULL) {
3647
                                        FILE_OP_ERROR(tmp_file, "fopen");
3648
                                        g_unlink(tmp_file);
3649
                                        g_free(tmp_file);
3650
                                        fclose(attach_fp);
3651
                                        continue;
3652
                                }
3653
                        }
3654
3655
                        while ((len = fread(inbuf, sizeof(gchar),
3656
                                            B64_LINE_SIZE, tmp_fp))
3657
                               == B64_LINE_SIZE) {
3658
                                base64_encode(outbuf, (guchar *)inbuf,
3659
                                              B64_LINE_SIZE);
3660
                                fputs(outbuf, fp);
3661
                                fputc('\n', fp);
3662
                        }
3663
                        if (len > 0 && feof(tmp_fp)) {
3664
                                base64_encode(outbuf, (guchar *)inbuf, len);
3665
                                fputs(outbuf, fp);
3666
                                fputc('\n', fp);
3667
                        }
3668
3669
                        if (tmp_file) {
3670
                                fclose(tmp_fp);
3671
                                g_unlink(tmp_file);
3672
                                g_free(tmp_file);
3673
                        }
3674
                } else if (encoding == ENC_QUOTED_PRINTABLE) {
3675
                        gchar inbuf[BUFFSIZE], outbuf[BUFFSIZE * 4];
3676
3677
                        while (fgets(inbuf, sizeof(inbuf), attach_fp) != NULL) {
3678
                                qp_encode_line(outbuf, (guchar *)inbuf);
3679
                                fputs(outbuf, fp);
3680
                        }
3681
                } else {
3682
                        gchar buf[BUFFSIZE];
3683
3684
                        while (fgets(buf, sizeof(buf), attach_fp) != NULL) {
3685
                                strcrchomp(buf);
3686
                                fputs(buf, fp);
3687
                        }
3688
                }
3689
3690
                fclose(attach_fp);
3691
        }
3692
3693
        fprintf(fp, "\n--%s--\n", compose->boundary);
3694
}
3695
3696
#define QUOTE_IF_REQUIRED(out, str)                        \
3697
{                                                        \
3698
        if (*str != '"' && strpbrk(str, ",.[]<>")) {        \
3699
                gchar *__tmp;                                \
3700
                gint len;                                \
3701
                                                        \
3702
                len = strlen(str) + 3;                        \
3703
                Xalloca(__tmp, len, return -1);                \
3704
                g_snprintf(__tmp, len, "\"%s\"", str);        \
3705
                out = __tmp;                                \
3706
        } else {                                        \
3707
                Xstrdup_a(out, str, return -1);                \
3708
        }                                                \
3709
}
3710
3711
#define PUT_RECIPIENT_HEADER(header, str)                                     \
3712
{                                                                             \
3713
        if (*str != '\0') {                                                     \
3714
                gchar *dest;                                                     \
3715
                                                                             \
3716
                Xstrdup_a(dest, str, return -1);                             \
3717
                g_strstrip(dest);                                             \
3718
                if (*dest != '\0') {                                             \
3719
                        compose->to_list = address_list_append                     \
3720
                                (compose->to_list, dest);                     \
3721
                        compose_convert_header                                     \
3722
                                (compose, buf, sizeof(buf), dest,             \
3723
                                 strlen(header) + 2, TRUE, charset);             \
3724
                        fprintf(fp, "%s: %s\n", header, buf);                     \
3725
                }                                                             \
3726
        }                                                                     \
3727
}
3728
3729
#define IS_IN_CUSTOM_HEADER(header) \
3730
        (compose->account->add_customhdr && \
3731
         custom_header_find(compose->account->customhdr_list, header) != NULL)
3732
3733
static gint compose_write_headers(Compose *compose, FILE *fp,
3734
                                  const gchar *charset,
3735
                                  const gchar *body_charset,
3736
                                  EncodingType encoding, gboolean is_draft)
3737
{
3738
        gchar buf[BUFFSIZE];
3739
        const gchar *entry_str;
3740
        gchar *str;
3741
        gchar *name;
3742
3743
        g_return_val_if_fail(fp != NULL, -1);
3744
        g_return_val_if_fail(charset != NULL, -1);
3745
        g_return_val_if_fail(compose->account != NULL, -1);
3746
        g_return_val_if_fail(compose->account->address != NULL, -1);
3747
3748
        /* Date */
3749
        if (compose->account->add_date) {
3750
                get_rfc822_date(buf, sizeof(buf));
3751
                fprintf(fp, "Date: %s\n", buf);
3752
        }
3753
3754
        /* From */
3755
        if (compose->account->name && *compose->account->name) {
3756
                compose_convert_header
3757
                        (compose, buf, sizeof(buf), compose->account->name,
3758
                         strlen("From: "), TRUE, charset);
3759
                QUOTE_IF_REQUIRED(name, buf);
3760
                fprintf(fp, "From: %s <%s>\n",
3761
                        name, compose->account->address);
3762
        } else
3763
                fprintf(fp, "From: %s\n", compose->account->address);
3764
3765
        slist_free_strings(compose->to_list);
3766
        g_slist_free(compose->to_list);
3767
        compose->to_list = NULL;
3768
3769
        /* To */
3770
        if (compose->use_to) {
3771
                entry_str = gtk_entry_get_text(GTK_ENTRY(compose->to_entry));
3772
                PUT_RECIPIENT_HEADER("To", entry_str);
3773
        }
3774
3775
        slist_free_strings(compose->newsgroup_list);
3776
        g_slist_free(compose->newsgroup_list);
3777
        compose->newsgroup_list = NULL;
3778
3779
        /* Newsgroups */
3780
        if (compose->use_newsgroups) {
3781
                entry_str = gtk_entry_get_text
3782
                        (GTK_ENTRY(compose->newsgroups_entry));
3783
                if (*entry_str != '\0') {
3784
                        Xstrdup_a(str, entry_str, return -1);
3785
                        g_strstrip(str);
3786
                        remove_space(str);
3787
                        if (*str != '\0') {
3788
                                compose->newsgroup_list =
3789
                                        newsgroup_list_append
3790
                                                (compose->newsgroup_list, str);
3791
                                compose_convert_header(compose,
3792
                                                       buf, sizeof(buf), str,
3793
                                                       strlen("Newsgroups: "),
3794
                                                       FALSE, charset);
3795
                                fprintf(fp, "Newsgroups: %s\n", buf);
3796
                        }
3797
                }
3798
        }
3799
3800
        /* Cc */
3801
        if (compose->use_cc) {
3802
                entry_str = gtk_entry_get_text(GTK_ENTRY(compose->cc_entry));
3803
                PUT_RECIPIENT_HEADER("Cc", entry_str);
3804
        }
3805
3806
        /* Bcc */
3807
        if (compose->use_bcc) {
3808
                entry_str = gtk_entry_get_text(GTK_ENTRY(compose->bcc_entry));
3809
                PUT_RECIPIENT_HEADER("Bcc", entry_str);
3810
        }
3811
3812
        if (!is_draft && !compose->to_list && !compose->newsgroup_list)
3813
                return -1;
3814
3815
        /* Subject */
3816
        entry_str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
3817
        if (*entry_str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
3818
                Xstrdup_a(str, entry_str, return -1);
3819
                g_strstrip(str);
3820
                if (*str != '\0') {
3821
                        compose_convert_header(compose, buf, sizeof(buf), str,
3822
                                               strlen("Subject: "), FALSE,
3823
                                               charset);
3824
                        fprintf(fp, "Subject: %s\n", buf);
3825
                }
3826
        }
3827
3828
        /* Message-ID */
3829
        if (compose->account->gen_msgid) {
3830
                compose_generate_msgid(compose, buf, sizeof(buf));
3831
                fprintf(fp, "Message-Id: <%s>\n", buf);
3832
                compose->msgid = g_strdup(buf);
3833
        }
3834
3835
        /* In-Reply-To */
3836
        if (compose->inreplyto && compose->to_list)
3837
                fprintf(fp, "In-Reply-To: <%s>\n", compose->inreplyto);
3838
3839
        /* References */
3840
        if (compose->references)
3841
                fprintf(fp, "References: %s\n", compose->references);
3842
3843
        /* Followup-To */
3844
        if (compose->use_followupto && !IS_IN_CUSTOM_HEADER("Followup-To")) {
3845
                entry_str = gtk_entry_get_text
3846
                        (GTK_ENTRY(compose->followup_entry));
3847
                if (*entry_str != '\0') {
3848
                        Xstrdup_a(str, entry_str, return -1);
3849
                        g_strstrip(str);
3850
                        remove_space(str);
3851
                        if (*str != '\0') {
3852
                                compose_convert_header(compose,
3853
                                                       buf, sizeof(buf), str,
3854
                                                       strlen("Followup-To: "),
3855
                                                       FALSE, charset);
3856
                                fprintf(fp, "Followup-To: %s\n", buf);
3857
                        }
3858
                }
3859
        }
3860
3861
        /* Reply-To */
3862
        if (compose->use_replyto && !IS_IN_CUSTOM_HEADER("Reply-To")) {
3863
                entry_str = gtk_entry_get_text(GTK_ENTRY(compose->reply_entry));
3864
                if (*entry_str != '\0') {
3865
                        Xstrdup_a(str, entry_str, return -1);
3866
                        g_strstrip(str);
3867
                        if (*str != '\0') {
3868
                                compose_convert_header(compose,
3869
                                                       buf, sizeof(buf), str,
3870
                                                       strlen("Reply-To: "),
3871
                                                       TRUE, charset);
3872
                                fprintf(fp, "Reply-To: %s\n", buf);
3873
                        }
3874
                }
3875
        }
3876
3877
        /* Organization */
3878
        if (compose->account->organization &&
3879
            !IS_IN_CUSTOM_HEADER("Organization")) {
3880
                compose_convert_header(compose, buf, sizeof(buf),
3881
                                       compose->account->organization,
3882
                                       strlen("Organization: "), FALSE,
3883
                                       charset);
3884
                fprintf(fp, "Organization: %s\n", buf);
3885
        }
3886
3887
        /* Program version and system info */
3888
        if (compose->to_list && !IS_IN_CUSTOM_HEADER("X-Mailer")) {
3889
                fprintf(fp, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
3890
                        prog_version,
3891
                        gtk_major_version, gtk_minor_version, gtk_micro_version,
3892
                        TARGET_ALIAS);
3893
        }
3894
        if (compose->newsgroup_list && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
3895
                fprintf(fp, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
3896
                        prog_version,
3897
                        gtk_major_version, gtk_minor_version, gtk_micro_version,
3898
                        TARGET_ALIAS);
3899
        }
3900
3901
        /* custom headers */
3902
        if (compose->account->add_customhdr) {
3903
                GSList *cur;
3904
3905
                for (cur = compose->account->customhdr_list; cur != NULL;
3906
                     cur = cur->next) {
3907
                        CustomHeader *chdr = (CustomHeader *)cur->data;
3908
3909
                        if (g_ascii_strcasecmp(chdr->name, "Date") != 0 &&
3910
                            g_ascii_strcasecmp(chdr->name, "From") != 0 &&
3911
                            g_ascii_strcasecmp(chdr->name, "To") != 0 &&
3912
                         /* g_ascii_strcasecmp(chdr->name, "Sender") != 0 && */
3913
                            g_ascii_strcasecmp(chdr->name, "Message-Id") != 0 &&
3914
                            g_ascii_strcasecmp(chdr->name, "In-Reply-To") != 0 &&
3915
                            g_ascii_strcasecmp(chdr->name, "References") != 0 &&
3916
                            g_ascii_strcasecmp(chdr->name, "Mime-Version") != 0 &&
3917
                            g_ascii_strcasecmp(chdr->name, "Content-Type") != 0 &&
3918
                            g_ascii_strcasecmp(chdr->name, "Content-Transfer-Encoding") != 0) {
3919
                                compose_convert_header
3920
                                        (compose, buf, sizeof(buf),
3921
                                         chdr->value ? chdr->value : "",
3922
                                         strlen(chdr->name) + 2, FALSE,
3923
                                         charset);
3924
                                fprintf(fp, "%s: %s\n", chdr->name, buf);
3925
                        }
3926
                }
3927
        }
3928
3929
        /* MIME */
3930
        fprintf(fp, "Mime-Version: 1.0\n");
3931
        if (compose->use_attach &&
3932
            gtk_tree_model_iter_n_children
3933
                (GTK_TREE_MODEL(compose->attach_store), NULL) > 0) {
3934
                compose->boundary = generate_mime_boundary(NULL);
3935
                fprintf(fp,
3936
                        "Content-Type: multipart/mixed;\n"
3937
                        " boundary=\"%s\"\n", compose->boundary);
3938
        } else {
3939
                fprintf(fp, "Content-Type: text/plain; charset=%s\n",
3940
                        body_charset);
3941
#if USE_GPGME
3942
                if (rfc2015_is_available() &&
3943
                    compose->use_signing && !compose->account->clearsign)
3944
                        fprintf(fp, "Content-Disposition: inline\n");
3945
#endif
3946
                fprintf(fp, "Content-Transfer-Encoding: %s\n",
3947
                        procmime_get_encoding_str(encoding));
3948
        }
3949
3950
        /* X-Sylpheed header */
3951
        if (is_draft)
3952
                fprintf(fp, "X-Sylpheed-Account-Id: %d\n",
3953
                        compose->account->account_id);
3954
3955
        /* separator between header and body */
3956
        fputs("\n", fp);
3957
3958
        return 0;
3959
}
3960
3961
static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
3962
{
3963
        gchar buf[BUFFSIZE];
3964
        const gchar *entry_str;
3965
        gchar *str;
3966
        const gchar *charset = NULL;
3967
3968
        g_return_val_if_fail(fp != NULL, -1);
3969
        g_return_val_if_fail(compose->account != NULL, -1);
3970
        g_return_val_if_fail(compose->account->address != NULL, -1);
3971
3972
        /* Resent-Date */
3973
        get_rfc822_date(buf, sizeof(buf));
3974
        fprintf(fp, "Resent-Date: %s\n", buf);
3975
3976
        /* Resent-From */
3977
        if (compose->account->name) {
3978
                compose_convert_header
3979
                        (compose, buf, sizeof(buf), compose->account->name,
3980
                         strlen("Resent-From: "), TRUE, NULL);
3981
                fprintf(fp, "Resent-From: %s <%s>\n",
3982
                        buf, compose->account->address);
3983
        } else
3984
                fprintf(fp, "Resent-From: %s\n", compose->account->address);
3985
3986
        slist_free_strings(compose->to_list);
3987
        g_slist_free(compose->to_list);
3988
        compose->to_list = NULL;
3989
3990
        /* Resent-To */
3991
        if (compose->use_to) {
3992
                entry_str = gtk_entry_get_text(GTK_ENTRY(compose->to_entry));
3993
                PUT_RECIPIENT_HEADER("Resent-To", entry_str);
3994
        }
3995
        if (compose->use_cc) {
3996
                entry_str = gtk_entry_get_text(GTK_ENTRY(compose->cc_entry));
3997
                PUT_RECIPIENT_HEADER("Resent-Cc", entry_str);
3998
        }
3999
        if (compose->use_bcc) {
4000
                entry_str = gtk_entry_get_text(GTK_ENTRY(compose->bcc_entry));
4001
                PUT_RECIPIENT_HEADER("Bcc", entry_str);
4002
        }
4003
4004
        slist_free_strings(compose->newsgroup_list);
4005
        g_slist_free(compose->newsgroup_list);
4006
        compose->newsgroup_list = NULL;
4007
4008
        /* Newsgroups */
4009
        if (compose->use_newsgroups) {
4010
                entry_str = gtk_entry_get_text
4011
                        (GTK_ENTRY(compose->newsgroups_entry));
4012
                if (*entry_str != '\0') {
4013
                        Xstrdup_a(str, entry_str, return -1);
4014
                        g_strstrip(str);
4015
                        remove_space(str);
4016
                        if (*str != '\0') {
4017
                                compose->newsgroup_list =
4018
                                        newsgroup_list_append
4019
                                                (compose->newsgroup_list, str);
4020
                                compose_convert_header(compose,
4021
                                                       buf, sizeof(buf), str,
4022
                                                       strlen("Newsgroups: "),
4023
                                                       FALSE, NULL);
4024
                                fprintf(fp, "Newsgroups: %s\n", buf);
4025
                        }
4026
                }
4027
        }
4028
4029
        if (!compose->to_list && !compose->newsgroup_list)
4030
                return -1;
4031
4032
        /* Subject */
4033
        entry_str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4034
        if (*entry_str != '\0') {
4035
                Xstrdup_a(str, entry_str, return -1);
4036
                g_strstrip(str);
4037
                if (*str != '\0') {
4038
                        compose_convert_header(compose, buf, sizeof(buf), str,
4039
                                               strlen("Subject: "), FALSE,
4040
                                               NULL);
4041
                        fprintf(fp, "Subject: %s\n", buf);
4042
                }
4043
        }
4044
4045
        /* Resent-Message-Id */
4046
        if (compose->account->gen_msgid) {
4047
                compose_generate_msgid(compose, buf, sizeof(buf));
4048
                fprintf(fp, "Resent-Message-Id: <%s>\n", buf);
4049
                compose->msgid = g_strdup(buf);
4050
        }
4051
4052
        /* Followup-To */
4053
        if (compose->use_followupto) {
4054
                entry_str = gtk_entry_get_text
4055
                        (GTK_ENTRY(compose->followup_entry));
4056
                if (*entry_str != '\0') {
4057
                        Xstrdup_a(str, entry_str, return -1);
4058
                        g_strstrip(str);
4059
                        remove_space(str);
4060
                        if (*str != '\0') {
4061
                                compose_convert_header(compose,
4062
                                                       buf, sizeof(buf), str,
4063
                                                       strlen("Followup-To: "),
4064
                                                       FALSE, NULL);
4065
                                fprintf(fp, "Followup-To: %s\n", buf);
4066
                        }
4067
                }
4068
        }
4069
4070
        /* Resent-Reply-To */
4071
        if (compose->use_replyto) {
4072
                entry_str = gtk_entry_get_text(GTK_ENTRY(compose->reply_entry));
4073
                if (*entry_str != '\0') {
4074
                        Xstrdup_a(str, entry_str, return -1);
4075
                        g_strstrip(str);
4076
                        if (*str != '\0') {
4077
                                compose_convert_header
4078
                                        (compose, buf, sizeof(buf), str,
4079
                                         strlen("Resent-Reply-To: "), TRUE,
4080
                                         NULL);
4081
                                fprintf(fp, "Resent-Reply-To: %s\n", buf);
4082
                        }
4083
                }
4084
        }
4085
4086
        fputs("\n", fp);
4087
4088
        return 0;
4089
}
4090
4091
#undef IS_IN_CUSTOM_HEADER
4092
4093
static void compose_convert_header(Compose *compose, gchar *dest, gint len,
4094
                                   const gchar *src, gint header_len,
4095
                                   gboolean addr_field, const gchar *encoding)
4096
{
4097
        gchar *src_;
4098
4099
        g_return_if_fail(src != NULL);
4100
        g_return_if_fail(dest != NULL);
4101
4102
        if (len < 1) return;
4103
4104
        if (addr_field)
4105
                src_ = normalize_address_field(src);
4106
        else
4107
                src_ = g_strdup(src);
4108
        g_strchomp(src_);
4109
        if (!encoding)
4110
                encoding = conv_get_charset_str(compose->out_encoding);
4111
4112
        conv_encode_header(dest, len, src_, header_len, addr_field, encoding);
4113
4114
        g_free(src_);
4115
}
4116
4117
static gchar *compose_convert_filename(Compose *compose, const gchar *src,
4118
                                       const gchar *param_name,
4119
                                       const gchar *encoding)
4120
{
4121
        gchar *str;
4122
4123
        g_return_val_if_fail(src != NULL, NULL);
4124
4125
        if (!encoding)
4126
                encoding = conv_get_charset_str(compose->out_encoding);
4127
4128
        str = conv_encode_filename(src, param_name, encoding);
4129
4130
        return str;
4131
}
4132
4133
static void compose_generate_msgid(Compose *compose, gchar *buf, gint len)
4134
{
4135
        struct tm *lt;
4136
        time_t t;
4137
        gchar *addr;
4138
4139
        t = time(NULL);
4140
        lt = localtime(&t);
4141
4142
        if (compose->account && compose->account->address &&
4143
            *compose->account->address) {
4144
                if (strchr(compose->account->address, '@'))
4145
                        addr = g_strdup(compose->account->address);
4146
                else
4147
                        addr = g_strconcat(compose->account->address, "@",
4148
                                           get_domain_name(), NULL);
4149
        } else
4150
                addr = g_strconcat(g_get_user_name(), "@", get_domain_name(),
4151
                                   NULL);
4152
4153
        g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x.%s",
4154
                   lt->tm_year + 1900, lt->tm_mon + 1,
4155
                   lt->tm_mday, lt->tm_hour,
4156
                   lt->tm_min, lt->tm_sec,
4157
                   g_random_int(), addr);
4158
4159
        debug_print(_("generated Message-ID: %s\n"), buf);
4160
4161
        g_free(addr);
4162
}
4163
4164
static void compose_add_entry_field(GtkWidget *table, GtkWidget **hbox,
4165
                                    GtkWidget **entry, gint *count,
4166
                                    const gchar *label_str,
4167
                                    gboolean is_addr_entry)
4168
{
4169
        GtkWidget *label;
4170
4171
        if (GTK_TABLE(table)->nrows < (*count) + 1)
4172
                gtk_table_resize(GTK_TABLE(table), (*count) + 1, 2);
4173
4174
        *hbox = gtk_hbox_new(FALSE, 0);
4175
        label = gtk_label_new
4176
                (prefs_common.trans_hdr ? gettext(label_str) : label_str);
4177
        gtk_box_pack_end(GTK_BOX(*hbox), label, FALSE, FALSE, 0);
4178
        gtk_table_attach(GTK_TABLE(table), *hbox, 0, 1, *count, (*count) + 1,
4179
                         GTK_FILL, 0, 2, 0);
4180
        *entry = gtk_entry_new();
4181
        gtk_entry_set_max_length(GTK_ENTRY(*entry), MAX_ENTRY_LENGTH);
4182
        gtk_table_attach_defaults
4183
                (GTK_TABLE(table), *entry, 1, 2, *count, (*count) + 1);
4184
        if (GTK_TABLE(table)->nrows > (*count) + 1)
4185
                gtk_table_set_row_spacing(GTK_TABLE(table), *count, 4);
4186
4187
        if (is_addr_entry)
4188
                address_completion_register_entry(GTK_ENTRY(*entry));
4189
4190
        (*count)++;
4191
}
4192
4193
static Compose *compose_create(PrefsAccount *account, ComposeMode mode)
4194
{
4195
        Compose   *compose;
4196
        GtkWidget *window;
4197
        GtkWidget *vbox;
4198
        GtkWidget *menubar;
4199
        GtkWidget *toolbar;
4200
4201
        GtkWidget *vbox2;
4202
4203
        GtkWidget *table_vbox;
4204
        GtkWidget *table;
4205
        GtkWidget *hbox;
4206
        GtkWidget *label;
4207
        GtkWidget *from_optmenu_hbox;
4208
        GtkWidget *to_entry;
4209
        GtkWidget *to_hbox;
4210
        GtkWidget *newsgroups_entry;
4211
        GtkWidget *newsgroups_hbox;
4212
        GtkWidget *subject_entry;
4213
        GtkWidget *cc_entry;
4214
        GtkWidget *cc_hbox;
4215
        GtkWidget *bcc_entry;
4216
        GtkWidget *bcc_hbox;
4217
        GtkWidget *reply_entry;
4218
        GtkWidget *reply_hbox;
4219
        GtkWidget *followup_entry;
4220
        GtkWidget *followup_hbox;
4221
4222
#if USE_GPGME
4223
        GtkWidget *misc_hbox;
4224
        GtkWidget *signing_chkbtn;
4225
        GtkWidget *encrypt_chkbtn;
4226
#endif /* USE_GPGME */
4227
#if 0
4228
        GtkWidget *attach_img;
4229
        GtkWidget *attach_toggle;
4230
#endif
4231
4232
        GtkWidget *paned;
4233
4234
        GtkWidget *attach_scrwin;
4235
        GtkWidget *attach_treeview;
4236
        GtkListStore *store;
4237
        GtkTreeSelection *selection;
4238
        GtkTreeViewColumn *column;
4239
        GtkCellRenderer *renderer;
4240
4241
        GtkWidget *edit_vbox;
4242
        GtkWidget *ruler_hbox;
4243
        GtkWidget *ruler;
4244
        GtkWidget *scrolledwin;
4245
        GtkWidget *text;
4246
4247
        GtkTextBuffer *buffer;
4248
        GtkClipboard *clipboard;
4249
        GtkTextTag *sig_tag;
4250
4251
#if USE_GTKSPELL
4252
        GtkWidget *spell_menu;
4253
#endif /* USE_GTKSPELL */
4254
4255
        UndoMain *undostruct;
4256
4257
        guint n_menu_entries;
4258
        GdkColormap *cmap;
4259
        GdkColor color[1];
4260
        gboolean success[1];
4261
        GtkWidget *popupmenu;
4262
        GtkItemFactory *popupfactory;
4263
        GtkItemFactory *ifactory;
4264
        GtkWidget *tmpl_menu;
4265
        gint n_entries;
4266
        gint count = 0;
4267
4268
#ifndef G_OS_WIN32
4269
        static GdkGeometry geometry;
4270
#endif
4271
4272
        g_return_val_if_fail(account != NULL, NULL);
4273
4274
        debug_print(_("Creating compose window...\n"));
4275
        compose = g_new0(Compose, 1);
4276
4277
        compose->account = account;
4278
4279
        window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
4280
        gtk_window_set_wmclass(GTK_WINDOW(window), "compose", "Sylpheed");
4281
        gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE);
4282
        gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
4283
        if (!compose_window_exist(prefs_common.compose_x,
4284
                                  prefs_common.compose_y))
4285
                gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
4286
                                prefs_common.compose_y);
4287
4288
#ifndef G_OS_WIN32
4289
        if (!geometry.max_width) {
4290
                geometry.max_width = gdk_screen_width();
4291
                geometry.max_height = gdk_screen_height();
4292
        }
4293
        gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
4294
                                      &geometry, GDK_HINT_MAX_SIZE);
4295
#endif
4296
4297
        g_signal_connect(G_OBJECT(window), "delete_event",
4298
                         G_CALLBACK(compose_delete_cb), compose);
4299
        MANAGE_WINDOW_SIGNALS_CONNECT(window);
4300
4301
        vbox = gtk_vbox_new(FALSE, 0);
4302
        gtk_container_add(GTK_CONTAINER(window), vbox);
4303
4304
        n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
4305
        menubar = menubar_create(window, compose_entries,
4306
                                 n_menu_entries, "<Compose>", compose);
4307
        gtk_widget_set_size_request(menubar, 300, -1);
4308
        gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
4309
4310
        toolbar = compose_toolbar_create(compose);
4311
        gtk_widget_set_size_request(toolbar, 300, -1);
4312
        gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
4313
4314
        vbox2 = gtk_vbox_new(FALSE, 2);
4315
        gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
4316
        gtk_container_set_border_width(GTK_CONTAINER(vbox2), BORDER_WIDTH);
4317
4318
        table_vbox = gtk_vbox_new(FALSE, 0);
4319
        gtk_box_pack_start(GTK_BOX(vbox2), table_vbox, FALSE, TRUE, 0);
4320
        gtk_container_set_border_width(GTK_CONTAINER(table_vbox), BORDER_WIDTH);
4321
4322
        table = gtk_table_new(8, 2, FALSE);
4323
        gtk_box_pack_start(GTK_BOX(table_vbox), table, FALSE, TRUE, 0);
4324
4325
        /* option menu for selecting accounts */
4326
        hbox = gtk_hbox_new(FALSE, 0);
4327
        label = gtk_label_new(prefs_common.trans_hdr ? _("From:") : "From:");
4328
        gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0);
4329
        gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, count, count + 1,
4330
                         GTK_FILL, 0, 2, 0);
4331
4332
        from_optmenu_hbox = gtk_hbox_new(FALSE, 0);
4333
        gtk_table_attach_defaults(GTK_TABLE(table), from_optmenu_hbox,
4334
                                  1, 2, count, count + 1);
4335
        gtk_table_set_row_spacing(GTK_TABLE(table), 0, 4);
4336
        count++;
4337
        compose_account_option_menu_create(compose, from_optmenu_hbox);
4338
4339
        /* header labels and entries */
4340
        compose_add_entry_field(table, &to_hbox, &to_entry, &count,
4341
                                "To:", TRUE); 
4342
        compose_add_entry_field(table, &newsgroups_hbox, &newsgroups_entry,
4343
                                &count, "Newsgroups:", FALSE);
4344
        compose_add_entry_field(table, &cc_hbox, &cc_entry, &count,
4345
                                "Cc:", TRUE);
4346
        compose_add_entry_field(table, &bcc_hbox, &bcc_entry, &count,
4347
                                "Bcc:", TRUE);
4348
        compose_add_entry_field(table, &reply_hbox, &reply_entry, &count,
4349
                                "Reply-To:", TRUE);
4350
        compose_add_entry_field(table, &followup_hbox, &followup_entry, &count,
4351
                                "Followup-To:", FALSE);
4352
        compose_add_entry_field(table, &hbox, &subject_entry, &count,
4353
                                "Subject:", FALSE);
4354
4355
        gtk_table_set_col_spacings(GTK_TABLE(table), 4);
4356
4357
        g_signal_connect(G_OBJECT(to_entry), "activate",
4358
                         G_CALLBACK(to_activated), compose);
4359
        g_signal_connect(G_OBJECT(newsgroups_entry), "activate",
4360
                         G_CALLBACK(newsgroups_activated), compose);
4361
        g_signal_connect(G_OBJECT(cc_entry), "activate",
4362
                         G_CALLBACK(cc_activated), compose);
4363
        g_signal_connect(G_OBJECT(bcc_entry), "activate",
4364
                         G_CALLBACK(bcc_activated), compose);
4365
        g_signal_connect(G_OBJECT(reply_entry), "activate",
4366
                         G_CALLBACK(replyto_activated), compose);
4367
        g_signal_connect(G_OBJECT(followup_entry), "activate",
4368
                         G_CALLBACK(followupto_activated), compose);
4369
        g_signal_connect(G_OBJECT(subject_entry), "activate",
4370
                         G_CALLBACK(subject_activated), compose);
4371
4372
        g_signal_connect(G_OBJECT(to_entry), "grab_focus",
4373
                         G_CALLBACK(compose_grab_focus_cb), compose);
4374
        g_signal_connect(G_OBJECT(newsgroups_entry), "grab_focus",
4375
                         G_CALLBACK(compose_grab_focus_cb), compose);
4376
        g_signal_connect(G_OBJECT(cc_entry), "grab_focus",
4377
                         G_CALLBACK(compose_grab_focus_cb), compose);
4378
        g_signal_connect(G_OBJECT(bcc_entry), "grab_focus",
4379
                         G_CALLBACK(compose_grab_focus_cb), compose);
4380
        g_signal_connect(G_OBJECT(reply_entry), "grab_focus",
4381
                         G_CALLBACK(compose_grab_focus_cb), compose);
4382
        g_signal_connect(G_OBJECT(followup_entry), "grab_focus",
4383
                         G_CALLBACK(compose_grab_focus_cb), compose);
4384
        g_signal_connect(G_OBJECT(subject_entry), "grab_focus",
4385
                         G_CALLBACK(compose_grab_focus_cb), compose);
4386
4387
#if 0
4388
        attach_img = stock_pixbuf_widget(window, STOCK_PIXMAP_CLIP);
4389
        attach_toggle = gtk_toggle_button_new();
4390
        GTK_WIDGET_UNSET_FLAGS(attach_toggle, GTK_CAN_FOCUS);
4391
        gtk_container_add(GTK_CONTAINER(attach_toggle), attach_img);
4392
        gtk_box_pack_start(GTK_BOX(misc_hbox), attach_toggle, FALSE, FALSE, 8);
4393
        g_signal_connect(G_OBJECT(attach_toggle), "toggled",
4394
                         G_CALLBACK(compose_attach_toggled), compose);
4395
#endif
4396
4397
#if USE_GPGME
4398
        misc_hbox = gtk_hbox_new(FALSE, 0);
4399
        gtk_box_pack_start(GTK_BOX(vbox2), misc_hbox, FALSE, FALSE, 0);
4400
4401
        signing_chkbtn = gtk_check_button_new_with_label(_("PGP Sign"));
4402
        GTK_WIDGET_UNSET_FLAGS(signing_chkbtn, GTK_CAN_FOCUS);
4403
        gtk_box_pack_start(GTK_BOX(misc_hbox), signing_chkbtn, FALSE, FALSE, 8);
4404
        encrypt_chkbtn = gtk_check_button_new_with_label(_("PGP Encrypt"));
4405
        GTK_WIDGET_UNSET_FLAGS(encrypt_chkbtn, GTK_CAN_FOCUS);
4406
        gtk_box_pack_start(GTK_BOX(misc_hbox), encrypt_chkbtn, FALSE, FALSE, 8);
4407
4408
        g_signal_connect(G_OBJECT(signing_chkbtn), "toggled",
4409
                         G_CALLBACK(compose_signing_toggled), compose);
4410
        g_signal_connect(G_OBJECT(encrypt_chkbtn), "toggled",
4411
                         G_CALLBACK(compose_encrypt_toggled), compose);
4412
#endif /* USE_GPGME */
4413
4414
        /* attachment list */
4415
        attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
4416
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
4417
                                       GTK_POLICY_AUTOMATIC,
4418
                                       GTK_POLICY_ALWAYS);
4419
        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(attach_scrwin),
4420
                                            GTK_SHADOW_IN);
4421
        gtk_widget_set_size_request(attach_scrwin, -1, 80);
4422
4423
        store = gtk_list_store_new(N_ATTACH_COLS, G_TYPE_STRING, G_TYPE_STRING,
4424
                                   G_TYPE_STRING, G_TYPE_POINTER);
4425
4426
        attach_treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
4427
        g_object_unref(G_OBJECT(store));
4428
        gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(attach_treeview), TRUE);
4429
        gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_treeview), TRUE);
4430
        gtk_tree_view_set_search_column(GTK_TREE_VIEW(attach_treeview),
4431
                                        COL_NAME);
4432
        gtk_tree_view_set_reorderable(GTK_TREE_VIEW(attach_treeview), FALSE);
4433
4434
        selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_treeview));
4435
        gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
4436
4437
        gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_treeview);
4438
4439
        renderer = gtk_cell_renderer_text_new();
4440
        g_object_set(renderer, "ypad", 0, NULL);
4441
        column = gtk_tree_view_column_new_with_attributes
4442
                (_("MIME type"), renderer, "text", COL_MIMETYPE, NULL);
4443
        gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
4444
        gtk_tree_view_column_set_fixed_width(column, 240);
4445
        gtk_tree_view_column_set_resizable(column, TRUE);
4446
        gtk_tree_view_append_column(GTK_TREE_VIEW(attach_treeview), column);
4447
4448
        renderer = gtk_cell_renderer_text_new();
4449
        g_object_set(renderer, "xalign", 1.0, "ypad", 0, NULL);
4450
        column = gtk_tree_view_column_new_with_attributes
4451
                (_("Size"), renderer, "text", COL_SIZE, NULL);
4452
        gtk_tree_view_column_set_alignment(column, 1.0);
4453
        gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
4454
        gtk_tree_view_column_set_fixed_width(column, 64);
4455
        gtk_tree_view_column_set_resizable(column, TRUE);
4456
        gtk_tree_view_append_column(GTK_TREE_VIEW(attach_treeview), column);
4457
4458
        renderer = gtk_cell_renderer_text_new();
4459
        g_object_set(renderer, "ypad", 0, NULL);
4460
        column = gtk_tree_view_column_new_with_attributes
4461
                (_("Name"), renderer, "text", COL_NAME, NULL);
4462
        gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
4463
        gtk_tree_view_column_set_resizable(column, TRUE);
4464
        gtk_tree_view_append_column(GTK_TREE_VIEW(attach_treeview), column);
4465
4466
        g_signal_connect(G_OBJECT(selection), "changed",
4467
                         G_CALLBACK(attach_selection_changed), compose);
4468
        g_signal_connect(G_OBJECT(attach_treeview), "button_press_event",
4469
                         G_CALLBACK(attach_button_pressed), compose);
4470
        g_signal_connect(G_OBJECT(attach_treeview), "key_press_event",
4471
                         G_CALLBACK(attach_key_pressed), compose);
4472
4473
        /* drag and drop */
4474
        gtk_drag_dest_set(window,
4475
                          GTK_DEST_DEFAULT_ALL, compose_drag_types,
4476
                          N_DRAG_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE);
4477
        g_signal_connect(G_OBJECT(window), "drag-data-received",
4478
                         G_CALLBACK(compose_attach_drag_received_cb),
4479
                         compose);
4480
4481
        /* pane between attach tree view and text */
4482
        paned = gtk_vpaned_new();
4483
        gtk_paned_add1(GTK_PANED(paned), attach_scrwin);
4484
        gtk_widget_ref(paned);
4485
        gtk_widget_show_all(paned);
4486
4487
        edit_vbox = gtk_vbox_new(FALSE, 0);
4488
        gtk_box_pack_start(GTK_BOX(vbox2), edit_vbox, TRUE, TRUE, 0);
4489
4490
        /* ruler */
4491
        ruler_hbox = gtk_hbox_new(FALSE, 0);
4492
        gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
4493
4494
        ruler = gtk_shruler_new();
4495
        gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
4496
        gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
4497
                           BORDER_WIDTH);
4498
4499
        /* text widget */
4500
        scrolledwin = gtk_scrolled_window_new(NULL, NULL);
4501
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
4502
                                       GTK_POLICY_AUTOMATIC,
4503
                                       GTK_POLICY_ALWAYS);
4504
        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
4505
                                            GTK_SHADOW_IN);
4506
        gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
4507
        gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width,
4508
                                    -1);
4509
4510
        text = gtk_text_view_new();
4511
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
4512
        gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
4513
        gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
4514
        clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
4515
        gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
4516
        sig_tag = gtk_text_buffer_create_tag(buffer, "signature", NULL);
4517
        gtk_container_add(GTK_CONTAINER(scrolledwin), text);
4518
4519
        g_signal_connect(G_OBJECT(text), "grab_focus",
4520
                         G_CALLBACK(compose_grab_focus_cb), compose);
4521
        g_signal_connect(G_OBJECT(buffer), "insert_text",
4522
                         G_CALLBACK(text_inserted), compose);
4523
        g_signal_connect_after(G_OBJECT(text), "size_allocate",
4524
                               G_CALLBACK(compose_edit_size_alloc),
4525
                               ruler);
4526
4527
        /* drag and drop */
4528
        gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_drag_types,
4529
                          N_DRAG_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE);
4530
        g_signal_connect(G_OBJECT(text), "drag-data-received",
4531
                         G_CALLBACK(compose_insert_drag_received_cb),
4532
                         compose);
4533
4534
        gtk_widget_show_all(vbox);
4535
4536
        if (prefs_common.textfont) {
4537
                PangoFontDescription *font_desc;
4538
4539
                font_desc = pango_font_description_from_string
4540
                        (prefs_common.textfont);
4541
                if (font_desc) {
4542
                        gtk_widget_modify_font(text, font_desc);
4543
                        pango_font_description_free(font_desc);
4544
                }
4545
        }
4546
4547
        gtk_text_view_set_pixels_above_lines
4548
                (GTK_TEXT_VIEW(text), prefs_common.line_space / 2);
4549
        gtk_text_view_set_pixels_below_lines
4550
                (GTK_TEXT_VIEW(text), prefs_common.line_space / 2);
4551
4552
        n_entries = sizeof(compose_popup_entries) /
4553
                sizeof(compose_popup_entries[0]);
4554
        popupmenu = menu_create_items(compose_popup_entries, n_entries,
4555
                                      "<Compose>", &popupfactory,
4556
                                      compose);
4557
4558
        ifactory = gtk_item_factory_from_widget(menubar);
4559
        menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
4560
        menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
4561
4562
        tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
4563
4564
#if USE_GTKSPELL
4565
        spell_menu = gtk_item_factory_get_item
4566
                (ifactory, "/Tools/Set spell language");
4567
#endif
4568
4569
        gtk_widget_hide(bcc_hbox);
4570
        gtk_widget_hide(bcc_entry);
4571
        gtk_widget_hide(reply_hbox);
4572
        gtk_widget_hide(reply_entry);
4573
        gtk_widget_hide(followup_hbox);
4574
        gtk_widget_hide(followup_entry);
4575
        gtk_widget_hide(ruler_hbox);
4576
        gtk_table_set_row_spacing(GTK_TABLE(table), 4, 0);
4577
        gtk_table_set_row_spacing(GTK_TABLE(table), 5, 0);
4578
        gtk_table_set_row_spacing(GTK_TABLE(table), 6, 0);
4579
4580
        if (account->protocol == A_NNTP) {
4581
                gtk_widget_hide(to_hbox);
4582
                gtk_widget_hide(to_entry);
4583
                gtk_widget_hide(cc_hbox);
4584
                gtk_widget_hide(cc_entry);
4585
                gtk_table_set_row_spacing(GTK_TABLE(table), 1, 0);
4586
                gtk_table_set_row_spacing(GTK_TABLE(table), 3, 0);
4587
        } else {
4588
                gtk_widget_hide(newsgroups_hbox);
4589
                gtk_widget_hide(newsgroups_entry);
4590
                gtk_table_set_row_spacing(GTK_TABLE(table), 2, 0);
4591
        }
4592
4593
#if USE_GPGME
4594
        if (!rfc2015_is_available())
4595
                gtk_widget_hide(misc_hbox);
4596
#endif
4597
4598
        undostruct = undo_init(text);
4599
        undo_set_change_state_func(undostruct, &compose_undo_state_changed,
4600
                                   menubar);
4601
4602
        address_completion_start(window);
4603
4604
        compose->window        = window;
4605
        compose->vbox               = vbox;
4606
        compose->menubar       = menubar;
4607
        compose->toolbar       = toolbar;
4608
4609
        compose->vbox2               = vbox2;
4610
4611
        compose->table_vbox       = table_vbox;
4612
        compose->table                  = table;
4613
        compose->to_hbox          = to_hbox;
4614
        compose->to_entry         = to_entry;
4615
        compose->newsgroups_hbox  = newsgroups_hbox;
4616
        compose->newsgroups_entry = newsgroups_entry;
4617
        compose->subject_entry    = subject_entry;
4618
        compose->cc_hbox          = cc_hbox;
4619
        compose->cc_entry         = cc_entry;
4620
        compose->bcc_hbox         = bcc_hbox;
4621
        compose->bcc_entry        = bcc_entry;
4622
        compose->reply_hbox       = reply_hbox;
4623
        compose->reply_entry      = reply_entry;
4624
        compose->followup_hbox    = followup_hbox;
4625
        compose->followup_entry   = followup_entry;
4626
4627
        /* compose->attach_toggle = attach_toggle; */
4628
#if USE_GPGME
4629
        compose->misc_hbox      = misc_hbox;
4630
        compose->signing_chkbtn = signing_chkbtn;
4631
        compose->encrypt_chkbtn = encrypt_chkbtn;
4632
#endif /* USE_GPGME */
4633
4634
        compose->paned = paned;
4635
4636
        compose->attach_scrwin   = attach_scrwin;
4637
        compose->attach_treeview = attach_treeview;
4638
        compose->attach_store    = store;
4639
4640
        compose->edit_vbox     = edit_vbox;
4641
        compose->ruler_hbox    = ruler_hbox;
4642
        compose->ruler         = ruler;
4643
        compose->scrolledwin   = scrolledwin;
4644
        compose->text               = text;
4645
4646
#ifdef USE_GTKSPELL
4647
        compose->check_spell = prefs_common.check_spell;
4648
        compose->spell_lang  = g_strdup(prefs_common.spell_lang);
4649
        compose->spell_menu  = spell_menu;
4650
#endif /* USE_GTKSPELL */
4651
4652
        compose->focused_editable = NULL;
4653
4654
        compose->popupmenu    = popupmenu;
4655
        compose->popupfactory = popupfactory;
4656
4657
        compose->tmpl_menu = tmpl_menu;
4658
4659
        compose->mode = mode;
4660
4661
        compose->targetinfo = NULL;
4662
        compose->replyinfo  = NULL;
4663
4664
        compose->replyto     = NULL;
4665
        compose->cc             = NULL;
4666
        compose->bcc             = NULL;
4667
        compose->followup_to = NULL;
4668
4669
        compose->ml_post     = NULL;
4670
4671
        compose->inreplyto   = NULL;
4672
        compose->references  = NULL;
4673
        compose->msgid       = NULL;
4674
        compose->boundary    = NULL;
4675
4676
        compose->autowrap       = prefs_common.autowrap;
4677
4678
        compose->use_to         = FALSE;
4679
        compose->use_cc         = FALSE;
4680
        compose->use_bcc        = FALSE;
4681
        compose->use_replyto    = FALSE;
4682
        compose->use_newsgroups = FALSE;
4683
        compose->use_followupto = FALSE;
4684
        compose->use_attach     = FALSE;
4685
4686
        compose->out_encoding   = C_AUTO;
4687
4688
#if USE_GPGME
4689
        compose->use_signing    = FALSE;
4690
        compose->use_encryption = FALSE;
4691
#endif /* USE_GPGME */
4692
4693
        compose->modified = FALSE;
4694
4695
        compose->to_list        = NULL;
4696
        compose->newsgroup_list = NULL;
4697
4698
        compose->undostruct = undostruct;
4699
4700
        compose->sig_tag = sig_tag;
4701
4702
        compose->exteditor_file = NULL;
4703
        compose->exteditor_pid  = 0;
4704
        compose->exteditor_tag  = 0;
4705
4706
        compose->autosave_tag = 0;
4707
4708
        compose_set_toolbar_button_visibility(compose);
4709
4710
        compose_select_account(compose, account, TRUE);
4711
4712
        menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
4713
        menu_set_active(ifactory, "/View/Ruler", prefs_common.show_ruler);
4714
4715
        if (mode == COMPOSE_REDIRECT) {
4716
                menu_set_sensitive(ifactory, "/File/Save to draft folder", FALSE);
4717
                menu_set_sensitive(ifactory, "/File/Save and keep editing", FALSE);
4718
                menu_set_sensitive(ifactory, "/File/Attach file", FALSE);
4719
                menu_set_sensitive(ifactory, "/File/Insert file", FALSE);
4720
                menu_set_sensitive(ifactory, "/File/Insert signature", FALSE);
4721
                menu_set_sensitive(ifactory, "/Edit/Cut", FALSE);
4722
                menu_set_sensitive(ifactory, "/Edit/Paste", FALSE);
4723
                menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", FALSE);
4724
                menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", FALSE);
4725
                menu_set_sensitive(ifactory, "/Edit/Auto wrapping", FALSE);
4726
                menu_set_sensitive(ifactory, "/View/Attachment", FALSE);
4727
                menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
4728
#ifndef G_OS_WIN32
4729
                menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
4730
#endif
4731
                menu_set_sensitive(ifactory, "/Tools/Edit with external editor", FALSE);
4732
#if USE_GPGME
4733
                menu_set_sensitive(ifactory, "/Tools/PGP Sign", FALSE);
4734
                menu_set_sensitive(ifactory, "/Tools/PGP Encrypt", FALSE);
4735
#endif /* USE_GPGME */
4736
#if USE_GTKSPELL
4737
                menu_set_sensitive(ifactory, "/Tools/Check spell", FALSE);
4738
                menu_set_sensitive(ifactory, "/Tools/Set spell language",
4739
                                   FALSE);
4740
#endif
4741
4742
                if (compose->insert_btn)
4743
                        gtk_widget_set_sensitive(compose->insert_btn, FALSE);
4744
                if (compose->attach_btn)
4745
                        gtk_widget_set_sensitive(compose->attach_btn, FALSE);
4746
                if (compose->sig_btn)
4747
                        gtk_widget_set_sensitive(compose->sig_btn, FALSE);
4748
                if (compose->exteditor_btn)
4749
                        gtk_widget_set_sensitive(compose->exteditor_btn, FALSE);
4750
                if (compose->linewrap_btn)
4751
                        gtk_widget_set_sensitive(compose->linewrap_btn, FALSE);
4752
4753
                /* gtk_widget_set_sensitive(compose->attach_toggle, FALSE); */
4754
4755
                menu_set_sensitive_all(GTK_MENU_SHELL(compose->popupmenu),
4756
                                       FALSE);
4757
        }
4758
4759
#if USE_GPGME
4760
        if (!rfc2015_is_available()) {
4761
                menu_set_sensitive(ifactory, "/Tools/PGP Sign", FALSE);
4762
                menu_set_sensitive(ifactory, "/Tools/PGP Encrypt", FALSE);
4763
        }
4764
#endif /* USE_GPGME */
4765
4766
        compose_set_out_encoding(compose);
4767
        addressbook_set_target_compose(compose);
4768
#ifndef G_OS_WIN32
4769
        action_update_compose_menu(ifactory, compose);
4770
#endif
4771
        compose_set_template_menu(compose);
4772
4773
#if USE_GTKSPELL
4774
        compose_set_spell_lang_menu(compose);
4775
        if (mode != COMPOSE_REDIRECT)
4776
                menu_set_active(ifactory, "/Tools/Check spell",
4777
                                prefs_common.check_spell);
4778
#endif
4779
4780
        compose_list = g_list_append(compose_list, compose);
4781
4782
        gtk_widget_show(window);
4783
4784
        color[0] = quote_color;
4785
        cmap = gdk_window_get_colormap(window->window);
4786
        gdk_colormap_alloc_colors(cmap, color, 1, FALSE, TRUE, success);
4787
        if (success[0] == FALSE) {
4788
                GtkStyle *style;
4789
4790
                g_warning("Compose: color allocation failed.\n");
4791
                style = gtk_widget_get_style(text);
4792
                quote_color = style->black;
4793
        }
4794
4795
        return compose;
4796
}
4797
4798
static Compose *compose_find_window_by_target(MsgInfo *msginfo)
4799
{
4800
        GList *cur;
4801
        Compose *compose;
4802
4803
        g_return_val_if_fail(msginfo != NULL, NULL);
4804
4805
        for (cur = compose_list; cur != NULL; cur = cur->next) {
4806
                compose = cur->data;
4807
                if (procmsg_msginfo_equal(compose->targetinfo, msginfo))
4808
                        return compose;
4809
        }
4810
4811
        return NULL;
4812
}
4813
4814
static gboolean compose_window_exist(gint x, gint y)
4815
{
4816
        GList *cur;
4817
        Compose *compose;
4818
        gint x_, y_;
4819
4820
        for (cur = compose_list; cur != NULL; cur = cur->next) {
4821
                compose = cur->data;
4822
                gtkut_widget_get_uposition(compose->window, &x_, &y_);
4823
                if (x == x_ && y == y_)
4824
                        return TRUE;
4825
        }
4826
4827
        return FALSE;
4828
}
4829
4830
static void compose_connect_changed_callbacks(Compose *compose)
4831
{
4832
        GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4833
        GtkTextBuffer *buffer;
4834
4835
        buffer = gtk_text_view_get_buffer(text);
4836
4837
        g_signal_connect(G_OBJECT(buffer), "changed",
4838
                         G_CALLBACK(compose_buffer_changed_cb), compose);
4839
        g_signal_connect(G_OBJECT(compose->to_entry), "changed",
4840
                         G_CALLBACK(compose_changed_cb), compose);
4841
        g_signal_connect(G_OBJECT(compose->newsgroups_entry), "changed",
4842
                         G_CALLBACK(compose_changed_cb), compose);
4843
        g_signal_connect(G_OBJECT(compose->cc_entry), "changed",
4844
                         G_CALLBACK(compose_changed_cb), compose);
4845
        g_signal_connect(G_OBJECT(compose->bcc_entry), "changed",
4846
                         G_CALLBACK(compose_changed_cb), compose);
4847
        g_signal_connect(G_OBJECT(compose->reply_entry), "changed",
4848
                         G_CALLBACK(compose_changed_cb), compose);
4849
        g_signal_connect(G_OBJECT(compose->followup_entry), "changed",
4850
                         G_CALLBACK(compose_changed_cb), compose);
4851
        g_signal_connect(G_OBJECT(compose->subject_entry), "changed",
4852
                         G_CALLBACK(compose_changed_cb), compose);
4853
}
4854
4855
static PrefsToolbarItem items[] =
4856
{
4857
        {T_SEND,        N_("Send message"),
4858
         STOCK_PIXMAP_MAIL_SEND,        NULL,        toolbar_send_cb},
4859
        {T_SEND_LATER,        N_("Put into queue folder and send later"),
4860
         STOCK_PIXMAP_MAIL_SEND_QUEUE,        NULL,        toolbar_send_later_cb},
4861
        {T_DRAFT,        N_("Save to draft folder"),
4862
         STOCK_PIXMAP_MAIL,                NULL,        toolbar_draft_cb},
4863
        {T_INSERT_FILE,        N_("Insert file"),
4864
         STOCK_PIXMAP_INSERT_FILE,        NULL,        toolbar_insert_cb},
4865
        {T_ATTACH_FILE,        N_("Attach file"),
4866
         STOCK_PIXMAP_MAIL_ATTACH,        NULL,        toolbar_attach_cb},
4867
        {T_SIGNATURE,        N_("Append signature"),
4868
         STOCK_PIXMAP_SIGN,                NULL,        toolbar_sig_cb},
4869
        {T_EDITOR,        N_("Edit with external editor"),
4870
         STOCK_PIXMAP_MAIL_COMPOSE,        NULL,        toolbar_ext_editor_cb},
4871
        {T_LINEWRAP,        N_("Wrap all long lines"),
4872
         STOCK_PIXMAP_LINEWRAP,                NULL,        toolbar_linewrap_cb},
4873
        {T_ADDRESS_BOOK,        N_("Address book"),
4874
         STOCK_PIXMAP_ADDRESS_BOOK,        NULL,        toolbar_address_cb},
4875
4876
        {-1, NULL, -1, NULL, NULL}
4877
};
4878
4879
static GtkWidget *compose_toolbar_create(Compose *compose)
4880
{
4881
        GtkWidget *toolbar;
4882
        const gchar *setting;
4883
        GList *item_list;
4884
4885
        if (prefs_common.compose_toolbar_setting &&
4886
            *prefs_common.compose_toolbar_setting != '\0')
4887
                setting = prefs_common.compose_toolbar_setting;
4888
        else
4889
                setting = prefs_toolbar_get_default_compose_setting_name_list();
4890
4891
        item_list = prefs_toolbar_get_item_list_from_name_list(setting);
4892
        toolbar = compose_toolbar_create_from_list(compose, item_list);
4893
        g_list_free(item_list);
4894
4895
        return toolbar;
4896
}
4897
4898
static GtkWidget *compose_toolbar_create_from_list(Compose *compose,
4899
                                                   GList *item_list)
4900
{
4901
        GtkWidget *toolbar;
4902
        GtkWidget *icon_wid;
4903
        GtkToolItem *toolitem;
4904
        gint i;
4905
        GList *cur;
4906
4907
        toolbar = gtk_toolbar_new();
4908
        gtk_toolbar_set_orientation(GTK_TOOLBAR(toolbar),
4909
                                    GTK_ORIENTATION_HORIZONTAL);
4910
        gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH);
4911
        g_signal_connect(G_OBJECT(toolbar), "button_press_event",
4912
                         G_CALLBACK(toolbar_button_pressed), compose);
4913
4914
        items[0].data = &compose->send_btn;
4915
        items[1].data = &compose->sendl_btn;
4916
        items[2].data = &compose->draft_btn;
4917
        items[3].data = &compose->insert_btn;
4918
        items[4].data = &compose->attach_btn;
4919
        items[5].data = &compose->sig_btn;
4920
        items[6].data = &compose->exteditor_btn;
4921
        items[7].data = &compose->linewrap_btn;
4922
        items[8].data = &compose->addrbook_btn;
4923
        for (i = 0; i <= 8; i++)
4924
                *(GtkWidget **)items[i].data = NULL;
4925
4926
        for (cur = item_list; cur != NULL; cur = cur->next) {
4927
                const PrefsDisplayItem *ditem = cur->data;
4928
                PrefsToolbarItem *item;
4929
                GtkTooltips *tips;
4930
                gint width;
4931
4932
                if (ditem->id == T_SEPARATOR) {
4933
                        toolitem = gtk_separator_tool_item_new();
4934
                        gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
4935
                        continue;
4936
                }
4937
4938
                for (item = items; item->id != -1; item++) {
4939
                        if (ditem->id == item->id)
4940
                                break;
4941
                }
4942
                if (item->id == -1)
4943
                        continue;
4944
4945
                if (item->stock_id) {
4946
                        icon_wid = gtk_image_new_from_stock
4947
                                (item->stock_id, GTK_ICON_SIZE_LARGE_TOOLBAR);
4948
                } else
4949
                        icon_wid = stock_pixbuf_widget(NULL, item->icon);
4950
4951
                toolitem = gtk_tool_button_new(icon_wid, gettext(ditem->label));
4952
                tips = gtk_tooltips_new();
4953
                gtk_tool_item_set_tooltip(toolitem, tips,
4954
                                          gettext(item->tooltip), ditem->name);
4955
4956
                gtkut_get_str_size(GTK_WIDGET(toolitem), gettext(ditem->label),
4957
                                   &width, NULL);
4958
                gtk_tool_item_set_homogeneous
4959
                        (toolitem, width < 52 ? TRUE : FALSE);
4960
4961
                gtk_toolbar_insert(GTK_TOOLBAR(toolbar), toolitem, -1);
4962
4963
                g_signal_connect(G_OBJECT(toolitem), "clicked",
4964
                                 G_CALLBACK(item->callback), compose);
4965
                g_signal_connect(G_OBJECT(GTK_BIN(toolitem)->child),
4966
                                 "button_press_event",
4967
                                 G_CALLBACK(toolbar_button_pressed), compose);
4968
4969
                *(GtkWidget **)item->data = GTK_WIDGET(toolitem);
4970
        }
4971
4972
        gtk_widget_show_all(toolbar);
4973
4974
        return toolbar;
4975
}
4976
4977
static void compose_set_toolbar_button_visibility(Compose *compose)
4978
{
4979
        GtkToolbarStyle style;
4980
4981
        if (prefs_common.toolbar_style == TOOLBAR_NONE)
4982
                style = -1;
4983
        else if (prefs_common.toolbar_style == TOOLBAR_ICON)
4984
                style = GTK_TOOLBAR_ICONS;
4985
        else if (prefs_common.toolbar_style == TOOLBAR_TEXT)
4986
                style = GTK_TOOLBAR_TEXT;
4987
        else if (prefs_common.toolbar_style == TOOLBAR_BOTH)
4988
                style = GTK_TOOLBAR_BOTH;
4989
4990
        if (style != -1) {
4991
                gtk_toolbar_set_style(GTK_TOOLBAR(compose->toolbar), style);
4992
                gtk_widget_show(compose->toolbar);
4993
                gtk_widget_queue_resize(compose->toolbar);
4994
        } else
4995
                gtk_widget_hide(compose->toolbar);
4996
}
4997
4998
static GtkWidget *compose_account_option_menu_create(Compose *compose,
4999
                                                     GtkWidget *hbox)
5000
{
5001
        GList *accounts;
5002
        GtkWidget *optmenu;
5003
        GtkWidget *menu;
5004
        gint num = 0, def_menu = 0;
5005
5006
        accounts = account_get_list();
5007
        g_return_val_if_fail(accounts != NULL, NULL);
5008
5009
        optmenu = gtk_option_menu_new();
5010
        gtk_box_pack_start(GTK_BOX(hbox), optmenu, FALSE, FALSE, 0);
5011
        menu = gtk_menu_new();
5012
5013
        for (; accounts != NULL; accounts = accounts->next, num++) {
5014
                PrefsAccount *ac = (PrefsAccount *)accounts->data;
5015
                GtkWidget *menuitem;
5016
                gchar *name;
5017
5018
                if (ac == compose->account) def_menu = num;
5019
5020
                if (ac->name)
5021
                        name = g_strdup_printf("%s: %s <%s>",
5022
                                               ac->account_name,
5023
                                               ac->name, ac->address);
5024
                else
5025
                        name = g_strdup_printf("%s: %s",
5026
                                               ac->account_name, ac->address);
5027
                MENUITEM_ADD(menu, menuitem, name, ac);
5028
                g_free(name);
5029
                g_signal_connect(G_OBJECT(menuitem), "activate",
5030
                                 G_CALLBACK(account_activated),
5031
                                 compose);
5032
        }
5033
5034
        gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), menu);
5035
        gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), def_menu);
5036
5037
        return optmenu;
5038
}
5039
5040
static void compose_set_out_encoding(Compose *compose)
5041
{
5042
        GtkItemFactoryEntry *entry;
5043
        GtkItemFactory *ifactory;
5044
        CharSet out_encoding;
5045
        gchar *path, *p, *q;
5046
        GtkWidget *item;
5047
5048
        out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
5049
        ifactory = gtk_item_factory_from_widget(compose->menubar);
5050
5051
        for (entry = compose_entries; entry->callback != compose_address_cb;
5052
             entry++) {
5053
                if (entry->callback == compose_set_encoding_cb &&
5054
                    (CharSet)entry->callback_action == out_encoding) {
5055
                        p = q = path = g_strdup(entry->path);
5056
                        while (*p) {
5057
                                if (*p == '_') {
5058
                                        if (p[1] == '_') {
5059
                                                p++;
5060
                                                *q++ = '_';
5061
                                        }
5062
                                } else
5063
                                        *q++ = *p;
5064
                                p++;
5065
                        }
5066
                        *q = '\0';
5067
                        item = gtk_item_factory_get_item(ifactory, path);
5068
                        gtk_widget_activate(item);
5069
                        g_free(path);
5070
                        break;
5071
                }
5072
        }
5073
}
5074
5075
#if USE_GTKSPELL
5076
static void compose_set_spell_lang_menu(Compose *compose)
5077
{
5078
        AspellConfig *config;
5079
        AspellDictInfoList *dlist;
5080
        AspellDictInfoEnumeration *dels;
5081
        const AspellDictInfo *entry;
5082
        GSList *dict_list = NULL, *menu_list = NULL, *cur;
5083
        GtkWidget *menu;
5084
        gboolean lang_set = FALSE;
5085
5086
        config = new_aspell_config();
5087
        dlist = get_aspell_dict_info_list(config);
5088
        delete_aspell_config(config);
5089
5090
        dels = aspell_dict_info_list_elements(dlist);
5091
        while ((entry = aspell_dict_info_enumeration_next(dels)) != 0) {
5092
                dict_list = g_slist_append(dict_list, (gchar *)entry->name);
5093
                if (compose->spell_lang != NULL &&
5094
                    g_ascii_strcasecmp(compose->spell_lang, entry->name) == 0)
5095
                        lang_set = TRUE;
5096
        }
5097
        delete_aspell_dict_info_enumeration(dels);
5098
5099
        menu = gtk_menu_new();
5100
5101
        for (cur = dict_list; cur != NULL; cur = cur->next) {
5102
                gchar *dict = (gchar *)cur->data;
5103
                GtkWidget *item;
5104
5105
                if (dict == NULL) continue;
5106
5107
                item = gtk_radio_menu_item_new_with_label(menu_list, dict);
5108
                menu_list = gtk_radio_menu_item_get_group
5109
                        (GTK_RADIO_MENU_ITEM(item));
5110
                if (compose->spell_lang != NULL &&
5111
                    g_ascii_strcasecmp(compose->spell_lang, dict) == 0)
5112
                        gtk_check_menu_item_set_active
5113
                                (GTK_CHECK_MENU_ITEM(item), TRUE);
5114
                gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
5115
                g_signal_connect(G_OBJECT(item), "activate",
5116
                                 G_CALLBACK(compose_set_spell_lang_cb),
5117
                                 compose);     
5118
                g_object_set_data(G_OBJECT(item), "spell-lang", dict);
5119
                gtk_widget_show(item);
5120
5121
                if (!lang_set && g_ascii_strcasecmp("en", dict) == 0)
5122
                        gtk_check_menu_item_set_active
5123
                                (GTK_CHECK_MENU_ITEM(item), TRUE);
5124
        }
5125
5126
        gtk_widget_show(menu);
5127
        gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->spell_menu), menu);
5128
}
5129
#endif
5130
5131
static void compose_set_template_menu(Compose *compose)
5132
{
5133
        GSList *tmpl_list, *cur;
5134
        GtkWidget *menu;
5135
        GtkWidget *item;
5136
5137
        tmpl_list = template_get_config();
5138
5139
        menu = gtk_menu_new();
5140
5141
        for (cur = tmpl_list; cur != NULL; cur = cur->next) {
5142
                Template *tmpl = (Template *)cur->data;
5143
5144
                item = gtk_menu_item_new_with_label(tmpl->name);
5145
                gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
5146
                g_signal_connect(G_OBJECT(item), "activate",
5147
                                 G_CALLBACK(compose_template_activate_cb),
5148
                                 compose);
5149
                g_object_set_data(G_OBJECT(item), "template", tmpl);
5150
                gtk_widget_show(item);
5151
        }
5152
5153
        gtk_widget_show(menu);
5154
        gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
5155
}
5156
5157
void compose_reflect_prefs_all(void)
5158
{
5159
        GList *cur;
5160
        Compose *compose;
5161
5162
        for (cur = compose_list; cur != NULL; cur = cur->next) {
5163
                compose = (Compose *)cur->data;
5164
5165
                if (compose->autosave_tag > 0) {
5166
                        g_source_remove(compose->autosave_tag);
5167
                        compose->autosave_tag = 0;
5168
                }
5169
5170
                compose_set_template_menu(compose);
5171
5172
                if (prefs_common.enable_autosave &&
5173
                    prefs_common.autosave_itv > 0 &&
5174
                    compose->mode != COMPOSE_REDIRECT)
5175
                        compose->autosave_tag =
5176
                                g_timeout_add
5177
                                        (prefs_common.autosave_itv * 60 * 1000,
5178
                                         autosave_timeout, compose);
5179
        }
5180
}
5181
5182
static void compose_template_apply(Compose *compose, Template *tmpl,
5183
                                   gboolean replace)
5184
{
5185
        GtkTextView *text = GTK_TEXT_VIEW(compose->text);
5186
        GtkTextBuffer *buffer;
5187
        GtkTextMark *mark;
5188
        GtkTextIter iter;
5189
        gchar *qmark;
5190
        gchar *parsed_str;
5191
5192
        if (!tmpl || !tmpl->value) return;
5193
5194
        buffer = gtk_text_view_get_buffer(text);
5195
5196
        if (tmpl->to && *tmpl->to != '\0')
5197
                compose_entry_set(compose, tmpl->to, COMPOSE_ENTRY_TO);
5198
        if (tmpl->cc && *tmpl->cc != '\0')
5199
                compose_entry_set(compose, tmpl->cc, COMPOSE_ENTRY_CC);
5200
        if (tmpl->bcc && *tmpl->bcc != '\0')
5201
                compose_entry_set(compose, tmpl->bcc, COMPOSE_ENTRY_BCC);
5202
        if (tmpl->replyto && *tmpl->replyto != '\0')
5203
                compose_entry_set(compose, tmpl->replyto,
5204
                                  COMPOSE_ENTRY_REPLY_TO);
5205
        if (tmpl->subject && *tmpl->subject != '\0')
5206
                compose_entry_set(compose, tmpl->subject,
5207
                                  COMPOSE_ENTRY_SUBJECT);
5208
5209
        if (replace)
5210
                gtk_text_buffer_set_text(buffer, "", 0);
5211
5212
        mark = gtk_text_buffer_get_insert(buffer);
5213
        gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
5214
5215
        if (compose->replyinfo == NULL) {
5216
                parsed_str = compose_quote_fmt(compose, NULL, tmpl->value,
5217
                                               NULL, NULL);
5218
        } else {
5219
                if (prefs_common.quotemark && *prefs_common.quotemark)
5220
                        qmark = prefs_common.quotemark;
5221
                else
5222
                        qmark = "> ";
5223
5224
                parsed_str = compose_quote_fmt(compose, compose->replyinfo,
5225
                                               tmpl->value, qmark, NULL);
5226
        }
5227
5228
        if (replace && parsed_str && prefs_common.auto_sig)
5229
                compose_insert_sig(compose, TRUE, FALSE, FALSE);
5230
5231
        if (replace && parsed_str) {
5232
                gtk_text_buffer_get_start_iter(buffer, &iter);
5233
                gtk_text_buffer_place_cursor(buffer, &iter);
5234
        }
5235
5236
        if (parsed_str)
5237
                compose_changed_cb(NULL, compose);
5238
}
5239
5240
static void compose_destroy(Compose *compose)
5241
{
5242
        GtkTreeModel *model = GTK_TREE_MODEL(compose->attach_store);
5243
        GtkTreeIter iter;
5244
        gboolean valid;
5245
        AttachInfo *ainfo;
5246
        GtkTextBuffer *buffer;
5247
        GtkClipboard *clipboard;
5248
5249
        compose_list = g_list_remove(compose_list, compose);
5250
5251
        if (compose->autosave_tag > 0)
5252
                g_source_remove(compose->autosave_tag);
5253
5254
        /* NOTE: address_completion_end() does nothing with the window
5255
         * however this may change. */
5256
        address_completion_end(compose->window);
5257
5258
#if USE_GTKSPELL
5259
        g_free(compose->spell_lang);
5260
#endif
5261
5262
        slist_free_strings(compose->to_list);
5263
        g_slist_free(compose->to_list);
5264
        slist_free_strings(compose->newsgroup_list);
5265
        g_slist_free(compose->newsgroup_list);
5266
5267
        procmsg_msginfo_free(compose->targetinfo);
5268
        procmsg_msginfo_free(compose->replyinfo);
5269
5270
        g_free(compose->replyto);
5271
        g_free(compose->cc);
5272
        g_free(compose->bcc);
5273
        g_free(compose->newsgroups);
5274
        g_free(compose->followup_to);
5275
5276
        g_free(compose->ml_post);
5277
5278
        g_free(compose->inreplyto);
5279
        g_free(compose->references);
5280
        g_free(compose->msgid);
5281
        g_free(compose->boundary);
5282
5283
        if (compose->undostruct)
5284
                undo_destroy(compose->undostruct);
5285
5286
        if (compose->exteditor_file) {
5287
                g_unlink(compose->exteditor_file);
5288
                g_free(compose->exteditor_file);
5289
        }
5290
5291
        for (valid = gtk_tree_model_get_iter_first(model, &iter); valid;
5292
             valid = gtk_tree_model_iter_next(model, &iter)) {
5293
                gtk_tree_model_get(model, &iter, COL_ATTACH_INFO, &ainfo, -1);
5294
                compose_attach_info_free(ainfo);
5295
        }
5296
5297
        if (addressbook_get_target_compose() == compose)
5298
                addressbook_set_target_compose(NULL);
5299
5300
        gtkut_widget_get_uposition(compose->window, &prefs_common.compose_x,
5301
                                   &prefs_common.compose_y);
5302
        prefs_common.compose_width = compose->scrolledwin->allocation.width;
5303
        prefs_common.compose_height = compose->window->allocation.height;
5304
5305
        if (!gtk_widget_get_parent(compose->paned))
5306
                gtk_widget_destroy(compose->paned);
5307
        gtk_widget_destroy(compose->popupmenu);
5308
5309
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5310
        clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
5311
        gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
5312
5313
        gtk_widget_destroy(compose->window);
5314
5315
        g_free(compose);
5316
}
5317
5318
static void compose_attach_info_free(AttachInfo *ainfo)
5319
{
5320
        g_free(ainfo->file);
5321
        g_free(ainfo->content_type);
5322
        g_free(ainfo->name);
5323
        g_free(ainfo);
5324
}
5325
5326
static void compose_attach_remove_selected(Compose *compose)
5327
{
5328
        GtkTreeModel *model = GTK_TREE_MODEL(compose->attach_store);
5329
        GtkTreeSelection *selection;
5330
        GtkTreeIter iter;
5331
        GList *rows, *cur;
5332
        AttachInfo *ainfo;
5333
5334
        selection = gtk_tree_view_get_selection
5335
                (GTK_TREE_VIEW(compose->attach_treeview));
5336
5337
        rows = gtk_tree_selection_get_selected_rows(selection, NULL);
5338
5339
        /* delete from below so that GtkTreePath doesn't point wrong row */
5340
        rows = g_list_reverse(rows);
5341
5342
        for (cur = rows; cur != NULL; cur = cur->next) {
5343
                gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)cur->data);
5344
                gtk_tree_model_get(model, &iter, COL_ATTACH_INFO, &ainfo, -1);
5345
                compose_attach_info_free(ainfo);
5346
                gtk_list_store_remove(compose->attach_store, &iter);
5347
                gtk_tree_path_free((GtkTreePath *)cur->data);
5348
        }
5349
5350
        g_list_free(rows);
5351
}
5352
5353
static struct _AttachProperty
5354
{
5355
        GtkWidget *window;
5356
        GtkWidget *mimetype_entry;
5357
        GtkWidget *encoding_optmenu;
5358
        GtkWidget *path_entry;
5359
        GtkWidget *filename_entry;
5360
        GtkWidget *ok_btn;
5361
        GtkWidget *cancel_btn;
5362
} attach_prop;
5363
5364
static void compose_attach_property(Compose *compose)
5365
{
5366
        GtkTreeModel *model = GTK_TREE_MODEL(compose->attach_store);
5367
        GtkTreeSelection *selection;
5368
        GtkTreeIter iter;
5369
        GList *rows;
5370
        AttachInfo *ainfo;
5371
        gchar *path = NULL;
5372
        GtkOptionMenu *optmenu;
5373
        static gboolean cancelled;
5374
5375
        selection = gtk_tree_view_get_selection
5376
                (GTK_TREE_VIEW(compose->attach_treeview));
5377
5378
        rows = gtk_tree_selection_get_selected_rows(selection, NULL);
5379
5380
        if (!rows)
5381
                return;
5382
5383
        gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)rows->data);
5384
        gtk_tree_model_get(model, &iter, COL_ATTACH_INFO, &ainfo, -1);
5385
5386
        g_list_foreach(rows, (GFunc)gtk_tree_path_free, NULL);
5387
        g_list_free(rows);
5388
5389
        if (!attach_prop.window)
5390
                compose_attach_property_create(&cancelled);
5391
        gtk_widget_grab_focus(attach_prop.ok_btn);
5392
        gtk_widget_show(attach_prop.window);
5393
        manage_window_set_transient(GTK_WINDOW(attach_prop.window));
5394
5395
        optmenu = GTK_OPTION_MENU(attach_prop.encoding_optmenu);
5396
        if (ainfo->encoding == ENC_UNKNOWN)
5397
                gtk_option_menu_set_history(optmenu, ENC_BASE64);
5398
        else
5399
                gtk_option_menu_set_history(optmenu, ainfo->encoding);
5400
5401
        if (ainfo->file)
5402
                path = conv_filename_to_utf8(ainfo->file);
5403
5404
        gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
5405
                           ainfo->content_type ? ainfo->content_type : "");
5406
        gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry), path ? path : "");
5407
        gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
5408
                           ainfo->name ? ainfo->name : "");
5409
5410
        g_free(path);
5411
5412
        for (;;) {
5413
                const gchar *entry_text;
5414
                gchar *text;
5415
                gchar *cnttype = NULL;
5416
                gchar *file = NULL;
5417
                off_t size = 0;
5418
                GtkWidget *menu;
5419
                GtkWidget *menuitem;
5420
5421
                cancelled = FALSE;
5422
                gtk_main();
5423
5424
                if (cancelled == TRUE) {
5425
                        gtk_widget_hide(attach_prop.window);
5426
                        break;
5427
                }
5428
5429
                entry_text = gtk_entry_get_text
5430
                        (GTK_ENTRY(attach_prop.mimetype_entry));
5431
                if (*entry_text != '\0') {
5432
                        gchar *p;
5433
5434
                        text = g_strstrip(g_strdup(entry_text));
5435
                        if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
5436
                                cnttype = g_strdup(text);
5437
                                g_free(text);
5438
                        } else {
5439
                                alertpanel_error(_("Invalid MIME type."));
5440
                                g_free(text);
5441
                                continue;
5442
                        }
5443
                }
5444
5445
                menu = gtk_option_menu_get_menu(optmenu);
5446
                menuitem = gtk_menu_get_active(GTK_MENU(menu));
5447
                ainfo->encoding = GPOINTER_TO_INT
5448
                        (g_object_get_data(G_OBJECT(menuitem), MENU_VAL_ID));
5449
5450
                entry_text = gtk_entry_get_text
5451
                        (GTK_ENTRY(attach_prop.path_entry));
5452
                if (*entry_text != '\0') {
5453
                        file = conv_filename_from_utf8(entry_text);
5454
                        if (!is_file_exist(file) ||
5455
                            (size = get_file_size(file)) <= 0) {
5456
                                alertpanel_error
5457
                                        (_("File doesn't exist or is empty."));
5458
                                g_free(file);
5459
                                g_free(cnttype);
5460
                                continue;
5461
                        }
5462
                        g_free(ainfo->file);
5463
                        ainfo->file = file;
5464
                }
5465
5466
                entry_text = gtk_entry_get_text
5467
                        (GTK_ENTRY(attach_prop.filename_entry));
5468
                if (*entry_text != '\0') {
5469
                        g_free(ainfo->name);
5470
                        ainfo->name = g_strdup(entry_text);
5471
                }
5472
5473
                if (cnttype) {
5474
                        g_free(ainfo->content_type);
5475
                        ainfo->content_type = cnttype;
5476
                }
5477
                if (size)
5478
                        ainfo->size = size;
5479
5480
                gtk_list_store_set(compose->attach_store, &iter,
5481
                                   COL_MIMETYPE, ainfo->content_type,
5482
                                   COL_SIZE, to_human_readable(ainfo->size),
5483
                                   COL_NAME, ainfo->name,
5484
                                   -1);
5485
5486
                gtk_widget_hide(attach_prop.window);
5487
                break;
5488
        }
5489
}
5490
5491
#define SET_LABEL_AND_ENTRY(str, entry, top) \
5492
{ \
5493
        label = gtk_label_new(str); \
5494
        gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
5495
                         GTK_FILL, 0, 0, 0); \
5496
        gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
5497
 \
5498
        entry = gtk_entry_new(); \
5499
        gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
5500
                         GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
5501
}
5502
5503
static void compose_attach_property_create(gboolean *cancelled)
5504
{
5505
        GtkWidget *window;
5506
        GtkWidget *vbox;
5507
        GtkWidget *table;
5508
        GtkWidget *label;
5509
        GtkWidget *mimetype_entry;
5510
        GtkWidget *hbox;
5511
        GtkWidget *optmenu;
5512
        GtkWidget *optmenu_menu;
5513
        GtkWidget *menuitem;
5514
        GtkWidget *path_entry;
5515
        GtkWidget *filename_entry;
5516
        GtkWidget *hbbox;
5517
        GtkWidget *ok_btn;
5518
        GtkWidget *cancel_btn;
5519
5520
        debug_print("Creating attach_property window...\n");
5521
5522
        window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
5523
        gtk_widget_set_size_request(window, 480, -1);
5524
        gtk_container_set_border_width(GTK_CONTAINER(window), 8);
5525
        gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
5526
        gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
5527
        gtk_window_set_modal(GTK_WINDOW(window), TRUE);
5528
        g_signal_connect(G_OBJECT(window), "delete_event",
5529
                         G_CALLBACK(attach_property_delete_event),
5530
                         cancelled);
5531
        g_signal_connect(G_OBJECT(window), "key_press_event",
5532
                         G_CALLBACK(attach_property_key_pressed),
5533
                         cancelled);
5534
5535
        vbox = gtk_vbox_new(FALSE, 8);
5536
        gtk_container_add(GTK_CONTAINER(window), vbox);
5537
5538
        table = gtk_table_new(4, 2, FALSE);
5539
        gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
5540
        gtk_table_set_row_spacings(GTK_TABLE(table), 8);
5541
        gtk_table_set_col_spacings(GTK_TABLE(table), 8);
5542
5543
        SET_LABEL_AND_ENTRY(_("MIME type"), mimetype_entry, 0);
5544
5545
        label = gtk_label_new(_("Encoding"));
5546
        gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
5547
                         GTK_FILL, 0, 0, 0);
5548
        gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
5549
5550
        hbox = gtk_hbox_new(FALSE, 0);
5551
        gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
5552
                         GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
5553
5554
        optmenu = gtk_option_menu_new();
5555
        gtk_box_pack_start(GTK_BOX(hbox), optmenu, FALSE, FALSE, 0);
5556
5557
        optmenu_menu = gtk_menu_new();
5558
        MENUITEM_ADD(optmenu_menu, menuitem, "7bit", ENC_7BIT);
5559
        gtk_widget_set_sensitive(menuitem, FALSE);
5560
        MENUITEM_ADD(optmenu_menu, menuitem, "8bit", ENC_8BIT);
5561
        gtk_widget_set_sensitive(menuitem, FALSE);
5562
        MENUITEM_ADD(optmenu_menu, menuitem, "quoted-printable",
5563
                     ENC_QUOTED_PRINTABLE);
5564
        MENUITEM_ADD(optmenu_menu, menuitem, "base64", ENC_BASE64);
5565
5566
        gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), optmenu_menu);
5567
5568
        SET_LABEL_AND_ENTRY(_("Path"),      path_entry,     2);
5569
        SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
5570
5571
        gtkut_stock_button_set_create(&hbbox, &ok_btn, GTK_STOCK_OK,
5572
                                      &cancel_btn, GTK_STOCK_CANCEL,
5573
                                      NULL, NULL);
5574
        gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
5575
        gtk_widget_grab_default(ok_btn);
5576
5577
        g_signal_connect(G_OBJECT(ok_btn), "clicked",
5578
                         G_CALLBACK(attach_property_ok),
5579
                         cancelled);
5580
        g_signal_connect(G_OBJECT(cancel_btn), "clicked",
5581
                         G_CALLBACK(attach_property_cancel),
5582
                         cancelled);
5583
5584
        gtk_widget_show_all(vbox);
5585
5586
        attach_prop.window           = window;
5587
        attach_prop.mimetype_entry   = mimetype_entry;
5588
        attach_prop.encoding_optmenu = optmenu;
5589
        attach_prop.path_entry       = path_entry;
5590
        attach_prop.filename_entry   = filename_entry;
5591
        attach_prop.ok_btn           = ok_btn;
5592
        attach_prop.cancel_btn       = cancel_btn;
5593
}
5594
5595
#undef SET_LABEL_AND_ENTRY
5596
5597
static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
5598
{
5599
        *cancelled = FALSE;
5600
        gtk_main_quit();
5601
}
5602
5603
static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
5604
{
5605
        *cancelled = TRUE;
5606
        gtk_main_quit();
5607
}
5608
5609
static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
5610
                                         gboolean *cancelled)
5611
{
5612
        *cancelled = TRUE;
5613
        gtk_main_quit();
5614
5615
        return TRUE;
5616
}
5617
5618
static gboolean attach_property_key_pressed(GtkWidget *widget,
5619
                                            GdkEventKey *event,
5620
                                            gboolean *cancelled)
5621
{
5622
        if (event && event->keyval == GDK_Escape) {
5623
                *cancelled = TRUE;
5624
                gtk_main_quit();
5625
        }
5626
        return FALSE;
5627
}
5628
5629
static void compose_exec_ext_editor(Compose *compose)
5630
{
5631
        gchar *tmp;
5632
        GPid pid;
5633
        static gchar *def_cmd = "emacs %s";
5634
        gchar buf[1024];
5635
        gchar **cmdline;
5636
        GError *error = NULL;
5637
5638
        tmp = g_strdup_printf("%s%ctmpmsg-%p.txt", get_tmp_dir(),
5639
                              G_DIR_SEPARATOR, compose);
5640
5641
        if (compose_write_body_to_file(compose, tmp) < 0) {
5642
                g_warning("Coundn't write to file: %s\n", tmp);
5643
                g_free(tmp);
5644
                return;
5645
        }
5646
#ifdef G_OS_WIN32
5647
        if (canonicalize_file_replace(tmp) < 0) {
5648
                g_warning("Coundn't write to file: %s\n", tmp);
5649
                g_free(tmp);
5650
                return;
5651
        }
5652
#endif
5653
5654
        if (prefs_common.ext_editor_cmd &&
5655
            str_find_format_times(prefs_common.ext_editor_cmd, 's') == 1)
5656
                g_snprintf(buf, sizeof(buf), prefs_common.ext_editor_cmd, tmp);
5657
        else {
5658
                if (prefs_common.ext_editor_cmd)
5659
                        g_warning(_("External editor command line is invalid: `%s'\n"),
5660
                                  prefs_common.ext_editor_cmd);
5661
                g_snprintf(buf, sizeof(buf), def_cmd, tmp);
5662
        }
5663
5664
        cmdline = strsplit_with_quote(buf, " ", 1024);
5665
5666
        if (g_spawn_async(NULL, cmdline, NULL,
5667
                          G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
5668
                          NULL, NULL, &pid, &error) == FALSE) {
5669
                g_warning("Couldn't execute external editor: %s\n", buf);
5670
                if (error) {
5671
                        g_warning("g_spawn_async(): %s\n", error->message);
5672
                        g_error_free(error);
5673
                }
5674
                g_strfreev(cmdline);
5675
                g_unlink(tmp);
5676
                g_free(tmp);
5677
                return;
5678
        }
5679
5680
        g_strfreev(cmdline);
5681
5682
        compose_set_ext_editor_sensitive(compose, FALSE);
5683
5684
        debug_print("compose_exec_ext_editor(): pid: %d file: %s\n", pid, tmp);
5685
5686
        compose->exteditor_file = tmp;
5687
        compose->exteditor_pid = pid;
5688
        compose->exteditor_tag =
5689
                g_child_watch_add(pid, compose_ext_editor_child_exit, compose);
5690
}
5691
5692
static gboolean compose_ext_editor_kill(Compose *compose)
5693
{
5694
#ifdef G_OS_WIN32
5695
        DWORD exitcode;
5696
#endif
5697
        gint ret;
5698
5699
        g_return_val_if_fail(compose->exteditor_pid != 0, TRUE);
5700
5701
#ifdef G_OS_WIN32
5702
        ret = GetExitCodeProcess(compose->exteditor_pid, &exitcode);
5703
5704
        if (ret && exitcode == STILL_ACTIVE) {
5705
#else
5706
        ret = kill(compose->exteditor_pid, 0);
5707
5708
        if (ret == 0 || (ret == -1 && EPERM == errno)) {
5709
#endif
5710
                AlertValue val;
5711
                gchar *msg;
5712
5713
                msg = g_strdup_printf
5714
                        (_("The external editor is still working.\n"
5715
                           "Force terminating the process (pid: %d)?\n"),
5716
                         compose->exteditor_pid);
5717
                val = alertpanel_full(_("Notice"), msg, ALERT_NOTICE,
5718
                                      G_ALERTALTERNATE, FALSE,
5719
                                      GTK_STOCK_YES, GTK_STOCK_NO, NULL);
5720
                g_free(msg);
5721
5722
                if (val != G_ALERTDEFAULT)
5723
                        return FALSE;
5724
        }
5725
5726
        if (compose->exteditor_tag != 0) {
5727
                g_source_remove(compose->exteditor_tag);
5728
                compose->exteditor_tag = 0;
5729
        }
5730
5731
        if (compose->exteditor_pid != 0) {
5732
#ifdef G_OS_WIN32
5733
                if (TerminateProcess(compose->exteditor_pid, 1) == 0)
5734
                        g_warning("TerminateProcess() failed: %d\n",
5735
                                  GetLastError());
5736
#else
5737
                if (kill(compose->exteditor_pid, SIGTERM) < 0)
5738
                        perror("kill");
5739
#endif
5740
                g_message("Terminated process group id: %d\n",
5741
                          compose->exteditor_pid);
5742
                g_message("Temporary file: %s\n",
5743
                          compose->exteditor_file);
5744
                compose->exteditor_pid = 0;
5745
        }
5746
5747
        if (compose->exteditor_file) {
5748
                g_unlink(compose->exteditor_file);
5749
                g_free(compose->exteditor_file);
5750
                compose->exteditor_file = NULL;
5751
        }
5752
5753
        compose_set_ext_editor_sensitive(compose, TRUE);
5754
5755
        return TRUE;
5756
}
5757
5758
static void compose_ext_editor_child_exit(GPid pid, gint status, gpointer data)
5759
{
5760
        Compose *compose = (Compose *)data;
5761
        GtkTextView *text = GTK_TEXT_VIEW(compose->text);
5762
        GtkTextBuffer *buffer;
5763
        GtkTextMark *mark;
5764
        GtkTextIter iter;
5765
5766
        debug_print("Compose: child exit (pid: %d status: %d)\n", pid, status);
5767
5768
        compose_lock(compose);
5769
5770
        g_spawn_close_pid(pid);
5771
5772
        buffer = gtk_text_view_get_buffer(text);
5773
5774
        gtk_text_buffer_set_text(buffer, "", 0);
5775
        compose_insert_file(compose, compose->exteditor_file, FALSE);
5776
        compose_enable_sig(compose);
5777
5778
        gtk_text_buffer_get_start_iter(buffer, &iter);
5779
        gtk_text_buffer_place_cursor(buffer, &iter);
5780
        mark = gtk_text_buffer_get_insert(buffer);
5781
        gtk_text_view_scroll_mark_onscreen(text, mark);
5782
5783
        compose_changed_cb(NULL, compose);
5784
5785
        if (g_unlink(compose->exteditor_file) < 0)
5786
                FILE_OP_ERROR(compose->exteditor_file, "unlink");
5787
5788
        compose_set_ext_editor_sensitive(compose, TRUE);
5789
5790
        g_free(compose->exteditor_file);
5791
        compose->exteditor_file = NULL;
5792
        compose->exteditor_pid  = 0;
5793
        compose->exteditor_tag  = 0;
5794
5795
        compose_unlock(compose);
5796
}
5797
5798
static void compose_set_ext_editor_sensitive(Compose *compose,
5799
                                             gboolean sensitive)
5800
{
5801
        GtkItemFactory *ifactory;
5802
5803
        ifactory = gtk_item_factory_from_widget(compose->menubar);
5804
5805
        menu_set_sensitive(ifactory, "/File/Send", sensitive);
5806
        menu_set_sensitive(ifactory, "/File/Send later", sensitive);
5807
        menu_set_sensitive(ifactory, "/File/Save to draft folder",
5808
                           sensitive);
5809
        menu_set_sensitive(ifactory, "/File/Insert file", sensitive);
5810
        menu_set_sensitive(ifactory, "/File/Insert signature", sensitive);
5811
        menu_set_sensitive(ifactory, "/File/Append signature", sensitive);
5812
        menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
5813
        menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
5814
        menu_set_sensitive(ifactory, "/Tools/Edit with external editor",
5815
                           sensitive);
5816
5817
#define SET_SENS(w) \
5818
        if (compose->w) \
5819
                gtk_widget_set_sensitive(compose->w, sensitive);
5820
5821
        SET_SENS(text);
5822
        SET_SENS(send_btn);
5823
        SET_SENS(sendl_btn);
5824
        SET_SENS(draft_btn);
5825
        SET_SENS(insert_btn);
5826
        SET_SENS(sig_btn);
5827
        SET_SENS(exteditor_btn);
5828
        SET_SENS(linewrap_btn);
5829
5830
#undef SET_SENS
5831
}
5832
5833
/**
5834
 * compose_undo_state_changed:
5835
 *
5836
 * Change the sensivity of the menuentries undo and redo
5837
 **/
5838
static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
5839
                                       gint redo_state, gpointer data)
5840
{
5841
        GtkWidget *widget = GTK_WIDGET(data);
5842
        GtkItemFactory *ifactory;
5843
5844
        g_return_if_fail(widget != NULL);
5845
5846
        ifactory = gtk_item_factory_from_widget(widget);
5847
5848
        switch (undo_state) {
5849
        case UNDO_STATE_TRUE:
5850
                if (!undostruct->undo_state) {
5851
                        debug_print ("Set_undo - Testpoint\n");
5852
                        undostruct->undo_state = TRUE;
5853
                        menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
5854
                }
5855
                break;
5856
        case UNDO_STATE_FALSE:
5857
                if (undostruct->undo_state) {
5858
                        undostruct->undo_state = FALSE;
5859
                        menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
5860
                }
5861
                break;
5862
        case UNDO_STATE_UNCHANGED:
5863
                break;
5864
        case UNDO_STATE_REFRESH:
5865
                menu_set_sensitive(ifactory, "/Edit/Undo",
5866
                                   undostruct->undo_state);
5867
                break;
5868
        default:
5869
                g_warning("Undo state not recognized");
5870
                break;
5871
        }
5872
5873
        switch (redo_state) {
5874
        case UNDO_STATE_TRUE:
5875
                if (!undostruct->redo_state) {
5876
                        undostruct->redo_state = TRUE;
5877
                        menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
5878
                }
5879
                break;
5880
        case UNDO_STATE_FALSE:
5881
                if (undostruct->redo_state) {
5882
                        undostruct->redo_state = FALSE;
5883
                        menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
5884
                }
5885
                break;
5886
        case UNDO_STATE_UNCHANGED:
5887
                break;
5888
        case UNDO_STATE_REFRESH:
5889
                menu_set_sensitive(ifactory, "/Edit/Redo",
5890
                                   undostruct->redo_state);
5891
                break;
5892
        default:
5893
                g_warning("Redo state not recognized");
5894
                break;
5895
        }
5896
}
5897
5898
static gint calc_cursor_xpos(GtkTextView *text, gint extra, gint char_width)
5899
{
5900
#if 0
5901
        gint cursor_pos;
5902
5903
        cursor_pos = (text->cursor_pos_x - extra) / char_width;
5904
        cursor_pos = MAX(cursor_pos, 0);
5905
5906
        return cursor_pos;
5907
#endif
5908
        return 0;
5909
}
5910
5911
/* callback functions */
5912
5913
/* compose_edit_size_alloc() - called when resized. don't know whether Gtk
5914
 * includes "non-client" (windows-izm) in calculation, so this calculation
5915
 * may not be accurate.
5916
 */
5917
static gboolean compose_edit_size_alloc(GtkEditable *widget,
5918
                                        GtkAllocation *allocation,
5919
                                        GtkSHRuler *shruler)
5920
{
5921
        if (prefs_common.show_ruler) {
5922
                gint char_width = 0, char_height = 0;
5923
                gint line_width_in_chars;
5924
5925
                gtkut_get_font_size(GTK_WIDGET(widget),
5926
                                    &char_width, &char_height);
5927
                line_width_in_chars =
5928
                        (allocation->width - allocation->x) / char_width;
5929
5930
                /* got the maximum */
5931
                gtk_ruler_set_range(GTK_RULER(shruler),
5932
                                    0.0, line_width_in_chars,
5933
                                    calc_cursor_xpos(GTK_TEXT_VIEW(widget),
5934
                                                     allocation->x,
5935
                                                     char_width),
5936
                                    /*line_width_in_chars*/ char_width);
5937
        }
5938
5939
        return TRUE;
5940
}
5941
5942
static void toolbar_send_cb(GtkWidget *widget, gpointer data)
5943
{
5944
        compose_send_cb(data, 0, NULL);
5945
}
5946
5947
static void toolbar_send_later_cb(GtkWidget *widget, gpointer data)
5948
{
5949
        compose_send_later_cb(data, 0, NULL);
5950
}
5951
5952
static void toolbar_draft_cb(GtkWidget *widget, gpointer data)
5953
{
5954
        compose_draft_cb(data, 0, NULL);
5955
}
5956
5957
static void toolbar_insert_cb(GtkWidget *widget, gpointer data)
5958
{
5959
        compose_insert_file_cb(data, 0, NULL);
5960
}
5961
5962
static void toolbar_attach_cb(GtkWidget *widget, gpointer data)
5963
{
5964
        compose_attach_cb(data, 0, NULL);
5965
}
5966
5967
static void toolbar_sig_cb(GtkWidget *widget, gpointer data)
5968
{
5969
        Compose *compose = (Compose *)data;
5970
5971
        compose_insert_sig(compose, TRUE, TRUE, TRUE);
5972
}
5973
5974
static void toolbar_ext_editor_cb(GtkWidget *widget, gpointer data)
5975
{
5976
        Compose *compose = (Compose *)data;
5977
5978
        compose_exec_ext_editor(compose);
5979
}
5980
5981
static void toolbar_linewrap_cb(GtkWidget *widget, gpointer data)
5982
{
5983
        Compose *compose = (Compose *)data;
5984