0.9.6claws72
[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 void textview_set_text(TextView *textview, const gchar *text)
1318 {
1319         GtkSText *stext;
1320
1321         g_return_if_fail(textview != NULL);
1322         g_return_if_fail(text != NULL);
1323
1324         textview_clear(textview);
1325
1326         stext = GTK_STEXT(textview->text);
1327         gtk_stext_freeze(stext);
1328         gtk_stext_insert(stext, textview->msgfont, NULL, NULL, text, strlen(text));
1329         gtk_stext_thaw(stext);
1330 }
1331
1332 enum
1333 {
1334         H_DATE          = 0,
1335         H_FROM          = 1,
1336         H_TO            = 2,
1337         H_NEWSGROUPS    = 3,
1338         H_SUBJECT       = 4,
1339         H_CC            = 5,
1340         H_REPLY_TO      = 6,
1341         H_FOLLOWUP_TO   = 7,
1342         H_X_MAILER      = 8,
1343         H_X_NEWSREADER  = 9,
1344         H_USER_AGENT    = 10,
1345         H_ORGANIZATION  = 11,
1346 };
1347
1348 void textview_set_position(TextView *textview, gint pos)
1349 {
1350         if (pos < 0) {
1351                 textview->cur_pos =
1352                         gtk_stext_get_length(GTK_STEXT(textview->text));
1353         } else {
1354                 textview->cur_pos = pos;
1355         }
1356 }
1357
1358 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1359 {
1360         gchar buf[BUFFSIZE];
1361         GPtrArray *headers, *sorted_headers;
1362         GSList *disphdr_list;
1363         Header *header;
1364         gint i;
1365
1366         g_return_val_if_fail(fp != NULL, NULL);
1367
1368         if (textview->show_all_headers)
1369                 return procheader_get_header_array_asis(fp);
1370
1371         if (!prefs_common.display_header) {
1372                 while (fgets(buf, sizeof(buf), fp) != NULL)
1373                         if (buf[0] == '\r' || buf[0] == '\n') break;
1374                 return NULL;
1375         }
1376
1377         headers = procheader_get_header_array_asis(fp);
1378
1379         sorted_headers = g_ptr_array_new();
1380
1381         for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1382              disphdr_list = disphdr_list->next) {
1383                 DisplayHeaderProp *dp =
1384                         (DisplayHeaderProp *)disphdr_list->data;
1385
1386                 for (i = 0; i < headers->len; i++) {
1387                         header = g_ptr_array_index(headers, i);
1388
1389                         if (procheader_headername_equal(header->name,
1390                                                         dp->name)) {
1391                                 if (dp->hidden)
1392                                         procheader_header_free(header);
1393                                 else
1394                                         g_ptr_array_add(sorted_headers, header);
1395
1396                                 g_ptr_array_remove_index(headers, i);
1397                                 i--;
1398                         }
1399                 }
1400         }
1401
1402         if (prefs_common.show_other_header) {
1403                 for (i = 0; i < headers->len; i++) {
1404                         header = g_ptr_array_index(headers, i);
1405                         g_ptr_array_add(sorted_headers, header);
1406                 }
1407                 g_ptr_array_free(headers, TRUE);
1408         } else
1409                 procheader_header_array_destroy(headers);
1410
1411
1412         return sorted_headers;
1413 }
1414
1415 static void textview_show_header(TextView *textview, GPtrArray *headers)
1416 {
1417         GtkSText *text = GTK_STEXT(textview->text);
1418         Header *header;
1419         gint i;
1420
1421         g_return_if_fail(headers != NULL);
1422
1423         gtk_stext_freeze(text);
1424
1425         for (i = 0; i < headers->len; i++) {
1426                 header = g_ptr_array_index(headers, i);
1427                 g_return_if_fail(header->name != NULL);
1428
1429                 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1430                                 header->name, -1);
1431                 if (header->name[strlen(header->name) - 1] != ' ')
1432                         gtk_stext_insert(text, textview->boldfont,
1433                                         NULL, NULL, " ", 1);
1434
1435                 if (procheader_headername_equal(header->name, "Subject") ||
1436                     procheader_headername_equal(header->name, "From")    ||
1437                     procheader_headername_equal(header->name, "To")      ||
1438                     procheader_headername_equal(header->name, "Cc"))
1439                         unfold_line(header->body);
1440
1441                 if (textview->text_is_mb == TRUE)
1442                         conv_unreadable_locale(header->body);
1443
1444                 if (prefs_common.enable_color &&
1445                     (procheader_headername_equal(header->name, "X-Mailer") ||
1446                      procheader_headername_equal(header->name,
1447                                                  "X-Newsreader")) &&
1448                     strstr(header->body, "Sylpheed") != NULL)
1449                         gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1450                                         header->body, -1);
1451                 else if (prefs_common.enable_color) {
1452                         textview_make_clickable_parts(textview,
1453                                                       NULL, NULL, &uri_color,
1454                                                       header->body);
1455                 } else {
1456                         textview_make_clickable_parts(textview,
1457                                                       NULL, NULL, NULL,
1458                                                       header->body);
1459                 }
1460                 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1461         }
1462
1463         gtk_stext_thaw(text);
1464 }
1465
1466 gboolean textview_search_string(TextView *textview, const gchar *str,
1467                                 gboolean case_sens)
1468 {
1469         GtkSText *text = GTK_STEXT(textview->text);
1470         gint pos;
1471         gint len;
1472
1473         g_return_val_if_fail(str != NULL, FALSE);
1474
1475         len = get_mbs_len(str);
1476         g_return_val_if_fail(len >= 0, FALSE);
1477
1478         pos = textview->cur_pos;
1479         if (pos < textview->body_pos)
1480                 pos = textview->body_pos;
1481
1482         if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1483                 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1484                 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1485                 textview_set_position(textview, pos + len);
1486                 return TRUE;
1487         }
1488
1489         return FALSE;
1490 }
1491
1492 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1493                                          gboolean case_sens)
1494 {
1495         GtkSText *text = GTK_STEXT(textview->text);
1496         gint pos;
1497         wchar_t *wcs;
1498         gint len;
1499         gint text_len;
1500         gboolean found = FALSE;
1501
1502         g_return_val_if_fail(str != NULL, FALSE);
1503
1504         wcs = strdup_mbstowcs(str);
1505         g_return_val_if_fail(wcs != NULL, FALSE);
1506         len = wcslen(wcs);
1507         pos = textview->cur_pos;
1508         text_len = gtk_stext_get_length(text);
1509         if (text_len - textview->body_pos < len) {
1510                 g_free(wcs);
1511                 return FALSE;
1512         }
1513         if (pos <= textview->body_pos || text_len - pos < len)
1514                 pos = text_len - len;
1515
1516         for (; pos >= textview->body_pos; pos--) {
1517                 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1518                     == TRUE) {
1519                         gtk_editable_set_position(GTK_EDITABLE(text), pos);
1520                         gtk_editable_select_region(GTK_EDITABLE(text),
1521                                                    pos, pos + len);
1522                         textview_set_position(textview, pos - 1);
1523                         found = TRUE;
1524                         break;
1525                 }
1526                 if (pos == textview->body_pos) break;
1527         }
1528
1529         g_free(wcs);
1530         return found;
1531 }
1532
1533 void textview_scroll_one_line(TextView *textview, gboolean up)
1534 {
1535         GtkSText *text = GTK_STEXT(textview->text);
1536         gfloat upper;
1537
1538         if (prefs_common.enable_smooth_scroll) {
1539                 textview_smooth_scroll_one_line(textview, up);
1540                 return;
1541         }
1542
1543         if (!up) {
1544                 upper = text->vadj->upper - text->vadj->page_size;
1545                 if (text->vadj->value < upper) {
1546                         text->vadj->value +=
1547                                 text->vadj->step_increment * 4;
1548                         text->vadj->value =
1549                                 MIN(text->vadj->value, upper);
1550                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1551                                                 "value_changed");
1552                 }
1553         } else {
1554                 if (text->vadj->value > 0.0) {
1555                         text->vadj->value -=
1556                                 text->vadj->step_increment * 4;
1557                         text->vadj->value =
1558                                 MAX(text->vadj->value, 0.0);
1559                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1560                                                 "value_changed");
1561                 }
1562         }
1563 }
1564
1565 gboolean textview_scroll_page(TextView *textview, gboolean up)
1566 {
1567         GtkSText *text = GTK_STEXT(textview->text);
1568         gfloat upper;
1569         gfloat page_incr;
1570
1571         if (prefs_common.enable_smooth_scroll)
1572                 return textview_smooth_scroll_page(textview, up);
1573
1574         if (prefs_common.scroll_halfpage)
1575                 page_incr = text->vadj->page_increment / 2;
1576         else
1577                 page_incr = text->vadj->page_increment;
1578
1579         if (!up) {
1580                 upper = text->vadj->upper - text->vadj->page_size;
1581                 if (text->vadj->value < upper) {
1582                         text->vadj->value += page_incr;
1583                         text->vadj->value = MIN(text->vadj->value, upper);
1584                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1585                                                 "value_changed");
1586                 } else
1587                         return FALSE;
1588         } else {
1589                 if (text->vadj->value > 0.0) {
1590                         text->vadj->value -= page_incr;
1591                         text->vadj->value = MAX(text->vadj->value, 0.0);
1592                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1593                                                 "value_changed");
1594                 } else
1595                         return FALSE;
1596         }
1597
1598         return TRUE;
1599 }
1600
1601 static void textview_smooth_scroll_do(TextView *textview,
1602                                       gfloat old_value, gfloat last_value,
1603                                       gint step)
1604 {
1605         GtkSText *text = GTK_STEXT(textview->text);
1606         gint change_value;
1607         gboolean up;
1608         gint i;
1609
1610         if (old_value < last_value) {
1611                 change_value = last_value - old_value;
1612                 up = FALSE;
1613         } else {
1614                 change_value = old_value - last_value;
1615                 up = TRUE;
1616         }
1617
1618         gdk_key_repeat_disable();
1619
1620         for (i = step; i <= change_value; i += step) {
1621                 text->vadj->value = old_value + (up ? -i : i);
1622                 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1623                                         "value_changed");
1624         }
1625
1626         text->vadj->value = last_value;
1627         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1628
1629         gdk_key_repeat_restore();
1630 }
1631
1632 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1633 {
1634         GtkSText *text = GTK_STEXT(textview->text);
1635         gfloat upper;
1636         gfloat old_value;
1637         gfloat last_value;
1638
1639         if (!up) {
1640                 upper = text->vadj->upper - text->vadj->page_size;
1641                 if (text->vadj->value < upper) {
1642                         old_value = text->vadj->value;
1643                         last_value = text->vadj->value +
1644                                 text->vadj->step_increment * 4;
1645                         last_value = MIN(last_value, upper);
1646
1647                         textview_smooth_scroll_do(textview, old_value,
1648                                                   last_value,
1649                                                   prefs_common.scroll_step);
1650                 }
1651         } else {
1652                 if (text->vadj->value > 0.0) {
1653                         old_value = text->vadj->value;
1654                         last_value = text->vadj->value -
1655                                 text->vadj->step_increment * 4;
1656                         last_value = MAX(last_value, 0.0);
1657
1658                         textview_smooth_scroll_do(textview, old_value,
1659                                                   last_value,
1660                                                   prefs_common.scroll_step);
1661                 }
1662         }
1663 }
1664
1665 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1666 {
1667         GtkSText *text = GTK_STEXT(textview->text);
1668         gfloat upper;
1669         gfloat page_incr;
1670         gfloat old_value;
1671         gfloat last_value;
1672
1673         if (prefs_common.scroll_halfpage)
1674                 page_incr = text->vadj->page_increment / 2;
1675         else
1676                 page_incr = text->vadj->page_increment;
1677
1678         if (!up) {
1679                 upper = text->vadj->upper - text->vadj->page_size;
1680                 if (text->vadj->value < upper) {
1681                         old_value = text->vadj->value;
1682                         last_value = text->vadj->value + page_incr;
1683                         last_value = MIN(last_value, upper);
1684
1685                         textview_smooth_scroll_do(textview, old_value,
1686                                                   last_value,
1687                                                   prefs_common.scroll_step);
1688                 } else
1689                         return FALSE;
1690         } else {
1691                 if (text->vadj->value > 0.0) {
1692                         old_value = text->vadj->value;
1693                         last_value = text->vadj->value - page_incr;
1694                         last_value = MAX(last_value, 0.0);
1695
1696                         textview_smooth_scroll_do(textview, old_value,
1697                                                   last_value,
1698                                                   prefs_common.scroll_step);
1699                 } else
1700                         return FALSE;
1701         }
1702
1703         return TRUE;
1704 }
1705
1706 #define KEY_PRESS_EVENT_STOP() \
1707         if (gtk_signal_n_emissions_by_name \
1708                 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1709                 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1710                                              "key_press_event"); \
1711         }
1712
1713 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1714                                  TextView *textview)
1715 {
1716         SummaryView *summaryview = NULL;
1717         MessageView *messageview = textview->messageview;
1718
1719         if (!event) return FALSE;
1720         if (messageview->mainwin)
1721                 summaryview = messageview->mainwin->summaryview;
1722
1723         switch (event->keyval) {
1724         case GDK_Tab:
1725         case GDK_Home:
1726         case GDK_Left:
1727         case GDK_Up:
1728         case GDK_Right:
1729         case GDK_Down:
1730         case GDK_Page_Up:
1731         case GDK_Page_Down:
1732         case GDK_End:
1733         case GDK_Control_L:
1734         case GDK_Control_R:
1735                 break;
1736         case GDK_space:
1737                 if (summaryview)
1738                         summary_pass_key_press_event(summaryview, event);
1739                 else
1740                         textview_scroll_page(textview, FALSE);
1741                 break;
1742         case GDK_BackSpace:
1743                 textview_scroll_page(textview, TRUE);
1744                 break;
1745         case GDK_Return:
1746                 textview_scroll_one_line(textview,
1747                                          (event->state & GDK_MOD1_MASK) != 0);
1748                 break;
1749         case GDK_Delete:
1750                 if (summaryview)
1751                         summary_pass_key_press_event(summaryview, event);
1752                 break;
1753         case GDK_y:
1754         case GDK_t:
1755         case GDK_l:
1756                 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1757                         KEY_PRESS_EVENT_STOP();
1758                         mimeview_pass_key_press_event(messageview->mimeview,
1759                                                       event);
1760                         break;
1761                 }
1762                 /* possible fall through */
1763         default:
1764                 if (summaryview &&
1765                     event->window != messageview->mainwin->window->window) {
1766                         GdkEventKey tmpev = *event;
1767
1768                         tmpev.window = messageview->mainwin->window->window;
1769                         KEY_PRESS_EVENT_STOP();
1770                         gtk_widget_event(messageview->mainwin->window,
1771                                          (GdkEvent *)&tmpev);
1772                 }
1773                 break;
1774         }
1775
1776         return TRUE;
1777 }
1778
1779 static gint show_url_timeout_cb(gpointer data)
1780 {
1781         TextView *textview = (TextView *)data;
1782         
1783         if (textview->messageview->mainwin)
1784                 if (textview->show_url_msgid)
1785                         gtk_statusbar_remove(GTK_STATUSBAR(
1786                                 textview->messageview->mainwin->statusbar),
1787                                 textview->messageview->mainwin->folderview_cid,
1788                                 textview->show_url_msgid);
1789                 return FALSE;
1790 }
1791
1792 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1793                                     TextView *textview)
1794 {
1795         if (event)
1796                 textview->last_buttonpress = event->type;
1797         return FALSE;
1798 }
1799
1800 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1801                                     TextView *textview)
1802 {
1803         textview->cur_pos = 
1804                 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1805
1806         if (event && 
1807             ((event->button == 1)
1808              || event->button == 2 || event->button == 3)) {
1809                 GSList *cur;
1810
1811                 /* double click seems to set the cursor after the current
1812                  * word. The cursor position needs fixing, otherwise the
1813                  * last word of a clickable zone will not work */
1814                 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1815                         textview->cur_pos--;
1816                 }
1817
1818                 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1819                         RemoteURI *uri = (RemoteURI *)cur->data;
1820
1821                         if (textview->cur_pos >= uri->start &&
1822                             textview->cur_pos <= uri->end) {
1823                                 gchar *trimmed_uri;
1824                                 
1825                                 trimmed_uri = trim_string(uri->uri, 60);
1826                                 /* single click: display url in statusbar */
1827                                 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1828                                         if (textview->messageview->mainwin) {
1829                                                 if (textview->show_url_msgid) {
1830                                                         gtk_timeout_remove(textview->show_url_timeout_tag);
1831                                                         gtk_statusbar_remove(GTK_STATUSBAR(
1832                                                                 textview->messageview->mainwin->statusbar),
1833                                                                 textview->messageview->mainwin->folderview_cid,
1834                                                                 textview->show_url_msgid);
1835                                                         textview->show_url_msgid = 0;
1836                                                 }
1837                                                 textview->show_url_msgid = gtk_statusbar_push(
1838                                                                 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1839                                                                 textview->messageview->mainwin->folderview_cid,
1840                                                                 trimmed_uri);
1841                                                 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1842                                                 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1843                                         }
1844                                 } else
1845                                 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1846                                         if (event->button == 3) {
1847                                                 gchar *fromname, *fromaddress;
1848                                                 
1849                                                 /* extract url */
1850                                                 fromaddress = g_strdup(uri->uri + 7);
1851                                                 /* Hiroyuki: please put this function in utils.c! */
1852                                                 fromname = procheader_get_fromname(fromaddress);
1853                                                 extract_address(fromaddress);
1854                                                 g_message("adding from textview %s <%s>", fromname, fromaddress);
1855                                                 /* Add to address book - Match */
1856                                                 addressbook_add_contact( fromname, fromaddress, NULL );
1857                                                 
1858                                                 g_free(fromaddress);
1859                                                 g_free(fromname);
1860                                         } else {
1861                                                 PrefsAccount *account = NULL;
1862                                                 FolderItem   *folder_item;
1863
1864                                                 if (textview->messageview && textview->messageview->mainwin 
1865                                                 &&  textview->messageview->mainwin->summaryview 
1866                                                 &&  textview->messageview->mainwin->summaryview->folder_item) {
1867                                                         folder_item = textview->messageview->mainwin->summaryview->folder_item;
1868                                                         if (folder_item->prefs && folder_item->prefs->enable_default_account)
1869                                                                 account = account_find_from_id(folder_item->prefs->default_account);
1870                                                 }
1871                                                 compose_new(account, uri->uri + 7, NULL);
1872                                         }
1873                                 } else {
1874                                         open_uri(uri->uri,
1875                                                  prefs_common.uri_cmd);
1876                                 }
1877                                 g_free(trimmed_uri);
1878                         }
1879                 }
1880         }
1881         if (event)
1882                 textview->last_buttonpress = event->type;
1883         return FALSE;
1884 }
1885
1886 static void textview_uri_list_remove_all(GSList *uri_list)
1887 {
1888         GSList *cur;
1889
1890         for (cur = uri_list; cur != NULL; cur = cur->next) {
1891                 if (cur->data) {
1892                         g_free(((RemoteURI *)cur->data)->uri);
1893                         g_free(cur->data);
1894                 }
1895         }
1896
1897         g_slist_free(uri_list);
1898 }