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