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