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