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