Statistics
| Revision:

root / src / compose.c @ 145

History | View | Annotate | Download (156.2 kB)

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