Statistics
| Revision:

root / src / compose.c @ 128

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