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