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