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