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