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