Statistics
| Revision:

root / src / compose.c @ 489

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