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