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