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