c50a87a08f6f2937563a7a2f7006537e848e5f97
[claws.git] / src / textview.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2002 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
854         /* the informative part of the email address (describing the name
855          * of the email address owner) may contain quoted parts. the
856          * closure stack stores the last encountered quotes. */
857         gchar closure_stack[128];
858         gchar *ptr = closure_stack;
859
860         g_return_val_if_fail(start != NULL, FALSE);
861         g_return_val_if_fail(scanpos != NULL, FALSE);
862         g_return_val_if_fail(bp != NULL, FALSE);
863         g_return_val_if_fail(ep != NULL, FALSE);
864
865         if (!dom_tab)
866                 dom_tab = create_domain_tab();
867         g_return_val_if_fail(dom_tab, FALSE);   
868
869         /* scan start of address */
870         for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
871                 ;
872
873         /* TODO: should start with an alnum? */
874         bp_++;
875         for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
876                 ;
877
878         if (bp_ != scanpos) {
879                 /* scan end of address */
880                 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
881                         if (*ep_ == '.') {
882                                 prelast_dot = last_dot;
883                                 last_dot = ep_;
884                                 if (*(last_dot + 1) == '.') {
885                                         if (prelast_dot == NULL)
886                                                 return FALSE;
887                                         last_dot = prelast_dot;
888                                         break;
889                                 }
890                         }
891
892                 /* TODO: really should terminate with an alnum? */
893                 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
894                         ;
895                 ep_++;
896
897                 if (last_dot == NULL)
898                         return FALSE;
899                 if (last_dot >= ep_)
900                         last_dot = prelast_dot;
901                 if (last_dot == NULL || (scanpos + 1 >= last_dot))
902                         return FALSE;
903                 last_dot++;
904         
905                 if (is_toplvl_domain(dom_tab, last_dot, ep_))
906                         result = TRUE;
907
908                 *ep = ep_;
909                 *bp = bp_;
910         }
911
912         if (!result) return FALSE;
913
914         /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
915         if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_)) 
916                 return FALSE;
917
918         /* see if this is <bracketed>; in this case we also scan for the informative part. */
919         if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
920                 return TRUE;
921
922 #define FULL_STACK()    ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
923 #define IN_STACK()      (ptr > closure_stack)
924 /* has underrun check */
925 #define POP_STACK()     if(IN_STACK()) --ptr
926 /* has overrun check */
927 #define PUSH_STACK(c)   if(!FULL_STACK()) *ptr++ = (c); else return TRUE
928 /* has underrun check */
929 #define PEEK_STACK()    (IN_STACK() ? *(ptr - 1) : 0)
930
931         ep_++;
932
933         /* scan for the informative part. */
934         for (bp_ -= 2; bp_ >= start; bp_--) {
935                 /* if closure on the stack keep scanning */
936                 if (PEEK_STACK() == *bp_) {
937                         POP_STACK();
938                         continue;
939                 }
940                 if (*bp_ == '\'' || *bp_ == '"') {
941                         PUSH_STACK(*bp_);
942                         continue;
943                 }
944
945                 /* if nothing in the closure stack, do the special conditions
946                  * the following if..else expression simply checks whether 
947                  * a token is acceptable. if not acceptable, the clause
948                  * should terminate the loop with a 'break' */
949                 if (!PEEK_STACK()) {
950                         if (*bp_ == '-'
951                         && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
952                         && (((bp_ + 1) < ep_)    && isalnum(*(bp_ + 1)))) {
953                                 /* hyphens are allowed, but only in
954                                    between alnums */
955                         } else if (!ispunct(*bp_)) {
956                                 /* but anything not being a punctiation
957                                    is ok */
958                         } else {
959                                 break; /* anything else is rejected */
960                         }
961                 }
962         }
963
964         bp_++;
965
966 #undef PEEK_STACK
967 #undef PUSH_STACK
968 #undef POP_STACK
969 #undef IN_STACK
970 #undef FULL_STACK
971
972         /* scan forward (should start with an alnum) */
973         for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
974                 ;
975
976         *ep = ep_;
977         *bp = bp_;
978
979         return result;
980 }
981
982 #undef IS_QUOTE
983 #undef IS_RFC822_CHAR
984
985 static gchar *make_email_string(const gchar *bp, const gchar *ep)
986 {
987         /* returns a mailto: URI; mailto: is also used to detect the
988          * uri type later on in the button_pressed signal handler */
989         gchar *tmp;
990         gchar *result;
991
992         tmp = g_strndup(bp, ep - bp);
993         result = g_strconcat("mailto:", tmp, NULL);
994         g_free(tmp);
995
996         return result;
997 }
998
999 #define ADD_TXT_POS(bp_, ep_, pti_) \
1000         if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1001                 last = last->next; \
1002                 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1003                 last->next = NULL; \
1004         } else { \
1005                 g_warning("alloc error scanning URIs\n"); \
1006                 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
1007                                 linebuf, -1); \
1008                 return; \
1009         }
1010
1011 /* textview_make_clickable_parts() - colorizes clickable parts */
1012 static void textview_make_clickable_parts(TextView *textview,
1013                                           GdkFont *font,
1014                                           GdkColor *fg_color,
1015                                           GdkColor *uri_color,
1016                                           const gchar *linebuf)
1017 {
1018         /* parse table - in order of priority */
1019         struct table {
1020                 const gchar *needle; /* token */
1021
1022                 /* token search function */
1023                 gchar    *(*search)     (const gchar *haystack,
1024                                          const gchar *needle);
1025                 /* part parsing function */
1026                 gboolean  (*parse)      (const gchar *start,
1027                                          const gchar *scanpos,
1028                                          const gchar **bp_,
1029                                          const gchar **ep_);
1030                 /* part to URI function */
1031                 gchar    *(*build_uri)  (const gchar *bp,
1032                                          const gchar *ep);
1033         };
1034
1035         static struct table parser[] = {
1036                 {"http://",  strcasestr, get_uri_part,   make_uri_string},
1037                 {"https://", strcasestr, get_uri_part,   make_uri_string},
1038                 {"ftp://",   strcasestr, get_uri_part,   make_uri_string},
1039                 {"www.",     strcasestr, get_uri_part,   make_uri_string},
1040                 {"mailto:",  strcasestr, get_uri_part,   make_uri_string},
1041                 {"@",        strcasestr, get_email_part, make_email_string}
1042         };
1043         const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1044
1045         gint  n;
1046         const gchar *walk, *bp, *ep;
1047
1048         struct txtpos {
1049                 const gchar     *bp, *ep;       /* text position */
1050                 gint             pti;           /* index in parse table */
1051                 struct txtpos   *next;          /* next */
1052         } head = {NULL, NULL, 0,  NULL}, *last = &head;
1053
1054         GtkSText *text = GTK_STEXT(textview->text);
1055
1056         /* parse for clickable parts, and build a list of begin and end positions  */
1057         for (walk = linebuf, n = 0;;) {
1058                 gint last_index = PARSE_ELEMS;
1059                 gchar *scanpos = NULL;
1060
1061                 /* FIXME: this looks phony. scanning for anything in the parse table */
1062                 for (n = 0; n < PARSE_ELEMS; n++) {
1063                         gchar *tmp;
1064
1065                         tmp = parser[n].search(walk, parser[n].needle);
1066                         if (tmp) {
1067                                 if (scanpos == NULL || tmp < scanpos) {
1068                                         scanpos = tmp;
1069                                         last_index = n;
1070                                 }
1071                         }                                       
1072                 }
1073
1074                 if (scanpos) {
1075                         /* check if URI can be parsed */
1076                         if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1077                             && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1078                                         ADD_TXT_POS(bp, ep, last_index);
1079                                         walk = ep;
1080                         } else
1081                                 walk = scanpos +
1082                                         strlen(parser[last_index].needle);
1083                 } else
1084                         break;
1085         }
1086
1087         /* colorize this line */
1088         if (head.next) {
1089                 const gchar *normal_text = linebuf;
1090
1091                 /* insert URIs */
1092                 for (last = head.next; last != NULL;
1093                      normal_text = last->ep, last = last->next) {
1094                         RemoteURI *uri;
1095
1096                         uri = g_new(RemoteURI, 1);
1097                         if (last->bp - normal_text > 0)
1098                                 gtk_stext_insert(text, font,
1099                                                 fg_color, NULL,
1100                                                 normal_text,
1101                                                 last->bp - normal_text);
1102                         uri->uri = parser[last->pti].build_uri(last->bp,
1103                                                                last->ep);
1104                         uri->start = gtk_stext_get_point(text);
1105                         gtk_stext_insert(text, font, uri_color,
1106                                         NULL, last->bp, last->ep - last->bp);
1107                         uri->end = gtk_stext_get_point(text);
1108                         textview->uri_list =
1109                                 g_slist_append(textview->uri_list, uri);
1110                 }
1111
1112                 if (*normal_text)
1113                         gtk_stext_insert(text, font, fg_color,
1114                                         NULL, normal_text, -1);
1115         } else
1116                 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1117 }
1118
1119 #undef ADD_TXT_POS
1120
1121 /* This function writes str as a double-clickable link with the given url. */ 
1122 static void textview_write_link(TextView *textview, const gchar *url,
1123                                 const gchar *str, CodeConverter *conv)
1124 {
1125     GdkColor *link_color = NULL;
1126     RemoteURI* uri;
1127     GtkSText *text = GTK_STEXT(textview->text);
1128     gchar buf[BUFFSIZE];
1129
1130     /* this part is taken from textview_write_line. Right now the only place
1131      * that calls this function passes NULL for conv, but you never know. */
1132 #if 0
1133     if (!conv)
1134             strncpy2(buf, str, sizeof(buf));
1135     else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
1136             gtk_stext_insert(text, textview->msgfont,
1137                             prefs_common.enable_color
1138                             ? &error_color : NULL, NULL,
1139                             "*** Warning: code conversion failed ***\n",
1140                             -1);
1141             return;
1142     }
1143 #endif
1144
1145     if (!conv || conv_convert(conv, buf, sizeof(buf), str) < 0)
1146         strncpy2(buf, str, sizeof(buf));
1147
1148     strcrchomp(buf);
1149     gtk_stext_insert(text, textview->msgfont, NULL, NULL, " ", 1);
1150  
1151     /* this part is based on the code in make_clickable_parts */
1152     if (prefs_common.enable_color) {
1153         link_color = &uri_color;
1154     }
1155     uri = g_new(RemoteURI, 1);
1156     uri->uri = g_strdup(url);
1157     uri->start = gtk_stext_get_point(text);
1158     gtk_stext_insert(text, textview->msgfont, link_color, NULL, str,
1159                     strlen(str));
1160     uri->end = gtk_stext_get_point(text);
1161     textview->uri_list = g_slist_append(textview->uri_list, uri);
1162 }
1163
1164 static void textview_write_line(TextView *textview, const gchar *str,
1165                                 CodeConverter *conv)
1166 {
1167         GtkSText *text = GTK_STEXT(textview->text);
1168         gchar buf[BUFFSIZE];
1169         GdkColor *fg_color;
1170         gint quotelevel = -1;
1171
1172 #if 0
1173         if (!conv)
1174                 strncpy2(buf, str, sizeof(buf));
1175         else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
1176                 gtk_stext_insert(text, textview->msgfont,
1177                                 prefs_common.enable_color
1178                                 ? &error_color : NULL, NULL,
1179                                 "*** Warning: code conversion failed ***\n",
1180                                 -1);
1181                 return;
1182         }
1183 #endif
1184         if (!conv || conv_convert(conv, buf, sizeof(buf), str) < 0)
1185                 strncpy2(buf, str, sizeof(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) {
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 (prefs_common.enable_color &&
1478                     (procheader_headername_equal(header->name, "X-Mailer") ||
1479                      procheader_headername_equal(header->name,
1480                                                  "X-Newsreader")) &&
1481                     strstr(header->body, "Sylpheed") != NULL)
1482                         gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1483                                         header->body, -1);
1484                 else if (prefs_common.enable_color) {
1485                         textview_make_clickable_parts(textview,
1486                                                       NULL, NULL, &uri_color,
1487                                                       header->body);
1488                 } else {
1489                         textview_make_clickable_parts(textview,
1490                                                       NULL, NULL, NULL,
1491                                                       header->body);
1492                 }
1493                 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1494         }
1495
1496         gtk_stext_thaw(text);
1497 }
1498
1499 gboolean textview_search_string(TextView *textview, const gchar *str,
1500                                 gboolean case_sens)
1501 {
1502         GtkSText *text = GTK_STEXT(textview->text);
1503         gint pos;
1504         wchar_t *wcs;
1505         gint len;
1506         gint text_len;
1507         gboolean found = FALSE;
1508
1509         g_return_val_if_fail(str != NULL, FALSE);
1510
1511         wcs = strdup_mbstowcs(str);
1512         g_return_val_if_fail(wcs != NULL, FALSE);
1513         len = wcslen(wcs);
1514         pos = textview->cur_pos;
1515         if (pos < textview->body_pos)
1516                 pos = textview->body_pos;
1517         text_len = gtk_stext_get_length(text);
1518         if (text_len - pos < len) {
1519                 g_free(wcs);
1520                 return FALSE;
1521         }
1522
1523         for (; pos < text_len; pos++) {
1524                 if (text_len - pos < len) break;
1525                 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1526                     == TRUE) {
1527                         gtk_widget_hide(GTK_WIDGET(textview->scrolledwin));
1528                         gtk_editable_set_position(GTK_EDITABLE(text),
1529                                                   pos + len);
1530                         gtk_editable_select_region(GTK_EDITABLE(text),
1531                                                    pos, pos + len);
1532                         gtk_widget_show(GTK_WIDGET(textview->scrolledwin));
1533                         textview_set_position(textview, pos + len);
1534                         found = TRUE;
1535                         break;
1536                 }
1537                 if (text_len - pos == len) break;
1538         }
1539         g_free(wcs);
1540         return found;
1541 }
1542
1543 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1544                                          gboolean case_sens)
1545 {
1546         GtkSText *text = GTK_STEXT(textview->text);
1547         gint pos;
1548         wchar_t *wcs;
1549         gint len;
1550         gint text_len;
1551         gboolean found = FALSE;
1552
1553         g_return_val_if_fail(str != NULL, FALSE);
1554
1555         wcs = strdup_mbstowcs(str);
1556         g_return_val_if_fail(wcs != NULL, FALSE);
1557         len = wcslen(wcs);
1558         pos = textview->cur_pos;
1559         text_len = gtk_stext_get_length(text);
1560         if (text_len - textview->body_pos < len) {
1561                 g_free(wcs);
1562                 return FALSE;
1563         }
1564         if (pos <= textview->body_pos || text_len - pos < len)
1565                 pos = text_len - len;
1566
1567         for (; pos >= textview->body_pos; pos--) {
1568                 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1569                     == TRUE) {
1570                         gtk_editable_set_position(GTK_EDITABLE(text), pos);
1571                         gtk_editable_select_region(GTK_EDITABLE(text),
1572                                                    pos, pos + len);
1573                         textview_set_position(textview, pos - 1);
1574                         found = TRUE;
1575                         break;
1576                 }
1577                 if (pos == textview->body_pos) break;
1578         }
1579
1580         g_free(wcs);
1581         return found;
1582 }
1583
1584 void textview_scroll_one_line(TextView *textview, gboolean up)
1585 {
1586         GtkSText *text = GTK_STEXT(textview->text);
1587         gfloat upper;
1588
1589         if (prefs_common.enable_smooth_scroll) {
1590                 textview_smooth_scroll_one_line(textview, up);
1591                 return;
1592         }
1593
1594         if (!up) {
1595                 upper = text->vadj->upper - text->vadj->page_size;
1596                 if (text->vadj->value < upper) {
1597                         text->vadj->value +=
1598                                 text->vadj->step_increment * 4;
1599                         text->vadj->value =
1600                                 MIN(text->vadj->value, upper);
1601                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1602                                                 "value_changed");
1603                 }
1604         } else {
1605                 if (text->vadj->value > 0.0) {
1606                         text->vadj->value -=
1607                                 text->vadj->step_increment * 4;
1608                         text->vadj->value =
1609                                 MAX(text->vadj->value, 0.0);
1610                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1611                                                 "value_changed");
1612                 }
1613         }
1614 }
1615
1616 gboolean textview_scroll_page(TextView *textview, gboolean up)
1617 {
1618         GtkSText *text = GTK_STEXT(textview->text);
1619         gfloat upper;
1620         gfloat page_incr;
1621
1622         if (prefs_common.enable_smooth_scroll)
1623                 return textview_smooth_scroll_page(textview, up);
1624
1625         if (prefs_common.scroll_halfpage)
1626                 page_incr = text->vadj->page_increment / 2;
1627         else
1628                 page_incr = text->vadj->page_increment;
1629
1630         if (!up) {
1631                 upper = text->vadj->upper - text->vadj->page_size;
1632                 if (text->vadj->value < upper) {
1633                         text->vadj->value += page_incr;
1634                         text->vadj->value = MIN(text->vadj->value, upper);
1635                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1636                                                 "value_changed");
1637                 } else
1638                         return FALSE;
1639         } else {
1640                 if (text->vadj->value > 0.0) {
1641                         text->vadj->value -= page_incr;
1642                         text->vadj->value = MAX(text->vadj->value, 0.0);
1643                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1644                                                 "value_changed");
1645                 } else
1646                         return FALSE;
1647         }
1648
1649         return TRUE;
1650 }
1651
1652 static void textview_smooth_scroll_do(TextView *textview,
1653                                       gfloat old_value, gfloat last_value,
1654                                       gint step)
1655 {
1656         GtkSText *text = GTK_STEXT(textview->text);
1657         gint change_value;
1658         gboolean up;
1659         gint i;
1660
1661         if (old_value < last_value) {
1662                 change_value = last_value - old_value;
1663                 up = FALSE;
1664         } else {
1665                 change_value = old_value - last_value;
1666                 up = TRUE;
1667         }
1668
1669         gdk_key_repeat_disable();
1670
1671         for (i = step; i <= change_value; i += step) {
1672                 text->vadj->value = old_value + (up ? -i : i);
1673                 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1674                                         "value_changed");
1675         }
1676
1677         text->vadj->value = last_value;
1678         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1679
1680         gdk_key_repeat_restore();
1681 }
1682
1683 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1684 {
1685         GtkSText *text = GTK_STEXT(textview->text);
1686         gfloat upper;
1687         gfloat old_value;
1688         gfloat last_value;
1689
1690         if (!up) {
1691                 upper = text->vadj->upper - text->vadj->page_size;
1692                 if (text->vadj->value < upper) {
1693                         old_value = text->vadj->value;
1694                         last_value = text->vadj->value +
1695                                 text->vadj->step_increment * 4;
1696                         last_value = MIN(last_value, upper);
1697
1698                         textview_smooth_scroll_do(textview, old_value,
1699                                                   last_value,
1700                                                   prefs_common.scroll_step);
1701                 }
1702         } else {
1703                 if (text->vadj->value > 0.0) {
1704                         old_value = text->vadj->value;
1705                         last_value = text->vadj->value -
1706                                 text->vadj->step_increment * 4;
1707                         last_value = MAX(last_value, 0.0);
1708
1709                         textview_smooth_scroll_do(textview, old_value,
1710                                                   last_value,
1711                                                   prefs_common.scroll_step);
1712                 }
1713         }
1714 }
1715
1716 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1717 {
1718         GtkSText *text = GTK_STEXT(textview->text);
1719         gfloat upper;
1720         gfloat page_incr;
1721         gfloat old_value;
1722         gfloat last_value;
1723
1724         if (prefs_common.scroll_halfpage)
1725                 page_incr = text->vadj->page_increment / 2;
1726         else
1727                 page_incr = text->vadj->page_increment;
1728
1729         if (!up) {
1730                 upper = text->vadj->upper - text->vadj->page_size;
1731                 if (text->vadj->value < upper) {
1732                         old_value = text->vadj->value;
1733                         last_value = text->vadj->value + page_incr;
1734                         last_value = MIN(last_value, upper);
1735
1736                         textview_smooth_scroll_do(textview, old_value,
1737                                                   last_value,
1738                                                   prefs_common.scroll_step);
1739                 } else
1740                         return FALSE;
1741         } else {
1742                 if (text->vadj->value > 0.0) {
1743                         old_value = text->vadj->value;
1744                         last_value = text->vadj->value - page_incr;
1745                         last_value = MAX(last_value, 0.0);
1746
1747                         textview_smooth_scroll_do(textview, old_value,
1748                                                   last_value,
1749                                                   prefs_common.scroll_step);
1750                 } else
1751                         return FALSE;
1752         }
1753
1754         return TRUE;
1755 }
1756
1757 #define KEY_PRESS_EVENT_STOP() \
1758         if (gtk_signal_n_emissions_by_name \
1759                 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1760                 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1761                                              "key_press_event"); \
1762         }
1763
1764 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1765                                  TextView *textview)
1766 {
1767         SummaryView *summaryview = NULL;
1768         MessageView *messageview = textview->messageview;
1769
1770         if (!event) return FALSE;
1771         if (messageview->mainwin)
1772                 summaryview = messageview->mainwin->summaryview;
1773
1774         switch (event->keyval) {
1775         case GDK_Tab:
1776         case GDK_Home:
1777         case GDK_Left:
1778         case GDK_Up:
1779         case GDK_Right:
1780         case GDK_Down:
1781         case GDK_Page_Up:
1782         case GDK_Page_Down:
1783         case GDK_End:
1784         case GDK_Control_L:
1785         case GDK_Control_R:
1786                 break;
1787         case GDK_space:
1788                 if (summaryview)
1789                         summary_pass_key_press_event(summaryview, event);
1790                 else
1791                         textview_scroll_page(textview, FALSE);
1792                 break;
1793         case GDK_BackSpace:
1794                 textview_scroll_page(textview, TRUE);
1795                 break;
1796         case GDK_Return:
1797                 textview_scroll_one_line(textview,
1798                                          (event->state & GDK_MOD1_MASK) != 0);
1799                 break;
1800         case GDK_Delete:
1801                 if (summaryview)
1802                         summary_pass_key_press_event(summaryview, event);
1803                 break;
1804         case GDK_n:
1805         case GDK_N:
1806         case GDK_p:
1807         case GDK_P:
1808         case GDK_y:
1809         case GDK_t:
1810         case GDK_l:
1811                 if (messageview->type == MVIEW_MIME &&
1812                     textview == messageview->mimeview->textview) {
1813                         KEY_PRESS_EVENT_STOP();
1814                         mimeview_pass_key_press_event(messageview->mimeview,
1815                                                       event);
1816                         break;
1817                 }
1818                 /* fall through */
1819         default:
1820                 if (summaryview &&
1821                     event->window != messageview->mainwin->window->window) {
1822                         GdkEventKey tmpev = *event;
1823
1824                         tmpev.window = messageview->mainwin->window->window;
1825                         KEY_PRESS_EVENT_STOP();
1826                         gtk_widget_event(messageview->mainwin->window,
1827                                          (GdkEvent *)&tmpev);
1828                 }
1829                 break;
1830         }
1831
1832         return TRUE;
1833 }
1834
1835 static gint show_url_timeout_cb(gpointer data)
1836 {
1837         TextView *textview = (TextView *)data;
1838         
1839         if (textview->messageview->mainwin)
1840                 if (textview->show_url_msgid)
1841                         gtk_statusbar_remove(GTK_STATUSBAR(
1842                                 textview->messageview->mainwin->statusbar),
1843                                 textview->messageview->mainwin->folderview_cid,
1844                                 textview->show_url_msgid);
1845                 return FALSE;
1846 }
1847
1848 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1849                                     TextView *textview)
1850 {
1851         if (event)
1852                 textview->last_buttonpress = event->type;
1853         return FALSE;
1854 }
1855
1856 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1857                                     TextView *textview)
1858 {
1859         textview->cur_pos = 
1860                 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1861
1862         if (event && 
1863             ((event->button == 1)
1864              || event->button == 2 || event->button == 3)) {
1865                 GSList *cur;
1866
1867                 /* double click seems to set the cursor after the current
1868                  * word. The cursor position needs fixing, otherwise the
1869                  * last word of a clickable zone will not work */
1870                 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1871                         textview->cur_pos--;
1872                 }
1873
1874                 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1875                         RemoteURI *uri = (RemoteURI *)cur->data;
1876
1877                         if (textview->cur_pos >= uri->start &&
1878                             textview->cur_pos <  uri->end) {
1879                                 /* single click: display url in statusbar */
1880                                 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1881                                         if (textview->messageview->mainwin) {
1882                                                 if (textview->show_url_msgid) {
1883                                                         gtk_timeout_remove(textview->show_url_timeout_tag);
1884                                                         gtk_statusbar_remove(GTK_STATUSBAR(
1885                                                                 textview->messageview->mainwin->statusbar),
1886                                                                 textview->messageview->mainwin->folderview_cid,
1887                                                                 textview->show_url_msgid);
1888                                                         textview->show_url_msgid = 0;
1889                                                 }
1890                                                 textview->show_url_msgid = gtk_statusbar_push(
1891                                                                 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1892                                                                 textview->messageview->mainwin->folderview_cid,
1893                                                                 uri->uri);
1894                                                 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1895                                                 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1896                                         }
1897                                 } else
1898                                 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1899                                         if (event->button == 3) {
1900                                                 gchar *fromname, *fromaddress;
1901                                                 
1902                                                 /* extract url */
1903                                                 fromaddress = g_strdup(uri->uri + 7);
1904                                                 /* Hiroyuki: please put this function in utils.c! */
1905                                                 fromname = procheader_get_fromname(fromaddress);
1906                                                 extract_address(fromaddress);
1907                                                 g_message("adding from textview %s <%s>", fromname, fromaddress);
1908                                                 /* Add to address book - Match */
1909                                                 addressbook_add_contact( fromname, fromaddress, NULL );
1910                                                 
1911                                                 g_free(fromaddress);
1912                                                 g_free(fromname);
1913                                         } else {
1914                                                 PrefsAccount *account = NULL;
1915                                                 FolderItem   *folder_item;
1916
1917                                                 if (textview->messageview && textview->messageview->mainwin 
1918                                                 &&  textview->messageview->mainwin->summaryview 
1919                                                 &&  textview->messageview->mainwin->summaryview->folder_item) {
1920                                                         folder_item = textview->messageview->mainwin->summaryview->folder_item;
1921                                                         if (folder_item->prefs && folder_item->prefs->enable_default_account)
1922                                                                 account = account_find_from_id(folder_item->prefs->default_account);
1923                                                 }
1924                                                 compose_new(account, uri->uri + 7, NULL);
1925                                         }
1926                                 } else 
1927 #if USE_GPGME
1928                                 if (!g_strncasecmp(uri->uri, "pgp:", 4)) {
1929                                         GtkAdjustment *pos = gtk_scrolled_window_get_vadjustment(
1930                                                                 GTK_SCROLLED_WINDOW(textview->scrolledwin));
1931                                         gfloat vpos = pos->value;
1932                                         mimeview_check_signature(textview->messageview->mimeview);
1933                                         /* scroll back where we were */
1934                                         gtk_adjustment_set_value(pos, vpos);
1935                                 } else
1936 #endif
1937                                 {
1938                                         open_uri(uri->uri,
1939                                                  prefs_common.uri_cmd);
1940                                 }
1941                         }
1942                 }
1943         }
1944         if (event)
1945                 textview->last_buttonpress = event->type;
1946         return FALSE;
1947 }
1948
1949 static void textview_uri_list_remove_all(GSList *uri_list)
1950 {
1951         GSList *cur;
1952
1953         for (cur = uri_list; cur != NULL; cur = cur->next) {
1954                 if (cur->data) {
1955                         g_free(((RemoteURI *)cur->data)->uri);
1956                         g_free(cur->data);
1957                 }
1958         }
1959
1960         g_slist_free(uri_list);
1961 }