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