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