* AUTHORS
[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
900                 if (last_dot >= ep_)
901                         last_dot = prelast_dot;
902                 if (last_dot == NULL)
903                         return FALSE;
904                 last_dot++;
905         
906                 if (is_toplvl_domain(dom_tab, last_dot, ep_))
907                         result = TRUE;
908
909                 *ep = ep_;
910                 *bp = bp_;
911         }
912
913         if (!result) return FALSE;
914
915         /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
916         if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_)) 
917                 return FALSE;
918
919         /* see if this is <bracketed>; in this case we also scan for the informative part. */
920         if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
921                 return TRUE;
922
923 #define FULL_STACK()    ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
924 #define IN_STACK()      (ptr > closure_stack)
925 /* has underrun check */
926 #define POP_STACK()     if(IN_STACK()) --ptr
927 /* has overrun check */
928 #define PUSH_STACK(c)   if(!FULL_STACK()) *ptr++ = (c); else return TRUE
929 /* has underrun check */
930 #define PEEK_STACK()    (IN_STACK() ? *(ptr - 1) : 0)
931
932         ep_++;
933
934         /* scan for the informative part. */
935         for (bp_ -= 2; bp_ >= start; bp_--) {
936                 /* if closure on the stack keep scanning */
937                 if (PEEK_STACK() == *bp_) {
938                         POP_STACK();
939                         continue;
940                 }
941                 if (*bp_ == '\'' || *bp_ == '"') {
942                         PUSH_STACK(*bp_);
943                         continue;
944                 }
945
946                 /* if nothing in the closure stack, do the special conditions
947                  * the following if..else expression simply checks whether 
948                  * a token is acceptable. if not acceptable, the clause
949                  * should terminate the loop with a 'break' */
950                 if (!PEEK_STACK()) {
951                         if (*bp_ == '-'
952                         && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
953                         && (((bp_ + 1) < ep_)    && isalnum(*(bp_ + 1)))) {
954                                 /* hyphens are allowed, but only in
955                                    between alnums */
956                         } else if (!ispunct(*bp_)) {
957                                 /* but anything not being a punctiation
958                                    is ok */
959                         } else {
960                                 break; /* anything else is rejected */
961                         }
962                 }
963         }
964
965         bp_++;
966
967 #undef PEEK_STACK
968 #undef PUSH_STACK
969 #undef POP_STACK
970 #undef IN_STACK
971 #undef FULL_STACK
972
973         /* scan forward (should start with an alnum) */
974         for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
975                 ;
976
977         *ep = ep_;
978         *bp = bp_;
979
980         return result;
981 }
982
983 #undef IS_QUOTE
984 #undef IS_RFC822_CHAR
985
986 static gchar *make_email_string(const gchar *bp, const gchar *ep)
987 {
988         /* returns a mailto: URI; mailto: is also used to detect the
989          * uri type later on in the button_pressed signal handler */
990         gchar *tmp;
991         gchar *result;
992
993         tmp = g_strndup(bp, ep - bp);
994         result = g_strconcat("mailto:", tmp, NULL);
995         g_free(tmp);
996
997         return result;
998 }
999
1000 #define ADD_TXT_POS(bp_, ep_, pti_) \
1001         if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1002                 last = last->next; \
1003                 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1004                 last->next = NULL; \
1005         } else { \
1006                 g_warning("alloc error scanning URIs\n"); \
1007                 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
1008                                 linebuf, -1); \
1009                 return; \
1010         }
1011
1012 /* textview_make_clickable_parts() - colorizes clickable parts */
1013 static void textview_make_clickable_parts(TextView *textview,
1014                                           GdkFont *font,
1015                                           GdkColor *fg_color,
1016                                           GdkColor *uri_color,
1017                                           const gchar *linebuf)
1018 {
1019         /* parse table - in order of priority */
1020         struct table {
1021                 const gchar *needle; /* token */
1022
1023                 /* token search function */
1024                 gchar    *(*search)     (const gchar *haystack,
1025                                          const gchar *needle);
1026                 /* part parsing function */
1027                 gboolean  (*parse)      (const gchar *start,
1028                                          const gchar *scanpos,
1029                                          const gchar **bp_,
1030                                          const gchar **ep_);
1031                 /* part to URI function */
1032                 gchar    *(*build_uri)  (const gchar *bp,
1033                                          const gchar *ep);
1034         };
1035
1036         static struct table parser[] = {
1037                 {"http://",  strcasestr, get_uri_part,   make_uri_string},
1038                 {"https://", strcasestr, get_uri_part,   make_uri_string},
1039                 {"ftp://",   strcasestr, get_uri_part,   make_uri_string},
1040                 {"www.",     strcasestr, get_uri_part,   make_uri_string},
1041                 {"mailto:",  strcasestr, get_uri_part,   make_uri_string},
1042                 {"@",        strcasestr, get_email_part, make_email_string}
1043         };
1044         const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1045
1046         gint  n;
1047         const gchar *walk, *bp, *ep;
1048
1049         struct txtpos {
1050                 const gchar     *bp, *ep;       /* text position */
1051                 gint             pti;           /* index in parse table */
1052                 struct txtpos   *next;          /* next */
1053         } head = {NULL, NULL, 0,  NULL}, *last = &head;
1054
1055         GtkSText *text = GTK_STEXT(textview->text);
1056
1057         /* parse for clickable parts, and build a list of begin and end positions  */
1058         for (walk = linebuf, n = 0;;) {
1059                 gint last_index = PARSE_ELEMS;
1060                 gchar *scanpos = NULL;
1061
1062                 /* FIXME: this looks phony. scanning for anything in the parse table */
1063                 for (n = 0; n < PARSE_ELEMS; n++) {
1064                         gchar *tmp;
1065
1066                         tmp = parser[n].search(walk, parser[n].needle);
1067                         if (tmp) {
1068                                 if (scanpos == NULL || tmp < scanpos) {
1069                                         scanpos = tmp;
1070                                         last_index = n;
1071                                 }
1072                         }                                       
1073                 }
1074
1075                 if (scanpos) {
1076                         /* check if URI can be parsed */
1077                         if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1078                             && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1079                                         ADD_TXT_POS(bp, ep, last_index);
1080                                         walk = ep;
1081                         } else
1082                                 walk = scanpos +
1083                                         strlen(parser[last_index].needle);
1084                 } else
1085                         break;
1086         }
1087
1088         /* colorize this line */
1089         if (head.next) {
1090                 const gchar *normal_text = linebuf;
1091
1092                 /* insert URIs */
1093                 for (last = head.next; last != NULL;
1094                      normal_text = last->ep, last = last->next) {
1095                         RemoteURI *uri;
1096
1097                         uri = g_new(RemoteURI, 1);
1098                         if (last->bp - normal_text > 0)
1099                                 gtk_stext_insert(text, font,
1100                                                 fg_color, NULL,
1101                                                 normal_text,
1102                                                 last->bp - normal_text);
1103                         uri->uri = parser[last->pti].build_uri(last->bp,
1104                                                                last->ep);
1105                         uri->start = gtk_stext_get_point(text);
1106                         gtk_stext_insert(text, font, uri_color,
1107                                         NULL, last->bp, last->ep - last->bp);
1108                         uri->end = gtk_stext_get_point(text);
1109                         textview->uri_list =
1110                                 g_slist_append(textview->uri_list, uri);
1111                 }
1112
1113                 if (*normal_text)
1114                         gtk_stext_insert(text, font, fg_color,
1115                                         NULL, normal_text, -1);
1116         } else
1117                 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1118 }
1119
1120 #undef ADD_TXT_POS
1121
1122 /* This function writes str as a double-clickable link with the given url. */ 
1123 static void textview_write_link(TextView *textview, const gchar *url,
1124                                 const gchar *str, CodeConverter *conv)
1125 {
1126     GdkColor *link_color = NULL;
1127     RemoteURI* uri;
1128     GtkSText *text = GTK_STEXT(textview->text);
1129     gchar buf[BUFFSIZE];
1130
1131     /* this part is taken from textview_write_line. Right now the only place
1132      * that calls this function passes NULL for conv, but you never know. */
1133 #if 0
1134     if (!conv)
1135             strncpy2(buf, str, sizeof(buf));
1136     else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
1137             gtk_stext_insert(text, textview->msgfont,
1138                             prefs_common.enable_color
1139                             ? &error_color : NULL, NULL,
1140                             "*** Warning: code conversion failed ***\n",
1141                             -1);
1142             return;
1143     }
1144 #endif
1145
1146     if (!conv || conv_convert(conv, buf, sizeof(buf), str) < 0)
1147         strncpy2(buf, str, sizeof(buf));
1148
1149     strcrchomp(buf);
1150     gtk_stext_insert(text, textview->msgfont, NULL, NULL, " ", 1);
1151  
1152     /* this part is based on the code in make_clickable_parts */
1153     if (prefs_common.enable_color) {
1154         link_color = &uri_color;
1155     }
1156     uri = g_new(RemoteURI, 1);
1157     uri->uri = g_strdup(url);
1158     uri->start = gtk_stext_get_point(text);
1159     gtk_stext_insert(text, textview->msgfont, link_color, NULL, str,
1160                     strlen(str));
1161     uri->end = gtk_stext_get_point(text);
1162     textview->uri_list = g_slist_append(textview->uri_list, uri);
1163 }
1164
1165 static void textview_write_line(TextView *textview, const gchar *str,
1166                                 CodeConverter *conv)
1167 {
1168         GtkSText *text = GTK_STEXT(textview->text);
1169         gchar buf[BUFFSIZE];
1170         GdkColor *fg_color;
1171         gint quotelevel = -1;
1172
1173 #if 0
1174         if (!conv)
1175                 strncpy2(buf, str, sizeof(buf));
1176         else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
1177                 gtk_stext_insert(text, textview->msgfont,
1178                                 prefs_common.enable_color
1179                                 ? &error_color : NULL, NULL,
1180                                 "*** Warning: code conversion failed ***\n",
1181                                 -1);
1182                 return;
1183         }
1184 #endif
1185         if (!conv || conv_convert(conv, buf, sizeof(buf), str) < 0)
1186                 strncpy2(buf, str, sizeof(buf));
1187
1188         strcrchomp(buf);
1189         if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1190         fg_color = NULL;
1191
1192         /* change color of quotation
1193            >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1194            Up to 3 levels of quotations are detected, and each
1195            level is colored using a different color. */
1196         if (prefs_common.enable_color 
1197             && line_has_quote_char(buf, prefs_common.quote_chars)) {
1198                 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1199
1200                 /* set up the correct foreground color */
1201                 if (quotelevel > 2) {
1202                         /* recycle colors */
1203                         if (prefs_common.recycle_quote_colors)
1204                                 quotelevel %= 3;
1205                         else
1206                                 quotelevel = 2;
1207                 }
1208         }
1209
1210         if (quotelevel == -1)
1211                 fg_color = NULL;
1212         else
1213                 fg_color = &quote_colors[quotelevel];
1214
1215         if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1216                 fg_color = &signature_color;
1217                 textview->is_in_signature = TRUE;
1218         }
1219         
1220         if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1221                 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1222
1223         if (prefs_common.enable_color)
1224                 textview_make_clickable_parts(textview, textview->msgfont,
1225                                               fg_color, &uri_color, buf);
1226         else
1227                 textview_make_clickable_parts(textview, textview->msgfont,
1228                                               fg_color, NULL, buf);
1229 }
1230
1231 void textview_clear(TextView *textview)
1232 {
1233         GtkSText *text = GTK_STEXT(textview->text);
1234
1235         gtk_stext_freeze(text);
1236         gtk_stext_set_point(text, 0);
1237         gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1238         gtk_stext_thaw(text);
1239
1240         textview_uri_list_remove_all(textview->uri_list);
1241         textview->uri_list = NULL;
1242
1243         textview->body_pos = 0;
1244         textview->cur_pos  = 0;
1245 }
1246
1247 void textview_destroy(TextView *textview)
1248 {
1249         textview_uri_list_remove_all(textview->uri_list);
1250         textview->uri_list = NULL;
1251
1252         if (!textview->scrolledwin_sb->parent)
1253                 gtk_widget_destroy(textview->scrolledwin_sb);
1254         if (!textview->scrolledwin_mb->parent)
1255                 gtk_widget_destroy(textview->scrolledwin_mb);
1256
1257         if (textview->msgfont)
1258                 gdk_font_unref(textview->msgfont);
1259         if (textview->boldfont)
1260                 gdk_font_unref(textview->boldfont);
1261
1262         g_free(textview);
1263 }
1264
1265 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1266 {
1267         textview->show_all_headers = all_headers;
1268 }
1269
1270 void textview_set_font(TextView *textview, const gchar *codeset)
1271 {
1272         gboolean use_fontset = TRUE;
1273
1274         /* In multi-byte mode, GtkSText can't display 8bit characters
1275            correctly, so it must be single-byte mode. */
1276         if (MB_CUR_MAX > 1) {
1277                 if (codeset) {
1278                         if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1279                             !g_strcasecmp(codeset, "BALTIC"))
1280                                 use_fontset = FALSE;
1281                         else if (conv_get_current_charset() != C_EUC_JP &&
1282                                  (!g_strncasecmp(codeset, "KOI8-", 5) ||
1283                                   !g_strncasecmp(codeset, "CP", 2)    ||
1284                                   !g_strncasecmp(codeset, "WINDOWS-", 8)))
1285                                 use_fontset = FALSE;
1286                 }
1287         } else
1288                 use_fontset = FALSE;
1289
1290         if (textview->text_is_mb && !use_fontset) {
1291                 GtkWidget *parent;
1292
1293                 parent = textview->scrolledwin_mb->parent;
1294                 gtkut_container_remove(GTK_CONTAINER(parent),
1295                                        textview->scrolledwin_mb);
1296                 gtk_container_add(GTK_CONTAINER(parent),
1297                                   textview->scrolledwin_sb);
1298
1299                 textview->text = textview->text_sb;
1300                 textview->text_is_mb = FALSE;
1301         } else if (!textview->text_is_mb && use_fontset) {
1302                 GtkWidget *parent;
1303
1304                 parent = textview->scrolledwin_sb->parent;
1305                 gtkut_container_remove(GTK_CONTAINER(parent),
1306                                        textview->scrolledwin_sb);
1307                 gtk_container_add(GTK_CONTAINER(parent),
1308                                   textview->scrolledwin_mb);
1309
1310                 textview->text = textview->text_mb;
1311                 textview->text_is_mb = TRUE;
1312         }
1313
1314         if (prefs_common.textfont) {
1315                 GdkFont *font;
1316
1317                 if (use_fontset) {
1318                         if (text_mb_font) {
1319                                 text_mb_font->ascent = text_mb_font_orig_ascent;
1320                                 text_mb_font->descent = text_mb_font_orig_descent;
1321                         }
1322                         font = gdk_fontset_load(prefs_common.textfont);
1323                         if (font && text_mb_font != font) {
1324                                 if (text_mb_font)
1325                                         gdk_font_unref(text_mb_font);
1326                                 text_mb_font = font;
1327                                 text_mb_font_orig_ascent = font->ascent;
1328                                 text_mb_font_orig_descent = font->descent;
1329                         }
1330                 } else {
1331                         if (text_sb_font) {
1332                                 text_sb_font->ascent = text_sb_font_orig_ascent;
1333                                 text_sb_font->descent = text_sb_font_orig_descent;
1334                         }
1335                         if (MB_CUR_MAX > 1)
1336                                 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1337                         else
1338                                 font = gtkut_font_load_from_fontset
1339                                         (prefs_common.textfont);
1340                         if (font && text_sb_font != font) {
1341                                 if (text_sb_font)
1342                                         gdk_font_unref(text_sb_font);
1343                                 text_sb_font = font;
1344                                 text_sb_font_orig_ascent = font->ascent;
1345                                 text_sb_font_orig_descent = font->descent;
1346                         }
1347                 }
1348
1349                 if (font) {
1350                         gint ascent, descent;
1351
1352                         descent = prefs_common.line_space / 2;
1353                         ascent  = prefs_common.line_space - descent;
1354                         font->ascent  += ascent;
1355                         font->descent += descent;
1356
1357                         if (textview->msgfont)
1358                                 gdk_font_unref(textview->msgfont);
1359                         textview->msgfont = font;
1360                         gdk_font_ref(font);
1361                 }
1362         }
1363
1364         if (!textview->boldfont && prefs_common.boldfont)
1365                 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1366         if (!spacingfont)
1367                 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1368 }
1369
1370 enum
1371 {
1372         H_DATE          = 0,
1373         H_FROM          = 1,
1374         H_TO            = 2,
1375         H_NEWSGROUPS    = 3,
1376         H_SUBJECT       = 4,
1377         H_CC            = 5,
1378         H_REPLY_TO      = 6,
1379         H_FOLLOWUP_TO   = 7,
1380         H_X_MAILER      = 8,
1381         H_X_NEWSREADER  = 9,
1382         H_USER_AGENT    = 10,
1383         H_ORGANIZATION  = 11,
1384 };
1385
1386 void textview_set_position(TextView *textview, gint pos)
1387 {
1388         if (pos < 0) {
1389                 textview->cur_pos =
1390                         gtk_stext_get_length(GTK_STEXT(textview->text));
1391         } else {
1392                 textview->cur_pos = pos;
1393         }
1394 }
1395
1396 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1397 {
1398         gchar buf[BUFFSIZE];
1399         GPtrArray *headers, *sorted_headers;
1400         GSList *disphdr_list;
1401         Header *header;
1402         gint i;
1403
1404         g_return_val_if_fail(fp != NULL, NULL);
1405
1406         if (textview->show_all_headers)
1407                 return procheader_get_header_array_asis(fp);
1408
1409         if (!prefs_common.display_header) {
1410                 while (fgets(buf, sizeof(buf), fp) != NULL)
1411                         if (buf[0] == '\r' || buf[0] == '\n') break;
1412                 return NULL;
1413         }
1414
1415         headers = procheader_get_header_array_asis(fp);
1416
1417         sorted_headers = g_ptr_array_new();
1418
1419         for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1420              disphdr_list = disphdr_list->next) {
1421                 DisplayHeaderProp *dp =
1422                         (DisplayHeaderProp *)disphdr_list->data;
1423
1424                 for (i = 0; i < headers->len; i++) {
1425                         header = g_ptr_array_index(headers, i);
1426
1427                         if (procheader_headername_equal(header->name,
1428                                                         dp->name)) {
1429                                 if (dp->hidden)
1430                                         procheader_header_free(header);
1431                                 else
1432                                         g_ptr_array_add(sorted_headers, header);
1433
1434                                 g_ptr_array_remove_index(headers, i);
1435                                 i--;
1436                         }
1437                 }
1438         }
1439
1440         if (prefs_common.show_other_header) {
1441                 for (i = 0; i < headers->len; i++) {
1442                         header = g_ptr_array_index(headers, i);
1443                         g_ptr_array_add(sorted_headers, header);
1444                 }
1445                 g_ptr_array_free(headers, TRUE);
1446         } else
1447                 procheader_header_array_destroy(headers);
1448
1449         return sorted_headers;
1450 }
1451
1452 static void textview_show_header(TextView *textview, GPtrArray *headers)
1453 {
1454         GtkSText *text = GTK_STEXT(textview->text);
1455         Header *header;
1456         gint i;
1457
1458         g_return_if_fail(headers != NULL);
1459
1460         gtk_stext_freeze(text);
1461
1462         for (i = 0; i < headers->len; i++) {
1463                 header = g_ptr_array_index(headers, i);
1464                 g_return_if_fail(header->name != NULL);
1465
1466                 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1467                                 header->name, -1);
1468                 if (header->name[strlen(header->name) - 1] != ' ')
1469                         gtk_stext_insert(text, textview->boldfont,
1470                                         NULL, NULL, " ", 1);
1471
1472                 if (procheader_headername_equal(header->name, "Subject") ||
1473                     procheader_headername_equal(header->name, "From")    ||
1474                     procheader_headername_equal(header->name, "To")      ||
1475                     procheader_headername_equal(header->name, "Cc"))
1476                         unfold_line(header->body);
1477
1478                 if (prefs_common.enable_color &&
1479                     (procheader_headername_equal(header->name, "X-Mailer") ||
1480                      procheader_headername_equal(header->name,
1481                                                  "X-Newsreader")) &&
1482                     strstr(header->body, "Sylpheed") != NULL)
1483                         gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1484                                         header->body, -1);
1485                 else if (prefs_common.enable_color) {
1486                         textview_make_clickable_parts(textview,
1487                                                       NULL, NULL, &uri_color,
1488                                                       header->body);
1489                 } else {
1490                         textview_make_clickable_parts(textview,
1491                                                       NULL, NULL, NULL,
1492                                                       header->body);
1493                 }
1494                 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1495         }
1496
1497         gtk_stext_thaw(text);
1498 }
1499
1500 gboolean textview_search_string(TextView *textview, const gchar *str,
1501                                 gboolean case_sens)
1502 {
1503         GtkSText *text = GTK_STEXT(textview->text);
1504         gint pos;
1505         wchar_t *wcs;
1506         gint len;
1507         gint text_len;
1508         gboolean found = FALSE;
1509
1510         g_return_val_if_fail(str != NULL, FALSE);
1511
1512         wcs = strdup_mbstowcs(str);
1513         g_return_val_if_fail(wcs != NULL, FALSE);
1514         len = wcslen(wcs);
1515         pos = textview->cur_pos;
1516         if (pos < textview->body_pos)
1517                 pos = textview->body_pos;
1518         text_len = gtk_stext_get_length(text);
1519         if (text_len - pos < len) {
1520                 g_free(wcs);
1521                 return FALSE;
1522         }
1523
1524         for (; pos < text_len; pos++) {
1525                 if (text_len - pos < len) break;
1526                 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1527                     == TRUE) {
1528                         gtk_widget_hide(GTK_WIDGET(textview->scrolledwin));
1529                         gtk_editable_set_position(GTK_EDITABLE(text),
1530                                                   pos + len);
1531                         gtk_editable_select_region(GTK_EDITABLE(text),
1532                                                    pos, pos + len);
1533                         gtk_widget_show(GTK_WIDGET(textview->scrolledwin));
1534                         textview_set_position(textview, pos + len);
1535                         found = TRUE;
1536                         break;
1537                 }
1538                 if (text_len - pos == len) break;
1539         }
1540         g_free(wcs);
1541         return found;
1542 }
1543
1544 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1545                                          gboolean case_sens)
1546 {
1547         GtkSText *text = GTK_STEXT(textview->text);
1548         gint pos;
1549         wchar_t *wcs;
1550         gint len;
1551         gint text_len;
1552         gboolean found = FALSE;
1553
1554         g_return_val_if_fail(str != NULL, FALSE);
1555
1556         wcs = strdup_mbstowcs(str);
1557         g_return_val_if_fail(wcs != NULL, FALSE);
1558         len = wcslen(wcs);
1559         pos = textview->cur_pos;
1560         text_len = gtk_stext_get_length(text);
1561         if (text_len - textview->body_pos < len) {
1562                 g_free(wcs);
1563                 return FALSE;
1564         }
1565         if (pos <= textview->body_pos || text_len - pos < len)
1566                 pos = text_len - len;
1567
1568         for (; pos >= textview->body_pos; pos--) {
1569                 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1570                     == TRUE) {
1571                         gtk_editable_set_position(GTK_EDITABLE(text), pos);
1572                         gtk_editable_select_region(GTK_EDITABLE(text),
1573                                                    pos, pos + len);
1574                         textview_set_position(textview, pos - 1);
1575                         found = TRUE;
1576                         break;
1577                 }
1578                 if (pos == textview->body_pos) break;
1579         }
1580
1581         g_free(wcs);
1582         return found;
1583 }
1584
1585 void textview_scroll_one_line(TextView *textview, gboolean up)
1586 {
1587         GtkSText *text = GTK_STEXT(textview->text);
1588         gfloat upper;
1589
1590         if (prefs_common.enable_smooth_scroll) {
1591                 textview_smooth_scroll_one_line(textview, up);
1592                 return;
1593         }
1594
1595         if (!up) {
1596                 upper = text->vadj->upper - text->vadj->page_size;
1597                 if (text->vadj->value < upper) {
1598                         text->vadj->value +=
1599                                 text->vadj->step_increment * 4;
1600                         text->vadj->value =
1601                                 MIN(text->vadj->value, upper);
1602                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1603                                                 "value_changed");
1604                 }
1605         } else {
1606                 if (text->vadj->value > 0.0) {
1607                         text->vadj->value -=
1608                                 text->vadj->step_increment * 4;
1609                         text->vadj->value =
1610                                 MAX(text->vadj->value, 0.0);
1611                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1612                                                 "value_changed");
1613                 }
1614         }
1615 }
1616
1617 gboolean textview_scroll_page(TextView *textview, gboolean up)
1618 {
1619         GtkSText *text = GTK_STEXT(textview->text);
1620         gfloat upper;
1621         gfloat page_incr;
1622
1623         if (prefs_common.enable_smooth_scroll)
1624                 return textview_smooth_scroll_page(textview, up);
1625
1626         if (prefs_common.scroll_halfpage)
1627                 page_incr = text->vadj->page_increment / 2;
1628         else
1629                 page_incr = text->vadj->page_increment;
1630
1631         if (!up) {
1632                 upper = text->vadj->upper - text->vadj->page_size;
1633                 if (text->vadj->value < upper) {
1634                         text->vadj->value += page_incr;
1635                         text->vadj->value = MIN(text->vadj->value, upper);
1636                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1637                                                 "value_changed");
1638                 } else
1639                         return FALSE;
1640         } else {
1641                 if (text->vadj->value > 0.0) {
1642                         text->vadj->value -= page_incr;
1643                         text->vadj->value = MAX(text->vadj->value, 0.0);
1644                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1645                                                 "value_changed");
1646                 } else
1647                         return FALSE;
1648         }
1649
1650         return TRUE;
1651 }
1652
1653 static void textview_smooth_scroll_do(TextView *textview,
1654                                       gfloat old_value, gfloat last_value,
1655                                       gint step)
1656 {
1657         GtkSText *text = GTK_STEXT(textview->text);
1658         gint change_value;
1659         gboolean up;
1660         gint i;
1661
1662         if (old_value < last_value) {
1663                 change_value = last_value - old_value;
1664                 up = FALSE;
1665         } else {
1666                 change_value = old_value - last_value;
1667                 up = TRUE;
1668         }
1669
1670         gdk_key_repeat_disable();
1671
1672         for (i = step; i <= change_value; i += step) {
1673                 text->vadj->value = old_value + (up ? -i : i);
1674                 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1675                                         "value_changed");
1676         }
1677
1678         text->vadj->value = last_value;
1679         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1680
1681         gdk_key_repeat_restore();
1682 }
1683
1684 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1685 {
1686         GtkSText *text = GTK_STEXT(textview->text);
1687         gfloat upper;
1688         gfloat old_value;
1689         gfloat last_value;
1690
1691         if (!up) {
1692                 upper = text->vadj->upper - text->vadj->page_size;
1693                 if (text->vadj->value < upper) {
1694                         old_value = text->vadj->value;
1695                         last_value = text->vadj->value +
1696                                 text->vadj->step_increment * 4;
1697                         last_value = MIN(last_value, upper);
1698
1699                         textview_smooth_scroll_do(textview, old_value,
1700                                                   last_value,
1701                                                   prefs_common.scroll_step);
1702                 }
1703         } else {
1704                 if (text->vadj->value > 0.0) {
1705                         old_value = text->vadj->value;
1706                         last_value = text->vadj->value -
1707                                 text->vadj->step_increment * 4;
1708                         last_value = MAX(last_value, 0.0);
1709
1710                         textview_smooth_scroll_do(textview, old_value,
1711                                                   last_value,
1712                                                   prefs_common.scroll_step);
1713                 }
1714         }
1715 }
1716
1717 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1718 {
1719         GtkSText *text = GTK_STEXT(textview->text);
1720         gfloat upper;
1721         gfloat page_incr;
1722         gfloat old_value;
1723         gfloat last_value;
1724
1725         if (prefs_common.scroll_halfpage)
1726                 page_incr = text->vadj->page_increment / 2;
1727         else
1728                 page_incr = text->vadj->page_increment;
1729
1730         if (!up) {
1731                 upper = text->vadj->upper - text->vadj->page_size;
1732                 if (text->vadj->value < upper) {
1733                         old_value = text->vadj->value;
1734                         last_value = text->vadj->value + page_incr;
1735                         last_value = MIN(last_value, upper);
1736
1737                         textview_smooth_scroll_do(textview, old_value,
1738                                                   last_value,
1739                                                   prefs_common.scroll_step);
1740                 } else
1741                         return FALSE;
1742         } else {
1743                 if (text->vadj->value > 0.0) {
1744                         old_value = text->vadj->value;
1745                         last_value = text->vadj->value - page_incr;
1746                         last_value = MAX(last_value, 0.0);
1747
1748                         textview_smooth_scroll_do(textview, old_value,
1749                                                   last_value,
1750                                                   prefs_common.scroll_step);
1751                 } else
1752                         return FALSE;
1753         }
1754
1755         return TRUE;
1756 }
1757
1758 #define KEY_PRESS_EVENT_STOP() \
1759         if (gtk_signal_n_emissions_by_name \
1760                 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1761                 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1762                                              "key_press_event"); \
1763         }
1764
1765 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1766                                  TextView *textview)
1767 {
1768         SummaryView *summaryview = NULL;
1769         MessageView *messageview = textview->messageview;
1770
1771         if (!event) return FALSE;
1772         if (messageview->mainwin)
1773                 summaryview = messageview->mainwin->summaryview;
1774
1775         switch (event->keyval) {
1776         case GDK_Tab:
1777         case GDK_Home:
1778         case GDK_Left:
1779         case GDK_Up:
1780         case GDK_Right:
1781         case GDK_Down:
1782         case GDK_Page_Up:
1783         case GDK_Page_Down:
1784         case GDK_End:
1785         case GDK_Control_L:
1786         case GDK_Control_R:
1787                 break;
1788         case GDK_space:
1789                 if (summaryview)
1790                         summary_pass_key_press_event(summaryview, event);
1791                 else
1792                         textview_scroll_page(textview, FALSE);
1793                 break;
1794         case GDK_BackSpace:
1795                 textview_scroll_page(textview, TRUE);
1796                 break;
1797         case GDK_Return:
1798                 textview_scroll_one_line(textview,
1799                                          (event->state & GDK_MOD1_MASK) != 0);
1800                 break;
1801         case GDK_Delete:
1802                 if (summaryview)
1803                         summary_pass_key_press_event(summaryview, event);
1804                 break;
1805         case GDK_n:
1806         case GDK_N:
1807         case GDK_p:
1808         case GDK_P:
1809         case GDK_y:
1810         case GDK_t:
1811         case GDK_l:
1812                 if (messageview->type == MVIEW_MIME &&
1813                     textview == messageview->mimeview->textview) {
1814                         KEY_PRESS_EVENT_STOP();
1815                         mimeview_pass_key_press_event(messageview->mimeview,
1816                                                       event);
1817                         break;
1818                 }
1819                 /* fall through */
1820         default:
1821                 if (summaryview &&
1822                     event->window != messageview->mainwin->window->window) {
1823                         GdkEventKey tmpev = *event;
1824
1825                         tmpev.window = messageview->mainwin->window->window;
1826                         KEY_PRESS_EVENT_STOP();
1827                         gtk_widget_event(messageview->mainwin->window,
1828                                          (GdkEvent *)&tmpev);
1829                 }
1830                 break;
1831         }
1832
1833         return TRUE;
1834 }
1835
1836 static gint show_url_timeout_cb(gpointer data)
1837 {
1838         TextView *textview = (TextView *)data;
1839         
1840         if (textview->messageview->mainwin)
1841                 if (textview->show_url_msgid)
1842                         gtk_statusbar_remove(GTK_STATUSBAR(
1843                                 textview->messageview->mainwin->statusbar),
1844                                 textview->messageview->mainwin->folderview_cid,
1845                                 textview->show_url_msgid);
1846                 return FALSE;
1847 }
1848
1849 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1850                                     TextView *textview)
1851 {
1852         if (event)
1853                 textview->last_buttonpress = event->type;
1854         return FALSE;
1855 }
1856
1857 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1858                                     TextView *textview)
1859 {
1860         textview->cur_pos = 
1861                 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1862
1863         if (event && 
1864             ((event->button == 1)
1865              || event->button == 2 || event->button == 3)) {
1866                 GSList *cur;
1867
1868                 /* double click seems to set the cursor after the current
1869                  * word. The cursor position needs fixing, otherwise the
1870                  * last word of a clickable zone will not work */
1871                 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1872                         textview->cur_pos--;
1873                 }
1874
1875                 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1876                         RemoteURI *uri = (RemoteURI *)cur->data;
1877
1878                         if (textview->cur_pos >= uri->start &&
1879                             textview->cur_pos <  uri->end) {
1880                                 /* single click: display url in statusbar */
1881                                 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1882                                         if (textview->messageview->mainwin) {
1883                                                 if (textview->show_url_msgid) {
1884                                                         gtk_timeout_remove(textview->show_url_timeout_tag);
1885                                                         gtk_statusbar_remove(GTK_STATUSBAR(
1886                                                                 textview->messageview->mainwin->statusbar),
1887                                                                 textview->messageview->mainwin->folderview_cid,
1888                                                                 textview->show_url_msgid);
1889                                                         textview->show_url_msgid = 0;
1890                                                 }
1891                                                 textview->show_url_msgid = gtk_statusbar_push(
1892                                                                 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1893                                                                 textview->messageview->mainwin->folderview_cid,
1894                                                                 uri->uri);
1895                                                 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1896                                                 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1897                                         }
1898                                 } else
1899                                 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1900                                         if (event->button == 3) {
1901                                                 gchar *fromname, *fromaddress;
1902                                                 
1903                                                 /* extract url */
1904                                                 fromaddress = g_strdup(uri->uri + 7);
1905                                                 /* Hiroyuki: please put this function in utils.c! */
1906                                                 fromname = procheader_get_fromname(fromaddress);
1907                                                 extract_address(fromaddress);
1908                                                 g_message("adding from textview %s <%s>", fromname, fromaddress);
1909                                                 /* Add to address book - Match */
1910                                                 addressbook_add_contact( fromname, fromaddress, NULL );
1911                                                 
1912                                                 g_free(fromaddress);
1913                                                 g_free(fromname);
1914                                         } else {
1915                                                 PrefsAccount *account = NULL;
1916                                                 FolderItem   *folder_item;
1917
1918                                                 if (textview->messageview && textview->messageview->mainwin 
1919                                                 &&  textview->messageview->mainwin->summaryview 
1920                                                 &&  textview->messageview->mainwin->summaryview->folder_item) {
1921                                                         folder_item = textview->messageview->mainwin->summaryview->folder_item;
1922                                                         if (folder_item->prefs && folder_item->prefs->enable_default_account)
1923                                                                 account = account_find_from_id(folder_item->prefs->default_account);
1924                                                 }
1925                                                 compose_new(account, uri->uri + 7, NULL);
1926                                         }
1927                                 } else 
1928 #if USE_GPGME
1929                                 if (!g_strncasecmp(uri->uri, "pgp:", 4)) {
1930                                         GtkAdjustment *pos = gtk_scrolled_window_get_vadjustment(
1931                                                                 GTK_SCROLLED_WINDOW(textview->scrolledwin));
1932                                         gfloat vpos = pos->value;
1933                                         mimeview_check_signature(textview->messageview->mimeview);
1934                                         /* scroll back where we were */
1935                                         gtk_adjustment_set_value(pos, vpos);
1936                                 } else
1937 #endif
1938                                 {
1939                                         open_uri(uri->uri,
1940                                                  prefs_common.uri_cmd);
1941                                 }
1942                         }
1943                 }
1944         }
1945         if (event)
1946                 textview->last_buttonpress = event->type;
1947         return FALSE;
1948 }
1949
1950 static void textview_uri_list_remove_all(GSList *uri_list)
1951 {
1952         GSList *cur;
1953
1954         for (cur = uri_list; cur != NULL; cur = cur->next) {
1955                 if (cur->data) {
1956                         g_free(((RemoteURI *)cur->data)->uri);
1957                         g_free(cur->data);
1958                 }
1959         }
1960
1961         g_slist_free(uri_list);
1962 }