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