2006-08-02 [colin] 2.4.0cvs17
[claws.git] / src / textview.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Sylpheed-Claws team
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 <glib/gi18n.h>
28 #include <gdk/gdk.h>
29 #include <gdk/gdkkeysyms.h>
30 #include <gtk/gtkvbox.h>
31 #include <gtk/gtkscrolledwindow.h>
32 #include <gtk/gtksignal.h>
33 #include <stdio.h>
34 #include <ctype.h>
35 #include <string.h>
36 #include <stdlib.h>
37 #include <errno.h>
38 #include <sys/wait.h>
39 #if HAVE_LIBCOMPFACE
40 #  include <compface.h>
41 #endif
42
43 #if HAVE_LIBCOMPFACE
44 #define XPM_XFACE_HEIGHT        (HEIGHT + 3)  /* 3 = 1 header + 2 colors */
45 #endif
46
47 #include "main.h"
48 #include "summaryview.h"
49 #include "procheader.h"
50 #include "prefs_common.h"
51 #include "codeconv.h"
52 #include "utils.h"
53 #include "gtkutils.h"
54 #include "procmime.h"
55 #include "html.h"
56 #include "enriched.h"
57 #include "compose.h"
58 #include "addressbook.h"
59 #include "displayheader.h"
60 #include "account.h"
61 #include "mimeview.h"
62 #include "alertpanel.h"
63 #include "menu.h"
64 #include "image_viewer.h"
65 #include "filesel.h"
66 #include "base64.h"
67
68 struct _ClickableText
69 {
70         gchar *uri;
71
72         gchar *filename;
73
74         gpointer data;
75
76         guint start;
77         guint end;
78         
79         gboolean is_quote;
80         gint quote_level;
81         gboolean q_expanded;
82         gchar *fg_color;
83 };
84
85 gint previousquotelevel = -1;
86
87 static GdkColor quote_colors[3] = {
88         {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
89         {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
90         {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
91 };
92
93 static GdkColor quote_bgcolors[3] = {
94         {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
95         {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
96         {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
97 };
98 static GdkColor signature_color = {
99         (gulong)0,
100         (gushort)0x7fff,
101         (gushort)0x7fff,
102         (gushort)0x7fff
103 };
104         
105 static GdkColor uri_color = {
106         (gulong)0,
107         (gushort)0,
108         (gushort)0,
109         (gushort)0
110 };
111
112 static GdkColor emphasis_color = {
113         (gulong)0,
114         (gushort)0,
115         (gushort)0,
116         (gushort)0xcfff
117 };
118
119 static GdkCursor *hand_cursor = NULL;
120 static GdkCursor *text_cursor = NULL;
121 static GdkCursor *watch_cursor= NULL;
122
123 #define TEXTVIEW_STATUSBAR_PUSH(textview, str)                              \
124 {       if (textview->messageview->statusbar)                               \
125         gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
126                            textview->messageview->statusbar_cid, str);      \
127 }
128
129 #define TEXTVIEW_STATUSBAR_POP(textview)                                   \
130 {       if (textview->messageview->statusbar)                              \
131         gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
132                           textview->messageview->statusbar_cid);           \
133 }
134
135 static void textview_show_ertf          (TextView       *textview,
136                                          FILE           *fp,
137                                          CodeConverter  *conv);
138 static void textview_add_part           (TextView       *textview,
139                                          MimeInfo       *mimeinfo);
140 static void textview_add_parts          (TextView       *textview,
141                                          MimeInfo       *mimeinfo);
142 static void textview_write_body         (TextView       *textview,
143                                          MimeInfo       *mimeinfo);
144 static void textview_show_html          (TextView       *textview,
145                                          FILE           *fp,
146                                          CodeConverter  *conv);
147
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
156 static GPtrArray *textview_scan_header  (TextView       *textview,
157                                          FILE           *fp);
158 static void textview_show_header        (TextView       *textview,
159                                          GPtrArray      *headers);
160
161 static gint textview_key_pressed                (GtkWidget      *widget,
162                                                  GdkEventKey    *event,
163                                                  TextView       *textview);
164 static gboolean textview_motion_notify          (GtkWidget      *widget,
165                                                  GdkEventMotion *motion,
166                                                  TextView       *textview);
167 static gboolean textview_leave_notify           (GtkWidget        *widget,
168                                                  GdkEventCrossing *event,
169                                                  TextView         *textview);
170 static gboolean textview_visibility_notify      (GtkWidget      *widget,
171                                                  GdkEventVisibility *event,
172                                                  TextView       *textview);
173 static void textview_uri_update                 (TextView       *textview,
174                                                  gint           x,
175                                                  gint           y);
176 static gboolean textview_get_uri_range          (TextView       *textview,
177                                                  GtkTextIter    *iter,
178                                                  GtkTextTag     *tag,
179                                                  GtkTextIter    *start_iter,
180                                                  GtkTextIter    *end_iter);
181 static ClickableText *textview_get_uri_from_range       (TextView       *textview,
182                                                  GtkTextIter    *iter,
183                                                  GtkTextTag     *tag,
184                                                  GtkTextIter    *start_iter,
185                                                  GtkTextIter    *end_iter);
186 static ClickableText *textview_get_uri          (TextView       *textview,
187                                                  GtkTextIter    *iter,
188                                                  GtkTextTag     *tag);
189 static gboolean textview_uri_button_pressed     (GtkTextTag     *tag,
190                                                  GObject        *obj,
191                                                  GdkEvent       *event,
192                                                  GtkTextIter    *iter,
193                                                  TextView       *textview);
194
195 static void textview_smooth_scroll_do           (TextView       *textview,
196                                                  gfloat          old_value,
197                                                  gfloat          last_value,
198                                                  gint            step);
199 static void textview_smooth_scroll_one_line     (TextView       *textview,
200                                                  gboolean        up);
201 static gboolean textview_smooth_scroll_page     (TextView       *textview,
202                                                  gboolean        up);
203
204 static gboolean textview_uri_security_check     (TextView       *textview,
205                                                  ClickableText  *uri);
206 static void textview_uri_list_remove_all        (GSList         *uri_list);
207
208 static void textview_toggle_quote               (TextView *textview, ClickableText *uri);
209
210 static void open_uri_cb                         (TextView       *textview,
211                                                  guint           action,
212                                                  void           *data);
213 static void copy_uri_cb                         (TextView       *textview,
214                                                  guint           action,
215                                                  void           *data);
216 static void add_uri_to_addrbook_cb              (TextView       *textview, 
217                                                  guint           action, 
218                                                  void           *data);
219 static void mail_to_uri_cb                      (TextView       *textview, 
220                                                  guint           action, 
221                                                  void           *data);
222 static void copy_mail_to_uri_cb                 (TextView       *textview,
223                                                  guint           action,
224                                                  void           *data);
225 static void save_file_cb                        (TextView       *textview,
226                                                  guint           action,
227                                                  void           *data);
228 static void open_image_cb                       (TextView       *textview,
229                                                  guint           action,
230                                                  void           *data);
231 static void textview_show_icon(TextView *textview, const gchar *stock_id);
232
233 static GtkItemFactoryEntry textview_link_popup_entries[] = 
234 {
235         {N_("/_Open with Web browser"), NULL, open_uri_cb, 0, NULL},
236         {N_("/Copy this _link"),        NULL, copy_uri_cb, 0, NULL},
237 };
238
239 static GtkItemFactoryEntry textview_mail_popup_entries[] = 
240 {
241         {N_("/Compose _new message"),   NULL, mail_to_uri_cb, 0, NULL},
242         {N_("/Add to _address book"),   NULL, add_uri_to_addrbook_cb, 0, NULL},
243         {N_("/Copy this add_ress"),     NULL, copy_mail_to_uri_cb, 0, NULL},
244 };
245
246 static GtkItemFactoryEntry textview_file_popup_entries[] = 
247 {
248         {N_("/_Open image"),            NULL, open_image_cb, 0, NULL},
249         {N_("/_Save image..."),         NULL, save_file_cb, 0, NULL},
250 };
251
252 static void scrolled_cb (GtkAdjustment *adj, TextView *textview)
253 {
254 #ifndef WIDTH
255 #  define WIDTH 48
256 #  define HEIGHT 48
257 #endif
258         if (textview->image) {
259                 gint x, y, x1;
260                 x1 = textview->text->allocation.width - WIDTH - 5;
261                 gtk_text_view_buffer_to_window_coords(
262                         GTK_TEXT_VIEW(textview->text),
263                         GTK_TEXT_WINDOW_TEXT, x1, 5, &x, &y);
264                 gtk_text_view_move_child(GTK_TEXT_VIEW(textview->text), 
265                         textview->image, x1, y);
266         }
267 }
268
269 static void textview_size_allocate_cb   (GtkWidget      *widget,
270                                          GtkAllocation  *allocation,
271                                          gpointer        data)
272 {
273         scrolled_cb(NULL, (TextView *)data);
274 }
275
276 TextView *textview_create(void)
277 {
278         TextView *textview;
279         GtkWidget *vbox;
280         GtkWidget *scrolledwin;
281         GtkWidget *text;
282         GtkTextBuffer *buffer;
283         GtkClipboard *clipboard;
284         GtkItemFactory *link_popupfactory, *mail_popupfactory, *file_popupfactory;
285         GtkWidget *link_popupmenu, *mail_popupmenu, *file_popupmenu;
286         GtkAdjustment *adj;
287         gint n_entries;
288
289         debug_print("Creating text view...\n");
290         textview = g_new0(TextView, 1);
291
292         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
293         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
294                                        GTK_POLICY_AUTOMATIC,
295                                        GTK_POLICY_AUTOMATIC);
296         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
297                                             GTK_SHADOW_IN);
298         gtk_widget_set_size_request
299                 (scrolledwin, prefs_common.mainview_width, -1);
300
301         /* create GtkSText widgets for single-byte and multi-byte character */
302         text = gtk_text_view_new();
303         gtk_widget_add_events(text, GDK_LEAVE_NOTIFY_MASK);
304         gtk_widget_show(text);
305         gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
306         gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
307         gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
308         gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
309         gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
310
311         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
312         clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
313         gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
314
315         gtk_widget_ensure_style(text);
316
317         gtk_widget_ref(scrolledwin);
318
319         gtk_container_add(GTK_CONTAINER(scrolledwin), text);
320
321         g_signal_connect(G_OBJECT(text), "key-press-event",
322                          G_CALLBACK(textview_key_pressed), textview);
323         g_signal_connect(G_OBJECT(text), "motion-notify-event",
324                          G_CALLBACK(textview_motion_notify), textview);
325         g_signal_connect(G_OBJECT(text), "leave-notify-event",
326                          G_CALLBACK(textview_leave_notify), textview);
327         g_signal_connect(G_OBJECT(text), "visibility-notify-event",
328                          G_CALLBACK(textview_visibility_notify), textview);
329         adj = gtk_scrolled_window_get_vadjustment(
330                 GTK_SCROLLED_WINDOW(scrolledwin));
331         g_signal_connect(G_OBJECT(adj), "value-changed",
332                          G_CALLBACK(scrolled_cb), textview);
333         g_signal_connect(G_OBJECT(text), "size_allocate",
334                          G_CALLBACK(textview_size_allocate_cb),
335                          textview);
336
337
338         gtk_widget_show(scrolledwin);
339
340         vbox = gtk_vbox_new(FALSE, 0);
341         gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
342
343         gtk_widget_show(vbox);
344
345         n_entries = sizeof(textview_link_popup_entries) /
346                 sizeof(textview_link_popup_entries[0]);
347         link_popupmenu = menu_create_items(textview_link_popup_entries, n_entries,
348                                       "<UriPopupMenu>", &link_popupfactory,
349                                       textview);
350
351         n_entries = sizeof(textview_mail_popup_entries) /
352                 sizeof(textview_mail_popup_entries[0]);
353         mail_popupmenu = menu_create_items(textview_mail_popup_entries, n_entries,
354                                       "<UriPopupMenu>", &mail_popupfactory,
355                                       textview);
356
357         n_entries = sizeof(textview_file_popup_entries) /
358                 sizeof(textview_file_popup_entries[0]);
359         file_popupmenu = menu_create_items(textview_file_popup_entries, n_entries,
360                                       "<FilePopupMenu>", &file_popupfactory,
361                                       textview);
362
363         textview->vbox               = vbox;
364         textview->scrolledwin        = scrolledwin;
365         textview->text               = text;
366         textview->uri_list           = NULL;
367         textview->body_pos           = 0;
368         textview->show_all_headers   = FALSE;
369         textview->last_buttonpress   = GDK_NOTHING;
370         textview->link_popup_menu    = link_popupmenu;
371         textview->link_popup_factory = link_popupfactory;
372         textview->mail_popup_menu    = mail_popupmenu;
373         textview->mail_popup_factory = mail_popupfactory;
374         textview->file_popup_menu    = file_popupmenu;
375         textview->file_popup_factory = file_popupfactory;
376         textview->image              = NULL;
377         return textview;
378 }
379
380 static void textview_create_tags(GtkTextView *text, TextView *textview)
381 {
382         GtkTextBuffer *buffer;
383         GtkTextTag *tag, *qtag;
384         static PangoFontDescription *font_desc, *bold_font_desc;
385         
386         if (!font_desc)
387                 font_desc = pango_font_description_from_string
388                         (NORMAL_FONT);
389
390         if (!bold_font_desc) {
391                 bold_font_desc = pango_font_description_from_string
392                         (NORMAL_FONT);
393                 pango_font_description_set_weight
394                         (bold_font_desc, PANGO_WEIGHT_BOLD);
395         }
396
397         buffer = gtk_text_view_get_buffer(text);
398
399         gtk_text_buffer_create_tag(buffer, "header",
400                                    "pixels-above-lines", 0,
401                                    "pixels-above-lines-set", TRUE,
402                                    "pixels-below-lines", 0,
403                                    "pixels-below-lines-set", TRUE,
404                                    "font-desc", font_desc,
405                                    "left-margin", 3,
406                                    "left-margin-set", TRUE,
407                                    NULL);
408         gtk_text_buffer_create_tag(buffer, "header_title",
409                                    "font-desc", bold_font_desc,
410                                    NULL);
411         if (prefs_common.enable_bgcolor) {
412                 gtk_text_buffer_create_tag(buffer, "quote0",
413                                 "foreground-gdk", &quote_colors[0],
414                                 "paragraph-background-gdk", &quote_bgcolors[0],
415                                 NULL);
416                 gtk_text_buffer_create_tag(buffer, "quote1",
417                                 "foreground-gdk", &quote_colors[1],
418                                 "paragraph-background-gdk", &quote_bgcolors[1],
419                                 NULL);
420                 gtk_text_buffer_create_tag(buffer, "quote2",
421                                 "foreground-gdk", &quote_colors[2],
422                                 "paragraph-background-gdk", &quote_bgcolors[2],
423                                 NULL);
424         } else {
425                 gtk_text_buffer_create_tag(buffer, "quote0",
426                                 "foreground-gdk", &quote_colors[0],
427                                 NULL);
428                 gtk_text_buffer_create_tag(buffer, "quote1",
429                                 "foreground-gdk", &quote_colors[1],
430                                 NULL);
431                 gtk_text_buffer_create_tag(buffer, "quote2",
432                                 "foreground-gdk", &quote_colors[2],
433                                 NULL);
434         }
435         gtk_text_buffer_create_tag(buffer, "emphasis",
436                         "foreground-gdk", &emphasis_color,
437                         NULL);
438         gtk_text_buffer_create_tag(buffer, "signature",
439                         "foreground-gdk", &signature_color,
440                         NULL);
441         tag = gtk_text_buffer_create_tag(buffer, "link",
442                         "foreground-gdk", &uri_color,
443                         NULL);
444         qtag = gtk_text_buffer_create_tag(buffer, "qlink",
445                         NULL);
446         gtk_text_buffer_create_tag(buffer, "link-hover",
447                         "underline", PANGO_UNDERLINE_SINGLE,
448                         NULL);
449         g_signal_connect(G_OBJECT(qtag), "event",
450                          G_CALLBACK(textview_uri_button_pressed), textview);
451         g_signal_connect(G_OBJECT(tag), "event",
452                          G_CALLBACK(textview_uri_button_pressed), textview);
453  }
454
455 void textview_init(TextView *textview)
456 {
457         if (!hand_cursor)
458                 hand_cursor = gdk_cursor_new(GDK_HAND2);
459         if (!text_cursor)
460                 text_cursor = gdk_cursor_new(GDK_XTERM);
461         if (!watch_cursor)
462                 watch_cursor = gdk_cursor_new(GDK_WATCH);
463
464         textview_reflect_prefs(textview);
465         textview_set_all_headers(textview, FALSE);
466         textview_set_font(textview, NULL);
467         textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
468 }
469
470 #if GTK_CHECK_VERSION(2, 8, 0)
471  #define CHANGE_TAG_COLOR(tagname, colorfg, colorbg) { \
472         tag = gtk_text_tag_table_lookup(tags, tagname); \
473         if (tag) \
474                 g_object_set(G_OBJECT(tag), "foreground-gdk", colorfg, "paragraph-background-gdk", colorbg, NULL); \
475  }
476 #else
477  #define CHANGE_TAG_COLOR(tagname, colorfg, colorbg) { \
478         tag = gtk_text_tag_table_lookup(tags, tagname); \
479         if (tag) \
480                 g_object_set(G_OBJECT(tag), "foreground-gdk", colorfg, NULL); \
481  }
482 #endif
483
484 static void textview_update_message_colors(TextView *textview)
485 {
486         GdkColor black = {0, 0, 0, 0};
487         GdkColor colored_emphasis = {0, 0, 0, 0xcfff};
488         GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
489
490         GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
491         GtkTextTag *tag = NULL;
492
493         quote_bgcolors[0] = quote_bgcolors[1] = quote_bgcolors[2] = black;
494         quote_colors[0] = quote_colors[1] = quote_colors[2] = 
495                 uri_color = emphasis_color = signature_color = black;
496
497         if (prefs_common.enable_color) {
498                 /* grab the quote colors, converting from an int to a GdkColor */
499                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
500                                                &quote_colors[0]);
501                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
502                                                &quote_colors[1]);
503                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
504                                                &quote_colors[2]);
505                 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
506                                                &uri_color);
507                 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
508                                                &signature_color);
509                 emphasis_color = colored_emphasis;
510         }
511         if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
512                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
513                                                    &quote_bgcolors[0]);
514                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
515                                                    &quote_bgcolors[1]);
516                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
517                                                    &quote_bgcolors[2]);
518                 CHANGE_TAG_COLOR("quote0", &quote_colors[0], &quote_bgcolors[0]);
519                 CHANGE_TAG_COLOR("quote1", &quote_colors[1], &quote_bgcolors[1]);
520                 CHANGE_TAG_COLOR("quote2", &quote_colors[2], &quote_bgcolors[2]);
521         } else {
522                 CHANGE_TAG_COLOR("quote0", &quote_colors[0], NULL);
523                 CHANGE_TAG_COLOR("quote1", &quote_colors[1], NULL);
524                 CHANGE_TAG_COLOR("quote2", &quote_colors[2], NULL);
525         }
526
527         CHANGE_TAG_COLOR("emphasis", &emphasis_color, NULL);
528         CHANGE_TAG_COLOR("signature", &signature_color, NULL);
529         CHANGE_TAG_COLOR("link", &uri_color, NULL);
530         CHANGE_TAG_COLOR("link-hover", &uri_color, NULL);
531 }
532 #undef CHANGE_TAG_COLOR
533
534 void textview_reflect_prefs(TextView *textview)
535 {
536         textview_set_font(textview, NULL);
537         textview_update_message_colors(textview);
538         gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(textview->text),
539                                          prefs_common.textview_cursor_visible);
540 }
541
542 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
543                            const gchar *file)
544 {
545         textview_clear(textview);
546
547         textview_add_parts(textview, mimeinfo);
548
549         textview_set_position(textview, 0);
550 }
551
552 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
553 {
554         g_return_if_fail(mimeinfo != NULL);
555         g_return_if_fail(fp != NULL);
556
557         if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
558             ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822"))) {
559                 textview_clear(textview);
560                 textview_add_parts(textview, mimeinfo);
561                 return;
562         }
563
564         if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
565                 perror("fseek");
566
567         textview_clear(textview);
568
569         if (mimeinfo->type == MIMETYPE_MULTIPART)
570                 textview_add_parts(textview, mimeinfo);
571         else
572                 textview_write_body(textview, mimeinfo);
573 }
574
575 #define TEXT_INSERT(str) \
576         gtk_text_buffer_insert_with_tags_by_name \
577                                 (buffer, &iter, str, -1,\
578                                  "header", NULL)
579
580 #define TEXT_INSERT_LINK(str, fname, udata) {                           \
581         ClickableText *uri;                                                     \
582         uri = g_new0(ClickableText, 1);                                 \
583         uri->uri = g_strdup("");                                        \
584         uri->start = gtk_text_iter_get_offset(&iter);                   \
585         gtk_text_buffer_insert_with_tags_by_name                        \
586                                 (buffer, &iter, str, -1,                \
587                                  "link", "header_title", "header",      \
588                                  NULL);                                 \
589         uri->end = gtk_text_iter_get_offset(&iter);                     \
590         uri->filename = fname?g_strdup(fname):NULL;                     \
591         uri->data = udata;                                              \
592         textview->uri_list =                                            \
593                 g_slist_append(textview->uri_list, uri);                \
594 }
595
596 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
597 {
598         GtkTextView *text;
599         GtkTextBuffer *buffer;
600         GtkTextIter iter, start_iter;
601         gchar buf[BUFFSIZE];
602         GPtrArray *headers = NULL;
603         const gchar *name;
604         gchar *content_type;
605         gint charcount;
606
607         g_return_if_fail(mimeinfo != NULL);
608         text = GTK_TEXT_VIEW(textview->text);
609         buffer = gtk_text_view_get_buffer(text);
610         charcount = gtk_text_buffer_get_char_count(buffer);
611         gtk_text_buffer_get_end_iter(buffer, &iter);
612
613         if (mimeinfo->type == MIMETYPE_MULTIPART) return;
614
615         if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
616                 FILE *fp;
617
618                 fp = g_fopen(mimeinfo->data.filename, "rb");
619                 fseek(fp, mimeinfo->offset, SEEK_SET);
620                 headers = textview_scan_header(textview, fp);
621                 if (headers) {
622                         if (charcount > 0)
623                                 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
624                         textview_show_header(textview, headers);
625                         procheader_header_array_destroy(headers);
626                 }
627                 fclose(fp);
628                 return;
629         }
630
631         name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
632         content_type = procmime_get_content_type_str(mimeinfo->type,
633                                                      mimeinfo->subtype);
634         if (name == NULL)
635                 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
636         if (name != NULL)
637                 g_snprintf(buf, sizeof(buf), _("[%s  %s (%d bytes)]"),
638                            name, content_type, mimeinfo->length);
639         else
640                 g_snprintf(buf, sizeof(buf), _("[%s (%d bytes)]"),
641                            content_type, mimeinfo->length);
642
643         g_free(content_type);                      
644
645         if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT
646         || (mimeinfo->disposition == DISPOSITIONTYPE_INLINE && 
647             mimeinfo->type != MIMETYPE_TEXT)) {
648                 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
649                 TEXT_INSERT_LINK(buf, "sc://select_attachment", mimeinfo);
650                 gtk_text_buffer_insert(buffer, &iter, " \n", -1);
651                 if (mimeinfo->type == MIMETYPE_IMAGE  &&
652                     prefs_common.inline_img ) {
653                         GdkPixbuf *pixbuf;
654                         GError *error = NULL;
655                         gchar *filename;
656                         ClickableText *uri;
657                         gchar *uri_str;
658
659                         filename = procmime_get_tmp_file_name(mimeinfo);
660                         if (procmime_get_part(filename, mimeinfo) < 0) {
661                                 g_warning("Can't get the image file.");
662                                 g_free(filename);
663                                 return;
664                         }
665
666                         pixbuf = gdk_pixbuf_new_from_file(filename, &error);
667                         if (error != NULL) {
668                                 g_warning("%s\n", error->message);
669                                 g_error_free(error);
670                         }
671                         if (!pixbuf) {
672                                 g_warning("Can't load the image.");
673                                 g_free(filename);
674                                 return;
675                         }
676
677                         if (prefs_common.resize_img) {
678                                 int new_width, new_height;
679                                 GdkPixbuf *scaled;
680                                 image_viewer_get_resized_size(gdk_pixbuf_get_width(pixbuf),
681                                                  gdk_pixbuf_get_height(pixbuf),
682                                                  textview->scrolledwin->allocation.width - 100, 
683                                                  gdk_pixbuf_get_height(pixbuf),
684                                                  &new_width, &new_height);
685                                 scaled = gdk_pixbuf_scale_simple
686                                         (pixbuf, new_width, new_height, GDK_INTERP_BILINEAR);
687
688                                 g_object_unref(pixbuf);
689                                 pixbuf = scaled;
690                         }
691
692                         uri_str = g_filename_to_uri(filename, NULL, NULL);
693                         if (uri_str) {
694                                 uri = g_new0(ClickableText, 1);
695                                 uri->uri = uri_str;
696                                 uri->start = gtk_text_iter_get_offset(&iter);
697                                 
698                                 gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf);
699                                 
700                                 uri->end = uri->start + 1;
701                                 uri->filename = procmime_get_part_file_name(mimeinfo);
702                                 textview->uri_list =
703                                         g_slist_append(textview->uri_list, uri);
704                                 
705                                 gtk_text_buffer_insert(buffer, &iter, " ", 1);
706                                 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, uri->start);    
707                                 gtk_text_buffer_apply_tag_by_name(buffer, "link", 
708                                                 &start_iter, &iter);
709                         } else {
710                                 gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf);
711                                 gtk_text_buffer_insert(buffer, &iter, " ", 1);
712                         }
713
714                         g_object_unref(pixbuf);
715                         g_free(filename);
716                 }
717         } else if (mimeinfo->type == MIMETYPE_TEXT) {
718                 if (prefs_common.display_header && (charcount > 0))
719                         gtk_text_buffer_insert(buffer, &iter, "\n", 1);
720
721                 textview_write_body(textview, mimeinfo);
722         }
723 }
724
725 static void recursive_add_parts(TextView *textview, GNode *node)
726 {
727         GNode * iter;
728         MimeInfo *mimeinfo;
729         
730         mimeinfo = (MimeInfo *) node->data;
731         
732         textview_add_part(textview, mimeinfo);
733         
734         if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
735             (mimeinfo->type != MIMETYPE_MESSAGE))
736                 return;
737         
738         if (g_ascii_strcasecmp(mimeinfo->subtype, "alternative") == 0) {
739                 GNode * prefered_body;
740                 int prefered_score;
741                 
742                 /*
743                   text/plain : score 3
744                   text/ *    : score 2
745                   other      : score 1
746                 */
747                 prefered_body = NULL;
748                 prefered_score = 0;
749                 
750                 for (iter = g_node_first_child(node) ; iter != NULL ;
751                      iter = g_node_next_sibling(iter)) {
752                         int score;
753                         MimeInfo * submime;
754                         
755                         score = 1;
756                         submime = (MimeInfo *) iter->data;
757                         if (submime->type == MIMETYPE_TEXT)
758                                 score = 2;
759                         
760                         if (submime->subtype != NULL) {
761                                 if (g_ascii_strcasecmp(submime->subtype, "plain") == 0)
762                                         score = 3;
763                         }
764                         
765                         if (score > prefered_score) {
766                                 prefered_score = score;
767                                 prefered_body = iter;
768                         }
769                 }
770                 
771                 if (prefered_body != NULL) {
772                         recursive_add_parts(textview, prefered_body);
773                 }
774         }
775         else {
776                 for (iter = g_node_first_child(node) ; iter != NULL ;
777                      iter = g_node_next_sibling(iter)) {
778                         recursive_add_parts(textview, iter);
779                 }
780         }
781 }
782
783 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
784 {
785         g_return_if_fail(mimeinfo != NULL);
786         
787         recursive_add_parts(textview, mimeinfo->node);
788 }
789
790 void textview_show_error(TextView *textview)
791 {
792         GtkTextView *text;
793         GtkTextBuffer *buffer;
794         GtkTextIter iter;
795
796         textview_set_font(textview, NULL);
797         textview_clear(textview);
798
799         text = GTK_TEXT_VIEW(textview->text);
800         buffer = gtk_text_view_get_buffer(text);
801         gtk_text_buffer_get_start_iter(buffer, &iter);
802
803         TEXT_INSERT(_("\n"
804                       "  This message can't be displayed.\n"
805                       "  This is probably due to a network error.\n"
806                       "\n"
807                       "  Use "));
808         TEXT_INSERT_LINK(_("'View Log'"), "sc://view_log", NULL);
809         TEXT_INSERT(_(" in the Tools menu for more information."));
810         textview_show_icon(textview, GTK_STOCK_DIALOG_ERROR);
811
812 }
813
814 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
815 {
816         GtkTextView *text;
817         GtkTextBuffer *buffer;
818         GtkTextIter iter;
819
820         if (!partinfo) return;
821
822         textview_set_font(textview, NULL);
823         textview_clear(textview);
824
825         text = GTK_TEXT_VIEW(textview->text);
826         buffer = gtk_text_view_get_buffer(text);
827         gtk_text_buffer_get_start_iter(buffer, &iter);
828
829         TEXT_INSERT("\n");
830         TEXT_INSERT(_("  The following can be performed on this part by\n"));
831         TEXT_INSERT(_("  right-clicking the icon or list item:\n"));
832
833         TEXT_INSERT(_("     - To save, select "));
834         TEXT_INSERT_LINK(_("'Save as...'"), "sc://save_as", NULL);
835         TEXT_INSERT(_(" (Shortcut key: 'y')\n"));
836         TEXT_INSERT(_("     - To display as text, select "));
837         TEXT_INSERT_LINK(_("'Display as text'"), "sc://display_as_text", NULL);
838         TEXT_INSERT(_(" (Shortcut key: 't')\n"));
839         TEXT_INSERT(_("     - To open with an external program, select "));
840         TEXT_INSERT_LINK(_("'Open'"), "sc://open", NULL);
841         TEXT_INSERT(_(" (Shortcut key: 'l')\n"));
842         TEXT_INSERT(_("       (alternately double-click, or click the middle "));
843         TEXT_INSERT(_("mouse button)\n"));
844         TEXT_INSERT(_("     - Or use "));
845         TEXT_INSERT_LINK(_("'Open with...'"), "sc://open_with", NULL);
846         TEXT_INSERT(_(" (Shortcut key: 'o')\n"));
847         textview_show_icon(textview, GTK_STOCK_DIALOG_INFO);
848 }
849
850 #undef TEXT_INSERT
851 #undef TEXT_INSERT_LINK
852
853 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
854 {
855         FILE *tmpfp;
856         gchar buf[BUFFSIZE];
857         CodeConverter *conv;
858         const gchar *charset, *p, *cmd;
859         GSList *cur;
860         
861         if (textview->messageview->forced_charset)
862                 charset = textview->messageview->forced_charset;
863         else
864                 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
865
866         textview_set_font(textview, charset);
867
868         conv = conv_code_converter_new(charset);
869
870         procmime_force_encoding(textview->messageview->forced_encoding);
871         
872         textview->is_in_signature = FALSE;
873
874         procmime_decode_content(mimeinfo);
875
876         if (!g_ascii_strcasecmp(mimeinfo->subtype, "html") &&
877             prefs_common.render_html) {
878                 gchar *filename;
879                 
880                 filename = procmime_get_tmp_file_name(mimeinfo);
881                 if (procmime_get_part(filename, mimeinfo) == 0) {
882                         tmpfp = g_fopen(filename, "rb");
883                         textview_show_html(textview, tmpfp, conv);
884                         fclose(tmpfp);
885                         g_unlink(filename);
886                 }
887                 g_free(filename);
888         } else if (!g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
889                 gchar *filename;
890                 
891                 filename = procmime_get_tmp_file_name(mimeinfo);
892                 if (procmime_get_part(filename, mimeinfo) == 0) {
893                         tmpfp = g_fopen(filename, "rb");
894                         textview_show_ertf(textview, tmpfp, conv);
895                         fclose(tmpfp);
896                         g_unlink(filename);
897                 }
898                 g_free(filename);
899         } else if ( g_ascii_strcasecmp(mimeinfo->subtype, "plain") &&
900                    (cmd = prefs_common.mime_textviewer) && *cmd &&
901                    (p = strchr(cmd, '%')) && *(p + 1) == 's') {
902                 int pid, pfd[2];
903                 const gchar *fname;
904
905                 fname  = procmime_get_tmp_file_name(mimeinfo);
906                 if (procmime_get_part(fname, mimeinfo)) goto textview_default;
907
908                 g_snprintf(buf, sizeof(buf), cmd, fname);
909                 debug_print("Viewing text content of type: %s (length: %d) "
910                         "using %s\n", mimeinfo->subtype, mimeinfo->length, buf);
911
912                 if (pipe(pfd) < 0) {
913                         g_snprintf(buf, sizeof(buf),
914                                 "pipe failed for textview\n\n%s\n", strerror(errno));
915                         textview_write_line(textview, buf, conv);
916                         goto textview_default;
917                 }
918                 pid = fork();
919                 if (pid < 0) {
920                         g_snprintf(buf, sizeof(buf),
921                                 "fork failed for textview\n\n%s\n", strerror(errno));
922                         textview_write_line(textview, buf, conv);
923                         close(pfd[0]);
924                         close(pfd[1]);
925                         goto textview_default;
926                 }
927                 if (pid == 0) { /* child */
928                         int rc;
929                         gchar **argv;
930                         argv = strsplit_with_quote(buf, " ", 0);
931                         close(1);
932                         close(pfd[0]);
933                         dup(pfd[1]);
934                         rc = execvp(argv[0], argv);
935                         close(pfd[1]);
936                         printf (_("The command to view attachment "
937                                 "as text failed:\n"
938                                 "    %s\n"
939                                 "Exit code %d\n"), buf, rc);
940                         exit(255);
941                 }
942                 close(pfd[1]);
943                 tmpfp = fdopen(pfd[0], "rb");
944                 while (fgets(buf, sizeof(buf), tmpfp))
945                         textview_write_line(textview, buf, conv);
946                 fclose(tmpfp);
947                 waitpid(pid, pfd, 0);
948                 unlink(fname);
949         } else {
950 textview_default:
951                 tmpfp = g_fopen(mimeinfo->data.filename, "rb");
952                 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
953                 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
954                 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) && 
955                        (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
956                         textview_write_line(textview, buf, conv);
957                 fclose(tmpfp);
958         }
959
960         conv_code_converter_destroy(conv);
961         procmime_force_encoding(0);
962
963         for (cur = textview->uri_list; cur; cur = cur->next) {
964                 ClickableText *uri = (ClickableText *)cur->data;
965                 if (!uri->is_quote)
966                         continue;
967                 if (!prefs_common.hide_quotes ||
968                     uri->quote_level+1 < prefs_common.hide_quotes) {
969                         textview_toggle_quote(textview, uri);
970                 }
971         }
972 }
973
974 static void textview_show_html(TextView *textview, FILE *fp,
975                                CodeConverter *conv)
976 {
977         SC_HTMLParser *parser;
978         gchar *str;
979
980         parser = sc_html_parser_new(fp, conv);
981         g_return_if_fail(parser != NULL);
982
983         while ((str = sc_html_parse(parser)) != NULL) {
984                 if (parser->state == SC_HTML_HREF) {
985                         /* first time : get and copy the URL */
986                         if (parser->href == NULL) {
987                                 /* ALF - the sylpheed html parser returns an empty string,
988                                  * if still inside an <a>, but already parsed past HREF */
989                                 str = strtok(str, " ");
990                                 if (str) {
991                                         while (str && *str && g_ascii_isspace(*str))
992                                                 str++; 
993                                         parser->href = g_strdup(str);
994                                         /* the URL may (or not) be followed by the
995                                          * referenced text */
996                                         str = strtok(NULL, "");
997                                 }       
998                         }
999                         if (str != NULL)
1000                                 textview_write_link(textview, str, parser->href, NULL);
1001                 } else
1002                         textview_write_line(textview, str, NULL);
1003         }
1004         textview_write_line(textview, "\n", NULL);
1005         sc_html_parser_destroy(parser);
1006 }
1007
1008 static void textview_show_ertf(TextView *textview, FILE *fp,
1009                                CodeConverter *conv)
1010 {
1011         ERTFParser *parser;
1012         gchar *str;
1013
1014         parser = ertf_parser_new(fp, conv);
1015         g_return_if_fail(parser != NULL);
1016
1017         while ((str = ertf_parse(parser)) != NULL) {
1018                 textview_write_line(textview, str, NULL);
1019         }
1020         
1021         ertf_parser_destroy(parser);
1022 }
1023
1024 #define ADD_TXT_POS(bp_, ep_, pti_) \
1025         if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1026                 last = last->next; \
1027                 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1028                 last->next = NULL; \
1029         } else { \
1030                 g_warning("alloc error scanning URIs\n"); \
1031                 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1032                                                          linebuf, -1, \
1033                                                          fg_tag, NULL); \
1034                 return; \
1035         }
1036
1037 #define ADD_TXT_POS_LATER(bp_, ep_, pti_) \
1038         if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1039                 last = last->next; \
1040                 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1041                 last->next = NULL; \
1042         } else { \
1043                 g_warning("alloc error scanning URIs\n"); \
1044         }
1045
1046 /* textview_make_clickable_parts() - colorizes clickable parts */
1047 static void textview_make_clickable_parts(TextView *textview,
1048                                           const gchar *fg_tag,
1049                                           const gchar *uri_tag,
1050                                           const gchar *linebuf,
1051                                           gboolean hdr)
1052 {
1053         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1054         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1055         GtkTextIter iter;
1056         gchar *mybuf = g_strdup(linebuf);
1057         
1058         /* parse table - in order of priority */
1059         struct table {
1060                 const gchar *needle; /* token */
1061
1062                 /* token search function */
1063                 gchar    *(*search)     (const gchar *haystack,
1064                                          const gchar *needle);
1065                 /* part parsing function */
1066                 gboolean  (*parse)      (const gchar *start,
1067                                          const gchar *scanpos,
1068                                          const gchar **bp_,
1069                                          const gchar **ep_,
1070                                          gboolean hdr);
1071                 /* part to URI function */
1072                 gchar    *(*build_uri)  (const gchar *bp,
1073                                          const gchar *ep);
1074         };
1075
1076         static struct table parser[] = {
1077                 {"http://",  strcasestr, get_uri_part,   make_uri_string},
1078                 {"https://", strcasestr, get_uri_part,   make_uri_string},
1079                 {"ftp://",   strcasestr, get_uri_part,   make_uri_string},
1080                 {"sftp://",  strcasestr, get_uri_part,   make_uri_string},
1081                 {"www.",     strcasestr, get_uri_part,   make_http_string},
1082                 {"mailto:",  strcasestr, get_uri_part,   make_uri_string},
1083                 {"@",        strcasestr, get_email_part, make_email_string}
1084         };
1085         const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1086
1087         gint  n;
1088         const gchar *walk, *bp, *ep;
1089
1090         struct txtpos {
1091                 const gchar     *bp, *ep;       /* text position */
1092                 gint             pti;           /* index in parse table */
1093                 struct txtpos   *next;          /* next */
1094         } head = {NULL, NULL, 0,  NULL}, *last = &head;
1095
1096         if (!g_utf8_validate(linebuf, -1, NULL)) {
1097                 mybuf = g_malloc(strlen(linebuf)*2 +1);
1098                 conv_localetodisp(mybuf, strlen(linebuf)*2 +1, linebuf);
1099         }
1100
1101         gtk_text_buffer_get_end_iter(buffer, &iter);
1102
1103         /* parse for clickable parts, and build a list of begin and end positions  */
1104         for (walk = mybuf, n = 0;;) {
1105                 gint last_index = PARSE_ELEMS;
1106                 gchar *scanpos = NULL;
1107
1108                 /* FIXME: this looks phony. scanning for anything in the parse table */
1109                 for (n = 0; n < PARSE_ELEMS; n++) {
1110                         gchar *tmp;
1111
1112                         tmp = parser[n].search(walk, parser[n].needle);
1113                         if (tmp) {
1114                                 if (scanpos == NULL || tmp < scanpos) {
1115                                         scanpos = tmp;
1116                                         last_index = n;
1117                                 }
1118                         }                                       
1119                 }
1120
1121                 if (scanpos) {
1122                         /* check if URI can be parsed */
1123                         if (parser[last_index].parse(walk, scanpos, &bp, &ep, hdr)
1124                             && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1125                                         ADD_TXT_POS(bp, ep, last_index);
1126                                         walk = ep;
1127                         } else
1128                                 walk = scanpos +
1129                                         strlen(parser[last_index].needle);
1130                 } else
1131                         break;
1132         }
1133
1134         /* colorize this line */
1135         if (head.next) {
1136                 const gchar *normal_text = mybuf;
1137
1138                 /* insert URIs */
1139                 for (last = head.next; last != NULL;
1140                      normal_text = last->ep, last = last->next) {
1141                         ClickableText *uri;
1142                         uri = g_new0(ClickableText, 1);
1143                         if (last->bp - normal_text > 0)
1144                                 gtk_text_buffer_insert_with_tags_by_name
1145                                         (buffer, &iter,
1146                                          normal_text,
1147                                          last->bp - normal_text,
1148                                          fg_tag, NULL);
1149                         uri->uri = parser[last->pti].build_uri(last->bp,
1150                                                                last->ep);
1151                         uri->start = gtk_text_iter_get_offset(&iter);
1152                         gtk_text_buffer_insert_with_tags_by_name
1153                                 (buffer, &iter, last->bp, last->ep - last->bp,
1154                                  uri_tag, fg_tag, NULL);
1155                         uri->end = gtk_text_iter_get_offset(&iter);
1156                         uri->filename = NULL;
1157                         textview->uri_list =
1158                                 g_slist_append(textview->uri_list, uri);
1159                 }
1160
1161                 if (*normal_text)
1162                         gtk_text_buffer_insert_with_tags_by_name
1163                                 (buffer, &iter, normal_text, -1, fg_tag, NULL);
1164         } else {
1165                 gtk_text_buffer_insert_with_tags_by_name
1166                         (buffer, &iter, mybuf, -1, fg_tag, NULL);
1167         }
1168         g_free(mybuf);
1169 }
1170
1171 /* textview_make_clickable_parts() - colorizes clickable parts */
1172 static void textview_make_clickable_parts_later(TextView *textview,
1173                                           gint start, gint end)
1174 {
1175         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1176         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1177         GtkTextIter start_iter, end_iter;
1178         gchar *mybuf;
1179         gint offset = 0;
1180         /* parse table - in order of priority */
1181         struct table {
1182                 const gchar *needle; /* token */
1183
1184                 /* token search function */
1185                 gchar    *(*search)     (const gchar *haystack,
1186                                          const gchar *needle);
1187                 /* part parsing function */
1188                 gboolean  (*parse)      (const gchar *start,
1189                                          const gchar *scanpos,
1190                                          const gchar **bp_,
1191                                          const gchar **ep_,
1192                                          gboolean hdr);
1193                 /* part to URI function */
1194                 gchar    *(*build_uri)  (const gchar *bp,
1195                                          const gchar *ep);
1196         };
1197
1198         static struct table parser[] = {
1199                 {"http://",  strcasestr, get_uri_part,   make_uri_string},
1200                 {"https://", strcasestr, get_uri_part,   make_uri_string},
1201                 {"ftp://",   strcasestr, get_uri_part,   make_uri_string},
1202                 {"sftp://",  strcasestr, get_uri_part,   make_uri_string},
1203                 {"www.",     strcasestr, get_uri_part,   make_http_string},
1204                 {"mailto:",  strcasestr, get_uri_part,   make_uri_string},
1205                 {"@",        strcasestr, get_email_part, make_email_string}
1206         };
1207         const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1208
1209         gint  n;
1210         const gchar *walk, *bp, *ep;
1211
1212         struct txtpos {
1213                 const gchar     *bp, *ep;       /* text position */
1214                 gint             pti;           /* index in parse table */
1215                 struct txtpos   *next;          /* next */
1216         } head = {NULL, NULL, 0,  NULL}, *last = &head;
1217
1218         gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
1219         gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
1220         mybuf = gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, FALSE);
1221         offset = gtk_text_iter_get_offset(&start_iter);
1222
1223         /* parse for clickable parts, and build a list of begin and end positions  */
1224         for (walk = mybuf, n = 0;;) {
1225                 gint last_index = PARSE_ELEMS;
1226                 gchar *scanpos = NULL;
1227
1228                 /* FIXME: this looks phony. scanning for anything in the parse table */
1229                 for (n = 0; n < PARSE_ELEMS; n++) {
1230                         gchar *tmp;
1231
1232                         tmp = parser[n].search(walk, parser[n].needle);
1233                         if (tmp) {
1234                                 if (scanpos == NULL || tmp < scanpos) {
1235                                         scanpos = tmp;
1236                                         last_index = n;
1237                                 }
1238                         }                                       
1239                 }
1240
1241                 if (scanpos) {
1242                         /* check if URI can be parsed */
1243                         if (parser[last_index].parse(walk, scanpos, &bp, &ep, FALSE)
1244                             && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1245                                         ADD_TXT_POS_LATER(bp, ep, last_index);
1246                                         walk = ep;
1247                         } else
1248                                 walk = scanpos +
1249                                         strlen(parser[last_index].needle);
1250                 } else
1251                         break;
1252         }
1253
1254         /* colorize this line */
1255         if (head.next) {
1256                 const gchar *normal_text = mybuf;
1257
1258                 /* insert URIs */
1259                 for (last = head.next; last != NULL;
1260                      normal_text = last->ep, last = last->next) {
1261                         ClickableText *uri;
1262                         uri = g_new0(ClickableText, 1);
1263                         uri->uri = parser[last->pti].build_uri(last->bp,
1264                                                                last->ep);
1265                                                                
1266                         gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, last->bp - mybuf + offset);
1267                         gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, last->ep - mybuf + offset);
1268                         
1269                         uri->start = gtk_text_iter_get_offset(&start_iter);
1270                         
1271                         gtk_text_buffer_apply_tag_by_name(buffer, "link", &start_iter, &end_iter);
1272
1273                         uri->end = gtk_text_iter_get_offset(&end_iter);
1274                         uri->filename = NULL;
1275                         textview->uri_list =
1276                                 g_slist_append(textview->uri_list, uri);
1277                 }
1278         } 
1279
1280         g_free(mybuf);
1281 }
1282
1283 #undef ADD_TXT_POS
1284
1285 static void textview_write_line(TextView *textview, const gchar *str,
1286                                 CodeConverter *conv)
1287 {
1288         GtkTextView *text;
1289         GtkTextBuffer *buffer;
1290         GtkTextIter iter;
1291         gchar buf[BUFFSIZE];
1292         gchar *fg_color;
1293         gint quotelevel = -1;
1294         gchar quote_tag_str[10];
1295
1296         text = GTK_TEXT_VIEW(textview->text);
1297         buffer = gtk_text_view_get_buffer(text);
1298         gtk_text_buffer_get_end_iter(buffer, &iter);
1299
1300         if (!conv)
1301                 strncpy2(buf, str, sizeof(buf));
1302         else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1303                 conv_localetodisp(buf, sizeof(buf), str);
1304                 
1305         strcrchomp(buf);
1306         fg_color = NULL;
1307
1308         /* change color of quotation
1309            >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1310            Up to 3 levels of quotations are detected, and each
1311            level is colored using a different color. */
1312         if (prefs_common.enable_color 
1313             && line_has_quote_char(buf, prefs_common.quote_chars)) {
1314                 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1315
1316                 /* set up the correct foreground color */
1317                 if (quotelevel > 2) {
1318                         /* recycle colors */
1319                         if (prefs_common.recycle_quote_colors)
1320                                 quotelevel %= 3;
1321                         else
1322                                 quotelevel = 2;
1323                 }
1324         }
1325
1326         if (quotelevel == -1)
1327                 fg_color = NULL;
1328         else {
1329                 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1330                            "quote%d", quotelevel);
1331                 fg_color = quote_tag_str;
1332         }
1333
1334         if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1335                 fg_color = "signature";
1336                 textview->is_in_signature = TRUE;
1337         }
1338
1339         if (quotelevel > -1) {
1340                 if ( previousquotelevel != quotelevel ) {
1341                         ClickableText *uri;
1342                         uri = g_new0(ClickableText, 1);
1343                         uri->uri = g_strdup("");
1344                         uri->data = g_strdup(buf);
1345                         uri->start = gtk_text_iter_get_offset(&iter);
1346                         gtk_text_buffer_insert_with_tags_by_name
1347                                                 (buffer, &iter, " [...]", -1,
1348                                                  "qlink", fg_color, NULL);
1349                         uri->end = gtk_text_iter_get_offset(&iter);
1350                         uri->filename = NULL;
1351                         uri->fg_color = g_strdup(fg_color);
1352                         uri->is_quote = TRUE;
1353                         uri->quote_level = quotelevel;
1354                         textview->uri_list =
1355                                 g_slist_append(textview->uri_list, uri);
1356                         gtk_text_buffer_insert(buffer, &iter, "  \n", -1);
1357                 
1358                         previousquotelevel = quotelevel;
1359                 } else {
1360                         GSList *last = g_slist_last(textview->uri_list);
1361                         ClickableText *lasturi = (ClickableText *)last->data;
1362                         gchar *tmp = g_strdup_printf("%s%s", (gchar *)lasturi->data, buf);
1363                         g_free(lasturi->data);
1364                         lasturi->data = tmp;
1365                 }
1366         } else {
1367                 textview_make_clickable_parts(textview, fg_color, "link", buf, FALSE);
1368                 previousquotelevel = -1;
1369         }
1370 }
1371
1372 void textview_write_link(TextView *textview, const gchar *str,
1373                          const gchar *uri, CodeConverter *conv)
1374 {
1375         GdkColor *link_color = NULL;
1376         GtkTextView *text;
1377         GtkTextBuffer *buffer;
1378         GtkTextIter iter;
1379         gchar buf[BUFFSIZE];
1380         gchar *bufp;
1381         ClickableText *r_uri;
1382
1383         if (!str || *str == '\0')
1384                 return;
1385         if (!uri)
1386                 return;
1387
1388         while (uri && *uri && g_ascii_isspace(*uri))
1389                 uri++;
1390                 
1391         text = GTK_TEXT_VIEW(textview->text);
1392         buffer = gtk_text_view_get_buffer(text);
1393         gtk_text_buffer_get_end_iter(buffer, &iter);
1394
1395         if (!conv)
1396                 strncpy2(buf, str, sizeof(buf));
1397         else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1398                 conv_utf8todisp(buf, sizeof(buf), str);
1399
1400         if (g_utf8_validate(buf, -1, NULL) == FALSE)
1401                 return;
1402
1403         strcrchomp(buf);
1404
1405         gtk_text_buffer_get_end_iter(buffer, &iter);
1406         for (bufp = buf; *bufp != '\0'; bufp = g_utf8_next_char(bufp)) {
1407                 gunichar ch;
1408
1409                 ch = g_utf8_get_char(bufp);
1410                 if (!g_unichar_isspace(ch))
1411                         break;
1412         }
1413         if (bufp > buf)
1414                 gtk_text_buffer_insert(buffer, &iter, buf, bufp - buf);
1415
1416         if (prefs_common.enable_color) {
1417                 link_color = &uri_color;
1418         }
1419         r_uri = g_new0(ClickableText, 1);
1420         r_uri->uri = g_strdup(uri);
1421         r_uri->start = gtk_text_iter_get_offset(&iter);
1422         gtk_text_buffer_insert_with_tags_by_name
1423                 (buffer, &iter, bufp, -1, "link", NULL);
1424         r_uri->end = gtk_text_iter_get_offset(&iter);
1425         r_uri->filename = NULL;
1426         textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1427 }
1428
1429 static void textview_set_cursor(GdkWindow *window, GdkCursor *cursor)
1430 {
1431         if (GDK_IS_WINDOW(window))
1432                 gdk_window_set_cursor(window, cursor);
1433 }
1434 void textview_clear(TextView *textview)
1435 {
1436         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1437         GtkTextBuffer *buffer;
1438         GdkWindow *window = gtk_text_view_get_window(text,
1439                                 GTK_TEXT_WINDOW_TEXT);
1440
1441         buffer = gtk_text_view_get_buffer(text);
1442         gtk_text_buffer_set_text(buffer, "", -1);
1443
1444         TEXTVIEW_STATUSBAR_POP(textview);
1445         textview_uri_list_remove_all(textview->uri_list);
1446         textview->uri_list = NULL;
1447         textview->uri_hover = NULL;
1448
1449         textview->body_pos = 0;
1450         if (textview->image) 
1451                 gtk_widget_destroy(textview->image);
1452         textview->image = NULL;
1453
1454         if (textview->messageview->mainwin->cursor_count == 0) {
1455                 textview_set_cursor(window, text_cursor);
1456         } else {
1457                 textview_set_cursor(window, watch_cursor);
1458         }
1459 }
1460
1461 void textview_destroy(TextView *textview)
1462 {
1463         textview_uri_list_remove_all(textview->uri_list);
1464         textview->uri_list = NULL;
1465
1466         g_free(textview);
1467 }
1468
1469 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1470 {
1471         textview->show_all_headers = all_headers;
1472 }
1473
1474 #define CHANGE_TAG_FONT(tagname, font) { \
1475         tag = gtk_text_tag_table_lookup(tags, tagname); \
1476         if (tag) \
1477                 g_object_set(G_OBJECT(tag), "font-desc", font, NULL); \
1478 }
1479
1480 void textview_set_font(TextView *textview, const gchar *codeset)
1481 {
1482         GtkTextTag *tag;
1483         GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1484         GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
1485         
1486         if (NORMAL_FONT) {
1487                 PangoFontDescription *font_desc, *bold_font_desc;
1488                 font_desc = pango_font_description_from_string
1489                                                 (NORMAL_FONT);
1490                 bold_font_desc = pango_font_description_from_string
1491                                                 (NORMAL_FONT);
1492                 if (font_desc) {
1493                         gtk_widget_modify_font(textview->text, font_desc);
1494                         CHANGE_TAG_FONT("header", font_desc);
1495                         pango_font_description_free(font_desc);
1496                 }
1497                 if (bold_font_desc) {
1498                         pango_font_description_set_weight
1499                                 (bold_font_desc, PANGO_WEIGHT_BOLD);
1500                         CHANGE_TAG_FONT("header_title", bold_font_desc);
1501                         pango_font_description_free(bold_font_desc);
1502                 }
1503         }
1504
1505         if (prefs_common.textfont) {
1506                 PangoFontDescription *font_desc;
1507
1508                 font_desc = pango_font_description_from_string
1509                                                 (prefs_common.textfont);
1510                 if (font_desc) {
1511                         gtk_widget_modify_font(textview->text, font_desc);
1512                         pango_font_description_free(font_desc);
1513                 }
1514         }
1515         gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1516                                              prefs_common.line_space / 2);
1517         gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1518                                              prefs_common.line_space / 2);
1519 }
1520
1521 void textview_set_text(TextView *textview, const gchar *text)
1522 {
1523         GtkTextView *view;
1524         GtkTextBuffer *buffer;
1525
1526         g_return_if_fail(textview != NULL);
1527         g_return_if_fail(text != NULL);
1528
1529         textview_clear(textview);
1530
1531         view = GTK_TEXT_VIEW(textview->text);
1532         buffer = gtk_text_view_get_buffer(view);
1533         gtk_text_buffer_set_text(buffer, text, strlen(text));
1534 }
1535
1536 enum
1537 {
1538         H_DATE          = 0,
1539         H_FROM          = 1,
1540         H_TO            = 2,
1541         H_NEWSGROUPS    = 3,
1542         H_SUBJECT       = 4,
1543         H_CC            = 5,
1544         H_REPLY_TO      = 6,
1545         H_FOLLOWUP_TO   = 7,
1546         H_X_MAILER      = 8,
1547         H_X_NEWSREADER  = 9,
1548         H_USER_AGENT    = 10,
1549         H_ORGANIZATION  = 11,
1550 };
1551
1552 void textview_set_position(TextView *textview, gint pos)
1553 {
1554         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1555
1556         gtkut_text_view_set_position(text, pos);
1557 }
1558
1559 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1560 {
1561         gchar buf[BUFFSIZE];
1562         GPtrArray *headers, *sorted_headers;
1563         GSList *disphdr_list;
1564         Header *header;
1565         gint i;
1566
1567         g_return_val_if_fail(fp != NULL, NULL);
1568
1569         if (textview->show_all_headers)
1570                 return procheader_get_header_array_asis(fp);
1571
1572         if (!prefs_common.display_header) {
1573                 while (fgets(buf, sizeof(buf), fp) != NULL)
1574                         if (buf[0] == '\r' || buf[0] == '\n') break;
1575                 return NULL;
1576         }
1577
1578         headers = procheader_get_header_array_asis(fp);
1579
1580         sorted_headers = g_ptr_array_new();
1581
1582         for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1583              disphdr_list = disphdr_list->next) {
1584                 DisplayHeaderProp *dp =
1585                         (DisplayHeaderProp *)disphdr_list->data;
1586
1587                 for (i = 0; i < headers->len; i++) {
1588                         header = g_ptr_array_index(headers, i);
1589
1590                         if (procheader_headername_equal(header->name,
1591                                                         dp->name)) {
1592                                 if (dp->hidden)
1593                                         procheader_header_free(header);
1594                                 else
1595                                         g_ptr_array_add(sorted_headers, header);
1596
1597                                 g_ptr_array_remove_index(headers, i);
1598                                 i--;
1599                         }
1600                 }
1601         }
1602
1603         if (prefs_common.show_other_header) {
1604                 for (i = 0; i < headers->len; i++) {
1605                         header = g_ptr_array_index(headers, i);
1606                         g_ptr_array_add(sorted_headers, header);
1607                 }
1608                 g_ptr_array_free(headers, TRUE);
1609         } else
1610                 procheader_header_array_destroy(headers);
1611
1612
1613         return sorted_headers;
1614 }
1615
1616 static void textview_show_face(TextView *textview)
1617 {
1618         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1619         MsgInfo *msginfo = textview->messageview->msginfo;
1620         int x = 0;
1621         
1622         if (prefs_common.display_header_pane
1623         ||  !prefs_common.display_xface)
1624                 goto bail;
1625         
1626         if (!msginfo->face) {
1627                 goto bail;
1628         }
1629
1630         if (textview->image) 
1631                 gtk_widget_destroy(textview->image);
1632         
1633         textview->image = face_get_from_header(msginfo->face);
1634         g_return_if_fail(textview->image != NULL);
1635
1636         gtk_widget_show(textview->image);
1637         
1638         x = textview->text->allocation.width - WIDTH -5;
1639
1640         gtk_text_view_add_child_in_window(text, textview->image, 
1641                 GTK_TEXT_WINDOW_TEXT, x, 5);
1642
1643         gtk_widget_show_all(textview->text);
1644         
1645
1646         return;
1647 bail:
1648         if (textview->image) 
1649                 gtk_widget_destroy(textview->image);
1650         textview->image = NULL; 
1651 }
1652
1653 static void textview_show_icon(TextView *textview, const gchar *stock_id)
1654 {
1655         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1656         int x = 0;
1657         
1658         if (textview->image) 
1659                 gtk_widget_destroy(textview->image);
1660         
1661         textview->image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_DIALOG);
1662         g_return_if_fail(textview->image != NULL);
1663
1664         gtk_widget_show(textview->image);
1665         
1666         x = textview->text->allocation.width - WIDTH -5;
1667
1668         gtk_text_view_add_child_in_window(text, textview->image, 
1669                 GTK_TEXT_WINDOW_TEXT, x, 5);
1670
1671         gtk_widget_show_all(textview->text);
1672         
1673
1674         return;
1675 }
1676
1677 #if HAVE_LIBCOMPFACE
1678 static void textview_show_xface(TextView *textview)
1679 {
1680         MsgInfo *msginfo = textview->messageview->msginfo;
1681         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1682         int x = 0;
1683
1684         if (prefs_common.display_header_pane
1685         ||  !prefs_common.display_xface)
1686                 goto bail;
1687         
1688         if (!msginfo)
1689                 goto bail;
1690
1691         if (msginfo->face)
1692                 return;
1693         
1694         if (!msginfo->xface || strlen(msginfo->xface) < 5) {
1695                 goto bail;
1696         }
1697
1698         if (textview->image) 
1699                 gtk_widget_destroy(textview->image);
1700         
1701         textview->image = xface_get_from_header(msginfo->xface,
1702                                 &textview->text->style->white,
1703                                 textview->text->window);
1704         g_return_if_fail(textview->image != NULL);
1705
1706         gtk_widget_show(textview->image);
1707         
1708         x = textview->text->allocation.width - WIDTH -5;
1709
1710         gtk_text_view_add_child_in_window(text, textview->image, 
1711                 GTK_TEXT_WINDOW_TEXT, x, 5);
1712
1713         gtk_widget_show_all(textview->text);
1714         
1715         return;
1716 bail:
1717         if (textview->image) 
1718                 gtk_widget_destroy(textview->image);
1719         textview->image = NULL;
1720         
1721 }
1722 #endif
1723
1724 static void textview_show_header(TextView *textview, GPtrArray *headers)
1725 {
1726         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1727         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1728         GtkTextIter iter;
1729         Header *header;
1730         gint i;
1731
1732         g_return_if_fail(headers != NULL);
1733
1734         for (i = 0; i < headers->len; i++) {
1735                 header = g_ptr_array_index(headers, i);
1736                 g_return_if_fail(header->name != NULL);
1737
1738                 gtk_text_buffer_get_end_iter (buffer, &iter);
1739                 if(prefs_common.trans_hdr == TRUE) {
1740                         gchar *hdr = g_strndup(header->name, strlen(header->name) - 1);
1741                         gchar *trans_hdr = gettext(hdr);
1742                         gtk_text_buffer_insert_with_tags_by_name(buffer,
1743                                 &iter, trans_hdr, -1,
1744                                 "header_title", "header", NULL);
1745                         gtk_text_buffer_insert_with_tags_by_name(buffer,
1746                                 &iter, ":", 1, "header_title", "header", NULL);
1747                         g_free(hdr);
1748                 } else {
1749                         gtk_text_buffer_insert_with_tags_by_name(buffer,
1750                                 &iter, header->name,
1751                                 -1, "header_title", "header", NULL);
1752                 }
1753                 if (header->name[strlen(header->name) - 1] != ' ')
1754                 gtk_text_buffer_insert_with_tags_by_name
1755                                 (buffer, &iter, " ", 1,
1756                                  "header_title", "header", NULL);
1757
1758                 if (procheader_headername_equal(header->name, "Subject") ||
1759                     procheader_headername_equal(header->name, "From")    ||
1760                     procheader_headername_equal(header->name, "To")      ||
1761                     procheader_headername_equal(header->name, "Cc"))
1762                         unfold_line(header->body);
1763
1764                 if ((procheader_headername_equal(header->name, "X-Mailer") ||
1765                      procheader_headername_equal(header->name,
1766                                                  "X-Newsreader")) &&
1767                     strstr(header->body, "Sylpheed-Claws") != NULL) {
1768                         gtk_text_buffer_get_end_iter (buffer, &iter);
1769                         gtk_text_buffer_insert_with_tags_by_name
1770                                 (buffer, &iter, header->body, -1,
1771                                  "header", "emphasis", NULL);
1772                 } else {
1773                         gboolean hdr = 
1774                           procheader_headername_equal(header->name, "From") ||
1775                           procheader_headername_equal(header->name, "To") ||
1776                           procheader_headername_equal(header->name, "Cc") ||
1777                           procheader_headername_equal(header->name, "Bcc") ||
1778                           procheader_headername_equal(header->name, "Reply-To") ||
1779                           procheader_headername_equal(header->name, "Sender");
1780                         textview_make_clickable_parts(textview, "header", 
1781                                                       "link", header->body, 
1782                                                       hdr);
1783                 }
1784                 gtk_text_buffer_get_end_iter (buffer, &iter);
1785                 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1786                                                          "header", NULL);
1787         }
1788         
1789         textview_show_face(textview);
1790 #if HAVE_LIBCOMPFACE
1791         textview_show_xface(textview);
1792 #endif
1793 }
1794
1795 gboolean textview_search_string(TextView *textview, const gchar *str,
1796                                 gboolean case_sens)
1797 {
1798         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1799
1800         return gtkut_text_view_search_string(text, str, case_sens);
1801 }
1802
1803 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1804                                          gboolean case_sens)
1805 {
1806         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1807
1808         return gtkut_text_view_search_string_backward(text, str, case_sens);
1809 }
1810
1811 void textview_scroll_one_line(TextView *textview, gboolean up)
1812 {
1813         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1814         GtkAdjustment *vadj = text->vadjustment;
1815         gfloat upper;
1816
1817         if (prefs_common.enable_smooth_scroll) {
1818                 textview_smooth_scroll_one_line(textview, up);
1819                 return;
1820         }
1821
1822         if (!up) {
1823                 upper = vadj->upper - vadj->page_size;
1824                 if (vadj->value < upper) {
1825                         vadj->value += vadj->step_increment;
1826                         vadj->value = MIN(vadj->value, upper);
1827                         g_signal_emit_by_name(G_OBJECT(vadj),
1828                                               "value_changed", 0);
1829                 }
1830         } else {
1831                 if (vadj->value > 0.0) {
1832                         vadj->value -= vadj->step_increment;
1833                         vadj->value = MAX(vadj->value, 0.0);
1834                         g_signal_emit_by_name(G_OBJECT(vadj),
1835                                               "value_changed", 0);
1836                 }
1837         }
1838 }
1839
1840 gboolean textview_scroll_page(TextView *textview, gboolean up)
1841 {
1842         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1843         GtkAdjustment *vadj = text->vadjustment;
1844         gfloat upper;
1845         gfloat page_incr;
1846
1847         if (prefs_common.enable_smooth_scroll)
1848                 return textview_smooth_scroll_page(textview, up);
1849
1850         if (prefs_common.scroll_halfpage)
1851                 page_incr = vadj->page_increment / 2;
1852         else
1853                 page_incr = vadj->page_increment;
1854
1855         if (!up) {
1856                 upper = vadj->upper - vadj->page_size;
1857                 if (vadj->value < upper) {
1858                         vadj->value += page_incr;
1859                         vadj->value = MIN(vadj->value, upper);
1860                         g_signal_emit_by_name(G_OBJECT(vadj),
1861                                               "value_changed", 0);
1862                 } else
1863                         return FALSE;
1864         } else {
1865                 if (vadj->value > 0.0) {
1866                         vadj->value -= page_incr;
1867                         vadj->value = MAX(vadj->value, 0.0);
1868                         g_signal_emit_by_name(G_OBJECT(vadj),
1869                                               "value_changed", 0);
1870                 } else
1871                         return FALSE;
1872         }
1873
1874         return TRUE;
1875 }
1876
1877 static void textview_smooth_scroll_do(TextView *textview,
1878                                       gfloat old_value, gfloat last_value,
1879                                       gint step)
1880 {
1881         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1882         GtkAdjustment *vadj = text->vadjustment;
1883         gint change_value;
1884         gboolean up;
1885         gint i;
1886
1887         if (old_value < last_value) {
1888                 change_value = last_value - old_value;
1889                 up = FALSE;
1890         } else {
1891                 change_value = old_value - last_value;
1892                 up = TRUE;
1893         }
1894
1895         for (i = step; i <= change_value; i += step) {
1896                 vadj->value = old_value + (up ? -i : i);
1897                 g_signal_emit_by_name(G_OBJECT(vadj),
1898                                       "value_changed", 0);
1899         }
1900
1901         vadj->value = last_value;
1902         g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1903
1904         gtk_widget_queue_draw(GTK_WIDGET(text));
1905 }
1906
1907 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1908 {
1909         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1910         GtkAdjustment *vadj = text->vadjustment;
1911         gfloat upper;
1912         gfloat old_value;
1913         gfloat last_value;
1914
1915         if (!up) {
1916                 upper = vadj->upper - vadj->page_size;
1917                 if (vadj->value < upper) {
1918                         old_value = vadj->value;
1919                         last_value = vadj->value + vadj->step_increment;
1920                         last_value = MIN(last_value, upper);
1921
1922                         textview_smooth_scroll_do(textview, old_value,
1923                                                   last_value,
1924                                                   prefs_common.scroll_step);
1925                 }
1926         } else {
1927                 if (vadj->value > 0.0) {
1928                         old_value = vadj->value;
1929                         last_value = vadj->value - vadj->step_increment;
1930                         last_value = MAX(last_value, 0.0);
1931
1932                         textview_smooth_scroll_do(textview, old_value,
1933                                                   last_value,
1934                                                   prefs_common.scroll_step);
1935                 }
1936         }
1937 }
1938
1939 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1940 {
1941         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1942         GtkAdjustment *vadj = text->vadjustment;
1943         gfloat upper;
1944         gfloat page_incr;
1945         gfloat old_value;
1946         gfloat last_value;
1947
1948         if (prefs_common.scroll_halfpage)
1949                 page_incr = vadj->page_increment / 2;
1950         else
1951                 page_incr = vadj->page_increment;
1952
1953         if (!up) {
1954                 upper = vadj->upper - vadj->page_size;
1955                 if (vadj->value < upper) {
1956                         old_value = vadj->value;
1957                         last_value = vadj->value + page_incr;
1958                         last_value = MIN(last_value, upper);
1959
1960                         textview_smooth_scroll_do(textview, old_value,
1961                                                   last_value,
1962                                                   prefs_common.scroll_step);
1963                 } else
1964                         return FALSE;
1965         } else {
1966                 if (vadj->value > 0.0) {
1967                         old_value = vadj->value;
1968                         last_value = vadj->value - page_incr;
1969                         last_value = MAX(last_value, 0.0);
1970
1971                         textview_smooth_scroll_do(textview, old_value,
1972                                                   last_value,
1973                                                   prefs_common.scroll_step);
1974                 } else
1975                         return FALSE;
1976         }
1977
1978         return TRUE;
1979 }
1980
1981 #define KEY_PRESS_EVENT_STOP() \
1982         g_signal_stop_emission_by_name(G_OBJECT(widget), \
1983                                        "key_press_event");
1984
1985 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1986                                  TextView *textview)
1987 {
1988         SummaryView *summaryview = NULL;
1989         MessageView *messageview = textview->messageview;
1990
1991         if (!event) return FALSE;
1992         if (messageview->mainwin)
1993                 summaryview = messageview->mainwin->summaryview;
1994
1995         switch (event->keyval) {
1996         case GDK_Tab:
1997         case GDK_Home:
1998         case GDK_Left:
1999         case GDK_Up:
2000         case GDK_Right:
2001         case GDK_Down:
2002         case GDK_Page_Up:
2003         case GDK_Page_Down:
2004         case GDK_End:
2005         case GDK_Control_L:
2006         case GDK_Control_R:
2007                 return FALSE;
2008         case GDK_space:
2009                 if (summaryview)
2010                         summary_pass_key_press_event(summaryview, event);
2011                 else
2012                         textview_scroll_page
2013                                 (textview,
2014                                  (event->state &
2015                                   (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
2016                 break;
2017         case GDK_BackSpace:
2018                 textview_scroll_page(textview, TRUE);
2019                 break;
2020         case GDK_Return:
2021                 textview_scroll_one_line
2022                         (textview, (event->state &
2023                                     (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
2024                 break;
2025         case GDK_Delete:
2026                 if (summaryview)
2027                         summary_pass_key_press_event(summaryview, event);
2028                 break;
2029         case GDK_y:
2030         case GDK_t:
2031         case GDK_l:
2032         case GDK_c:
2033                 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
2034                         KEY_PRESS_EVENT_STOP();
2035                         mimeview_pass_key_press_event(messageview->mimeview,
2036                                                       event);
2037                         break;
2038                 }
2039                 /* possible fall through */
2040         default:
2041                 if (summaryview &&
2042                     event->window != messageview->mainwin->window->window) {
2043                         GdkEventKey tmpev = *event;
2044
2045                         tmpev.window = messageview->mainwin->window->window;
2046                         KEY_PRESS_EVENT_STOP();
2047                         gtk_widget_event(messageview->mainwin->window,
2048                                          (GdkEvent *)&tmpev);
2049                 }
2050                 break;
2051         }
2052
2053         return TRUE;
2054 }
2055
2056 static gboolean textview_motion_notify(GtkWidget *widget,
2057                                        GdkEventMotion *event,
2058                                        TextView *textview)
2059 {
2060         textview_uri_update(textview, event->x, event->y);
2061         gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
2062
2063         return FALSE;
2064 }
2065
2066 static gboolean textview_leave_notify(GtkWidget *widget,
2067                                       GdkEventCrossing *event,
2068                                       TextView *textview)
2069 {
2070         textview_uri_update(textview, -1, -1);
2071
2072         return FALSE;
2073 }
2074
2075 static gboolean textview_visibility_notify(GtkWidget *widget,
2076                                            GdkEventVisibility *event,
2077                                            TextView *textview)
2078 {
2079         gint wx, wy;
2080         GdkWindow *window;
2081
2082         window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
2083                                           GTK_TEXT_WINDOW_TEXT);
2084
2085         /* check if occurred for the text window part */
2086         if (window != event->window)
2087                 return FALSE;
2088         
2089         gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
2090         textview_uri_update(textview, wx, wy);
2091
2092         return FALSE;
2093 }
2094
2095 void textview_cursor_wait(TextView *textview)
2096 {
2097         GdkWindow *window = gtk_text_view_get_window(
2098                         GTK_TEXT_VIEW(textview->text),
2099                         GTK_TEXT_WINDOW_TEXT);
2100         textview_set_cursor(window, watch_cursor);
2101 }
2102
2103 void textview_cursor_normal(TextView *textview)
2104 {
2105         GdkWindow *window = gtk_text_view_get_window(
2106                         GTK_TEXT_VIEW(textview->text),
2107                         GTK_TEXT_WINDOW_TEXT);
2108         textview_set_cursor(window, text_cursor);
2109 }
2110
2111 static void textview_uri_update(TextView *textview, gint x, gint y)
2112 {
2113         GtkTextBuffer *buffer;
2114         GtkTextIter start_iter, end_iter;
2115         ClickableText *uri = NULL;
2116         
2117         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2118
2119         if (x != -1 && y != -1) {
2120                 gint bx, by;
2121                 GtkTextIter iter;
2122                 GSList *tags;
2123                 GSList *cur;
2124
2125                 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text), 
2126                                                       GTK_TEXT_WINDOW_WIDGET,
2127                                                       x, y, &bx, &by);
2128                 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
2129                                                    &iter, bx, by);
2130
2131                 tags = gtk_text_iter_get_tags(&iter);
2132                 for (cur = tags; cur != NULL; cur = cur->next) {
2133                         GtkTextTag *tag = cur->data;
2134                         char *name;
2135
2136                         g_object_get(G_OBJECT(tag), "name", &name, NULL);
2137
2138                         if ((!strcmp(name, "link") || !strcmp(name, "qlink"))
2139                             && textview_get_uri_range(textview, &iter, tag,
2140                                                       &start_iter, &end_iter)) {
2141
2142                                 uri = textview_get_uri_from_range(textview,
2143                                                                   &iter, tag,
2144                                                                   &start_iter,
2145                                                                   &end_iter);
2146                         } 
2147                         g_free(name);
2148
2149                         if (uri)
2150                                 break;
2151                 }
2152                 g_slist_free(tags);
2153         }
2154         
2155         if (uri != textview->uri_hover) {
2156                 GdkWindow *window;
2157
2158                 if (textview->uri_hover)
2159                         gtk_text_buffer_remove_tag_by_name(buffer,
2160                                                            "link-hover",
2161                                                            &textview->uri_hover_start_iter,
2162                                                            &textview->uri_hover_end_iter);
2163                     
2164                 textview->uri_hover = uri;
2165                 if (uri) {
2166                         textview->uri_hover_start_iter = start_iter;
2167                         textview->uri_hover_end_iter = end_iter;
2168                 }
2169                 
2170                 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
2171                                                   GTK_TEXT_WINDOW_TEXT);
2172                 if (textview->messageview->mainwin->cursor_count == 0) {
2173                         textview_set_cursor(window, uri ? hand_cursor : text_cursor);
2174                 } else {
2175                         textview_set_cursor(window, watch_cursor);
2176                 }
2177
2178                 TEXTVIEW_STATUSBAR_POP(textview);
2179
2180                 if (uri) {
2181                         char *trimmed_uri;
2182
2183                         if (!uri->is_quote)
2184                                 gtk_text_buffer_apply_tag_by_name(buffer,
2185                                                           "link-hover",
2186                                                           &start_iter,
2187                                                           &end_iter);
2188
2189                         trimmed_uri = trim_string(uri->uri, 60);
2190                         TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
2191                         g_free(trimmed_uri);
2192                 }
2193         }
2194 }
2195
2196 static gboolean textview_get_uri_range(TextView *textview,
2197                                        GtkTextIter *iter,
2198                                        GtkTextTag *tag,
2199                                        GtkTextIter *start_iter,
2200                                        GtkTextIter *end_iter)
2201 {
2202         return get_tag_range(iter, tag, start_iter, end_iter);
2203 }
2204
2205 static ClickableText *textview_get_uri_from_range(TextView *textview,
2206                                               GtkTextIter *iter,
2207                                               GtkTextTag *tag,
2208                                               GtkTextIter *start_iter,
2209                                               GtkTextIter *end_iter)
2210 {
2211         gint start_pos, end_pos, cur_pos;
2212         ClickableText *uri = NULL;
2213         GSList *cur;
2214
2215         start_pos = gtk_text_iter_get_offset(start_iter);
2216         end_pos = gtk_text_iter_get_offset(end_iter);
2217         cur_pos = gtk_text_iter_get_offset(iter);
2218
2219         for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
2220                 ClickableText *uri_ = (ClickableText *)cur->data;
2221                 if (start_pos == uri_->start &&
2222                     end_pos ==  uri_->end) {
2223                         uri = uri_;
2224                         break;
2225                 } else if (start_pos == uri_->start ||
2226                            end_pos == uri_->end) {
2227                         /* in case of contiguous links, textview_get_uri_range
2228                          * returns a broader range (start of 1st link to end
2229                          * of last link).
2230                          * In that case, correct link is the one covering
2231                          * current iter.
2232                          */
2233                         if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
2234                                 uri = uri_;
2235                                 break;
2236                         }
2237                 } 
2238         }
2239
2240         return uri;
2241 }
2242
2243 static ClickableText *textview_get_uri(TextView *textview,
2244                                    GtkTextIter *iter,
2245                                    GtkTextTag *tag)
2246 {
2247         GtkTextIter start_iter, end_iter;
2248         ClickableText *uri = NULL;
2249
2250         if (textview_get_uri_range(textview, iter, tag, &start_iter,
2251                                    &end_iter))
2252                 uri = textview_get_uri_from_range(textview, iter, tag,
2253                                                   &start_iter, &end_iter);
2254
2255         return uri;
2256 }
2257
2258 static void textview_shift_uris_after(TextView *textview, gint start, gint shift)
2259 {
2260         GSList *cur;
2261         for (cur = textview->uri_list; cur; cur = cur->next) {
2262                 ClickableText *uri = (ClickableText *)cur->data;
2263                 if (uri->start <= start)
2264                         continue;
2265                 uri->start += shift;
2266                 uri->end += shift;
2267         }
2268 }
2269
2270 static void textview_remove_uris_in(TextView *textview, gint start, gint end)
2271 {
2272         GSList *cur;
2273         for (cur = textview->uri_list; cur; ) {
2274                 ClickableText *uri = (ClickableText *)cur->data;
2275                 if (uri->start > start && uri->end < end) {
2276                         cur = cur->next;
2277                         textview->uri_list = g_slist_remove(textview->uri_list, uri);
2278                         g_free(uri->uri);
2279                         g_free(uri->filename);
2280                         if (uri->is_quote) {
2281                                 g_free(uri->fg_color);
2282                                 g_free(uri->data); 
2283                                 /* (only free data in quotes uris) */
2284                         }
2285                         g_free(uri);
2286                 } else {
2287                         cur = cur->next;
2288                 }
2289                 
2290         }
2291 }
2292
2293 static void textview_toggle_quote(TextView *textview, ClickableText *uri)
2294 {
2295         GtkTextIter start, end;
2296         GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2297         
2298         if (!uri->is_quote)
2299                 return;
2300         
2301         gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2302         gtk_text_buffer_get_iter_at_offset(buffer, &end,   uri->end);
2303         if (textview->uri_hover)
2304                 gtk_text_buffer_remove_tag_by_name(buffer,
2305                                                    "link-hover",
2306                                                    &textview->uri_hover_start_iter,
2307                                                    &textview->uri_hover_end_iter);
2308         textview->uri_hover = NULL;
2309         gtk_text_buffer_remove_tag_by_name(buffer,
2310                                            "qlink",
2311                                            &start,
2312                                            &end);
2313         /* when shifting URIs start and end, we have to do it per-UTF8-char
2314          * so use g_utf8_strlen(). OTOH, when inserting in the text buffer, 
2315          * we have to pass a number of bytes, so use strlen(). disturbing. */
2316         if (!uri->q_expanded) {
2317                 gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2318                 gtk_text_buffer_get_iter_at_offset(buffer, &end,   uri->end);
2319                 textview_shift_uris_after(textview, uri->start, 
2320                         g_utf8_strlen((gchar *)uri->data, -1)-strlen(" [...]\n"));
2321                 gtk_text_buffer_delete(buffer, &start, &end);
2322                 gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2323                 gtk_text_buffer_insert_with_tags_by_name
2324                                 (buffer, &start, (gchar *)uri->data, 
2325                                  strlen((gchar *)uri->data)-1,
2326                                  "qlink", (gchar *)uri->fg_color, NULL);
2327                 uri->end = gtk_text_iter_get_offset(&start);
2328                 textview_make_clickable_parts_later(textview,
2329                                           uri->start, uri->end);
2330                 uri->q_expanded = TRUE;
2331         } else {
2332                 gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2333                 gtk_text_buffer_get_iter_at_offset(buffer, &end,   uri->end);
2334                 textview_remove_uris_in(textview, uri->start, uri->end);
2335                 textview_shift_uris_after(textview, uri->start, 
2336                         strlen(" [...]\n")-g_utf8_strlen((gchar *)uri->data, -1));
2337                 gtk_text_buffer_delete(buffer, &start, &end);
2338                 gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2339                 gtk_text_buffer_insert_with_tags_by_name
2340                                 (buffer, &start, " [...]", -1,
2341                                  "qlink", (gchar *)uri->fg_color, NULL);
2342                 uri->end = gtk_text_iter_get_offset(&start);
2343                 uri->q_expanded = FALSE;
2344         }
2345         if (textview->messageview->mainwin->cursor_count == 0) {
2346                 textview_cursor_normal(textview);
2347         } else {
2348                 textview_cursor_wait(textview);
2349         }
2350 }
2351 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
2352                                             GdkEvent *event, GtkTextIter *iter,
2353                                             TextView *textview)
2354 {
2355         GdkEventButton *bevent;
2356         ClickableText *uri = NULL;
2357         char *tagname;
2358         gboolean qlink = FALSE;
2359
2360         if (!event)
2361                 return FALSE;
2362
2363         if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2364                 && event->type != GDK_MOTION_NOTIFY)
2365                 return FALSE;
2366
2367         uri = textview_get_uri(textview, iter, tag);
2368         if (!uri)
2369                 return FALSE;
2370
2371         g_object_get(G_OBJECT(tag), "name", &tagname, NULL);
2372         
2373         if (!strcmp(tagname, "qlink"))
2374                 qlink = TRUE;
2375
2376         g_free(tagname);
2377         
2378         bevent = (GdkEventButton *) event;
2379         
2380         /* doubleclick: open compose / add address / browser */
2381         if ((event->type == (qlink ? GDK_2BUTTON_PRESS:GDK_BUTTON_PRESS) && bevent->button == 1) ||
2382                 bevent->button == 2 || bevent->button == 3) {
2383                 if (uri->filename && !g_ascii_strncasecmp(uri->filename, "sc://", 5)) {
2384                         MimeView *mimeview = 
2385                                 (textview->messageview)?
2386                                         textview->messageview->mimeview:NULL;
2387                         if (mimeview && bevent->button == 1) {
2388                                 mimeview_handle_cmd(mimeview, uri->filename, NULL, uri->data);
2389                         } else if (mimeview && bevent->button == 2 && 
2390                                 !g_ascii_strcasecmp(uri->filename, "sc://select_attachment")) {
2391                                 mimeview_handle_cmd(mimeview, "sc://open_attachment", NULL, uri->data);
2392                         } else if (mimeview && bevent->button == 3 && 
2393                                 !g_ascii_strcasecmp(uri->filename, "sc://select_attachment")) {
2394                                 mimeview_handle_cmd(mimeview, "sc://menu_attachment", bevent, uri->data);
2395                         } 
2396                         return TRUE;
2397                 } else if (qlink && bevent->button == 1) {
2398                         textview_toggle_quote(textview, uri);
2399                         return TRUE;
2400                                 
2401                 } else if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2402                         if (bevent->button == 3) {
2403                                 g_object_set_data(
2404                                         G_OBJECT(textview->mail_popup_menu),
2405                                         "menu_button", uri);
2406                                 gtk_menu_popup(GTK_MENU(textview->mail_popup_menu), 
2407                                                NULL, NULL, NULL, NULL, 
2408                                                bevent->button, bevent->time);
2409                         } else {
2410                                 PrefsAccount *account = NULL;
2411
2412                                 if (textview->messageview && textview->messageview->msginfo &&
2413                                     textview->messageview->msginfo->folder) {
2414                                         FolderItem   *folder_item;
2415
2416                                         folder_item = textview->messageview->msginfo->folder;
2417                                         if (folder_item->prefs && folder_item->prefs->enable_default_account)
2418                                                 account = account_find_from_id(folder_item->prefs->default_account);
2419                                 }
2420                                 compose_new(account, uri->uri + 7, NULL);
2421                         }
2422                         return TRUE;
2423                 } else if (g_ascii_strncasecmp(uri->uri, "file:", 5)) {
2424                         if (bevent->button == 1 &&
2425                             textview_uri_security_check(textview, uri) == TRUE) 
2426                                         open_uri(uri->uri,
2427                                                  prefs_common.uri_cmd);
2428                         else if (bevent->button == 3 && !qlink) {
2429                                 g_object_set_data(
2430                                         G_OBJECT(textview->link_popup_menu),
2431                                         "menu_button", uri);
2432                                 gtk_menu_popup(GTK_MENU(textview->link_popup_menu), 
2433                                                NULL, NULL, NULL, NULL, 
2434                                                bevent->button, bevent->time);
2435                         }
2436                         return TRUE;
2437                 } else {
2438                         if (bevent->button == 3 && !qlink) {
2439                                 g_object_set_data(
2440                                         G_OBJECT(textview->file_popup_menu),
2441                                         "menu_button", uri);
2442                                 gtk_menu_popup(GTK_MENU(textview->file_popup_menu), 
2443                                                NULL, NULL, NULL, NULL, 
2444                                                bevent->button, bevent->time);
2445                                 return TRUE;
2446                         }
2447                 }
2448         }
2449
2450         return FALSE;
2451 }
2452
2453 /*!
2454  *\brief    Check to see if a web URL has been disguised as a different
2455  *          URL (possible with HTML email).
2456  *
2457  *\param    uri The uri to check
2458  *
2459  *\param    textview The TextView the URL is contained in
2460  *
2461  *\return   gboolean TRUE if the URL is ok, or if the user chose to open
2462  *          it anyway, otherwise FALSE          
2463  */
2464 static gboolean textview_uri_security_check(TextView *textview, ClickableText *uri)
2465 {
2466         gchar *visible_str;
2467         gboolean retval = TRUE;
2468         GtkTextBuffer *buffer;
2469         GtkTextIter start, end;
2470
2471         if (is_uri_string(uri->uri) == FALSE)
2472                 return TRUE;
2473
2474         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2475
2476         gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2477         gtk_text_buffer_get_iter_at_offset(buffer, &end,   uri->end);
2478
2479         visible_str = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2480
2481         if (visible_str == NULL)
2482                 return TRUE;
2483
2484         if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2485                 gchar *uri_path;
2486                 gchar *visible_uri_path;
2487
2488                 uri_path = get_uri_path(uri->uri);
2489                 visible_uri_path = get_uri_path(visible_str);
2490                 if (path_cmp(uri_path, visible_uri_path) != 0)
2491                         retval = FALSE;
2492         }
2493
2494         if (retval == FALSE) {
2495                 gchar *msg;
2496                 AlertValue aval;
2497
2498                 msg = g_markup_printf_escaped(_("The real URL is different from "
2499                                                 "the displayed URL.\n"
2500                                                 "\n"
2501                                                 "<b>Displayed URL:</b> %s\n"
2502                                                 "\n"
2503                                                 "<b>Real URL:</b> %s\n"
2504                                                 "\n"
2505                                                 "Open it anyway?"),
2506                                                visible_str,uri->uri);
2507                 aval = alertpanel_full(_("Phishing attempt warning"), msg,
2508                                        GTK_STOCK_CANCEL, _("_Open URL"), NULL, FALSE,
2509                                        NULL, ALERT_WARNING, G_ALERTDEFAULT);
2510                 g_free(msg);
2511                 if (aval == G_ALERTALTERNATE)
2512                         retval = TRUE;
2513         }
2514
2515         g_free(visible_str);
2516
2517         return retval;
2518 }
2519
2520 static void textview_uri_list_remove_all(GSList *uri_list)
2521 {
2522         GSList *cur;
2523
2524         for (cur = uri_list; cur != NULL; cur = cur->next) {
2525                 if (cur->data) {
2526                         g_free(((ClickableText *)cur->data)->uri);
2527                         g_free(((ClickableText *)cur->data)->filename);
2528                         if (((ClickableText *)cur->data)->is_quote) {
2529                                 g_free(((ClickableText *)cur->data)->fg_color);
2530                                 g_free(((ClickableText *)cur->data)->data); 
2531                                 /* (only free data in quotes uris) */
2532                         }
2533                         g_free(cur->data);
2534                 }
2535         }
2536
2537         g_slist_free(uri_list);
2538 }
2539
2540 static void open_uri_cb (TextView *textview, guint action, void *data)
2541 {
2542         ClickableText *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2543                                            "menu_button");
2544         if (uri == NULL)
2545                 return;
2546
2547         if (textview_uri_security_check(textview, uri) == TRUE) 
2548                 open_uri(uri->uri,
2549                          prefs_common.uri_cmd);
2550         g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2551                           NULL);
2552 }
2553
2554 static void open_image_cb (TextView *textview, guint action, void *data)
2555 {
2556         ClickableText *uri = g_object_get_data(G_OBJECT(textview->file_popup_menu),
2557                                            "menu_button");
2558
2559         static gchar *default_cmdline = DEFAULT_IMAGE_VIEWER_CMD;
2560         gchar buf[1024];
2561         const gchar *cmd;
2562         const gchar *def_cmd;
2563         const gchar *p;
2564         gchar *filename = NULL;
2565         gchar *tmp_filename = NULL;
2566
2567         if (uri == NULL)
2568                 return;
2569
2570         if (uri->filename == NULL)
2571                 return;
2572         
2573         filename = g_strdup(uri->filename);
2574         
2575         if (!g_utf8_validate(filename, -1, NULL)) {
2576                 gchar *tmp = conv_filename_to_utf8(filename);
2577                 g_free(filename);
2578                 filename = tmp;
2579         }
2580
2581         subst_for_filename(filename);
2582
2583         tmp_filename = g_filename_from_uri(uri->uri, NULL, NULL);
2584         copy_file(tmp_filename, filename, FALSE);
2585         g_free(tmp_filename);
2586
2587         cmd = prefs_common.mime_image_viewer;
2588         def_cmd = default_cmdline;
2589         
2590         if (cmd && (p = strchr(cmd, '%')) && *(p + 1) == 's' &&
2591             !strchr(p + 2, '%'))
2592                 g_snprintf(buf, sizeof(buf), cmd, filename);
2593         else {
2594                 if (cmd)
2595                         g_warning("Image viewer command line is invalid: '%s'", cmd);
2596                 if (def_cmd)
2597                         g_snprintf(buf, sizeof(buf), def_cmd, filename);
2598                 else
2599                         return;
2600         }
2601
2602         execute_command_line(buf, TRUE);
2603
2604         g_free(filename);
2605
2606         g_object_set_data(G_OBJECT(textview->file_popup_menu), "menu_button",
2607                           NULL);
2608 }
2609
2610 static void save_file_cb (TextView *textview, guint action, void *data)
2611 {
2612         ClickableText *uri = g_object_get_data(G_OBJECT(textview->file_popup_menu),
2613                                            "menu_button");
2614         gchar *filename = NULL;
2615         gchar *filepath = NULL;
2616         gchar *filedir = NULL;
2617         gchar *tmp_filename = NULL;
2618         if (uri == NULL)
2619                 return;
2620
2621         if (uri->filename == NULL)
2622                 return;
2623         
2624         filename = g_strdup(uri->filename);
2625         
2626         if (!g_utf8_validate(filename, -1, NULL)) {
2627                 gchar *tmp = conv_filename_to_utf8(filename);
2628                 g_free(filename);
2629                 filename = tmp;
2630         }
2631
2632         subst_for_filename(filename);
2633         
2634         if (prefs_common.attach_save_dir)
2635                 filepath = g_strconcat(prefs_common.attach_save_dir,
2636                                        G_DIR_SEPARATOR_S, filename, NULL);
2637         else
2638                 filepath = g_strdup(filename);
2639
2640         g_free(filename);
2641
2642         filename = filesel_select_file_save(_("Save as"), filepath);
2643         if (!filename) {
2644                 g_free(filepath);
2645                 return;
2646         }
2647
2648         if (is_file_exist(filename)) {
2649                 AlertValue aval;
2650                 gchar *res;
2651                 
2652                 res = g_strdup_printf(_("Overwrite existing file '%s'?"),
2653                                       filename);
2654                 aval = alertpanel(_("Overwrite"), res, GTK_STOCK_CANCEL, 
2655                                   GTK_STOCK_OK, NULL);
2656                 g_free(res);                                      
2657                 if (G_ALERTALTERNATE != aval)
2658                         return;
2659         }
2660
2661         tmp_filename = g_filename_from_uri(uri->uri, NULL, NULL);
2662         copy_file(tmp_filename, filename, FALSE);
2663         g_free(tmp_filename);
2664         
2665         filedir = g_path_get_dirname(filename);
2666         if (filedir && strcmp(filedir, ".")) {
2667                 g_free(prefs_common.attach_save_dir);
2668                 prefs_common.attach_save_dir = g_strdup(filedir);
2669         }
2670
2671         g_free(filedir);
2672         g_free(filepath);
2673
2674         g_object_set_data(G_OBJECT(textview->file_popup_menu), "menu_button",
2675                           NULL);
2676 }
2677
2678 static void copy_uri_cb (TextView *textview, guint action, void *data)
2679 {
2680         ClickableText *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2681                                            "menu_button");
2682         if (uri == NULL)
2683                 return;
2684
2685         gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), uri->uri, -1);
2686         gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), uri->uri, -1);
2687         g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2688                           NULL);
2689 }
2690
2691 static void add_uri_to_addrbook_cb (TextView *textview, guint action, void *data)
2692 {
2693         gchar *fromname, *fromaddress;
2694         ClickableText *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2695                                            "menu_button");
2696         if (uri == NULL)
2697                 return;
2698
2699         /* extract url */
2700         fromaddress = g_strdup(uri->uri + 7);
2701         /* Hiroyuki: please put this function in utils.c! */
2702         fromname = procheader_get_fromname(fromaddress);
2703         extract_address(fromaddress);
2704         g_message("adding from textview %s <%s>", fromname, fromaddress);
2705         /* Add to address book - Match */
2706         addressbook_add_contact( fromname, fromaddress, NULL );
2707
2708         g_free(fromaddress);
2709         g_free(fromname);
2710 }
2711
2712 static void mail_to_uri_cb (TextView *textview, guint action, void *data)
2713 {
2714         PrefsAccount *account = NULL;
2715         ClickableText *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2716                                            "menu_button");
2717         if (uri == NULL)
2718                 return;
2719
2720         if (textview->messageview && textview->messageview->msginfo &&
2721             textview->messageview->msginfo->folder) {
2722                 FolderItem   *folder_item;
2723
2724                 folder_item = textview->messageview->msginfo->folder;
2725                 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2726                         account = account_find_from_id(folder_item->prefs->default_account);
2727         }
2728         compose_new(account, uri->uri + 7, NULL);
2729 }
2730
2731 static void copy_mail_to_uri_cb (TextView *textview, guint action, void *data)
2732 {
2733         ClickableText *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2734                                            "menu_button");
2735         if (uri == NULL)
2736                 return;
2737
2738         gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), uri->uri +7, -1);
2739         gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), uri->uri +7, -1);
2740         g_object_set_data(G_OBJECT(textview->mail_popup_menu), "menu_button",
2741                           NULL);
2742 }
2743