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