preps for fixing the email address scanner
[claws.git] / src / textview.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999,2000 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/gtktext.h>
32 #include <gtk/gtksignal.h>
33 #include <stdio.h>
34 #include <ctype.h>
35 #include <string.h>
36 #include <stdlib.h>
37
38 #include "intl.h"
39 #include "main.h"
40 #include "summaryview.h"
41 #include "procheader.h"
42 #include "prefs_common.h"
43 #include "codeconv.h"
44 #include "utils.h"
45 #include "gtkutils.h"
46 #include "procmime.h"
47 #include "html.h"
48 #include "compose.h"
49 #include "addressbook.h"
50 #include "headers_display.h"
51 #include "prefs_display_headers.h"
52
53 #define FONT_LOAD(font, s) \
54 { \
55         gchar *fontstr, *p; \
56  \
57         Xstrdup_a(fontstr, s, ); \
58         if ((p = strchr(fontstr, ',')) != NULL) *p = '\0'; \
59         font = gdk_font_load(fontstr); \
60 }
61
62 typedef struct _RemoteURI       RemoteURI;
63
64 struct _RemoteURI
65 {
66         gchar *uri;
67
68         guint start;
69         guint end;
70 };
71
72 static GdkColor quote_colors[3] = {
73         {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
74         {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
75         {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
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 static GdkColor error_color = {
93         (gulong)0,
94         (gushort)0xefff,
95         (gushort)0,
96         (gushort)0
97 };
98
99 static GdkFont *spacingfont;
100
101 static void textview_show_html          (TextView       *textview,
102                                          FILE           *fp,
103                                          CodeConverter  *conv);
104 static void textview_write_line         (TextView       *textview,
105                                          const gchar    *str,
106                                          CodeConverter  *conv);
107 static GPtrArray *textview_scan_header  (TextView       *textview,
108                                          FILE           *fp);
109 static void textview_show_header        (TextView       *textview,
110                                          GPtrArray      *headers);
111
112 static void textview_key_pressed        (GtkWidget      *widget,
113                                          GdkEventKey    *event,
114                                          TextView       *textview);
115 static void textview_button_pressed     (GtkWidget      *widget,
116                                          GdkEventButton *event,
117                                          TextView       *textview);
118
119 static void textview_uri_list_remove_all(GSList         *uri_list);
120
121 static void textview_smooth_scroll_do           (TextView       *textview,
122                                                  gfloat          old_value,
123                                                  gfloat          last_value,
124                                                  gint            step);
125 static void textview_smooth_scroll_one_line     (TextView       *textview,
126                                                  gboolean        up);
127 static gboolean textview_smooth_scroll_page     (TextView       *textview,
128                                                  gboolean        up);
129
130
131 TextView *textview_create(void)
132 {
133         TextView *textview;
134         GtkWidget *vbox;
135         GtkWidget *scrolledwin_sb;
136         GtkWidget *scrolledwin_mb;
137         GtkWidget *text_sb;
138         GtkWidget *text_mb;
139
140         debug_print(_("Creating text view...\n"));
141         textview = g_new0(TextView, 1);
142
143         scrolledwin_sb = gtk_scrolled_window_new(NULL, NULL);
144         scrolledwin_mb = gtk_scrolled_window_new(NULL, NULL);
145         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_sb),
146                                        GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
147         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_mb),
148                                        GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
149         gtk_widget_set_usize(scrolledwin_sb, prefs_common.mainview_width, -1);
150         gtk_widget_set_usize(scrolledwin_mb, prefs_common.mainview_width, -1);
151
152         /* create GtkText widgets for single-byte and multi-byte character */
153         text_sb = gtk_text_new(NULL, NULL);
154         text_mb = gtk_text_new(NULL, NULL);
155         gtk_widget_show(text_sb);
156         gtk_widget_show(text_mb);
157         gtk_text_set_word_wrap(GTK_TEXT(text_sb), TRUE);
158         gtk_text_set_word_wrap(GTK_TEXT(text_mb), TRUE);
159         gtk_widget_ensure_style(text_sb);
160         gtk_widget_ensure_style(text_mb);
161         if (text_sb->style && text_sb->style->font->type == GDK_FONT_FONTSET) {
162                 GtkStyle *style;
163
164                 style = gtk_style_copy(text_sb->style);
165                 gdk_font_unref(style->font);
166                 FONT_LOAD(style->font, NORMAL_FONT);
167                 gtk_widget_set_style(text_sb, style);
168         }
169         if (text_mb->style && text_mb->style->font->type == GDK_FONT_FONT) {
170                 GtkStyle *style;
171
172                 style = gtk_style_copy(text_mb->style);
173                 gdk_font_unref(style->font);
174                 style->font = gdk_fontset_load(NORMAL_FONT);
175                 gtk_widget_set_style(text_mb, style);
176         }
177         gtk_widget_ref(scrolledwin_sb);
178         gtk_widget_ref(scrolledwin_mb);
179
180         gtk_container_add(GTK_CONTAINER(scrolledwin_sb), text_sb);
181         gtk_container_add(GTK_CONTAINER(scrolledwin_mb), text_mb);
182         gtk_signal_connect(GTK_OBJECT(text_sb), "key_press_event",
183                            GTK_SIGNAL_FUNC(textview_key_pressed),
184                            textview);
185         gtk_signal_connect(GTK_OBJECT(text_sb), "button_press_event",
186                            GTK_SIGNAL_FUNC(textview_button_pressed),
187                            textview);
188         gtk_signal_connect(GTK_OBJECT(text_mb), "key_press_event",
189                            GTK_SIGNAL_FUNC(textview_key_pressed),
190                            textview);
191         gtk_signal_connect(GTK_OBJECT(text_mb), "button_press_event",
192                            GTK_SIGNAL_FUNC(textview_button_pressed),
193                            textview);
194
195         gtk_widget_show(scrolledwin_sb);
196         gtk_widget_show(scrolledwin_mb);
197
198         vbox = gtk_vbox_new(FALSE, 0);
199         gtk_box_pack_start(GTK_BOX(vbox), scrolledwin_sb, TRUE, TRUE, 0);
200
201         textview->vbox           = vbox;
202         textview->scrolledwin    = scrolledwin_sb;
203         textview->scrolledwin_sb = scrolledwin_sb;
204         textview->scrolledwin_mb = scrolledwin_mb;
205         textview->text           = text_sb;
206         textview->text_sb        = text_sb;
207         textview->text_mb        = text_mb;
208         textview->text_is_mb     = FALSE;
209         textview->uri_list       = NULL;
210
211         return textview;
212 }
213
214 void textview_init(TextView *textview)
215 {
216         gtkut_widget_disable_theme_engine(textview->text_sb);
217         gtkut_widget_disable_theme_engine(textview->text_mb);
218         textview_update_message_colors();
219         textview_set_font(textview, NULL);
220 }
221
222 void textview_update_message_colors(void)
223 {
224         GdkColor black = {0, 0, 0, 0};
225
226         if (prefs_common.enable_color) {
227                 /* grab the quote colors, converting from an int to a GdkColor */
228                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
229                                                &quote_colors[0]);
230                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
231                                                &quote_colors[1]);
232                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
233                                                &quote_colors[2]);
234                 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
235                                                &uri_color);
236         } else {
237                 quote_colors[0] = quote_colors[1] = quote_colors[2] = 
238                         uri_color = emphasis_color = black;
239         }
240 }
241
242 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
243                            const gchar *file)
244 {
245         FILE *fp;
246
247         if ((fp = fopen(file, "r")) == NULL) {
248                 FILE_OP_ERROR(file, "fopen");
249                 return;
250         }
251
252         textview_show_part(textview, mimeinfo, fp);
253
254         fclose(fp);
255 }
256
257 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
258 {
259         GtkText *text;
260         gchar buf[BUFFSIZE];
261         const gchar *boundary = NULL;
262         gint boundary_len = 0;
263         const gchar *charset = NULL;
264         FILE *tmpfp;
265         GPtrArray *headers = NULL;
266         CodeConverter *conv;
267
268         g_return_if_fail(mimeinfo != NULL);
269         g_return_if_fail(fp != NULL);
270
271         if (mimeinfo->mime_type == MIME_MULTIPART) {
272                 if (mimeinfo->sub) {
273                         mimeinfo = mimeinfo->sub;
274                         if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0) {
275                                 perror("fseek");
276                                 return;
277                         }
278                 } else
279                         return;
280         }
281         if (mimeinfo->parent && mimeinfo->parent->boundary) {
282                 boundary = mimeinfo->parent->boundary;
283                 boundary_len = strlen(boundary);
284         }
285
286         if (!boundary && mimeinfo->mime_type == MIME_TEXT) {
287                 if (fseek(fp, mimeinfo->fpos, SEEK_SET) < 0)
288                         perror("fseek");
289                 headers = textview_scan_header(textview, fp);
290         } else {
291                 if (mimeinfo->mime_type == MIME_TEXT && mimeinfo->parent) {
292                         glong fpos;
293
294                         if ((fpos = ftell(fp)) < 0)
295                                 perror("ftell");
296                         else if (fseek(fp, mimeinfo->parent->fpos, SEEK_SET)
297                                  < 0)
298                                 perror("fseek");
299                         else {
300                                 headers = textview_scan_header(textview, fp);
301                                 if (fseek(fp, fpos, SEEK_SET) < 0)
302                                         perror("fseek");
303                         }
304                 }
305                 while (fgets(buf, sizeof(buf), fp) != NULL)
306                         if (buf[0] == '\r' || buf[0] == '\n') break;
307         }
308
309         /* display attached RFC822 single text message */
310         if (mimeinfo->parent && mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
311                 if (!mimeinfo->sub || mimeinfo->sub->children) return;
312                 headers = textview_scan_header(textview, fp);
313                 mimeinfo = mimeinfo->sub;
314         } else if (!mimeinfo->parent &&
315                    mimeinfo->mime_type == MIME_MESSAGE_RFC822) {
316                 if (!mimeinfo->sub) return;
317                 headers = textview_scan_header(textview, fp);
318                 mimeinfo = mimeinfo->sub;
319         }
320
321         if (prefs_common.force_charset)
322                 charset = prefs_common.force_charset;
323         else if (mimeinfo->charset)
324                 charset = mimeinfo->charset;
325         textview_set_font(textview, charset);
326
327         conv = conv_code_converter_new(charset);
328
329         textview_clear(textview);
330         text = GTK_TEXT(textview->text);
331         gtk_text_freeze(text);
332
333         if (headers) {
334                 gint i;
335
336                 textview_show_header(textview, headers);
337                 for (i = 0; i < headers->len; i++) {
338                         Header *header = g_ptr_array_index(headers, i);
339                         g_free(header->body);
340                         g_free(header->name);
341                         g_free(header);
342                 }
343                 g_ptr_array_free(headers, TRUE);
344         }
345
346         tmpfp = procmime_decode_content(NULL, fp, mimeinfo);
347         if (tmpfp) {
348                 if (mimeinfo->mime_type == MIME_TEXT_HTML)
349                         textview_show_html(textview, tmpfp, conv);
350                 else
351                         while (fgets(buf, sizeof(buf), tmpfp) != NULL)
352                                 textview_write_line(textview, buf, conv);
353                 fclose(tmpfp);
354         }
355
356         conv_code_converter_destroy(conv);
357
358         gtk_text_thaw(text);
359 }
360
361 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
362 {
363         GtkText *text;
364
365         if (!partinfo) return;
366
367         textview_set_font(textview, NULL);
368         text = GTK_TEXT(textview->text);
369         textview_clear(textview);
370
371         gtk_text_freeze(text);
372
373         gtk_text_insert
374                 (text, textview->msgfont, NULL, NULL,
375                  _("To save this part, pop up the context menu with\n"), -1);
376         gtk_text_insert
377                 (text, textview->msgfont, NULL, NULL,
378                  _("right click and select `Save as...', or press `y' key.\n\n"), -1);
379
380         gtk_text_insert
381                 (text, textview->msgfont, NULL, NULL,
382                  _("To display this part as a text message, select\n"), -1);
383         gtk_text_insert
384                 (text, textview->msgfont, NULL, NULL,
385                  _("`Display as text', or press `t' key.\n\n"), -1);
386
387         gtk_text_insert
388                 (text, textview->msgfont, NULL, NULL,
389                  _("To open this part with external program, select `Open',\n"), -1);
390         gtk_text_insert
391                 (text, textview->msgfont, NULL, NULL,
392                  _("or double-click, or click the center button, or press `l' key."), -1);
393
394         gtk_text_thaw(text);
395 }
396
397 #if USE_GPGME
398 void textview_show_signature_part(TextView *textview, MimeInfo *partinfo)
399 {
400         GtkText *text;
401
402         if (!partinfo) return;
403
404         textview_set_font(textview, NULL);
405         text = GTK_TEXT(textview->text);
406         textview_clear(textview);
407
408         gtk_text_freeze(text);
409
410         if (partinfo->sigstatus_full == NULL) {
411                 gtk_text_insert
412                         (text, textview->msgfont, NULL, NULL,
413                          _("This signature has not been checked yet.\n"), -1);
414                 gtk_text_insert
415                         (text, textview->msgfont, NULL, NULL,
416                          _("To check it, pop up the context menu with\n"), -1);
417                 gtk_text_insert
418                         (text, textview->msgfont, NULL, NULL,
419                          _("right click and select `Check signature'.\n"), -1);
420         } else {
421                 gtk_text_insert(text, textview->msgfont, NULL, NULL,
422                                 partinfo->sigstatus_full, -1);
423         }
424
425         gtk_text_thaw(text);
426 }
427 #endif /* USE_GPGME */
428
429 static void textview_show_html(TextView *textview, FILE *fp,
430                                CodeConverter *conv)
431 {
432         HTMLParser *parser;
433         gchar *str;
434
435         parser = html_parser_new(fp, conv);
436         g_return_if_fail(parser != NULL);
437
438         while ((str = html_parse(parser)) != NULL) {
439                 textview_write_line(textview, str, NULL);
440         }
441         html_parser_destroy(parser);
442 }
443
444 /* get_uri_part() - retrieves a URI starting from scanpos.
445                     Returns TRUE if succesful */
446 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
447                              const gchar **bp, const gchar **ep)
448 {
449         const gchar *ep_;
450
451         g_return_val_if_fail(start != NULL, FALSE);
452         g_return_val_if_fail(scanpos != NULL, FALSE);
453         g_return_val_if_fail(bp != NULL, FALSE);
454         g_return_val_if_fail(ep != NULL, FALSE);
455
456         *bp = scanpos;
457
458         /* find end point of URI */
459         for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
460                 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
461                         break;
462         }
463
464         /* no punctuation at end of string */
465
466         /* FIXME: this stripping of trailing punctuations may bite with other URIs.
467          * should pass some URI type to this function and decide on that whether
468          * to perform punctuation stripping */
469
470 #define IS_REAL_PUNCT(ch) \
471         (ispunct(ch) && ((ch) != '/')) 
472
473         for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
474                 ;
475
476 #undef IS_REAL_PUNCT
477
478         *ep = ep_;
479
480         return TRUE;            
481 }
482
483 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
484 {
485         return g_strndup(bp, ep - bp);
486 }
487
488 /* valid mail address characters */
489 #define IS_RFC822_CHAR(ch) \
490         (isascii(ch) && \
491          (ch) > 32   && \
492          (ch) != 127 && \
493          !isspace(ch) && \
494          !strchr("()<>\"", (ch)))
495
496 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
497 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
498                                const gchar **bp, const gchar **ep)
499 {
500         /* more complex than the uri part because we need to scan back and forward starting from
501          * the scan position. */
502         gboolean result = FALSE;
503         const gchar *bp_;
504         const gchar *ep_;
505
506         g_return_val_if_fail(start != NULL, FALSE);
507         g_return_val_if_fail(scanpos != NULL, FALSE);
508         g_return_val_if_fail(bp != NULL, FALSE);
509         g_return_val_if_fail(ep != NULL, FALSE);
510
511         /* scan start of address */
512         for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
513                 ;
514
515         /* TODO: should start with an alnum? */
516         bp_++;
517         for (; bp_ < scanpos && !isalnum(*bp_); bp_++)
518                 ;
519
520         if (bp_ != scanpos) {
521                 /* scan end of address */
522                 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
523                         ;
524
525                 /* TODO: really should terminate with an alnum? */
526                 for (; ep_ > scanpos  && !isalnum(*ep_); --ep_)
527                         ;
528                 ep_++;
529
530                 if (ep_ > scanpos + 1) {
531                         *ep = ep_;
532                         *bp = bp_;
533                         result = TRUE;
534                 }
535         }
536
537         /* we now have at least the address. now see if this is <bracketed>; in this
538          * case we also scan for the informative part. we could make this a useful
539          * function because it tries to parse out an email address backwards. :) */
540         if (result) {
541                 if ( ((bp_ - 1 > start) && *(bp_ - 1) == '<') &&  (*ep_ == '>')) {
542                         
543                         /* the informative part of the email address (describing the name
544                          * of the email address owner) may contain quoted parts. the
545                          * closure stack stores the last encountered quotes. */
546                         char    closure_stack[128];
547                         char   *ptr = closure_stack;
548 #define FULL_STACK()    ((ptr - closure_stack) >= sizeof closure_stack) 
549 #define IN_STACK()              (ptr > closure_stack)
550 /* has underrun check */
551 #define POP_STACK()             if(IN_STACK()) --ptr            
552 /* has overrun check */
553 #define PUSH_STACK(c)   if(!FULL_STACK()) *ptr++ = (c); else return TRUE 
554 /* has underrun check */
555 #define PEEK_STACK()    (IN_STACK() ? *(ptr - 1) : 0)
556
557                         ep_++;
558
559                         /* scan for the informative part. */
560                         
561                         for (bp_ -= 2; bp_ >= start; bp_--) {
562                                 /* if closure on the stack keep scanning */
563                                 if (PEEK_STACK() == *bp_) {
564                                         POP_STACK();
565                                 }                                       
566                                 else {
567                                         if (*bp_ == '\'' || *bp_ == '"')  {
568                                                 PUSH_STACK(*bp_);
569                                         }                                               
570                                         else {
571                                                 /* if nothing in the closure stack, do the special conditions
572                                                  * the following if..else expression simply checks whether 
573                                                  * a token is acceptable. if not acceptable, the clause
574                                                  * should terminate the loop with a 'break' */
575                                                 if (!PEEK_STACK()) {
576                                                         if ( *bp_ == '-' 
577                                                         &&   (((bp_ - 1) >= start) && isalnum(*(bp_ - 1))) 
578                                                         &&   (((bp_ + 1) < ep_)    && isalnum(*(bp_ + 1))) ) {
579                                                                 /* hyphens are allowed, but only in between alnums */
580                                                         }
581                                                         else if ( !ispunct(*bp_) ) {
582                                                                 /* but anything not being a punctiation is ok */
583                                                         }
584                                                         else {
585                                                                 break; /* anything else is rejected */
586                                                         }
587                                                 }
588                                         }
589                                 }
590                         }
591                         
592                         bp_++;
593
594 #undef PEEK_STACK
595 #undef PUSH_STACK
596 #undef POP_STACK
597 #undef IN_STACK
598 #undef FULL_STACK
599
600                         /* scan forward (should start with an alnum) */
601                         for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
602                                 ;
603
604                         *ep = ep_;
605                         *bp = bp_;
606                 }
607         }
608         return result;
609 }
610
611 #undef IS_RFC822_CHAR
612
613 static gchar *make_email_string(const gchar *bp, const gchar *ep)
614 {
615         /* returns a mailto: URI; mailto: is also used to detect the
616          * uri type later on in the button_pressed signal handler */
617         gchar *tmp;
618         gchar *result;
619
620         tmp = g_strndup(bp, ep - bp);
621         result = g_strconcat("mailto:", tmp, NULL);
622         g_free(tmp);
623
624         return result;
625 }
626
627 #define ADD_TXT_POS(bp_, ep_, pti_) \
628         if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
629                 last = last->next; \
630                 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
631                 last->next = NULL; \
632         } else { \
633                 g_warning("alloc error scanning URIs\n"); \
634                 gtk_text_insert(text, textview->msgfont, fg_color, NULL, \
635                                 linebuf, -1); \
636                 return; \
637         }
638
639 /* textview_make_clickable_parts() - colorizes clickable parts */
640 static void textview_make_clickable_parts(TextView *textview, GtkText *text,
641                                           GdkFont  *textfont,
642                                           GdkColor *fg_color,
643                                           GdkColor *uri_color,
644                                           const gchar *linebuf)
645 {
646         /* parse table - in order of priority */
647         struct table {
648                 const gchar *needle; /* token */
649
650                 /* token search function */
651                 gchar    *(*search)     (const gchar *haystack,
652                                          const gchar *needle);
653                 /* part parsing function */
654                 gboolean  (*parse)      (const gchar *start,
655                                          const gchar *scanpos,
656                                          const gchar **bp_,
657                                          const gchar **ep_);
658                 /* part to URI function */
659                 gchar    *(*build_uri)  (const gchar *bp,
660                                          const gchar *ep);
661         };
662
663         static struct table parser[] = {
664                 {"http://",  strcasestr, get_uri_part,   make_uri_string},
665                 {"https://", strcasestr, get_uri_part,   make_uri_string},
666                 {"ftp://",   strcasestr, get_uri_part,   make_uri_string},
667                 {"mailto:",  strcasestr, get_uri_part,   make_uri_string},
668                 {"@",        strcasestr, get_email_part, make_email_string}
669         };
670         const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
671
672         gint  n;
673         const gchar *walk, *bp, *ep;
674
675         struct txtpos {
676                 const gchar     *bp, *ep;       /* text position */
677                 gint             pti;           /* index in parse table */
678                 struct txtpos   *next;          /* next */
679         } head = {NULL, NULL, 0,  NULL}, *last = &head;
680
681         /* parse for clickable parts, and build a list of begin and end positions  */
682         for (walk = linebuf, n = 0;;) {
683                 gint last_index = PARSE_ELEMS;
684                 gchar *scanpos = NULL;
685
686                 /* FIXME: this looks phony. scanning for anything in the parse table */
687                 for (n = 0; n < PARSE_ELEMS; n++) {
688                         gchar *tmp;
689
690                         tmp = parser[n].search(walk, parser[n].needle);
691                         if (tmp) {
692                                 if (scanpos == NULL || tmp < scanpos) {
693                                         scanpos = tmp;
694                                         last_index = n;
695                                 }
696                         }                                       
697                 }
698
699                 if (scanpos) {
700                         /* check if URI can be parsed */
701                         if (parser[last_index].parse(linebuf, scanpos, &bp, &ep)
702                             && (ep - bp - 1) > strlen(parser[last_index].needle)) {
703                                         ADD_TXT_POS(bp, ep, last_index);
704                                         walk = ep;
705                         } else
706                                 walk = scanpos +
707                                         strlen(parser[last_index].needle);
708                 } else
709                         break;
710         }
711
712         /* colorize this line */
713         if (head.next) {
714                 const gchar *normal_text = linebuf;
715
716                 /* insert URIs */
717                 for (last = head.next; last != NULL;
718                      normal_text = last->ep, last = last->next) {
719                         RemoteURI *uri;
720
721                         uri = g_new(RemoteURI, 1);
722                         if (last->bp - normal_text > 0)
723                                 gtk_text_insert(text, textfont,
724                                                 fg_color, NULL,
725                                                 normal_text,
726                                                 last->bp - normal_text);
727                         uri->uri = parser[last->pti].build_uri(last->bp, last->ep);
728                         uri->start = gtk_text_get_point(text);
729                         gtk_text_insert(text, textfont, uri_color,
730                                         NULL, last->bp, last->ep - last->bp);
731                         uri->end = gtk_text_get_point(text);
732                         textview->uri_list =
733                                 g_slist_append(textview->uri_list, uri);
734                 }
735
736                 if (*normal_text)
737                         gtk_text_insert(text, textfont, fg_color,
738                                         NULL, normal_text, -1);
739         } else
740                 gtk_text_insert(text, textfont, fg_color, NULL,
741                                 linebuf, -1);
742 }
743
744 #undef ADD_TXT_POS
745
746 static void textview_write_line(TextView *textview, const gchar *str,
747                                 CodeConverter *conv)
748 {
749         GtkText *text = GTK_TEXT(textview->text);
750         gchar buf[BUFFSIZE];
751         size_t len;
752         GdkColor *fg_color;
753         gint quotelevel = -1;
754
755         if (!conv)
756                 strncpy2(buf, str, sizeof(buf));
757         else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
758                 gtk_text_insert(text, textview->msgfont,
759                                 prefs_common.enable_color
760                                 ? &error_color : NULL, NULL,
761                                 "*** Warning: code conversion failed ***\n",
762                                 -1);
763                 return;
764         }
765
766         len = strlen(buf);
767         if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
768                 buf[len - 2] = '\n';
769                 buf[len - 1] = '\0';
770         }
771         if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
772         fg_color = NULL;
773
774         /* change color of quotation
775            >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
776            Up to 3 levels of quotations are detected, and each
777            level is colored using a different color. */
778         if (prefs_common.enable_color && strchr(buf, '>')) {
779                 quotelevel = get_quote_level(buf);
780
781                 /* set up the correct foreground color */
782                 if (quotelevel > 2) {
783                         /* recycle colors */
784                         if (prefs_common.recycle_quote_colors)
785                                 quotelevel %= 3;
786                         else
787                                 quotelevel = 2;
788                 }
789         }
790
791         if (quotelevel == -1)
792                 fg_color = NULL;
793         else
794                 fg_color = &quote_colors[quotelevel];
795
796         if (prefs_common.head_space && spacingfont && buf[0] != '\n')
797                 gtk_text_insert(text, spacingfont, NULL, NULL, " ", 1);
798
799         if (prefs_common.enable_color)
800                 textview_make_clickable_parts(textview, text,
801                                                   textview->msgfont,
802                                               fg_color, &uri_color, buf);
803         else
804                 gtk_text_insert(text, textview->msgfont, fg_color, NULL,
805                                 buf, -1);
806 }
807
808 void textview_clear(TextView *textview)
809 {
810         GtkText *text = GTK_TEXT(textview->text);
811
812         gtk_text_freeze(text);
813         gtk_text_backward_delete(text, gtk_text_get_length(text));
814         gtk_text_thaw(text);
815
816         textview_uri_list_remove_all(textview->uri_list);
817         textview->uri_list = NULL;
818 }
819
820 void textview_destroy(TextView *textview)
821 {
822         textview_uri_list_remove_all(textview->uri_list);
823         textview->uri_list = NULL;
824
825         if (!textview->scrolledwin_sb->parent)
826                 gtk_widget_destroy(textview->scrolledwin_sb);
827         if (!textview->scrolledwin_mb->parent)
828                 gtk_widget_destroy(textview->scrolledwin_mb);
829
830         if (textview->msgfont) {
831                 textview->msgfont->ascent = textview->prev_ascent;
832                 textview->msgfont->descent = textview->prev_descent;
833                 gdk_font_unref(textview->msgfont);
834         }
835         if (textview->boldfont)
836                 gdk_font_unref(textview->boldfont);
837
838         g_free(textview);
839 }
840
841 void textview_set_font(TextView *textview, const gchar *codeset)
842 {
843         gboolean use_fontset = TRUE;
844
845         /* In multi-byte mode, GtkText can't display 8bit characters
846            correctly, so it must be single-byte mode. */
847         if (MB_CUR_MAX > 1) {
848                 if (codeset) {
849                         if (!strncasecmp(codeset, "ISO-8859-", 9) ||
850                             !strncasecmp(codeset, "KOI8-", 5)     ||
851                             !strncasecmp(codeset, "CP", 2)        ||
852                             !strncasecmp(codeset, "WINDOWS-", 8)  ||
853                             !strcasecmp(codeset, "BALTIC"))
854                                 use_fontset = FALSE;
855                 }
856         } else
857                 use_fontset = FALSE;
858
859         if (textview->text_is_mb && !use_fontset) {
860                 GtkWidget *parent;
861
862                 parent = textview->scrolledwin_mb->parent;
863                 gtk_editable_select_region
864                         (GTK_EDITABLE(textview->text_mb), 0, 0);
865                 gtk_container_remove(GTK_CONTAINER(parent),
866                                      textview->scrolledwin_mb);
867                 gtk_container_add(GTK_CONTAINER(parent),
868                                   textview->scrolledwin_sb);
869
870                 textview->text = textview->text_sb;
871                 textview->text_is_mb = FALSE;
872         } else if (!textview->text_is_mb && use_fontset) {
873                 GtkWidget *parent;
874
875                 parent = textview->scrolledwin_sb->parent;
876                 gtk_editable_select_region
877                         (GTK_EDITABLE(textview->text_sb), 0, 0);
878                 gtk_container_remove(GTK_CONTAINER(parent),
879                                      textview->scrolledwin_sb);
880                 gtk_container_add(GTK_CONTAINER(parent),
881                                   textview->scrolledwin_mb);
882
883                 textview->text = textview->text_mb;
884                 textview->text_is_mb = TRUE;
885         }
886
887         if (prefs_common.textfont) {
888                 if (textview->msgfont) {
889                         textview->msgfont->ascent = textview->prev_ascent;
890                         textview->msgfont->descent = textview->prev_descent;
891                         gdk_font_unref(textview->msgfont);
892                         textview->msgfont = NULL;
893                 }
894                 if (use_fontset)
895                         textview->msgfont =
896                                 gdk_fontset_load(prefs_common.textfont);
897                 else {
898                         if (MB_CUR_MAX > 1) {
899                                 FONT_LOAD(textview->msgfont,
900                                           "-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
901                         } else {
902                                 FONT_LOAD(textview->msgfont,
903                                           prefs_common.textfont);
904                         }
905                 }
906
907                 if (textview->msgfont) {
908                         gint ascent, descent;
909
910                         textview->prev_ascent = textview->msgfont->ascent;
911                         textview->prev_descent = textview->msgfont->descent;
912                         descent = prefs_common.line_space / 2;
913                         ascent  = prefs_common.line_space - descent;
914                         textview->msgfont->ascent  += ascent;
915                         textview->msgfont->descent += descent;
916                 }
917         }
918
919         if (!textview->boldfont)
920                 FONT_LOAD(textview->boldfont, BOLD_FONT);
921         if (!spacingfont)
922                 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
923 }
924
925 enum
926 {
927         H_DATE          = 0,
928         H_FROM          = 1,
929         H_TO            = 2,
930         H_NEWSGROUPS    = 3,
931         H_SUBJECT       = 4,
932         H_CC            = 5,
933         H_REPLY_TO      = 6,
934         H_FOLLOWUP_TO   = 7,
935         H_X_MAILER      = 8,
936         H_X_NEWSREADER  = 9,
937         H_USER_AGENT    = 10,
938         H_ORGANIZATION  = 11,
939 };
940
941 static gboolean hdrequal(char * hdr1, char * hdr2)
942 {
943         int len1;
944         int len2;
945
946         len1 = strlen(hdr1);
947         len2 = strlen(hdr2);
948         if (hdr1[len1 - 1] == ':')
949                 len1--;
950         if (hdr2[len2 - 1] == ':')
951                 len2--;
952         if (len1 != len2)
953                 return 0;
954         return (strncasecmp(hdr1, hdr2, len1) == 0);
955 }
956
957 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
958 {
959         /*
960         static HeaderEntry hentry[] = {{"Date:",         NULL, FALSE},
961                                        {"From:",         NULL, TRUE},
962                                        {"To:",           NULL, FALSE},
963                                        {"Newsgroups:",   NULL, FALSE},
964                                        {"Subject:",      NULL, TRUE},
965                                        {"Cc:",           NULL, FALSE},
966                                        {"Reply-To:",     NULL, FALSE},
967                                        {"Followup-To:",  NULL, FALSE},
968                                        {"X-Mailer:",     NULL, TRUE},
969                                        {"X-Newsreader:", NULL, TRUE},
970                                        {"User-Agent:",   NULL, TRUE},
971                                        {"Organization:", NULL, TRUE},
972                                        {NULL,            NULL, FALSE}};
973         */
974         gchar buf[BUFFSIZE], tmp[BUFFSIZE];
975         gint hnum;
976         HeaderEntry *hp;
977         GPtrArray *headers;
978         GSList * l;
979
980         int i;
981         GPtrArray *sorted_headers;
982
983         g_return_val_if_fail(fp != NULL, NULL);
984
985         if (!prefs_common.display_header) {
986                 while (fgets(buf, sizeof(buf), fp) != NULL)
987                         if (buf[0] == '\r' || buf[0] == '\n') break;
988                 return NULL;
989         }
990
991         headers = g_ptr_array_new();
992
993         /*
994         while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry))
995                != -1) {
996                 Header *header;
997
998                 hp = hentry + hnum;
999
1000                 header = g_new(Header, 1);
1001                 header->name = g_strndup(buf, strlen(hp->name));
1002                 conv_unmime_header(tmp, sizeof(tmp), buf + strlen(hp->name),
1003                                    NULL);
1004                 header->body = g_strdup(tmp);
1005
1006                 g_ptr_array_add(headers, header);
1007         }
1008         */
1009         //      while (procheader_get_unfolded_line(buf, sizeof(buf), fp) != NULL) {
1010         while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
1011                 gchar * p;
1012                 Header *header;
1013
1014                 if (*buf == ':') continue;
1015                 for (p = buf; *p && *p != ' '; p++) {
1016                         if (*p == ':') {
1017                                 header = g_new(Header, 1);
1018                                 header->name = g_strndup(buf, p - buf + 1);
1019                                 p++;
1020                                 /* while (*p == ' ' || *p == '\t') p++; */
1021                                 conv_unmime_header(tmp, sizeof(tmp), p, NULL);
1022                                 header->body = g_strdup(tmp);
1023
1024                                 g_ptr_array_add(headers, header);
1025                                 break;
1026                         }
1027                 }
1028         }
1029
1030         sorted_headers = g_ptr_array_new();
1031         for(l = prefs_display_headers.headers_list ; l != NULL ;
1032             l = g_slist_next(l)) {
1033                 HeaderDisplayProp * dp = (HeaderDisplayProp *) l->data;
1034                 for(i = 0 ; i < headers->len ; i++) {
1035                         Header * header = g_ptr_array_index(headers, i);
1036                         if (hdrequal(header->name, dp->name)) {
1037                                 if (dp->hidden) {
1038                                         g_ptr_array_remove_index(headers, i);
1039                                         g_free(header->body);
1040                                         g_free(header->name);
1041                                         g_free(header);
1042                                         i--;
1043                                 }
1044                                 else {
1045                                         g_ptr_array_add(sorted_headers,
1046                                                         header);
1047                                         g_ptr_array_remove_index(headers, i);
1048                                         i--;
1049                                 }
1050                         }
1051                 }
1052         }
1053
1054         if (prefs_display_headers.show_other_headers) {
1055                 for(i = 0 ; i < headers->len ; i++) {
1056                         Header * header = g_ptr_array_index(headers, i);
1057                         g_ptr_array_add(sorted_headers, header);
1058                 }
1059         }
1060
1061         g_ptr_array_free(headers, TRUE);
1062         
1063         return sorted_headers;
1064 }
1065
1066 static void textview_show_header(TextView *textview, GPtrArray *headers)
1067 {
1068         GtkText *text = GTK_TEXT(textview->text);
1069         Header *header;
1070         gint i;
1071
1072         g_return_if_fail(headers != NULL);
1073
1074         gtk_text_freeze(text);
1075
1076         for (i = 0; i < headers->len; i++) {
1077                 header = g_ptr_array_index(headers, i);
1078                 g_return_if_fail(header->name != NULL);
1079
1080                 gtk_text_insert(text, textview->boldfont, NULL, NULL,
1081                                 header->name, -1);
1082                 if (prefs_common.enable_color &&
1083                     (strncmp(header->name, "X-Mailer", 8) == 0 ||
1084                      strncmp(header->name, "X-Newsreader", 12) == 0) &&
1085                     strstr(header->body, "Sylpheed") != NULL)
1086                         gtk_text_insert(text, NULL, &emphasis_color, NULL,
1087                                         header->body, -1);
1088                 else {
1089                         if (prefs_common.enable_color) {
1090                                 textview_make_clickable_parts(textview, text,
1091                                                                                           NULL,
1092                                                                                           NULL, 
1093                                                                                           &uri_color, 
1094                                                                                           header->body);
1095                         }                                                                                         
1096                         else {                                                                                    
1097                                 gtk_text_insert(text, NULL, NULL, NULL,
1098                                                 header->body, -1);
1099                         }                                               
1100                 }                                       
1101                 gtk_text_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1102         }
1103
1104         gtk_text_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1105         gtk_text_thaw(text);
1106 }
1107
1108 void textview_scroll_one_line(TextView *textview, gboolean up)
1109 {
1110         GtkText *text = GTK_TEXT(textview->text);
1111         gfloat upper;
1112
1113         if (prefs_common.enable_smooth_scroll) {
1114                 textview_smooth_scroll_one_line(textview, up);
1115                 return;
1116         }
1117
1118         if (!up) {
1119                 upper = text->vadj->upper - text->vadj->page_size;
1120                 if (text->vadj->value < upper) {
1121                         text->vadj->value +=
1122                                 text->vadj->step_increment * 4;
1123                         text->vadj->value =
1124                                 MIN(text->vadj->value, upper);
1125                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1126                                                 "value_changed");
1127                 }
1128         } else {
1129                 if (text->vadj->value > 0.0) {
1130                         text->vadj->value -=
1131                                 text->vadj->step_increment * 4;
1132                         text->vadj->value =
1133                                 MAX(text->vadj->value, 0.0);
1134                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1135                                                 "value_changed");
1136                 }
1137         }
1138 }
1139
1140 gboolean textview_scroll_page(TextView *textview, gboolean up)
1141 {
1142         GtkText *text = GTK_TEXT(textview->text);
1143         gfloat upper;
1144         gfloat page_incr;
1145
1146         if (prefs_common.enable_smooth_scroll)
1147                 return textview_smooth_scroll_page(textview, up);
1148
1149         if (prefs_common.scroll_halfpage)
1150                 page_incr = text->vadj->page_increment / 2;
1151         else
1152                 page_incr = text->vadj->page_increment;
1153
1154         if (!up) {
1155                 upper = text->vadj->upper - text->vadj->page_size;
1156                 if (text->vadj->value < upper) {
1157                         text->vadj->value += page_incr;
1158                         text->vadj->value = MIN(text->vadj->value, upper);
1159                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1160                                                 "value_changed");
1161                 } else
1162                         return FALSE;
1163         } else {
1164                 if (text->vadj->value > 0.0) {
1165                         text->vadj->value -= page_incr;
1166                         text->vadj->value = MAX(text->vadj->value, 0.0);
1167                         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1168                                                 "value_changed");
1169                 } else
1170                         return FALSE;
1171         }
1172
1173         return TRUE;
1174 }
1175
1176 static void textview_smooth_scroll_do(TextView *textview,
1177                                       gfloat old_value, gfloat last_value,
1178                                       gint step)
1179 {
1180         GtkText *text = GTK_TEXT(textview->text);
1181         gint change_value;
1182         gboolean up;
1183         gint i;
1184
1185         if (old_value < last_value) {
1186                 change_value = last_value - old_value;
1187                 up = FALSE;
1188         } else {
1189                 change_value = old_value - last_value;
1190                 up = TRUE;
1191         }
1192
1193         gdk_key_repeat_disable();
1194
1195         for (i = step; i <= change_value; i += step) {
1196                 text->vadj->value = old_value + (up ? -i : i);
1197                 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1198                                         "value_changed");
1199         }
1200
1201         text->vadj->value = last_value;
1202         gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1203
1204         gdk_key_repeat_restore();
1205 }
1206
1207 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1208 {
1209         GtkText *text = GTK_TEXT(textview->text);
1210         gfloat upper;
1211         gfloat old_value;
1212         gfloat last_value;
1213
1214         if (!up) {
1215                 upper = text->vadj->upper - text->vadj->page_size;
1216                 if (text->vadj->value < upper) {
1217                         old_value = text->vadj->value;
1218                         last_value = text->vadj->value +
1219                                 text->vadj->step_increment * 4;
1220                         last_value = MIN(last_value, upper);
1221
1222                         textview_smooth_scroll_do(textview, old_value,
1223                                                   last_value,
1224                                                   prefs_common.scroll_step);
1225                 }
1226         } else {
1227                 if (text->vadj->value > 0.0) {
1228                         old_value = text->vadj->value;
1229                         last_value = text->vadj->value -
1230                                 text->vadj->step_increment * 4;
1231                         last_value = MAX(last_value, 0.0);
1232
1233                         textview_smooth_scroll_do(textview, old_value,
1234                                                   last_value,
1235                                                   prefs_common.scroll_step);
1236                 }
1237         }
1238 }
1239
1240 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1241 {
1242         GtkText *text = GTK_TEXT(textview->text);
1243         gfloat upper;
1244         gfloat page_incr;
1245         gfloat old_value;
1246         gfloat last_value;
1247
1248         if (prefs_common.scroll_halfpage)
1249                 page_incr = text->vadj->page_increment / 2;
1250         else
1251                 page_incr = text->vadj->page_increment;
1252
1253         if (!up) {
1254                 upper = text->vadj->upper - text->vadj->page_size;
1255                 if (text->vadj->value < upper) {
1256                         old_value = text->vadj->value;
1257                         last_value = text->vadj->value + page_incr;
1258                         last_value = MIN(last_value, upper);
1259
1260                         textview_smooth_scroll_do(textview, old_value,
1261                                                   last_value,
1262                                                   prefs_common.scroll_step);
1263                 } else
1264                         return FALSE;
1265         } else {
1266                 if (text->vadj->value > 0.0) {
1267                         old_value = text->vadj->value;
1268                         last_value = text->vadj->value - page_incr;
1269                         last_value = MAX(last_value, 0.0);
1270
1271                         textview_smooth_scroll_do(textview, old_value,
1272                                                   last_value,
1273                                                   prefs_common.scroll_step);
1274                 } else
1275                         return FALSE;
1276         }
1277
1278         return TRUE;
1279 }
1280
1281 static void textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1282                                  TextView *textview)
1283 {
1284         SummaryView *summaryview = NULL;
1285
1286         if (!event) return;
1287         if (textview->messageview->mainwin)
1288                 summaryview = textview->messageview->mainwin->summaryview;
1289
1290         switch (event->keyval) {
1291         case GDK_Tab:
1292         case GDK_Home:
1293         case GDK_Left:
1294         case GDK_Up:
1295         case GDK_Right:
1296         case GDK_Down:
1297         case GDK_Page_Up:
1298         case GDK_Page_Down:
1299         case GDK_End:
1300         case GDK_Control_L:
1301         case GDK_Control_R:
1302                 break;
1303         case GDK_space:
1304                 if (summaryview)
1305                         summary_pass_key_press_event(summaryview, event);
1306                 else
1307                         textview_scroll_page(textview, FALSE);
1308                 break;
1309         case GDK_BackSpace:
1310         case GDK_Delete:
1311                 textview_scroll_page(textview, TRUE);
1312                 break;
1313         case GDK_Return:
1314                 textview_scroll_one_line(textview,
1315                                          (event->state & GDK_MOD1_MASK) != 0);
1316                 break;
1317         default:
1318                 if (summaryview)
1319                         summary_pass_key_press_event(summaryview, event);
1320                 break;
1321         }
1322 }
1323
1324 static void textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1325                                     TextView *textview)
1326 {
1327         if (event &&
1328             ((event->button == 1 && event->type == GDK_2BUTTON_PRESS) 
1329                 || event->button == 2
1330                 || event->button == 3)) {
1331                 GSList *cur;
1332                 guint current_pos;
1333
1334                 current_pos = GTK_EDITABLE(textview->text)->current_pos;
1335
1336                 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1337                         RemoteURI *uri = (RemoteURI *)cur->data;
1338
1339                         if (current_pos >= uri->start &&
1340                             current_pos <  uri->end) {
1341                                 if (!strncasecmp(uri->uri, "mailto:", 7)) {
1342                                         compose_new_with_recipient
1343                                                 (NULL, uri->uri + 7);
1344                                 }                                               
1345                                 else
1346                                         open_uri(uri->uri,
1347                                                  prefs_common.uri_cmd);
1348                         }                               
1349                 }
1350         }
1351 }
1352
1353 static void textview_uri_list_remove_all(GSList *uri_list)
1354 {
1355         GSList *cur;
1356
1357         for (cur = uri_list; cur != NULL; cur = cur->next) {
1358                 if (cur->data) {
1359                         g_free(((RemoteURI *)cur->data)->uri);
1360                         g_free(cur->data);
1361                 }
1362         }
1363
1364         g_slist_free(uri_list);
1365 }