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