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