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