sync with 0.8.9cvs6
[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    *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)
1190                 strncpy2(buf, str, sizeof(buf));
1191         else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1192                 conv_localetodisp(buf, sizeof(buf), str);
1193
1194         strcrchomp(buf);
1195         if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1196         fg_color = NULL;
1197
1198         /* change color of quotation
1199            >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1200            Up to 3 levels of quotations are detected, and each
1201            level is colored using a different color. */
1202         if (prefs_common.enable_color 
1203             && line_has_quote_char(buf, prefs_common.quote_chars)) {
1204                 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1205
1206                 /* set up the correct foreground color */
1207                 if (quotelevel > 2) {
1208                         /* recycle colors */
1209                         if (prefs_common.recycle_quote_colors)
1210                                 quotelevel %= 3;
1211                         else
1212                                 quotelevel = 2;
1213                 }
1214         }
1215
1216         if (quotelevel == -1)
1217                 fg_color = NULL;
1218         else
1219                 fg_color = &quote_colors[quotelevel];
1220
1221         if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1222                 fg_color = &signature_color;
1223                 textview->is_in_signature = TRUE;
1224         }
1225         
1226         if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1227                 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1228
1229         if (prefs_common.enable_color)
1230                 textview_make_clickable_parts(textview, textview->msgfont,
1231                                               fg_color, &uri_color, buf);
1232         else
1233                 textview_make_clickable_parts(textview, textview->msgfont,
1234                                               fg_color, NULL, buf);
1235 }
1236
1237 void textview_clear(TextView *textview)
1238 {
1239         GtkSText *text = GTK_STEXT(textview->text);
1240
1241         gtk_stext_freeze(text);
1242         gtk_stext_set_point(text, 0);
1243         gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1244         gtk_stext_thaw(text);
1245
1246         textview_uri_list_remove_all(textview->uri_list);
1247         textview->uri_list = NULL;
1248
1249         textview->body_pos = 0;
1250         textview->cur_pos  = 0;
1251 }
1252
1253 void textview_destroy(TextView *textview)
1254 {
1255         textview_uri_list_remove_all(textview->uri_list);
1256         textview->uri_list = NULL;
1257
1258         if (!textview->scrolledwin_sb->parent)
1259                 gtk_widget_destroy(textview->scrolledwin_sb);
1260         if (!textview->scrolledwin_mb->parent)
1261                 gtk_widget_destroy(textview->scrolledwin_mb);
1262
1263         if (textview->msgfont)
1264                 gdk_font_unref(textview->msgfont);
1265         if (textview->boldfont)
1266                 gdk_font_unref(textview->boldfont);
1267
1268         g_free(textview);
1269 }
1270
1271 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1272 {
1273         textview->show_all_headers = all_headers;
1274 }
1275
1276 void textview_set_font(TextView *textview, const gchar *codeset)
1277 {
1278         gboolean use_fontset = TRUE;
1279
1280         /* In multi-byte mode, GtkSText can't display 8bit characters
1281            correctly, so it must be single-byte mode. */
1282         if (MB_CUR_MAX > 1) {
1283                 if (codeset) {
1284                         if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1285                             !g_strcasecmp(codeset, "BALTIC"))
1286                                 use_fontset = FALSE;
1287                         else if (conv_get_current_charset() != C_EUC_JP &&
1288                                  (!g_strncasecmp(codeset, "KOI8-", 5) ||
1289                                   !g_strncasecmp(codeset, "CP", 2)    ||
1290                                   !g_strncasecmp(codeset, "WINDOWS-", 8)))
1291                                 use_fontset = FALSE;
1292                 }
1293         } else
1294                 use_fontset = FALSE;
1295
1296         if (textview->text_is_mb && !use_fontset) {
1297                 GtkWidget *parent;
1298
1299                 parent = textview->scrolledwin_mb->parent;
1300                 gtkut_container_remove(GTK_CONTAINER(parent),
1301                                        textview->scrolledwin_mb);
1302                 gtk_container_add(GTK_CONTAINER(parent),
1303                                   textview->scrolledwin_sb);
1304
1305                 textview->text = textview->text_sb;
1306                 textview->text_is_mb = FALSE;
1307         } else if (!textview->text_is_mb && use_fontset) {
1308                 GtkWidget *parent;
1309
1310                 parent = textview->scrolledwin_sb->parent;
1311                 gtkut_container_remove(GTK_CONTAINER(parent),
1312                                        textview->scrolledwin_sb);
1313                 gtk_container_add(GTK_CONTAINER(parent),
1314                                   textview->scrolledwin_mb);
1315
1316                 textview->text = textview->text_mb;
1317                 textview->text_is_mb = TRUE;
1318         }
1319
1320         if (prefs_common.textfont) {
1321                 GdkFont *font;
1322
1323                 if (use_fontset) {
1324                         if (text_mb_font) {
1325                                 text_mb_font->ascent = text_mb_font_orig_ascent;
1326                                 text_mb_font->descent = text_mb_font_orig_descent;
1327                         }
1328                         font = gdk_fontset_load(prefs_common.textfont);
1329                         if (font && text_mb_font != font) {
1330                                 if (text_mb_font)
1331                                         gdk_font_unref(text_mb_font);
1332                                 text_mb_font = font;
1333                                 text_mb_font_orig_ascent = font->ascent;
1334                                 text_mb_font_orig_descent = font->descent;
1335                         }
1336                 } else {
1337                         if (text_sb_font) {
1338                                 text_sb_font->ascent = text_sb_font_orig_ascent;
1339                                 text_sb_font->descent = text_sb_font_orig_descent;
1340                         }
1341                         if (MB_CUR_MAX > 1)
1342                                 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1343                         else
1344                                 font = gtkut_font_load_from_fontset
1345                                         (prefs_common.textfont);
1346                         if (font && text_sb_font != font) {
1347                                 if (text_sb_font)
1348                                         gdk_font_unref(text_sb_font);
1349                                 text_sb_font = font;
1350                                 text_sb_font_orig_ascent = font->ascent;
1351                                 text_sb_font_orig_descent = font->descent;
1352                         }
1353                 }
1354
1355                 if (font) {
1356                         gint ascent, descent;
1357
1358                         descent = prefs_common.line_space / 2;
1359                         ascent  = prefs_common.line_space - descent;
1360                         font->ascent  += ascent;
1361                         font->descent += descent;
1362
1363                         if (textview->msgfont)
1364                                 gdk_font_unref(textview->msgfont);
1365                         textview->msgfont = font;
1366                         gdk_font_ref(font);
1367                 }
1368         }
1369
1370         if (!textview->boldfont && prefs_common.boldfont)
1371                 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1372         if (!spacingfont)
1373                 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1374 }
1375
1376 enum
1377 {
1378         H_DATE          = 0,
1379         H_FROM          = 1,
1380         H_TO            = 2,
1381         H_NEWSGROUPS    = 3,
1382         H_SUBJECT       = 4,
1383         H_CC            = 5,
1384         H_REPLY_TO      = 6,
1385         H_FOLLOWUP_TO   = 7,
1386         H_X_MAILER      = 8,
1387         H_X_NEWSREADER  = 9,
1388         H_USER_AGENT    = 10,
1389         H_ORGANIZATION  = 11,
1390 };
1391
1392 void textview_set_position(TextView *textview, gint pos)
1393 {
1394         if (pos < 0) {
1395                 textview->cur_pos =
1396                         gtk_stext_get_length(GTK_STEXT(textview->text));
1397         } else {
1398                 textview->cur_pos = pos;
1399         }
1400 }
1401
1402 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1403 {
1404         gchar buf[BUFFSIZE];
1405         GPtrArray *headers, *sorted_headers;
1406         GSList *disphdr_list;
1407         Header *header;
1408         gint i;
1409
1410         g_return_val_if_fail(fp != NULL, NULL);
1411
1412         if (textview->show_all_headers)
1413                 return procheader_get_header_array_asis(fp);
1414
1415         if (!prefs_common.display_header) {
1416                 while (fgets(buf, sizeof(buf), fp) != NULL)
1417                         if (buf[0] == '\r' || buf[0] == '\n') break;
1418                 return NULL;
1419         }
1420
1421         headers = procheader_get_header_array_asis(fp);
1422
1423         sorted_headers = g_ptr_array_new();
1424
1425         for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1426              disphdr_list = disphdr_list->next) {
1427                 DisplayHeaderProp *dp =
1428                         (DisplayHeaderProp *)disphdr_list->data;
1429
1430                 for (i = 0; i < headers->len; i++) {
1431                         header = g_ptr_array_index(headers, i);
1432
1433                         if (procheader_headername_equal(header->name,
1434                                                         dp->name)) {
1435                                 if (dp->hidden)
1436                                         procheader_header_free(header);
1437                                 else
1438                                         g_ptr_array_add(sorted_headers, header);
1439
1440                                 g_ptr_array_remove_index(headers, i);
1441                                 i--;
1442                         }
1443                 }
1444         }
1445
1446         if (prefs_common.show_other_header) {
1447                 for (i = 0; i < headers->len; i++) {
1448                         header = g_ptr_array_index(headers, i);
1449                         g_ptr_array_add(sorted_headers, header);
1450                 }
1451                 g_ptr_array_free(headers, TRUE);
1452         } else
1453                 procheader_header_array_destroy(headers);
1454
1455         return sorted_headers;
1456 }
1457
1458 static void textview_show_header(TextView *textview, GPtrArray *headers)
1459 {
1460         GtkSText *text = GTK_STEXT(textview->text);
1461         Header *header;
1462         gint i;
1463
1464         g_return_if_fail(headers != NULL);
1465
1466         gtk_stext_freeze(text);
1467
1468         for (i = 0; i < headers->len; i++) {
1469                 header = g_ptr_array_index(headers, i);
1470                 g_return_if_fail(header->name != NULL);
1471
1472                 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1473                                 header->name, -1);
1474                 if (header->name[strlen(header->name) - 1] != ' ')
1475                         gtk_stext_insert(text, textview->boldfont,
1476                                         NULL, NULL, " ", 1);
1477
1478                 if (procheader_headername_equal(header->name, "Subject") ||
1479                     procheader_headername_equal(header->name, "From")    ||
1480                     procheader_headername_equal(header->name, "To")      ||
1481                     procheader_headername_equal(header->name, "Cc"))
1482                         unfold_line(header->body);
1483
1484                 if (prefs_common.enable_color &&
1485                     (procheader_headername_equal(header->name, "X-Mailer") ||
1486                      procheader_headername_equal(header->name,
1487                                                  "X-Newsreader")) &&
1488                     strstr(header->body, "Sylpheed") != NULL)
1489                         gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1490                                         header->body, -1);
1491                 else if (prefs_common.enable_color) {
1492                         textview_make_clickable_parts(textview,
1493                                                       NULL, NULL, &uri_color,
1494                                                       header->body);
1495                 } else {
1496                         textview_make_clickable_parts(textview,
1497                                                       NULL, NULL, NULL,
1498                                                       header->body);
1499                 }
1500                 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1501         }
1502
1503         gtk_stext_thaw(text);
1504 }
1505
1506 gboolean textview_search_string(TextView *textview, const gchar *str,
1507                                 gboolean case_sens)
1508 {
1509         GtkSText *text = GTK_STEXT(textview->text);
1510         gint pos;
1511         wchar_t *wcs;
1512         gint len;
1513         gint text_len;
1514         gboolean found = FALSE;
1515
1516         g_return_val_if_fail(str != NULL, FALSE);
1517
1518         wcs = strdup_mbstowcs(str);
1519         g_return_val_if_fail(wcs != NULL, FALSE);
1520         len = wcslen(wcs);
1521         pos = textview->cur_pos;
1522         if (pos < textview->body_pos)
1523                 pos = textview->body_pos;
1524         text_len = gtk_stext_get_length(text);
1525         if (text_len - pos < len) {
1526                 g_free(wcs);
1527                 return FALSE;
1528         }
1529
1530         for (; pos < text_len; pos++) {
1531                 if (text_len - pos < len) break;
1532                 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1533                     == TRUE) {
1534                         gtk_widget_hide(GTK_WIDGET(textview->scrolledwin));
1535                         gtk_editable_set_position(GTK_EDITABLE(text),
1536                                                   pos + len);
1537                         gtk_editable_select_region(GTK_EDITABLE(text),
1538                                                    pos, pos + len);
1539                         gtk_widget_show(GTK_WIDGET(textview->scrolledwin));
1540                         textview_set_position(textview, pos + len);
1541                         found = TRUE;
1542                         break;
1543                 }
1544                 if (text_len - pos == len) break;
1545         }
1546         g_free(wcs);
1547         return found;
1548 }
1549
1550 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1551                                          gboolean case_sens)
1552 {
1553         GtkSText *text = GTK_STEXT(textview->text);
1554         gint pos;
1555         wchar_t *wcs;
1556         gint len;
1557         gint text_len;
1558         gboolean found = FALSE;
1559
1560         g_return_val_if_fail(str != NULL, FALSE);
1561
1562         wcs = strdup_mbstowcs(str);
1563         g_return_val_if_fail(wcs != NULL, FALSE);
1564         len = wcslen(wcs);
1565         pos = textview->cur_pos;
1566         text_len = gtk_stext_get_length(text);
1567         if (text_len - textview->body_pos < len) {
1568                 g_free(wcs);
1569                 return FALSE;
1570         }
1571         if (pos <= textview->body_pos || text_len - pos < len)
1572                 pos = text_len - len;
1573
1574         for (; pos >= textview->body_pos; pos--) {
1575                 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1576                     == TRUE) {
1577                         gtk_editable_set_position(GTK_EDITABLE(text), pos);
1578                         gtk_editable_select_region(GTK_EDITABLE(text),
1579                                                    pos, pos + len);
1580                         textview_set_position(textview, pos - 1);
1581                         found = TRUE;
1582                         break;
1583                 }
1584                 if (pos == textview->body_pos) break;
1585         }
1586
1587         g_free(wcs);
1588         return found;
1589 }
1590
1591 void textview_scroll_one_line(TextView *textview, gboolean up)
1592 {
1593         GtkSText *text = GTK_STEXT(textview->text);
1594         gfloat upper;
1595
1596         if (prefs_common.enable_smooth_scroll) {
1597                 textview_smooth_scroll_one_line(textview, up);
1598                 return;
1599         }
1600
1601         if (!up) {
1602                 upper = text->vadj->upper - text->vadj->page_size;
1603                 if (text->vadj->value < upper) {
1604                         text->vadj->value +=
1605                                 text->vadj->step_increment * 4;
1606                         text->vadj->value =
1607                                 MIN(text->vadj->value, upper);
1608                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1609                                                 "value_changed");
1610                 }
1611         } else {
1612                 if (text->vadj->value > 0.0) {
1613                         text->vadj->value -=
1614                                 text->vadj->step_increment * 4;
1615                         text->vadj->value =
1616                                 MAX(text->vadj->value, 0.0);
1617                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1618                                                 "value_changed");
1619                 }
1620         }
1621 }
1622
1623 gboolean textview_scroll_page(TextView *textview, gboolean up)
1624 {
1625         GtkSText *text = GTK_STEXT(textview->text);
1626         gfloat upper;
1627         gfloat page_incr;
1628
1629         if (prefs_common.enable_smooth_scroll)
1630                 return textview_smooth_scroll_page(textview, up);
1631
1632         if (prefs_common.scroll_halfpage)
1633                 page_incr = text->vadj->page_increment / 2;
1634         else
1635                 page_incr = text->vadj->page_increment;
1636
1637         if (!up) {
1638                 upper = text->vadj->upper - text->vadj->page_size;
1639                 if (text->vadj->value < upper) {
1640                         text->vadj->value += page_incr;
1641                         text->vadj->value = MIN(text->vadj->value, upper);
1642                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1643                                                 "value_changed");
1644                 } else
1645                         return FALSE;
1646         } else {
1647                 if (text->vadj->value > 0.0) {
1648                         text->vadj->value -= page_incr;
1649                         text->vadj->value = MAX(text->vadj->value, 0.0);
1650                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1651                                                 "value_changed");
1652                 } else
1653                         return FALSE;
1654         }
1655
1656         return TRUE;
1657 }
1658
1659 static void textview_smooth_scroll_do(TextView *textview,
1660                                       gfloat old_value, gfloat last_value,
1661                                       gint step)
1662 {
1663         GtkSText *text = GTK_STEXT(textview->text);
1664         gint change_value;
1665         gboolean up;
1666         gint i;
1667
1668         if (old_value < last_value) {
1669                 change_value = last_value - old_value;
1670                 up = FALSE;
1671         } else {
1672                 change_value = old_value - last_value;
1673                 up = TRUE;
1674         }
1675
1676         gdk_key_repeat_disable();
1677
1678         for (i = step; i <= change_value; i += step) {
1679                 text->vadj->value = old_value + (up ? -i : i);
1680                 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1681                                         "value_changed");
1682         }
1683
1684         text->vadj->value = last_value;
1685         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1686
1687         gdk_key_repeat_restore();
1688 }
1689
1690 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1691 {
1692         GtkSText *text = GTK_STEXT(textview->text);
1693         gfloat upper;
1694         gfloat old_value;
1695         gfloat last_value;
1696
1697         if (!up) {
1698                 upper = text->vadj->upper - text->vadj->page_size;
1699                 if (text->vadj->value < upper) {
1700                         old_value = text->vadj->value;
1701                         last_value = text->vadj->value +
1702                                 text->vadj->step_increment * 4;
1703                         last_value = MIN(last_value, upper);
1704
1705                         textview_smooth_scroll_do(textview, old_value,
1706                                                   last_value,
1707                                                   prefs_common.scroll_step);
1708                 }
1709         } else {
1710                 if (text->vadj->value > 0.0) {
1711                         old_value = text->vadj->value;
1712                         last_value = text->vadj->value -
1713                                 text->vadj->step_increment * 4;
1714                         last_value = MAX(last_value, 0.0);
1715
1716                         textview_smooth_scroll_do(textview, old_value,
1717                                                   last_value,
1718                                                   prefs_common.scroll_step);
1719                 }
1720         }
1721 }
1722
1723 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1724 {
1725         GtkSText *text = GTK_STEXT(textview->text);
1726         gfloat upper;
1727         gfloat page_incr;
1728         gfloat old_value;
1729         gfloat last_value;
1730
1731         if (prefs_common.scroll_halfpage)
1732                 page_incr = text->vadj->page_increment / 2;
1733         else
1734                 page_incr = text->vadj->page_increment;
1735
1736         if (!up) {
1737                 upper = text->vadj->upper - text->vadj->page_size;
1738                 if (text->vadj->value < upper) {
1739                         old_value = text->vadj->value;
1740                         last_value = text->vadj->value + page_incr;
1741                         last_value = MIN(last_value, upper);
1742
1743                         textview_smooth_scroll_do(textview, old_value,
1744                                                   last_value,
1745                                                   prefs_common.scroll_step);
1746                 } else
1747                         return FALSE;
1748         } else {
1749                 if (text->vadj->value > 0.0) {
1750                         old_value = text->vadj->value;
1751                         last_value = text->vadj->value - page_incr;
1752                         last_value = MAX(last_value, 0.0);
1753
1754                         textview_smooth_scroll_do(textview, old_value,
1755                                                   last_value,
1756                                                   prefs_common.scroll_step);
1757                 } else
1758                         return FALSE;
1759         }
1760
1761         return TRUE;
1762 }
1763
1764 #define KEY_PRESS_EVENT_STOP() \
1765         if (gtk_signal_n_emissions_by_name \
1766                 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1767                 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1768                                              "key_press_event"); \
1769         }
1770
1771 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1772                                  TextView *textview)
1773 {
1774         SummaryView *summaryview = NULL;
1775         MessageView *messageview = textview->messageview;
1776
1777         if (!event) return FALSE;
1778         if (messageview->mainwin)
1779                 summaryview = messageview->mainwin->summaryview;
1780
1781         switch (event->keyval) {
1782         case GDK_Tab:
1783         case GDK_Home:
1784         case GDK_Left:
1785         case GDK_Up:
1786         case GDK_Right:
1787         case GDK_Down:
1788         case GDK_Page_Up:
1789         case GDK_Page_Down:
1790         case GDK_End:
1791         case GDK_Control_L:
1792         case GDK_Control_R:
1793                 break;
1794         case GDK_space:
1795                 if (summaryview)
1796                         summary_pass_key_press_event(summaryview, event);
1797                 else
1798                         textview_scroll_page(textview, FALSE);
1799                 break;
1800         case GDK_BackSpace:
1801                 textview_scroll_page(textview, TRUE);
1802                 break;
1803         case GDK_Return:
1804                 textview_scroll_one_line(textview,
1805                                          (event->state & GDK_MOD1_MASK) != 0);
1806                 break;
1807         case GDK_Delete:
1808                 if (summaryview)
1809                         summary_pass_key_press_event(summaryview, event);
1810                 break;
1811         case GDK_n:
1812         case GDK_N:
1813         case GDK_p:
1814         case GDK_P:
1815         case GDK_y:
1816         case GDK_t:
1817         case GDK_l:
1818                 if (messageview->type == MVIEW_MIME &&
1819                     textview == messageview->mimeview->textview) {
1820                         KEY_PRESS_EVENT_STOP();
1821                         mimeview_pass_key_press_event(messageview->mimeview,
1822                                                       event);
1823                         break;
1824                 }
1825                 /* fall through */
1826         default:
1827                 if (summaryview &&
1828                     event->window != messageview->mainwin->window->window) {
1829                         GdkEventKey tmpev = *event;
1830
1831                         tmpev.window = messageview->mainwin->window->window;
1832                         KEY_PRESS_EVENT_STOP();
1833                         gtk_widget_event(messageview->mainwin->window,
1834                                          (GdkEvent *)&tmpev);
1835                 }
1836                 break;
1837         }
1838
1839         return TRUE;
1840 }
1841
1842 static gint show_url_timeout_cb(gpointer data)
1843 {
1844         TextView *textview = (TextView *)data;
1845         
1846         if (textview->messageview->mainwin)
1847                 if (textview->show_url_msgid)
1848                         gtk_statusbar_remove(GTK_STATUSBAR(
1849                                 textview->messageview->mainwin->statusbar),
1850                                 textview->messageview->mainwin->folderview_cid,
1851                                 textview->show_url_msgid);
1852                 return FALSE;
1853 }
1854
1855 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1856                                     TextView *textview)
1857 {
1858         if (event)
1859                 textview->last_buttonpress = event->type;
1860         return FALSE;
1861 }
1862
1863 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1864                                     TextView *textview)
1865 {
1866         textview->cur_pos = 
1867                 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1868
1869         if (event && 
1870             ((event->button == 1)
1871              || event->button == 2 || event->button == 3)) {
1872                 GSList *cur;
1873
1874                 /* double click seems to set the cursor after the current
1875                  * word. The cursor position needs fixing, otherwise the
1876                  * last word of a clickable zone will not work */
1877                 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1878                         textview->cur_pos--;
1879                 }
1880
1881                 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1882                         RemoteURI *uri = (RemoteURI *)cur->data;
1883
1884                         if (textview->cur_pos >= uri->start &&
1885                             textview->cur_pos <  uri->end) {
1886                                 /* single click: display url in statusbar */
1887                                 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1888                                         if (textview->messageview->mainwin) {
1889                                                 if (textview->show_url_msgid) {
1890                                                         gtk_timeout_remove(textview->show_url_timeout_tag);
1891                                                         gtk_statusbar_remove(GTK_STATUSBAR(
1892                                                                 textview->messageview->mainwin->statusbar),
1893                                                                 textview->messageview->mainwin->folderview_cid,
1894                                                                 textview->show_url_msgid);
1895                                                         textview->show_url_msgid = 0;
1896                                                 }
1897                                                 textview->show_url_msgid = gtk_statusbar_push(
1898                                                                 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1899                                                                 textview->messageview->mainwin->folderview_cid,
1900                                                                 uri->uri);
1901                                                 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1902                                                 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1903                                         }
1904                                 } else
1905                                 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1906                                         if (event->button == 3) {
1907                                                 gchar *fromname, *fromaddress;
1908                                                 
1909                                                 /* extract url */
1910                                                 fromaddress = g_strdup(uri->uri + 7);
1911                                                 /* Hiroyuki: please put this function in utils.c! */
1912                                                 fromname = procheader_get_fromname(fromaddress);
1913                                                 extract_address(fromaddress);
1914                                                 g_message("adding from textview %s <%s>", fromname, fromaddress);
1915                                                 /* Add to address book - Match */
1916                                                 addressbook_add_contact( fromname, fromaddress, NULL );
1917                                                 
1918                                                 g_free(fromaddress);
1919                                                 g_free(fromname);
1920                                         } else {
1921                                                 PrefsAccount *account = NULL;
1922                                                 FolderItem   *folder_item;
1923
1924                                                 if (textview->messageview && textview->messageview->mainwin 
1925                                                 &&  textview->messageview->mainwin->summaryview 
1926                                                 &&  textview->messageview->mainwin->summaryview->folder_item) {
1927                                                         folder_item = textview->messageview->mainwin->summaryview->folder_item;
1928                                                         if (folder_item->prefs && folder_item->prefs->enable_default_account)
1929                                                                 account = account_find_from_id(folder_item->prefs->default_account);
1930                                                 }
1931                                                 compose_new(account, uri->uri + 7, NULL);
1932                                         }
1933                                 } else 
1934 #if USE_GPGME
1935                                 if (!g_strncasecmp(uri->uri, "pgp:", 4)) {
1936                                         GtkAdjustment *pos = gtk_scrolled_window_get_vadjustment(
1937                                                                 GTK_SCROLLED_WINDOW(textview->scrolledwin));
1938                                         gfloat vpos = pos->value;
1939                                         mimeview_check_signature(textview->messageview->mimeview);
1940                                         /* scroll back where we were */
1941                                         gtk_adjustment_set_value(pos, vpos);
1942                                 } else
1943 #endif
1944                                 {
1945                                         open_uri(uri->uri,
1946                                                  prefs_common.uri_cmd);
1947                                 }
1948                         }
1949                 }
1950         }
1951         if (event)
1952                 textview->last_buttonpress = event->type;
1953         return FALSE;
1954 }
1955
1956 static void textview_uri_list_remove_all(GSList *uri_list)
1957 {
1958         GSList *cur;
1959
1960         for (cur = uri_list; cur != NULL; cur = cur->next) {
1961                 if (cur->data) {
1962                         g_free(((RemoteURI *)cur->data)->uri);
1963                         g_free(cur->data);
1964                 }
1965         }
1966
1967         g_slist_free(uri_list);
1968 }