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