Statistics
| Revision:

root / src / compose.c @ 30

History | View | Annotate | Download (163.2 kB)

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