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