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