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