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