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