updated
[claws.git] / src / textview.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2002 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <gdk/gdk.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <gtk/gtkvbox.h>
30 #include <gtk/gtkscrolledwindow.h>
31 #include <gtk/gtksignal.h>
32 #include <stdio.h>
33 #include <ctype.h>
34 #include <string.h>
35 #include <stdlib.h>
36
37 #include "intl.h"
38 #include "main.h"
39 #include "summaryview.h"
40 #include "procheader.h"
41 #include "prefs_common.h"
42 #include "codeconv.h"
43 #include "gtkstext.h"
44 #include "utils.h"
45 #include "gtkutils.h"
46 #include "procmime.h"
47 #include "html.h"
48 #include "enriched.h"
49 #include "compose.h"
50 #include "addressbook.h"
51 #include "displayheader.h"
52 #include "account.h"
53
54 #define FONT_LOAD(font, s) \
55 { \
56         gchar *fontstr, *p; \
57  \
58         Xstrdup_a(fontstr, s, ); \
59         if ((p = strchr(fontstr, ',')) != NULL) *p = '\0'; \
60         font = gdk_font_load(fontstr); \
61         if (!font) \
62                 g_warning("Couldn't load the font '%s'\n", fontstr); \
63 }
64
65 typedef struct _RemoteURI       RemoteURI;
66
67 struct _RemoteURI
68 {
69         gchar *uri;
70
71         guint start;
72         guint end;
73 };
74
75 static GdkColor quote_colors[3] = {
76         {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
77         {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
78         {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
79 };
80
81 static GdkColor signature_color = {
82         (gulong)0,
83         (gushort)0x7fff,
84         (gushort)0x7fff,
85         (gushort)0x7fff
86 };
87         
88 static GdkColor uri_color = {
89         (gulong)0,
90         (gushort)0,
91         (gushort)0,
92         (gushort)0
93 };
94
95 static GdkColor emphasis_color = {
96         (gulong)0,
97         (gushort)0,
98         (gushort)0,
99         (gushort)0xcfff
100 };
101
102 #if 0
103 static GdkColor error_color = {
104         (gulong)0,
105         (gushort)0xefff,
106         (gushort)0,
107         (gushort)0
108 };
109 #endif
110
111 static GdkFont *text_sb_font;
112 static GdkFont *text_mb_font;
113 static gint text_sb_font_orig_ascent;
114 static gint text_sb_font_orig_descent;
115 static gint text_mb_font_orig_ascent;
116 static gint text_mb_font_orig_descent;
117 static GdkFont *spacingfont;
118
119 static void textview_show_ertf          (TextView       *textview,
120                                          FILE           *fp,
121                                          CodeConverter  *conv);
122 static void textview_add_part           (TextView       *textview,
123                                          MimeInfo       *mimeinfo,
124                                          FILE           *fp);
125 static void textview_add_parts          (TextView       *textview,
126                                          MimeInfo       *mimeinfo,
127                                          FILE           *fp);
128 static void textview_write_body         (TextView       *textview,
129                                          MimeInfo       *mimeinfo,
130                                          FILE           *fp,
131                                          const gchar    *charset);
132 static void textview_show_html          (TextView       *textview,
133                                          FILE           *fp,
134                                          CodeConverter  *conv);
135 static void textview_write_line         (TextView       *textview,
136                                          const gchar    *str,
137                                          CodeConverter  *conv);
138 static void textview_write_link         (TextView       *textview,
139                                          const gchar    *url,
140                                          const gchar    *str,
141                                          CodeConverter  *conv);
142 static GPtrArray *textview_scan_header  (TextView       *textview,
143                                          FILE           *fp);
144 static void textview_show_header        (TextView       *textview,
145                                          GPtrArray      *headers);
146
147 static gint textview_key_pressed        (GtkWidget      *widget,
148                                          GdkEventKey    *event,
149                                          TextView       *textview);
150 static gint textview_button_pressed     (GtkWidget      *widget,
151                                          GdkEventButton *event,
152                                          TextView       *textview);
153 static gint textview_button_released    (GtkWidget      *widget,
154                                          GdkEventButton *event,
155                                          TextView       *textview);
156
157 static void textview_uri_list_remove_all(GSList         *uri_list);
158
159 static void textview_smooth_scroll_do           (TextView       *textview,
160                                                  gfloat          old_value,
161                                                  gfloat          last_value,
162                                                  gint            step);
163 static void textview_smooth_scroll_one_line     (TextView       *textview,
164                                                  gboolean        up);
165 static gboolean textview_smooth_scroll_page     (TextView       *textview,
166                                                  gboolean        up);
167
168
169 TextView *textview_create(void)
170 {
171         TextView *textview;
172         GtkWidget *vbox;
173         GtkWidget *scrolledwin_sb;
174         GtkWidget *scrolledwin_mb;
175         GtkWidget *text_sb;
176         GtkWidget *text_mb;
177
178         debug_print("Creating text view...\n");
179         textview = g_new0(TextView, 1);
180
181         scrolledwin_sb = gtk_scrolled_window_new(NULL, NULL);
182         scrolledwin_mb = gtk_scrolled_window_new(NULL, NULL);
183         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_sb),
184                                        GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
185         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_mb),
186                                        GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
187         gtk_widget_set_usize(scrolledwin_sb, prefs_common.mainview_width, -1);
188         gtk_widget_set_usize(scrolledwin_mb, prefs_common.mainview_width, -1);
189
190         /* create GtkSText widgets for single-byte and multi-byte character */
191         text_sb = gtk_stext_new(NULL, NULL);
192         text_mb = gtk_stext_new(NULL, NULL);
193         GTK_STEXT(text_sb)->default_tab_width = 8;
194         GTK_STEXT(text_mb)->default_tab_width = 8;
195         gtk_widget_show(text_sb);
196         gtk_widget_show(text_mb);
197         gtk_stext_set_word_wrap(GTK_STEXT(text_sb), TRUE);
198         gtk_stext_set_word_wrap(GTK_STEXT(text_mb), TRUE);
199         gtk_widget_ensure_style(text_sb);
200         gtk_widget_ensure_style(text_mb);
201         if (text_sb->style && text_sb->style->font->type == GDK_FONT_FONTSET) {
202                 GtkStyle *style;
203                 GdkFont *font;
204
205                 FONT_LOAD(font, prefs_common.normalfont);
206                 if (font) {
207                         style = gtk_style_copy(text_sb->style);
208                         gdk_font_unref(style->font);
209                         style->font = font;
210                         gtk_widget_set_style(text_sb, style);
211                 }
212         }
213         if (text_mb->style && text_mb->style->font->type == GDK_FONT_FONT) {
214                 GtkStyle *style;
215                 GdkFont *font;
216
217                 font = gdk_fontset_load(prefs_common.normalfont);
218                 if (font) {
219                         style = gtk_style_copy(text_mb->style);
220                         gdk_font_unref(style->font);
221                         style->font = font;
222                         gtk_widget_set_style(text_mb, style);
223                 }
224         }
225         gtk_widget_ref(scrolledwin_sb);
226         gtk_widget_ref(scrolledwin_mb);
227
228         gtk_container_add(GTK_CONTAINER(scrolledwin_sb), text_sb);
229         gtk_container_add(GTK_CONTAINER(scrolledwin_mb), text_mb);
230         gtk_signal_connect(GTK_OBJECT(text_sb), "key_press_event",
231                            GTK_SIGNAL_FUNC(textview_key_pressed),
232                            textview);
233         gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_press_event",
234                                  GTK_SIGNAL_FUNC(textview_button_pressed),
235                                  textview);
236         gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_release_event",
237                                  GTK_SIGNAL_FUNC(textview_button_released),
238                                  textview);
239         gtk_signal_connect(GTK_OBJECT(text_mb), "key_press_event",
240                            GTK_SIGNAL_FUNC(textview_key_pressed),
241                            textview);
242         gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_press_event",
243                                  GTK_SIGNAL_FUNC(textview_button_pressed),
244                                  textview);
245         gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_release_event",
246                                  GTK_SIGNAL_FUNC(textview_button_released),
247                                  textview);
248
249         gtk_widget_show(scrolledwin_sb);
250         gtk_widget_show(scrolledwin_mb);
251
252         vbox = gtk_vbox_new(FALSE, 0);
253         gtk_box_pack_start(GTK_BOX(vbox), scrolledwin_sb, TRUE, TRUE, 0);
254
255         gtk_widget_show(vbox);
256
257         textview->vbox             = vbox;
258         textview->scrolledwin      = scrolledwin_sb;
259         textview->scrolledwin_sb   = scrolledwin_sb;
260         textview->scrolledwin_mb   = scrolledwin_mb;
261         textview->text             = text_sb;
262         textview->text_sb          = text_sb;
263         textview->text_mb          = text_mb;
264         textview->text_is_mb       = FALSE;
265         textview->uri_list         = NULL;
266         textview->body_pos         = 0;
267         textview->cur_pos          = 0;
268         textview->show_all_headers = FALSE;
269         textview->last_buttonpress = GDK_NOTHING;
270         textview->show_url_msgid   = 0;
271
272         return textview;
273 }
274
275 void textview_init(TextView *textview)
276 {
277         gtkut_widget_disable_theme_engine(textview->text_sb);
278         gtkut_widget_disable_theme_engine(textview->text_mb);
279         textview_update_message_colors();
280         textview_set_all_headers(textview, FALSE);
281         textview_set_font(textview, NULL);
282 }
283
284 void textview_update_message_colors(void)
285 {
286         GdkColor black = {0, 0, 0, 0};
287
288         if (prefs_common.enable_color) {
289                 /* grab the quote colors, converting from an int to a GdkColor */
290                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
291                                                &quote_colors[0]);
292                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
293                                                &quote_colors[1]);
294                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
295                                                &quote_colors[2]);
296                 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
297                                                &uri_color);
298                 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
299                                                &signature_color);
300         } else {
301                 quote_colors[0] = quote_colors[1] = quote_colors[2] = 
302                         uri_color = emphasis_color = signature_color = black;
303         }
304 }
305
306 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
307                            const gchar *file)
308 {
309         GtkSText *text;
310         FILE *fp;
311         const gchar *charset = NULL;
312         GPtrArray *headers = NULL;
313
314         if ((fp = fopen(file, "rb")) == NULL) {
315                 FILE_OP_ERROR(file, "fopen");
316                 return;
317         }
318
319         if (prefs_common.force_charset)
320                 charset = prefs_common.force_charset;
321         else if (mimeinfo->charset)
322                 charset = mimeinfo->charset;
323         textview_set_font(textview, charset);
324         textview_clear(textview);
325
326         text = GTK_STEXT(textview->text);
327
328         gtk_stext_freeze(text);
329
330         if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) perror("fseek");
331         headers = textview_scan_header(textview, fp);
332         if (headers) {
333                 textview_show_header(textview, headers);
334                 procheader_header_array_destroy(headers);
335                 textview->body_pos = gtk_stext_get_length(text);
336         }
337
338         textview_add_parts(textview, mimeinfo, fp);
339
340         gtk_stext_thaw(text);
341
342         fclose(fp);
343 }
344
345 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
346 {
347         GtkSText *text;
348         gchar buf[BUFFSIZE];
349         const gchar *boundary = NULL;
350         gint boundary_len = 0;
351         const gchar *charset = NULL;
352         GPtrArray *headers = NULL;
353         gboolean is_rfc822_part = FALSE;
354
355         g_return_if_fail(mimeinfo != NULL);
356         g_return_if_fail(fp != NULL);
357
358         if (mimeinfo->mime_type == MIME_MULTIPART) {
359                 textview_clear(textview);
360                 textview_add_parts(textview, mimeinfo, fp);
361                 return;
362         }
363
364         if (mimeinfo->parent && mimeinfo->parent->boundary) {
365                 boundary = mimeinfo->parent->boundary;
366                 boundary_len = strlen(boundary);
367         }
368
369         if (!boundary && (mimeinfo->mime_type == MIME_TEXT || 
370                           mimeinfo->mime_type == MIME_TEXT_HTML || 
371                           mimeinfo->mime_type == MIME_TEXT_ENRICHED)) {
372                 
373                 if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0)
374                         perror("fseek");
375                 headers = textview_scan_header(textview, fp);
376         } else {
377                 if (mimeinfo->mime_type == MIME_TEXT && mimeinfo->parent) {
378                         glong fpos;
379                         MimeInfo *parent = mimeinfo->parent;
380
381                         while (parent->parent) {
382                                 if (parent->main &&
383                                     parent->main->mime_type ==
384                                         MIME_MESSAGE_RFC822)
385                                         break;
386                                 parent = parent->parent;
387                         }
388
389                         if ((fpos = ftell(fp)) < 0)
390                                 perror("ftell");
391                         else if (fseek(fp, parent->fpos, SEEK_SET) < 0)
392                                 perror("fseek");
393                         else {
394                                 headers = textview_scan_header(textview, fp);
395                                 if (fseek(fp, fpos, SEEK_SET) < 0)
396                                         perror("fseek");
397                         }
398                 }
399                 /* skip MIME part headers */
400                 while (fgets(buf, sizeof(buf), fp) != NULL)
401                         if (buf[0] == '\r' || buf[0] == '\n') break;
402         }
403
404         /* display attached RFC822 single text message */
405         if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
406                 if (headers) procheader_header_array_destroy(headers);
407                 if (!mimeinfo->sub) {
408                         textview_clear(textview);
409                         return;
410                 }
411                 headers = textview_scan_header(textview, fp);
412                 mimeinfo = mimeinfo->sub;
413                 is_rfc822_part = TRUE;
414         }
415
416         if (prefs_common.force_charset)
417                 charset = prefs_common.force_charset;
418         else if (mimeinfo->charset)
419                 charset = mimeinfo->charset;
420         textview_set_font(textview, charset);
421
422         text = GTK_STEXT(textview->text);
423
424         gtk_stext_freeze(text);
425         textview_clear(textview);
426
427         if (headers) {
428                 textview_show_header(textview, headers);
429                 procheader_header_array_destroy(headers);
430                 textview->body_pos = gtk_stext_get_length(text);
431                 if (!mimeinfo->main)
432                         gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
433         }
434
435         if (mimeinfo->mime_type == MIME_MULTIPART || is_rfc822_part)
436                 textview_add_parts(textview, mimeinfo, fp);
437         else
438                 textview_write_body(textview, mimeinfo, fp, charset);
439
440         gtk_stext_thaw(text);
441 }
442
443 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
444 {
445         GtkSText *text = GTK_STEXT(textview->text);
446         gchar buf[BUFFSIZE];
447         const gchar *boundary = NULL;
448         gint boundary_len = 0;
449         const gchar *charset = NULL;
450         GPtrArray *headers = NULL;
451
452         g_return_if_fail(mimeinfo != NULL);
453         g_return_if_fail(fp != NULL);
454
455         if (mimeinfo->mime_type == MIME_MULTIPART) return;
456
457         if (!mimeinfo->parent &&
458             mimeinfo->mime_type != MIME_TEXT &&
459             mimeinfo->mime_type != MIME_TEXT_HTML &&
460             mimeinfo->mime_type != MIME_TEXT_ENRICHED)
461                 return;
462
463         if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) {
464                 perror("fseek");
465                 return;
466         }
467
468         if (mimeinfo->parent && mimeinfo->parent->boundary) {
469                 boundary = mimeinfo->parent->boundary;
470                 boundary_len = strlen(boundary);
471         }
472
473         while (fgets(buf, sizeof(buf), fp) != NULL)
474                 if (buf[0] == '\r' || buf[0] == '\n') break;
475
476         if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
477                 headers = textview_scan_header(textview, fp);
478                 if (headers) {
479                         gtk_stext_freeze(text);
480                         gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
481                         textview_show_header(textview, headers);
482                         procheader_header_array_destroy(headers);
483                         gtk_stext_thaw(text);
484                 }
485                 return;
486         }
487
488         gtk_stext_freeze(text);
489
490 #if USE_GPGME
491         if (mimeinfo->sigstatus)
492                 g_snprintf(buf, sizeof(buf), "\n[%s (%s)]\n",
493                            mimeinfo->content_type, mimeinfo->sigstatus);
494         else
495 #endif
496         if (mimeinfo->filename || mimeinfo->name)
497                 g_snprintf(buf, sizeof(buf), "\n[%s  %s (%d bytes)]\n",
498                            mimeinfo->filename ? mimeinfo->filename :
499                            mimeinfo->name,
500                            mimeinfo->content_type, mimeinfo->size);
501         else
502                 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
503                            mimeinfo->content_type, mimeinfo->size);
504
505         if (mimeinfo->mime_type != MIME_TEXT &&
506             mimeinfo->mime_type != MIME_TEXT_HTML &&
507             mimeinfo->mime_type != MIME_TEXT_ENRICHED) {
508                 gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
509         } else {
510                 if (!mimeinfo->main &&
511                     mimeinfo->parent &&
512                     mimeinfo->parent->children != mimeinfo)
513                         gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
514                 else
515                         gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
516                 if (prefs_common.force_charset)
517                         charset = prefs_common.force_charset;
518                 else if (mimeinfo->charset)
519                         charset = mimeinfo->charset;
520                 textview_write_body(textview, mimeinfo, fp, charset);
521         }
522         
523         gtk_stext_thaw(text);
524 }
525
526 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
527 {
528         gint level;
529
530         g_return_if_fail(mimeinfo != NULL);
531         g_return_if_fail(fp != NULL);
532
533         level = mimeinfo->level;
534
535         for (;;) {
536                 textview_add_part(textview, mimeinfo, fp);
537                 if (mimeinfo->parent && mimeinfo->parent->content_type &&
538                     !strcasecmp(mimeinfo->parent->content_type,
539                                 "multipart/alternative"))
540                         mimeinfo = mimeinfo->parent->next;
541                 else
542                         mimeinfo = procmime_mimeinfo_next(mimeinfo);
543                 if (!mimeinfo || mimeinfo->level <= level)
544                         break;
545         }
546 }
547
548 #define TEXT_INSERT(str) \
549         gtk_stext_insert(text, textview->msgfont, NULL, NULL, str, -1)
550
551 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
552 {
553         GtkSText *text;
554
555         if (!partinfo) return;
556
557         textview_set_font(textview, NULL);
558         text = GTK_STEXT(textview->text);
559         textview_clear(textview);
560         
561         gtk_stext_freeze(text);
562
563         TEXT_INSERT(_("To save this part, pop up the context menu with "));
564         TEXT_INSERT(_("right click and select `Save as...', "));
565         TEXT_INSERT(_("or press `y' key.\n\n"));
566
567         TEXT_INSERT(_("To display this part as a text message, select "));
568         TEXT_INSERT(_("`Display as text', or press `t' key.\n\n"));
569
570         TEXT_INSERT(_("To display this part as an image, select "));
571         TEXT_INSERT(_("`Display image', or press `i' key.\n\n"));
572
573         TEXT_INSERT(_("To open this part with external program, select "));
574         TEXT_INSERT(_("`Open' or `Open with...', "));
575         TEXT_INSERT(_("or double-click, or click the center button, "));
576         TEXT_INSERT(_("or press `l' key."));
577
578         gtk_stext_thaw(text);
579 }
580
581 #if USE_GPGME
582 void textview_show_signature_part(TextView *textview, MimeInfo *partinfo)
583 {
584         GtkSText *text;
585
586         if (!partinfo) return;
587
588         textview_set_font(textview, NULL);
589         text = GTK_STEXT(textview->text);
590         textview_clear(textview);
591
592         gtk_stext_freeze(text);
593
594         if (partinfo->sigstatus_full == NULL) {
595                 TEXT_INSERT(_("This signature has not been checked yet.\n"));
596                 TEXT_INSERT(_("To check it, pop up the context menu with\n"));
597                 TEXT_INSERT(_("right click and select `Check signature'.\n"));
598         } else {
599                 TEXT_INSERT(partinfo->sigstatus_full);
600         }
601                 
602         gtk_stext_thaw(text);
603 }
604 #endif /* USE_GPGME */
605
606 #undef TEXT_INSERT
607
608 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
609                                 FILE *fp, const gchar *charset)
610 {
611         FILE *tmpfp;
612         gchar buf[BUFFSIZE];
613         CodeConverter *conv;
614
615         conv = conv_code_converter_new(charset);
616
617         tmpfp = procmime_decode_content(NULL, fp, mimeinfo);
618         
619         textview->is_in_signature = FALSE;
620
621         if (tmpfp) {
622                 
623                 if (mimeinfo->mime_type == MIME_TEXT_HTML)
624                         textview_show_html(textview, tmpfp, conv);
625                 else if (mimeinfo->mime_type == MIME_TEXT_ENRICHED)
626                         textview_show_ertf(textview, tmpfp, conv);
627                 else
628                         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
629                                 textview_write_line(textview, buf, conv);
630                 fclose(tmpfp);
631         }
632
633         conv_code_converter_destroy(conv);
634 }
635
636 static void textview_show_html(TextView *textview, FILE *fp,
637                                CodeConverter *conv)
638 {
639         HTMLParser *parser;
640         gchar *str;
641         gchar* url = NULL;
642
643         parser = html_parser_new(fp, conv);
644         g_return_if_fail(parser != NULL);
645
646         while ((str = html_parse(parser)) != NULL) {
647                 if (parser->state == HTML_HREF) {
648                         /* first time : get and copy the URL */
649                         if (url == NULL) {
650                                 /* ALF - the sylpheed html parser returns an empty string,
651                                  * if still inside an <a>, but already parsed past HREF */
652                                 str = strtok(str, " ");
653                                 if (str) { 
654                                         url = strdup(str);
655                                         /* the URL may (or not) be followed by the
656                                          * referenced text */
657                                         str = strtok(NULL, "");
658                                 }       
659                         }
660                         if (str != NULL) {
661                                 textview_write_link(textview, url, str, NULL);
662                         }
663                 } else {
664                         if (url != NULL) {
665                                 free(url);
666                                 url = NULL;
667                         }
668                         textview_write_line(textview, str, NULL);
669                 }
670         }
671         
672         html_parser_destroy(parser);
673 }
674
675 static void textview_show_ertf(TextView *textview, FILE *fp,
676                                CodeConverter *conv)
677 {
678         ERTFParser *parser;
679         gchar *str;
680
681         parser = ertf_parser_new(fp, conv);
682         g_return_if_fail(parser != NULL);
683
684         while ((str = ertf_parse(parser)) != NULL) {
685                 textview_write_line(textview, str, NULL);
686         }
687         
688         ertf_parser_destroy(parser);
689 }
690
691 /* get_uri_part() - retrieves a URI starting from scanpos.
692                     Returns TRUE if succesful */
693 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
694                              const gchar **bp, const gchar **ep)
695 {
696         const gchar *ep_;
697
698         g_return_val_if_fail(start != NULL, FALSE);
699         g_return_val_if_fail(scanpos != NULL, FALSE);
700         g_return_val_if_fail(bp != NULL, FALSE);
701         g_return_val_if_fail(ep != NULL, FALSE);
702
703         *bp = scanpos;
704
705         /* find end point of URI */
706         for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
707                 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
708                         break;
709         }
710
711         /* no punctuation at end of string */
712
713         /* FIXME: this stripping of trailing punctuations may bite with other URIs.
714          * should pass some URI type to this function and decide on that whether
715          * to perform punctuation stripping */
716
717 #define IS_REAL_PUNCT(ch)       (ispunct(ch) && ((ch) != '/')) 
718
719         for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
720                 ;
721
722 #undef IS_REAL_PUNCT
723
724         *ep = ep_;
725
726         return TRUE;            
727 }
728
729 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
730 {
731         return g_strndup(bp, ep - bp);
732 }
733
734 /* valid mail address characters */
735 #define IS_RFC822_CHAR(ch) \
736         (isascii(ch) && \
737          (ch) > 32   && \
738          (ch) != 127 && \
739          !isspace(ch) && \
740          !strchr("(),;<>\"", (ch)))
741
742 /* alphabet and number within 7bit ASCII */
743 #define IS_ASCII_ALNUM(ch)      (isascii(ch) && isalnum(ch))
744 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
745
746 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
747 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
748                                const gchar **bp, const gchar **ep)
749 {
750         /* more complex than the uri part because we need to scan back and forward starting from
751          * the scan position. */
752         gboolean result = FALSE;
753         const gchar *bp_ = NULL;
754         const gchar *ep_ = NULL;
755
756         /* the informative part of the email address (describing the name
757          * of the email address owner) may contain quoted parts. the
758          * closure stack stores the last encountered quotes. */
759         gchar closure_stack[128];
760         gchar *ptr = closure_stack;
761
762         g_return_val_if_fail(start != NULL, FALSE);
763         g_return_val_if_fail(scanpos != NULL, FALSE);
764         g_return_val_if_fail(bp != NULL, FALSE);
765         g_return_val_if_fail(ep != NULL, FALSE);
766
767         /* scan start of address */
768         for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
769                 ;
770
771         /* TODO: should start with an alnum? */
772         bp_++;
773         for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
774                 ;
775
776         if (bp_ != scanpos) {
777                 /* scan end of address */
778                 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
779                         ;
780
781                 /* TODO: really should terminate with an alnum? */
782                 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
783                         ;
784                 ep_++;
785
786                 if (ep_ > scanpos + 1) {
787                         *ep = ep_;
788                         *bp = bp_;
789                         result = TRUE;
790                 }
791         }
792
793         if (!result) return FALSE;
794
795         /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
796         if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_)) 
797                 return FALSE;
798
799         /* see if this is <bracketed>; in this case we also scan for the informative part. */
800         if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
801                 return TRUE;
802
803 #define FULL_STACK()    ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
804 #define IN_STACK()      (ptr > closure_stack)
805 /* has underrun check */
806 #define POP_STACK()     if(IN_STACK()) --ptr
807 /* has overrun check */
808 #define PUSH_STACK(c)   if(!FULL_STACK()) *ptr++ = (c); else return TRUE
809 /* has underrun check */
810 #define PEEK_STACK()    (IN_STACK() ? *(ptr - 1) : 0)
811
812         ep_++;
813
814         /* scan for the informative part. */
815         for (bp_ -= 2; bp_ >= start; bp_--) {
816                 /* if closure on the stack keep scanning */
817                 if (PEEK_STACK() == *bp_) {
818                         POP_STACK();
819                         continue;
820                 }
821                 if (*bp_ == '\'' || *bp_ == '"') {
822                         PUSH_STACK(*bp_);
823                         continue;
824                 }
825
826                 /* if nothing in the closure stack, do the special conditions
827                  * the following if..else expression simply checks whether 
828                  * a token is acceptable. if not acceptable, the clause
829                  * should terminate the loop with a 'break' */
830                 if (!PEEK_STACK()) {
831                         if (*bp_ == '-'
832                         && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
833                         && (((bp_ + 1) < ep_)    && isalnum(*(bp_ + 1)))) {
834                                 /* hyphens are allowed, but only in
835                                    between alnums */
836                         } else if (!ispunct(*bp_)) {
837                                 /* but anything not being a punctiation
838                                    is ok */
839                         } else {
840                                 break; /* anything else is rejected */
841                         }
842                 }
843         }
844
845         bp_++;
846
847 #undef PEEK_STACK
848 #undef PUSH_STACK
849 #undef POP_STACK
850 #undef IN_STACK
851 #undef FULL_STACK
852
853         /* scan forward (should start with an alnum) */
854         for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
855                 ;
856
857         *ep = ep_;
858         *bp = bp_;
859
860         return result;
861 }
862
863 #undef IS_QUOTE
864 #undef IS_RFC822_CHAR
865
866 static gchar *make_email_string(const gchar *bp, const gchar *ep)
867 {
868         /* returns a mailto: URI; mailto: is also used to detect the
869          * uri type later on in the button_pressed signal handler */
870         gchar *tmp;
871         gchar *result;
872
873         tmp = g_strndup(bp, ep - bp);
874         result = g_strconcat("mailto:", tmp, NULL);
875         g_free(tmp);
876
877         return result;
878 }
879
880 #define ADD_TXT_POS(bp_, ep_, pti_) \
881         if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
882                 last = last->next; \
883                 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
884                 last->next = NULL; \
885         } else { \
886                 g_warning("alloc error scanning URIs\n"); \
887                 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
888                                 linebuf, -1); \
889                 return; \
890         }
891
892 /* textview_make_clickable_parts() - colorizes clickable parts */
893 static void textview_make_clickable_parts(TextView *textview,
894                                           GdkFont *font,
895                                           GdkColor *fg_color,
896                                           GdkColor *uri_color,
897                                           const gchar *linebuf)
898 {
899         /* parse table - in order of priority */
900         struct table {
901                 const gchar *needle; /* token */
902
903                 /* token search function */
904                 gchar    *(*search)     (const gchar *haystack,
905                                          const gchar *needle);
906                 /* part parsing function */
907                 gboolean  (*parse)      (const gchar *start,
908                                          const gchar *scanpos,
909                                          const gchar **bp_,
910                                          const gchar **ep_);
911                 /* part to URI function */
912                 gchar    *(*build_uri)  (const gchar *bp,
913                                          const gchar *ep);
914         };
915
916         static struct table parser[] = {
917                 {"http://",  strcasestr, get_uri_part,   make_uri_string},
918                 {"https://", strcasestr, get_uri_part,   make_uri_string},
919                 {"www.",     strcasestr, get_uri_part,   make_uri_string},
920                 {"ftp://",   strcasestr, get_uri_part,   make_uri_string},
921                 {"mailto:",  strcasestr, get_uri_part,   make_uri_string},
922                 {"@",        strcasestr, get_email_part, make_email_string}
923         };
924         const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
925
926         gint  n;
927         const gchar *walk, *bp, *ep;
928
929         struct txtpos {
930                 const gchar     *bp, *ep;       /* text position */
931                 gint             pti;           /* index in parse table */
932                 struct txtpos   *next;          /* next */
933         } head = {NULL, NULL, 0,  NULL}, *last = &head;
934
935         GtkSText *text = GTK_STEXT(textview->text);
936
937         /* parse for clickable parts, and build a list of begin and end positions  */
938         for (walk = linebuf, n = 0;;) {
939                 gint last_index = PARSE_ELEMS;
940                 gchar *scanpos = NULL;
941
942                 /* FIXME: this looks phony. scanning for anything in the parse table */
943                 for (n = 0; n < PARSE_ELEMS; n++) {
944                         gchar *tmp;
945
946                         tmp = parser[n].search(walk, parser[n].needle);
947                         if (tmp) {
948                                 if (scanpos == NULL || tmp < scanpos) {
949                                         scanpos = tmp;
950                                         last_index = n;
951                                 }
952                         }                                       
953                 }
954
955                 if (scanpos) {
956                         /* check if URI can be parsed */
957                         if (parser[last_index].parse(linebuf, scanpos, &bp, &ep)
958                             && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
959                                         ADD_TXT_POS(bp, ep, last_index);
960                                         walk = ep;
961                         } else
962                                 walk = scanpos +
963                                         strlen(parser[last_index].needle);
964                 } else
965                         break;
966         }
967
968         /* colorize this line */
969         if (head.next) {
970                 const gchar *normal_text = linebuf;
971
972                 /* insert URIs */
973                 for (last = head.next; last != NULL;
974                      normal_text = last->ep, last = last->next) {
975                         RemoteURI *uri;
976
977                         uri = g_new(RemoteURI, 1);
978                         if (last->bp - normal_text > 0)
979                                 gtk_stext_insert(text, font,
980                                                 fg_color, NULL,
981                                                 normal_text,
982                                                 last->bp - normal_text);
983                         uri->uri = parser[last->pti].build_uri(last->bp,
984                                                                last->ep);
985                         uri->start = gtk_stext_get_point(text);
986                         gtk_stext_insert(text, font, uri_color,
987                                         NULL, last->bp, last->ep - last->bp);
988                         uri->end = gtk_stext_get_point(text);
989                         textview->uri_list =
990                                 g_slist_append(textview->uri_list, uri);
991                 }
992
993                 if (*normal_text)
994                         gtk_stext_insert(text, font, fg_color,
995                                         NULL, normal_text, -1);
996         } else
997                 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
998 }
999
1000 #undef ADD_TXT_POS
1001
1002 /* This function writes str as a double-clickable link with the given url. */ 
1003 static void textview_write_link(TextView *textview, const gchar *url,
1004                                 const gchar *str, CodeConverter *conv)
1005 {
1006     GdkColor *link_color = NULL;
1007     RemoteURI* uri;
1008     GtkSText *text = GTK_STEXT(textview->text);
1009     gchar buf[BUFFSIZE];
1010
1011     /* this part is taken from textview_write_line. Right now the only place
1012      * that calls this function passes NULL for conv, but you never know. */
1013 #if 0
1014     if (!conv)
1015             strncpy2(buf, str, sizeof(buf));
1016     else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
1017             gtk_stext_insert(text, textview->msgfont,
1018                             prefs_common.enable_color
1019                             ? &error_color : NULL, NULL,
1020                             "*** Warning: code conversion failed ***\n",
1021                             -1);
1022             return;
1023     }
1024 #endif
1025
1026     if (!conv || conv_convert(conv, buf, sizeof(buf), str) < 0)
1027         strncpy2(buf, str, sizeof(buf));
1028
1029     strcrchomp(buf);
1030     gtk_stext_insert(text, textview->msgfont, NULL, NULL, " ", 1);
1031  
1032     /* this part is based on the code in make_clickable_parts */
1033     if (prefs_common.enable_color) {
1034         link_color = &uri_color;
1035     }
1036     uri = g_new(RemoteURI, 1);
1037     uri->uri = g_strdup(url);
1038     uri->start = gtk_stext_get_point(text);
1039     gtk_stext_insert(text, textview->msgfont, link_color, NULL, str,
1040                     strlen(str));
1041     uri->end = gtk_stext_get_point(text);
1042     textview->uri_list = g_slist_append(textview->uri_list, uri);
1043 }
1044
1045 static void textview_write_line(TextView *textview, const gchar *str,
1046                                 CodeConverter *conv)
1047 {
1048         GtkSText *text = GTK_STEXT(textview->text);
1049         gchar buf[BUFFSIZE];
1050         GdkColor *fg_color;
1051         gint quotelevel = -1;
1052
1053 #if 0
1054         if (!conv)
1055                 strncpy2(buf, str, sizeof(buf));
1056         else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
1057                 gtk_stext_insert(text, textview->msgfont,
1058                                 prefs_common.enable_color
1059                                 ? &error_color : NULL, NULL,
1060                                 "*** Warning: code conversion failed ***\n",
1061                                 -1);
1062                 return;
1063         }
1064 #endif
1065         if (!conv || conv_convert(conv, buf, sizeof(buf), str) < 0)
1066                 strncpy2(buf, str, sizeof(buf));
1067
1068         strcrchomp(buf);
1069         if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1070         fg_color = NULL;
1071
1072         /* change color of quotation
1073            >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1074            Up to 3 levels of quotations are detected, and each
1075            level is colored using a different color. */
1076         if (prefs_common.enable_color 
1077             && line_has_quote_char(buf, prefs_common.quote_chars)) {
1078                 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1079
1080                 /* set up the correct foreground color */
1081                 if (quotelevel > 2) {
1082                         /* recycle colors */
1083                         if (prefs_common.recycle_quote_colors)
1084                                 quotelevel %= 3;
1085                         else
1086                                 quotelevel = 2;
1087                 }
1088         }
1089
1090         if (quotelevel == -1)
1091                 fg_color = NULL;
1092         else
1093                 fg_color = &quote_colors[quotelevel];
1094
1095         if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1096                 fg_color = &signature_color;
1097                 textview->is_in_signature = TRUE;
1098         }
1099         
1100         if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1101                 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1102
1103         if (prefs_common.enable_color)
1104                 textview_make_clickable_parts(textview, textview->msgfont,
1105                                               fg_color, &uri_color, buf);
1106         else
1107                 textview_make_clickable_parts(textview, textview->msgfont,
1108                                               fg_color, NULL, buf);
1109 }
1110
1111 void textview_clear(TextView *textview)
1112 {
1113         GtkSText *text = GTK_STEXT(textview->text);
1114
1115         gtk_stext_freeze(text);
1116         gtk_stext_set_point(text, 0);
1117         gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1118         gtk_stext_thaw(text);
1119
1120         textview_uri_list_remove_all(textview->uri_list);
1121         textview->uri_list = NULL;
1122
1123         textview->body_pos = 0;
1124         textview->cur_pos  = 0;
1125 }
1126
1127 void textview_destroy(TextView *textview)
1128 {
1129         textview_uri_list_remove_all(textview->uri_list);
1130         textview->uri_list = NULL;
1131
1132         if (!textview->scrolledwin_sb->parent)
1133                 gtk_widget_destroy(textview->scrolledwin_sb);
1134         if (!textview->scrolledwin_mb->parent)
1135                 gtk_widget_destroy(textview->scrolledwin_mb);
1136
1137         if (textview->msgfont)
1138                 gdk_font_unref(textview->msgfont);
1139         if (textview->boldfont)
1140                 gdk_font_unref(textview->boldfont);
1141
1142         g_free(textview);
1143 }
1144
1145 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1146 {
1147         textview->show_all_headers = all_headers;
1148 }
1149
1150 void textview_set_font(TextView *textview, const gchar *codeset)
1151 {
1152         gboolean use_fontset = TRUE;
1153
1154         /* In multi-byte mode, GtkSText can't display 8bit characters
1155            correctly, so it must be single-byte mode. */
1156         if (MB_CUR_MAX > 1) {
1157                 if (codeset) {
1158                         if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1159                             !g_strcasecmp(codeset, "BALTIC"))
1160                                 use_fontset = FALSE;
1161                         else if (conv_get_current_charset() != C_EUC_JP &&
1162                                  (!g_strncasecmp(codeset, "KOI8-", 5) ||
1163                                   !g_strncasecmp(codeset, "CP", 2)    ||
1164                                   !g_strncasecmp(codeset, "WINDOWS-", 8)))
1165                                 use_fontset = FALSE;
1166                 }
1167         } else
1168                 use_fontset = FALSE;
1169
1170         if (textview->text_is_mb && !use_fontset) {
1171                 GtkWidget *parent;
1172
1173                 parent = textview->scrolledwin_mb->parent;
1174                 gtkut_container_remove(GTK_CONTAINER(parent),
1175                                        textview->scrolledwin_mb);
1176                 gtk_container_add(GTK_CONTAINER(parent),
1177                                   textview->scrolledwin_sb);
1178
1179                 textview->text = textview->text_sb;
1180                 textview->text_is_mb = FALSE;
1181         } else if (!textview->text_is_mb && use_fontset) {
1182                 GtkWidget *parent;
1183
1184                 parent = textview->scrolledwin_sb->parent;
1185                 gtkut_container_remove(GTK_CONTAINER(parent),
1186                                        textview->scrolledwin_sb);
1187                 gtk_container_add(GTK_CONTAINER(parent),
1188                                   textview->scrolledwin_mb);
1189
1190                 textview->text = textview->text_mb;
1191                 textview->text_is_mb = TRUE;
1192         }
1193
1194         if (prefs_common.textfont) {
1195                 GdkFont *font;
1196
1197                 if (use_fontset) {
1198                         if (text_mb_font) {
1199                                 text_mb_font->ascent = text_mb_font_orig_ascent;
1200                                 text_mb_font->descent = text_mb_font_orig_descent;
1201                         }
1202                         font = gdk_fontset_load(prefs_common.textfont);
1203                         if (font && text_mb_font != font) {
1204                                 if (text_mb_font)
1205                                         gdk_font_unref(text_mb_font);
1206                                 text_mb_font = font;
1207                                 text_mb_font_orig_ascent = font->ascent;
1208                                 text_mb_font_orig_descent = font->descent;
1209                         }
1210                 } else {
1211                         if (text_sb_font) {
1212                                 text_sb_font->ascent = text_sb_font_orig_ascent;
1213                                 text_sb_font->descent = text_sb_font_orig_descent;
1214                         }
1215                         if (MB_CUR_MAX > 1) {
1216                                 FONT_LOAD(font, "-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1217                         } else {
1218                                 FONT_LOAD(font, prefs_common.textfont);
1219                         }
1220                         if (font && text_sb_font != font) {
1221                                 if (text_sb_font)
1222                                         gdk_font_unref(text_sb_font);
1223                                 text_sb_font = font;
1224                                 text_sb_font_orig_ascent = font->ascent;
1225                                 text_sb_font_orig_descent = font->descent;
1226                         }
1227                 }
1228
1229                 if (font) {
1230                         gint ascent, descent;
1231
1232                         descent = prefs_common.line_space / 2;
1233                         ascent  = prefs_common.line_space - descent;
1234                         font->ascent  += ascent;
1235                         font->descent += descent;
1236
1237                         if (textview->msgfont)
1238                                 gdk_font_unref(textview->msgfont);
1239                         textview->msgfont = font;
1240                         gdk_font_ref(font);
1241                 }
1242         }
1243
1244         if (!textview->boldfont && prefs_common.boldfont)
1245                 FONT_LOAD(textview->boldfont, prefs_common.boldfont);
1246         if (!spacingfont)
1247                 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1248 }
1249
1250 enum
1251 {
1252         H_DATE          = 0,
1253         H_FROM          = 1,
1254         H_TO            = 2,
1255         H_NEWSGROUPS    = 3,
1256         H_SUBJECT       = 4,
1257         H_CC            = 5,
1258         H_REPLY_TO      = 6,
1259         H_FOLLOWUP_TO   = 7,
1260         H_X_MAILER      = 8,
1261         H_X_NEWSREADER  = 9,
1262         H_USER_AGENT    = 10,
1263         H_ORGANIZATION  = 11,
1264 };
1265
1266 void textview_set_position(TextView *textview, gint pos)
1267 {
1268         if (pos < 0) {
1269                 textview->cur_pos =
1270                         gtk_stext_get_length(GTK_STEXT(textview->text));
1271         } else {
1272                 textview->cur_pos = pos;
1273         }
1274 }
1275
1276 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1277 {
1278         gchar buf[BUFFSIZE];
1279         GPtrArray *headers, *sorted_headers;
1280         GSList *disphdr_list;
1281         Header *header;
1282         gint i;
1283
1284         g_return_val_if_fail(fp != NULL, NULL);
1285
1286         if (textview->show_all_headers)
1287                 return procheader_get_header_array_asis(fp);
1288
1289         if (!prefs_common.display_header) {
1290                 while (fgets(buf, sizeof(buf), fp) != NULL)
1291                         if (buf[0] == '\r' || buf[0] == '\n') break;
1292                 return NULL;
1293         }
1294
1295         headers = procheader_get_header_array_asis(fp);
1296
1297         sorted_headers = g_ptr_array_new();
1298
1299         for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1300              disphdr_list = disphdr_list->next) {
1301                 DisplayHeaderProp *dp =
1302                         (DisplayHeaderProp *)disphdr_list->data;
1303
1304                 for (i = 0; i < headers->len; i++) {
1305                         header = g_ptr_array_index(headers, i);
1306
1307                         if (procheader_headername_equal(header->name,
1308                                                         dp->name)) {
1309                                 if (dp->hidden)
1310                                         procheader_header_free(header);
1311                                 else
1312                                         g_ptr_array_add(sorted_headers, header);
1313
1314                                 g_ptr_array_remove_index(headers, i);
1315                                 i--;
1316                         }
1317                 }
1318         }
1319
1320         if (prefs_common.show_other_header) {
1321                 for (i = 0; i < headers->len; i++) {
1322                         header = g_ptr_array_index(headers, i);
1323                         g_ptr_array_add(sorted_headers, header);
1324                 }
1325                 g_ptr_array_free(headers, TRUE);
1326         } else
1327                 procheader_header_array_destroy(headers);
1328
1329
1330         return sorted_headers;
1331 }
1332
1333 static void textview_show_header(TextView *textview, GPtrArray *headers)
1334 {
1335         GtkSText *text = GTK_STEXT(textview->text);
1336         Header *header;
1337         gint i;
1338
1339         g_return_if_fail(headers != NULL);
1340
1341         gtk_stext_freeze(text);
1342
1343         for (i = 0; i < headers->len; i++) {
1344                 header = g_ptr_array_index(headers, i);
1345                 g_return_if_fail(header->name != NULL);
1346
1347                 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1348                                 header->name, -1);
1349                 if (header->name[strlen(header->name) - 1] != ' ')
1350                         gtk_stext_insert(text, textview->boldfont,
1351                                         NULL, NULL, " ", 1);
1352
1353                 if (procheader_headername_equal(header->name, "Subject") ||
1354                     procheader_headername_equal(header->name, "From")    ||
1355                     procheader_headername_equal(header->name, "To")      ||
1356                     procheader_headername_equal(header->name, "Cc"))
1357                         unfold_line(header->body);
1358
1359                 if (prefs_common.enable_color &&
1360                     (procheader_headername_equal(header->name, "X-Mailer") ||
1361                      procheader_headername_equal(header->name,
1362                                                  "X-Newsreader")) &&
1363                     strstr(header->body, "Sylpheed") != NULL)
1364                         gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1365                                         header->body, -1);
1366                 else if (prefs_common.enable_color) {
1367                         textview_make_clickable_parts(textview,
1368                                                       NULL, NULL, &uri_color,
1369                                                       header->body);
1370                 } else {
1371                         textview_make_clickable_parts(textview,
1372                                                       NULL, NULL, NULL,
1373                                                       header->body);
1374                 }
1375                 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1376         }
1377
1378         gtk_stext_thaw(text);
1379 }
1380
1381 gboolean textview_search_string(TextView *textview, const gchar *str,
1382                                 gboolean case_sens)
1383 {
1384         GtkSText *text = GTK_STEXT(textview->text);
1385         gint pos;
1386         wchar_t *wcs;
1387         gint len;
1388         gint text_len;
1389         gboolean found = FALSE;
1390
1391         g_return_val_if_fail(str != NULL, FALSE);
1392
1393         wcs = strdup_mbstowcs(str);
1394         g_return_val_if_fail(wcs != NULL, FALSE);
1395         len = wcslen(wcs);
1396         pos = textview->cur_pos;
1397         if (pos < textview->body_pos)
1398                 pos = textview->body_pos;
1399         text_len = gtk_stext_get_length(text);
1400         if (text_len - pos < len) {
1401                 g_free(wcs);
1402                 return FALSE;
1403         }
1404
1405         for (; pos < text_len; pos++) {
1406                 if (text_len - pos < len) break;
1407                 if (gtkut_stext_match_string(text, pos, wcs, len, case_sens)
1408                     == TRUE) {
1409                         gtk_widget_hide(GTK_WIDGET(textview->scrolledwin));
1410                         gtk_editable_set_position(GTK_EDITABLE(text),
1411                                                   pos + len);
1412                         gtk_editable_select_region(GTK_EDITABLE(text),
1413                                                    pos, pos + len);
1414                         gtk_widget_show(GTK_WIDGET(textview->scrolledwin));
1415                         textview_set_position(textview, pos + len);
1416                         found = TRUE;
1417                         break;
1418                 }
1419                 if (text_len - pos == len) break;
1420         }
1421         g_free(wcs);
1422         return found;
1423 }
1424
1425 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1426                                          gboolean case_sens)
1427 {
1428         GtkSText *text = GTK_STEXT(textview->text);
1429         gint pos;
1430         wchar_t *wcs;
1431         gint len;
1432         gint text_len;
1433         gboolean found = FALSE;
1434
1435         g_return_val_if_fail(str != NULL, FALSE);
1436
1437         wcs = strdup_mbstowcs(str);
1438         g_return_val_if_fail(wcs != NULL, FALSE);
1439         len = wcslen(wcs);
1440         pos = textview->cur_pos;
1441         text_len = gtk_stext_get_length(text);
1442         if (text_len - textview->body_pos < len) {
1443                 g_free(wcs);
1444                 return FALSE;
1445         }
1446         if (pos <= textview->body_pos || text_len - pos < len)
1447                 pos = text_len - len;
1448
1449         for (; pos >= textview->body_pos; pos--) {
1450                 if (gtkut_stext_match_string(text, pos, wcs, len, case_sens)
1451                     == TRUE) {
1452                         gtk_editable_set_position(GTK_EDITABLE(text), pos);
1453                         gtk_editable_select_region(GTK_EDITABLE(text),
1454                                                    pos, pos + len);
1455                         textview_set_position(textview, pos - 1);
1456                         found = TRUE;
1457                         break;
1458                 }
1459                 if (pos == textview->body_pos) break;
1460         }
1461
1462         g_free(wcs);
1463         return found;
1464 }
1465
1466 void textview_scroll_one_line(TextView *textview, gboolean up)
1467 {
1468         GtkSText *text = GTK_STEXT(textview->text);
1469         gfloat upper;
1470
1471         if (prefs_common.enable_smooth_scroll) {
1472                 textview_smooth_scroll_one_line(textview, up);
1473                 return;
1474         }
1475
1476         if (!up) {
1477                 upper = text->vadj->upper - text->vadj->page_size;
1478                 if (text->vadj->value < upper) {
1479                         text->vadj->value +=
1480                                 text->vadj->step_increment * 4;
1481                         text->vadj->value =
1482                                 MIN(text->vadj->value, upper);
1483                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1484                                                 "value_changed");
1485                 }
1486         } else {
1487                 if (text->vadj->value > 0.0) {
1488                         text->vadj->value -=
1489                                 text->vadj->step_increment * 4;
1490                         text->vadj->value =
1491                                 MAX(text->vadj->value, 0.0);
1492                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1493                                                 "value_changed");
1494                 }
1495         }
1496 }
1497
1498 gboolean textview_scroll_page(TextView *textview, gboolean up)
1499 {
1500         GtkSText *text = GTK_STEXT(textview->text);
1501         gfloat upper;
1502         gfloat page_incr;
1503
1504         if (prefs_common.enable_smooth_scroll)
1505                 return textview_smooth_scroll_page(textview, up);
1506
1507         if (prefs_common.scroll_halfpage)
1508                 page_incr = text->vadj->page_increment / 2;
1509         else
1510                 page_incr = text->vadj->page_increment;
1511
1512         if (!up) {
1513                 upper = text->vadj->upper - text->vadj->page_size;
1514                 if (text->vadj->value < upper) {
1515                         text->vadj->value += page_incr;
1516                         text->vadj->value = MIN(text->vadj->value, upper);
1517                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1518                                                 "value_changed");
1519                 } else
1520                         return FALSE;
1521         } else {
1522                 if (text->vadj->value > 0.0) {
1523                         text->vadj->value -= page_incr;
1524                         text->vadj->value = MAX(text->vadj->value, 0.0);
1525                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1526                                                 "value_changed");
1527                 } else
1528                         return FALSE;
1529         }
1530
1531         return TRUE;
1532 }
1533
1534 static void textview_smooth_scroll_do(TextView *textview,
1535                                       gfloat old_value, gfloat last_value,
1536                                       gint step)
1537 {
1538         GtkSText *text = GTK_STEXT(textview->text);
1539         gint change_value;
1540         gboolean up;
1541         gint i;
1542
1543         if (old_value < last_value) {
1544                 change_value = last_value - old_value;
1545                 up = FALSE;
1546         } else {
1547                 change_value = old_value - last_value;
1548                 up = TRUE;
1549         }
1550
1551         gdk_key_repeat_disable();
1552
1553         for (i = step; i <= change_value; i += step) {
1554                 text->vadj->value = old_value + (up ? -i : i);
1555                 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1556                                         "value_changed");
1557         }
1558
1559         text->vadj->value = last_value;
1560         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1561
1562         gdk_key_repeat_restore();
1563 }
1564
1565 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1566 {
1567         GtkSText *text = GTK_STEXT(textview->text);
1568         gfloat upper;
1569         gfloat old_value;
1570         gfloat last_value;
1571
1572         if (!up) {
1573                 upper = text->vadj->upper - text->vadj->page_size;
1574                 if (text->vadj->value < upper) {
1575                         old_value = text->vadj->value;
1576                         last_value = text->vadj->value +
1577                                 text->vadj->step_increment * 4;
1578                         last_value = MIN(last_value, upper);
1579
1580                         textview_smooth_scroll_do(textview, old_value,
1581                                                   last_value,
1582                                                   prefs_common.scroll_step);
1583                 }
1584         } else {
1585                 if (text->vadj->value > 0.0) {
1586                         old_value = text->vadj->value;
1587                         last_value = text->vadj->value -
1588                                 text->vadj->step_increment * 4;
1589                         last_value = MAX(last_value, 0.0);
1590
1591                         textview_smooth_scroll_do(textview, old_value,
1592                                                   last_value,
1593                                                   prefs_common.scroll_step);
1594                 }
1595         }
1596 }
1597
1598 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1599 {
1600         GtkSText *text = GTK_STEXT(textview->text);
1601         gfloat upper;
1602         gfloat page_incr;
1603         gfloat old_value;
1604         gfloat last_value;
1605
1606         if (prefs_common.scroll_halfpage)
1607                 page_incr = text->vadj->page_increment / 2;
1608         else
1609                 page_incr = text->vadj->page_increment;
1610
1611         if (!up) {
1612                 upper = text->vadj->upper - text->vadj->page_size;
1613                 if (text->vadj->value < upper) {
1614                         old_value = text->vadj->value;
1615                         last_value = text->vadj->value + page_incr;
1616                         last_value = MIN(last_value, upper);
1617
1618                         textview_smooth_scroll_do(textview, old_value,
1619                                                   last_value,
1620                                                   prefs_common.scroll_step);
1621                 } else
1622                         return FALSE;
1623         } else {
1624                 if (text->vadj->value > 0.0) {
1625                         old_value = text->vadj->value;
1626                         last_value = text->vadj->value - page_incr;
1627                         last_value = MAX(last_value, 0.0);
1628
1629                         textview_smooth_scroll_do(textview, old_value,
1630                                                   last_value,
1631                                                   prefs_common.scroll_step);
1632                 } else
1633                         return FALSE;
1634         }
1635
1636         return TRUE;
1637 }
1638
1639 #define KEY_PRESS_EVENT_STOP() \
1640         if (gtk_signal_n_emissions_by_name \
1641                 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1642                 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1643                                              "key_press_event"); \
1644         }
1645
1646 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1647                                  TextView *textview)
1648 {
1649         SummaryView *summaryview = NULL;
1650         MessageView *messageview = textview->messageview;
1651
1652         if (!event) return FALSE;
1653         if (messageview->mainwin)
1654                 summaryview = messageview->mainwin->summaryview;
1655
1656         switch (event->keyval) {
1657         case GDK_Tab:
1658         case GDK_Home:
1659         case GDK_Left:
1660         case GDK_Up:
1661         case GDK_Right:
1662         case GDK_Down:
1663         case GDK_Page_Up:
1664         case GDK_Page_Down:
1665         case GDK_End:
1666         case GDK_Control_L:
1667         case GDK_Control_R:
1668                 break;
1669         case GDK_space:
1670                 if (summaryview)
1671                         summary_pass_key_press_event(summaryview, event);
1672                 else
1673                         textview_scroll_page(textview, FALSE);
1674                 break;
1675         case GDK_BackSpace:
1676                 textview_scroll_page(textview, TRUE);
1677                 break;
1678         case GDK_Return:
1679                 textview_scroll_one_line(textview,
1680                                          (event->state & GDK_MOD1_MASK) != 0);
1681                 break;
1682         case GDK_Delete:
1683                 if (summaryview)
1684                         summary_pass_key_press_event(summaryview, event);
1685                 break;
1686         case GDK_n:
1687         case GDK_N:
1688         case GDK_p:
1689         case GDK_P:
1690         case GDK_y:
1691         case GDK_t:
1692         case GDK_l:
1693                 if (messageview->type == MVIEW_MIME &&
1694                     textview == messageview->mimeview->textview) {
1695                         KEY_PRESS_EVENT_STOP();
1696                         mimeview_pass_key_press_event(messageview->mimeview,
1697                                                       event);
1698                         break;
1699                 }
1700                 /* fall through */
1701         default:
1702                 if (summaryview &&
1703                     event->window != messageview->mainwin->window->window) {
1704                         GdkEventKey tmpev = *event;
1705
1706                         tmpev.window = messageview->mainwin->window->window;
1707                         KEY_PRESS_EVENT_STOP();
1708                         gtk_widget_event(messageview->mainwin->window,
1709                                          (GdkEvent *)&tmpev);
1710                 }
1711                 break;
1712         }
1713
1714         return TRUE;
1715 }
1716
1717 static gint show_url_timeout_cb(gpointer data)
1718 {
1719         TextView *textview = (TextView *)data;
1720         
1721         if (textview->messageview->mainwin)
1722                 if (textview->show_url_msgid)
1723                         gtk_statusbar_remove(GTK_STATUSBAR(
1724                                 textview->messageview->mainwin->statusbar),
1725                                 textview->messageview->mainwin->folderview_cid,
1726                                 textview->show_url_msgid);
1727                 return FALSE;
1728 }
1729
1730 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1731                                     TextView *textview)
1732 {
1733         if (event)
1734                 textview->last_buttonpress = event->type;
1735         return FALSE;
1736 }
1737
1738 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1739                                     TextView *textview)
1740 {
1741         textview->cur_pos = 
1742                 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1743
1744         if (event && 
1745             ((event->button == 1)
1746              || event->button == 2 || event->button == 3)) {
1747                 GSList *cur;
1748
1749                 /* double click seems to set the cursor after the current
1750                  * word. The cursor position needs fixing, otherwise the
1751                  * last word of a clickable zone will not work */
1752                 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1753                         textview->cur_pos--;
1754                 }
1755
1756                 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1757                         RemoteURI *uri = (RemoteURI *)cur->data;
1758
1759                         if (textview->cur_pos >= uri->start &&
1760                             textview->cur_pos <  uri->end) {
1761                                 /* single click: display url in statusbar */
1762                                 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1763                                         if (textview->messageview->mainwin) {
1764                                                 if (textview->show_url_msgid) {
1765                                                         gtk_timeout_remove(textview->show_url_timeout_tag);
1766                                                         gtk_statusbar_remove(GTK_STATUSBAR(
1767                                                                 textview->messageview->mainwin->statusbar),
1768                                                                 textview->messageview->mainwin->folderview_cid,
1769                                                                 textview->show_url_msgid);
1770                                                         textview->show_url_msgid = 0;
1771                                                 }
1772                                                 textview->show_url_msgid = gtk_statusbar_push(
1773                                                                 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1774                                                                 textview->messageview->mainwin->folderview_cid,
1775                                                                 uri->uri);
1776                                                 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1777                                                 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1778                                         }
1779                                 } else
1780                                 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1781                                         if (event->button == 3) {
1782                                                 gchar *fromname, *fromaddress;
1783                                                 
1784                                                 /* extract url */
1785                                                 fromaddress = g_strdup(uri->uri + 7);
1786                                                 /* Hiroyuki: please put this function in utils.c! */
1787                                                 fromname = procheader_get_fromname(fromaddress);
1788                                                 extract_address(fromaddress);
1789                                                 g_message("adding from textview %s <%s>", fromname, fromaddress);
1790                                                 /* Add to address book - Match */
1791                                                 addressbook_add_contact( fromname, fromaddress, NULL );
1792                                                 
1793                                                 g_free(fromaddress);
1794                                                 g_free(fromname);
1795                                         } else {
1796                                                 PrefsAccount *account = NULL;
1797                                                 FolderItem   *folder_item;
1798
1799                                                 if (textview->messageview && textview->messageview->mainwin 
1800                                                 &&  textview->messageview->mainwin->summaryview 
1801                                                 &&  textview->messageview->mainwin->summaryview->folder_item) {
1802                                                         folder_item = textview->messageview->mainwin->summaryview->folder_item;
1803                                                         if (folder_item->prefs && folder_item->prefs->enable_default_account)
1804                                                                 account = account_find_from_id(folder_item->prefs->default_account);
1805                                                 }
1806                                                 compose_new(account, uri->uri + 7, NULL);
1807                                         }
1808                                 } else {
1809                                         open_uri(uri->uri,
1810                                                  prefs_common.uri_cmd);
1811                                 }
1812                         }
1813                 }
1814         }
1815         if (event)
1816                 textview->last_buttonpress = event->type;
1817         return FALSE;
1818 }
1819
1820 static void textview_uri_list_remove_all(GSList *uri_list)
1821 {
1822         GSList *cur;
1823
1824         for (cur = uri_list; cur != NULL; cur = cur->next) {
1825                 if (cur->data) {
1826                         g_free(((RemoteURI *)cur->data)->uri);
1827                         g_free(cur->data);
1828                 }
1829         }
1830
1831         g_slist_free(uri_list);
1832 }