Statistics
| Revision:

root / src / compose.c @ 1029

History | View | Annotate | Download (180.3 kB)

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