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