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