sync with 0.9.0cvs8
[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 (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) {
477                 perror("fseek");
478                 return;
479         }
480
481         if (mimeinfo->parent && mimeinfo->parent->boundary) {
482                 boundary = mimeinfo->parent->boundary;
483                 boundary_len = strlen(boundary);
484         }
485
486         while (fgets(buf, sizeof(buf), fp) != NULL)
487                 if (buf[0] == '\r' || buf[0] == '\n') break;
488
489         if (mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
490                 headers = textview_scan_header(textview, fp);
491                 if (headers) {
492                         gtk_stext_freeze(text);
493                         gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
494                         textview_show_header(textview, headers);
495                         procheader_header_array_destroy(headers);
496                         gtk_stext_thaw(text);
497                 }
498                 return;
499         }
500
501         gtk_stext_freeze(text);
502
503 #if USE_GPGME
504         if (mimeinfo->sigstatus)
505                 g_snprintf(buf, sizeof(buf), "\n[%s (%s)]\n",
506                            mimeinfo->content_type, mimeinfo->sigstatus);
507         else
508 #endif
509         if (mimeinfo->filename || mimeinfo->name)
510                 g_snprintf(buf, sizeof(buf), "\n[%s  %s (%d bytes)]\n",
511                            mimeinfo->filename ? mimeinfo->filename :
512                            mimeinfo->name,
513                            mimeinfo->content_type, mimeinfo->size);
514         else
515                 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
516                            mimeinfo->content_type, mimeinfo->size);
517
518 #if USE_GPGME
519         if (mimeinfo->sigstatus && !mimeinfo->sigstatus_full) {
520                 gchar *tmp;
521                 /* use standard font */
522                 gpointer oldfont = textview->msgfont;
523                 textview->msgfont = NULL;
524
525                 tmp = g_strconcat("pgp: ", _("Check signature"), NULL);
526                 textview_write_link(textview, buf, tmp, NULL);
527                 
528                 /* put things back */
529                 textview->msgfont = (GdkFont *)oldfont;
530                 oldfont = NULL;
531                 g_free(tmp);
532         } else if (mimeinfo->sigstatus) {
533                 GdkColor *color;
534                 if (!strcmp(mimeinfo->sigstatus, _("Good signature")))
535                         color = &good_sig_color;
536                 else if (!strcmp(mimeinfo->sigstatus, _("BAD signature")))
537                         color = &bad_sig_color;
538                 else
539                         color = &nocheck_sig_color; 
540                 gtk_stext_insert(text, NULL, color, NULL, buf, -1);
541         } else
542 #endif
543         if (mimeinfo->mime_type != MIME_TEXT &&
544             mimeinfo->mime_type != MIME_TEXT_HTML &&
545             mimeinfo->mime_type != MIME_TEXT_ENRICHED) {
546                 gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
547         } else {
548                 if (!mimeinfo->main &&
549                     mimeinfo->parent &&
550                     mimeinfo->parent->children != mimeinfo)
551                         gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
552                 else if (prefs_common.display_header)
553                         gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
554                 if (textview->messageview->forced_charset)
555                         charset = textview->messageview->forced_charset;
556                 else if (prefs_common.force_charset)
557                         charset = prefs_common.force_charset;
558                 else if (mimeinfo->charset)
559                         charset = mimeinfo->charset;
560                 textview_write_body(textview, mimeinfo, fp, charset);
561         }
562
563         gtk_stext_thaw(text);
564 }
565
566 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
567 {
568         gint level;
569
570         g_return_if_fail(mimeinfo != NULL);
571         g_return_if_fail(fp != NULL);
572
573         level = mimeinfo->level;
574
575         for (;;) {
576                 textview_add_part(textview, mimeinfo, fp);
577                 if (mimeinfo->parent && mimeinfo->parent->content_type &&
578                     !strcasecmp(mimeinfo->parent->content_type,
579                                 "multipart/alternative"))
580                         mimeinfo = mimeinfo->parent->next;
581                 else
582                         mimeinfo = procmime_mimeinfo_next(mimeinfo);
583                 if (!mimeinfo || mimeinfo->level <= level)
584                         break;
585         }
586 }
587
588 #define TEXT_INSERT(str) \
589         gtk_stext_insert(text, textview->msgfont, NULL, NULL, str, -1)
590
591 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
592 {
593         GtkSText *text;
594
595         if (!partinfo) return;
596
597         textview_set_font(textview, NULL);
598         text = GTK_STEXT(textview->text);
599         textview_clear(textview);
600
601         gtk_stext_freeze(text);
602
603         TEXT_INSERT(_("To save this part, pop up the context menu with "));
604         TEXT_INSERT(_("right click and select `Save as...', "));
605         TEXT_INSERT(_("or press `y' key.\n\n"));
606
607         TEXT_INSERT(_("To display this part as a text message, select "));
608         TEXT_INSERT(_("`Display as text', or press `t' key.\n\n"));
609
610         TEXT_INSERT(_("To open this part with external program, select "));
611         TEXT_INSERT(_("`Open' or `Open with...', "));
612         TEXT_INSERT(_("or double-click, or click the center button, "));
613         TEXT_INSERT(_("or press `l' key."));
614
615         gtk_stext_thaw(text);
616 }
617
618 #if USE_GPGME
619 void textview_show_signature_part(TextView *textview, MimeInfo *partinfo)
620 {
621         GtkSText *text;
622
623         if (!partinfo) return;
624
625         textview_set_font(textview, NULL);
626         text = GTK_STEXT(textview->text);
627         textview_clear(textview);
628
629         gtk_stext_freeze(text);
630
631         if (partinfo->sigstatus_full == NULL) {
632                 TEXT_INSERT(_("This signature has not been checked yet.\n"));
633                 TEXT_INSERT(_("To check it, pop up the context menu with\n"));
634                 TEXT_INSERT(_("right click and select `Check signature'.\n"));
635         } else {
636                 TEXT_INSERT(partinfo->sigstatus_full);
637         }
638
639         gtk_stext_thaw(text);
640 }
641 #endif /* USE_GPGME */
642
643 #undef TEXT_INSERT
644
645 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
646                                 FILE *fp, const gchar *charset)
647 {
648         FILE *tmpfp;
649         gchar buf[BUFFSIZE];
650         CodeConverter *conv;
651
652         conv = conv_code_converter_new(charset);
653
654         tmpfp = procmime_decode_content(NULL, fp, mimeinfo);
655         
656         textview->is_in_signature = FALSE;
657
658         if (tmpfp) {
659                 if (mimeinfo->mime_type == MIME_TEXT_HTML)
660                         textview_show_html(textview, tmpfp, conv);
661                 else if (mimeinfo->mime_type == MIME_TEXT_ENRICHED)
662                         textview_show_ertf(textview, tmpfp, conv);
663                 else
664                         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
665                                 textview_write_line(textview, buf, conv);
666                 fclose(tmpfp);
667         }
668
669         conv_code_converter_destroy(conv);
670 }
671
672 static void textview_show_html(TextView *textview, FILE *fp,
673                                CodeConverter *conv)
674 {
675         HTMLParser *parser;
676         gchar *str;
677
678         parser = html_parser_new(fp, conv);
679         g_return_if_fail(parser != NULL);
680
681         while ((str = html_parse(parser)) != NULL) {
682                 if (parser->state == HTML_HREF) {
683                         /* first time : get and copy the URL */
684                         if (parser->href == NULL) {
685                                 /* ALF - the sylpheed html parser returns an empty string,
686                                  * if still inside an <a>, but already parsed past HREF */
687                                 str = strtok(str, " ");
688                                 if (str) { 
689                                         parser->href = strdup(str);
690                                         /* the URL may (or not) be followed by the
691                                          * referenced text */
692                                         str = strtok(NULL, "");
693                                 }       
694                         }
695                         if (str != NULL)
696                                 textview_write_link(textview, str, parser->href, NULL);
697                 } else
698                         textview_write_line(textview, str, NULL);
699         }
700         html_parser_destroy(parser);
701 }
702
703 static void textview_show_ertf(TextView *textview, FILE *fp,
704                                CodeConverter *conv)
705 {
706         ERTFParser *parser;
707         gchar *str;
708
709         parser = ertf_parser_new(fp, conv);
710         g_return_if_fail(parser != NULL);
711
712         while ((str = ertf_parse(parser)) != NULL) {
713                 textview_write_line(textview, str, NULL);
714         }
715         
716         ertf_parser_destroy(parser);
717 }
718
719 /* get_uri_part() - retrieves a URI starting from scanpos.
720                     Returns TRUE if succesful */
721 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
722                              const gchar **bp, const gchar **ep)
723 {
724         const gchar *ep_;
725
726         g_return_val_if_fail(start != NULL, FALSE);
727         g_return_val_if_fail(scanpos != NULL, FALSE);
728         g_return_val_if_fail(bp != NULL, FALSE);
729         g_return_val_if_fail(ep != NULL, FALSE);
730
731         *bp = scanpos;
732
733         /* find end point of URI */
734         for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
735                 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
736                         break;
737         }
738
739         /* no punctuation at end of string */
740
741         /* FIXME: this stripping of trailing punctuations may bite with other URIs.
742          * should pass some URI type to this function and decide on that whether
743          * to perform punctuation stripping */
744
745 #define IS_REAL_PUNCT(ch)       (ispunct(ch) && ((ch) != '/')) 
746
747         for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
748                 ;
749
750 #undef IS_REAL_PUNCT
751
752         *ep = ep_;
753
754         return TRUE;            
755 }
756
757 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
758 {
759         return g_strndup(bp, ep - bp);
760 }
761
762 /* valid mail address characters */
763 #define IS_RFC822_CHAR(ch) \
764         (isascii(ch) && \
765          (ch) > 32   && \
766          (ch) != 127 && \
767          !isspace(ch) && \
768          !strchr("(),;<>\"", (ch)))
769
770 /* alphabet and number within 7bit ASCII */
771 #define IS_ASCII_ALNUM(ch)      (isascii(ch) && isalnum(ch))
772 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
773
774 static GHashTable *create_domain_tab(void)
775 {
776         static const gchar *toplvl_domains [] = {
777             "museum", "aero",
778             "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
779             "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
780             "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
781             "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
782             "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
783             "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
784             "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
785             "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
786             "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
787             "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
788             "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
789             "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
790             "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
791             "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
792             "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
793             "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
794             "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
795             "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
796             "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
797             "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
798             "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
799             "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
800             "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
801             "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
802             "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
803             "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw" 
804         };
805         gint n;
806         GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
807         
808         g_return_val_if_fail(htab, NULL);
809         for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++) 
810                 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
811         return htab;
812 }
813
814 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
815 {
816         const gint MAX_LVL_DOM_NAME_LEN = 6;
817         gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
818         const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
819         register gchar *p;
820         
821         if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
822                 return FALSE;
823
824         for (p = buf; p < m &&  first < last; *p++ = *first++)
825                 ;
826         *p = 0;
827
828         return g_hash_table_lookup(tab, buf) != NULL;
829 }
830
831 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
832 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
833                                const gchar **bp, const gchar **ep)
834 {
835         /* more complex than the uri part because we need to scan back and forward starting from
836          * the scan position. */
837         gboolean result = FALSE;
838         const gchar *bp_ = NULL;
839         const gchar *ep_ = NULL;
840         static GHashTable *dom_tab;
841         const gchar *last_dot = NULL;
842         const gchar *prelast_dot = NULL;
843         const gchar *last_tld_char = NULL;
844
845         /* the informative part of the email address (describing the name
846          * of the email address owner) may contain quoted parts. the
847          * closure stack stores the last encountered quotes. */
848         gchar closure_stack[128];
849         gchar *ptr = closure_stack;
850
851         g_return_val_if_fail(start != NULL, FALSE);
852         g_return_val_if_fail(scanpos != NULL, FALSE);
853         g_return_val_if_fail(bp != NULL, FALSE);
854         g_return_val_if_fail(ep != NULL, FALSE);
855
856         if (!dom_tab)
857                 dom_tab = create_domain_tab();
858         g_return_val_if_fail(dom_tab, FALSE);   
859
860         /* scan start of address */
861         for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
862                 ;
863
864         /* TODO: should start with an alnum? */
865         bp_++;
866         for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
867                 ;
868
869         if (bp_ != scanpos) {
870                 /* scan end of address */
871                 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
872                         if (*ep_ == '.') {
873                                 prelast_dot = last_dot;
874                                 last_dot = ep_;
875                                 if (*(last_dot + 1) == '.') {
876                                         if (prelast_dot == NULL)
877                                                 return FALSE;
878                                         last_dot = prelast_dot;
879                                         break;
880                                 }
881                         }
882
883                 /* TODO: really should terminate with an alnum? */
884                 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
885                         ;
886                 ep_++;
887
888                 if (last_dot == NULL)
889                         return FALSE;
890                 if (last_dot >= ep_)
891                         last_dot = prelast_dot;
892                 if (last_dot == NULL || (scanpos + 1 >= last_dot))
893                         return FALSE;
894                 last_dot++;
895
896                 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
897                         if (*last_tld_char == '?')
898                                 break;
899
900                 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
901                         result = TRUE;
902
903                 *ep = ep_;
904                 *bp = bp_;
905         }
906
907         if (!result) return FALSE;
908
909         /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
910         if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_)) 
911                 return FALSE;
912
913         /* see if this is <bracketed>; in this case we also scan for the informative part. */
914         if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
915                 return TRUE;
916
917 #define FULL_STACK()    ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
918 #define IN_STACK()      (ptr > closure_stack)
919 /* has underrun check */
920 #define POP_STACK()     if(IN_STACK()) --ptr
921 /* has overrun check */
922 #define PUSH_STACK(c)   if(!FULL_STACK()) *ptr++ = (c); else return TRUE
923 /* has underrun check */
924 #define PEEK_STACK()    (IN_STACK() ? *(ptr - 1) : 0)
925
926         ep_++;
927
928         /* scan for the informative part. */
929         for (bp_ -= 2; bp_ >= start; bp_--) {
930                 /* if closure on the stack keep scanning */
931                 if (PEEK_STACK() == *bp_) {
932                         POP_STACK();
933                         continue;
934                 }
935                 if (*bp_ == '\'' || *bp_ == '"') {
936                         PUSH_STACK(*bp_);
937                         continue;
938                 }
939
940                 /* if nothing in the closure stack, do the special conditions
941                  * the following if..else expression simply checks whether 
942                  * a token is acceptable. if not acceptable, the clause
943                  * should terminate the loop with a 'break' */
944                 if (!PEEK_STACK()) {
945                         if (*bp_ == '-'
946                         && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
947                         && (((bp_ + 1) < ep_)    && isalnum(*(bp_ + 1)))) {
948                                 /* hyphens are allowed, but only in
949                                    between alnums */
950                         } else if (!ispunct(*bp_)) {
951                                 /* but anything not being a punctiation
952                                    is ok */
953                         } else {
954                                 break; /* anything else is rejected */
955                         }
956                 }
957         }
958
959         bp_++;
960
961 #undef PEEK_STACK
962 #undef PUSH_STACK
963 #undef POP_STACK
964 #undef IN_STACK
965 #undef FULL_STACK
966
967         /* scan forward (should start with an alnum) */
968         for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
969                 ;
970
971         *ep = ep_;
972         *bp = bp_;
973
974         return result;
975 }
976
977 #undef IS_QUOTE
978 #undef IS_RFC822_CHAR
979
980 static gchar *make_email_string(const gchar *bp, const gchar *ep)
981 {
982         /* returns a mailto: URI; mailto: is also used to detect the
983          * uri type later on in the button_pressed signal handler */
984         gchar *tmp;
985         gchar *result;
986
987         tmp = g_strndup(bp, ep - bp);
988         result = g_strconcat("mailto:", tmp, NULL);
989         g_free(tmp);
990
991         return result;
992 }
993
994 #define ADD_TXT_POS(bp_, ep_, pti_) \
995         if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
996                 last = last->next; \
997                 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
998                 last->next = NULL; \
999         } else { \
1000                 g_warning("alloc error scanning URIs\n"); \
1001                 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
1002                                 linebuf, -1); \
1003                 return; \
1004         }
1005
1006 /* textview_make_clickable_parts() - colorizes clickable parts */
1007 static void textview_make_clickable_parts(TextView *textview,
1008                                           GdkFont *font,
1009                                           GdkColor *fg_color,
1010                                           GdkColor *uri_color,
1011                                           const gchar *linebuf)
1012 {
1013         /* parse table - in order of priority */
1014         struct table {
1015                 const gchar *needle; /* token */
1016
1017                 /* token search function */
1018                 gchar    *(*search)     (const gchar *haystack,
1019                                          const gchar *needle);
1020                 /* part parsing function */
1021                 gboolean  (*parse)      (const gchar *start,
1022                                          const gchar *scanpos,
1023                                          const gchar **bp_,
1024                                          const gchar **ep_);
1025                 /* part to URI function */
1026                 gchar    *(*build_uri)  (const gchar *bp,
1027                                          const gchar *ep);
1028         };
1029
1030         static struct table parser[] = {
1031                 {"http://",  strcasestr, get_uri_part,   make_uri_string},
1032                 {"https://", strcasestr, get_uri_part,   make_uri_string},
1033                 {"ftp://",   strcasestr, get_uri_part,   make_uri_string},
1034                 {"www.",     strcasestr, get_uri_part,   make_uri_string},
1035                 {"mailto:",  strcasestr, get_uri_part,   make_uri_string},
1036                 {"@",        strcasestr, get_email_part, make_email_string}
1037         };
1038         const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1039
1040         gint  n;
1041         const gchar *walk, *bp, *ep;
1042
1043         struct txtpos {
1044                 const gchar     *bp, *ep;       /* text position */
1045                 gint             pti;           /* index in parse table */
1046                 struct txtpos   *next;          /* next */
1047         } head = {NULL, NULL, 0,  NULL}, *last = &head;
1048
1049         GtkSText *text = GTK_STEXT(textview->text);
1050
1051         /* parse for clickable parts, and build a list of begin and end positions  */
1052         for (walk = linebuf, n = 0;;) {
1053                 gint last_index = PARSE_ELEMS;
1054                 gchar *scanpos = NULL;
1055
1056                 /* FIXME: this looks phony. scanning for anything in the parse table */
1057                 for (n = 0; n < PARSE_ELEMS; n++) {
1058                         gchar *tmp;
1059
1060                         tmp = parser[n].search(walk, parser[n].needle);
1061                         if (tmp) {
1062                                 if (scanpos == NULL || tmp < scanpos) {
1063                                         scanpos = tmp;
1064                                         last_index = n;
1065                                 }
1066                         }                                       
1067                 }
1068
1069                 if (scanpos) {
1070                         /* check if URI can be parsed */
1071                         if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1072                             && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1073                                         ADD_TXT_POS(bp, ep, last_index);
1074                                         walk = ep;
1075                         } else
1076                                 walk = scanpos +
1077                                         strlen(parser[last_index].needle);
1078                 } else
1079                         break;
1080         }
1081
1082         /* colorize this line */
1083         if (head.next) {
1084                 const gchar *normal_text = linebuf;
1085
1086                 /* insert URIs */
1087                 for (last = head.next; last != NULL;
1088                      normal_text = last->ep, last = last->next) {
1089                         RemoteURI *uri;
1090
1091                         uri = g_new(RemoteURI, 1);
1092                         if (last->bp - normal_text > 0)
1093                                 gtk_stext_insert(text, font,
1094                                                 fg_color, NULL,
1095                                                 normal_text,
1096                                                 last->bp - normal_text);
1097                         uri->uri = parser[last->pti].build_uri(last->bp,
1098                                                                last->ep);
1099                         uri->start = gtk_stext_get_point(text);
1100                         gtk_stext_insert(text, font, uri_color,
1101                                         NULL, last->bp, last->ep - last->bp);
1102                         uri->end = gtk_stext_get_point(text);
1103                         textview->uri_list =
1104                                 g_slist_append(textview->uri_list, uri);
1105                 }
1106
1107                 if (*normal_text)
1108                         gtk_stext_insert(text, font, fg_color,
1109                                         NULL, normal_text, -1);
1110         } else
1111                 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1112 }
1113
1114 #undef ADD_TXT_POS
1115
1116 static void textview_write_line(TextView *textview, const gchar *str,
1117                                 CodeConverter *conv)
1118 {
1119         GtkSText *text = GTK_STEXT(textview->text);
1120         gchar buf[BUFFSIZE];
1121         GdkColor *fg_color;
1122         gint quotelevel = -1;
1123
1124         if (!conv) {
1125                 if (textview->text_is_mb)
1126                         conv_localetodisp(buf, sizeof(buf), str);
1127                 else
1128                         strncpy2(buf, str, sizeof(buf));
1129         } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1130                 conv_localetodisp(buf, sizeof(buf), str);
1131         else if (textview->text_is_mb)
1132                 conv_unreadable_locale(buf);
1133
1134         strcrchomp(buf);
1135         if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1136         fg_color = NULL;
1137
1138         /* change color of quotation
1139            >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1140            Up to 3 levels of quotations are detected, and each
1141            level is colored using a different color. */
1142         if (prefs_common.enable_color 
1143             && line_has_quote_char(buf, prefs_common.quote_chars)) {
1144                 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1145
1146                 /* set up the correct foreground color */
1147                 if (quotelevel > 2) {
1148                         /* recycle colors */
1149                         if (prefs_common.recycle_quote_colors)
1150                                 quotelevel %= 3;
1151                         else
1152                                 quotelevel = 2;
1153                 }
1154         }
1155
1156         if (quotelevel == -1)
1157                 fg_color = NULL;
1158         else
1159                 fg_color = &quote_colors[quotelevel];
1160
1161         if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1162                 fg_color = &signature_color;
1163                 textview->is_in_signature = TRUE;
1164         }
1165         
1166         if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1167                 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1168
1169         if (prefs_common.enable_color)
1170                 textview_make_clickable_parts(textview, textview->msgfont,
1171                                               fg_color, &uri_color, buf);
1172         else
1173                 textview_make_clickable_parts(textview, textview->msgfont,
1174                                               fg_color, NULL, buf);
1175 }
1176
1177 void textview_write_link(TextView *textview, const gchar *str,
1178                          const gchar *uri, CodeConverter *conv)
1179 {
1180         GdkColor *link_color = NULL;
1181         GtkSText *text = GTK_STEXT(textview->text);
1182         gchar buf[BUFFSIZE];
1183         gchar *bufp;
1184         RemoteURI *r_uri;
1185
1186         if (*str == '\0')
1187                 return;
1188
1189         if (!conv) {
1190                 if (textview->text_is_mb)
1191                         conv_localetodisp(buf, sizeof(buf), str);
1192                 else
1193                         strncpy2(buf, str, sizeof(buf));
1194         } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1195                 conv_localetodisp(buf, sizeof(buf), str);
1196         else if (textview->text_is_mb)
1197                 conv_unreadable_locale(buf);
1198
1199         strcrchomp(buf);
1200
1201         for (bufp = buf; isspace(*bufp); bufp++)
1202                 gtk_stext_insert(text, textview->msgfont, NULL, NULL, bufp, 1);
1203
1204         if (prefs_common.enable_color) {
1205                 link_color = &uri_color;
1206         }
1207         r_uri = g_new(RemoteURI, 1);
1208         r_uri->uri = g_strdup(uri);
1209         r_uri->start = gtk_stext_get_point(text);
1210         gtk_stext_insert(text, textview->msgfont, link_color, NULL, bufp, -1);
1211         r_uri->end = gtk_stext_get_point(text);
1212         textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1213 }
1214
1215 void textview_clear(TextView *textview)
1216 {
1217         GtkSText *text = GTK_STEXT(textview->text);
1218
1219         gtk_stext_freeze(text);
1220         gtk_stext_set_point(text, 0);
1221         gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1222         gtk_stext_thaw(text);
1223
1224         textview_uri_list_remove_all(textview->uri_list);
1225         textview->uri_list = NULL;
1226
1227         textview->body_pos = 0;
1228         textview->cur_pos  = 0;
1229 }
1230
1231 void textview_destroy(TextView *textview)
1232 {
1233         textview_uri_list_remove_all(textview->uri_list);
1234         textview->uri_list = NULL;
1235
1236         if (!textview->scrolledwin_sb->parent)
1237                 gtk_widget_destroy(textview->scrolledwin_sb);
1238         if (!textview->scrolledwin_mb->parent)
1239                 gtk_widget_destroy(textview->scrolledwin_mb);
1240
1241         if (textview->msgfont)
1242                 gdk_font_unref(textview->msgfont);
1243         if (textview->boldfont)
1244                 gdk_font_unref(textview->boldfont);
1245
1246         g_free(textview);
1247 }
1248
1249 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1250 {
1251         textview->show_all_headers = all_headers;
1252 }
1253
1254 void textview_set_font(TextView *textview, const gchar *codeset)
1255 {
1256         gboolean use_fontset = TRUE;
1257
1258         /* In multi-byte mode, GtkSText can't display 8bit characters
1259            correctly, so it must be single-byte mode. */
1260         if (MB_CUR_MAX > 1) {
1261                 if (codeset && conv_get_current_charset() != C_UTF_8) {
1262                         if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1263                             !g_strcasecmp(codeset, "BALTIC"))
1264                                 use_fontset = FALSE;
1265                         else if (conv_get_current_charset() != C_EUC_JP &&
1266                                  (!g_strncasecmp(codeset, "KOI8-", 5) ||
1267                                   !g_strncasecmp(codeset, "CP", 2)    ||
1268                                   !g_strncasecmp(codeset, "WINDOWS-", 8)))
1269                                 use_fontset = FALSE;
1270                 }
1271         } else
1272                 use_fontset = FALSE;
1273
1274         if (textview->text_is_mb && !use_fontset) {
1275                 GtkWidget *parent;
1276
1277                 parent = textview->scrolledwin_mb->parent;
1278                 gtkut_container_remove(GTK_CONTAINER(parent),
1279                                        textview->scrolledwin_mb);
1280                 gtk_container_add(GTK_CONTAINER(parent),
1281                                   textview->scrolledwin_sb);
1282
1283                 textview->text = textview->text_sb;
1284                 textview->text_is_mb = FALSE;
1285         } else if (!textview->text_is_mb && use_fontset) {
1286                 GtkWidget *parent;
1287
1288                 parent = textview->scrolledwin_sb->parent;
1289                 gtkut_container_remove(GTK_CONTAINER(parent),
1290                                        textview->scrolledwin_sb);
1291                 gtk_container_add(GTK_CONTAINER(parent),
1292                                   textview->scrolledwin_mb);
1293
1294                 textview->text = textview->text_mb;
1295                 textview->text_is_mb = TRUE;
1296         }
1297
1298         if (prefs_common.textfont) {
1299                 GdkFont *font;
1300
1301                 if (use_fontset) {
1302                         if (text_mb_font) {
1303                                 text_mb_font->ascent = text_mb_font_orig_ascent;
1304                                 text_mb_font->descent = text_mb_font_orig_descent;
1305                         }
1306                         font = gdk_fontset_load(prefs_common.textfont);
1307                         if (font && text_mb_font != font) {
1308                                 if (text_mb_font)
1309                                         gdk_font_unref(text_mb_font);
1310                                 text_mb_font = font;
1311                                 text_mb_font_orig_ascent = font->ascent;
1312                                 text_mb_font_orig_descent = font->descent;
1313                         }
1314                 } else {
1315                         if (text_sb_font) {
1316                                 text_sb_font->ascent = text_sb_font_orig_ascent;
1317                                 text_sb_font->descent = text_sb_font_orig_descent;
1318                         }
1319                         if (MB_CUR_MAX > 1)
1320                                 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1321                         else
1322                                 font = gtkut_font_load_from_fontset
1323                                         (prefs_common.textfont);
1324                         if (font && text_sb_font != font) {
1325                                 if (text_sb_font)
1326                                         gdk_font_unref(text_sb_font);
1327                                 text_sb_font = font;
1328                                 text_sb_font_orig_ascent = font->ascent;
1329                                 text_sb_font_orig_descent = font->descent;
1330                         }
1331                 }
1332
1333                 if (font) {
1334                         gint ascent, descent;
1335
1336                         descent = prefs_common.line_space / 2;
1337                         ascent  = prefs_common.line_space - descent;
1338                         font->ascent  += ascent;
1339                         font->descent += descent;
1340
1341                         if (textview->msgfont)
1342                                 gdk_font_unref(textview->msgfont);
1343                         textview->msgfont = font;
1344                         gdk_font_ref(font);
1345                 }
1346         }
1347
1348         if (!textview->boldfont && prefs_common.boldfont)
1349                 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1350         if (!spacingfont)
1351                 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1352 }
1353
1354 enum
1355 {
1356         H_DATE          = 0,
1357         H_FROM          = 1,
1358         H_TO            = 2,
1359         H_NEWSGROUPS    = 3,
1360         H_SUBJECT       = 4,
1361         H_CC            = 5,
1362         H_REPLY_TO      = 6,
1363         H_FOLLOWUP_TO   = 7,
1364         H_X_MAILER      = 8,
1365         H_X_NEWSREADER  = 9,
1366         H_USER_AGENT    = 10,
1367         H_ORGANIZATION  = 11,
1368 };
1369
1370 void textview_set_position(TextView *textview, gint pos)
1371 {
1372         if (pos < 0) {
1373                 textview->cur_pos =
1374                         gtk_stext_get_length(GTK_STEXT(textview->text));
1375         } else {
1376                 textview->cur_pos = pos;
1377         }
1378 }
1379
1380 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1381 {
1382         gchar buf[BUFFSIZE];
1383         GPtrArray *headers, *sorted_headers;
1384         GSList *disphdr_list;
1385         Header *header;
1386         gint i;
1387
1388         g_return_val_if_fail(fp != NULL, NULL);
1389
1390         if (textview->show_all_headers)
1391                 return procheader_get_header_array_asis(fp);
1392
1393         if (!prefs_common.display_header) {
1394                 while (fgets(buf, sizeof(buf), fp) != NULL)
1395                         if (buf[0] == '\r' || buf[0] == '\n') break;
1396                 return NULL;
1397         }
1398
1399         headers = procheader_get_header_array_asis(fp);
1400
1401         sorted_headers = g_ptr_array_new();
1402
1403         for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1404              disphdr_list = disphdr_list->next) {
1405                 DisplayHeaderProp *dp =
1406                         (DisplayHeaderProp *)disphdr_list->data;
1407
1408                 for (i = 0; i < headers->len; i++) {
1409                         header = g_ptr_array_index(headers, i);
1410
1411                         if (procheader_headername_equal(header->name,
1412                                                         dp->name)) {
1413                                 if (dp->hidden)
1414                                         procheader_header_free(header);
1415                                 else
1416                                         g_ptr_array_add(sorted_headers, header);
1417
1418                                 g_ptr_array_remove_index(headers, i);
1419                                 i--;
1420                         }
1421                 }
1422         }
1423
1424         if (prefs_common.show_other_header) {
1425                 for (i = 0; i < headers->len; i++) {
1426                         header = g_ptr_array_index(headers, i);
1427                         g_ptr_array_add(sorted_headers, header);
1428                 }
1429                 g_ptr_array_free(headers, TRUE);
1430         } else
1431                 procheader_header_array_destroy(headers);
1432
1433
1434         return sorted_headers;
1435 }
1436
1437 static void textview_show_header(TextView *textview, GPtrArray *headers)
1438 {
1439         GtkSText *text = GTK_STEXT(textview->text);
1440         Header *header;
1441         gint i;
1442
1443         g_return_if_fail(headers != NULL);
1444
1445         gtk_stext_freeze(text);
1446
1447         for (i = 0; i < headers->len; i++) {
1448                 header = g_ptr_array_index(headers, i);
1449                 g_return_if_fail(header->name != NULL);
1450
1451                 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1452                                 header->name, -1);
1453                 if (header->name[strlen(header->name) - 1] != ' ')
1454                         gtk_stext_insert(text, textview->boldfont,
1455                                         NULL, NULL, " ", 1);
1456
1457                 if (procheader_headername_equal(header->name, "Subject") ||
1458                     procheader_headername_equal(header->name, "From")    ||
1459                     procheader_headername_equal(header->name, "To")      ||
1460                     procheader_headername_equal(header->name, "Cc"))
1461                         unfold_line(header->body);
1462
1463                 if (textview->text_is_mb == TRUE)
1464                         conv_unreadable_locale(header->body);
1465
1466                 if (prefs_common.enable_color &&
1467                     (procheader_headername_equal(header->name, "X-Mailer") ||
1468                      procheader_headername_equal(header->name,
1469                                                  "X-Newsreader")) &&
1470                     strstr(header->body, "Sylpheed") != NULL)
1471                         gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1472                                         header->body, -1);
1473                 else if (prefs_common.enable_color) {
1474                         textview_make_clickable_parts(textview,
1475                                                       NULL, NULL, &uri_color,
1476                                                       header->body);
1477                 } else {
1478                         textview_make_clickable_parts(textview,
1479                                                       NULL, NULL, NULL,
1480                                                       header->body);
1481                 }
1482                 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1483         }
1484
1485         gtk_stext_thaw(text);
1486 }
1487
1488 gboolean textview_search_string(TextView *textview, const gchar *str,
1489                                 gboolean case_sens)
1490 {
1491         GtkSText *text = GTK_STEXT(textview->text);
1492         gint pos;
1493         gint len;
1494
1495         g_return_val_if_fail(str != NULL, FALSE);
1496
1497         len = get_mbs_len(str);
1498         g_return_val_if_fail(len >= 0, FALSE);
1499
1500         pos = textview->cur_pos;
1501         if (pos < textview->body_pos)
1502                 pos = textview->body_pos;
1503
1504         if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1505                 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1506                 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1507                 textview_set_position(textview, pos + len);
1508                 return TRUE;
1509         }
1510
1511         return FALSE;
1512 }
1513
1514 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1515                                          gboolean case_sens)
1516 {
1517         GtkSText *text = GTK_STEXT(textview->text);
1518         gint pos;
1519         wchar_t *wcs;
1520         gint len;
1521         gint text_len;
1522         gboolean found = FALSE;
1523
1524         g_return_val_if_fail(str != NULL, FALSE);
1525
1526         wcs = strdup_mbstowcs(str);
1527         g_return_val_if_fail(wcs != NULL, FALSE);
1528         len = wcslen(wcs);
1529         pos = textview->cur_pos;
1530         text_len = gtk_stext_get_length(text);
1531         if (text_len - textview->body_pos < len) {
1532                 g_free(wcs);
1533                 return FALSE;
1534         }
1535         if (pos <= textview->body_pos || text_len - pos < len)
1536                 pos = text_len - len;
1537
1538         for (; pos >= textview->body_pos; pos--) {
1539                 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1540                     == TRUE) {
1541                         gtk_editable_set_position(GTK_EDITABLE(text), pos);
1542                         gtk_editable_select_region(GTK_EDITABLE(text),
1543                                                    pos, pos + len);
1544                         textview_set_position(textview, pos - 1);
1545                         found = TRUE;
1546                         break;
1547                 }
1548                 if (pos == textview->body_pos) break;
1549         }
1550
1551         g_free(wcs);
1552         return found;
1553 }
1554
1555 void textview_scroll_one_line(TextView *textview, gboolean up)
1556 {
1557         GtkSText *text = GTK_STEXT(textview->text);
1558         gfloat upper;
1559
1560         if (prefs_common.enable_smooth_scroll) {
1561                 textview_smooth_scroll_one_line(textview, up);
1562                 return;
1563         }
1564
1565         if (!up) {
1566                 upper = text->vadj->upper - text->vadj->page_size;
1567                 if (text->vadj->value < upper) {
1568                         text->vadj->value +=
1569                                 text->vadj->step_increment * 4;
1570                         text->vadj->value =
1571                                 MIN(text->vadj->value, upper);
1572                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1573                                                 "value_changed");
1574                 }
1575         } else {
1576                 if (text->vadj->value > 0.0) {
1577                         text->vadj->value -=
1578                                 text->vadj->step_increment * 4;
1579                         text->vadj->value =
1580                                 MAX(text->vadj->value, 0.0);
1581                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1582                                                 "value_changed");
1583                 }
1584         }
1585 }
1586
1587 gboolean textview_scroll_page(TextView *textview, gboolean up)
1588 {
1589         GtkSText *text = GTK_STEXT(textview->text);
1590         gfloat upper;
1591         gfloat page_incr;
1592
1593         if (prefs_common.enable_smooth_scroll)
1594                 return textview_smooth_scroll_page(textview, up);
1595
1596         if (prefs_common.scroll_halfpage)
1597                 page_incr = text->vadj->page_increment / 2;
1598         else
1599                 page_incr = text->vadj->page_increment;
1600
1601         if (!up) {
1602                 upper = text->vadj->upper - text->vadj->page_size;
1603                 if (text->vadj->value < upper) {
1604                         text->vadj->value += page_incr;
1605                         text->vadj->value = MIN(text->vadj->value, upper);
1606                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1607                                                 "value_changed");
1608                 } else
1609                         return FALSE;
1610         } else {
1611                 if (text->vadj->value > 0.0) {
1612                         text->vadj->value -= page_incr;
1613                         text->vadj->value = MAX(text->vadj->value, 0.0);
1614                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1615                                                 "value_changed");
1616                 } else
1617                         return FALSE;
1618         }
1619
1620         return TRUE;
1621 }
1622
1623 static void textview_smooth_scroll_do(TextView *textview,
1624                                       gfloat old_value, gfloat last_value,
1625                                       gint step)
1626 {
1627         GtkSText *text = GTK_STEXT(textview->text);
1628         gint change_value;
1629         gboolean up;
1630         gint i;
1631
1632         if (old_value < last_value) {
1633                 change_value = last_value - old_value;
1634                 up = FALSE;
1635         } else {
1636                 change_value = old_value - last_value;
1637                 up = TRUE;
1638         }
1639
1640         gdk_key_repeat_disable();
1641
1642         for (i = step; i <= change_value; i += step) {
1643                 text->vadj->value = old_value + (up ? -i : i);
1644                 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1645                                         "value_changed");
1646         }
1647
1648         text->vadj->value = last_value;
1649         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1650
1651         gdk_key_repeat_restore();
1652 }
1653
1654 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1655 {
1656         GtkSText *text = GTK_STEXT(textview->text);
1657         gfloat upper;
1658         gfloat old_value;
1659         gfloat last_value;
1660
1661         if (!up) {
1662                 upper = text->vadj->upper - text->vadj->page_size;
1663                 if (text->vadj->value < upper) {
1664                         old_value = text->vadj->value;
1665                         last_value = text->vadj->value +
1666                                 text->vadj->step_increment * 4;
1667                         last_value = MIN(last_value, upper);
1668
1669                         textview_smooth_scroll_do(textview, old_value,
1670                                                   last_value,
1671                                                   prefs_common.scroll_step);
1672                 }
1673         } else {
1674                 if (text->vadj->value > 0.0) {
1675                         old_value = text->vadj->value;
1676                         last_value = text->vadj->value -
1677                                 text->vadj->step_increment * 4;
1678                         last_value = MAX(last_value, 0.0);
1679
1680                         textview_smooth_scroll_do(textview, old_value,
1681                                                   last_value,
1682                                                   prefs_common.scroll_step);
1683                 }
1684         }
1685 }
1686
1687 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1688 {
1689         GtkSText *text = GTK_STEXT(textview->text);
1690         gfloat upper;
1691         gfloat page_incr;
1692         gfloat old_value;
1693         gfloat last_value;
1694
1695         if (prefs_common.scroll_halfpage)
1696                 page_incr = text->vadj->page_increment / 2;
1697         else
1698                 page_incr = text->vadj->page_increment;
1699
1700         if (!up) {
1701                 upper = text->vadj->upper - text->vadj->page_size;
1702                 if (text->vadj->value < upper) {
1703                         old_value = text->vadj->value;
1704                         last_value = text->vadj->value + page_incr;
1705                         last_value = MIN(last_value, upper);
1706
1707                         textview_smooth_scroll_do(textview, old_value,
1708                                                   last_value,
1709                                                   prefs_common.scroll_step);
1710                 } else
1711                         return FALSE;
1712         } else {
1713                 if (text->vadj->value > 0.0) {
1714                         old_value = text->vadj->value;
1715                         last_value = text->vadj->value - page_incr;
1716                         last_value = MAX(last_value, 0.0);
1717
1718                         textview_smooth_scroll_do(textview, old_value,
1719                                                   last_value,
1720                                                   prefs_common.scroll_step);
1721                 } else
1722                         return FALSE;
1723         }
1724
1725         return TRUE;
1726 }
1727
1728 #define KEY_PRESS_EVENT_STOP() \
1729         if (gtk_signal_n_emissions_by_name \
1730                 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1731                 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1732                                              "key_press_event"); \
1733         }
1734
1735 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1736                                  TextView *textview)
1737 {
1738         SummaryView *summaryview = NULL;
1739         MessageView *messageview = textview->messageview;
1740
1741         if (!event) return FALSE;
1742         if (messageview->mainwin)
1743                 summaryview = messageview->mainwin->summaryview;
1744
1745         switch (event->keyval) {
1746         case GDK_Tab:
1747         case GDK_Home:
1748         case GDK_Left:
1749         case GDK_Up:
1750         case GDK_Right:
1751         case GDK_Down:
1752         case GDK_Page_Up:
1753         case GDK_Page_Down:
1754         case GDK_End:
1755         case GDK_Control_L:
1756         case GDK_Control_R:
1757                 break;
1758         case GDK_space:
1759                 if (summaryview)
1760                         summary_pass_key_press_event(summaryview, event);
1761                 else
1762                         textview_scroll_page(textview, FALSE);
1763                 break;
1764         case GDK_BackSpace:
1765                 textview_scroll_page(textview, TRUE);
1766                 break;
1767         case GDK_Return:
1768                 textview_scroll_one_line(textview,
1769                                          (event->state & GDK_MOD1_MASK) != 0);
1770                 break;
1771         case GDK_Delete:
1772                 if (summaryview)
1773                         summary_pass_key_press_event(summaryview, event);
1774                 break;
1775         case GDK_n:
1776         case GDK_N:
1777         case GDK_p:
1778         case GDK_P:
1779         case GDK_y:
1780         case GDK_t:
1781         case GDK_l:
1782                 if (messageview->type == MVIEW_MIME &&
1783                     textview == messageview->mimeview->textview) {
1784                         KEY_PRESS_EVENT_STOP();
1785                         mimeview_pass_key_press_event(messageview->mimeview,
1786                                                       event);
1787                         break;
1788                 }
1789                 /* fall through */
1790         default:
1791                 if (summaryview &&
1792                     event->window != messageview->mainwin->window->window) {
1793                         GdkEventKey tmpev = *event;
1794
1795                         tmpev.window = messageview->mainwin->window->window;
1796                         KEY_PRESS_EVENT_STOP();
1797                         gtk_widget_event(messageview->mainwin->window,
1798                                          (GdkEvent *)&tmpev);
1799                 }
1800                 break;
1801         }
1802
1803         return TRUE;
1804 }
1805
1806 static gint show_url_timeout_cb(gpointer data)
1807 {
1808         TextView *textview = (TextView *)data;
1809         
1810         if (textview->messageview->mainwin)
1811                 if (textview->show_url_msgid)
1812                         gtk_statusbar_remove(GTK_STATUSBAR(
1813                                 textview->messageview->mainwin->statusbar),
1814                                 textview->messageview->mainwin->folderview_cid,
1815                                 textview->show_url_msgid);
1816                 return FALSE;
1817 }
1818
1819 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1820                                     TextView *textview)
1821 {
1822         if (event)
1823                 textview->last_buttonpress = event->type;
1824         return FALSE;
1825 }
1826
1827 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1828                                     TextView *textview)
1829 {
1830         textview->cur_pos = 
1831                 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1832
1833         if (event && 
1834             ((event->button == 1)
1835              || event->button == 2 || event->button == 3)) {
1836                 GSList *cur;
1837
1838                 /* double click seems to set the cursor after the current
1839                  * word. The cursor position needs fixing, otherwise the
1840                  * last word of a clickable zone will not work */
1841                 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1842                         textview->cur_pos--;
1843                 }
1844
1845                 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1846                         RemoteURI *uri = (RemoteURI *)cur->data;
1847
1848                         if (textview->cur_pos >= uri->start &&
1849                             textview->cur_pos <= uri->end) {
1850                                 /* single click: display url in statusbar */
1851                                 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1852                                         if (textview->messageview->mainwin) {
1853                                                 if (textview->show_url_msgid) {
1854                                                         gtk_timeout_remove(textview->show_url_timeout_tag);
1855                                                         gtk_statusbar_remove(GTK_STATUSBAR(
1856                                                                 textview->messageview->mainwin->statusbar),
1857                                                                 textview->messageview->mainwin->folderview_cid,
1858                                                                 textview->show_url_msgid);
1859                                                         textview->show_url_msgid = 0;
1860                                                 }
1861                                                 textview->show_url_msgid = gtk_statusbar_push(
1862                                                                 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1863                                                                 textview->messageview->mainwin->folderview_cid,
1864                                                                 uri->uri);
1865                                                 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1866                                                 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1867                                         }
1868                                 } else
1869                                 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1870                                         if (event->button == 3) {
1871                                                 gchar *fromname, *fromaddress;
1872                                                 
1873                                                 /* extract url */
1874                                                 fromaddress = g_strdup(uri->uri + 7);
1875                                                 /* Hiroyuki: please put this function in utils.c! */
1876                                                 fromname = procheader_get_fromname(fromaddress);
1877                                                 extract_address(fromaddress);
1878                                                 g_message("adding from textview %s <%s>", fromname, fromaddress);
1879                                                 /* Add to address book - Match */
1880                                                 addressbook_add_contact( fromname, fromaddress, NULL );
1881                                                 
1882                                                 g_free(fromaddress);
1883                                                 g_free(fromname);
1884                                         } else {
1885                                                 PrefsAccount *account = NULL;
1886                                                 FolderItem   *folder_item;
1887
1888                                                 if (textview->messageview && textview->messageview->mainwin 
1889                                                 &&  textview->messageview->mainwin->summaryview 
1890                                                 &&  textview->messageview->mainwin->summaryview->folder_item) {
1891                                                         folder_item = textview->messageview->mainwin->summaryview->folder_item;
1892                                                         if (folder_item->prefs && folder_item->prefs->enable_default_account)
1893                                                                 account = account_find_from_id(folder_item->prefs->default_account);
1894                                                 }
1895                                                 compose_new(account, uri->uri + 7, NULL);
1896                                         }
1897                                 } else 
1898 #if USE_GPGME
1899                                 if (!g_strncasecmp(uri->uri, "pgp:", 4)) {
1900                                         GtkAdjustment *pos = gtk_scrolled_window_get_vadjustment(
1901                                                                 GTK_SCROLLED_WINDOW(textview->scrolledwin));
1902                                         gfloat vpos = pos->value;
1903                                         mimeview_check_signature(textview->messageview->mimeview);
1904                                         /* scroll back where we were */
1905                                         gtk_adjustment_set_value(pos, vpos);
1906                                 } else
1907 #endif
1908                                 {
1909                                         open_uri(uri->uri,
1910                                                  prefs_common.uri_cmd);
1911                                 }
1912                         }
1913                 }
1914         }
1915         if (event)
1916                 textview->last_buttonpress = event->type;
1917         return FALSE;
1918 }
1919
1920 static void textview_uri_list_remove_all(GSList *uri_list)
1921 {
1922         GSList *cur;
1923
1924         for (cur = uri_list; cur != NULL; cur = cur->next) {
1925                 if (cur->data) {
1926                         g_free(((RemoteURI *)cur->data)->uri);
1927                         g_free(cur->data);
1928                 }
1929         }
1930
1931         g_slist_free(uri_list);
1932 }