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