2005-07-17 [paul] 1.9.12cvs76
[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         gchar *mybuf = g_strdup(linebuf);
1162         
1163         if (!g_utf8_validate(linebuf, -1, NULL)) {
1164                 mybuf = g_malloc(strlen(linebuf)*2 +1);
1165                 conv_localetodisp(mybuf, strlen(linebuf)*2 +1, linebuf);
1166         }
1167
1168         /* parse table - in order of priority */
1169         struct table {
1170                 const gchar *needle; /* token */
1171
1172                 /* token search function */
1173                 gchar    *(*search)     (const gchar *haystack,
1174                                          const gchar *needle);
1175                 /* part parsing function */
1176                 gboolean  (*parse)      (const gchar *start,
1177                                          const gchar *scanpos,
1178                                          const gchar **bp_,
1179                                          const gchar **ep_);
1180                 /* part to URI function */
1181                 gchar    *(*build_uri)  (const gchar *bp,
1182                                          const gchar *ep);
1183         };
1184
1185         static struct table parser[] = {
1186                 {"http://",  strcasestr, get_uri_part,   make_uri_string},
1187                 {"https://", strcasestr, get_uri_part,   make_uri_string},
1188                 {"ftp://",   strcasestr, get_uri_part,   make_uri_string},
1189                 {"www.",     strcasestr, get_uri_part,   make_http_string},
1190                 {"mailto:",  strcasestr, get_uri_part,   make_uri_string},
1191                 {"@",        strcasestr, get_email_part, make_email_string}
1192         };
1193         const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1194
1195         gint  n;
1196         const gchar *walk, *bp, *ep;
1197
1198         struct txtpos {
1199                 const gchar     *bp, *ep;       /* text position */
1200                 gint             pti;           /* index in parse table */
1201                 struct txtpos   *next;          /* next */
1202         } head = {NULL, NULL, 0,  NULL}, *last = &head;
1203
1204         gtk_text_buffer_get_end_iter(buffer, &iter);
1205
1206         /* parse for clickable parts, and build a list of begin and end positions  */
1207         for (walk = mybuf, n = 0;;) {
1208                 gint last_index = PARSE_ELEMS;
1209                 gchar *scanpos = NULL;
1210
1211                 /* FIXME: this looks phony. scanning for anything in the parse table */
1212                 for (n = 0; n < PARSE_ELEMS; n++) {
1213                         gchar *tmp;
1214
1215                         tmp = parser[n].search(walk, parser[n].needle);
1216                         if (tmp) {
1217                                 if (scanpos == NULL || tmp < scanpos) {
1218                                         scanpos = tmp;
1219                                         last_index = n;
1220                                 }
1221                         }                                       
1222                 }
1223
1224                 if (scanpos) {
1225                         /* check if URI can be parsed */
1226                         if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1227                             && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1228                                         ADD_TXT_POS(bp, ep, last_index);
1229                                         walk = ep;
1230                         } else
1231                                 walk = scanpos +
1232                                         strlen(parser[last_index].needle);
1233                 } else
1234                         break;
1235         }
1236
1237         /* colorize this line */
1238         if (head.next) {
1239                 const gchar *normal_text = mybuf;
1240
1241                 /* insert URIs */
1242                 for (last = head.next; last != NULL;
1243                      normal_text = last->ep, last = last->next) {
1244                         RemoteURI *uri;
1245                         uri = g_new(RemoteURI, 1);
1246                         if (last->bp - normal_text > 0)
1247                                 gtk_text_buffer_insert_with_tags_by_name
1248                                         (buffer, &iter,
1249                                          normal_text,
1250                                          last->bp - normal_text,
1251                                          fg_tag, NULL);
1252                         uri->uri = parser[last->pti].build_uri(last->bp,
1253                                                                last->ep);
1254                         uri->start = gtk_text_iter_get_offset(&iter);
1255                         gtk_text_buffer_insert_with_tags_by_name
1256                                 (buffer, &iter, last->bp, last->ep - last->bp,
1257                                  uri_tag, fg_tag, NULL);
1258                         uri->end = gtk_text_iter_get_offset(&iter);
1259                         uri->filename = NULL;
1260                         textview->uri_list =
1261                                 g_slist_append(textview->uri_list, uri);
1262                 }
1263
1264                 if (*normal_text)
1265                         gtk_text_buffer_insert_with_tags_by_name
1266                                 (buffer, &iter, normal_text, -1, fg_tag, NULL);
1267         } else {
1268                 gtk_text_buffer_insert_with_tags_by_name
1269                         (buffer, &iter, mybuf, -1, fg_tag, NULL);
1270         }
1271         g_free(mybuf);
1272 }
1273
1274 #undef ADD_TXT_POS
1275
1276 static void textview_write_line(TextView *textview, const gchar *str,
1277                                 CodeConverter *conv)
1278 {
1279         GtkTextView *text;
1280         GtkTextBuffer *buffer;
1281         GtkTextIter iter;
1282         gchar buf[BUFFSIZE];
1283         gchar *fg_color;
1284         gint quotelevel = -1;
1285         gchar quote_tag_str[10];
1286
1287         text = GTK_TEXT_VIEW(textview->text);
1288         buffer = gtk_text_view_get_buffer(text);
1289         gtk_text_buffer_get_end_iter(buffer, &iter);
1290
1291         if (!conv)
1292                 strncpy2(buf, str, sizeof(buf));
1293         else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1294                 conv_localetodisp(buf, sizeof(buf), str);
1295                 
1296         strcrchomp(buf);
1297         //if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1298         fg_color = NULL;
1299
1300         /* change color of quotation
1301            >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1302            Up to 3 levels of quotations are detected, and each
1303            level is colored using a different color. */
1304         if (prefs_common.enable_color 
1305             && line_has_quote_char(buf, prefs_common.quote_chars)) {
1306                 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1307
1308                 /* set up the correct foreground color */
1309                 if (quotelevel > 2) {
1310                         /* recycle colors */
1311                         if (prefs_common.recycle_quote_colors)
1312                                 quotelevel %= 3;
1313                         else
1314                                 quotelevel = 2;
1315                 }
1316         }
1317
1318         if (quotelevel == -1)
1319                 fg_color = NULL;
1320         else {
1321                 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1322                            "quote%d", quotelevel);
1323                 fg_color = quote_tag_str;
1324         }
1325
1326         if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1327                 fg_color = "signature";
1328                 textview->is_in_signature = TRUE;
1329         }
1330
1331         if (prefs_common.enable_color)
1332                 textview_make_clickable_parts(textview, fg_color, "link", buf);
1333         else
1334                 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1335 }
1336
1337 void textview_write_link(TextView *textview, const gchar *str,
1338                          const gchar *uri, CodeConverter *conv)
1339 {
1340         GdkColor *link_color = NULL;
1341         GtkTextView *text;
1342         GtkTextBuffer *buffer;
1343         GtkTextIter iter;
1344         gchar buf[BUFFSIZE];
1345         gchar *bufp;
1346         RemoteURI *r_uri;
1347
1348         if (!str || *str == '\0')
1349                 return;
1350         if (!uri)
1351                 return;
1352
1353         text = GTK_TEXT_VIEW(textview->text);
1354         buffer = gtk_text_view_get_buffer(text);
1355         gtk_text_buffer_get_end_iter(buffer, &iter);
1356
1357         if (!conv)
1358                 strncpy2(buf, str, sizeof(buf));
1359         else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1360                 conv_utf8todisp(buf, sizeof(buf), str);
1361
1362         if (g_utf8_validate(buf, -1, NULL) == FALSE)
1363                 return;
1364
1365         strcrchomp(buf);
1366
1367         gtk_text_buffer_get_end_iter(buffer, &iter);
1368         for (bufp = buf; *bufp != '\0'; bufp = g_utf8_next_char(bufp)) {
1369                 gunichar ch;
1370
1371                 ch = g_utf8_get_char(bufp);
1372                 if (!g_unichar_isspace(ch))
1373                         break;
1374         }
1375         if (bufp > buf)
1376                 gtk_text_buffer_insert(buffer, &iter, buf, bufp - buf);
1377
1378         if (prefs_common.enable_color) {
1379                 link_color = &uri_color;
1380         }
1381         r_uri = g_new(RemoteURI, 1);
1382         r_uri->uri = g_strdup(uri);
1383         r_uri->start = gtk_text_iter_get_offset(&iter);
1384         gtk_text_buffer_insert_with_tags_by_name
1385                 (buffer, &iter, bufp, -1, "link", NULL);
1386         r_uri->end = gtk_text_iter_get_offset(&iter);
1387         r_uri->filename = NULL;
1388         textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1389 }
1390
1391 void textview_clear(TextView *textview)
1392 {
1393         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1394         GtkTextBuffer *buffer;
1395
1396         buffer = gtk_text_view_get_buffer(text);
1397         gtk_text_buffer_set_text(buffer, "", -1);
1398
1399         TEXTVIEW_STATUSBAR_POP(textview);
1400         textview_uri_list_remove_all(textview->uri_list);
1401         textview->uri_list = NULL;
1402
1403         textview->body_pos = 0;
1404 }
1405
1406 void textview_destroy(TextView *textview)
1407 {
1408         textview_uri_list_remove_all(textview->uri_list);
1409         textview->uri_list = NULL;
1410
1411         g_free(textview);
1412 }
1413
1414 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1415 {
1416         textview->show_all_headers = all_headers;
1417 }
1418
1419 void textview_set_font(TextView *textview, const gchar *codeset)
1420 {
1421         if (prefs_common.textfont) {
1422                 PangoFontDescription *font_desc = NULL;
1423
1424                 font_desc = pango_font_description_from_string
1425                                                 (prefs_common.textfont);
1426                 if (font_desc) {
1427                         gtk_widget_modify_font(textview->text, font_desc);
1428                         pango_font_description_free(font_desc);
1429                 }
1430         }
1431         gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1432                                              prefs_common.line_space / 2);
1433         gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1434                                              prefs_common.line_space / 2);
1435 }
1436
1437 void textview_set_text(TextView *textview, const gchar *text)
1438 {
1439         GtkTextView *view;
1440         GtkTextBuffer *buffer;
1441
1442         g_return_if_fail(textview != NULL);
1443         g_return_if_fail(text != NULL);
1444
1445         textview_clear(textview);
1446
1447         view = GTK_TEXT_VIEW(textview->text);
1448         buffer = gtk_text_view_get_buffer(view);
1449         gtk_text_buffer_set_text(buffer, text, strlen(text));
1450 }
1451
1452 enum
1453 {
1454         H_DATE          = 0,
1455         H_FROM          = 1,
1456         H_TO            = 2,
1457         H_NEWSGROUPS    = 3,
1458         H_SUBJECT       = 4,
1459         H_CC            = 5,
1460         H_REPLY_TO      = 6,
1461         H_FOLLOWUP_TO   = 7,
1462         H_X_MAILER      = 8,
1463         H_X_NEWSREADER  = 9,
1464         H_USER_AGENT    = 10,
1465         H_ORGANIZATION  = 11,
1466 };
1467
1468 void textview_set_position(TextView *textview, gint pos)
1469 {
1470         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1471         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1472         GtkTextIter iter;
1473
1474         gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1475         gtk_text_buffer_place_cursor(buffer, &iter);
1476         gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1477 }
1478
1479 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1480 {
1481         gchar buf[BUFFSIZE];
1482         GPtrArray *headers, *sorted_headers;
1483         GSList *disphdr_list;
1484         Header *header;
1485         gint i;
1486
1487         g_return_val_if_fail(fp != NULL, NULL);
1488
1489         if (textview->show_all_headers)
1490                 return procheader_get_header_array_asis(fp);
1491
1492         if (!prefs_common.display_header) {
1493                 while (fgets(buf, sizeof(buf), fp) != NULL)
1494                         if (buf[0] == '\r' || buf[0] == '\n') break;
1495                 return NULL;
1496         }
1497
1498         headers = procheader_get_header_array_asis(fp);
1499
1500         sorted_headers = g_ptr_array_new();
1501
1502         for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1503              disphdr_list = disphdr_list->next) {
1504                 DisplayHeaderProp *dp =
1505                         (DisplayHeaderProp *)disphdr_list->data;
1506
1507                 for (i = 0; i < headers->len; i++) {
1508                         header = g_ptr_array_index(headers, i);
1509
1510                         if (procheader_headername_equal(header->name,
1511                                                         dp->name)) {
1512                                 if (dp->hidden)
1513                                         procheader_header_free(header);
1514                                 else
1515                                         g_ptr_array_add(sorted_headers, header);
1516
1517                                 g_ptr_array_remove_index(headers, i);
1518                                 i--;
1519                         }
1520                 }
1521         }
1522
1523         if (prefs_common.show_other_header) {
1524                 for (i = 0; i < headers->len; i++) {
1525                         header = g_ptr_array_index(headers, i);
1526                         g_ptr_array_add(sorted_headers, header);
1527                 }
1528                 g_ptr_array_free(headers, TRUE);
1529         } else
1530                 procheader_header_array_destroy(headers);
1531
1532
1533         return sorted_headers;
1534 }
1535
1536 static void textview_show_header(TextView *textview, GPtrArray *headers)
1537 {
1538         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1539         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1540         GtkTextIter iter;
1541         Header *header;
1542         gint i;
1543
1544         g_return_if_fail(headers != NULL);
1545
1546         for (i = 0; i < headers->len; i++) {
1547                 header = g_ptr_array_index(headers, i);
1548                 g_return_if_fail(header->name != NULL);
1549
1550                 gtk_text_buffer_get_end_iter (buffer, &iter);
1551                 gtk_text_buffer_insert_with_tags_by_name
1552                         (buffer, &iter, header->name, -1,
1553                          "header_title", "header", NULL);
1554                 if (header->name[strlen(header->name) - 1] != ' ')
1555                 gtk_text_buffer_insert_with_tags_by_name
1556                                 (buffer, &iter, " ", 1,
1557                                  "header_title", "header", NULL);
1558
1559                 if (procheader_headername_equal(header->name, "Subject") ||
1560                     procheader_headername_equal(header->name, "From")    ||
1561                     procheader_headername_equal(header->name, "To")      ||
1562                     procheader_headername_equal(header->name, "Cc"))
1563                         unfold_line(header->body);
1564
1565                 if (prefs_common.enable_color &&
1566                     (procheader_headername_equal(header->name, "X-Mailer") ||
1567                      procheader_headername_equal(header->name,
1568                                                  "X-Newsreader")) &&
1569                     strstr(header->body, "Sylpheed") != NULL) {
1570                         gtk_text_buffer_get_end_iter (buffer, &iter);
1571                         gtk_text_buffer_insert_with_tags_by_name
1572                                 (buffer, &iter, header->body, -1,
1573                                  "header", "emphasis", NULL);
1574                 } else if (prefs_common.enable_color) {
1575                         textview_make_clickable_parts(textview, "header", "link",
1576                                                       header->body);
1577                 } else {
1578                         textview_make_clickable_parts(textview, "header", NULL,
1579                                                       header->body);
1580                 }
1581                 gtk_text_buffer_get_end_iter (buffer, &iter);
1582                 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1583                                                          "header", NULL);
1584         }
1585 }
1586
1587 gboolean textview_search_string(TextView *textview, const gchar *str,
1588                                 gboolean case_sens)
1589 {
1590         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1591         GtkTextBuffer *buffer;
1592         GtkTextIter iter, match_pos;
1593         GtkTextMark *mark;
1594         gint len;
1595
1596         g_return_val_if_fail(str != NULL, FALSE);
1597
1598         buffer = gtk_text_view_get_buffer(text);
1599
1600         len = g_utf8_strlen(str, -1);
1601         g_return_val_if_fail(len >= 0, FALSE);
1602
1603         mark = gtk_text_buffer_get_insert(buffer);
1604         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1605
1606         if (gtkut_text_buffer_find(buffer, &iter, str, case_sens,
1607                                    &match_pos)) {
1608                 GtkTextIter end = match_pos;
1609
1610                 gtk_text_iter_forward_chars(&end, len);
1611                 /* place "insert" at the last character */
1612                 gtk_text_buffer_select_range(buffer, &end, &match_pos);
1613                 gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1614                 return TRUE;
1615         }
1616
1617         return FALSE;
1618 }
1619
1620 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1621                                          gboolean case_sens)
1622 {
1623         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1624         GtkTextBuffer *buffer;
1625         GtkTextIter iter, match_pos;
1626         GtkTextMark *mark;
1627         gint len;
1628
1629         g_return_val_if_fail(str != NULL, FALSE);
1630
1631         buffer = gtk_text_view_get_buffer(text);
1632
1633         len = g_utf8_strlen(str, -1);
1634         g_return_val_if_fail(len >= 0, FALSE);
1635
1636         mark = gtk_text_buffer_get_insert(buffer);
1637         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1638
1639         if (gtkut_text_buffer_find_backward(buffer, &iter, str, case_sens,
1640                                             &match_pos)) {
1641                 GtkTextIter end = match_pos;
1642
1643                 gtk_text_iter_forward_chars(&end, len);
1644                 gtk_text_buffer_select_range(buffer, &match_pos, &end);
1645                 gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1646                 return TRUE;
1647         }
1648
1649         return FALSE;
1650 }
1651
1652 void textview_scroll_one_line(TextView *textview, gboolean up)
1653 {
1654         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1655         GtkAdjustment *vadj = text->vadjustment;
1656         gfloat upper;
1657
1658         if (prefs_common.enable_smooth_scroll) {
1659                 textview_smooth_scroll_one_line(textview, up);
1660                 return;
1661         }
1662
1663         if (!up) {
1664                 upper = vadj->upper - vadj->page_size;
1665                 if (vadj->value < upper) {
1666                         vadj->value += vadj->step_increment;
1667                         vadj->value = MIN(vadj->value, upper);
1668                         g_signal_emit_by_name(G_OBJECT(vadj),
1669                                               "value_changed", 0);
1670                 }
1671         } else {
1672                 if (vadj->value > 0.0) {
1673                         vadj->value -= vadj->step_increment;
1674                         vadj->value = MAX(vadj->value, 0.0);
1675                         g_signal_emit_by_name(G_OBJECT(vadj),
1676                                               "value_changed", 0);
1677                 }
1678         }
1679 }
1680
1681 gboolean textview_scroll_page(TextView *textview, gboolean up)
1682 {
1683         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1684         GtkAdjustment *vadj = text->vadjustment;
1685         gfloat upper;
1686         gfloat page_incr;
1687
1688         if (prefs_common.enable_smooth_scroll)
1689                 return textview_smooth_scroll_page(textview, up);
1690
1691         if (prefs_common.scroll_halfpage)
1692                 page_incr = vadj->page_increment / 2;
1693         else
1694                 page_incr = vadj->page_increment;
1695
1696         if (!up) {
1697                 upper = vadj->upper - vadj->page_size;
1698                 if (vadj->value < upper) {
1699                         vadj->value += page_incr;
1700                         vadj->value = MIN(vadj->value, upper);
1701                         g_signal_emit_by_name(G_OBJECT(vadj),
1702                                               "value_changed", 0);
1703                 } else
1704                         return FALSE;
1705         } else {
1706                 if (vadj->value > 0.0) {
1707                         vadj->value -= page_incr;
1708                         vadj->value = MAX(vadj->value, 0.0);
1709                         g_signal_emit_by_name(G_OBJECT(vadj),
1710                                               "value_changed", 0);
1711                 } else
1712                         return FALSE;
1713         }
1714
1715         return TRUE;
1716 }
1717
1718 static void textview_smooth_scroll_do(TextView *textview,
1719                                       gfloat old_value, gfloat last_value,
1720                                       gint step)
1721 {
1722         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1723         GtkAdjustment *vadj = text->vadjustment;
1724         gint change_value;
1725         gboolean up;
1726         gint i;
1727
1728         if (old_value < last_value) {
1729                 change_value = last_value - old_value;
1730                 up = FALSE;
1731         } else {
1732                 change_value = old_value - last_value;
1733                 up = TRUE;
1734         }
1735
1736         for (i = step; i <= change_value; i += step) {
1737                 vadj->value = old_value + (up ? -i : i);
1738                 g_signal_emit_by_name(G_OBJECT(vadj),
1739                                       "value_changed", 0);
1740         }
1741
1742         vadj->value = last_value;
1743         g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1744
1745         gtk_widget_queue_draw(GTK_WIDGET(text));
1746 }
1747
1748 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1749 {
1750         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1751         GtkAdjustment *vadj = text->vadjustment;
1752         gfloat upper;
1753         gfloat old_value;
1754         gfloat last_value;
1755
1756         if (!up) {
1757                 upper = vadj->upper - vadj->page_size;
1758                 if (vadj->value < upper) {
1759                         old_value = vadj->value;
1760                         last_value = vadj->value + vadj->step_increment;
1761                         last_value = MIN(last_value, upper);
1762
1763                         textview_smooth_scroll_do(textview, old_value,
1764                                                   last_value,
1765                                                   prefs_common.scroll_step);
1766                 }
1767         } else {
1768                 if (vadj->value > 0.0) {
1769                         old_value = vadj->value;
1770                         last_value = vadj->value - vadj->step_increment;
1771                         last_value = MAX(last_value, 0.0);
1772
1773                         textview_smooth_scroll_do(textview, old_value,
1774                                                   last_value,
1775                                                   prefs_common.scroll_step);
1776                 }
1777         }
1778 }
1779
1780 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1781 {
1782         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1783         GtkAdjustment *vadj = text->vadjustment;
1784         gfloat upper;
1785         gfloat page_incr;
1786         gfloat old_value;
1787         gfloat last_value;
1788
1789         if (prefs_common.scroll_halfpage)
1790                 page_incr = vadj->page_increment / 2;
1791         else
1792                 page_incr = vadj->page_increment;
1793
1794         if (!up) {
1795                 upper = vadj->upper - vadj->page_size;
1796                 if (vadj->value < upper) {
1797                         old_value = vadj->value;
1798                         last_value = vadj->value + page_incr;
1799                         last_value = MIN(last_value, upper);
1800
1801                         textview_smooth_scroll_do(textview, old_value,
1802                                                   last_value,
1803                                                   prefs_common.scroll_step);
1804                 } else
1805                         return FALSE;
1806         } else {
1807                 if (vadj->value > 0.0) {
1808                         old_value = vadj->value;
1809                         last_value = vadj->value - page_incr;
1810                         last_value = MAX(last_value, 0.0);
1811
1812                         textview_smooth_scroll_do(textview, old_value,
1813                                                   last_value,
1814                                                   prefs_common.scroll_step);
1815                 } else
1816                         return FALSE;
1817         }
1818
1819         return TRUE;
1820 }
1821
1822 #define KEY_PRESS_EVENT_STOP() \
1823         g_signal_stop_emission_by_name(G_OBJECT(widget), \
1824                                        "key_press_event");
1825
1826 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1827                                  TextView *textview)
1828 {
1829         SummaryView *summaryview = NULL;
1830         MessageView *messageview = textview->messageview;
1831
1832         if (!event) return FALSE;
1833         if (messageview->mainwin)
1834                 summaryview = messageview->mainwin->summaryview;
1835
1836         switch (event->keyval) {
1837         case GDK_Tab:
1838         case GDK_Home:
1839         case GDK_Left:
1840         case GDK_Up:
1841         case GDK_Right:
1842         case GDK_Down:
1843         case GDK_Page_Up:
1844         case GDK_Page_Down:
1845         case GDK_End:
1846         case GDK_Control_L:
1847         case GDK_Control_R:
1848                 return FALSE;
1849         case GDK_space:
1850                 if (summaryview)
1851                         summary_pass_key_press_event(summaryview, event);
1852                 else
1853                         textview_scroll_page
1854                                 (textview,
1855                                  (event->state &
1856                                   (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1857                 break;
1858         case GDK_BackSpace:
1859                 textview_scroll_page(textview, TRUE);
1860                 break;
1861         case GDK_Return:
1862                 textview_scroll_one_line
1863                         (textview, (event->state &
1864                                     (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1865                 break;
1866         case GDK_Delete:
1867                 if (summaryview)
1868                         summary_pass_key_press_event(summaryview, event);
1869                 break;
1870         case GDK_y:
1871         case GDK_t:
1872         case GDK_l:
1873                 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1874                         KEY_PRESS_EVENT_STOP();
1875                         mimeview_pass_key_press_event(messageview->mimeview,
1876                                                       event);
1877                         break;
1878                 }
1879                 /* possible fall through */
1880         default:
1881                 if (summaryview &&
1882                     event->window != messageview->mainwin->window->window) {
1883                         GdkEventKey tmpev = *event;
1884
1885                         tmpev.window = messageview->mainwin->window->window;
1886                         KEY_PRESS_EVENT_STOP();
1887                         gtk_widget_event(messageview->mainwin->window,
1888                                          (GdkEvent *)&tmpev);
1889                 }
1890                 break;
1891         }
1892
1893         return TRUE;
1894 }
1895
1896 static gboolean textview_motion_notify(GtkWidget *widget,
1897                                        GdkEventMotion *event,
1898                                        TextView *textview)
1899 {
1900         textview_uri_update(textview, event->x, event->y);
1901         gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1902
1903         return FALSE;
1904 }
1905
1906 static gboolean textview_leave_notify(GtkWidget *widget,
1907                                       GdkEventCrossing *event,
1908                                       TextView *textview)
1909 {
1910         textview_uri_update(textview, -1, -1);
1911
1912         return FALSE;
1913 }
1914
1915 static gboolean textview_visibility_notify(GtkWidget *widget,
1916                                            GdkEventVisibility *event,
1917                                            TextView *textview)
1918 {
1919         gint wx, wy;
1920         GdkWindow *window;
1921
1922         window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
1923                                           GTK_TEXT_WINDOW_TEXT);
1924
1925         /* check if occurred for the text window part */
1926         if (window != event->window)
1927                 return FALSE;
1928         
1929         gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1930         textview_uri_update(textview, wx, wy);
1931
1932         return FALSE;
1933 }
1934
1935 static void textview_uri_update(TextView *textview, gint x, gint y)
1936 {
1937         GtkTextBuffer *buffer;
1938         GtkTextIter start_iter, end_iter;
1939         RemoteURI *uri = NULL;
1940         
1941         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1942
1943         if (x != -1 && y != -1) {
1944                 gint bx, by;
1945                 GtkTextIter iter;
1946                 GSList *tags;
1947                 GSList *cur;
1948             
1949                 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text), 
1950                                                       GTK_TEXT_WINDOW_WIDGET,
1951                                                       x, y, &bx, &by);
1952                 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1953                                                    &iter, bx, by);
1954
1955                 tags = gtk_text_iter_get_tags(&iter);
1956                 for (cur = tags; cur != NULL; cur = cur->next) {
1957                         GtkTextTag *tag = cur->data;
1958                         char *name;
1959
1960                         g_object_get(G_OBJECT(tag), "name", &name, NULL);
1961                         if (!strcmp(name, "link")
1962                             && textview_get_uri_range(textview, &iter, tag,
1963                                                       &start_iter, &end_iter))
1964                                 uri = textview_get_uri_from_range(textview,
1965                                                                   &iter, tag,
1966                                                                   &start_iter,
1967                                                                   &end_iter);
1968                         g_free(name);
1969
1970                         if (uri)
1971                                 break;
1972                 }
1973                 g_slist_free(tags);
1974         }
1975         
1976         if (uri != textview->uri_hover) {
1977                 GdkWindow *window;
1978
1979                 if (textview->uri_hover)
1980                         gtk_text_buffer_remove_tag_by_name(buffer,
1981                                                            "link-hover",
1982                                                            &textview->uri_hover_start_iter,
1983                                                            &textview->uri_hover_end_iter);
1984                     
1985                 textview->uri_hover = uri;
1986                 if (uri) {
1987                         textview->uri_hover_start_iter = start_iter;
1988                         textview->uri_hover_end_iter = end_iter;
1989                 }
1990                 
1991                 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1992                                                   GTK_TEXT_WINDOW_TEXT);
1993                 gdk_window_set_cursor(window, uri ? hand_cursor : text_cursor);
1994
1995                 TEXTVIEW_STATUSBAR_POP(textview);
1996
1997                 if (uri) {
1998                         char *trimmed_uri;
1999
2000                         gtk_text_buffer_apply_tag_by_name(buffer,
2001                                                           "link-hover",
2002                                                           &start_iter,
2003                                                           &end_iter);
2004
2005                         trimmed_uri = trim_string(uri->uri, 60);
2006                         TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
2007                         g_free(trimmed_uri);
2008                 }
2009         }
2010 }
2011
2012 static gboolean textview_get_uri_range(TextView *textview,
2013                                        GtkTextIter *iter,
2014                                        GtkTextTag *tag,
2015                                        GtkTextIter *start_iter,
2016                                        GtkTextIter *end_iter)
2017 {
2018         GtkTextIter _start_iter, _end_iter;
2019
2020         _end_iter = *iter;
2021         if (!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
2022                 debug_print("Can't find end");
2023                 return FALSE;
2024         }
2025
2026         _start_iter = _end_iter;
2027         if (!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
2028                 debug_print("Can't find start.");
2029                 return FALSE;
2030         }
2031
2032         *start_iter = _start_iter;
2033         *end_iter = _end_iter;
2034
2035         return TRUE;
2036 }
2037
2038 static RemoteURI *textview_get_uri_from_range(TextView *textview,
2039                                               GtkTextIter *iter,
2040                                               GtkTextTag *tag,
2041                                               GtkTextIter *start_iter,
2042                                               GtkTextIter *end_iter)
2043 {
2044         gint start_pos, end_pos, cur_pos;
2045         RemoteURI *uri = NULL;
2046         GSList *cur;
2047
2048         start_pos = gtk_text_iter_get_offset(start_iter);
2049         end_pos = gtk_text_iter_get_offset(end_iter);
2050         cur_pos = gtk_text_iter_get_offset(iter);
2051
2052         for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
2053                 RemoteURI *uri_ = (RemoteURI *)cur->data;
2054                 if (start_pos == uri_->start &&
2055                     end_pos ==  uri_->end) {
2056                         uri = uri_;
2057                         break;
2058                 } else if (start_pos == uri_->start ||
2059                            end_pos == uri_->end) {
2060                         /* in case of contiguous links, textview_get_uri_range
2061                          * returns a broader range (start of 1st link to end
2062                          * of last link).
2063                          * In that case, correct link is the one covering
2064                          * current iter.
2065                          */
2066                         if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
2067                                 uri = uri_;
2068                                 break;
2069                         }
2070                 } 
2071         }
2072
2073         return uri;
2074 }
2075
2076 static RemoteURI *textview_get_uri(TextView *textview,
2077                                    GtkTextIter *iter,
2078                                    GtkTextTag *tag)
2079 {
2080         GtkTextIter start_iter, end_iter;
2081         RemoteURI *uri = NULL;
2082
2083         if (textview_get_uri_range(textview, iter, tag, &start_iter,
2084                                    &end_iter))
2085                 uri = textview_get_uri_from_range(textview, iter, tag,
2086                                                   &start_iter, &end_iter);
2087
2088         return uri;
2089 }
2090
2091 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
2092                                             GdkEvent *event, GtkTextIter *iter,
2093                                             TextView *textview)
2094 {
2095         GdkEventButton *bevent;
2096         RemoteURI *uri = NULL;
2097
2098         if (!event)
2099                 return FALSE;
2100
2101         if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2102                 && event->type != GDK_MOTION_NOTIFY)
2103                 return FALSE;
2104
2105         uri = textview_get_uri(textview, iter, tag);
2106         if (!uri)
2107                 return FALSE;
2108
2109         bevent = (GdkEventButton *) event;
2110         
2111         /* doubleclick: open compose / add address / browser */
2112         if ((event->type == GDK_BUTTON_PRESS && bevent->button == 1) ||
2113                 bevent->button == 2 || bevent->button == 3) {
2114                 if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2115                         if (bevent->button == 3) {
2116                                 g_object_set_data(
2117                                         G_OBJECT(textview->mail_popup_menu),
2118                                         "menu_button", uri);
2119                                 gtk_menu_popup(GTK_MENU(textview->mail_popup_menu), 
2120                                                NULL, NULL, NULL, NULL, 
2121                                                bevent->button, bevent->time);
2122                         } else {
2123                                 PrefsAccount *account = NULL;
2124
2125                                 if (textview->messageview && textview->messageview->msginfo &&
2126                                     textview->messageview->msginfo->folder) {
2127                                         FolderItem   *folder_item;
2128
2129                                         folder_item = textview->messageview->msginfo->folder;
2130                                         if (folder_item->prefs && folder_item->prefs->enable_default_account)
2131                                                 account = account_find_from_id(folder_item->prefs->default_account);
2132                                 }
2133                                 compose_new(account, uri->uri + 7, NULL);
2134                         }
2135                         return TRUE;
2136                 } else if (g_ascii_strncasecmp(uri->uri, "file:", 5)) {
2137                         if (bevent->button == 1 &&
2138                             textview_uri_security_check(textview, uri) == TRUE) 
2139                                         open_uri(uri->uri,
2140                                                  prefs_common.uri_cmd);
2141                         else if (bevent->button == 3) {
2142                                 g_object_set_data(
2143                                         G_OBJECT(textview->link_popup_menu),
2144                                         "menu_button", uri);
2145                                 gtk_menu_popup(GTK_MENU(textview->link_popup_menu), 
2146                                                NULL, NULL, NULL, NULL, 
2147                                                bevent->button, bevent->time);
2148                         }
2149                         return TRUE;
2150                 } else {
2151                         if (bevent->button == 3) {
2152                                 g_object_set_data(
2153                                         G_OBJECT(textview->file_popup_menu),
2154                                         "menu_button", uri);
2155                                 gtk_menu_popup(GTK_MENU(textview->file_popup_menu), 
2156                                                NULL, NULL, NULL, NULL, 
2157                                                bevent->button, bevent->time);
2158                                 return TRUE;
2159                         }
2160                 }
2161         }
2162
2163         return FALSE;
2164 }
2165
2166 /*!
2167  *\brief    Check to see if a web URL has been disguised as a different
2168  *          URL (possible with HTML email).
2169  *
2170  *\param    uri The uri to check
2171  *
2172  *\param    textview The TextView the URL is contained in
2173  *
2174  *\return   gboolean TRUE if the URL is ok, or if the user chose to open
2175  *          it anyway, otherwise FALSE          
2176  */
2177 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2178 {
2179         gchar *visible_str;
2180         gboolean retval = TRUE;
2181         GtkTextBuffer *buffer;
2182         GtkTextIter start, end;
2183
2184         if (is_uri_string(uri->uri) == FALSE)
2185                 return TRUE;
2186
2187         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2188
2189         gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2190         gtk_text_buffer_get_iter_at_offset(buffer, &end,   uri->end);
2191
2192         visible_str = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2193
2194         if (visible_str == NULL)
2195                 return TRUE;
2196
2197         if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2198                 gchar *uri_path;
2199                 gchar *visible_uri_path;
2200
2201                 uri_path = get_uri_path(uri->uri);
2202                 visible_uri_path = get_uri_path(visible_str);
2203                 if (strcmp(uri_path, visible_uri_path) != 0)
2204                         retval = FALSE;
2205         }
2206
2207         if (retval == FALSE) {
2208                 gchar *msg;
2209                 AlertValue aval;
2210
2211                 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2212                                         "the apparent URL (%s).\n"
2213                                         "\n"
2214                                         "Open it anyway?"),
2215                                       uri->uri, visible_str);
2216                 aval = alertpanel_full(_("Fake URL warning"), msg,
2217                                        GTK_STOCK_YES, GTK_STOCK_NO, NULL, FALSE,
2218                                        NULL, ALERT_WARNING, G_ALERTALTERNATE);
2219                 g_free(msg);
2220                 if (aval == G_ALERTDEFAULT)
2221                         retval = TRUE;
2222         }
2223
2224         g_free(visible_str);
2225
2226         return retval;
2227 }
2228
2229 static void textview_uri_list_remove_all(GSList *uri_list)
2230 {
2231         GSList *cur;
2232
2233         for (cur = uri_list; cur != NULL; cur = cur->next) {
2234                 if (cur->data) {
2235                         g_free(((RemoteURI *)cur->data)->uri);
2236                         g_free(((RemoteURI *)cur->data)->filename);
2237                         g_free(cur->data);
2238                 }
2239         }
2240
2241         g_slist_free(uri_list);
2242 }
2243
2244 static void open_uri_cb (TextView *textview, guint action, void *data)
2245 {
2246         RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2247                                            "menu_button");
2248         if (uri == NULL)
2249                 return;
2250
2251         if (textview_uri_security_check(textview, uri) == TRUE) 
2252                 open_uri(uri->uri,
2253                          prefs_common.uri_cmd);
2254         g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2255                           NULL);
2256 }
2257
2258 static void save_file_cb (TextView *textview, guint action, void *data)
2259 {
2260         RemoteURI *uri = g_object_get_data(G_OBJECT(textview->file_popup_menu),
2261                                            "menu_button");
2262         gchar *filename = NULL;
2263         gchar *filepath = NULL;
2264         gchar *filedir = NULL;
2265         gchar *tmp_filename = NULL;
2266         if (uri == NULL)
2267                 return;
2268
2269         if (uri->filename == NULL)
2270                 return;
2271         
2272         filename = g_strdup(uri->filename);
2273         
2274         if (!g_utf8_validate(filename, -1, NULL)) {
2275                 gchar *tmp = conv_filename_to_utf8(filename);
2276                 g_free(filename);
2277                 filename = tmp;
2278         }
2279
2280         subst_for_filename(filename);
2281         
2282         if (prefs_common.attach_save_dir)
2283                 filepath = g_strconcat(prefs_common.attach_save_dir,
2284                                        G_DIR_SEPARATOR_S, filename, NULL);
2285         else
2286                 filepath = g_strdup(filename);
2287
2288         g_free(filename);
2289
2290         filename = filesel_select_file_save(_("Save as"), filepath);
2291         if (!filename) {
2292                 g_free(filepath);
2293                 return;
2294         }
2295
2296         if (is_file_exist(filename)) {
2297                 AlertValue aval;
2298                 gchar *res;
2299                 
2300                 res = g_strdup_printf(_("Overwrite existing file '%s'?"),
2301                                       filename);
2302                 aval = alertpanel(_("Overwrite"), res, GTK_STOCK_OK, 
2303                                   GTK_STOCK_CANCEL, NULL);
2304                 g_free(res);                                      
2305                 if (G_ALERTDEFAULT != aval) 
2306                         return;
2307         }
2308
2309         tmp_filename = g_filename_from_uri(uri->uri, NULL, NULL);
2310         copy_file(tmp_filename, filename, FALSE);
2311         g_free(tmp_filename);
2312         
2313         filedir = g_path_get_dirname(filename);
2314         if (filedir && strcmp(filedir, ".")) {
2315                 if (prefs_common.attach_save_dir)
2316                         g_free(prefs_common.attach_save_dir);
2317                 prefs_common.attach_save_dir = g_strdup(filedir);
2318         }
2319
2320         g_free(filedir);
2321         g_free(filepath);
2322
2323         g_object_set_data(G_OBJECT(textview->file_popup_menu), "menu_button",
2324                           NULL);
2325 }
2326
2327 static void copy_uri_cb (TextView *textview, guint action, void *data)
2328 {
2329         RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2330                                            "menu_button");
2331         if (uri == NULL)
2332                 return;
2333
2334         gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), uri->uri, -1);
2335         gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), uri->uri, -1);
2336         g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2337                           NULL);
2338 }
2339
2340 static void add_uri_to_addrbook_cb (TextView *textview, guint action, void *data)
2341 {
2342         gchar *fromname, *fromaddress;
2343         RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2344                                            "menu_button");
2345         if (uri == NULL)
2346                 return;
2347
2348         /* extract url */
2349         fromaddress = g_strdup(uri->uri + 7);
2350         /* Hiroyuki: please put this function in utils.c! */
2351         fromname = procheader_get_fromname(fromaddress);
2352         extract_address(fromaddress);
2353         g_message("adding from textview %s <%s>", fromname, fromaddress);
2354         /* Add to address book - Match */
2355         addressbook_add_contact( fromname, fromaddress, NULL );
2356
2357         g_free(fromaddress);
2358         g_free(fromname);
2359 }
2360
2361 static void mail_to_uri_cb (TextView *textview, guint action, void *data)
2362 {
2363         PrefsAccount *account = NULL;
2364         RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2365                                            "menu_button");
2366         if (uri == NULL)
2367                 return;
2368
2369         if (textview->messageview && textview->messageview->msginfo &&
2370             textview->messageview->msginfo->folder) {
2371                 FolderItem   *folder_item;
2372
2373                 folder_item = textview->messageview->msginfo->folder;
2374                 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2375                         account = account_find_from_id(folder_item->prefs->default_account);
2376         }
2377         compose_new(account, uri->uri + 7, NULL);
2378 }
2379
2380 static void copy_mail_to_uri_cb (TextView *textview, guint action, void *data)
2381 {
2382         RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2383                                            "menu_button");
2384         if (uri == NULL)
2385                 return;
2386
2387         gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), uri->uri +7, -1);
2388         gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), uri->uri +7, -1);
2389         g_object_set_data(G_OBJECT(textview->mail_popup_menu), "menu_button",
2390                           NULL);
2391 }
2392