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