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