c5356b519a94e0058dcf5063b429b6c0df3c639e
[claws.git] / src / textview.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2005 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 <glib/gi18n.h>
28 #include <gdk/gdk.h>
29 #include <gdk/gdkkeysyms.h>
30 #include <gtk/gtkvbox.h>
31 #include <gtk/gtkscrolledwindow.h>
32 #include <gtk/gtksignal.h>
33 #include <stdio.h>
34 #include <ctype.h>
35 #include <string.h>
36 #include <stdlib.h>
37
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 #include "menu.h"
55
56 struct _RemoteURI
57 {
58         gchar *uri;
59
60         guint start;
61         guint end;
62 };
63
64 static GdkColor quote_colors[3] = {
65         {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
66         {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
67         {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
68 };
69
70 static GdkColor signature_color = {
71         (gulong)0,
72         (gushort)0x7fff,
73         (gushort)0x7fff,
74         (gushort)0x7fff
75 };
76         
77 static GdkColor uri_color = {
78         (gulong)0,
79         (gushort)0,
80         (gushort)0,
81         (gushort)0
82 };
83
84 static GdkColor emphasis_color = {
85         (gulong)0,
86         (gushort)0,
87         (gushort)0,
88         (gushort)0xcfff
89 };
90
91 #if 0
92 static GdkColor error_color = {
93         (gulong)0,
94         (gushort)0xefff,
95         (gushort)0,
96         (gushort)0
97 };
98 #endif
99
100
101 static GdkCursor *hand_cursor = NULL;
102 static GdkCursor *text_cursor = NULL;
103
104 #define TEXTVIEW_STATUSBAR_PUSH(textview, str)                                      \
105 {                                                                           \
106         gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
107                            textview->messageview->statusbar_cid, str);      \
108 }
109
110 #define TEXTVIEW_STATUSBAR_POP(textview)                                                   \
111 {                                                                          \
112         gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
113                           textview->messageview->statusbar_cid);           \
114 }
115
116 static void textview_show_ertf          (TextView       *textview,
117                                          FILE           *fp,
118                                          CodeConverter  *conv);
119 static void textview_add_part           (TextView       *textview,
120                                          MimeInfo       *mimeinfo);
121 static void textview_add_parts          (TextView       *textview,
122                                          MimeInfo       *mimeinfo);
123 static void textview_write_body         (TextView       *textview,
124                                          MimeInfo       *mimeinfo);
125 static void textview_show_html          (TextView       *textview,
126                                          FILE           *fp,
127                                          CodeConverter  *conv);
128
129 static void textview_write_line         (TextView       *textview,
130                                          const gchar    *str,
131                                          CodeConverter  *conv);
132 static void textview_write_link         (TextView       *textview,
133                                          const gchar    *str,
134                                          const gchar    *uri,
135                                          CodeConverter  *conv);
136
137 static GPtrArray *textview_scan_header  (TextView       *textview,
138                                          FILE           *fp);
139 static void textview_show_header        (TextView       *textview,
140                                          GPtrArray      *headers);
141
142 static gint textview_key_pressed                (GtkWidget      *widget,
143                                                  GdkEventKey    *event,
144                                                  TextView       *textview);
145 static gboolean textview_motion_notify          (GtkWidget      *widget,
146                                                  GdkEventMotion *motion,
147                                                  TextView       *textview);
148 static gboolean textview_leave_notify           (GtkWidget      *widget,
149                                                  GdkEventCrossing *event,
150                                                  TextView       *textview);
151 static gboolean textview_visibility_notify      (GtkWidget      *widget,
152                                                  GdkEventVisibility *event,
153                                                  TextView       *textview);
154 static void textview_uri_update                 (TextView       *textview,
155                                                  gint           x,
156                                                  gint           y);
157 static gboolean textview_get_uri_range          (TextView       *textview,
158                                                  GtkTextIter    *iter,
159                                                  GtkTextTag     *tag,
160                                                  GtkTextIter    *start_iter,
161                                                  GtkTextIter    *end_iter);
162 static RemoteURI *textview_get_uri_from_range   (TextView       *textview,
163                                                  GtkTextIter    *iter,
164                                                  GtkTextTag     *tag,
165                                                  GtkTextIter    *start_iter,
166                                                  GtkTextIter    *end_iter);
167 static RemoteURI *textview_get_uri              (TextView       *textview,
168                                                  GtkTextIter    *iter,
169                                                  GtkTextTag     *tag);
170 static gboolean textview_uri_button_pressed     (GtkTextTag     *tag,
171                                                  GObject        *obj,
172                                                  GdkEvent       *event,
173                                                  GtkTextIter    *iter,
174                                                  TextView       *textview);
175
176 static void textview_smooth_scroll_do           (TextView       *textview,
177                                                  gfloat          old_value,
178                                                  gfloat          last_value,
179                                                  gint            step);
180 static void textview_smooth_scroll_one_line     (TextView       *textview,
181                                                  gboolean        up);
182 static gboolean textview_smooth_scroll_page     (TextView       *textview,
183                                                  gboolean        up);
184
185 static gboolean textview_uri_security_check     (TextView       *textview,
186                                                  RemoteURI      *uri);
187 static void textview_uri_list_remove_all        (GSList         *uri_list);
188
189 static void open_uri_cb                         (TextView       *textview,
190                                                  guint           action,
191                                                  void           *data);
192 static void copy_uri_cb                         (TextView       *textview,
193                                                  guint           action,
194                                                  void           *data);
195 static void add_uri_to_addrbook_cb              (TextView       *textview, 
196                                                  guint           action, 
197                                                  void           *data);
198 static void mail_to_uri_cb                      (TextView       *textview, 
199                                                  guint           action, 
200                                                  void           *data);
201 static void copy_mail_to_uri_cb                 (TextView       *textview,
202                                                  guint           action,
203                                                  void           *data);
204
205 static GtkItemFactoryEntry textview_link_popup_entries[] = 
206 {
207         {N_("/_Open link"),             NULL, open_uri_cb, 0, NULL},
208         {N_("/_Copy link location"),    NULL, copy_uri_cb, 0, NULL},
209 };
210
211 static GtkItemFactoryEntry textview_mail_popup_entries[] = 
212 {
213         {N_("/_Add to addressbook"),    NULL, add_uri_to_addrbook_cb, 0, NULL},
214         {N_("/_Email"),                 NULL, mail_to_uri_cb, 0, NULL},
215         {N_("/_Copy"),                  NULL, copy_mail_to_uri_cb, 0, NULL},
216 };
217
218
219 TextView *textview_create(void)
220 {
221         TextView *textview;
222         GtkWidget *vbox;
223         GtkWidget *scrolledwin;
224         GtkWidget *text;
225         GtkTextBuffer *buffer;
226         GtkClipboard *clipboard;
227         GtkItemFactory *link_popupfactory, *mail_popupfactory;
228         GtkWidget *link_popupmenu, *mail_popupmenu;
229         gint n_entries;
230
231         debug_print("Creating text view...\n");
232         textview = g_new0(TextView, 1);
233
234         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
235         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
236                                        GTK_POLICY_AUTOMATIC,
237                                        GTK_POLICY_AUTOMATIC);
238         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
239                                             GTK_SHADOW_IN);
240         gtk_widget_set_size_request
241                 (scrolledwin, prefs_common.mainview_width, -1);
242
243         /* create GtkSText widgets for single-byte and multi-byte character */
244         text = gtk_text_view_new();
245         gtk_widget_add_events(text, GDK_LEAVE_NOTIFY_MASK);
246         gtk_widget_show(text);
247         gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
248         gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
249         gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
250         gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
251         gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
252
253         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
254         clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
255         gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
256
257         gtk_widget_ensure_style(text);
258
259         gtk_widget_ref(scrolledwin);
260
261         gtk_container_add(GTK_CONTAINER(scrolledwin), text);
262
263         g_signal_connect(G_OBJECT(text), "key_press_event",
264                          G_CALLBACK(textview_key_pressed),
265                          textview);
266         g_signal_connect(G_OBJECT(text), "motion_notify_event",
267                          G_CALLBACK(textview_motion_notify),
268                          textview);
269         g_signal_connect(G_OBJECT(text), "leave_notify_event",
270                          G_CALLBACK(textview_leave_notify),
271                          textview);
272         g_signal_connect(G_OBJECT(text), "visibility_notify_event",
273                          G_CALLBACK(textview_visibility_notify),
274                          textview);
275
276         gtk_widget_show(scrolledwin);
277
278         vbox = gtk_vbox_new(FALSE, 0);
279         gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
280
281         gtk_widget_show(vbox);
282
283         n_entries = sizeof(textview_link_popup_entries) /
284                 sizeof(textview_link_popup_entries[0]);
285         link_popupmenu = menu_create_items(textview_link_popup_entries, n_entries,
286                                       "<UriPopupMenu>", &link_popupfactory,
287                                       textview);
288
289         n_entries = sizeof(textview_mail_popup_entries) /
290                 sizeof(textview_mail_popup_entries[0]);
291         mail_popupmenu = menu_create_items(textview_mail_popup_entries, n_entries,
292                                       "<UriPopupMenu>", &mail_popupfactory,
293                                       textview);
294
295         textview->vbox               = vbox;
296         textview->scrolledwin        = scrolledwin;
297         textview->text               = text;
298         textview->uri_list           = NULL;
299         textview->body_pos           = 0;
300         textview->show_all_headers   = FALSE;
301         textview->last_buttonpress   = GDK_NOTHING;
302         textview->link_popup_menu    = link_popupmenu;
303         textview->link_popup_factory = link_popupfactory;
304         textview->mail_popup_menu    = mail_popupmenu;
305         textview->mail_popup_factory = mail_popupfactory;
306
307         return textview;
308 }
309
310 static void textview_create_tags(GtkTextView *text, TextView *textview)
311 {
312         GtkTextBuffer *buffer;
313         GtkTextTag *tag;
314         static PangoFontDescription *font_desc, *bold_font_desc;
315
316         if (!font_desc)
317                 font_desc = pango_font_description_from_string
318                         (NORMAL_FONT);
319
320         if (!bold_font_desc) {
321                 bold_font_desc = pango_font_description_from_string
322                         (BOLD_FONT);
323                 pango_font_description_set_weight
324                         (bold_font_desc, PANGO_WEIGHT_BOLD);
325         }
326
327         buffer = gtk_text_view_get_buffer(text);
328
329         gtk_text_buffer_create_tag(buffer, "header",
330                                    "pixels-above-lines", 0,
331                                    "pixels-above-lines-set", TRUE,
332                                    "pixels-below-lines", 0,
333                                    "pixels-below-lines-set", TRUE,
334                                    "font-desc", font_desc,
335                                    "left-margin", 0,
336                                    "left-margin-set", TRUE,
337                                    NULL);
338         gtk_text_buffer_create_tag(buffer, "header_title",
339                                    "font-desc", bold_font_desc,
340                                    NULL);
341         gtk_text_buffer_create_tag(buffer, "quote0",
342                                    "foreground-gdk", &quote_colors[0],
343                                    NULL);
344         gtk_text_buffer_create_tag(buffer, "quote1",
345                                    "foreground-gdk", &quote_colors[1],
346                                    NULL);
347         gtk_text_buffer_create_tag(buffer, "quote2",
348                                    "foreground-gdk", &quote_colors[2],
349                                    NULL);
350         gtk_text_buffer_create_tag(buffer, "emphasis",
351                                    "foreground-gdk", &emphasis_color,
352                                    NULL);
353         gtk_text_buffer_create_tag(buffer, "signature",
354                                    "foreground-gdk", &signature_color,
355                                    NULL);
356         tag = gtk_text_buffer_create_tag(buffer, "link",
357                                          "foreground-gdk", &uri_color,
358                                          NULL);
359         gtk_text_buffer_create_tag(buffer, "link-hover",
360                                    "foreground-gdk", &uri_color,
361                                    "underline", PANGO_UNDERLINE_SINGLE,
362                                    NULL);
363
364        g_signal_connect(G_OBJECT(tag), "event",
365                          G_CALLBACK(textview_uri_button_pressed), textview);
366  }
367
368 void textview_init(TextView *textview)
369 {
370         if (!hand_cursor)
371                 hand_cursor = gdk_cursor_new(GDK_HAND2);
372         if (!text_cursor)
373                 text_cursor = gdk_cursor_new(GDK_XTERM);
374
375         textview_update_message_colors();
376         textview_set_all_headers(textview, FALSE);
377         textview_set_font(textview, NULL);
378         textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
379 }
380
381 void textview_update_message_colors(void)
382 {
383         GdkColor black = {0, 0, 0, 0};
384
385         if (prefs_common.enable_color) {
386                 /* grab the quote colors, converting from an int to a GdkColor */
387                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
388                                                &quote_colors[0]);
389                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
390                                                &quote_colors[1]);
391                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
392                                                &quote_colors[2]);
393                 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
394                                                &uri_color);
395                 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
396                                                &signature_color);
397         } else {
398                 quote_colors[0] = quote_colors[1] = quote_colors[2] = 
399                         uri_color = emphasis_color = signature_color = black;
400         }
401 }
402
403 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
404                            const gchar *file)
405 {
406         FILE *fp;
407
408         if ((fp = fopen(file, "rb")) == NULL) {
409                 FILE_OP_ERROR(file, "fopen");
410                 return;
411         }
412
413         textview_clear(textview);
414
415         textview_add_parts(textview, mimeinfo);
416
417         fclose(fp);
418
419         textview_set_position(textview, 0);
420 }
421
422 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
423 {
424         GtkTextView *text;
425
426         g_return_if_fail(mimeinfo != NULL);
427         g_return_if_fail(fp != NULL);
428
429         if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
430             ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822"))) {
431                 textview_clear(textview);
432                 textview_add_parts(textview, mimeinfo);
433                 return;
434         }
435
436         if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
437                 perror("fseek");
438
439         textview_clear(textview);
440
441         if (mimeinfo->type == MIMETYPE_MULTIPART)
442                 textview_add_parts(textview, mimeinfo);
443         else
444                 textview_write_body(textview, mimeinfo);
445
446 }
447
448 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
449 {
450         GtkTextView *text;
451         GtkTextBuffer *buffer;
452         GtkTextIter iter;
453         gchar buf[BUFFSIZE];
454         GPtrArray *headers = NULL;
455         const gchar *name;
456         gchar *content_type;
457         gint charcount;
458
459         g_return_if_fail(mimeinfo != NULL);
460         text = GTK_TEXT_VIEW(textview->text);
461         buffer = gtk_text_view_get_buffer(text);
462         charcount = gtk_text_buffer_get_char_count(buffer);
463         gtk_text_buffer_get_end_iter(buffer, &iter);
464
465         if (mimeinfo->type == MIMETYPE_MULTIPART) return;
466
467         if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
468                 FILE *fp;
469
470                 fp = fopen(mimeinfo->data.filename, "rb");
471                 fseek(fp, mimeinfo->offset, SEEK_SET);
472                 headers = textview_scan_header(textview, fp);
473                 if (headers) {
474                         if (charcount > 0)
475                                 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
476                         textview_show_header(textview, headers);
477                         procheader_header_array_destroy(headers);
478                 }
479                 fclose(fp);
480                 return;
481         }
482
483         name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
484         content_type = procmime_get_content_type_str(mimeinfo->type,
485                                                      mimeinfo->subtype);
486         if (name == NULL)
487                 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
488         if (name != NULL)
489                 g_snprintf(buf, sizeof(buf), "\n[%s  %s (%d bytes)]\n",
490                            name, content_type, mimeinfo->length);
491         else
492                 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
493                            content_type, mimeinfo->length);
494
495         g_free(content_type);                      
496
497         if (mimeinfo->type != MIMETYPE_TEXT) {
498                 gtk_text_buffer_insert(buffer, &iter, buf, -1);
499         } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
500                 if (prefs_common.display_header && (charcount > 0))
501                         gtk_text_buffer_insert(buffer, &iter, "\n", 1);
502
503                 textview_write_body(textview, mimeinfo);
504         }
505 }
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 (g_ascii_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 (g_ascii_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 {
625         FILE *tmpfp;
626         gchar buf[BUFFSIZE];
627         CodeConverter *conv;
628         const gchar *charset;
629         
630         if (textview->messageview->forced_charset)
631                 charset = textview->messageview->forced_charset;
632         else
633                 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
634
635         textview_set_font(textview, charset);
636
637         conv = conv_code_converter_new(charset);
638
639         procmime_force_encoding(textview->messageview->forced_encoding);
640         
641         textview->is_in_signature = FALSE;
642
643         procmime_decode_content(mimeinfo);
644
645         if (!g_ascii_strcasecmp(mimeinfo->subtype, "html")) {
646                 gchar *filename;
647                 
648                 filename = procmime_get_tmp_file_name(mimeinfo);
649                 if (procmime_get_part(filename, mimeinfo) == 0) {
650                         tmpfp = fopen(filename, "rb");
651                         textview_show_html(textview, tmpfp, conv);
652                         fclose(tmpfp);
653                         unlink(filename);
654                 }
655                 g_free(filename);
656         } else if (!g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
657                 gchar *filename;
658                 
659                 filename = procmime_get_tmp_file_name(mimeinfo);
660                 if (procmime_get_part(filename, mimeinfo) == 0) {
661                         tmpfp = fopen(filename, "rb");
662                         textview_show_ertf(textview, tmpfp, conv);
663                         fclose(tmpfp);
664                         unlink(filename);
665                 }
666                 g_free(filename);
667         } else {
668                 tmpfp = fopen(mimeinfo->data.filename, "rb");
669                 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
670                 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
671                 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) && 
672                        (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
673                         textview_write_line(textview, buf, conv);
674                 fclose(tmpfp);
675         }
676
677         conv_code_converter_destroy(conv);
678         procmime_force_encoding(0);
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 = g_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                     !IS_ASCII(*(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         (IS_ASCII(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)      (IS_ASCII(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         if (*ep_ && *(bp_ - 1) == '"' && *(ep_) == '"' 
926         && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
927         && IS_RFC822_CHAR(*(ep_ + 3))) {
928                 /* this informative part with an @ in it is 
929                  * followed by the email address */
930                 ep_ += 3;
931                 
932                 /* go to matching '>' (or next non-rfc822 char, like \n) */
933                 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
934                         ;
935                         
936                 /* include the bracket */
937                 if (*ep_ == '>') ep_++;
938                 
939                 /* include the leading quote */         
940                 bp_--;
941
942                 *ep = ep_;
943                 *bp = bp_;
944                 return TRUE;
945         }
946
947         /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
948         if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
949                 return FALSE;
950
951         /* see if this is <bracketed>; in this case we also scan for the informative part. */
952         if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
953                 return TRUE;
954
955 #define FULL_STACK()    ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
956 #define IN_STACK()      (ptr > closure_stack)
957 /* has underrun check */
958 #define POP_STACK()     if(IN_STACK()) --ptr
959 /* has overrun check */
960 #define PUSH_STACK(c)   if(!FULL_STACK()) *ptr++ = (c); else return TRUE
961 /* has underrun check */
962 #define PEEK_STACK()    (IN_STACK() ? *(ptr - 1) : 0)
963
964         ep_++;
965
966         /* scan for the informative part. */
967         for (bp_ -= 2; bp_ >= start; bp_--) {
968                 /* if closure on the stack keep scanning */
969                 if (PEEK_STACK() == *bp_) {
970                         POP_STACK();
971                         continue;
972                 }
973                 if (*bp_ == '\'' || *bp_ == '"') {
974                         PUSH_STACK(*bp_);
975                         continue;
976                 }
977
978                 /* if nothing in the closure stack, do the special conditions
979                  * the following if..else expression simply checks whether 
980                  * a token is acceptable. if not acceptable, the clause
981                  * should terminate the loop with a 'break' */
982                 if (!PEEK_STACK()) {
983                         if (*bp_ == '-'
984                         && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
985                         && (((bp_ + 1) < ep_)    && isalnum(*(bp_ + 1)))) {
986                                 /* hyphens are allowed, but only in
987                                    between alnums */
988                         } else if (!ispunct(*bp_)) {
989                                 /* but anything not being a punctiation
990                                    is ok */
991                         } else {
992                                 break; /* anything else is rejected */
993                         }
994                 }
995         }
996
997         bp_++;
998
999 #undef PEEK_STACK
1000 #undef PUSH_STACK
1001 #undef POP_STACK
1002 #undef IN_STACK
1003 #undef FULL_STACK
1004
1005         /* scan forward (should start with an alnum) */
1006         for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
1007                 ;
1008
1009         *ep = ep_;
1010         *bp = bp_;
1011         
1012         return result;
1013 }
1014
1015 #undef IS_QUOTE
1016 #undef IS_ASCII_ALNUM
1017 #undef IS_RFC822_CHAR
1018
1019 static gchar *make_email_string(const gchar *bp, const gchar *ep)
1020 {
1021         /* returns a mailto: URI; mailto: is also used to detect the
1022          * uri type later on in the button_pressed signal handler */
1023         gchar *tmp;
1024         gchar *result;
1025
1026         tmp = g_strndup(bp, ep - bp);
1027         result = g_strconcat("mailto:", tmp, NULL);
1028         g_free(tmp);
1029
1030         return result;
1031 }
1032
1033 static gchar *make_http_string(const gchar *bp, const gchar *ep)
1034 {
1035         /* returns an http: URI; */
1036         gchar *tmp;
1037         gchar *result;
1038
1039         tmp = g_strndup(bp, ep - bp);
1040         result = g_strconcat("http://", tmp, NULL);
1041         g_free(tmp);
1042
1043         return result;
1044 }
1045
1046 #define ADD_TXT_POS(bp_, ep_, pti_) \
1047         if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1048                 last = last->next; \
1049                 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1050                 last->next = NULL; \
1051         } else { \
1052                 g_warning("alloc error scanning URIs\n"); \
1053                 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1054                                                          linebuf, -1, \
1055                                                          fg_tag, NULL); \
1056                 return; \
1057         }
1058
1059 /* textview_make_clickable_parts() - colorizes clickable parts */
1060 static void textview_make_clickable_parts(TextView *textview,
1061                                           const gchar *fg_tag,
1062                                           const gchar *uri_tag,
1063                                           const gchar *linebuf)
1064 {
1065         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1066         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1067         GtkTextIter iter;
1068
1069         /* parse table - in order of priority */
1070         struct table {
1071                 const gchar *needle; /* token */
1072
1073                 /* token search function */
1074                 gchar    *(*search)     (const gchar *haystack,
1075                                          const gchar *needle);
1076                 /* part parsing function */
1077                 gboolean  (*parse)      (const gchar *start,
1078                                          const gchar *scanpos,
1079                                          const gchar **bp_,
1080                                          const gchar **ep_);
1081                 /* part to URI function */
1082                 gchar    *(*build_uri)  (const gchar *bp,
1083                                          const gchar *ep);
1084         };
1085
1086         static struct table parser[] = {
1087                 {"http://",  strcasestr, get_uri_part,   make_uri_string},
1088                 {"https://", strcasestr, get_uri_part,   make_uri_string},
1089                 {"ftp://",   strcasestr, get_uri_part,   make_uri_string},
1090                 {"www.",     strcasestr, get_uri_part,   make_http_string},
1091                 {"mailto:",  strcasestr, get_uri_part,   make_uri_string},
1092                 {"@",        strcasestr, get_email_part, make_email_string}
1093         };
1094         const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1095
1096         gint  n;
1097         const gchar *walk, *bp, *ep;
1098
1099         struct txtpos {
1100                 const gchar     *bp, *ep;       /* text position */
1101                 gint             pti;           /* index in parse table */
1102                 struct txtpos   *next;          /* next */
1103         } head = {NULL, NULL, 0,  NULL}, *last = &head;
1104
1105         gtk_text_buffer_get_end_iter(buffer, &iter);
1106
1107         /* parse for clickable parts, and build a list of begin and end positions  */
1108         for (walk = linebuf, n = 0;;) {
1109                 gint last_index = PARSE_ELEMS;
1110                 gchar *scanpos = NULL;
1111
1112                 /* FIXME: this looks phony. scanning for anything in the parse table */
1113                 for (n = 0; n < PARSE_ELEMS; n++) {
1114                         gchar *tmp;
1115
1116                         tmp = parser[n].search(walk, parser[n].needle);
1117                         if (tmp) {
1118                                 if (scanpos == NULL || tmp < scanpos) {
1119                                         scanpos = tmp;
1120                                         last_index = n;
1121                                 }
1122                         }                                       
1123                 }
1124
1125                 if (scanpos) {
1126                         /* check if URI can be parsed */
1127                         if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1128                             && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1129                                         ADD_TXT_POS(bp, ep, last_index);
1130                                         walk = ep;
1131                         } else
1132                                 walk = scanpos +
1133                                         strlen(parser[last_index].needle);
1134                 } else
1135                         break;
1136         }
1137
1138         /* colorize this line */
1139         if (head.next) {
1140                 const gchar *normal_text = linebuf;
1141
1142                 /* insert URIs */
1143                 for (last = head.next; last != NULL;
1144                      normal_text = last->ep, last = last->next) {
1145                         RemoteURI *uri;
1146                         uri = g_new(RemoteURI, 1);
1147                         if (last->bp - normal_text > 0)
1148                                 gtk_text_buffer_insert_with_tags_by_name
1149                                         (buffer, &iter,
1150                                          normal_text,
1151                                          last->bp - normal_text,
1152                                          fg_tag, NULL);
1153                         uri->uri = parser[last->pti].build_uri(last->bp,
1154                                                                last->ep);
1155                         uri->start = gtk_text_iter_get_offset(&iter);
1156                         gtk_text_buffer_insert_with_tags_by_name
1157                                 (buffer, &iter, last->bp, last->ep - last->bp,
1158                                  uri_tag, fg_tag, NULL);
1159                         uri->end = gtk_text_iter_get_offset(&iter);
1160                         textview->uri_list =
1161                                 g_slist_append(textview->uri_list, uri);
1162                 }
1163
1164                 if (*normal_text)
1165                         gtk_text_buffer_insert_with_tags_by_name
1166                                 (buffer, &iter, normal_text, -1, fg_tag, NULL);
1167         } else {
1168                 gtk_text_buffer_insert_with_tags_by_name
1169                         (buffer, &iter, linebuf, -1, fg_tag, NULL);
1170         }
1171 }
1172
1173 #undef ADD_TXT_POS
1174
1175 static void textview_write_line(TextView *textview, const gchar *str,
1176                                 CodeConverter *conv)
1177 {
1178         GtkTextView *text;
1179         GtkTextBuffer *buffer;
1180         GtkTextIter iter;
1181         gchar buf[BUFFSIZE];
1182         gchar *fg_color;
1183         gint quotelevel = -1;
1184         gchar quote_tag_str[10];
1185
1186         text = GTK_TEXT_VIEW(textview->text);
1187         buffer = gtk_text_view_get_buffer(text);
1188         gtk_text_buffer_get_end_iter(buffer, &iter);
1189
1190         if (!conv)
1191                 strncpy2(buf, str, sizeof(buf));
1192         else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1193                 conv_utf8todisp(buf, sizeof(buf), str);
1194
1195         strcrchomp(buf);
1196         //if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1197         fg_color = NULL;
1198
1199         /* change color of quotation
1200            >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1201            Up to 3 levels of quotations are detected, and each
1202            level is colored using a different color. */
1203         if (prefs_common.enable_color 
1204             && line_has_quote_char(buf, prefs_common.quote_chars)) {
1205                 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1206
1207                 /* set up the correct foreground color */
1208                 if (quotelevel > 2) {
1209                         /* recycle colors */
1210                         if (prefs_common.recycle_quote_colors)
1211                                 quotelevel %= 3;
1212                         else
1213                                 quotelevel = 2;
1214                 }
1215         }
1216
1217         if (quotelevel == -1)
1218                 fg_color = NULL;
1219         else {
1220                 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1221                            "quote%d", quotelevel);
1222                 fg_color = quote_tag_str;
1223         }
1224
1225         if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1226                 fg_color = "signature";
1227                 textview->is_in_signature = TRUE;
1228         }
1229
1230         if (prefs_common.enable_color)
1231                 textview_make_clickable_parts(textview, fg_color, "link", buf);
1232         else
1233                 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1234 }
1235
1236 void textview_write_link(TextView *textview, const gchar *str,
1237                          const gchar *uri, CodeConverter *conv)
1238 {
1239         GdkColor *link_color = NULL;
1240         GtkTextView *text;
1241         GtkTextBuffer *buffer;
1242         GtkTextIter iter;
1243         gchar buf[BUFFSIZE];
1244         gchar *bufp;
1245         RemoteURI *r_uri;
1246
1247         if (*str == '\0')
1248                 return;
1249
1250         text = GTK_TEXT_VIEW(textview->text);
1251         buffer = gtk_text_view_get_buffer(text);
1252         gtk_text_buffer_get_end_iter(buffer, &iter);
1253
1254         if (!conv)
1255                 strncpy2(buf, str, sizeof(buf));
1256         else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1257                 conv_utf8todisp(buf, sizeof(buf), str);
1258
1259         strcrchomp(buf);
1260
1261         gtk_text_buffer_get_end_iter(buffer, &iter);
1262
1263         for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1264                 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1265
1266         if (prefs_common.enable_color) {
1267                 link_color = &uri_color;
1268         }
1269         r_uri = g_new(RemoteURI, 1);
1270         r_uri->uri = g_strdup(uri);
1271         r_uri->start = gtk_text_iter_get_offset(&iter);
1272         gtk_text_buffer_insert_with_tags_by_name
1273                 (buffer, &iter, bufp, -1, "link", NULL);
1274         r_uri->end = gtk_text_iter_get_offset(&iter);
1275         textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1276 }
1277
1278 void textview_clear(TextView *textview)
1279 {
1280         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1281         GtkTextBuffer *buffer;
1282
1283         buffer = gtk_text_view_get_buffer(text);
1284         gtk_text_buffer_set_text(buffer, "", -1);
1285
1286         TEXTVIEW_STATUSBAR_POP(textview);
1287         textview_uri_list_remove_all(textview->uri_list);
1288         textview->uri_list = NULL;
1289
1290         textview->body_pos = 0;
1291 }
1292
1293 void textview_destroy(TextView *textview)
1294 {
1295         textview_uri_list_remove_all(textview->uri_list);
1296         textview->uri_list = NULL;
1297
1298         g_free(textview);
1299 }
1300
1301 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1302 {
1303         textview->show_all_headers = all_headers;
1304 }
1305
1306 void textview_set_font(TextView *textview, const gchar *codeset)
1307 {
1308         if (prefs_common.textfont) {
1309                 PangoFontDescription *font_desc = NULL;
1310
1311                 font_desc = pango_font_description_from_string
1312                                                 (prefs_common.textfont);
1313                 if (font_desc) {
1314                         gtk_widget_modify_font(textview->text, font_desc);
1315                         pango_font_description_free(font_desc);
1316                 }
1317         }
1318         gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1319                                              prefs_common.line_space / 2);
1320         gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1321                                              prefs_common.line_space / 2);
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         gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1364 }
1365
1366 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1367 {
1368         gchar buf[BUFFSIZE];
1369         GPtrArray *headers, *sorted_headers;
1370         GSList *disphdr_list;
1371         Header *header;
1372         gint i;
1373
1374         g_return_val_if_fail(fp != NULL, NULL);
1375
1376         if (textview->show_all_headers)
1377                 return procheader_get_header_array_asis(fp);
1378
1379         if (!prefs_common.display_header) {
1380                 while (fgets(buf, sizeof(buf), fp) != NULL)
1381                         if (buf[0] == '\r' || buf[0] == '\n') break;
1382                 return NULL;
1383         }
1384
1385         headers = procheader_get_header_array_asis(fp);
1386
1387         sorted_headers = g_ptr_array_new();
1388
1389         for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1390              disphdr_list = disphdr_list->next) {
1391                 DisplayHeaderProp *dp =
1392                         (DisplayHeaderProp *)disphdr_list->data;
1393
1394                 for (i = 0; i < headers->len; i++) {
1395                         header = g_ptr_array_index(headers, i);
1396
1397                         if (procheader_headername_equal(header->name,
1398                                                         dp->name)) {
1399                                 if (dp->hidden)
1400                                         procheader_header_free(header);
1401                                 else
1402                                         g_ptr_array_add(sorted_headers, header);
1403
1404                                 g_ptr_array_remove_index(headers, i);
1405                                 i--;
1406                         }
1407                 }
1408         }
1409
1410         if (prefs_common.show_other_header) {
1411                 for (i = 0; i < headers->len; i++) {
1412                         header = g_ptr_array_index(headers, i);
1413                         g_ptr_array_add(sorted_headers, header);
1414                 }
1415                 g_ptr_array_free(headers, TRUE);
1416         } else
1417                 procheader_header_array_destroy(headers);
1418
1419
1420         return sorted_headers;
1421 }
1422
1423 static void textview_show_header(TextView *textview, GPtrArray *headers)
1424 {
1425         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1426         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1427         GtkTextIter iter;
1428         Header *header;
1429         gint i;
1430
1431         g_return_if_fail(headers != NULL);
1432
1433         for (i = 0; i < headers->len; i++) {
1434                 header = g_ptr_array_index(headers, i);
1435                 g_return_if_fail(header->name != NULL);
1436
1437                 gtk_text_buffer_get_end_iter (buffer, &iter);
1438                 gtk_text_buffer_insert_with_tags_by_name
1439                         (buffer, &iter, header->name, -1,
1440                          "header_title", "header", NULL);
1441                 if (header->name[strlen(header->name) - 1] != ' ')
1442                 gtk_text_buffer_insert_with_tags_by_name
1443                                 (buffer, &iter, " ", 1,
1444                                  "header_title", "header", NULL);
1445
1446                 if (procheader_headername_equal(header->name, "Subject") ||
1447                     procheader_headername_equal(header->name, "From")    ||
1448                     procheader_headername_equal(header->name, "To")      ||
1449                     procheader_headername_equal(header->name, "Cc"))
1450                         unfold_line(header->body);
1451
1452 #warning FIXME_GTK2
1453 #if 0
1454                 if (textview->text_is_mb == TRUE)
1455                         conv_unreadable_locale(header->body);
1456 #endif
1457
1458                 if (prefs_common.enable_color &&
1459                     (procheader_headername_equal(header->name, "X-Mailer") ||
1460                      procheader_headername_equal(header->name,
1461                                                  "X-Newsreader")) &&
1462                     strstr(header->body, "Sylpheed") != NULL) {
1463                         gtk_text_buffer_get_end_iter (buffer, &iter);
1464                         gtk_text_buffer_insert_with_tags_by_name
1465                                 (buffer, &iter, header->body, -1,
1466                                  "header", "emphasis", NULL);
1467                 } else if (prefs_common.enable_color) {
1468                         textview_make_clickable_parts(textview, "header", "link",
1469                                                       header->body);
1470                 } else {
1471                         textview_make_clickable_parts(textview, "header", NULL,
1472                                                       header->body);
1473                 }
1474                 gtk_text_buffer_get_end_iter (buffer, &iter);
1475                 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1476                                                          "header", NULL);
1477         }
1478 }
1479
1480 gboolean textview_search_string(TextView *textview, const gchar *str,
1481                                 gboolean case_sens)
1482 {
1483         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1484         GtkTextBuffer *buffer;
1485         GtkTextIter iter, end_iter;
1486         GtkTextMark *mark;
1487         gint pos;
1488         gint len;
1489
1490         g_return_val_if_fail(str != NULL, FALSE);
1491
1492         buffer = gtk_text_view_get_buffer(text);
1493
1494         len = g_utf8_strlen(str, -1);
1495         g_return_val_if_fail(len >= 0, FALSE);
1496
1497         mark = gtk_text_buffer_get_insert(buffer);
1498         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1499         pos = gtk_text_iter_get_offset(&iter);
1500
1501         if ((pos = gtkut_text_buffer_find(buffer, pos, str, case_sens)) != -1) {
1502                 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, pos);
1503                 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos + len);
1504                 gtk_text_buffer_select_range(buffer, &iter, &end_iter);
1505                 gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1506                 return TRUE;
1507         }
1508
1509         return FALSE;
1510 }
1511
1512 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1513                                          gboolean case_sens)
1514 {
1515         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1516         GtkTextBuffer *buffer;
1517         GtkTextIter iter, end_iter;
1518         GtkTextMark *mark;
1519         gint pos;
1520         gunichar *wcs;
1521         gint len;
1522         glong items_read = 0, items_written = 0;
1523         GError *error = NULL;
1524         gboolean found = FALSE;
1525
1526         g_return_val_if_fail(str != NULL, FALSE);
1527
1528         buffer = gtk_text_view_get_buffer(text);
1529
1530         wcs = g_utf8_to_ucs4(str, -1, &items_read, &items_written, &error);
1531         if (error != NULL) {
1532                 g_warning("An error occured while converting a string from UTF-8 to UCS-4: %s\n", error->message);
1533                 g_error_free(error);
1534         }
1535         if (!wcs || items_written <= 0) return FALSE;
1536         len = (gint)items_written;
1537
1538         mark = gtk_text_buffer_get_insert(buffer);
1539         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1540
1541         while (gtk_text_iter_backward_char(&iter)) {
1542                 pos = gtk_text_iter_get_offset(&iter);
1543                 if (gtkut_text_buffer_match_string
1544                         (buffer, pos, wcs, len, case_sens) == TRUE) {
1545                         gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1546                         gtk_text_buffer_get_iter_at_offset
1547                                 (buffer, &end_iter, pos + len);
1548                         gtk_text_buffer_select_range(buffer, &iter, &end_iter);
1549                         gtk_text_view_scroll_to_mark
1550                                 (text, mark, 0.0, FALSE, 0.0, 0.0);
1551                         found = TRUE;
1552                         break;
1553                 }
1554         }
1555
1556         g_free(wcs);
1557         return found;
1558 }
1559
1560 void textview_scroll_one_line(TextView *textview, gboolean up)
1561 {
1562         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1563         GtkAdjustment *vadj = text->vadjustment;
1564         gfloat upper;
1565
1566         if (prefs_common.enable_smooth_scroll) {
1567                 textview_smooth_scroll_one_line(textview, up);
1568                 return;
1569         }
1570
1571         if (!up) {
1572                 upper = vadj->upper - vadj->page_size;
1573                 if (vadj->value < upper) {
1574                         vadj->value += vadj->step_increment;
1575                         vadj->value = MIN(vadj->value, upper);
1576                         g_signal_emit_by_name(G_OBJECT(vadj),
1577                                               "value_changed", 0);
1578                 }
1579         } else {
1580                 if (vadj->value > 0.0) {
1581                         vadj->value -= vadj->step_increment;
1582                         vadj->value = MAX(vadj->value, 0.0);
1583                         g_signal_emit_by_name(G_OBJECT(vadj),
1584                                               "value_changed", 0);
1585                 }
1586         }
1587 }
1588
1589 gboolean textview_scroll_page(TextView *textview, gboolean up)
1590 {
1591         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1592         GtkAdjustment *vadj = text->vadjustment;
1593         gfloat upper;
1594         gfloat page_incr;
1595
1596         if (prefs_common.enable_smooth_scroll)
1597                 return textview_smooth_scroll_page(textview, up);
1598
1599         if (prefs_common.scroll_halfpage)
1600                 page_incr = vadj->page_increment / 2;
1601         else
1602                 page_incr = vadj->page_increment;
1603
1604         if (!up) {
1605                 upper = vadj->upper - vadj->page_size;
1606                 if (vadj->value < upper) {
1607                         vadj->value += page_incr;
1608                         vadj->value = MIN(vadj->value, upper);
1609                         g_signal_emit_by_name(G_OBJECT(vadj),
1610                                               "value_changed", 0);
1611                 } else
1612                         return FALSE;
1613         } else {
1614                 if (vadj->value > 0.0) {
1615                         vadj->value -= page_incr;
1616                         vadj->value = MAX(vadj->value, 0.0);
1617                         g_signal_emit_by_name(G_OBJECT(vadj),
1618                                               "value_changed", 0);
1619                 } else
1620                         return FALSE;
1621         }
1622
1623         return TRUE;
1624 }
1625
1626 static void textview_smooth_scroll_do(TextView *textview,
1627                                       gfloat old_value, gfloat last_value,
1628                                       gint step)
1629 {
1630         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1631         GtkAdjustment *vadj = text->vadjustment;
1632         gint change_value;
1633         gboolean up;
1634         gint i;
1635
1636         if (old_value < last_value) {
1637                 change_value = last_value - old_value;
1638                 up = FALSE;
1639         } else {
1640                 change_value = old_value - last_value;
1641                 up = TRUE;
1642         }
1643
1644         for (i = step; i <= change_value; i += step) {
1645                 vadj->value = old_value + (up ? -i : i);
1646                 g_signal_emit_by_name(G_OBJECT(vadj),
1647                                       "value_changed", 0);
1648         }
1649
1650         vadj->value = last_value;
1651         g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1652 }
1653
1654 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1655 {
1656         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1657         GtkAdjustment *vadj = text->vadjustment;
1658         gfloat upper;
1659         gfloat old_value;
1660         gfloat last_value;
1661
1662         if (!up) {
1663                 upper = vadj->upper - vadj->page_size;
1664                 if (vadj->value < upper) {
1665                         old_value = vadj->value;
1666                         last_value = vadj->value + vadj->step_increment;
1667                         last_value = MIN(last_value, upper);
1668
1669                         textview_smooth_scroll_do(textview, old_value,
1670                                                   last_value,
1671                                                   prefs_common.scroll_step);
1672                 }
1673         } else {
1674                 if (vadj->value > 0.0) {
1675                         old_value = vadj->value;
1676                         last_value = vadj->value - vadj->step_increment;
1677                         last_value = MAX(last_value, 0.0);
1678
1679                         textview_smooth_scroll_do(textview, old_value,
1680                                                   last_value,
1681                                                   prefs_common.scroll_step);
1682                 }
1683         }
1684 }
1685
1686 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1687 {
1688         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1689         GtkAdjustment *vadj = text->vadjustment;
1690         gfloat upper;
1691         gfloat page_incr;
1692         gfloat old_value;
1693         gfloat last_value;
1694
1695         if (prefs_common.scroll_halfpage)
1696                 page_incr = vadj->page_increment / 2;
1697         else
1698                 page_incr = vadj->page_increment;
1699
1700         if (!up) {
1701                 upper = vadj->upper - vadj->page_size;
1702                 if (vadj->value < upper) {
1703                         old_value = vadj->value;
1704                         last_value = vadj->value + page_incr;
1705                         last_value = MIN(last_value, upper);
1706
1707                         textview_smooth_scroll_do(textview, old_value,
1708                                                   last_value,
1709                                                   prefs_common.scroll_step);
1710                 } else
1711                         return FALSE;
1712         } else {
1713                 if (vadj->value > 0.0) {
1714                         old_value = vadj->value;
1715                         last_value = vadj->value - page_incr;
1716                         last_value = MAX(last_value, 0.0);
1717
1718                         textview_smooth_scroll_do(textview, old_value,
1719                                                   last_value,
1720                                                   prefs_common.scroll_step);
1721                 } else
1722                         return FALSE;
1723         }
1724
1725         return TRUE;
1726 }
1727
1728 #define KEY_PRESS_EVENT_STOP() \
1729         g_signal_stop_emission_by_name(G_OBJECT(widget), \
1730                                        "key_press_event");
1731
1732 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1733                                  TextView *textview)
1734 {
1735         SummaryView *summaryview = NULL;
1736         MessageView *messageview = textview->messageview;
1737
1738         if (!event) return FALSE;
1739         if (messageview->mainwin)
1740                 summaryview = messageview->mainwin->summaryview;
1741
1742         switch (event->keyval) {
1743         case GDK_Tab:
1744         case GDK_Home:
1745         case GDK_Left:
1746         case GDK_Up:
1747         case GDK_Right:
1748         case GDK_Down:
1749         case GDK_Page_Up:
1750         case GDK_Page_Down:
1751         case GDK_End:
1752         case GDK_Control_L:
1753         case GDK_Control_R:
1754                 return FALSE;
1755         case GDK_space:
1756                 if (summaryview)
1757                         summary_pass_key_press_event(summaryview, event);
1758                 else
1759                         textview_scroll_page
1760                                 (textview,
1761                                  (event->state &
1762                                   (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1763                 break;
1764         case GDK_BackSpace:
1765                 textview_scroll_page(textview, TRUE);
1766                 break;
1767         case GDK_Return:
1768                 textview_scroll_one_line
1769                         (textview, (event->state &
1770                                     (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1771                 break;
1772         case GDK_Delete:
1773                 if (summaryview)
1774                         summary_pass_key_press_event(summaryview, event);
1775                 break;
1776         case GDK_y:
1777         case GDK_t:
1778         case GDK_l:
1779                 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1780                         KEY_PRESS_EVENT_STOP();
1781                         mimeview_pass_key_press_event(messageview->mimeview,
1782                                                       event);
1783                         break;
1784                 }
1785                 /* possible fall through */
1786         default:
1787                 if (summaryview &&
1788                     event->window != messageview->mainwin->window->window) {
1789                         GdkEventKey tmpev = *event;
1790
1791                         tmpev.window = messageview->mainwin->window->window;
1792                         KEY_PRESS_EVENT_STOP();
1793                         gtk_widget_event(messageview->mainwin->window,
1794                                          (GdkEvent *)&tmpev);
1795                 }
1796                 break;
1797         }
1798
1799         return TRUE;
1800 }
1801
1802 static gboolean textview_motion_notify(GtkWidget *widget,
1803                                        GdkEventMotion *event,
1804                                        TextView *textview)
1805 {
1806         textview_uri_update(textview, event->x, event->y);
1807         gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1808
1809         return FALSE;
1810 }
1811
1812 static gboolean textview_leave_notify(GtkWidget *widget,
1813                                       GdkEventCrossing *event,
1814                                       TextView *textview)
1815 {
1816         textview_uri_update(textview, -1, -1);
1817
1818         return FALSE;
1819 }
1820
1821 static gboolean textview_visibility_notify(GtkWidget *widget,
1822                                            GdkEventVisibility *event,
1823                                            TextView *textview)
1824 {
1825         gint wx, wy;
1826         GdkWindow *window;
1827
1828         window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
1829                                           GTK_TEXT_WINDOW_TEXT);
1830
1831         /* check if occurred for the text window part */
1832         if (window != event->window)
1833                 return FALSE;
1834         
1835         gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1836         textview_uri_update(textview, wx, wy);
1837
1838         return FALSE;
1839 }
1840
1841 static void textview_uri_update(TextView *textview, gint x, gint y)
1842 {
1843         GtkTextBuffer *buffer;
1844         GtkTextIter start_iter, end_iter;
1845         RemoteURI *uri = NULL;
1846         
1847         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1848
1849         if (x != -1 && y != -1) {
1850                 gint bx, by;
1851                 GtkTextIter iter;
1852                 GSList *tags;
1853                 GSList *cur;
1854             
1855                 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text), 
1856                                                       GTK_TEXT_WINDOW_WIDGET,
1857                                                       x, y, &bx, &by);
1858                 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1859                                                    &iter, bx, by);
1860
1861                 tags = gtk_text_iter_get_tags(&iter);
1862                 for (cur = tags; cur != NULL; cur = cur->next) {
1863                         GtkTextTag *tag = cur->data;
1864                         char *name;
1865
1866                         g_object_get(G_OBJECT(tag), "name", &name, NULL);
1867                         if (!strcmp(name, "link")
1868                             && textview_get_uri_range(textview, &iter, tag,
1869                                                       &start_iter, &end_iter))
1870                                 uri = textview_get_uri_from_range(textview,
1871                                                                   &iter, tag,
1872                                                                   &start_iter,
1873                                                                   &end_iter);
1874                         g_free(name);
1875
1876                         if (uri)
1877                                 break;
1878                 }
1879                 g_slist_free(tags);
1880         }
1881         
1882         if (uri != textview->uri_hover) {
1883                 GdkWindow *window;
1884
1885                 if (textview->uri_hover)
1886                         gtk_text_buffer_remove_tag_by_name(buffer,
1887                                                            "link-hover",
1888                                                            &textview->uri_hover_start_iter,
1889                                                            &textview->uri_hover_end_iter);
1890                     
1891                 textview->uri_hover = uri;
1892                 if (uri) {
1893                         textview->uri_hover_start_iter = start_iter;
1894                         textview->uri_hover_end_iter = end_iter;
1895                 }
1896                 
1897                 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1898                                                   GTK_TEXT_WINDOW_TEXT);
1899                 gdk_window_set_cursor(window, uri ? hand_cursor : text_cursor);
1900
1901                 TEXTVIEW_STATUSBAR_POP(textview);
1902
1903                 if (uri) {
1904                         char *trimmed_uri;
1905
1906                         gtk_text_buffer_apply_tag_by_name(buffer,
1907                                                           "link-hover",
1908                                                           &start_iter,
1909                                                           &end_iter);
1910
1911                         trimmed_uri = trim_string(uri->uri, 60);
1912                         TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1913                         g_free(trimmed_uri);
1914                 }
1915         }
1916 }
1917
1918 static gboolean textview_get_uri_range(TextView *textview,
1919                                        GtkTextIter *iter,
1920                                        GtkTextTag *tag,
1921                                        GtkTextIter *start_iter,
1922                                        GtkTextIter *end_iter)
1923 {
1924         GtkTextIter _start_iter, _end_iter;
1925
1926         _end_iter = *iter;
1927         if (!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
1928                 debug_print("Can't find end");
1929                 return FALSE;
1930         }
1931
1932         _start_iter = _end_iter;
1933         if (!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
1934                 debug_print("Can't find start.");
1935                 return FALSE;
1936         }
1937
1938         *start_iter = _start_iter;
1939         *end_iter = _end_iter;
1940
1941         return TRUE;
1942 }
1943
1944 static RemoteURI *textview_get_uri_from_range(TextView *textview,
1945                                               GtkTextIter *iter,
1946                                               GtkTextTag *tag,
1947                                               GtkTextIter *start_iter,
1948                                               GtkTextIter *end_iter)
1949 {
1950         gint start_pos, end_pos, cur_pos;
1951         RemoteURI *uri = NULL;
1952         GSList *cur;
1953
1954         start_pos = gtk_text_iter_get_offset(start_iter);
1955         end_pos = gtk_text_iter_get_offset(end_iter);
1956         cur_pos = gtk_text_iter_get_offset(iter);
1957
1958         for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1959                 RemoteURI *uri_ = (RemoteURI *)cur->data;
1960                 if (start_pos == uri_->start &&
1961                     end_pos ==  uri_->end) {
1962                         uri = uri_;
1963                         break;
1964                 } else if (start_pos == uri_->start ||
1965                            end_pos == uri_->end) {
1966                         /* in case of contiguous links, textview_get_uri_range
1967                          * returns a broader range (start of 1st link to end
1968                          * of last link).
1969                          * In that case, correct link is the one covering
1970                          * current iter.
1971                          */
1972                         if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
1973                                 uri = uri_;
1974                                 break;
1975                         }
1976                 } 
1977         }
1978
1979         return uri;
1980 }
1981
1982 static RemoteURI *textview_get_uri(TextView *textview,
1983                                    GtkTextIter *iter,
1984                                    GtkTextTag *tag)
1985 {
1986         GtkTextIter start_iter, end_iter;
1987         RemoteURI *uri = NULL;
1988
1989         if (textview_get_uri_range(textview, iter, tag, &start_iter,
1990                                    &end_iter))
1991                 uri = textview_get_uri_from_range(textview, iter, tag,
1992                                                   &start_iter, &end_iter);
1993
1994         return uri;
1995 }
1996
1997 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
1998                                             GdkEvent *event, GtkTextIter *iter,
1999                                             TextView *textview)
2000 {
2001         GdkEventButton *bevent;
2002         RemoteURI *uri = NULL;
2003         GSList *cur;
2004         gchar *trimmed_uri;
2005
2006         if (!event)
2007                 return FALSE;
2008
2009         if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2010                 && event->type != GDK_MOTION_NOTIFY)
2011                 return FALSE;
2012
2013         uri = textview_get_uri(textview, iter, tag);
2014         if (!uri)
2015                 return FALSE;
2016
2017         bevent = (GdkEventButton *) event;
2018         
2019         /* doubleclick: open compose / add address / browser */
2020         if ((event->type == GDK_BUTTON_PRESS && bevent->button == 1) ||
2021                 bevent->button == 2 || bevent->button == 3) {
2022                 if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2023                         if (bevent->button == 3) {
2024                                 g_object_set_data(
2025                                         G_OBJECT(textview->mail_popup_menu),
2026                                         "menu_button", uri);
2027                                 gtk_menu_popup(GTK_MENU(textview->mail_popup_menu), 
2028                                                NULL, NULL, NULL, NULL, 
2029                                                bevent->button, bevent->time);
2030                         } else {
2031                                 PrefsAccount *account = NULL;
2032
2033                                 if (textview->messageview && textview->messageview->msginfo &&
2034                                     textview->messageview->msginfo->folder) {
2035                                         FolderItem   *folder_item;
2036
2037                                         folder_item = textview->messageview->msginfo->folder;
2038                                         if (folder_item->prefs && folder_item->prefs->enable_default_account)
2039                                                 account = account_find_from_id(folder_item->prefs->default_account);
2040                                 }
2041                                 compose_new(account, uri->uri + 7, NULL);
2042                         }
2043                         return TRUE;
2044                 } else {
2045                         if (bevent->button == 1 &&
2046                             textview_uri_security_check(textview, uri) == TRUE) 
2047                                         open_uri(uri->uri,
2048                                                  prefs_common.uri_cmd);
2049                         else if (bevent->button == 3) {
2050                                 g_object_set_data(
2051                                         G_OBJECT(textview->link_popup_menu),
2052                                         "menu_button", uri);
2053                                 gtk_menu_popup(GTK_MENU(textview->link_popup_menu), 
2054                                                NULL, NULL, NULL, NULL, 
2055                                                bevent->button, bevent->time);
2056                         }
2057                         return TRUE;
2058                 }
2059         }
2060
2061         return FALSE;
2062 }
2063
2064 /*!
2065  *\brief    Check to see if a web URL has been disguised as a different
2066  *          URL (possible with HTML email).
2067  *
2068  *\param    uri The uri to check
2069  *
2070  *\param    textview The TextView the URL is contained in
2071  *
2072  *\return   gboolean TRUE if the URL is ok, or if the user chose to open
2073  *          it anyway, otherwise FALSE          
2074  */
2075 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2076 {
2077         gchar *visible_str;
2078         gboolean retval = TRUE;
2079         GtkTextBuffer *buffer;
2080         GtkTextIter start, end;
2081
2082         if (is_uri_string(uri->uri) == FALSE)
2083                 return TRUE;
2084
2085         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2086
2087         gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2088         gtk_text_buffer_get_iter_at_offset(buffer, &end,   uri->end);
2089
2090         visible_str = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2091
2092         if (visible_str == NULL)
2093                 return TRUE;
2094
2095         if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2096                 gchar *uri_path;
2097                 gchar *visible_uri_path;
2098
2099                 uri_path = get_uri_path(uri->uri);
2100                 visible_uri_path = get_uri_path(visible_str);
2101                 if (strcmp(uri_path, visible_uri_path) != 0)
2102                         retval = FALSE;
2103         }
2104
2105         if (retval == FALSE) {
2106                 gchar *msg;
2107                 AlertValue aval;
2108
2109                 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2110                                         "the apparent URL (%s).\n"
2111                                         "Open it anyway?"),
2112                                       uri->uri, visible_str);
2113                 aval = alertpanel_with_type(_("Warning"), msg,
2114                                             GTK_STOCK_YES, GTK_STOCK_NO,
2115                                             NULL, NULL, ALERT_WARNING);
2116                 g_free(msg);
2117                 if (aval == G_ALERTDEFAULT)
2118                         retval = TRUE;
2119         }
2120
2121         g_free(visible_str);
2122
2123         return retval;
2124 }
2125
2126 static void textview_uri_list_remove_all(GSList *uri_list)
2127 {
2128         GSList *cur;
2129
2130         for (cur = uri_list; cur != NULL; cur = cur->next) {
2131                 if (cur->data) {
2132                         g_free(((RemoteURI *)cur->data)->uri);
2133                         g_free(cur->data);
2134                 }
2135         }
2136
2137         g_slist_free(uri_list);
2138 }
2139
2140 static void open_uri_cb (TextView *textview, guint action, void *data)
2141 {
2142         RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2143                                            "menu_button");
2144         if (uri == NULL)
2145                 return;
2146
2147         if (textview_uri_security_check(textview, uri) == TRUE) 
2148                 open_uri(uri->uri,
2149                          prefs_common.uri_cmd);
2150         g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2151                           NULL);
2152 }
2153
2154 static void copy_uri_cb (TextView *textview, guint action, void *data)
2155 {
2156         RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2157                                            "menu_button");
2158         if (uri == NULL)
2159                 return;
2160
2161         gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), uri->uri, -1);
2162         g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2163                           NULL);
2164 }
2165
2166 static void add_uri_to_addrbook_cb (TextView *textview, guint action, void *data)
2167 {
2168         gchar *fromname, *fromaddress;
2169         RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2170                                            "menu_button");
2171         if (uri == NULL)
2172                 return;
2173
2174         /* extract url */
2175         fromaddress = g_strdup(uri->uri + 7);
2176         /* Hiroyuki: please put this function in utils.c! */
2177         fromname = procheader_get_fromname(fromaddress);
2178         extract_address(fromaddress);
2179         g_message("adding from textview %s <%s>", fromname, fromaddress);
2180         /* Add to address book - Match */
2181         addressbook_add_contact( fromname, fromaddress, NULL );
2182
2183         g_free(fromaddress);
2184         g_free(fromname);
2185 }
2186
2187 static void mail_to_uri_cb (TextView *textview, guint action, void *data)
2188 {
2189         PrefsAccount *account = NULL;
2190         RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2191                                            "menu_button");
2192         if (uri == NULL)
2193                 return;
2194
2195         if (textview->messageview && textview->messageview->msginfo &&
2196             textview->messageview->msginfo->folder) {
2197                 FolderItem   *folder_item;
2198
2199                 folder_item = textview->messageview->msginfo->folder;
2200                 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2201                         account = account_find_from_id(folder_item->prefs->default_account);
2202         }
2203         compose_new(account, uri->uri + 7, NULL);
2204 }
2205
2206 static void copy_mail_to_uri_cb (TextView *textview, guint action, void *data)
2207 {
2208         RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2209                                            "menu_button");
2210         if (uri == NULL)
2211                 return;
2212
2213         gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), uri->uri + 7, -1);
2214         g_object_set_data(G_OBJECT(textview->mail_popup_menu), "menu_button",
2215                           NULL);
2216 }
2217