2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2004 Hiroyuki Yamamoto
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.
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.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 #include <gdk/gdkkeysyms.h>
29 #include <gtk/gtkvbox.h>
30 #include <gtk/gtkscrolledwindow.h>
31 #include <gtk/gtksignal.h>
39 #include "summaryview.h"
40 #include "procheader.h"
41 #include "prefs_common.h"
50 #include "addressbook.h"
51 #include "displayheader.h"
54 #include "alertpanel.h"
56 typedef struct _RemoteURI RemoteURI;
66 static GdkColor quote_colors[3] = {
67 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
68 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
69 {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
72 static GdkColor signature_color = {
79 static GdkColor uri_color = {
86 static GdkColor emphasis_color = {
94 static GdkColor error_color = {
102 static GdkFont *text_sb_font;
103 static GdkFont *text_mb_font;
104 static gint text_sb_font_orig_ascent;
105 static gint text_sb_font_orig_descent;
106 static gint text_mb_font_orig_ascent;
107 static gint text_mb_font_orig_descent;
108 static GdkFont *spacingfont;
110 #define TEXTVIEW_STATUSBAR_PUSH(textview, str) \
112 gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
113 textview->messageview->statusbar_cid, str); \
116 #define TEXTVIEW_STATUSBAR_POP(textview) \
118 gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
119 textview->messageview->statusbar_cid); \
122 static void textview_show_ertf (TextView *textview,
124 CodeConverter *conv);
125 static void textview_add_part (TextView *textview,
127 static void textview_add_parts (TextView *textview,
129 static void textview_write_body (TextView *textview,
131 static void textview_show_html (TextView *textview,
133 CodeConverter *conv);
135 static void textview_write_line (TextView *textview,
137 CodeConverter *conv);
138 static void textview_write_link (TextView *textview,
141 CodeConverter *conv);
143 static GPtrArray *textview_scan_header (TextView *textview,
145 static void textview_show_header (TextView *textview,
148 static gint textview_key_pressed (GtkWidget *widget,
151 static gint textview_button_pressed (GtkWidget *widget,
152 GdkEventButton *event,
154 static gint textview_button_released (GtkWidget *widget,
155 GdkEventButton *event,
158 static void textview_smooth_scroll_do (TextView *textview,
162 static void textview_smooth_scroll_one_line (TextView *textview,
164 static gboolean textview_smooth_scroll_page (TextView *textview,
167 static gboolean textview_uri_security_check (TextView *textview,
169 static void textview_uri_list_remove_all (GSList *uri_list);
172 TextView *textview_create(void)
176 GtkWidget *scrolledwin_sb;
177 GtkWidget *scrolledwin_mb;
181 debug_print("Creating text view...\n");
182 textview = g_new0(TextView, 1);
184 scrolledwin_sb = gtk_scrolled_window_new(NULL, NULL);
185 scrolledwin_mb = gtk_scrolled_window_new(NULL, NULL);
186 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_sb),
187 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
188 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_mb),
189 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
190 gtk_widget_set_usize(scrolledwin_sb, prefs_common.mainview_width, -1);
191 gtk_widget_set_usize(scrolledwin_mb, prefs_common.mainview_width, -1);
193 /* create GtkSText widgets for single-byte and multi-byte character */
194 text_sb = gtk_stext_new(NULL, NULL);
195 text_mb = gtk_stext_new(NULL, NULL);
196 GTK_STEXT(text_sb)->default_tab_width = 8;
197 GTK_STEXT(text_mb)->default_tab_width = 8;
198 gtk_widget_show(text_sb);
199 gtk_widget_show(text_mb);
200 gtk_stext_set_word_wrap(GTK_STEXT(text_sb), TRUE);
201 gtk_stext_set_word_wrap(GTK_STEXT(text_mb), TRUE);
202 gtk_widget_ensure_style(text_sb);
203 gtk_widget_ensure_style(text_mb);
204 if (text_sb->style && text_sb->style->font->type == GDK_FONT_FONTSET) {
208 font = gtkut_font_load_from_fontset(prefs_common.normalfont);
210 style = gtk_style_copy(text_sb->style);
211 gdk_font_unref(style->font);
213 gtk_widget_set_style(text_sb, style);
216 if (text_mb->style && text_mb->style->font->type == GDK_FONT_FONT) {
220 font = gdk_fontset_load(prefs_common.normalfont);
222 style = gtk_style_copy(text_mb->style);
223 gdk_font_unref(style->font);
225 gtk_widget_set_style(text_mb, style);
228 gtk_widget_ref(scrolledwin_sb);
229 gtk_widget_ref(scrolledwin_mb);
231 gtk_container_add(GTK_CONTAINER(scrolledwin_sb), text_sb);
232 gtk_container_add(GTK_CONTAINER(scrolledwin_mb), text_mb);
233 gtk_signal_connect(GTK_OBJECT(text_sb), "key_press_event",
234 GTK_SIGNAL_FUNC(textview_key_pressed),
236 gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_press_event",
237 GTK_SIGNAL_FUNC(textview_button_pressed),
239 gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_release_event",
240 GTK_SIGNAL_FUNC(textview_button_released),
242 gtk_signal_connect(GTK_OBJECT(text_mb), "key_press_event",
243 GTK_SIGNAL_FUNC(textview_key_pressed),
245 gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_press_event",
246 GTK_SIGNAL_FUNC(textview_button_pressed),
248 gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_release_event",
249 GTK_SIGNAL_FUNC(textview_button_released),
252 gtk_widget_show(scrolledwin_sb);
253 gtk_widget_show(scrolledwin_mb);
255 vbox = gtk_vbox_new(FALSE, 0);
256 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin_sb, TRUE, TRUE, 0);
258 gtk_widget_show(vbox);
260 textview->vbox = vbox;
261 textview->scrolledwin = scrolledwin_sb;
262 textview->scrolledwin_sb = scrolledwin_sb;
263 textview->scrolledwin_mb = scrolledwin_mb;
264 textview->text = text_sb;
265 textview->text_sb = text_sb;
266 textview->text_mb = text_mb;
267 textview->text_is_mb = FALSE;
268 textview->uri_list = NULL;
269 textview->body_pos = 0;
270 textview->cur_pos = 0;
271 textview->show_all_headers = FALSE;
272 textview->last_buttonpress = GDK_NOTHING;
277 void textview_init(TextView *textview)
279 gtkut_widget_disable_theme_engine(textview->text_sb);
280 gtkut_widget_disable_theme_engine(textview->text_mb);
281 textview_update_message_colors();
282 textview_set_all_headers(textview, FALSE);
283 textview_set_font(textview, NULL);
286 void textview_update_message_colors(void)
288 GdkColor black = {0, 0, 0, 0};
290 if (prefs_common.enable_color) {
291 /* grab the quote colors, converting from an int to a GdkColor */
292 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
294 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
296 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
298 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
300 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
303 quote_colors[0] = quote_colors[1] = quote_colors[2] =
304 uri_color = emphasis_color = signature_color = black;
308 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
314 if ((fp = fopen(file, "rb")) == NULL) {
315 FILE_OP_ERROR(file, "fopen");
319 textview_clear(textview);
321 text = GTK_STEXT(textview->text);
323 gtk_stext_freeze(text);
326 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) perror("fseek");
327 headers = textview_scan_header(textview, fp);
329 textview_show_header(textview, headers);
330 procheader_header_array_destroy(headers);
331 textview->body_pos = gtk_stext_get_length(text);
334 textview_add_parts(textview, mimeinfo);
336 gtk_stext_thaw(text);
341 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
345 g_return_if_fail(mimeinfo != NULL);
346 g_return_if_fail(fp != NULL);
348 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
349 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822"))) {
350 textview_clear(textview);
351 textview_add_parts(textview, mimeinfo);
355 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
358 headers = textview_scan_header(textview, fp);
361 text = GTK_STEXT(textview->text);
363 gtk_stext_freeze(text);
364 textview_clear(textview);
368 textview_show_header(textview, headers);
369 procheader_header_array_destroy(headers);
370 textview->body_pos = gtk_stext_get_length(text);
372 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
375 if (mimeinfo->type == MIMETYPE_MULTIPART)
376 textview_add_parts(textview, mimeinfo);
378 textview_write_body(textview, mimeinfo);
380 gtk_stext_thaw(text);
383 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
385 GtkSText *text = GTK_STEXT(textview->text);
387 GPtrArray *headers = NULL;
391 g_return_if_fail(mimeinfo != NULL);
393 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
395 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822")) {
398 fp = fopen(mimeinfo->data.filename, "rb");
399 fseek(fp, mimeinfo->offset, SEEK_SET);
400 headers = textview_scan_header(textview, fp);
402 gtk_stext_freeze(text);
403 if (gtk_stext_get_length(text) > 0)
404 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
405 textview_show_header(textview, headers);
406 procheader_header_array_destroy(headers);
407 gtk_stext_thaw(text);
413 gtk_stext_freeze(text);
415 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
416 content_type = procmime_get_content_type_str(mimeinfo->type,
419 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
421 g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n",
422 name, content_type, mimeinfo->length);
424 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
425 content_type, mimeinfo->length);
427 g_free(content_type);
429 if (mimeinfo->type != MIMETYPE_TEXT) {
430 gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
431 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
432 if (prefs_common.display_header && (gtk_stext_get_length(text) > 0))
433 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
435 textview_write_body(textview, mimeinfo);
438 gtk_stext_thaw(text);
441 static void recursive_add_parts(TextView *textview, GNode *node)
446 mimeinfo = (MimeInfo *) node->data;
448 textview_add_part(textview, mimeinfo);
450 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
451 (mimeinfo->type != MIMETYPE_MESSAGE))
454 if (g_strcasecmp(mimeinfo->subtype, "alternative") == 0) {
455 GNode * prefered_body;
463 prefered_body = NULL;
466 for(iter = g_node_first_child(node) ; iter != NULL ;
467 iter = g_node_next_sibling(iter)) {
472 submime = (MimeInfo *) iter->data;
473 if (submime->type == MIMETYPE_TEXT)
476 if (submime->subtype != NULL) {
477 if (g_strcasecmp(submime->subtype, "plain") == 0)
481 if (score > prefered_score) {
482 prefered_score = score;
483 prefered_body = iter;
487 if (prefered_body != NULL) {
488 recursive_add_parts(textview, prefered_body);
492 for(iter = g_node_first_child(node) ; iter != NULL ;
493 iter = g_node_next_sibling(iter)) {
494 recursive_add_parts(textview, iter);
499 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
501 g_return_if_fail(mimeinfo != NULL);
503 recursive_add_parts(textview, mimeinfo->node);
506 #define TEXT_INSERT(str) \
507 gtk_stext_insert(text, textview->msgfont, NULL, NULL, str, -1)
509 void textview_show_error(TextView *textview)
513 textview_set_font(textview, NULL);
514 text = GTK_STEXT(textview->text);
515 textview_clear(textview);
517 gtk_stext_freeze(text);
519 TEXT_INSERT(_("This message can't be displayed.\n"));
521 gtk_stext_thaw(text);
524 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
528 if (!partinfo) return;
530 textview_set_font(textview, NULL);
531 text = GTK_STEXT(textview->text);
532 textview_clear(textview);
534 gtk_stext_freeze(text);
536 TEXT_INSERT(_("The following can be performed on this part by "));
537 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
539 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
540 TEXT_INSERT(_(" To display as text select 'Display as text' "));
541 TEXT_INSERT(_("(Shortcut key: 't')\n"));
542 TEXT_INSERT(_(" To open with an external program select 'Open' "));
543 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
544 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
545 TEXT_INSERT(_("mouse button),\n"));
546 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
548 gtk_stext_thaw(text);
553 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
558 const gchar *charset;
560 if (textview->messageview->forced_charset)
561 charset = textview->messageview->forced_charset;
563 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
565 textview_set_font(textview, charset);
567 conv = conv_code_converter_new(charset);
569 procmime_force_encoding(textview->messageview->forced_encoding);
571 textview->is_in_signature = FALSE;
573 procmime_decode_content(mimeinfo);
575 if (!g_strcasecmp(mimeinfo->subtype, "html")) {
578 filename = procmime_get_tmp_file_name(mimeinfo);
579 if (procmime_get_part(filename, mimeinfo) == 0) {
580 tmpfp = fopen(filename, "rb");
581 textview_show_html(textview, tmpfp, conv);
586 } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
589 filename = procmime_get_tmp_file_name(mimeinfo);
590 if (procmime_get_part(filename, mimeinfo) == 0) {
591 tmpfp = fopen(filename, "rb");
592 textview_show_ertf(textview, tmpfp, conv);
598 tmpfp = fopen(mimeinfo->data.filename, "rb");
599 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
600 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
601 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
602 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
603 textview_write_line(textview, buf, conv);
607 conv_code_converter_destroy(conv);
608 procmime_force_encoding(0);
611 static void textview_show_html(TextView *textview, FILE *fp,
617 parser = html_parser_new(fp, conv);
618 g_return_if_fail(parser != NULL);
620 while ((str = html_parse(parser)) != NULL) {
621 if (parser->state == HTML_HREF) {
622 /* first time : get and copy the URL */
623 if (parser->href == NULL) {
624 /* ALF - the sylpheed html parser returns an empty string,
625 * if still inside an <a>, but already parsed past HREF */
626 str = strtok(str, " ");
628 parser->href = g_strdup(str);
629 /* the URL may (or not) be followed by the
631 str = strtok(NULL, "");
635 textview_write_link(textview, str, parser->href, NULL);
637 textview_write_line(textview, str, NULL);
639 html_parser_destroy(parser);
642 static void textview_show_ertf(TextView *textview, FILE *fp,
648 parser = ertf_parser_new(fp, conv);
649 g_return_if_fail(parser != NULL);
651 while ((str = ertf_parse(parser)) != NULL) {
652 textview_write_line(textview, str, NULL);
655 ertf_parser_destroy(parser);
658 /* get_uri_part() - retrieves a URI starting from scanpos.
659 Returns TRUE if succesful */
660 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
661 const gchar **bp, const gchar **ep)
665 g_return_val_if_fail(start != NULL, FALSE);
666 g_return_val_if_fail(scanpos != NULL, FALSE);
667 g_return_val_if_fail(bp != NULL, FALSE);
668 g_return_val_if_fail(ep != NULL, FALSE);
672 /* find end point of URI */
673 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
674 if (!isgraph(*(const guchar *)ep_) ||
675 !IS_ASCII(*(const guchar *)ep_) ||
676 strchr("()<>\"", *ep_))
680 /* no punctuation at end of string */
682 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
683 * should pass some URI type to this function and decide on that whether
684 * to perform punctuation stripping */
686 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
688 for (; ep_ - 1 > scanpos + 1 &&
689 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
700 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
702 return g_strndup(bp, ep - bp);
705 /* valid mail address characters */
706 #define IS_RFC822_CHAR(ch) \
711 !strchr("(),;<>\"", (ch)))
713 /* alphabet and number within 7bit ASCII */
714 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && isalnum(ch))
715 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
717 static GHashTable *create_domain_tab(void)
719 static const gchar *toplvl_domains [] = {
721 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
722 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
723 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
724 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
725 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
726 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
727 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
728 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
729 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
730 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
731 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
732 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
733 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
734 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
735 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
736 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
737 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
738 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
739 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
740 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
741 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
742 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
743 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
744 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
745 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
746 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
749 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
751 g_return_val_if_fail(htab, NULL);
752 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
753 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
757 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
759 const gint MAX_LVL_DOM_NAME_LEN = 6;
760 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
761 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
764 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
767 for (p = buf; p < m && first < last; *p++ = *first++)
771 return g_hash_table_lookup(tab, buf) != NULL;
774 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
775 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
776 const gchar **bp, const gchar **ep)
778 /* more complex than the uri part because we need to scan back and forward starting from
779 * the scan position. */
780 gboolean result = FALSE;
781 const gchar *bp_ = NULL;
782 const gchar *ep_ = NULL;
783 static GHashTable *dom_tab;
784 const gchar *last_dot = NULL;
785 const gchar *prelast_dot = NULL;
786 const gchar *last_tld_char = NULL;
788 /* the informative part of the email address (describing the name
789 * of the email address owner) may contain quoted parts. the
790 * closure stack stores the last encountered quotes. */
791 gchar closure_stack[128];
792 gchar *ptr = closure_stack;
794 g_return_val_if_fail(start != NULL, FALSE);
795 g_return_val_if_fail(scanpos != NULL, FALSE);
796 g_return_val_if_fail(bp != NULL, FALSE);
797 g_return_val_if_fail(ep != NULL, FALSE);
800 dom_tab = create_domain_tab();
801 g_return_val_if_fail(dom_tab, FALSE);
803 /* scan start of address */
804 for (bp_ = scanpos - 1;
805 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
808 /* TODO: should start with an alnum? */
810 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
813 if (bp_ != scanpos) {
814 /* scan end of address */
815 for (ep_ = scanpos + 1;
816 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
818 prelast_dot = last_dot;
820 if (*(last_dot + 1) == '.') {
821 if (prelast_dot == NULL)
823 last_dot = prelast_dot;
828 /* TODO: really should terminate with an alnum? */
829 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
834 if (last_dot == NULL)
837 last_dot = prelast_dot;
838 if (last_dot == NULL || (scanpos + 1 >= last_dot))
842 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
843 if (*last_tld_char == '?')
846 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
853 if (!result) return FALSE;
855 if (*ep_ && *(bp_ - 1) == '"' && *(ep_) == '"'
856 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
857 && IS_RFC822_CHAR(*(ep_ + 3))) {
858 /* this informative part with an @ in it is
859 * followed by the email address */
862 /* go to matching '>' (or next non-rfc822 char, like \n) */
863 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
866 /* include the bracket */
867 if (*ep_ == '>') ep_++;
869 /* include the leading quote */
877 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
878 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
881 /* see if this is <bracketed>; in this case we also scan for the informative part. */
882 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
885 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
886 #define IN_STACK() (ptr > closure_stack)
887 /* has underrun check */
888 #define POP_STACK() if(IN_STACK()) --ptr
889 /* has overrun check */
890 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
891 /* has underrun check */
892 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
896 /* scan for the informative part. */
897 for (bp_ -= 2; bp_ >= start; bp_--) {
898 /* if closure on the stack keep scanning */
899 if (PEEK_STACK() == *bp_) {
903 if (*bp_ == '\'' || *bp_ == '"') {
908 /* if nothing in the closure stack, do the special conditions
909 * the following if..else expression simply checks whether
910 * a token is acceptable. if not acceptable, the clause
911 * should terminate the loop with a 'break' */
914 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
915 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
916 /* hyphens are allowed, but only in
918 } else if (!ispunct(*bp_)) {
919 /* but anything not being a punctiation
922 break; /* anything else is rejected */
935 /* scan forward (should start with an alnum) */
936 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
946 #undef IS_ASCII_ALNUM
947 #undef IS_RFC822_CHAR
949 static gchar *make_email_string(const gchar *bp, const gchar *ep)
951 /* returns a mailto: URI; mailto: is also used to detect the
952 * uri type later on in the button_pressed signal handler */
956 tmp = g_strndup(bp, ep - bp);
957 result = g_strconcat("mailto:", tmp, NULL);
963 static gchar *make_http_string(const gchar *bp, const gchar *ep)
965 /* returns an http: URI; */
969 tmp = g_strndup(bp, ep - bp);
970 result = g_strconcat("http://", tmp, NULL);
976 #define ADD_TXT_POS(bp_, ep_, pti_) \
977 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
979 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
982 g_warning("alloc error scanning URIs\n"); \
983 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
988 /* textview_make_clickable_parts() - colorizes clickable parts */
989 static void textview_make_clickable_parts(TextView *textview,
993 const gchar *linebuf)
995 /* parse table - in order of priority */
997 const gchar *needle; /* token */
999 /* token search function */
1000 gchar *(*search) (const gchar *haystack,
1001 const gchar *needle);
1002 /* part parsing function */
1003 gboolean (*parse) (const gchar *start,
1004 const gchar *scanpos,
1007 /* part to URI function */
1008 gchar *(*build_uri) (const gchar *bp,
1012 static struct table parser[] = {
1013 {"http://", strcasestr, get_uri_part, make_uri_string},
1014 {"https://", strcasestr, get_uri_part, make_uri_string},
1015 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1016 {"www.", strcasestr, get_uri_part, make_http_string},
1017 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1018 {"@", strcasestr, get_email_part, make_email_string}
1020 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1023 const gchar *walk, *bp, *ep;
1026 const gchar *bp, *ep; /* text position */
1027 gint pti; /* index in parse table */
1028 struct txtpos *next; /* next */
1029 } head = {NULL, NULL, 0, NULL}, *last = &head;
1031 GtkSText *text = GTK_STEXT(textview->text);
1033 /* parse for clickable parts, and build a list of begin and end positions */
1034 for (walk = linebuf, n = 0;;) {
1035 gint last_index = PARSE_ELEMS;
1036 gchar *scanpos = NULL;
1038 /* FIXME: this looks phony. scanning for anything in the parse table */
1039 for (n = 0; n < PARSE_ELEMS; n++) {
1042 tmp = parser[n].search(walk, parser[n].needle);
1044 if (scanpos == NULL || tmp < scanpos) {
1052 /* check if URI can be parsed */
1053 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1054 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1055 ADD_TXT_POS(bp, ep, last_index);
1059 strlen(parser[last_index].needle);
1064 /* colorize this line */
1066 const gchar *normal_text = linebuf;
1069 for (last = head.next; last != NULL;
1070 normal_text = last->ep, last = last->next) {
1072 uri = g_new(RemoteURI, 1);
1073 if (last->bp - normal_text > 0)
1074 gtk_stext_insert(text, font,
1077 last->bp - normal_text);
1078 uri->uri = parser[last->pti].build_uri(last->bp,
1080 uri->start = gtk_stext_get_point(text);
1081 gtk_stext_insert(text, font, uri_color,
1082 NULL, last->bp, last->ep - last->bp);
1083 uri->end = gtk_stext_get_point(text);
1084 textview->uri_list =
1085 g_slist_append(textview->uri_list, uri);
1089 gtk_stext_insert(text, font, fg_color,
1090 NULL, normal_text, -1);
1092 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1097 static void textview_write_line(TextView *textview, const gchar *str,
1098 CodeConverter *conv)
1100 GtkSText *text = GTK_STEXT(textview->text);
1101 gchar buf[BUFFSIZE];
1103 gint quotelevel = -1;
1106 if (textview->text_is_mb)
1107 conv_localetodisp(buf, sizeof(buf), str);
1109 strncpy2(buf, str, sizeof(buf));
1110 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1111 conv_localetodisp(buf, sizeof(buf), str);
1112 else if (textview->text_is_mb)
1113 conv_unreadable_locale(buf);
1116 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1119 /* change color of quotation
1120 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1121 Up to 3 levels of quotations are detected, and each
1122 level is colored using a different color. */
1123 if (prefs_common.enable_color
1124 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1125 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1127 /* set up the correct foreground color */
1128 if (quotelevel > 2) {
1129 /* recycle colors */
1130 if (prefs_common.recycle_quote_colors)
1137 if (quotelevel == -1)
1140 fg_color = "e_colors[quotelevel];
1142 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1143 fg_color = &signature_color;
1144 textview->is_in_signature = TRUE;
1147 if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1148 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1150 if (prefs_common.enable_color)
1151 textview_make_clickable_parts(textview, textview->msgfont,
1152 fg_color, &uri_color, buf);
1154 textview_make_clickable_parts(textview, textview->msgfont,
1155 fg_color, NULL, buf);
1158 void textview_write_link(TextView *textview, const gchar *str,
1159 const gchar *uri, CodeConverter *conv)
1161 GdkColor *link_color = NULL;
1162 GtkSText *text = GTK_STEXT(textview->text);
1163 gchar buf[BUFFSIZE];
1171 if (textview->text_is_mb)
1172 conv_localetodisp(buf, sizeof(buf), str);
1174 strncpy2(buf, str, sizeof(buf));
1175 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1176 conv_localetodisp(buf, sizeof(buf), str);
1177 else if (textview->text_is_mb)
1178 conv_unreadable_locale(buf);
1182 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1183 gtk_stext_insert(text, textview->msgfont, NULL, NULL, bufp, 1);
1185 if (prefs_common.enable_color) {
1186 link_color = &uri_color;
1188 r_uri = g_new(RemoteURI, 1);
1189 r_uri->uri = g_strdup(uri);
1190 r_uri->start = gtk_stext_get_point(text);
1191 gtk_stext_insert(text, textview->msgfont, link_color, NULL, bufp, -1);
1192 r_uri->end = gtk_stext_get_point(text);
1193 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1196 void textview_clear(TextView *textview)
1198 GtkSText *text = GTK_STEXT(textview->text);
1200 gtk_stext_freeze(text);
1201 gtk_stext_set_point(text, 0);
1202 gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1203 gtk_stext_thaw(text);
1205 TEXTVIEW_STATUSBAR_POP(textview);
1206 textview_uri_list_remove_all(textview->uri_list);
1207 textview->uri_list = NULL;
1209 textview->body_pos = 0;
1210 textview->cur_pos = 0;
1213 void textview_destroy(TextView *textview)
1215 textview_uri_list_remove_all(textview->uri_list);
1216 textview->uri_list = NULL;
1218 if (!textview->scrolledwin_sb->parent)
1219 gtk_widget_destroy(textview->scrolledwin_sb);
1220 if (!textview->scrolledwin_mb->parent)
1221 gtk_widget_destroy(textview->scrolledwin_mb);
1223 if (textview->msgfont)
1224 gdk_font_unref(textview->msgfont);
1225 if (textview->boldfont)
1226 gdk_font_unref(textview->boldfont);
1231 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1233 textview->show_all_headers = all_headers;
1236 void textview_set_font(TextView *textview, const gchar *codeset)
1238 gboolean use_fontset = TRUE;
1240 /* In multi-byte mode, GtkSText can't display 8bit characters
1241 correctly, so it must be single-byte mode. */
1242 if (MB_CUR_MAX > 1) {
1243 if (codeset && conv_get_current_charset() != C_UTF_8) {
1244 if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1245 !g_strcasecmp(codeset, "BALTIC"))
1246 use_fontset = FALSE;
1247 else if (conv_get_current_charset() != C_EUC_JP &&
1248 (!g_strncasecmp(codeset, "KOI8-", 5) ||
1249 !g_strncasecmp(codeset, "CP", 2) ||
1250 !g_strncasecmp(codeset, "WINDOWS-", 8)))
1251 use_fontset = FALSE;
1254 use_fontset = FALSE;
1256 if (textview->text_is_mb && !use_fontset) {
1259 parent = textview->scrolledwin_mb->parent;
1260 gtkut_container_remove(GTK_CONTAINER(parent),
1261 textview->scrolledwin_mb);
1262 gtk_container_add(GTK_CONTAINER(parent),
1263 textview->scrolledwin_sb);
1265 textview->text = textview->text_sb;
1266 textview->text_is_mb = FALSE;
1267 } else if (!textview->text_is_mb && use_fontset) {
1270 parent = textview->scrolledwin_sb->parent;
1271 gtkut_container_remove(GTK_CONTAINER(parent),
1272 textview->scrolledwin_sb);
1273 gtk_container_add(GTK_CONTAINER(parent),
1274 textview->scrolledwin_mb);
1276 textview->text = textview->text_mb;
1277 textview->text_is_mb = TRUE;
1280 if (prefs_common.textfont) {
1285 text_mb_font->ascent = text_mb_font_orig_ascent;
1286 text_mb_font->descent = text_mb_font_orig_descent;
1288 font = gdk_fontset_load(prefs_common.textfont);
1289 if (font && text_mb_font != font) {
1291 gdk_font_unref(text_mb_font);
1292 text_mb_font = font;
1293 text_mb_font_orig_ascent = font->ascent;
1294 text_mb_font_orig_descent = font->descent;
1298 text_sb_font->ascent = text_sb_font_orig_ascent;
1299 text_sb_font->descent = text_sb_font_orig_descent;
1302 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1304 font = gtkut_font_load_from_fontset
1305 (prefs_common.textfont);
1306 if (font && text_sb_font != font) {
1308 gdk_font_unref(text_sb_font);
1309 text_sb_font = font;
1310 text_sb_font_orig_ascent = font->ascent;
1311 text_sb_font_orig_descent = font->descent;
1316 gint ascent, descent;
1318 descent = prefs_common.line_space / 2;
1319 ascent = prefs_common.line_space - descent;
1320 font->ascent += ascent;
1321 font->descent += descent;
1323 if (textview->msgfont)
1324 gdk_font_unref(textview->msgfont);
1325 textview->msgfont = font;
1330 if (!textview->boldfont && prefs_common.boldfont)
1331 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1333 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1336 void textview_set_text(TextView *textview, const gchar *text)
1340 g_return_if_fail(textview != NULL);
1341 g_return_if_fail(text != NULL);
1343 textview_clear(textview);
1345 stext = GTK_STEXT(textview->text);
1346 gtk_stext_freeze(stext);
1347 gtk_stext_insert(stext, textview->msgfont, NULL, NULL, text, strlen(text));
1348 gtk_stext_thaw(stext);
1364 H_ORGANIZATION = 11,
1367 void textview_set_position(TextView *textview, gint pos)
1371 gtk_stext_get_length(GTK_STEXT(textview->text));
1373 textview->cur_pos = pos;
1377 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1379 gchar buf[BUFFSIZE];
1380 GPtrArray *headers, *sorted_headers;
1381 GSList *disphdr_list;
1385 g_return_val_if_fail(fp != NULL, NULL);
1387 if (textview->show_all_headers)
1388 return procheader_get_header_array_asis(fp);
1390 if (!prefs_common.display_header) {
1391 while (fgets(buf, sizeof(buf), fp) != NULL)
1392 if (buf[0] == '\r' || buf[0] == '\n') break;
1396 headers = procheader_get_header_array_asis(fp);
1398 sorted_headers = g_ptr_array_new();
1400 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1401 disphdr_list = disphdr_list->next) {
1402 DisplayHeaderProp *dp =
1403 (DisplayHeaderProp *)disphdr_list->data;
1405 for (i = 0; i < headers->len; i++) {
1406 header = g_ptr_array_index(headers, i);
1408 if (procheader_headername_equal(header->name,
1411 procheader_header_free(header);
1413 g_ptr_array_add(sorted_headers, header);
1415 g_ptr_array_remove_index(headers, i);
1421 if (prefs_common.show_other_header) {
1422 for (i = 0; i < headers->len; i++) {
1423 header = g_ptr_array_index(headers, i);
1424 g_ptr_array_add(sorted_headers, header);
1426 g_ptr_array_free(headers, TRUE);
1428 procheader_header_array_destroy(headers);
1431 return sorted_headers;
1434 static void textview_show_header(TextView *textview, GPtrArray *headers)
1436 GtkSText *text = GTK_STEXT(textview->text);
1440 g_return_if_fail(headers != NULL);
1442 gtk_stext_freeze(text);
1444 for (i = 0; i < headers->len; i++) {
1445 header = g_ptr_array_index(headers, i);
1446 g_return_if_fail(header->name != NULL);
1448 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1450 if (header->name[strlen(header->name) - 1] != ' ')
1451 gtk_stext_insert(text, textview->boldfont,
1452 NULL, NULL, " ", 1);
1454 if (procheader_headername_equal(header->name, "Subject") ||
1455 procheader_headername_equal(header->name, "From") ||
1456 procheader_headername_equal(header->name, "To") ||
1457 procheader_headername_equal(header->name, "Cc"))
1458 unfold_line(header->body);
1460 if (textview->text_is_mb == TRUE)
1461 conv_unreadable_locale(header->body);
1463 if (prefs_common.enable_color &&
1464 (procheader_headername_equal(header->name, "X-Mailer") ||
1465 procheader_headername_equal(header->name,
1467 strstr(header->body, "Sylpheed") != NULL)
1468 gtk_stext_insert(text, textview->msgfont, &emphasis_color, NULL,
1470 else if (prefs_common.enable_color) {
1471 textview_make_clickable_parts(textview,
1472 textview->msgfont, NULL, &uri_color,
1475 textview_make_clickable_parts(textview,
1476 textview->msgfont, NULL, NULL,
1479 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1482 gtk_stext_thaw(text);
1485 gboolean textview_search_string(TextView *textview, const gchar *str,
1488 GtkSText *text = GTK_STEXT(textview->text);
1492 g_return_val_if_fail(str != NULL, FALSE);
1494 len = get_mbs_len(str);
1495 g_return_val_if_fail(len >= 0, FALSE);
1497 pos = textview->cur_pos;
1498 if (pos < textview->body_pos)
1499 pos = textview->body_pos;
1501 if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1502 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1503 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1504 textview_set_position(textview, pos + len);
1511 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1514 GtkSText *text = GTK_STEXT(textview->text);
1519 gboolean found = FALSE;
1521 g_return_val_if_fail(str != NULL, FALSE);
1523 wcs = strdup_mbstowcs(str);
1524 g_return_val_if_fail(wcs != NULL, FALSE);
1526 pos = textview->cur_pos;
1527 text_len = gtk_stext_get_length(text);
1528 if (text_len - textview->body_pos < len) {
1532 if (pos <= textview->body_pos || text_len - pos < len)
1533 pos = text_len - len;
1535 for (; pos >= textview->body_pos; pos--) {
1536 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1538 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1539 gtk_editable_select_region(GTK_EDITABLE(text),
1541 textview_set_position(textview, pos - 1);
1545 if (pos == textview->body_pos) break;
1552 void textview_scroll_one_line(TextView *textview, gboolean up)
1554 GtkSText *text = GTK_STEXT(textview->text);
1557 if (prefs_common.enable_smooth_scroll) {
1558 textview_smooth_scroll_one_line(textview, up);
1563 upper = text->vadj->upper - text->vadj->page_size;
1564 if (text->vadj->value < upper) {
1565 text->vadj->value +=
1566 text->vadj->step_increment * 4;
1568 MIN(text->vadj->value, upper);
1569 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1573 if (text->vadj->value > 0.0) {
1574 text->vadj->value -=
1575 text->vadj->step_increment * 4;
1577 MAX(text->vadj->value, 0.0);
1578 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1584 gboolean textview_scroll_page(TextView *textview, gboolean up)
1586 GtkSText *text = GTK_STEXT(textview->text);
1590 if (prefs_common.enable_smooth_scroll)
1591 return textview_smooth_scroll_page(textview, up);
1593 if (prefs_common.scroll_halfpage)
1594 page_incr = text->vadj->page_increment / 2;
1596 page_incr = text->vadj->page_increment;
1599 upper = text->vadj->upper - text->vadj->page_size;
1600 if (text->vadj->value < upper) {
1601 text->vadj->value += page_incr;
1602 text->vadj->value = MIN(text->vadj->value, upper);
1603 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1608 if (text->vadj->value > 0.0) {
1609 text->vadj->value -= page_incr;
1610 text->vadj->value = MAX(text->vadj->value, 0.0);
1611 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1620 static void textview_smooth_scroll_do(TextView *textview,
1621 gfloat old_value, gfloat last_value,
1624 GtkSText *text = GTK_STEXT(textview->text);
1629 if (old_value < last_value) {
1630 change_value = last_value - old_value;
1633 change_value = old_value - last_value;
1637 gdk_key_repeat_disable();
1639 for (i = step; i <= change_value; i += step) {
1640 text->vadj->value = old_value + (up ? -i : i);
1641 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1645 text->vadj->value = last_value;
1646 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1648 gdk_key_repeat_restore();
1651 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1653 GtkSText *text = GTK_STEXT(textview->text);
1659 upper = text->vadj->upper - text->vadj->page_size;
1660 if (text->vadj->value < upper) {
1661 old_value = text->vadj->value;
1662 last_value = text->vadj->value +
1663 text->vadj->step_increment * 4;
1664 last_value = MIN(last_value, upper);
1666 textview_smooth_scroll_do(textview, old_value,
1668 prefs_common.scroll_step);
1671 if (text->vadj->value > 0.0) {
1672 old_value = text->vadj->value;
1673 last_value = text->vadj->value -
1674 text->vadj->step_increment * 4;
1675 last_value = MAX(last_value, 0.0);
1677 textview_smooth_scroll_do(textview, old_value,
1679 prefs_common.scroll_step);
1684 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1686 GtkSText *text = GTK_STEXT(textview->text);
1692 if (prefs_common.scroll_halfpage)
1693 page_incr = text->vadj->page_increment / 2;
1695 page_incr = text->vadj->page_increment;
1698 upper = text->vadj->upper - text->vadj->page_size;
1699 if (text->vadj->value < upper) {
1700 old_value = text->vadj->value;
1701 last_value = text->vadj->value + page_incr;
1702 last_value = MIN(last_value, upper);
1704 textview_smooth_scroll_do(textview, old_value,
1706 prefs_common.scroll_step);
1710 if (text->vadj->value > 0.0) {
1711 old_value = text->vadj->value;
1712 last_value = text->vadj->value - page_incr;
1713 last_value = MAX(last_value, 0.0);
1715 textview_smooth_scroll_do(textview, old_value,
1717 prefs_common.scroll_step);
1725 #define KEY_PRESS_EVENT_STOP() \
1726 if (gtk_signal_n_emissions_by_name \
1727 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1728 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1729 "key_press_event"); \
1732 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1735 SummaryView *summaryview = NULL;
1736 MessageView *messageview = textview->messageview;
1738 if (!event) return FALSE;
1739 if (messageview->mainwin)
1740 summaryview = messageview->mainwin->summaryview;
1742 switch (event->keyval) {
1757 summary_pass_key_press_event(summaryview, event);
1759 textview_scroll_page
1762 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1765 textview_scroll_page(textview, TRUE);
1768 textview_scroll_one_line
1769 (textview, (event->state &
1770 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1774 summary_pass_key_press_event(summaryview, event);
1779 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1780 KEY_PRESS_EVENT_STOP();
1781 mimeview_pass_key_press_event(messageview->mimeview,
1785 /* possible fall through */
1788 event->window != messageview->mainwin->window->window) {
1789 GdkEventKey tmpev = *event;
1791 tmpev.window = messageview->mainwin->window->window;
1792 KEY_PRESS_EVENT_STOP();
1793 gtk_widget_event(messageview->mainwin->window,
1794 (GdkEvent *)&tmpev);
1802 static gint show_url_timeout_cb(gpointer data)
1804 TextView *textview = (TextView *)data;
1806 TEXTVIEW_STATUSBAR_POP(textview);
1807 textview->show_url_timeout_tag = 0;
1811 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1815 textview->last_buttonpress = event->type;
1819 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1823 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1826 ((event->button == 1)
1827 || event->button == 2 || event->button == 3)) {
1830 /* double click seems to set the cursor after the current
1831 * word. The cursor position needs fixing, otherwise the
1832 * last word of a clickable zone will not work */
1833 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1834 textview->cur_pos--;
1837 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1838 RemoteURI *uri = (RemoteURI *)cur->data;
1840 if (textview->cur_pos >= uri->start &&
1841 textview->cur_pos <= uri->end) {
1844 trimmed_uri = trim_string(uri->uri, 60);
1845 /* single click: display url in statusbar */
1846 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1847 if (textview->messageview->mainwin) {
1848 if (textview->show_url_timeout_tag != 0) {
1849 gtk_timeout_remove(textview->show_url_timeout_tag);
1850 TEXTVIEW_STATUSBAR_POP(textview);
1852 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1853 textview->show_url_timeout_tag = gtk_timeout_add
1854 (4000, show_url_timeout_cb, textview);
1856 } else if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1857 if (event->button == 3) {
1858 gchar *fromname, *fromaddress;
1861 fromaddress = g_strdup(uri->uri + 7);
1862 /* Hiroyuki: please put this function in utils.c! */
1863 fromname = procheader_get_fromname(fromaddress);
1864 extract_address(fromaddress);
1865 g_message("adding from textview %s <%s>", fromname, fromaddress);
1866 /* Add to address book - Match */
1867 addressbook_add_contact( fromname, fromaddress, NULL );
1869 g_free(fromaddress);
1872 PrefsAccount *account = NULL;
1874 if (textview->messageview && textview->messageview->msginfo &&
1875 textview->messageview->msginfo->folder) {
1876 FolderItem *folder_item;
1878 folder_item = textview->messageview->msginfo->folder;
1879 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1880 account = account_find_from_id(folder_item->prefs->default_account);
1882 compose_new(account, uri->uri + 7, NULL);
1885 if (textview_uri_security_check(textview, uri) == TRUE)
1887 prefs_common.uri_cmd);
1889 g_free(trimmed_uri);
1894 textview->last_buttonpress = event->type;
1899 *\brief Check to see if a web URL has been disguised as a different
1900 * URL (possible with HTML email).
1902 *\param uri The uri to check
1904 *\param textview The TextView the URL is contained in
1906 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1907 * it anyway, otherwise FALSE
1909 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
1912 gboolean retval = TRUE;
1914 if (is_uri_string(uri->uri) == FALSE)
1917 visible_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1918 uri->start, uri->end);
1919 if (visible_str == NULL)
1922 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
1924 gchar *visible_uri_path;
1926 uri_path = get_uri_path(uri->uri);
1927 visible_uri_path = get_uri_path(visible_str);
1928 if (strcmp(uri_path, visible_uri_path) != 0)
1932 if (retval == FALSE) {
1936 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1937 "the apparent URL (%s).\n"
1939 uri->uri, visible_str);
1940 aval = alertpanel_with_type(_("Warning"), msg, _("Yes"),
1941 _("No"), NULL, NULL, ALERT_WARNING);
1943 if (aval == G_ALERTDEFAULT)
1947 g_free(visible_str);
1952 static void textview_uri_list_remove_all(GSList *uri_list)
1956 for (cur = uri_list; cur != NULL; cur = cur->next) {
1958 g_free(((RemoteURI *)cur->data)->uri);
1963 g_slist_free(uri_list);