2004-11-08 [colin] 0.9.12cvs140.3
[claws.git] / src / textview.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2004 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <gdk/gdk.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <gtk/gtkvbox.h>
30 #include <gtk/gtkscrolledwindow.h>
31 #include <gtk/gtksignal.h>
32 #include <stdio.h>
33 #include <ctype.h>
34 #include <string.h>
35 #include <stdlib.h>
36
37 #include "intl.h"
38 #include "main.h"
39 #include "summaryview.h"
40 #include "procheader.h"
41 #include "prefs_common.h"
42 #include "codeconv.h"
43 #include "utils.h"
44 #include "gtkutils.h"
45 #include "procmime.h"
46 #include "html.h"
47 #include "enriched.h"
48 #include "compose.h"
49 #include "addressbook.h"
50 #include "displayheader.h"
51 #include "account.h"
52 #include "mimeview.h"
53 #include "alertpanel.h"
54
55 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;
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
1076                         uri = g_new(RemoteURI, 1);
1077                         if (last->bp - normal_text > 0)
1078                                 gtk_text_buffer_insert_with_tags_by_name
1079                                         (buffer, &iter,
1080                                          normal_text,
1081                                          last->bp - normal_text,
1082                                          fg_tag, NULL);
1083                         uri->uri = parser[last->pti].build_uri(last->bp,
1084                                                                last->ep);
1085                         uri->start = gtk_text_iter_get_offset(&iter);
1086                         gtk_text_buffer_insert_with_tags_by_name
1087                                 (buffer, &iter,
1088                                  last->bp, last->ep - last->bp,
1089                                  uri_tag, NULL);
1090                         uri->end = gtk_text_iter_get_offset(&iter);
1091                         textview->uri_list =
1092                                 g_slist_append(textview->uri_list, uri);
1093                 }
1094
1095                 if (*normal_text)
1096                         gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1097                                                                  normal_text, -1,
1098                                                                  fg_tag, NULL);
1099         } else {
1100                 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1101                                                          linebuf, -1,
1102                                                          fg_tag, NULL);
1103         }
1104 }
1105
1106 #undef ADD_TXT_POS
1107
1108 static void textview_write_line(TextView *textview, const gchar *str,
1109                                 CodeConverter *conv)
1110 {
1111         GtkTextView *text;
1112         GtkTextBuffer *buffer;
1113         GtkTextIter iter;
1114         gchar buf[BUFFSIZE];
1115         gchar *fg_color;
1116         gint quotelevel = -1;
1117         gchar quote_tag_str[10];
1118
1119         text = GTK_TEXT_VIEW(textview->text);
1120         buffer = gtk_text_view_get_buffer(text);
1121         gtk_text_buffer_get_end_iter(buffer, &iter);
1122
1123         if (!conv) {
1124                 strncpy2(buf, str, sizeof(buf));
1125         } else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
1126                 conv_localetodisp(buf, sizeof(buf), str);
1127         }
1128
1129         strcrchomp(buf);
1130         if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1131         fg_color = NULL;
1132
1133         /* change color of quotation
1134            >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1135            Up to 3 levels of quotations are detected, and each
1136            level is colored using a different color. */
1137         if (prefs_common.enable_color 
1138             && line_has_quote_char(buf, prefs_common.quote_chars)) {
1139                 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1140
1141                 /* set up the correct foreground color */
1142                 if (quotelevel > 2) {
1143                         /* recycle colors */
1144                         if (prefs_common.recycle_quote_colors)
1145                                 quotelevel %= 3;
1146                         else
1147                                 quotelevel = 2;
1148                 }
1149         }
1150
1151         if (quotelevel == -1)
1152                 fg_color = NULL;
1153         else {
1154                 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1155                            "quote%d", quotelevel);
1156                 fg_color = quote_tag_str;
1157         }
1158
1159         if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1160                 fg_color = "signature";
1161                 textview->is_in_signature = TRUE;
1162         }
1163
1164         if (prefs_common.enable_color)
1165                 textview_make_clickable_parts(textview, fg_color, "link", buf);
1166         else
1167                 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1168 }
1169
1170 void textview_write_link(TextView *textview, const gchar *str,
1171                          const gchar *uri, CodeConverter *conv)
1172 {
1173         GdkColor *link_color = NULL;
1174         GtkTextView *text;
1175         GtkTextBuffer *buffer;
1176         GtkTextIter iter;
1177         gchar buf[BUFFSIZE];
1178         gchar *bufp;
1179         RemoteURI *r_uri;
1180
1181         if (*str == '\0')
1182                 return;
1183
1184         text = GTK_TEXT_VIEW(textview->text);
1185         buffer = gtk_text_view_get_buffer(text);
1186         gtk_text_buffer_get_end_iter(buffer, &iter);
1187
1188 #warning FIXME_GTK2
1189 #if 0
1190         if (!conv) {
1191                 if (textview->text_is_mb)
1192                         conv_localetodisp(buf, sizeof(buf), str);
1193                 else
1194                         strncpy2(buf, str, sizeof(buf));
1195         } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1196                 conv_localetodisp(buf, sizeof(buf), str);
1197         else if (textview->text_is_mb)
1198                 conv_unreadable_locale(buf);
1199 #else
1200         if (!conv)
1201                 strncpy2(buf, str, sizeof(buf));
1202         else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1203                 conv_localetodisp(buf, sizeof(buf), str);
1204 #endif
1205
1206         strcrchomp(buf);
1207
1208         gtk_text_buffer_get_end_iter(buffer, &iter);
1209
1210         for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1211                 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1212
1213         if (prefs_common.enable_color) {
1214                 link_color = &uri_color;
1215         }
1216         r_uri = g_new(RemoteURI, 1);
1217         r_uri->uri = g_strdup(uri);
1218         r_uri->start = gtk_text_iter_get_offset(&iter);
1219         gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, bufp, -1,
1220                                                  "link", NULL);
1221         r_uri->end = gtk_text_iter_get_offset(&iter);
1222         textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1223 }
1224
1225 void textview_clear(TextView *textview)
1226 {
1227         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1228         GtkTextBuffer *buffer;
1229
1230         buffer = gtk_text_view_get_buffer(text);
1231         gtk_text_buffer_set_text(buffer, "\0", -1);
1232
1233         TEXTVIEW_STATUSBAR_POP(textview);
1234         textview_uri_list_remove_all(textview->uri_list);
1235         textview->uri_list = NULL;
1236
1237         textview->body_pos = 0;
1238 }
1239
1240 void textview_destroy(TextView *textview)
1241 {
1242         textview_uri_list_remove_all(textview->uri_list);
1243         textview->uri_list = NULL;
1244
1245         g_free(textview);
1246 }
1247
1248 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1249 {
1250         textview->show_all_headers = all_headers;
1251 }
1252
1253 void textview_set_font(TextView *textview, const gchar *codeset)
1254 {
1255         if (prefs_common.textfont) {
1256                 PangoFontDescription *font_desc = NULL;
1257
1258                 if (prefs_common.textfont)
1259                         font_desc = pango_font_description_from_string
1260                                                 (prefs_common.textfont);
1261                 if (font_desc) {
1262                         gtk_widget_modify_font(textview->text, font_desc);
1263                         pango_font_description_free(font_desc);
1264                 }
1265         }
1266         gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1267                                              prefs_common.line_space / 2);
1268         gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1269                                              prefs_common.line_space / 2);
1270         if (prefs_common.head_space) {
1271                 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 6);
1272         } else {
1273                 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 0);
1274         }
1275 }
1276
1277 void textview_set_text(TextView *textview, const gchar *text)
1278 {
1279         GtkTextView *view;
1280         GtkTextBuffer *buffer;
1281
1282         g_return_if_fail(textview != NULL);
1283         g_return_if_fail(text != NULL);
1284
1285         textview_clear(textview);
1286
1287         view = GTK_TEXT_VIEW(textview->text);
1288         buffer = gtk_text_view_get_buffer(view);
1289         gtk_text_buffer_set_text(buffer, text, strlen(text));
1290 }
1291
1292 enum
1293 {
1294         H_DATE          = 0,
1295         H_FROM          = 1,
1296         H_TO            = 2,
1297         H_NEWSGROUPS    = 3,
1298         H_SUBJECT       = 4,
1299         H_CC            = 5,
1300         H_REPLY_TO      = 6,
1301         H_FOLLOWUP_TO   = 7,
1302         H_X_MAILER      = 8,
1303         H_X_NEWSREADER  = 9,
1304         H_USER_AGENT    = 10,
1305         H_ORGANIZATION  = 11,
1306 };
1307
1308 void textview_set_position(TextView *textview, gint pos)
1309 {
1310         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1311         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1312         GtkTextIter iter;
1313
1314         gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1315         gtk_text_buffer_place_cursor(buffer, &iter);
1316         gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1317 }
1318
1319 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1320 {
1321         gchar buf[BUFFSIZE];
1322         GPtrArray *headers, *sorted_headers;
1323         GSList *disphdr_list;
1324         Header *header;
1325         gint i;
1326
1327         g_return_val_if_fail(fp != NULL, NULL);
1328
1329         if (textview->show_all_headers)
1330                 return procheader_get_header_array_asis(fp);
1331
1332         if (!prefs_common.display_header) {
1333                 while (fgets(buf, sizeof(buf), fp) != NULL)
1334                         if (buf[0] == '\r' || buf[0] == '\n') break;
1335                 return NULL;
1336         }
1337
1338         headers = procheader_get_header_array_asis(fp);
1339
1340         sorted_headers = g_ptr_array_new();
1341
1342         for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1343              disphdr_list = disphdr_list->next) {
1344                 DisplayHeaderProp *dp =
1345                         (DisplayHeaderProp *)disphdr_list->data;
1346
1347                 for (i = 0; i < headers->len; i++) {
1348                         header = g_ptr_array_index(headers, i);
1349
1350                         if (procheader_headername_equal(header->name,
1351                                                         dp->name)) {
1352                                 if (dp->hidden)
1353                                         procheader_header_free(header);
1354                                 else
1355                                         g_ptr_array_add(sorted_headers, header);
1356
1357                                 g_ptr_array_remove_index(headers, i);
1358                                 i--;
1359                         }
1360                 }
1361         }
1362
1363         if (prefs_common.show_other_header) {
1364                 for (i = 0; i < headers->len; i++) {
1365                         header = g_ptr_array_index(headers, i);
1366                         g_ptr_array_add(sorted_headers, header);
1367                 }
1368                 g_ptr_array_free(headers, TRUE);
1369         } else
1370                 procheader_header_array_destroy(headers);
1371
1372
1373         return sorted_headers;
1374 }
1375
1376 static void textview_show_header(TextView *textview, GPtrArray *headers)
1377 {
1378         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1379         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1380         GtkTextIter iter;
1381         Header *header;
1382         gint i;
1383
1384         g_return_if_fail(headers != NULL);
1385
1386         for (i = 0; i < headers->len; i++) {
1387                 header = g_ptr_array_index(headers, i);
1388                 g_return_if_fail(header->name != NULL);
1389
1390                 gtk_text_buffer_get_end_iter (buffer, &iter);
1391                 gtk_text_buffer_insert_with_tags_by_name
1392                         (buffer, &iter, header->name, -1,
1393                          "header_title", "header", NULL);
1394                 if (header->name[strlen(header->name) - 1] != ' ')
1395                 gtk_text_buffer_insert_with_tags_by_name
1396                                 (buffer, &iter, " ", 1,
1397                                  "header_title", "header", NULL);
1398
1399                 if (procheader_headername_equal(header->name, "Subject") ||
1400                     procheader_headername_equal(header->name, "From")    ||
1401                     procheader_headername_equal(header->name, "To")      ||
1402                     procheader_headername_equal(header->name, "Cc"))
1403                         unfold_line(header->body);
1404
1405 #warning FIXME_GTK2
1406 #if 0
1407                 if (textview->text_is_mb == TRUE)
1408                         conv_unreadable_locale(header->body);
1409 #endif
1410
1411                 if (prefs_common.enable_color &&
1412                     (procheader_headername_equal(header->name, "X-Mailer") ||
1413                      procheader_headername_equal(header->name,
1414                                                  "X-Newsreader")) &&
1415                     strstr(header->body, "Sylpheed") != NULL) {
1416                         gtk_text_buffer_get_end_iter (buffer, &iter);
1417                         gtk_text_buffer_insert_with_tags_by_name
1418                                 (buffer, &iter, header->body, -1,
1419                                  "header", "emphasis", NULL);
1420                 } else if (prefs_common.enable_color) {
1421                         textview_make_clickable_parts(textview, "header", "link",
1422                                                       header->body);
1423                 } else {
1424                         textview_make_clickable_parts(textview, "header", NULL,
1425                                                       header->body);
1426                 }
1427                 gtk_text_buffer_get_end_iter (buffer, &iter);
1428                 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1429                                                          "header", NULL);
1430         }
1431 }
1432
1433 gboolean textview_search_string(TextView *textview, const gchar *str,
1434                                 gboolean case_sens)
1435 {
1436         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1437         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1438         GtkTextMark *mark;
1439         GtkTextIter iter, start, end, real_end, *pos;
1440         gboolean found = FALSE;
1441         gint insert_offset, selbound_offset;
1442
1443         /* reset selection */
1444         mark = gtk_text_buffer_get_mark(buffer, "insert");
1445         gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1446         insert_offset = gtk_text_iter_get_offset(&start);
1447         mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1448         gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1449         selbound_offset = gtk_text_iter_get_offset(&end);
1450
1451         pos = insert_offset > selbound_offset ? &start : &end;
1452         gtk_text_buffer_place_cursor(buffer, pos);
1453
1454         /* search */
1455         mark = gtk_text_buffer_get_insert(buffer);
1456         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1457         if (case_sens) {
1458                 found = gtk_text_iter_forward_search(&iter, str,
1459                                              GTK_TEXT_SEARCH_VISIBLE_ONLY,
1460                                              &start, &end, NULL);
1461         } else {
1462                 gchar *text = NULL;
1463                 int i = 0;
1464                 gtk_text_buffer_get_end_iter(buffer, &real_end);
1465                 text = gtk_text_buffer_get_text(buffer, &iter, 
1466                                                 &real_end, FALSE);
1467                 
1468                 while (!found && i++ < strlen(text) - 1) {
1469                         found = (strncasecmp(text+i, str, strlen(str)) == 0);
1470                 }
1471                 
1472                 i += gtk_text_iter_get_offset(&end);
1473                 
1474                 if (found) {
1475                         gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1476                         gtk_text_buffer_get_iter_at_offset(buffer, &end, 
1477                                                            i + strlen(str));
1478                 }
1479                 
1480                 g_free(text);
1481         }
1482         
1483         if (found) {
1484                 gtk_text_buffer_place_cursor(buffer, &start);
1485                 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", 
1486                                                   &end);
1487                 mark = gtk_text_buffer_get_mark(buffer, "insert");
1488                 gtk_text_view_scroll_mark_onscreen(text, mark);
1489         }
1490
1491         return found;
1492 }
1493
1494 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1495                                          gboolean case_sens)
1496 {
1497         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1498         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1499         GtkTextMark *mark;
1500         GtkTextIter iter, start, real_start, end, *pos;
1501         gboolean found = FALSE;
1502         gint insert_offset, selbound_offset;
1503
1504         /* reset selection */
1505         mark = gtk_text_buffer_get_mark(buffer, "insert");
1506         gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1507         insert_offset = gtk_text_iter_get_offset(&start);
1508         mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1509         gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1510         selbound_offset = gtk_text_iter_get_offset(&end);
1511
1512         pos = insert_offset < selbound_offset ? &start : &end;
1513         gtk_text_buffer_place_cursor(buffer, pos);
1514
1515         /* search */
1516         mark = gtk_text_buffer_get_insert(buffer);
1517         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1518         if (case_sens) {
1519                 found = gtk_text_iter_backward_search(&iter, str,
1520                                               GTK_TEXT_SEARCH_VISIBLE_ONLY,
1521                                               &start, &end, NULL);
1522         } else {
1523                 gchar *text = NULL;
1524                 int i = 0;
1525                 if (gtk_text_iter_get_offset(&iter) == 0) 
1526                         gtk_text_buffer_get_end_iter(buffer, &iter);
1527                 
1528                 i = gtk_text_iter_get_offset(&iter) - strlen(str) - 1;
1529                 gtk_text_buffer_get_start_iter(buffer, &real_start);
1530                 
1531                 text = gtk_text_buffer_get_text(buffer, &real_start, 
1532                                                 &iter, FALSE);
1533
1534                 while (!found && i-- > 0) {
1535                         found = (strncasecmp(text+i, str, strlen(str)) == 0);
1536                 }
1537                                 
1538                 if (found) {
1539                         gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1540                         gtk_text_buffer_get_iter_at_offset(buffer, &end, 
1541                                                            i + strlen(str));
1542                 }
1543                 
1544                 g_free(text);
1545         }
1546                 
1547         if (found) {
1548                 gtk_text_buffer_place_cursor(buffer, &end);
1549                 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", 
1550                                                   &start);
1551                 mark = gtk_text_buffer_get_mark(buffer, "insert");
1552                 gtk_text_view_scroll_mark_onscreen(text, mark);
1553         }
1554
1555         return found;
1556 }
1557
1558 void textview_scroll_one_line(TextView *textview, gboolean up)
1559 {
1560         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1561         GtkAdjustment *vadj = text->vadjustment;
1562         gfloat upper;
1563
1564         if (prefs_common.enable_smooth_scroll) {
1565                 textview_smooth_scroll_one_line(textview, up);
1566                 return;
1567         }
1568
1569         if (!up) {
1570                 upper = vadj->upper - vadj->page_size;
1571                 if (vadj->value < upper) {
1572                         vadj->value +=
1573                                 vadj->step_increment * 4;
1574                         vadj->value =
1575                                 MIN(vadj->value, upper);
1576                         g_signal_emit_by_name(G_OBJECT(vadj),
1577                                               "value_changed", 0);
1578                 }
1579         } else {
1580                 if (vadj->value > 0.0) {
1581                         vadj->value -=
1582                                 vadj->step_increment * 4;
1583                         vadj->value =
1584                                 MAX(vadj->value, 0.0);
1585                         g_signal_emit_by_name(G_OBJECT(vadj),
1586                                               "value_changed", 0);
1587                 }
1588         }
1589 }
1590
1591 gboolean textview_scroll_page(TextView *textview, gboolean up)
1592 {
1593         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1594         GtkAdjustment *vadj = text->vadjustment;
1595         gfloat upper;
1596         gfloat page_incr;
1597
1598         if (prefs_common.enable_smooth_scroll)
1599                 return textview_smooth_scroll_page(textview, up);
1600
1601         if (prefs_common.scroll_halfpage)
1602                 page_incr = vadj->page_increment / 2;
1603         else
1604                 page_incr = vadj->page_increment;
1605
1606         if (!up) {
1607                 upper = vadj->upper - vadj->page_size;
1608                 if (vadj->value < upper) {
1609                         vadj->value += page_incr;
1610                         vadj->value = MIN(vadj->value, upper);
1611                         g_signal_emit_by_name(G_OBJECT(vadj),
1612                                               "value_changed", 0);
1613                 } else
1614                         return FALSE;
1615         } else {
1616                 if (vadj->value > 0.0) {
1617                         vadj->value -= page_incr;
1618                         vadj->value = MAX(vadj->value, 0.0);
1619                         g_signal_emit_by_name(G_OBJECT(vadj),
1620                                               "value_changed", 0);
1621                 } else
1622                         return FALSE;
1623         }
1624
1625         return TRUE;
1626 }
1627
1628 static void textview_smooth_scroll_do(TextView *textview,
1629                                       gfloat old_value, gfloat last_value,
1630                                       gint step)
1631 {
1632         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1633         GtkAdjustment *vadj = text->vadjustment;
1634         gint change_value;
1635         gboolean up;
1636         gint i;
1637
1638         if (old_value < last_value) {
1639                 change_value = last_value - old_value;
1640                 up = FALSE;
1641         } else {
1642                 change_value = old_value - last_value;
1643                 up = TRUE;
1644         }
1645
1646         for (i = step; i <= change_value; i += step) {
1647                 vadj->value = old_value + (up ? -i : i);
1648                 g_signal_emit_by_name(G_OBJECT(vadj),
1649                                       "value_changed", 0);
1650         }
1651
1652         vadj->value = last_value;
1653         g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1654 }
1655
1656 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1657 {
1658         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1659         GtkAdjustment *vadj = text->vadjustment;
1660         gfloat upper;
1661         gfloat old_value;
1662         gfloat last_value;
1663
1664         if (!up) {
1665                 upper = vadj->upper - vadj->page_size;
1666                 if (vadj->value < upper) {
1667                         old_value = vadj->value;
1668                         last_value = vadj->value +
1669                                 vadj->step_increment * 4;
1670                         last_value = MIN(last_value, upper);
1671
1672                         textview_smooth_scroll_do(textview, old_value,
1673                                                   last_value,
1674                                                   prefs_common.scroll_step);
1675                 }
1676         } else {
1677                 if (vadj->value > 0.0) {
1678                         old_value = vadj->value;
1679                         last_value = vadj->value -
1680                                 vadj->step_increment * 4;
1681                         last_value = MAX(last_value, 0.0);
1682
1683                         textview_smooth_scroll_do(textview, old_value,
1684                                                   last_value,
1685                                                   prefs_common.scroll_step);
1686                 }
1687         }
1688 }
1689
1690 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1691 {
1692         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1693         GtkAdjustment *vadj = text->vadjustment;
1694         gfloat upper;
1695         gfloat page_incr;
1696         gfloat old_value;
1697         gfloat last_value;
1698
1699         if (prefs_common.scroll_halfpage)
1700                 page_incr = vadj->page_increment / 2;
1701         else
1702                 page_incr = vadj->page_increment;
1703
1704         if (!up) {
1705                 upper = vadj->upper - vadj->page_size;
1706                 if (vadj->value < upper) {
1707                         old_value = vadj->value;
1708                         last_value = vadj->value + page_incr;
1709                         last_value = MIN(last_value, upper);
1710
1711                         textview_smooth_scroll_do(textview, old_value,
1712                                                   last_value,
1713                                                   prefs_common.scroll_step);
1714                 } else
1715                         return FALSE;
1716         } else {
1717                 if (vadj->value > 0.0) {
1718                         old_value = vadj->value;
1719                         last_value = vadj->value - page_incr;
1720                         last_value = MAX(last_value, 0.0);
1721
1722                         textview_smooth_scroll_do(textview, old_value,
1723                                                   last_value,
1724                                                   prefs_common.scroll_step);
1725                 } else
1726                         return FALSE;
1727         }
1728
1729         return TRUE;
1730 }
1731
1732 #define KEY_PRESS_EVENT_STOP() \
1733         g_signal_stop_emission_by_name(G_OBJECT(widget), \
1734                                        "key_press_event");
1735
1736 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1737                                  TextView *textview)
1738 {
1739         SummaryView *summaryview = NULL;
1740         MessageView *messageview = textview->messageview;
1741
1742         if (!event) return FALSE;
1743         if (messageview->mainwin)
1744                 summaryview = messageview->mainwin->summaryview;
1745
1746         switch (event->keyval) {
1747         case GDK_Tab:
1748         case GDK_Home:
1749         case GDK_Left:
1750         case GDK_Up:
1751         case GDK_Right:
1752         case GDK_Down:
1753         case GDK_Page_Up:
1754         case GDK_Page_Down:
1755         case GDK_End:
1756         case GDK_Control_L:
1757         case GDK_Control_R:
1758                 break;
1759         case GDK_space:
1760                 if (summaryview)
1761                         summary_pass_key_press_event(summaryview, event);
1762                 else
1763                         textview_scroll_page(textview, FALSE);
1764                 break;
1765         case GDK_BackSpace:
1766                 textview_scroll_page(textview, TRUE);
1767                 break;
1768         case GDK_Return:
1769                 textview_scroll_one_line(textview,
1770                                          (event->state & GDK_MOD1_MASK) != 0);
1771                 break;
1772         case GDK_Delete:
1773                 if (summaryview)
1774                         summary_pass_key_press_event(summaryview, event);
1775                 break;
1776         case GDK_y:
1777         case GDK_t:
1778         case GDK_l:
1779                 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1780                         KEY_PRESS_EVENT_STOP();
1781                         mimeview_pass_key_press_event(messageview->mimeview,
1782                                                       event);
1783                         break;
1784                 }
1785                 /* possible fall through */
1786         default:
1787                 if (summaryview &&
1788                     event->window != messageview->mainwin->window->window) {
1789                         GdkEventKey tmpev = *event;
1790
1791                         tmpev.window = messageview->mainwin->window->window;
1792                         KEY_PRESS_EVENT_STOP();
1793                         gtk_widget_event(messageview->mainwin->window,
1794                                          (GdkEvent *)&tmpev);
1795                 }
1796                 break;
1797         }
1798
1799         return TRUE;
1800 }
1801
1802 /*!
1803  *\brief    Check to see if a web URL has been disguised as a different
1804  *          URL (possible with HTML email).
1805  *
1806  *\param    uri The uri to check
1807  *
1808  *\param    textview The TextView the URL is contained in
1809  *
1810  *\return   gboolean TRUE if the URL is ok, or if the user chose to open
1811  *          it anyway, otherwise FALSE          
1812  */
1813 static gboolean uri_security_check(RemoteURI *uri, TextView *textview) 
1814 {
1815         gchar *clicked_str;
1816         gboolean retval = TRUE;
1817
1818         if (g_ascii_strncasecmp(uri->uri, "http:", 5) &&
1819             g_ascii_strncasecmp(uri->uri, "https:", 6) &&
1820             g_ascii_strncasecmp(uri->uri, "www.", 4)) 
1821                 return retval;
1822
1823         clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1824                                              uri->start,
1825                                              uri->end);
1826         if (clicked_str == NULL)
1827                 return TRUE;
1828
1829         if (strcmp(clicked_str, uri->uri) &&
1830             (!g_ascii_strncasecmp(clicked_str, "http:",  5) ||
1831              !g_ascii_strncasecmp(clicked_str, "https:", 6) ||
1832              !g_ascii_strncasecmp(clicked_str, "www.",   4))) {
1833                 gchar *str;
1834                 retval = FALSE;
1835
1836                 /* allow uri->uri    == http://somewhere.com
1837                    and   clicked_str ==        somewhere.com */
1838                 str = g_strconcat("http://", clicked_str, NULL);
1839
1840                 if (!g_ascii_strcasecmp(str, uri->uri))
1841                         retval = TRUE;
1842                 g_free(str);
1843         }
1844
1845         if (retval == FALSE) {
1846                 gchar *msg = NULL;
1847                 AlertValue resp;
1848
1849                 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1850                                         "the apparent URL (%s).  \n"
1851                                         "Open it anyway?"),
1852                                         uri->uri, clicked_str);
1853                 resp = alertpanel_with_type(_("Warning"), 
1854                                   msg,
1855                                   _("Yes"), 
1856                                   _("No"),
1857                                   NULL, NULL, ALERT_WARNING);
1858                 g_free(msg);
1859                 if (resp == G_ALERTDEFAULT)
1860                         retval = TRUE;
1861         } 
1862         g_free(clicked_str);
1863         return retval;
1864 }
1865
1866 static gboolean textview_motion_notify(GtkWidget *widget,
1867                                        GdkEventMotion *event,
1868                                        TextView *textview)
1869 {
1870         textview_uri_update(textview, event->x, event->y);
1871         gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1872
1873         return FALSE;
1874 }
1875
1876 static gboolean textview_leave_notify(GtkWidget *widget,
1877                                       GdkEventCrossing *event,
1878                                       TextView *textview)
1879 {
1880         textview_uri_update(textview, -1, -1);
1881
1882         return FALSE;
1883 }
1884
1885 static gboolean textview_visibility_notify(GtkWidget *widget,
1886                                            GdkEventVisibility *event,
1887                                            TextView *textview)
1888 {
1889         gint wx, wy;
1890   
1891         gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1892         textview_uri_update(textview, wx, wy);
1893
1894         return FALSE;
1895 }
1896
1897 static void textview_uri_update(TextView *textview, gint x, gint y)
1898 {
1899         GtkTextBuffer *buffer;
1900         GtkTextIter start_iter, end_iter;
1901         RemoteURI *uri = NULL;
1902         
1903         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1904
1905         if (x != -1 && y != -1) {
1906                 gint bx, by;
1907                 GtkTextIter iter;
1908                 GSList *tags;
1909                 GSList *cur;
1910             
1911                 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text), 
1912                                                       GTK_TEXT_WINDOW_WIDGET,
1913                                                       x, y, &bx, &by);
1914                 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1915                                                    &iter, bx, by);
1916
1917                 tags = gtk_text_iter_get_tags(&iter);
1918                 for (cur = tags; cur != NULL; cur = cur->next) {
1919                         GtkTextTag *tag = cur->data;
1920                         char *name;
1921
1922                         g_object_get(G_OBJECT(tag), "name", &name, NULL);
1923                         if (!strcmp(name, "link")
1924                             && textview_get_uri_range(textview, &iter, tag,
1925                                                       &start_iter, &end_iter))
1926                                 uri = textview_get_uri_from_range(textview,
1927                                                                   &iter, tag,
1928                                                                   &start_iter,
1929                                                                   &end_iter);
1930                         g_free(name);
1931
1932                         if (uri)
1933                                 break;
1934                 }
1935                 g_slist_free(tags);
1936         }
1937         
1938         if (uri != textview->uri_hover) {
1939                 GdkWindow *window;
1940
1941                 if (textview->uri_hover)
1942                         gtk_text_buffer_remove_tag_by_name(buffer,
1943                                                            "link-hover",
1944                                                            &textview->uri_hover_start_iter,
1945                                                            &textview->uri_hover_end_iter);
1946                     
1947                 textview->uri_hover = uri;
1948                 if (uri) {
1949                         textview->uri_hover_start_iter = start_iter;
1950                         textview->uri_hover_end_iter = end_iter;
1951                 }
1952                 
1953                 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1954                                                   GTK_TEXT_WINDOW_TEXT);
1955                 gdk_window_set_cursor(window, uri ? hand_cursor : text_cursor);
1956
1957                 TEXTVIEW_STATUSBAR_POP(textview);
1958
1959                 if (uri) {
1960                         char *trimmed_uri;
1961
1962                         gtk_text_buffer_apply_tag_by_name(buffer,
1963                                                           "link-hover",
1964                                                           &start_iter,
1965                                                           &end_iter);
1966
1967                         trimmed_uri = trim_string(uri->uri, 60);
1968                         TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1969                         g_free(trimmed_uri);
1970                 }
1971         }
1972 }
1973
1974 static gboolean textview_get_uri_range(TextView *textview,
1975                                        GtkTextIter *iter,
1976                                        GtkTextTag *tag,
1977                                        GtkTextIter *start_iter,
1978                                        GtkTextIter *end_iter)
1979 {
1980         GtkTextIter _start_iter, _end_iter;
1981
1982         _start_iter = *iter;
1983         if(!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
1984                 debug_print("Can't find start.");
1985                 return FALSE;
1986         }
1987
1988         _end_iter = *iter;
1989         if(!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
1990                 debug_print("Can't find end");
1991                 return FALSE;
1992         }
1993
1994         *start_iter = _start_iter;
1995         *end_iter = _end_iter;
1996
1997         return TRUE;
1998 }
1999
2000 static RemoteURI *textview_get_uri_from_range(TextView *textview,
2001                                               GtkTextIter *iter,
2002                                               GtkTextTag *tag,
2003                                               GtkTextIter *start_iter,
2004                                               GtkTextIter *end_iter)
2005 {
2006         gint start_pos, end_pos, cur_pos;
2007         RemoteURI *uri = NULL;
2008         GSList *cur;
2009
2010         start_pos = gtk_text_iter_get_offset(start_iter);
2011         end_pos = gtk_text_iter_get_offset(end_iter);
2012         cur_pos = gtk_text_iter_get_offset(iter);
2013
2014         for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
2015                 RemoteURI *uri_ = (RemoteURI *)cur->data;
2016                 if (start_pos == uri_->start &&
2017                     end_pos ==  uri_->end) {
2018                         uri = uri_;
2019                         break;
2020                 } else if (start_pos == uri_->start ||
2021                            end_pos == uri_->end) {
2022                         /* in case of contiguous links, textview_get_uri_range
2023                          * returns a broader range (start of 1st link to end
2024                          * of last link).
2025                          * In that case, correct link is the one covering
2026                          * current iter.
2027                          */
2028                         if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
2029                                 uri = uri_;
2030                                 break;
2031                         }
2032                 } 
2033         }
2034
2035         return uri;
2036 }
2037
2038 static RemoteURI *textview_get_uri(TextView *textview,
2039                                    GtkTextIter *iter,
2040                                    GtkTextTag *tag)
2041 {
2042         GtkTextIter start_iter, end_iter;
2043         RemoteURI *uri = NULL;
2044
2045         if (textview_get_uri_range(textview, iter, tag, &start_iter,
2046                                    &end_iter))
2047                 uri = textview_get_uri_from_range(textview, iter, tag,
2048                                                   &start_iter, &end_iter);
2049
2050         return uri;
2051 }
2052
2053 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
2054                                             GdkEvent *event, GtkTextIter *iter,
2055                                             TextView *textview)
2056 {
2057         GdkEventButton *bevent;
2058         RemoteURI *uri = NULL;
2059         GSList *cur;
2060         gchar *trimmed_uri;
2061
2062         if (!event)
2063                 return FALSE;
2064
2065         if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2066                 && event->type != GDK_MOTION_NOTIFY)
2067                 return FALSE;
2068
2069         uri = textview_get_uri(textview, iter, tag);
2070         if (!uri)
2071                 return FALSE;
2072
2073         bevent = (GdkEventButton *) event;
2074         
2075         /* doubleclick: open compose / add address / browser */
2076         if ((event->type == GDK_BUTTON_PRESS && bevent->button == 1) ||
2077                 bevent->button == 2 || bevent->button == 3) {
2078                 if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2079                         if (bevent->button == 3) {
2080                                 gchar *fromname, *fromaddress;
2081                                                 
2082                                 /* extract url */
2083                                 fromaddress = g_strdup(uri->uri + 7);
2084                                 /* Hiroyuki: please put this function in utils.c! */
2085                                 fromname = procheader_get_fromname(fromaddress);
2086                                 extract_address(fromaddress);
2087                                 g_message("adding from textview %s <%s>", fromname, fromaddress);
2088                                 /* Add to address book - Match */
2089                                 addressbook_add_contact( fromname, fromaddress, NULL );
2090                                                 
2091                                 g_free(fromaddress);
2092                                 g_free(fromname);
2093                         } else {
2094                                 PrefsAccount *account = NULL;
2095
2096                                 if (textview->messageview && textview->messageview->msginfo &&
2097                                     textview->messageview->msginfo->folder) {
2098                                         FolderItem   *folder_item;
2099
2100                                         folder_item = textview->messageview->msginfo->folder;
2101                                         if (folder_item->prefs && folder_item->prefs->enable_default_account)
2102                                                 account = account_find_from_id(folder_item->prefs->default_account);
2103                                 }
2104                                 compose_new(account, uri->uri + 7, NULL);
2105                         }
2106                         return TRUE;
2107                 } else {
2108                         if (textview_uri_security_check(textview, uri) == TRUE) 
2109                                 open_uri(uri->uri,
2110                                          prefs_common.uri_cmd);
2111                         return TRUE;
2112                 }
2113         }
2114
2115         return FALSE;
2116 }
2117
2118 /*!
2119  *\brief    Check to see if a web URL has been disguised as a different
2120  *          URL (possible with HTML email).
2121  *
2122  *\param    uri The uri to check
2123  *
2124  *\param    textview The TextView the URL is contained in
2125  *
2126  *\return   gboolean TRUE if the URL is ok, or if the user chose to open
2127  *          it anyway, otherwise FALSE          
2128  */
2129 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2130 {
2131         gchar *visible_str;
2132         gboolean retval = TRUE;
2133
2134         if (is_uri_string(uri->uri) == FALSE)
2135                 return TRUE;
2136
2137         visible_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
2138                                              uri->start, uri->end);
2139         if (visible_str == NULL)
2140                 return TRUE;
2141
2142         if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2143                 gchar *uri_path;
2144                 gchar *visible_uri_path;
2145
2146                 uri_path = get_uri_path(uri->uri);
2147                 visible_uri_path = get_uri_path(visible_str);
2148                 if (strcmp(uri_path, visible_uri_path) != 0)
2149                         retval = FALSE;
2150         }
2151
2152         if (retval == FALSE) {
2153                 gchar *msg;
2154                 AlertValue aval;
2155
2156                 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2157                                         "the apparent URL (%s).\n"
2158                                         "Open it anyway?"),
2159                                       uri->uri, visible_str);
2160                 aval = alertpanel_with_type(_("Warning"), msg, _("Yes"), _("No"), NULL,
2161                                             NULL, ALERT_WARNING);
2162                 g_free(msg);
2163                 if (aval == G_ALERTDEFAULT)
2164                         retval = TRUE;
2165         }
2166
2167         g_free(visible_str);
2168
2169         return retval;
2170 }
2171
2172 static void textview_uri_list_remove_all(GSList *uri_list)
2173 {
2174         GSList *cur;
2175
2176         for (cur = uri_list; cur != NULL; cur = cur->next) {
2177                 if (cur->data) {
2178                         g_free(((RemoteURI *)cur->data)->uri);
2179                         g_free(cur->data);
2180                 }
2181         }
2182
2183         g_slist_free(uri_list);
2184 }