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