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