sync with HEAD
[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         gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1356 }
1357
1358 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1359 {
1360         gchar buf[BUFFSIZE];
1361         GPtrArray *headers, *sorted_headers;
1362         GSList *disphdr_list;
1363         Header *header;
1364         gint i;
1365
1366         g_return_val_if_fail(fp != NULL, NULL);
1367
1368         if (textview->show_all_headers)
1369                 return procheader_get_header_array_asis(fp);
1370
1371         if (!prefs_common.display_header) {
1372                 while (fgets(buf, sizeof(buf), fp) != NULL)
1373                         if (buf[0] == '\r' || buf[0] == '\n') break;
1374                 return NULL;
1375         }
1376
1377         headers = procheader_get_header_array_asis(fp);
1378
1379         sorted_headers = g_ptr_array_new();
1380
1381         for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1382              disphdr_list = disphdr_list->next) {
1383                 DisplayHeaderProp *dp =
1384                         (DisplayHeaderProp *)disphdr_list->data;
1385
1386                 for (i = 0; i < headers->len; i++) {
1387                         header = g_ptr_array_index(headers, i);
1388
1389                         if (procheader_headername_equal(header->name,
1390                                                         dp->name)) {
1391                                 if (dp->hidden)
1392                                         procheader_header_free(header);
1393                                 else
1394                                         g_ptr_array_add(sorted_headers, header);
1395
1396                                 g_ptr_array_remove_index(headers, i);
1397                                 i--;
1398                         }
1399                 }
1400         }
1401
1402         if (prefs_common.show_other_header) {
1403                 for (i = 0; i < headers->len; i++) {
1404                         header = g_ptr_array_index(headers, i);
1405                         g_ptr_array_add(sorted_headers, header);
1406                 }
1407                 g_ptr_array_free(headers, TRUE);
1408         } else
1409                 procheader_header_array_destroy(headers);
1410
1411
1412         return sorted_headers;
1413 }
1414
1415 static void textview_show_header(TextView *textview, GPtrArray *headers)
1416 {
1417         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1418         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1419         GtkTextIter iter;
1420         Header *header;
1421         gint i;
1422
1423         g_return_if_fail(headers != NULL);
1424
1425         for (i = 0; i < headers->len; i++) {
1426                 header = g_ptr_array_index(headers, i);
1427                 g_return_if_fail(header->name != NULL);
1428
1429                 gtk_text_buffer_get_end_iter (buffer, &iter);
1430                 gtk_text_buffer_insert_with_tags_by_name
1431                         (buffer, &iter, header->name, -1,
1432                          "header_title", "header", NULL);
1433                 if (header->name[strlen(header->name) - 1] != ' ')
1434                 gtk_text_buffer_insert_with_tags_by_name
1435                                 (buffer, &iter, " ", 1,
1436                                  "header_title", "header", NULL);
1437
1438                 if (procheader_headername_equal(header->name, "Subject") ||
1439                     procheader_headername_equal(header->name, "From")    ||
1440                     procheader_headername_equal(header->name, "To")      ||
1441                     procheader_headername_equal(header->name, "Cc"))
1442                         unfold_line(header->body);
1443
1444 #warning FIXME_GTK2
1445 #if 0
1446                 if (textview->text_is_mb == TRUE)
1447                         conv_unreadable_locale(header->body);
1448 #endif
1449
1450                 if (prefs_common.enable_color &&
1451                     (procheader_headername_equal(header->name, "X-Mailer") ||
1452                      procheader_headername_equal(header->name,
1453                                                  "X-Newsreader")) &&
1454                     strstr(header->body, "Sylpheed") != NULL) {
1455                         gtk_text_buffer_get_end_iter (buffer, &iter);
1456                         gtk_text_buffer_insert_with_tags_by_name
1457                                 (buffer, &iter, header->body, -1,
1458                                  "header", "emphasis", NULL);
1459                 } else if (prefs_common.enable_color) {
1460                         textview_make_clickable_parts(textview, "header", "link",
1461                                                       header->body);
1462                 } else {
1463                         textview_make_clickable_parts(textview, "header", NULL,
1464                                                       header->body);
1465                 }
1466                 gtk_text_buffer_get_end_iter (buffer, &iter);
1467                 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1468                                                          "header", NULL);
1469         }
1470 }
1471
1472 gboolean textview_search_string(TextView *textview, const gchar *str,
1473                                 gboolean case_sens)
1474 {
1475 #warning FIXME_GTK2 /* currently, these search functions ignores case_sens */
1476         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1477         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1478         GtkTextMark *mark;
1479         GtkTextIter iter, start, end, *pos;
1480         gboolean found;
1481         gint insert_offset, selbound_offset;
1482
1483         /* reset selection */
1484         mark = gtk_text_buffer_get_mark(buffer, "insert");
1485         gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1486         insert_offset = gtk_text_iter_get_offset(&start);
1487         mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1488         gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1489         selbound_offset = gtk_text_iter_get_offset(&end);
1490
1491         pos = insert_offset > selbound_offset ? &start : &end;
1492         gtk_text_buffer_place_cursor(buffer, pos);
1493
1494         /* search */
1495         mark = gtk_text_buffer_get_insert(buffer);
1496         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1497         found = gtk_text_iter_forward_search(&iter, str,
1498                                              GTK_TEXT_SEARCH_VISIBLE_ONLY,
1499                                              &start, &end, NULL);
1500         if (found) {
1501                 gtk_text_buffer_place_cursor(buffer, &start);
1502                 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", &end);
1503                 mark = gtk_text_buffer_get_mark(buffer, "insert");
1504                 gtk_text_view_scroll_mark_onscreen(text, mark);
1505         }
1506
1507         return found;
1508 }
1509
1510 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1511                                          gboolean case_sens)
1512 {
1513 #warning FIXME_GTK2
1514         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1515         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1516         GtkTextMark *mark;
1517         GtkTextIter iter, start, end, *pos;
1518         gboolean found;
1519         gint insert_offset, selbound_offset;
1520
1521         /* reset selection */
1522         mark = gtk_text_buffer_get_mark(buffer, "insert");
1523         gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1524         insert_offset = gtk_text_iter_get_offset(&start);
1525         mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1526         gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1527         selbound_offset = gtk_text_iter_get_offset(&end);
1528
1529         pos = insert_offset < selbound_offset ? &start : &end;
1530         gtk_text_buffer_place_cursor(buffer, pos);
1531
1532         /* search */
1533         mark = gtk_text_buffer_get_insert(buffer);
1534         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1535         found = gtk_text_iter_backward_search(&iter, str,
1536                                               GTK_TEXT_SEARCH_VISIBLE_ONLY,
1537                                               &start, &end, NULL);
1538         if (found) {
1539                 gtk_text_buffer_place_cursor(buffer, &end);
1540                 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", &start);
1541                 mark = gtk_text_buffer_get_mark(buffer, "insert");
1542                 gtk_text_view_scroll_mark_onscreen(text, mark);
1543         }
1544
1545         return found;
1546 }
1547
1548 void textview_scroll_one_line(TextView *textview, gboolean up)
1549 {
1550         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1551         GtkAdjustment *vadj = text->vadjustment;
1552         gfloat upper;
1553
1554         if (prefs_common.enable_smooth_scroll) {
1555                 textview_smooth_scroll_one_line(textview, up);
1556                 return;
1557         }
1558
1559         if (!up) {
1560                 upper = vadj->upper - vadj->page_size;
1561                 if (vadj->value < upper) {
1562                         vadj->value +=
1563                                 vadj->step_increment * 4;
1564                         vadj->value =
1565                                 MIN(vadj->value, upper);
1566                         g_signal_emit_by_name(G_OBJECT(vadj),
1567                                               "value_changed", 0);
1568                 }
1569         } else {
1570                 if (vadj->value > 0.0) {
1571                         vadj->value -=
1572                                 vadj->step_increment * 4;
1573                         vadj->value =
1574                                 MAX(vadj->value, 0.0);
1575                         g_signal_emit_by_name(G_OBJECT(vadj),
1576                                               "value_changed", 0);
1577                 }
1578         }
1579 }
1580
1581 gboolean textview_scroll_page(TextView *textview, gboolean up)
1582 {
1583         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1584         GtkAdjustment *vadj = text->vadjustment;
1585         gfloat upper;
1586         gfloat page_incr;
1587
1588         if (prefs_common.enable_smooth_scroll)
1589                 return textview_smooth_scroll_page(textview, up);
1590
1591         if (prefs_common.scroll_halfpage)
1592                 page_incr = vadj->page_increment / 2;
1593         else
1594                 page_incr = vadj->page_increment;
1595
1596         if (!up) {
1597                 upper = vadj->upper - vadj->page_size;
1598                 if (vadj->value < upper) {
1599                         vadj->value += page_incr;
1600                         vadj->value = MIN(vadj->value, upper);
1601                         g_signal_emit_by_name(G_OBJECT(vadj),
1602                                               "value_changed", 0);
1603                 } else
1604                         return FALSE;
1605         } else {
1606                 if (vadj->value > 0.0) {
1607                         vadj->value -= page_incr;
1608                         vadj->value = MAX(vadj->value, 0.0);
1609                         g_signal_emit_by_name(G_OBJECT(vadj),
1610                                               "value_changed", 0);
1611                 } else
1612                         return FALSE;
1613         }
1614
1615         return TRUE;
1616 }
1617
1618 static void textview_smooth_scroll_do(TextView *textview,
1619                                       gfloat old_value, gfloat last_value,
1620                                       gint step)
1621 {
1622         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1623         GtkAdjustment *vadj = text->vadjustment;
1624         gint change_value;
1625         gboolean up;
1626         gint i;
1627
1628         if (old_value < last_value) {
1629                 change_value = last_value - old_value;
1630                 up = FALSE;
1631         } else {
1632                 change_value = old_value - last_value;
1633                 up = TRUE;
1634         }
1635
1636         for (i = step; i <= change_value; i += step) {
1637                 vadj->value = old_value + (up ? -i : i);
1638                 g_signal_emit_by_name(G_OBJECT(vadj),
1639                                       "value_changed", 0);
1640         }
1641
1642         vadj->value = last_value;
1643         g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1644 }
1645
1646 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1647 {
1648         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1649         GtkAdjustment *vadj = text->vadjustment;
1650         gfloat upper;
1651         gfloat old_value;
1652         gfloat last_value;
1653
1654         if (!up) {
1655                 upper = vadj->upper - vadj->page_size;
1656                 if (vadj->value < upper) {
1657                         old_value = vadj->value;
1658                         last_value = vadj->value +
1659                                 vadj->step_increment * 4;
1660                         last_value = MIN(last_value, upper);
1661
1662                         textview_smooth_scroll_do(textview, old_value,
1663                                                   last_value,
1664                                                   prefs_common.scroll_step);
1665                 }
1666         } else {
1667                 if (vadj->value > 0.0) {
1668                         old_value = vadj->value;
1669                         last_value = vadj->value -
1670                                 vadj->step_increment * 4;
1671                         last_value = MAX(last_value, 0.0);
1672
1673                         textview_smooth_scroll_do(textview, old_value,
1674                                                   last_value,
1675                                                   prefs_common.scroll_step);
1676                 }
1677         }
1678 }
1679
1680 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1681 {
1682         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1683         GtkAdjustment *vadj = text->vadjustment;
1684         gfloat upper;
1685         gfloat page_incr;
1686         gfloat old_value;
1687         gfloat last_value;
1688
1689         if (prefs_common.scroll_halfpage)
1690                 page_incr = vadj->page_increment / 2;
1691         else
1692                 page_incr = vadj->page_increment;
1693
1694         if (!up) {
1695                 upper = vadj->upper - vadj->page_size;
1696                 if (vadj->value < upper) {
1697                         old_value = vadj->value;
1698                         last_value = vadj->value + page_incr;
1699                         last_value = MIN(last_value, upper);
1700
1701                         textview_smooth_scroll_do(textview, old_value,
1702                                                   last_value,
1703                                                   prefs_common.scroll_step);
1704                 } else
1705                         return FALSE;
1706         } else {
1707                 if (vadj->value > 0.0) {
1708                         old_value = vadj->value;
1709                         last_value = vadj->value - page_incr;
1710                         last_value = MAX(last_value, 0.0);
1711
1712                         textview_smooth_scroll_do(textview, old_value,
1713                                                   last_value,
1714                                                   prefs_common.scroll_step);
1715                 } else
1716                         return FALSE;
1717         }
1718
1719         return TRUE;
1720 }
1721
1722 #define KEY_PRESS_EVENT_STOP() \
1723         g_signal_stop_emission_by_name(G_OBJECT(widget), \
1724                                        "key_press_event");
1725
1726 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1727                                  TextView *textview)
1728 {
1729         SummaryView *summaryview = NULL;
1730         MessageView *messageview = textview->messageview;
1731
1732         if (!event) return FALSE;
1733         if (messageview->mainwin)
1734                 summaryview = messageview->mainwin->summaryview;
1735
1736         switch (event->keyval) {
1737         case GDK_Tab:
1738         case GDK_Home:
1739         case GDK_Left:
1740         case GDK_Up:
1741         case GDK_Right:
1742         case GDK_Down:
1743         case GDK_Page_Up:
1744         case GDK_Page_Down:
1745         case GDK_End:
1746         case GDK_Control_L:
1747         case GDK_Control_R:
1748                 break;
1749         case GDK_space:
1750                 if (summaryview)
1751                         summary_pass_key_press_event(summaryview, event);
1752                 else
1753                         textview_scroll_page(textview, FALSE);
1754                 break;
1755         case GDK_BackSpace:
1756                 textview_scroll_page(textview, TRUE);
1757                 break;
1758         case GDK_Return:
1759                 textview_scroll_one_line(textview,
1760                                          (event->state & GDK_MOD1_MASK) != 0);
1761                 break;
1762         case GDK_Delete:
1763                 if (summaryview)
1764                         summary_pass_key_press_event(summaryview, event);
1765                 break;
1766         case GDK_y:
1767         case GDK_t:
1768         case GDK_l:
1769                 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1770                         KEY_PRESS_EVENT_STOP();
1771                         mimeview_pass_key_press_event(messageview->mimeview,
1772                                                       event);
1773                         break;
1774                 }
1775                 /* possible fall through */
1776         default:
1777                 if (summaryview &&
1778                     event->window != messageview->mainwin->window->window) {
1779                         GdkEventKey tmpev = *event;
1780
1781                         tmpev.window = messageview->mainwin->window->window;
1782                         KEY_PRESS_EVENT_STOP();
1783                         gtk_widget_event(messageview->mainwin->window,
1784                                          (GdkEvent *)&tmpev);
1785                 }
1786                 break;
1787         }
1788
1789         return TRUE;
1790 }
1791
1792 static gint show_url_timeout_cb(gpointer data)
1793 {
1794         TextView *textview = (TextView *)data;
1795         
1796         TEXTVIEW_STATUSBAR_POP(textview);
1797         textview->show_url_timeout_tag = 0;
1798         return FALSE;
1799 }
1800
1801 /*!
1802  *\brief    Check to see if a web URL has been disguised as a different
1803  *          URL (possible with HTML email).
1804  *
1805  *\param    uri The uri to check
1806  *
1807  *\param    textview The TextView the URL is contained in
1808  *
1809  *\return   gboolean TRUE if the URL is ok, or if the user chose to open
1810  *          it anyway, otherwise FALSE          
1811  */
1812 static gboolean uri_security_check(RemoteURI *uri, TextView *textview) 
1813 {
1814         gchar *clicked_str;
1815         gboolean retval = TRUE;
1816
1817         if (g_strncasecmp(uri->uri, "http:", 5) &&
1818             g_strncasecmp(uri->uri, "https:", 6) &&
1819             g_strncasecmp(uri->uri, "www.", 4)) 
1820                 return retval;
1821
1822         clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1823                                              uri->start,
1824                                              uri->end);
1825         if (clicked_str == NULL)
1826                 return TRUE;
1827
1828         if (strcmp(clicked_str, uri->uri) &&
1829             (!g_strncasecmp(clicked_str, "http:",  5) ||
1830              !g_strncasecmp(clicked_str, "https:", 6) ||
1831              !g_strncasecmp(clicked_str, "www.",   4))) {
1832                 gchar *str;
1833                 retval = FALSE;
1834
1835                 /* allow uri->uri    == http://somewhere.com
1836                    and   clicked_str ==        somewhere.com */
1837                 str = g_strconcat("http://", clicked_str, NULL);
1838
1839                 if (!g_strcasecmp(str, uri->uri))
1840                         retval = TRUE;
1841                 g_free(str);
1842         }
1843
1844         if (retval == FALSE) {
1845                 gchar *msg = NULL;
1846                 AlertValue resp;
1847
1848                 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1849                                         "the apparent URL (%s).  \n"
1850                                         "Open it anyway?"),
1851                                         uri->uri, clicked_str);
1852                 resp = alertpanel_with_type(_("Warning"), 
1853                                   msg,
1854                                   _("Yes"), 
1855                                   _("No"),
1856                                   NULL, NULL, ALERT_WARNING);
1857                 g_free(msg);
1858                 if (resp == G_ALERTDEFAULT)
1859                         retval = TRUE;
1860         } 
1861         g_free(clicked_str);
1862         return retval;
1863 }
1864
1865 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
1866                                             GdkEvent *event, GtkTextIter *iter,
1867                                             TextView *textview)
1868 {
1869         GtkTextIter start_iter, end_iter;
1870         gint start_pos, end_pos;
1871         GdkEventButton *bevent;
1872         GSList *cur;
1873         gchar *trimmed_uri;
1874         if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
1875                 && event->type != GDK_MOTION_NOTIFY)
1876                 return FALSE;
1877
1878         bevent = (GdkEventButton *) event;
1879
1880         /* get start and end positions */
1881         start_iter = *iter;
1882         if(!gtk_text_iter_backward_to_tag_toggle(&start_iter, tag)) {
1883                 debug_print("Can't find start.");
1884                 return FALSE;
1885         }
1886         start_pos = gtk_text_iter_get_offset(&start_iter);
1887
1888         end_iter = *iter;
1889         if(!gtk_text_iter_forward_to_tag_toggle(&end_iter, tag)) {
1890                 debug_print("Can't find end");
1891                 return FALSE;
1892         }
1893         end_pos = gtk_text_iter_get_offset(&end_iter);
1894
1895         /* search current uri */
1896         for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1897                 RemoteURI *uri = (RemoteURI *)cur->data;
1898
1899                 if (start_pos != uri->start || end_pos !=  uri->end)
1900                         continue;
1901
1902                 trimmed_uri = trim_string(uri->uri, 60);
1903                 /* hover or single click: display url in statusbar */
1904
1905                 if (event->type == GDK_MOTION_NOTIFY
1906                     || (event->type == GDK_BUTTON_PRESS && bevent->button == 1)) {
1907                         if (textview->messageview->mainwin) {
1908                                 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1909                                 textview->show_url_timeout_tag = gtk_timeout_add
1910                                         (4000, show_url_timeout_cb, textview);
1911                         }
1912                         return FALSE;
1913                 }
1914                 /* doubleclick: open compose / add address / browser */
1915                 if ((event->type == GDK_2BUTTON_PRESS && bevent->button == 1) ||
1916                         bevent->button == 2 || bevent->button == 3) {
1917                         if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1918                                 if (bevent->button == 3) {
1919                                         gchar *fromname, *fromaddress;
1920                                                 
1921                                         /* extract url */
1922                                         fromaddress = g_strdup(uri->uri + 7);
1923                                         /* Hiroyuki: please put this function in utils.c! */
1924                                         fromname = procheader_get_fromname(fromaddress);
1925                                         extract_address(fromaddress);
1926                                         g_message("adding from textview %s <%s>", fromname, fromaddress);
1927                                         /* Add to address book - Match */
1928                                         addressbook_add_contact( fromname, fromaddress, NULL );
1929                                                 
1930                                         g_free(fromaddress);
1931                                         g_free(fromname);
1932                                 } else {
1933                                         PrefsAccount *account = NULL;
1934
1935                                         if (textview->messageview && textview->messageview->msginfo &&
1936                                             textview->messageview->msginfo->folder) {
1937                                                 FolderItem   *folder_item;
1938
1939                                                 folder_item = textview->messageview->msginfo->folder;
1940                                                 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1941                                                         account = account_find_from_id(folder_item->prefs->default_account);
1942                                         }
1943                                         compose_new(account, uri->uri + 7, NULL);
1944                                 }
1945                                 return TRUE;
1946                         } else {
1947                                 if (textview_uri_security_check(textview, uri) == TRUE) 
1948                                         open_uri(uri->uri,
1949                                                  prefs_common.uri_cmd);
1950                                 return TRUE;
1951                         }
1952                 }
1953                 g_free(trimmed_uri);
1954         }
1955
1956         return FALSE;
1957 }
1958
1959 /*!
1960  *\brief    Check to see if a web URL has been disguised as a different
1961  *          URL (possible with HTML email).
1962  *
1963  *\param    uri The uri to check
1964  *
1965  *\param    textview The TextView the URL is contained in
1966  *
1967  *\return   gboolean TRUE if the URL is ok, or if the user chose to open
1968  *          it anyway, otherwise FALSE          
1969  */
1970 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
1971 {
1972         gchar *visible_str;
1973         gboolean retval = TRUE;
1974
1975         if (is_uri_string(uri->uri) == FALSE)
1976                 return TRUE;
1977
1978         visible_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1979                                              uri->start, uri->end);
1980         if (visible_str == NULL)
1981                 return TRUE;
1982
1983         if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
1984                 gchar *uri_path;
1985                 gchar *visible_uri_path;
1986
1987                 uri_path = get_uri_path(uri->uri);
1988                 visible_uri_path = get_uri_path(visible_str);
1989                 if (strcmp(uri_path, visible_uri_path) != 0)
1990                         retval = FALSE;
1991         }
1992
1993         if (retval == FALSE) {
1994                 gchar *msg;
1995                 AlertValue aval;
1996
1997                 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1998                                         "the apparent URL (%s).\n"
1999                                         "Open it anyway?"),
2000                                       uri->uri, visible_str);
2001                 aval = alertpanel_with_type(_("Warning"), msg, _("Yes"), _("No"), NULL,
2002                                             NULL, ALERT_WARNING);
2003                 g_free(msg);
2004                 if (aval == G_ALERTDEFAULT)
2005                         retval = TRUE;
2006         }
2007
2008         g_free(visible_str);
2009
2010         return retval;
2011 }
2012
2013 static void textview_uri_list_remove_all(GSList *uri_list)
2014 {
2015         GSList *cur;
2016
2017         for (cur = uri_list; cur != NULL; cur = cur->next) {
2018                 if (cur->data) {
2019                         g_free(((RemoteURI *)cur->data)->uri);
2020                         g_free(cur->data);
2021                 }
2022         }
2023
2024         g_slist_free(uri_list);
2025 }