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