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