Statistics
| Revision:

root / src / compose.c @ 744

History | View | Annotate | Download (173.4 kB)

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