Statistics
| Revision:

root / src / compose.c @ 609

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