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