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