2005-02-10 [paul] 1.0.1cvs3.2
[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
202 static GtkItemFactoryEntry textview_link_popup_entries[] = 
203 {
204         {N_("/_Open link"),             NULL, open_uri_cb, 0, NULL},
205         {N_("/_Copy link location"),    NULL, copy_uri_cb, 0, NULL},
206 };
207
208 static GtkItemFactoryEntry textview_mail_popup_entries[] = 
209 {
210         {N_("/_Add to addressbook"),    NULL, add_uri_to_addrbook_cb, 0, NULL},
211         {N_("/_Email"),                 NULL, mail_to_uri_cb, 0, NULL},
212 };
213
214
215 TextView *textview_create(void)
216 {
217         TextView *textview;
218         GtkWidget *vbox;
219         GtkWidget *scrolledwin;
220         GtkWidget *text;
221         GtkTextBuffer *buffer;
222         GtkClipboard *clipboard;
223         GtkItemFactory *link_popupfactory, *mail_popupfactory;
224         GtkWidget *link_popupmenu, *mail_popupmenu;
225         gint n_entries;
226
227         debug_print("Creating text view...\n");
228         textview = g_new0(TextView, 1);
229
230         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
231         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
232                                        GTK_POLICY_AUTOMATIC,
233                                        GTK_POLICY_AUTOMATIC);
234         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
235                                             GTK_SHADOW_IN);
236         gtk_widget_set_size_request
237                 (scrolledwin, prefs_common.mainview_width, -1);
238
239         /* create GtkSText widgets for single-byte and multi-byte character */
240         text = gtk_text_view_new();
241         gtk_widget_add_events(text, GDK_LEAVE_NOTIFY_MASK);
242         gtk_widget_show(text);
243         gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
244         gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
245         gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
246         gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
247         gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
248
249         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
250         clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
251         gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
252
253         gtk_widget_ensure_style(text);
254
255         gtk_widget_ref(scrolledwin);
256
257         gtk_container_add(GTK_CONTAINER(scrolledwin), text);
258
259         g_signal_connect(G_OBJECT(text), "key_press_event",
260                          G_CALLBACK(textview_key_pressed),
261                          textview);
262         g_signal_connect(G_OBJECT(text), "motion_notify_event",
263                          G_CALLBACK(textview_motion_notify),
264                          textview);
265         g_signal_connect(G_OBJECT(text), "leave_notify_event",
266                          G_CALLBACK(textview_leave_notify),
267                          textview);
268         g_signal_connect(G_OBJECT(text), "visibility_notify_event",
269                          G_CALLBACK(textview_visibility_notify),
270                          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 (!ispunct(*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 #warning FIXME_GTK2
1449 #if 0
1450                 if (textview->text_is_mb == TRUE)
1451                         conv_unreadable_locale(header->body);
1452 #endif
1453
1454                 if (prefs_common.enable_color &&
1455                     (procheader_headername_equal(header->name, "X-Mailer") ||
1456                      procheader_headername_equal(header->name,
1457                                                  "X-Newsreader")) &&
1458                     strstr(header->body, "Sylpheed") != NULL) {
1459                         gtk_text_buffer_get_end_iter (buffer, &iter);
1460                         gtk_text_buffer_insert_with_tags_by_name
1461                                 (buffer, &iter, header->body, -1,
1462                                  "header", "emphasis", NULL);
1463                 } else if (prefs_common.enable_color) {
1464                         textview_make_clickable_parts(textview, "header", "link",
1465                                                       header->body);
1466                 } else {
1467                         textview_make_clickable_parts(textview, "header", NULL,
1468                                                       header->body);
1469                 }
1470                 gtk_text_buffer_get_end_iter (buffer, &iter);
1471                 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1472                                                          "header", NULL);
1473         }
1474 }
1475
1476 gboolean textview_search_string(TextView *textview, const gchar *str,
1477                                 gboolean case_sens)
1478 {
1479         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1480         GtkTextBuffer *buffer;
1481         GtkTextIter iter, end_iter;
1482         GtkTextMark *mark;
1483         gint pos;
1484         gint len;
1485
1486         g_return_val_if_fail(str != NULL, FALSE);
1487
1488         buffer = gtk_text_view_get_buffer(text);
1489
1490         len = g_utf8_strlen(str, -1);
1491         g_return_val_if_fail(len >= 0, FALSE);
1492
1493         mark = gtk_text_buffer_get_insert(buffer);
1494         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1495         pos = gtk_text_iter_get_offset(&iter);
1496
1497         if ((pos = gtkut_text_buffer_find(buffer, pos, str, case_sens)) != -1) {
1498                 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, pos);
1499                 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos + len);
1500                 gtk_text_buffer_select_range(buffer, &iter, &end_iter);
1501                 gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1502                 return TRUE;
1503         }
1504
1505         return FALSE;
1506 }
1507
1508 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1509                                          gboolean case_sens)
1510 {
1511         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1512         GtkTextBuffer *buffer;
1513         GtkTextIter iter, end_iter;
1514         GtkTextMark *mark;
1515         gint pos;
1516         gunichar *wcs;
1517         gint len;
1518         glong items_read = 0, items_written = 0;
1519         GError *error = NULL;
1520         gboolean found = FALSE;
1521
1522         g_return_val_if_fail(str != NULL, FALSE);
1523
1524         buffer = gtk_text_view_get_buffer(text);
1525
1526         wcs = g_utf8_to_ucs4(str, -1, &items_read, &items_written, &error);
1527         if (error != NULL) {
1528                 g_warning("An error occured while converting a string from UTF-8 to UCS-4: %s\n", error->message);
1529                 g_error_free(error);
1530         }
1531         if (!wcs || items_written <= 0) return FALSE;
1532         len = (gint)items_written;
1533
1534         mark = gtk_text_buffer_get_insert(buffer);
1535         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1536
1537         while (gtk_text_iter_backward_char(&iter)) {
1538                 pos = gtk_text_iter_get_offset(&iter);
1539                 if (gtkut_text_buffer_match_string
1540                         (buffer, pos, wcs, len, case_sens) == TRUE) {
1541                         gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1542                         gtk_text_buffer_get_iter_at_offset
1543                                 (buffer, &end_iter, pos + len);
1544                         gtk_text_buffer_select_range(buffer, &iter, &end_iter);
1545                         gtk_text_view_scroll_to_mark
1546                                 (text, mark, 0.0, FALSE, 0.0, 0.0);
1547                         found = TRUE;
1548                         break;
1549                 }
1550         }
1551
1552         g_free(wcs);
1553         return found;
1554 }
1555
1556 void textview_scroll_one_line(TextView *textview, gboolean up)
1557 {
1558         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1559         GtkAdjustment *vadj = text->vadjustment;
1560         gfloat upper;
1561
1562         if (prefs_common.enable_smooth_scroll) {
1563                 textview_smooth_scroll_one_line(textview, up);
1564                 return;
1565         }
1566
1567         if (!up) {
1568                 upper = vadj->upper - vadj->page_size;
1569                 if (vadj->value < upper) {
1570                         vadj->value += vadj->step_increment;
1571                         vadj->value = MIN(vadj->value, upper);
1572                         g_signal_emit_by_name(G_OBJECT(vadj),
1573                                               "value_changed", 0);
1574                 }
1575         } else {
1576                 if (vadj->value > 0.0) {
1577                         vadj->value -= vadj->step_increment;
1578                         vadj->value = MAX(vadj->value, 0.0);
1579                         g_signal_emit_by_name(G_OBJECT(vadj),
1580                                               "value_changed", 0);
1581                 }
1582         }
1583 }
1584
1585 gboolean textview_scroll_page(TextView *textview, gboolean up)
1586 {
1587         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1588         GtkAdjustment *vadj = text->vadjustment;
1589         gfloat upper;
1590         gfloat page_incr;
1591
1592         if (prefs_common.enable_smooth_scroll)
1593                 return textview_smooth_scroll_page(textview, up);
1594
1595         if (prefs_common.scroll_halfpage)
1596                 page_incr = vadj->page_increment / 2;
1597         else
1598                 page_incr = vadj->page_increment;
1599
1600         if (!up) {
1601                 upper = vadj->upper - vadj->page_size;
1602                 if (vadj->value < upper) {
1603                         vadj->value += page_incr;
1604                         vadj->value = MIN(vadj->value, upper);
1605                         g_signal_emit_by_name(G_OBJECT(vadj),
1606                                               "value_changed", 0);
1607                 } else
1608                         return FALSE;
1609         } else {
1610                 if (vadj->value > 0.0) {
1611                         vadj->value -= page_incr;
1612                         vadj->value = MAX(vadj->value, 0.0);
1613                         g_signal_emit_by_name(G_OBJECT(vadj),
1614                                               "value_changed", 0);
1615                 } else
1616                         return FALSE;
1617         }
1618
1619         return TRUE;
1620 }
1621
1622 static void textview_smooth_scroll_do(TextView *textview,
1623                                       gfloat old_value, gfloat last_value,
1624                                       gint step)
1625 {
1626         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1627         GtkAdjustment *vadj = text->vadjustment;
1628         gint change_value;
1629         gboolean up;
1630         gint i;
1631
1632         if (old_value < last_value) {
1633                 change_value = last_value - old_value;
1634                 up = FALSE;
1635         } else {
1636                 change_value = old_value - last_value;
1637                 up = TRUE;
1638         }
1639
1640         for (i = step; i <= change_value; i += step) {
1641                 vadj->value = old_value + (up ? -i : i);
1642                 g_signal_emit_by_name(G_OBJECT(vadj),
1643                                       "value_changed", 0);
1644         }
1645
1646         vadj->value = last_value;
1647         g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1648 }
1649
1650 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1651 {
1652         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1653         GtkAdjustment *vadj = text->vadjustment;
1654         gfloat upper;
1655         gfloat old_value;
1656         gfloat last_value;
1657
1658         if (!up) {
1659                 upper = vadj->upper - vadj->page_size;
1660                 if (vadj->value < upper) {
1661                         old_value = vadj->value;
1662                         last_value = vadj->value + vadj->step_increment;
1663                         last_value = MIN(last_value, upper);
1664
1665                         textview_smooth_scroll_do(textview, old_value,
1666                                                   last_value,
1667                                                   prefs_common.scroll_step);
1668                 }
1669         } else {
1670                 if (vadj->value > 0.0) {
1671                         old_value = vadj->value;
1672                         last_value = vadj->value - vadj->step_increment;
1673                         last_value = MAX(last_value, 0.0);
1674
1675                         textview_smooth_scroll_do(textview, old_value,
1676                                                   last_value,
1677                                                   prefs_common.scroll_step);
1678                 }
1679         }
1680 }
1681
1682 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1683 {
1684         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1685         GtkAdjustment *vadj = text->vadjustment;
1686         gfloat upper;
1687         gfloat page_incr;
1688         gfloat old_value;
1689         gfloat last_value;
1690
1691         if (prefs_common.scroll_halfpage)
1692                 page_incr = vadj->page_increment / 2;
1693         else
1694                 page_incr = vadj->page_increment;
1695
1696         if (!up) {
1697                 upper = vadj->upper - vadj->page_size;
1698                 if (vadj->value < upper) {
1699                         old_value = vadj->value;
1700                         last_value = vadj->value + page_incr;
1701                         last_value = MIN(last_value, upper);
1702
1703                         textview_smooth_scroll_do(textview, old_value,
1704                                                   last_value,
1705                                                   prefs_common.scroll_step);
1706                 } else
1707                         return FALSE;
1708         } else {
1709                 if (vadj->value > 0.0) {
1710                         old_value = vadj->value;
1711                         last_value = vadj->value - page_incr;
1712                         last_value = MAX(last_value, 0.0);
1713
1714                         textview_smooth_scroll_do(textview, old_value,
1715                                                   last_value,
1716                                                   prefs_common.scroll_step);
1717                 } else
1718                         return FALSE;
1719         }
1720
1721         return TRUE;
1722 }
1723
1724 #define KEY_PRESS_EVENT_STOP() \
1725         g_signal_stop_emission_by_name(G_OBJECT(widget), \
1726                                        "key_press_event");
1727
1728 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1729                                  TextView *textview)
1730 {
1731         SummaryView *summaryview = NULL;
1732         MessageView *messageview = textview->messageview;
1733
1734         if (!event) return FALSE;
1735         if (messageview->mainwin)
1736                 summaryview = messageview->mainwin->summaryview;
1737
1738         switch (event->keyval) {
1739         case GDK_Tab:
1740         case GDK_Home:
1741         case GDK_Left:
1742         case GDK_Up:
1743         case GDK_Right:
1744         case GDK_Down:
1745         case GDK_Page_Up:
1746         case GDK_Page_Down:
1747         case GDK_End:
1748         case GDK_Control_L:
1749         case GDK_Control_R:
1750                 return FALSE;
1751         case GDK_space:
1752                 if (summaryview)
1753                         summary_pass_key_press_event(summaryview, event);
1754                 else
1755                         textview_scroll_page
1756                                 (textview,
1757                                  (event->state &
1758                                   (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1759                 break;
1760         case GDK_BackSpace:
1761                 textview_scroll_page(textview, TRUE);
1762                 break;
1763         case GDK_Return:
1764                 textview_scroll_one_line
1765                         (textview, (event->state &
1766                                     (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1767                 break;
1768         case GDK_Delete:
1769                 if (summaryview)
1770                         summary_pass_key_press_event(summaryview, event);
1771                 break;
1772         case GDK_y:
1773         case GDK_t:
1774         case GDK_l:
1775                 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1776                         KEY_PRESS_EVENT_STOP();
1777                         mimeview_pass_key_press_event(messageview->mimeview,
1778                                                       event);
1779                         break;
1780                 }
1781                 /* possible fall through */
1782         default:
1783                 if (summaryview &&
1784                     event->window != messageview->mainwin->window->window) {
1785                         GdkEventKey tmpev = *event;
1786
1787                         tmpev.window = messageview->mainwin->window->window;
1788                         KEY_PRESS_EVENT_STOP();
1789                         gtk_widget_event(messageview->mainwin->window,
1790                                          (GdkEvent *)&tmpev);
1791                 }
1792                 break;
1793         }
1794
1795         return TRUE;
1796 }
1797
1798 static gboolean textview_motion_notify(GtkWidget *widget,
1799                                        GdkEventMotion *event,
1800                                        TextView *textview)
1801 {
1802         textview_uri_update(textview, event->x, event->y);
1803         gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1804
1805         return FALSE;
1806 }
1807
1808 static gboolean textview_leave_notify(GtkWidget *widget,
1809                                       GdkEventCrossing *event,
1810                                       TextView *textview)
1811 {
1812         textview_uri_update(textview, -1, -1);
1813
1814         return FALSE;
1815 }
1816
1817 static gboolean textview_visibility_notify(GtkWidget *widget,
1818                                            GdkEventVisibility *event,
1819                                            TextView *textview)
1820 {
1821         gint wx, wy;
1822         GdkWindow *window;
1823
1824         window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
1825                                           GTK_TEXT_WINDOW_TEXT);
1826
1827         /* check if occurred for the text window part */
1828         if (window != event->window)
1829                 return FALSE;
1830         
1831         gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1832         textview_uri_update(textview, wx, wy);
1833
1834         return FALSE;
1835 }
1836
1837 static void textview_uri_update(TextView *textview, gint x, gint y)
1838 {
1839         GtkTextBuffer *buffer;
1840         GtkTextIter start_iter, end_iter;
1841         RemoteURI *uri = NULL;
1842         
1843         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1844
1845         if (x != -1 && y != -1) {
1846                 gint bx, by;
1847                 GtkTextIter iter;
1848                 GSList *tags;
1849                 GSList *cur;
1850             
1851                 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text), 
1852                                                       GTK_TEXT_WINDOW_WIDGET,
1853                                                       x, y, &bx, &by);
1854                 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1855                                                    &iter, bx, by);
1856
1857                 tags = gtk_text_iter_get_tags(&iter);
1858                 for (cur = tags; cur != NULL; cur = cur->next) {
1859                         GtkTextTag *tag = cur->data;
1860                         char *name;
1861
1862                         g_object_get(G_OBJECT(tag), "name", &name, NULL);
1863                         if (!strcmp(name, "link")
1864                             && textview_get_uri_range(textview, &iter, tag,
1865                                                       &start_iter, &end_iter))
1866                                 uri = textview_get_uri_from_range(textview,
1867                                                                   &iter, tag,
1868                                                                   &start_iter,
1869                                                                   &end_iter);
1870                         g_free(name);
1871
1872                         if (uri)
1873                                 break;
1874                 }
1875                 g_slist_free(tags);
1876         }
1877         
1878         if (uri != textview->uri_hover) {
1879                 GdkWindow *window;
1880
1881                 if (textview->uri_hover)
1882                         gtk_text_buffer_remove_tag_by_name(buffer,
1883                                                            "link-hover",
1884                                                            &textview->uri_hover_start_iter,
1885                                                            &textview->uri_hover_end_iter);
1886                     
1887                 textview->uri_hover = uri;
1888                 if (uri) {
1889                         textview->uri_hover_start_iter = start_iter;
1890                         textview->uri_hover_end_iter = end_iter;
1891                 }
1892                 
1893                 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1894                                                   GTK_TEXT_WINDOW_TEXT);
1895                 gdk_window_set_cursor(window, uri ? hand_cursor : text_cursor);
1896
1897                 TEXTVIEW_STATUSBAR_POP(textview);
1898
1899                 if (uri) {
1900                         char *trimmed_uri;
1901
1902                         gtk_text_buffer_apply_tag_by_name(buffer,
1903                                                           "link-hover",
1904                                                           &start_iter,
1905                                                           &end_iter);
1906
1907                         trimmed_uri = trim_string(uri->uri, 60);
1908                         TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1909                         g_free(trimmed_uri);
1910                 }
1911         }
1912 }
1913
1914 static gboolean textview_get_uri_range(TextView *textview,
1915                                        GtkTextIter *iter,
1916                                        GtkTextTag *tag,
1917                                        GtkTextIter *start_iter,
1918                                        GtkTextIter *end_iter)
1919 {
1920         GtkTextIter _start_iter, _end_iter;
1921
1922         _end_iter = *iter;
1923         if (!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
1924                 debug_print("Can't find end");
1925                 return FALSE;
1926         }
1927
1928         _start_iter = _end_iter;
1929         if (!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
1930                 debug_print("Can't find start.");
1931                 return FALSE;
1932         }
1933
1934         *start_iter = _start_iter;
1935         *end_iter = _end_iter;
1936
1937         return TRUE;
1938 }
1939
1940 static RemoteURI *textview_get_uri_from_range(TextView *textview,
1941                                               GtkTextIter *iter,
1942                                               GtkTextTag *tag,
1943                                               GtkTextIter *start_iter,
1944                                               GtkTextIter *end_iter)
1945 {
1946         gint start_pos, end_pos, cur_pos;
1947         RemoteURI *uri = NULL;
1948         GSList *cur;
1949
1950         start_pos = gtk_text_iter_get_offset(start_iter);
1951         end_pos = gtk_text_iter_get_offset(end_iter);
1952         cur_pos = gtk_text_iter_get_offset(iter);
1953
1954         for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1955                 RemoteURI *uri_ = (RemoteURI *)cur->data;
1956                 if (start_pos == uri_->start &&
1957                     end_pos ==  uri_->end) {
1958                         uri = uri_;
1959                         break;
1960                 } else if (start_pos == uri_->start ||
1961                            end_pos == uri_->end) {
1962                         /* in case of contiguous links, textview_get_uri_range
1963                          * returns a broader range (start of 1st link to end
1964                          * of last link).
1965                          * In that case, correct link is the one covering
1966                          * current iter.
1967                          */
1968                         if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
1969                                 uri = uri_;
1970                                 break;
1971                         }
1972                 } 
1973         }
1974
1975         return uri;
1976 }
1977
1978 static RemoteURI *textview_get_uri(TextView *textview,
1979                                    GtkTextIter *iter,
1980                                    GtkTextTag *tag)
1981 {
1982         GtkTextIter start_iter, end_iter;
1983         RemoteURI *uri = NULL;
1984
1985         if (textview_get_uri_range(textview, iter, tag, &start_iter,
1986                                    &end_iter))
1987                 uri = textview_get_uri_from_range(textview, iter, tag,
1988                                                   &start_iter, &end_iter);
1989
1990         return uri;
1991 }
1992
1993 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
1994                                             GdkEvent *event, GtkTextIter *iter,
1995                                             TextView *textview)
1996 {
1997         GdkEventButton *bevent;
1998         RemoteURI *uri = NULL;
1999         GSList *cur;
2000         gchar *trimmed_uri;
2001
2002         if (!event)
2003                 return FALSE;
2004
2005         if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2006                 && event->type != GDK_MOTION_NOTIFY)
2007                 return FALSE;
2008
2009         uri = textview_get_uri(textview, iter, tag);
2010         if (!uri)
2011                 return FALSE;
2012
2013         bevent = (GdkEventButton *) event;
2014         
2015         /* doubleclick: open compose / add address / browser */
2016         if ((event->type == GDK_BUTTON_PRESS && bevent->button == 1) ||
2017                 bevent->button == 2 || bevent->button == 3) {
2018                 if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2019                         if (bevent->button == 3) {
2020                                 g_object_set_data(
2021                                         G_OBJECT(textview->mail_popup_menu),
2022                                         "menu_button", uri);
2023                                 gtk_menu_popup(GTK_MENU(textview->mail_popup_menu), 
2024                                                NULL, NULL, NULL, NULL, 
2025                                                bevent->button, bevent->time);
2026                         } else {
2027                                 PrefsAccount *account = NULL;
2028
2029                                 if (textview->messageview && textview->messageview->msginfo &&
2030                                     textview->messageview->msginfo->folder) {
2031                                         FolderItem   *folder_item;
2032
2033                                         folder_item = textview->messageview->msginfo->folder;
2034                                         if (folder_item->prefs && folder_item->prefs->enable_default_account)
2035                                                 account = account_find_from_id(folder_item->prefs->default_account);
2036                                 }
2037                                 compose_new(account, uri->uri + 7, NULL);
2038                         }
2039                         return TRUE;
2040                 } else {
2041                         if (bevent->button == 1 &&
2042                             textview_uri_security_check(textview, uri) == TRUE) 
2043                                         open_uri(uri->uri,
2044                                                  prefs_common.uri_cmd);
2045                         else if (bevent->button == 3) {
2046                                 g_object_set_data(
2047                                         G_OBJECT(textview->link_popup_menu),
2048                                         "menu_button", uri);
2049                                 gtk_menu_popup(GTK_MENU(textview->link_popup_menu), 
2050                                                NULL, NULL, NULL, NULL, 
2051                                                bevent->button, bevent->time);
2052                         }
2053                         return TRUE;
2054                 }
2055         }
2056
2057         return FALSE;
2058 }
2059
2060 /*!
2061  *\brief    Check to see if a web URL has been disguised as a different
2062  *          URL (possible with HTML email).
2063  *
2064  *\param    uri The uri to check
2065  *
2066  *\param    textview The TextView the URL is contained in
2067  *
2068  *\return   gboolean TRUE if the URL is ok, or if the user chose to open
2069  *          it anyway, otherwise FALSE          
2070  */
2071 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2072 {
2073         gchar *visible_str;
2074         gboolean retval = TRUE;
2075         GtkTextBuffer *buffer;
2076         GtkTextIter start, end;
2077
2078         if (is_uri_string(uri->uri) == FALSE)
2079                 return TRUE;
2080
2081         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2082
2083         gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2084         gtk_text_buffer_get_iter_at_offset(buffer, &end,   uri->end);
2085
2086         visible_str = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2087
2088         if (visible_str == NULL)
2089                 return TRUE;
2090
2091         if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2092                 gchar *uri_path;
2093                 gchar *visible_uri_path;
2094
2095                 uri_path = get_uri_path(uri->uri);
2096                 visible_uri_path = get_uri_path(visible_str);
2097                 if (strcmp(uri_path, visible_uri_path) != 0)
2098                         retval = FALSE;
2099         }
2100
2101         if (retval == FALSE) {
2102                 gchar *msg;
2103                 AlertValue aval;
2104
2105                 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2106                                         "the apparent URL (%s).\n"
2107                                         "Open it anyway?"),
2108                                       uri->uri, visible_str);
2109                 aval = alertpanel_with_type(_("Warning"), msg,
2110                                             GTK_STOCK_YES, GTK_STOCK_NO,
2111                                             NULL, NULL, ALERT_WARNING);
2112                 g_free(msg);
2113                 if (aval == G_ALERTDEFAULT)
2114                         retval = TRUE;
2115         }
2116
2117         g_free(visible_str);
2118
2119         return retval;
2120 }
2121
2122 static void textview_uri_list_remove_all(GSList *uri_list)
2123 {
2124         GSList *cur;
2125
2126         for (cur = uri_list; cur != NULL; cur = cur->next) {
2127                 if (cur->data) {
2128                         g_free(((RemoteURI *)cur->data)->uri);
2129                         g_free(cur->data);
2130                 }
2131         }
2132
2133         g_slist_free(uri_list);
2134 }
2135
2136 static void open_uri_cb (TextView *textview, guint action, void *data)
2137 {
2138         RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2139                                            "menu_button");
2140         if (uri == NULL)
2141                 return;
2142
2143         if (textview_uri_security_check(textview, uri) == TRUE) 
2144                 open_uri(uri->uri,
2145                          prefs_common.uri_cmd);
2146         g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2147                           NULL);
2148 }
2149
2150 static void copy_uri_cb (TextView *textview, guint action, void *data)
2151 {
2152         RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2153                                            "menu_button");
2154         if (uri == NULL)
2155                 return;
2156
2157         gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), uri->uri, -1);
2158         g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2159                           NULL);
2160 }
2161
2162 static void add_uri_to_addrbook_cb (TextView *textview, guint action, void *data)
2163 {
2164         gchar *fromname, *fromaddress;
2165         RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2166                                            "menu_button");
2167         if (uri == NULL)
2168                 return;
2169
2170         /* extract url */
2171         fromaddress = g_strdup(uri->uri + 7);
2172         /* Hiroyuki: please put this function in utils.c! */
2173         fromname = procheader_get_fromname(fromaddress);
2174         extract_address(fromaddress);
2175         g_message("adding from textview %s <%s>", fromname, fromaddress);
2176         /* Add to address book - Match */
2177         addressbook_add_contact( fromname, fromaddress, NULL );
2178
2179         g_free(fromaddress);
2180         g_free(fromname);
2181 }
2182
2183 static void mail_to_uri_cb (TextView *textview, guint action, void *data)
2184 {
2185         PrefsAccount *account = NULL;
2186         RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2187                                            "menu_button");
2188         if (uri == NULL)
2189                 return;
2190
2191         if (textview->messageview && textview->messageview->msginfo &&
2192             textview->messageview->msginfo->folder) {
2193                 FolderItem   *folder_item;
2194
2195                 folder_item = textview->messageview->msginfo->folder;
2196                 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2197                         account = account_find_from_id(folder_item->prefs->default_account);
2198         }
2199         compose_new(account, uri->uri + 7, NULL);
2200 }
2201
2202