sync with sylpheed 0.7.4cvs2
[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
54 #define FONT_LOAD(font, s) \
55 { \
56         gchar *fontstr, *p; \
57  \
58         Xstrdup_a(fontstr, s, ); \
59         if ((p = strchr(fontstr, ',')) != NULL) *p = '\0'; \
60         font = gdk_font_load(fontstr); \
61         if (!font) \
62                 g_warning("Couldn't load the font '%s'\n", fontstr); \
63 }
64
65 typedef struct _RemoteURI       RemoteURI;
66
67 struct _RemoteURI
68 {
69         gchar *uri;
70
71         guint start;
72         guint end;
73 };
74
75 static GdkColor quote_colors[3] = {
76         {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
77         {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
78         {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
79 };
80
81 static GdkColor uri_color = {
82         (gulong)0,
83         (gushort)0,
84         (gushort)0,
85         (gushort)0
86 };
87
88 static GdkColor emphasis_color = {
89         (gulong)0,
90         (gushort)0,
91         (gushort)0,
92         (gushort)0xcfff
93 };
94
95 #if 0
96 static GdkColor error_color = {
97         (gulong)0,
98         (gushort)0xefff,
99         (gushort)0,
100         (gushort)0
101 };
102 #endif
103
104 static GdkFont *text_sb_font;
105 static GdkFont *text_mb_font;
106 static gint text_sb_font_orig_ascent;
107 static gint text_sb_font_orig_descent;
108 static gint text_mb_font_orig_ascent;
109 static gint text_mb_font_orig_descent;
110 static GdkFont *spacingfont;
111
112 static void textview_show_ertf          (TextView       *textview,
113                                          FILE           *fp,
114                                          CodeConverter  *conv);
115 static void textview_add_part           (TextView       *textview,
116                                          MimeInfo       *mimeinfo,
117                                          FILE           *fp);
118 static void textview_write_body         (TextView       *textview,
119                                          MimeInfo       *mimeinfo,
120                                          FILE           *fp,
121                                          const gchar    *charset);
122 static void textview_show_html          (TextView       *textview,
123                                          FILE           *fp,
124                                          CodeConverter  *conv);
125 static void textview_write_line         (TextView       *textview,
126                                          const gchar    *str,
127                                          CodeConverter  *conv);
128 static void textview_write_link         (TextView       *textview,
129                                          const gchar    *url,
130                                          const gchar    *str,
131                                          CodeConverter  *conv);
132 static GPtrArray *textview_scan_header  (TextView       *textview,
133                                          FILE           *fp);
134 static void textview_show_header        (TextView       *textview,
135                                          GPtrArray      *headers);
136
137 static gint textview_key_pressed        (GtkWidget      *widget,
138                                          GdkEventKey    *event,
139                                          TextView       *textview);
140 static gint textview_button_pressed     (GtkWidget      *widget,
141                                          GdkEventButton *event,
142                                          TextView       *textview);
143 static gint textview_button_released    (GtkWidget      *widget,
144                                          GdkEventButton *event,
145                                          TextView       *textview);
146
147 static void textview_uri_list_remove_all(GSList         *uri_list);
148
149 static void textview_smooth_scroll_do           (TextView       *textview,
150                                                  gfloat          old_value,
151                                                  gfloat          last_value,
152                                                  gint            step);
153 static void textview_smooth_scroll_one_line     (TextView       *textview,
154                                                  gboolean        up);
155 static gboolean textview_smooth_scroll_page     (TextView       *textview,
156                                                  gboolean        up);
157
158
159 TextView *textview_create(void)
160 {
161         TextView *textview;
162         GtkWidget *vbox;
163         GtkWidget *scrolledwin_sb;
164         GtkWidget *scrolledwin_mb;
165         GtkWidget *text_sb;
166         GtkWidget *text_mb;
167
168         debug_print(_("Creating text view...\n"));
169         textview = g_new0(TextView, 1);
170
171         scrolledwin_sb = gtk_scrolled_window_new(NULL, NULL);
172         scrolledwin_mb = gtk_scrolled_window_new(NULL, NULL);
173         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_sb),
174                                        GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
175         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_mb),
176                                        GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
177         gtk_widget_set_usize(scrolledwin_sb, prefs_common.mainview_width, -1);
178         gtk_widget_set_usize(scrolledwin_mb, prefs_common.mainview_width, -1);
179
180         /* create GtkSText widgets for single-byte and multi-byte character */
181         text_sb = gtk_stext_new(NULL, NULL);
182         text_mb = gtk_stext_new(NULL, NULL);
183         GTK_STEXT(text_sb)->default_tab_width = 8;
184         GTK_STEXT(text_mb)->default_tab_width = 8;
185         gtk_widget_show(text_sb);
186         gtk_widget_show(text_mb);
187         gtk_stext_set_word_wrap(GTK_STEXT(text_sb), TRUE);
188         gtk_stext_set_word_wrap(GTK_STEXT(text_mb), TRUE);
189         gtk_widget_ensure_style(text_sb);
190         gtk_widget_ensure_style(text_mb);
191         if (text_sb->style && text_sb->style->font->type == GDK_FONT_FONTSET) {
192                 GtkStyle *style;
193                 GdkFont *font;
194
195                 FONT_LOAD(font, prefs_common.normalfont);
196                 if (font) {
197                         style = gtk_style_copy(text_sb->style);
198                         gdk_font_unref(style->font);
199                         style->font = font;
200                         gtk_widget_set_style(text_sb, style);
201                 }
202         }
203         if (text_mb->style && text_mb->style->font->type == GDK_FONT_FONT) {
204                 GtkStyle *style;
205                 GdkFont *font;
206
207                 font = gdk_fontset_load(prefs_common.normalfont);
208                 if (font) {
209                         style = gtk_style_copy(text_mb->style);
210                         gdk_font_unref(style->font);
211                         style->font = font;
212                         gtk_widget_set_style(text_mb, style);
213                 }
214         }
215         gtk_widget_ref(scrolledwin_sb);
216         gtk_widget_ref(scrolledwin_mb);
217
218         gtk_container_add(GTK_CONTAINER(scrolledwin_sb), text_sb);
219         gtk_container_add(GTK_CONTAINER(scrolledwin_mb), text_mb);
220         gtk_signal_connect(GTK_OBJECT(text_sb), "key_press_event",
221                            GTK_SIGNAL_FUNC(textview_key_pressed),
222                            textview);
223         gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_press_event",
224                                  GTK_SIGNAL_FUNC(textview_button_pressed),
225                                  textview);
226         gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_release_event",
227                                  GTK_SIGNAL_FUNC(textview_button_released),
228                                  textview);
229         gtk_signal_connect(GTK_OBJECT(text_mb), "key_press_event",
230                            GTK_SIGNAL_FUNC(textview_key_pressed),
231                            textview);
232         gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_press_event",
233                                  GTK_SIGNAL_FUNC(textview_button_pressed),
234                                  textview);
235         gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_release_event",
236                                  GTK_SIGNAL_FUNC(textview_button_released),
237                                  textview);
238
239         gtk_widget_show(scrolledwin_sb);
240         gtk_widget_show(scrolledwin_mb);
241
242         vbox = gtk_vbox_new(FALSE, 0);
243         gtk_box_pack_start(GTK_BOX(vbox), scrolledwin_sb, TRUE, TRUE, 0);
244
245         gtk_widget_show(vbox);
246
247         textview->vbox             = vbox;
248         textview->scrolledwin      = scrolledwin_sb;
249         textview->scrolledwin_sb   = scrolledwin_sb;
250         textview->scrolledwin_mb   = scrolledwin_mb;
251         textview->text             = text_sb;
252         textview->text_sb          = text_sb;
253         textview->text_mb          = text_mb;
254         textview->text_is_mb       = FALSE;
255         textview->uri_list         = NULL;
256         textview->body_pos         = 0;
257         textview->cur_pos          = 0;
258         textview->show_all_headers = FALSE;
259         textview->last_buttonpress = GDK_NOTHING;
260
261         return textview;
262 }
263
264 void textview_init(TextView *textview)
265 {
266         gtkut_widget_disable_theme_engine(textview->text_sb);
267         gtkut_widget_disable_theme_engine(textview->text_mb);
268         textview_update_message_colors();
269         textview_set_all_headers(textview, FALSE);
270         textview_set_font(textview, NULL);
271 }
272
273 void textview_update_message_colors(void)
274 {
275         GdkColor black = {0, 0, 0, 0};
276
277         if (prefs_common.enable_color) {
278                 /* grab the quote colors, converting from an int to a GdkColor */
279                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
280                                                &quote_colors[0]);
281                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
282                                                &quote_colors[1]);
283                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
284                                                &quote_colors[2]);
285                 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
286                                                &uri_color);
287         } else {
288                 quote_colors[0] = quote_colors[1] = quote_colors[2] = 
289                         uri_color = emphasis_color = black;
290         }
291 }
292
293 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
294                            const gchar *file)
295 {
296         GtkSText *text = GTK_STEXT(textview->text);
297         FILE *fp;
298         const gchar *charset = NULL;
299         GPtrArray *headers = NULL;
300
301         if ((fp = fopen(file, "rb")) == NULL) {
302                 FILE_OP_ERROR(file, "fopen");
303                 return;
304         }
305
306         if (prefs_common.force_charset)
307                 charset = prefs_common.force_charset;
308         else if (mimeinfo->charset)
309                 charset = mimeinfo->charset;
310         textview_set_font(textview, charset);
311         textview_clear(textview);
312         textview->body_pos = 0;
313         textview->cur_pos  = 0;
314
315         gtk_stext_freeze(text);
316
317         if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) perror("fseek");
318         headers = textview_scan_header(textview, fp);
319         if (headers) {
320                 textview_show_header(textview, headers);
321                 procheader_header_array_destroy(headers);
322                 textview->body_pos = gtk_stext_get_length(text);
323         }
324
325         while (mimeinfo != NULL) {
326                 textview_add_part(textview, mimeinfo, fp);
327                 if (mimeinfo->parent && mimeinfo->parent->content_type &&
328                     !strcasecmp(mimeinfo->parent->content_type,
329                                 "multipart/alternative"))
330                         mimeinfo = mimeinfo->parent->next;
331                 else
332                         mimeinfo = procmime_mimeinfo_next(mimeinfo);
333         }
334
335         gtk_stext_thaw(text);
336
337         fclose(fp);
338 }
339
340 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
341 {
342         GtkSText *text = GTK_STEXT(textview->text);
343         gchar buf[BUFFSIZE];
344         const gchar *boundary = NULL;
345         gint boundary_len = 0;
346         const gchar *charset = NULL;
347         GPtrArray *headers = NULL;
348
349         g_return_if_fail(mimeinfo != NULL);
350         g_return_if_fail(fp != NULL);
351
352         if (mimeinfo->mime_type == MIME_MULTIPART) return;
353
354         if (mimeinfo->parent && mimeinfo->parent->boundary) {
355                 boundary = mimeinfo->parent->boundary;
356                 boundary_len = strlen(boundary);
357         }
358
359         if (!boundary && (mimeinfo->mime_type == MIME_TEXT || 
360                           mimeinfo->mime_type == MIME_TEXT_HTML || 
361                           mimeinfo->mime_type == MIME_TEXT_ENRICHED)) {
362         
363                 if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0)
364                         perror("fseek");
365                 headers = textview_scan_header(textview, fp);
366         } else {
367                 if (mimeinfo->mime_type == MIME_TEXT && mimeinfo->parent) {
368                         glong fpos;
369                         MimeInfo *parent = mimeinfo->parent;
370
371                         while (parent->parent) {
372                                 if (parent->main &&
373                                     parent->main->mime_type ==
374                                         MIME_MESSAGE_RFC822)
375                                         break;
376                                 parent = parent->parent;
377                         }
378
379                         if ((fpos = ftell(fp)) < 0)
380                                 perror("ftell");
381                         else if (fseek(fp, parent->fpos, SEEK_SET) < 0)
382                                 perror("fseek");
383                         else {
384                                 headers = textview_scan_header(textview, fp);
385                                 if (fseek(fp, fpos, SEEK_SET) < 0)
386                                         perror("fseek");
387                         }
388                 }
389                 /* skip MIME part headers */
390                 while (fgets(buf, sizeof(buf), fp) != NULL)
391                         if (buf[0] == '\r' || buf[0] == '\n') break;
392         }
393
394         /* display attached RFC822 single text message */
395         if (mimeinfo->parent && mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
396                 if (headers) procheader_header_array_destroy(headers);
397                 if (!mimeinfo->sub || mimeinfo->sub->children) return;
398                 headers = textview_scan_header(textview, fp);
399                 mimeinfo = mimeinfo->sub;
400         } else if (!mimeinfo->parent &&
401                    mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
402                 if (headers) procheader_header_array_destroy(headers);
403                 if (!mimeinfo->sub) return;
404                 headers = textview_scan_header(textview, fp);
405                 mimeinfo = mimeinfo->sub;
406         }
407
408         if (prefs_common.force_charset)
409                 charset = prefs_common.force_charset;
410         else if (mimeinfo->charset)
411                 charset = mimeinfo->charset;
412         textview_set_font(textview, charset);
413
414         textview_clear(textview);
415         gtk_stext_freeze(text);
416
417         textview->body_pos = 0;
418         textview->cur_pos  = 0;
419
420         if (headers) {
421                 textview_show_header(textview, headers);
422                 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
423                 procheader_header_array_destroy(headers);
424                 textview->body_pos = gtk_stext_get_length(text);
425         }
426
427         textview_write_body(textview, mimeinfo, fp, charset);
428
429         gtk_stext_thaw(text);
430 }
431
432 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
433 {
434         GtkSText *text = GTK_STEXT(textview->text);
435         gchar buf[BUFFSIZE];
436         const gchar *boundary = NULL;
437         gint boundary_len = 0;
438         const gchar *charset = NULL;
439         GPtrArray *headers = NULL;
440
441         g_return_if_fail(mimeinfo != NULL);
442         g_return_if_fail(fp != NULL);
443
444         if (mimeinfo->mime_type == MIME_MULTIPART) return;
445
446         if (!mimeinfo->parent &&
447             mimeinfo->mime_type != MIME_TEXT &&
448             mimeinfo->mime_type != MIME_TEXT_HTML &&
449             mimeinfo->mime_type != MIME_TEXT_ENRICHED)
450                 return;
451
452         if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) {
453                 perror("fseek");
454                 return;
455         }
456
457         if (mimeinfo->parent && mimeinfo->parent->boundary) {
458                 boundary = mimeinfo->parent->boundary;
459                 boundary_len = strlen(boundary);
460         }
461
462         while (fgets(buf, sizeof(buf), fp) != NULL)
463                 if (buf[0] == '\r' || buf[0] == '\n') break;
464
465         if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
466                 headers = textview_scan_header(textview, fp);
467                 if (headers) {
468                         gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
469                         textview_show_header(textview, headers);
470                         procheader_header_array_destroy(headers);
471                 }
472                 return;
473         }
474
475         gtk_stext_freeze(text);
476
477         if (mimeinfo->filename || mimeinfo->name)
478                 g_snprintf(buf, sizeof(buf), "\n[%s  %s (%d bytes)]\n",
479                            mimeinfo->filename ? mimeinfo->filename :
480                            mimeinfo->name,
481                            mimeinfo->content_type, mimeinfo->size);
482         else
483                 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
484                            mimeinfo->content_type, mimeinfo->size);
485
486         if (mimeinfo->mime_type != MIME_TEXT &&
487             mimeinfo->mime_type != MIME_TEXT_HTML &&
488             mimeinfo->mime_type != MIME_TEXT_ENRICHED) {
489                 gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
490         } else {
491                 if (!mimeinfo->main &&
492                     mimeinfo->parent &&
493                     mimeinfo->parent->children != mimeinfo)
494                         gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
495                 else
496                         gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
497                 if (prefs_common.force_charset)
498                         charset = prefs_common.force_charset;
499                 else if (mimeinfo->charset)
500                         charset = mimeinfo->charset;
501                 textview_write_body(textview, mimeinfo, fp, charset);
502         }
503
504         gtk_stext_thaw(text);
505 }
506
507 #define TEXT_INSERT(str) \
508         gtk_stext_insert(text, textview->msgfont, NULL, NULL, str, -1)
509
510 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
511 {
512         GtkSText *text;
513
514         if (!partinfo) return;
515
516         textview_set_font(textview, NULL);
517         text = GTK_STEXT(textview->text);
518         textview_clear(textview);
519
520         gtk_stext_freeze(text);
521
522         TEXT_INSERT(_("To save this part, pop up the context menu with "));
523         TEXT_INSERT(_("right click and select `Save as...', "));
524         TEXT_INSERT(_("or press `y' key.\n\n"));
525
526         TEXT_INSERT(_("To display this part as a text message, select "));
527         TEXT_INSERT(_("`Display as text', or press `t' key.\n\n"));
528
529         TEXT_INSERT(_("To open this part with external program, select "));
530         TEXT_INSERT(_("`Open' or `Open with...', "));
531         TEXT_INSERT(_("or double-click, or click the center button, "));
532         TEXT_INSERT(_("or press `l' key."));
533
534         gtk_stext_thaw(text);
535 }
536
537 #if USE_GPGME
538 void textview_show_signature_part(TextView *textview, MimeInfo *partinfo)
539 {
540         GtkSText *text;
541
542         if (!partinfo) return;
543
544         textview_set_font(textview, NULL);
545         text = GTK_STEXT(textview->text);
546         textview_clear(textview);
547
548         gtk_stext_freeze(text);
549
550         if (partinfo->sigstatus_full == NULL) {
551                 TEXT_INSERT(_("This signature has not been checked yet.\n"));
552                 TEXT_INSERT(_("To check it, pop up the context menu with\n"));
553                 TEXT_INSERT(_("right click and select `Check signature'.\n"));
554         } else {
555                 TEXT_INSERT(partinfo->sigstatus_full);
556         }
557
558         gtk_stext_thaw(text);
559 }
560 #endif /* USE_GPGME */
561
562 #undef TEXT_INSERT
563
564 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
565                                 FILE *fp, const gchar *charset)
566 {
567         FILE *tmpfp;
568         gchar buf[BUFFSIZE];
569         CodeConverter *conv;
570
571         conv = conv_code_converter_new(charset);
572
573         tmpfp = procmime_decode_content(NULL, fp, mimeinfo);
574         if (tmpfp) {
575                 if (mimeinfo->mime_type == MIME_TEXT_HTML)
576                         textview_show_html(textview, tmpfp, conv);
577                 else if (mimeinfo->mime_type == MIME_TEXT_ENRICHED)
578                         textview_show_ertf(textview, tmpfp, conv);
579                 else
580                         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
581                                 textview_write_line(textview, buf, conv);
582                 fclose(tmpfp);
583         }
584
585         conv_code_converter_destroy(conv);
586 }
587
588 static void textview_show_html(TextView *textview, FILE *fp,
589                                CodeConverter *conv)
590 {
591         HTMLParser *parser;
592         gchar *str;
593         gchar* url = NULL;
594
595         parser = html_parser_new(fp, conv);
596         g_return_if_fail(parser != NULL);
597
598         while ((str = html_parse(parser)) != NULL) {
599                 if (parser->state == HTML_HREF) {
600                         /* first time : get and copy the URL */
601                         if (url == NULL) {
602                                 /* ALF - the sylpheed html parser returns an empty string,
603                                  * if still inside an <a>, but already parsed past HREF */
604                                 str = strtok(str, " ");
605                                 if (str) { 
606                                         url = strdup(str);
607                                         /* the URL may (or not) be followed by the
608                                          * referenced text */
609                                         str = strtok(NULL, "");
610                                 }       
611                         }
612                         if (str != NULL) {
613                                 textview_write_link(textview, url, str, NULL);
614                         }
615                 } else {
616                         if (url != NULL) {
617                                 free(url);
618                                 url = NULL;
619                         }
620                         textview_write_line(textview, str, NULL);
621                 }
622         }
623         html_parser_destroy(parser);
624 }
625
626 static void textview_show_ertf(TextView *textview, FILE *fp,
627                                CodeConverter *conv)
628 {
629         ERTFParser *parser;
630         gchar *str;
631         gchar* url = NULL;
632
633         parser = ertf_parser_new(fp, conv);
634         g_return_if_fail(parser != NULL);
635
636         while ((str = ertf_parse(parser)) != NULL) {
637                 textview_write_line(textview, str, NULL);
638         }
639         ertf_parser_destroy(parser);
640 }
641
642 /* get_uri_part() - retrieves a URI starting from scanpos.
643                     Returns TRUE if succesful */
644 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
645                              const gchar **bp, const gchar **ep)
646 {
647         const gchar *ep_;
648
649         g_return_val_if_fail(start != NULL, FALSE);
650         g_return_val_if_fail(scanpos != NULL, FALSE);
651         g_return_val_if_fail(bp != NULL, FALSE);
652         g_return_val_if_fail(ep != NULL, FALSE);
653
654         *bp = scanpos;
655
656         /* find end point of URI */
657         for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
658                 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
659                         break;
660         }
661
662         /* no punctuation at end of string */
663
664         /* FIXME: this stripping of trailing punctuations may bite with other URIs.
665          * should pass some URI type to this function and decide on that whether
666          * to perform punctuation stripping */
667
668 #define IS_REAL_PUNCT(ch)       (ispunct(ch) && ((ch) != '/')) 
669
670         for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
671                 ;
672
673 #undef IS_REAL_PUNCT
674
675         *ep = ep_;
676
677         return TRUE;            
678 }
679
680 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
681 {
682         return g_strndup(bp, ep - bp);
683 }
684
685 /* valid mail address characters */
686 #define IS_RFC822_CHAR(ch) \
687         (isascii(ch) && \
688          (ch) > 32   && \
689          (ch) != 127 && \
690          !isspace(ch) && \
691          !strchr("()<>\"", (ch)))
692
693 /* alphabet and number within 7bit ASCII */
694 #define IS_ASCII_ALNUM(ch)      (isascii(ch) && isalnum(ch))
695 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
696
697 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
698 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
699                                const gchar **bp, const gchar **ep)
700 {
701         /* more complex than the uri part because we need to scan back and forward starting from
702          * the scan position. */
703         gboolean result = FALSE;
704         const gchar *bp_ = NULL;
705         const gchar *ep_ = NULL;
706
707         /* the informative part of the email address (describing the name
708          * of the email address owner) may contain quoted parts. the
709          * closure stack stores the last encountered quotes. */
710         gchar closure_stack[128];
711         gchar *ptr = closure_stack;
712
713         g_return_val_if_fail(start != NULL, FALSE);
714         g_return_val_if_fail(scanpos != NULL, FALSE);
715         g_return_val_if_fail(bp != NULL, FALSE);
716         g_return_val_if_fail(ep != NULL, FALSE);
717
718         /* scan start of address */
719         for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
720                 ;
721
722         /* TODO: should start with an alnum? */
723         bp_++;
724         for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
725                 ;
726
727         if (bp_ != scanpos) {
728                 /* scan end of address */
729                 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
730                         ;
731
732                 /* TODO: really should terminate with an alnum? */
733                 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
734                         ;
735                 ep_++;
736
737                 if (ep_ > scanpos + 1) {
738                         *ep = ep_;
739                         *bp = bp_;
740                         result = TRUE;
741                 }
742         }
743
744         if (!result) return FALSE;
745
746         /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
747         if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_)) 
748                 return FALSE;
749
750         /* see if this is <bracketed>; in this case we also scan for the informative part. */
751         if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
752                 return TRUE;
753
754 #define FULL_STACK()    ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
755 #define IN_STACK()      (ptr > closure_stack)
756 /* has underrun check */
757 #define POP_STACK()     if(IN_STACK()) --ptr
758 /* has overrun check */
759 #define PUSH_STACK(c)   if(!FULL_STACK()) *ptr++ = (c); else return TRUE
760 /* has underrun check */
761 #define PEEK_STACK()    (IN_STACK() ? *(ptr - 1) : 0)
762
763         ep_++;
764
765         /* scan for the informative part. */
766         for (bp_ -= 2; bp_ >= start; bp_--) {
767                 /* if closure on the stack keep scanning */
768                 if (PEEK_STACK() == *bp_) {
769                         POP_STACK();
770                         continue;
771                 }
772                 if (*bp_ == '\'' || *bp_ == '"') {
773                         PUSH_STACK(*bp_);
774                         continue;
775                 }
776
777                 /* if nothing in the closure stack, do the special conditions
778                  * the following if..else expression simply checks whether 
779                  * a token is acceptable. if not acceptable, the clause
780                  * should terminate the loop with a 'break' */
781                 if (!PEEK_STACK()) {
782                         if (*bp_ == '-'
783                         && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
784                         && (((bp_ + 1) < ep_)    && isalnum(*(bp_ + 1)))) {
785                                 /* hyphens are allowed, but only in
786                                    between alnums */
787                         } else if (!ispunct(*bp_)) {
788                                 /* but anything not being a punctiation
789                                    is ok */
790                         } else {
791                                 break; /* anything else is rejected */
792                         }
793                 }
794         }
795
796         bp_++;
797
798 #undef PEEK_STACK
799 #undef PUSH_STACK
800 #undef POP_STACK
801 #undef IN_STACK
802 #undef FULL_STACK
803
804         /* scan forward (should start with an alnum) */
805         for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
806                 ;
807
808         *ep = ep_;
809         *bp = bp_;
810
811         return result;
812 }
813
814 #undef IS_QUOTE
815 #undef IS_RFC822_CHAR
816
817 static gchar *make_email_string(const gchar *bp, const gchar *ep)
818 {
819         /* returns a mailto: URI; mailto: is also used to detect the
820          * uri type later on in the button_pressed signal handler */
821         gchar *tmp;
822         gchar *result;
823
824         tmp = g_strndup(bp, ep - bp);
825         result = g_strconcat("mailto:", tmp, NULL);
826         g_free(tmp);
827
828         return result;
829 }
830
831 #define ADD_TXT_POS(bp_, ep_, pti_) \
832         if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
833                 last = last->next; \
834                 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
835                 last->next = NULL; \
836         } else { \
837                 g_warning("alloc error scanning URIs\n"); \
838                 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
839                                 linebuf, -1); \
840                 return; \
841         }
842
843 /* textview_make_clickable_parts() - colorizes clickable parts */
844 static void textview_make_clickable_parts(TextView *textview,
845                                           GdkFont *font,
846                                           GdkColor *fg_color,
847                                           GdkColor *uri_color,
848                                           const gchar *linebuf)
849 {
850         /* parse table - in order of priority */
851         struct table {
852                 const gchar *needle; /* token */
853
854                 /* token search function */
855                 gchar    *(*search)     (const gchar *haystack,
856                                          const gchar *needle);
857                 /* part parsing function */
858                 gboolean  (*parse)      (const gchar *start,
859                                          const gchar *scanpos,
860                                          const gchar **bp_,
861                                          const gchar **ep_);
862                 /* part to URI function */
863                 gchar    *(*build_uri)  (const gchar *bp,
864                                          const gchar *ep);
865         };
866
867         static struct table parser[] = {
868                 {"http://",  strcasestr, get_uri_part,   make_uri_string},
869                 {"https://", strcasestr, get_uri_part,   make_uri_string},
870                 {"www.",     strcasestr, get_uri_part,   make_uri_string},
871                 {"ftp://",   strcasestr, get_uri_part,   make_uri_string},
872                 {"mailto:",  strcasestr, get_uri_part,   make_uri_string},
873                 {"@",        strcasestr, get_email_part, make_email_string}
874         };
875         const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
876
877         gint  n;
878         const gchar *walk, *bp, *ep;
879
880         struct txtpos {
881                 const gchar     *bp, *ep;       /* text position */
882                 gint             pti;           /* index in parse table */
883                 struct txtpos   *next;          /* next */
884         } head = {NULL, NULL, 0,  NULL}, *last = &head;
885
886         GtkSText *text = GTK_STEXT(textview->text);
887
888         /* parse for clickable parts, and build a list of begin and end positions  */
889         for (walk = linebuf, n = 0;;) {
890                 gint last_index = PARSE_ELEMS;
891                 gchar *scanpos = NULL;
892
893                 /* FIXME: this looks phony. scanning for anything in the parse table */
894                 for (n = 0; n < PARSE_ELEMS; n++) {
895                         gchar *tmp;
896
897                         tmp = parser[n].search(walk, parser[n].needle);
898                         if (tmp) {
899                                 if (scanpos == NULL || tmp < scanpos) {
900                                         scanpos = tmp;
901                                         last_index = n;
902                                 }
903                         }                                       
904                 }
905
906                 if (scanpos) {
907                         /* check if URI can be parsed */
908                         if (parser[last_index].parse(linebuf, scanpos, &bp, &ep)
909                             && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
910                                         ADD_TXT_POS(bp, ep, last_index);
911                                         walk = ep;
912                         } else
913                                 walk = scanpos +
914                                         strlen(parser[last_index].needle);
915                 } else
916                         break;
917         }
918
919         /* colorize this line */
920         if (head.next) {
921                 const gchar *normal_text = linebuf;
922
923                 /* insert URIs */
924                 for (last = head.next; last != NULL;
925                      normal_text = last->ep, last = last->next) {
926                         RemoteURI *uri;
927
928                         uri = g_new(RemoteURI, 1);
929                         if (last->bp - normal_text > 0)
930                                 gtk_stext_insert(text, font,
931                                                 fg_color, NULL,
932                                                 normal_text,
933                                                 last->bp - normal_text);
934                         uri->uri = parser[last->pti].build_uri(last->bp,
935                                                                last->ep);
936                         uri->start = gtk_stext_get_point(text);
937                         gtk_stext_insert(text, font, uri_color,
938                                         NULL, last->bp, last->ep - last->bp);
939                         uri->end = gtk_stext_get_point(text);
940                         textview->uri_list =
941                                 g_slist_append(textview->uri_list, uri);
942                 }
943
944                 if (*normal_text)
945                         gtk_stext_insert(text, font, fg_color,
946                                         NULL, normal_text, -1);
947         } else
948                 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
949 }
950
951 #undef ADD_TXT_POS
952
953 /* This function writes str as a double-clickable link with the given url. */ 
954 static void textview_write_link(TextView *textview, const gchar *url,
955                                 const gchar *str, CodeConverter *conv)
956 {
957     GdkColor *link_color = NULL;
958     RemoteURI* uri;
959     GtkSText *text = GTK_STEXT(textview->text);
960     gchar buf[BUFFSIZE];
961
962     /* this part is taken from textview_write_line. Right now the only place
963      * that calls this function passes NULL for conv, but you never know. */
964 #if 0
965     if (!conv)
966             strncpy2(buf, str, sizeof(buf));
967     else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
968             gtk_stext_insert(text, textview->msgfont,
969                             prefs_common.enable_color
970                             ? &error_color : NULL, NULL,
971                             "*** Warning: code conversion failed ***\n",
972                             -1);
973             return;
974     }
975 #endif
976
977     /* this part is based on the code in make_clickable_parts */
978     if (prefs_common.enable_color) {
979         link_color = &uri_color;
980     }
981     uri = g_new(RemoteURI, 1);
982     uri->uri = g_strdup(url);
983     uri->start = gtk_stext_get_point(text);
984     gtk_stext_insert(text, textview->msgfont, link_color, NULL, buf,
985                     strlen(buf));
986     uri->end = gtk_stext_get_point(text);
987     textview->uri_list = g_slist_append(textview->uri_list, uri);
988 }
989
990 static void textview_write_line(TextView *textview, const gchar *str,
991                                 CodeConverter *conv)
992 {
993         GtkSText *text = GTK_STEXT(textview->text);
994         gchar buf[BUFFSIZE];
995         GdkColor *fg_color;
996         gint quotelevel = -1;
997
998 #if 0
999         if (!conv)
1000                 strncpy2(buf, str, sizeof(buf));
1001         else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
1002                 gtk_stext_insert(text, textview->msgfont,
1003                                 prefs_common.enable_color
1004                                 ? &error_color : NULL, NULL,
1005                                 "*** Warning: code conversion failed ***\n",
1006                                 -1);
1007                 return;
1008         }
1009 #endif
1010         if (!conv || conv_convert(conv, buf, sizeof(buf), str) < 0)
1011                 strncpy2(buf, str, sizeof(buf));
1012
1013         strcrchomp(buf);
1014         if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1015         fg_color = NULL;
1016
1017         /* change color of quotation
1018            >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1019            Up to 3 levels of quotations are detected, and each
1020            level is colored using a different color. */
1021         if (prefs_common.enable_color && strchr(buf, '>')) {
1022                 quotelevel = get_quote_level(buf);
1023
1024                 /* set up the correct foreground color */
1025                 if (quotelevel > 2) {
1026                         /* recycle colors */
1027                         if (prefs_common.recycle_quote_colors)
1028                                 quotelevel %= 3;
1029                         else
1030                                 quotelevel = 2;
1031                 }
1032         }
1033
1034         if (quotelevel == -1)
1035                 fg_color = NULL;
1036         else
1037                 fg_color = &quote_colors[quotelevel];
1038
1039         if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1040                 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1041
1042         if (prefs_common.enable_color)
1043                 textview_make_clickable_parts(textview, textview->msgfont,
1044                                               fg_color, &uri_color, buf);
1045         else
1046                 textview_make_clickable_parts(textview, textview->msgfont,
1047                                               fg_color, NULL, buf);
1048 }
1049
1050 void textview_clear(TextView *textview)
1051 {
1052         GtkSText *text = GTK_STEXT(textview->text);
1053
1054         gtk_stext_freeze(text);
1055         gtk_stext_set_point(text, 0);
1056         gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1057         gtk_stext_thaw(text);
1058
1059         textview_uri_list_remove_all(textview->uri_list);
1060         textview->uri_list = NULL;
1061 }
1062
1063 void textview_destroy(TextView *textview)
1064 {
1065         textview_uri_list_remove_all(textview->uri_list);
1066         textview->uri_list = NULL;
1067
1068         if (!textview->scrolledwin_sb->parent)
1069                 gtk_widget_destroy(textview->scrolledwin_sb);
1070         if (!textview->scrolledwin_mb->parent)
1071                 gtk_widget_destroy(textview->scrolledwin_mb);
1072
1073         if (textview->msgfont)
1074                 gdk_font_unref(textview->msgfont);
1075         if (textview->boldfont)
1076                 gdk_font_unref(textview->boldfont);
1077
1078         g_free(textview);
1079 }
1080
1081 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1082 {
1083         textview->show_all_headers = all_headers;
1084 }
1085
1086 void textview_set_font(TextView *textview, const gchar *codeset)
1087 {
1088         gboolean use_fontset = TRUE;
1089
1090         /* In multi-byte mode, GtkSText can't display 8bit characters
1091            correctly, so it must be single-byte mode. */
1092         if (MB_CUR_MAX > 1) {
1093                 if (codeset) {
1094                         if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1095                             !g_strcasecmp(codeset, "BALTIC"))
1096                                 use_fontset = FALSE;
1097                         else if (conv_get_current_charset() != C_EUC_JP &&
1098                                  (!g_strncasecmp(codeset, "KOI8-", 5) ||
1099                                   !g_strncasecmp(codeset, "CP", 2)    ||
1100                                   !g_strncasecmp(codeset, "WINDOWS-", 8)))
1101                                 use_fontset = FALSE;
1102                 }
1103         } else
1104                 use_fontset = FALSE;
1105
1106         if (textview->text_is_mb && !use_fontset) {
1107                 GtkWidget *parent;
1108
1109                 parent = textview->scrolledwin_mb->parent;
1110                 gtkut_container_remove(GTK_CONTAINER(parent),
1111                                        textview->scrolledwin_mb);
1112                 gtk_container_add(GTK_CONTAINER(parent),
1113                                   textview->scrolledwin_sb);
1114
1115                 textview->text = textview->text_sb;
1116                 textview->text_is_mb = FALSE;
1117         } else if (!textview->text_is_mb && use_fontset) {
1118                 GtkWidget *parent;
1119
1120                 parent = textview->scrolledwin_sb->parent;
1121                 gtkut_container_remove(GTK_CONTAINER(parent),
1122                                        textview->scrolledwin_sb);
1123                 gtk_container_add(GTK_CONTAINER(parent),
1124                                   textview->scrolledwin_mb);
1125
1126                 textview->text = textview->text_mb;
1127                 textview->text_is_mb = TRUE;
1128         }
1129
1130         if (prefs_common.textfont) {
1131                 GdkFont *font;
1132
1133                 if (use_fontset) {
1134                         if (text_mb_font) {
1135                                 text_mb_font->ascent = text_mb_font_orig_ascent;
1136                                 text_mb_font->descent = text_mb_font_orig_descent;
1137                         }
1138                         font = gdk_fontset_load(prefs_common.textfont);
1139                         if (font && text_mb_font != font) {
1140                                 if (text_mb_font)
1141                                         gdk_font_unref(text_mb_font);
1142                                 text_mb_font = font;
1143                                 text_mb_font_orig_ascent = font->ascent;
1144                                 text_mb_font_orig_descent = font->descent;
1145                         }
1146                 } else {
1147                         if (text_sb_font) {
1148                                 text_sb_font->ascent = text_sb_font_orig_ascent;
1149                                 text_sb_font->descent = text_sb_font_orig_descent;
1150                         }
1151                         if (MB_CUR_MAX > 1) {
1152                                 FONT_LOAD(font, "-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1153                         } else {
1154                                 FONT_LOAD(font, prefs_common.textfont);
1155                         }
1156                         if (font && text_sb_font != font) {
1157                                 if (text_sb_font)
1158                                         gdk_font_unref(text_sb_font);
1159                                 text_sb_font = font;
1160                                 text_sb_font_orig_ascent = font->ascent;
1161                                 text_sb_font_orig_descent = font->descent;
1162                         }
1163                 }
1164
1165                 if (font) {
1166                         gint ascent, descent;
1167
1168                         descent = prefs_common.line_space / 2;
1169                         ascent  = prefs_common.line_space - descent;
1170                         font->ascent  += ascent;
1171                         font->descent += descent;
1172
1173                         if (textview->msgfont)
1174                                 gdk_font_unref(textview->msgfont);
1175                         textview->msgfont = font;
1176                         gdk_font_ref(font);
1177                 }
1178         }
1179
1180         if (!textview->boldfont && prefs_common.boldfont)
1181                 FONT_LOAD(textview->boldfont, prefs_common.boldfont);
1182         if (!spacingfont)
1183                 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1184 }
1185
1186 enum
1187 {
1188         H_DATE          = 0,
1189         H_FROM          = 1,
1190         H_TO            = 2,
1191         H_NEWSGROUPS    = 3,
1192         H_SUBJECT       = 4,
1193         H_CC            = 5,
1194         H_REPLY_TO      = 6,
1195         H_FOLLOWUP_TO   = 7,
1196         H_X_MAILER      = 8,
1197         H_X_NEWSREADER  = 9,
1198         H_USER_AGENT    = 10,
1199         H_ORGANIZATION  = 11,
1200 };
1201
1202 void textview_set_position(TextView *textview, gint pos)
1203 {
1204         if (pos < 0) {
1205                 textview->cur_pos =
1206                         gtk_stext_get_length(GTK_STEXT(textview->text));
1207         } else {
1208                 textview->cur_pos = pos;
1209         }
1210 }
1211
1212 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1213 {
1214         gchar buf[BUFFSIZE];
1215         GPtrArray *headers, *sorted_headers;
1216         GSList *disphdr_list;
1217         Header *header;
1218         gint i;
1219
1220         g_return_val_if_fail(fp != NULL, NULL);
1221
1222         if (textview->show_all_headers)
1223                 return procheader_get_header_array_asis(fp);
1224
1225         if (!prefs_common.display_header) {
1226                 while (fgets(buf, sizeof(buf), fp) != NULL)
1227                         if (buf[0] == '\r' || buf[0] == '\n') break;
1228                 return NULL;
1229         }
1230
1231         headers = procheader_get_header_array_asis(fp);
1232
1233         sorted_headers = g_ptr_array_new();
1234
1235         for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1236              disphdr_list = disphdr_list->next) {
1237                 DisplayHeaderProp *dp =
1238                         (DisplayHeaderProp *)disphdr_list->data;
1239
1240                 for (i = 0; i < headers->len; i++) {
1241                         header = g_ptr_array_index(headers, i);
1242
1243                         if (procheader_headername_equal(header->name,
1244                                                         dp->name)) {
1245                                 if (dp->hidden)
1246                                         procheader_header_free(header);
1247                                 else
1248                                         g_ptr_array_add(sorted_headers, header);
1249
1250                                 g_ptr_array_remove_index(headers, i);
1251                                 i--;
1252                         }
1253                 }
1254         }
1255
1256         if (prefs_common.show_other_header) {
1257                 for (i = 0; i < headers->len; i++) {
1258                         header = g_ptr_array_index(headers, i);
1259                         g_ptr_array_add(sorted_headers, header);
1260                 }
1261         }
1262
1263         g_ptr_array_free(headers, FALSE);
1264
1265         return sorted_headers;
1266 }
1267
1268 static void textview_show_header(TextView *textview, GPtrArray *headers)
1269 {
1270         GtkSText *text = GTK_STEXT(textview->text);
1271         Header *header;
1272         gint i;
1273
1274         g_return_if_fail(headers != NULL);
1275
1276         gtk_stext_freeze(text);
1277
1278         for (i = 0; i < headers->len; i++) {
1279                 header = g_ptr_array_index(headers, i);
1280                 g_return_if_fail(header->name != NULL);
1281
1282                 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1283                                 header->name, -1);
1284                 if (header->name[strlen(header->name) - 1] != ' ')
1285                         gtk_stext_insert(text, textview->boldfont,
1286                                         NULL, NULL, " ", 1);
1287
1288                 if (procheader_headername_equal(header->name, "Subject") ||
1289                     procheader_headername_equal(header->name, "From")    ||
1290                     procheader_headername_equal(header->name, "To")      ||
1291                     procheader_headername_equal(header->name, "Cc"))
1292                         unfold_line(header->body);
1293
1294                 if (prefs_common.enable_color &&
1295                     (procheader_headername_equal(header->name, "X-Mailer") ||
1296                      procheader_headername_equal(header->name,
1297                                                  "X-Newsreader")) &&
1298                     strstr(header->body, "Sylpheed") != NULL)
1299                         gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1300                                         header->body, -1);
1301                 else if (prefs_common.enable_color) {
1302                         textview_make_clickable_parts(textview,
1303                                                       NULL, NULL, &uri_color,
1304                                                       header->body);
1305                 } else {
1306                         textview_make_clickable_parts(textview,
1307                                                       NULL, NULL, NULL,
1308                                                       header->body);
1309                 }
1310                 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1311         }
1312
1313         gtk_stext_thaw(text);
1314 }
1315
1316 gboolean textview_search_string(TextView *textview, const gchar *str,
1317                                 gboolean case_sens)
1318 {
1319         GtkSText *text = GTK_STEXT(textview->text);
1320         gint pos;
1321         wchar_t *wcs;
1322         gint len;
1323         gint text_len;
1324         gboolean found = FALSE;
1325
1326         g_return_val_if_fail(str != NULL, FALSE);
1327
1328         wcs = strdup_mbstowcs(str);
1329         g_return_val_if_fail(wcs != NULL, FALSE);
1330         len = wcslen(wcs);
1331         pos = textview->cur_pos;
1332         if (pos < textview->body_pos)
1333                 pos = textview->body_pos;
1334         text_len = gtk_stext_get_length(text);
1335         if (text_len - pos < len) {
1336                 g_free(wcs);
1337                 return FALSE;
1338         }
1339
1340         for (; pos < text_len; pos++) {
1341                 if (text_len - pos < len) break;
1342                 if (gtkut_stext_match_string(text, pos, wcs, len, case_sens)
1343                     == TRUE) {
1344                         gtk_editable_set_position(GTK_EDITABLE(text),
1345                                                   pos + len);
1346                         gtk_editable_select_region(GTK_EDITABLE(text),
1347                                                    pos, pos + len);
1348                         textview_set_position(textview, pos + len);
1349                         found = TRUE;
1350                         break;
1351                 }
1352                 if (text_len - pos == len) break;
1353         }
1354
1355         g_free(wcs);
1356         return found;
1357 }
1358
1359 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1360                                          gboolean case_sens)
1361 {
1362         GtkSText *text = GTK_STEXT(textview->text);
1363         gint pos;
1364         wchar_t *wcs;
1365         gint len;
1366         gint text_len;
1367         gboolean found = FALSE;
1368
1369         g_return_val_if_fail(str != NULL, FALSE);
1370
1371         wcs = strdup_mbstowcs(str);
1372         g_return_val_if_fail(wcs != NULL, FALSE);
1373         len = wcslen(wcs);
1374         pos = textview->cur_pos;
1375         text_len = gtk_stext_get_length(text);
1376         if (text_len - textview->body_pos < len) {
1377                 g_free(wcs);
1378                 return FALSE;
1379         }
1380         if (pos <= textview->body_pos || text_len - pos < len)
1381                 pos = text_len - len;
1382
1383         for (; pos >= textview->body_pos; pos--) {
1384                 if (gtkut_stext_match_string(text, pos, wcs, len, case_sens)
1385                     == TRUE) {
1386                         gtk_editable_set_position(GTK_EDITABLE(text), pos);
1387                         gtk_editable_select_region(GTK_EDITABLE(text),
1388                                                    pos, pos + len);
1389                         textview_set_position(textview, pos - 1);
1390                         found = TRUE;
1391                         break;
1392                 }
1393                 if (pos == textview->body_pos) break;
1394         }
1395
1396         g_free(wcs);
1397         return found;
1398 }
1399
1400 void textview_scroll_one_line(TextView *textview, gboolean up)
1401 {
1402         GtkSText *text = GTK_STEXT(textview->text);
1403         gfloat upper;
1404
1405         if (prefs_common.enable_smooth_scroll) {
1406                 textview_smooth_scroll_one_line(textview, up);
1407                 return;
1408         }
1409
1410         if (!up) {
1411                 upper = text->vadj->upper - text->vadj->page_size;
1412                 if (text->vadj->value < upper) {
1413                         text->vadj->value +=
1414                                 text->vadj->step_increment * 4;
1415                         text->vadj->value =
1416                                 MIN(text->vadj->value, upper);
1417                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1418                                                 "value_changed");
1419                 }
1420         } else {
1421                 if (text->vadj->value > 0.0) {
1422                         text->vadj->value -=
1423                                 text->vadj->step_increment * 4;
1424                         text->vadj->value =
1425                                 MAX(text->vadj->value, 0.0);
1426                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1427                                                 "value_changed");
1428                 }
1429         }
1430 }
1431
1432 gboolean textview_scroll_page(TextView *textview, gboolean up)
1433 {
1434         GtkSText *text = GTK_STEXT(textview->text);
1435         gfloat upper;
1436         gfloat page_incr;
1437
1438         if (prefs_common.enable_smooth_scroll)
1439                 return textview_smooth_scroll_page(textview, up);
1440
1441         if (prefs_common.scroll_halfpage)
1442                 page_incr = text->vadj->page_increment / 2;
1443         else
1444                 page_incr = text->vadj->page_increment;
1445
1446         if (!up) {
1447                 upper = text->vadj->upper - text->vadj->page_size;
1448                 if (text->vadj->value < upper) {
1449                         text->vadj->value += page_incr;
1450                         text->vadj->value = MIN(text->vadj->value, upper);
1451                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1452                                                 "value_changed");
1453                 } else
1454                         return FALSE;
1455         } else {
1456                 if (text->vadj->value > 0.0) {
1457                         text->vadj->value -= page_incr;
1458                         text->vadj->value = MAX(text->vadj->value, 0.0);
1459                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1460                                                 "value_changed");
1461                 } else
1462                         return FALSE;
1463         }
1464
1465         return TRUE;
1466 }
1467
1468 static void textview_smooth_scroll_do(TextView *textview,
1469                                       gfloat old_value, gfloat last_value,
1470                                       gint step)
1471 {
1472         GtkSText *text = GTK_STEXT(textview->text);
1473         gint change_value;
1474         gboolean up;
1475         gint i;
1476
1477         if (old_value < last_value) {
1478                 change_value = last_value - old_value;
1479                 up = FALSE;
1480         } else {
1481                 change_value = old_value - last_value;
1482                 up = TRUE;
1483         }
1484
1485         gdk_key_repeat_disable();
1486
1487         for (i = step; i <= change_value; i += step) {
1488                 text->vadj->value = old_value + (up ? -i : i);
1489                 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1490                                         "value_changed");
1491         }
1492
1493         text->vadj->value = last_value;
1494         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1495
1496         gdk_key_repeat_restore();
1497 }
1498
1499 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1500 {
1501         GtkSText *text = GTK_STEXT(textview->text);
1502         gfloat upper;
1503         gfloat old_value;
1504         gfloat last_value;
1505
1506         if (!up) {
1507                 upper = text->vadj->upper - text->vadj->page_size;
1508                 if (text->vadj->value < upper) {
1509                         old_value = text->vadj->value;
1510                         last_value = text->vadj->value +
1511                                 text->vadj->step_increment * 4;
1512                         last_value = MIN(last_value, upper);
1513
1514                         textview_smooth_scroll_do(textview, old_value,
1515                                                   last_value,
1516                                                   prefs_common.scroll_step);
1517                 }
1518         } else {
1519                 if (text->vadj->value > 0.0) {
1520                         old_value = text->vadj->value;
1521                         last_value = text->vadj->value -
1522                                 text->vadj->step_increment * 4;
1523                         last_value = MAX(last_value, 0.0);
1524
1525                         textview_smooth_scroll_do(textview, old_value,
1526                                                   last_value,
1527                                                   prefs_common.scroll_step);
1528                 }
1529         }
1530 }
1531
1532 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1533 {
1534         GtkSText *text = GTK_STEXT(textview->text);
1535         gfloat upper;
1536         gfloat page_incr;
1537         gfloat old_value;
1538         gfloat last_value;
1539
1540         if (prefs_common.scroll_halfpage)
1541                 page_incr = text->vadj->page_increment / 2;
1542         else
1543                 page_incr = text->vadj->page_increment;
1544
1545         if (!up) {
1546                 upper = text->vadj->upper - text->vadj->page_size;
1547                 if (text->vadj->value < upper) {
1548                         old_value = text->vadj->value;
1549                         last_value = text->vadj->value + page_incr;
1550                         last_value = MIN(last_value, upper);
1551
1552                         textview_smooth_scroll_do(textview, old_value,
1553                                                   last_value,
1554                                                   prefs_common.scroll_step);
1555                 } else
1556                         return FALSE;
1557         } else {
1558                 if (text->vadj->value > 0.0) {
1559                         old_value = text->vadj->value;
1560                         last_value = text->vadj->value - page_incr;
1561                         last_value = MAX(last_value, 0.0);
1562
1563                         textview_smooth_scroll_do(textview, old_value,
1564                                                   last_value,
1565                                                   prefs_common.scroll_step);
1566                 } else
1567                         return FALSE;
1568         }
1569
1570         return TRUE;
1571 }
1572
1573 #define KEY_PRESS_EVENT_STOP() \
1574         if (gtk_signal_n_emissions_by_name \
1575                 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1576                 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1577                                              "key_press_event"); \
1578         }
1579
1580 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1581                                  TextView *textview)
1582 {
1583         SummaryView *summaryview = NULL;
1584         MessageView *messageview = textview->messageview;
1585
1586         if (!event) return FALSE;
1587         if (messageview->mainwin)
1588                 summaryview = messageview->mainwin->summaryview;
1589
1590         switch (event->keyval) {
1591         case GDK_Tab:
1592         case GDK_Home:
1593         case GDK_Left:
1594         case GDK_Up:
1595         case GDK_Right:
1596         case GDK_Down:
1597         case GDK_Page_Up:
1598         case GDK_Page_Down:
1599         case GDK_End:
1600         case GDK_Control_L:
1601         case GDK_Control_R:
1602                 break;
1603         case GDK_space:
1604                 if (summaryview)
1605                         summary_pass_key_press_event(summaryview, event);
1606                 else
1607                         textview_scroll_page(textview, FALSE);
1608                 break;
1609         case GDK_BackSpace:
1610                 textview_scroll_page(textview, TRUE);
1611                 break;
1612         case GDK_Return:
1613                 textview_scroll_one_line(textview,
1614                                          (event->state & GDK_MOD1_MASK) != 0);
1615                 break;
1616         case GDK_n:
1617         case GDK_N:
1618         case GDK_p:
1619         case GDK_P:
1620         case GDK_y:
1621         case GDK_t:
1622         case GDK_l:
1623                 if (messageview->type == MVIEW_MIME) {
1624                         KEY_PRESS_EVENT_STOP();
1625                         mimeview_pass_key_press_event(messageview->mimeview,
1626                                                       event);
1627                         break;
1628                 }
1629                 /* fall through */
1630         default:
1631                 if (summaryview)
1632                         summary_pass_key_press_event(summaryview, event);
1633                 break;
1634         }
1635
1636         return TRUE;
1637 }
1638
1639 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1640                                     TextView *textview)
1641 {
1642         if (event)
1643                 textview->last_buttonpress = event->type;
1644         return FALSE;
1645 }
1646
1647 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1648                                     TextView *textview)
1649 {
1650         textview->cur_pos = 
1651                 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1652
1653         if (event && 
1654             ((event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS)
1655              || event->button == 2 || event->button == 3)) {
1656                 GSList *cur;
1657
1658                 /* double click seems to set the cursor after the current
1659                  * word. The cursor position needs fixing, otherwise the
1660                  * last word of a clickable zone will not work */
1661                 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1662                         textview->cur_pos--;
1663                 }
1664
1665                 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1666                         RemoteURI *uri = (RemoteURI *)cur->data;
1667
1668                         if (textview->cur_pos >= uri->start &&
1669                             textview->cur_pos <  uri->end) {
1670                                 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1671                                         if (event->button == 3) {
1672                                                 gchar *fromname, *fromaddress;
1673                                                 GdkEventButton tmpev;   
1674                                                 
1675                                                 /* extract url */
1676                                                 fromaddress = g_strdup(uri->uri + 7);
1677                                                 /* Hiroyuki: please put this function in utils.c! */
1678                                                 fromname = procheader_get_fromname(fromaddress);
1679                                                 extract_address(fromaddress);
1680                                                 g_message("adding from textview %s <%s>", fromname, fromaddress);
1681                                                 /* Add to address book - Match */
1682                                                 addressbook_add_contact( fromname, fromaddress, NULL );
1683                                                 
1684                                                 g_free(fromaddress);
1685                                                 g_free(fromname);
1686                                         } else {
1687                                                 PrefsAccount *account = NULL;
1688                                                 FolderItem   *folder_item;
1689
1690                                                 if (textview->messageview && textview->messageview->mainwin 
1691                                                 &&  textview->messageview->mainwin->summaryview 
1692                                                 &&  textview->messageview->mainwin->summaryview->folder_item) {
1693                                                         folder_item = textview->messageview->mainwin->summaryview->folder_item;
1694                                                         if (folder_item->prefs && folder_item->prefs->enable_default_account)
1695                                                                 account = account_find_from_id(folder_item->prefs->default_account);
1696                                                 }
1697                                                 compose_new_with_recipient
1698                                                         (account, uri->uri + 7);
1699                                         }
1700                                 } else {
1701                                         open_uri(uri->uri,
1702                                                  prefs_common.uri_cmd);
1703                                 }
1704                         }
1705                 }
1706         }
1707         if (event)
1708                 textview->last_buttonpress = event->type;
1709         return FALSE;
1710 }
1711
1712 static void textview_uri_list_remove_all(GSList *uri_list)
1713 {
1714         GSList *cur;
1715
1716         for (cur = uri_list; cur != NULL; cur = cur->next) {
1717                 if (cur->data) {
1718                         g_free(((RemoteURI *)cur->data)->uri);
1719                         g_free(cur->data);
1720                 }
1721         }
1722
1723         g_slist_free(uri_list);
1724 }