Statistics
| Revision:

root / src / compose.c @ 106

History | View | Annotate | Download (160.7 kB)

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