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