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