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 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
856 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
859 /* see if this is <bracketed>; in this case we also scan for the informative part. */
860 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
863 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
864 #define IN_STACK() (ptr > closure_stack)
865 /* has underrun check */
866 #define POP_STACK() if(IN_STACK()) --ptr
867 /* has overrun check */
868 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
869 /* has underrun check */
870 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
874 /* scan for the informative part. */
875 for (bp_ -= 2; bp_ >= start; bp_--) {
876 /* if closure on the stack keep scanning */
877 if (PEEK_STACK() == *bp_) {
881 if (*bp_ == '\'' || *bp_ == '"') {
886 /* if nothing in the closure stack, do the special conditions
887 * the following if..else expression simply checks whether
888 * a token is acceptable. if not acceptable, the clause
889 * should terminate the loop with a 'break' */
892 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
893 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
894 /* hyphens are allowed, but only in
896 } else if (!ispunct(*bp_)) {
897 /* but anything not being a punctiation
900 break; /* anything else is rejected */
913 /* scan forward (should start with an alnum) */
914 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
924 #undef IS_ASCII_ALNUM
925 #undef IS_RFC822_CHAR
927 static gchar *make_email_string(const gchar *bp, const gchar *ep)
929 /* returns a mailto: URI; mailto: is also used to detect the
930 * uri type later on in the button_pressed signal handler */
934 tmp = g_strndup(bp, ep - bp);
935 result = g_strconcat("mailto:", tmp, NULL);
941 static gchar *make_http_string(const gchar *bp, const gchar *ep)
943 /* returns an http: URI; */
947 tmp = g_strndup(bp, ep - bp);
948 result = g_strconcat("http://", tmp, NULL);
954 #define ADD_TXT_POS(bp_, ep_, pti_) \
955 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
957 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
960 g_warning("alloc error scanning URIs\n"); \
961 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
966 /* textview_make_clickable_parts() - colorizes clickable parts */
967 static void textview_make_clickable_parts(TextView *textview,
971 const gchar *linebuf)
973 /* parse table - in order of priority */
975 const gchar *needle; /* token */
977 /* token search function */
978 gchar *(*search) (const gchar *haystack,
979 const gchar *needle);
980 /* part parsing function */
981 gboolean (*parse) (const gchar *start,
982 const gchar *scanpos,
985 /* part to URI function */
986 gchar *(*build_uri) (const gchar *bp,
990 static struct table parser[] = {
991 {"http://", strcasestr, get_uri_part, make_uri_string},
992 {"https://", strcasestr, get_uri_part, make_uri_string},
993 {"ftp://", strcasestr, get_uri_part, make_uri_string},
994 {"www.", strcasestr, get_uri_part, make_http_string},
995 {"mailto:", strcasestr, get_uri_part, make_uri_string},
996 {"@", strcasestr, get_email_part, make_email_string}
998 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1001 const gchar *walk, *bp, *ep;
1004 const gchar *bp, *ep; /* text position */
1005 gint pti; /* index in parse table */
1006 struct txtpos *next; /* next */
1007 } head = {NULL, NULL, 0, NULL}, *last = &head;
1009 GtkSText *text = GTK_STEXT(textview->text);
1011 /* parse for clickable parts, and build a list of begin and end positions */
1012 for (walk = linebuf, n = 0;;) {
1013 gint last_index = PARSE_ELEMS;
1014 gchar *scanpos = NULL;
1016 /* FIXME: this looks phony. scanning for anything in the parse table */
1017 for (n = 0; n < PARSE_ELEMS; n++) {
1020 tmp = parser[n].search(walk, parser[n].needle);
1022 if (scanpos == NULL || tmp < scanpos) {
1030 /* check if URI can be parsed */
1031 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1032 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1033 ADD_TXT_POS(bp, ep, last_index);
1037 strlen(parser[last_index].needle);
1042 /* colorize this line */
1044 const gchar *normal_text = linebuf;
1047 for (last = head.next; last != NULL;
1048 normal_text = last->ep, last = last->next) {
1051 uri = g_new(RemoteURI, 1);
1052 if (last->bp - normal_text > 0)
1053 gtk_stext_insert(text, font,
1056 last->bp - normal_text);
1057 uri->uri = parser[last->pti].build_uri(last->bp,
1059 uri->start = gtk_stext_get_point(text);
1060 gtk_stext_insert(text, font, uri_color,
1061 NULL, last->bp, last->ep - last->bp);
1062 uri->end = gtk_stext_get_point(text);
1063 textview->uri_list =
1064 g_slist_append(textview->uri_list, uri);
1068 gtk_stext_insert(text, font, fg_color,
1069 NULL, normal_text, -1);
1071 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1076 static void textview_write_line(TextView *textview, const gchar *str,
1077 CodeConverter *conv)
1079 GtkSText *text = GTK_STEXT(textview->text);
1080 gchar buf[BUFFSIZE];
1082 gint quotelevel = -1;
1085 if (textview->text_is_mb)
1086 conv_localetodisp(buf, sizeof(buf), str);
1088 strncpy2(buf, str, sizeof(buf));
1089 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1090 conv_localetodisp(buf, sizeof(buf), str);
1091 else if (textview->text_is_mb)
1092 conv_unreadable_locale(buf);
1095 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1098 /* change color of quotation
1099 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1100 Up to 3 levels of quotations are detected, and each
1101 level is colored using a different color. */
1102 if (prefs_common.enable_color
1103 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1104 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1106 /* set up the correct foreground color */
1107 if (quotelevel > 2) {
1108 /* recycle colors */
1109 if (prefs_common.recycle_quote_colors)
1116 if (quotelevel == -1)
1119 fg_color = "e_colors[quotelevel];
1121 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1122 fg_color = &signature_color;
1123 textview->is_in_signature = TRUE;
1126 if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1127 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1129 if (prefs_common.enable_color)
1130 textview_make_clickable_parts(textview, textview->msgfont,
1131 fg_color, &uri_color, buf);
1133 textview_make_clickable_parts(textview, textview->msgfont,
1134 fg_color, NULL, buf);
1137 void textview_write_link(TextView *textview, const gchar *str,
1138 const gchar *uri, CodeConverter *conv)
1140 GdkColor *link_color = NULL;
1141 GtkSText *text = GTK_STEXT(textview->text);
1142 gchar buf[BUFFSIZE];
1150 if (textview->text_is_mb)
1151 conv_localetodisp(buf, sizeof(buf), str);
1153 strncpy2(buf, str, sizeof(buf));
1154 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1155 conv_localetodisp(buf, sizeof(buf), str);
1156 else if (textview->text_is_mb)
1157 conv_unreadable_locale(buf);
1161 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1162 gtk_stext_insert(text, textview->msgfont, NULL, NULL, bufp, 1);
1164 if (prefs_common.enable_color) {
1165 link_color = &uri_color;
1167 r_uri = g_new(RemoteURI, 1);
1168 r_uri->uri = g_strdup(uri);
1169 r_uri->start = gtk_stext_get_point(text);
1170 gtk_stext_insert(text, textview->msgfont, link_color, NULL, bufp, -1);
1171 r_uri->end = gtk_stext_get_point(text);
1172 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1175 void textview_clear(TextView *textview)
1177 GtkSText *text = GTK_STEXT(textview->text);
1179 gtk_stext_freeze(text);
1180 gtk_stext_set_point(text, 0);
1181 gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1182 gtk_stext_thaw(text);
1184 TEXTVIEW_STATUSBAR_POP(textview);
1185 textview_uri_list_remove_all(textview->uri_list);
1186 textview->uri_list = NULL;
1188 textview->body_pos = 0;
1189 textview->cur_pos = 0;
1192 void textview_destroy(TextView *textview)
1194 textview_uri_list_remove_all(textview->uri_list);
1195 textview->uri_list = NULL;
1197 if (!textview->scrolledwin_sb->parent)
1198 gtk_widget_destroy(textview->scrolledwin_sb);
1199 if (!textview->scrolledwin_mb->parent)
1200 gtk_widget_destroy(textview->scrolledwin_mb);
1202 if (textview->msgfont)
1203 gdk_font_unref(textview->msgfont);
1204 if (textview->boldfont)
1205 gdk_font_unref(textview->boldfont);
1210 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1212 textview->show_all_headers = all_headers;
1215 void textview_set_font(TextView *textview, const gchar *codeset)
1217 gboolean use_fontset = TRUE;
1219 /* In multi-byte mode, GtkSText can't display 8bit characters
1220 correctly, so it must be single-byte mode. */
1221 if (MB_CUR_MAX > 1) {
1222 if (codeset && conv_get_current_charset() != C_UTF_8) {
1223 if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1224 !g_strcasecmp(codeset, "BALTIC"))
1225 use_fontset = FALSE;
1226 else if (conv_get_current_charset() != C_EUC_JP &&
1227 (!g_strncasecmp(codeset, "KOI8-", 5) ||
1228 !g_strncasecmp(codeset, "CP", 2) ||
1229 !g_strncasecmp(codeset, "WINDOWS-", 8)))
1230 use_fontset = FALSE;
1233 use_fontset = FALSE;
1235 if (textview->text_is_mb && !use_fontset) {
1238 parent = textview->scrolledwin_mb->parent;
1239 gtkut_container_remove(GTK_CONTAINER(parent),
1240 textview->scrolledwin_mb);
1241 gtk_container_add(GTK_CONTAINER(parent),
1242 textview->scrolledwin_sb);
1244 textview->text = textview->text_sb;
1245 textview->text_is_mb = FALSE;
1246 } else if (!textview->text_is_mb && use_fontset) {
1249 parent = textview->scrolledwin_sb->parent;
1250 gtkut_container_remove(GTK_CONTAINER(parent),
1251 textview->scrolledwin_sb);
1252 gtk_container_add(GTK_CONTAINER(parent),
1253 textview->scrolledwin_mb);
1255 textview->text = textview->text_mb;
1256 textview->text_is_mb = TRUE;
1259 if (prefs_common.textfont) {
1264 text_mb_font->ascent = text_mb_font_orig_ascent;
1265 text_mb_font->descent = text_mb_font_orig_descent;
1267 font = gdk_fontset_load(prefs_common.textfont);
1268 if (font && text_mb_font != font) {
1270 gdk_font_unref(text_mb_font);
1271 text_mb_font = font;
1272 text_mb_font_orig_ascent = font->ascent;
1273 text_mb_font_orig_descent = font->descent;
1277 text_sb_font->ascent = text_sb_font_orig_ascent;
1278 text_sb_font->descent = text_sb_font_orig_descent;
1281 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1283 font = gtkut_font_load_from_fontset
1284 (prefs_common.textfont);
1285 if (font && text_sb_font != font) {
1287 gdk_font_unref(text_sb_font);
1288 text_sb_font = font;
1289 text_sb_font_orig_ascent = font->ascent;
1290 text_sb_font_orig_descent = font->descent;
1295 gint ascent, descent;
1297 descent = prefs_common.line_space / 2;
1298 ascent = prefs_common.line_space - descent;
1299 font->ascent += ascent;
1300 font->descent += descent;
1302 if (textview->msgfont)
1303 gdk_font_unref(textview->msgfont);
1304 textview->msgfont = font;
1309 if (!textview->boldfont && prefs_common.boldfont)
1310 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1312 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1315 void textview_set_text(TextView *textview, const gchar *text)
1319 g_return_if_fail(textview != NULL);
1320 g_return_if_fail(text != NULL);
1322 textview_clear(textview);
1324 stext = GTK_STEXT(textview->text);
1325 gtk_stext_freeze(stext);
1326 gtk_stext_insert(stext, textview->msgfont, NULL, NULL, text, strlen(text));
1327 gtk_stext_thaw(stext);
1343 H_ORGANIZATION = 11,
1346 void textview_set_position(TextView *textview, gint pos)
1350 gtk_stext_get_length(GTK_STEXT(textview->text));
1352 textview->cur_pos = pos;
1356 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1358 gchar buf[BUFFSIZE];
1359 GPtrArray *headers, *sorted_headers;
1360 GSList *disphdr_list;
1364 g_return_val_if_fail(fp != NULL, NULL);
1366 if (textview->show_all_headers)
1367 return procheader_get_header_array_asis(fp);
1369 if (!prefs_common.display_header) {
1370 while (fgets(buf, sizeof(buf), fp) != NULL)
1371 if (buf[0] == '\r' || buf[0] == '\n') break;
1375 headers = procheader_get_header_array_asis(fp);
1377 sorted_headers = g_ptr_array_new();
1379 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1380 disphdr_list = disphdr_list->next) {
1381 DisplayHeaderProp *dp =
1382 (DisplayHeaderProp *)disphdr_list->data;
1384 for (i = 0; i < headers->len; i++) {
1385 header = g_ptr_array_index(headers, i);
1387 if (procheader_headername_equal(header->name,
1390 procheader_header_free(header);
1392 g_ptr_array_add(sorted_headers, header);
1394 g_ptr_array_remove_index(headers, i);
1400 if (prefs_common.show_other_header) {
1401 for (i = 0; i < headers->len; i++) {
1402 header = g_ptr_array_index(headers, i);
1403 g_ptr_array_add(sorted_headers, header);
1405 g_ptr_array_free(headers, TRUE);
1407 procheader_header_array_destroy(headers);
1410 return sorted_headers;
1413 static void textview_show_header(TextView *textview, GPtrArray *headers)
1415 GtkSText *text = GTK_STEXT(textview->text);
1419 g_return_if_fail(headers != NULL);
1421 gtk_stext_freeze(text);
1423 for (i = 0; i < headers->len; i++) {
1424 header = g_ptr_array_index(headers, i);
1425 g_return_if_fail(header->name != NULL);
1427 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1429 if (header->name[strlen(header->name) - 1] != ' ')
1430 gtk_stext_insert(text, textview->boldfont,
1431 NULL, NULL, " ", 1);
1433 if (procheader_headername_equal(header->name, "Subject") ||
1434 procheader_headername_equal(header->name, "From") ||
1435 procheader_headername_equal(header->name, "To") ||
1436 procheader_headername_equal(header->name, "Cc"))
1437 unfold_line(header->body);
1439 if (textview->text_is_mb == TRUE)
1440 conv_unreadable_locale(header->body);
1442 if (prefs_common.enable_color &&
1443 (procheader_headername_equal(header->name, "X-Mailer") ||
1444 procheader_headername_equal(header->name,
1446 strstr(header->body, "Sylpheed") != NULL)
1447 gtk_stext_insert(text, textview->msgfont, &emphasis_color, NULL,
1449 else if (prefs_common.enable_color) {
1450 textview_make_clickable_parts(textview,
1451 textview->msgfont, NULL, &uri_color,
1454 textview_make_clickable_parts(textview,
1455 textview->msgfont, NULL, NULL,
1458 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1461 gtk_stext_thaw(text);
1464 gboolean textview_search_string(TextView *textview, const gchar *str,
1467 GtkSText *text = GTK_STEXT(textview->text);
1471 g_return_val_if_fail(str != NULL, FALSE);
1473 len = get_mbs_len(str);
1474 g_return_val_if_fail(len >= 0, FALSE);
1476 pos = textview->cur_pos;
1477 if (pos < textview->body_pos)
1478 pos = textview->body_pos;
1480 if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1481 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1482 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1483 textview_set_position(textview, pos + len);
1490 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1493 GtkSText *text = GTK_STEXT(textview->text);
1498 gboolean found = FALSE;
1500 g_return_val_if_fail(str != NULL, FALSE);
1502 wcs = strdup_mbstowcs(str);
1503 g_return_val_if_fail(wcs != NULL, FALSE);
1505 pos = textview->cur_pos;
1506 text_len = gtk_stext_get_length(text);
1507 if (text_len - textview->body_pos < len) {
1511 if (pos <= textview->body_pos || text_len - pos < len)
1512 pos = text_len - len;
1514 for (; pos >= textview->body_pos; pos--) {
1515 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1517 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1518 gtk_editable_select_region(GTK_EDITABLE(text),
1520 textview_set_position(textview, pos - 1);
1524 if (pos == textview->body_pos) break;
1531 void textview_scroll_one_line(TextView *textview, gboolean up)
1533 GtkSText *text = GTK_STEXT(textview->text);
1536 if (prefs_common.enable_smooth_scroll) {
1537 textview_smooth_scroll_one_line(textview, up);
1542 upper = text->vadj->upper - text->vadj->page_size;
1543 if (text->vadj->value < upper) {
1544 text->vadj->value +=
1545 text->vadj->step_increment * 4;
1547 MIN(text->vadj->value, upper);
1548 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1552 if (text->vadj->value > 0.0) {
1553 text->vadj->value -=
1554 text->vadj->step_increment * 4;
1556 MAX(text->vadj->value, 0.0);
1557 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1563 gboolean textview_scroll_page(TextView *textview, gboolean up)
1565 GtkSText *text = GTK_STEXT(textview->text);
1569 if (prefs_common.enable_smooth_scroll)
1570 return textview_smooth_scroll_page(textview, up);
1572 if (prefs_common.scroll_halfpage)
1573 page_incr = text->vadj->page_increment / 2;
1575 page_incr = text->vadj->page_increment;
1578 upper = text->vadj->upper - text->vadj->page_size;
1579 if (text->vadj->value < upper) {
1580 text->vadj->value += page_incr;
1581 text->vadj->value = MIN(text->vadj->value, upper);
1582 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1587 if (text->vadj->value > 0.0) {
1588 text->vadj->value -= page_incr;
1589 text->vadj->value = MAX(text->vadj->value, 0.0);
1590 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1599 static void textview_smooth_scroll_do(TextView *textview,
1600 gfloat old_value, gfloat last_value,
1603 GtkSText *text = GTK_STEXT(textview->text);
1608 if (old_value < last_value) {
1609 change_value = last_value - old_value;
1612 change_value = old_value - last_value;
1616 gdk_key_repeat_disable();
1618 for (i = step; i <= change_value; i += step) {
1619 text->vadj->value = old_value + (up ? -i : i);
1620 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1624 text->vadj->value = last_value;
1625 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1627 gdk_key_repeat_restore();
1630 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1632 GtkSText *text = GTK_STEXT(textview->text);
1638 upper = text->vadj->upper - text->vadj->page_size;
1639 if (text->vadj->value < upper) {
1640 old_value = text->vadj->value;
1641 last_value = text->vadj->value +
1642 text->vadj->step_increment * 4;
1643 last_value = MIN(last_value, upper);
1645 textview_smooth_scroll_do(textview, old_value,
1647 prefs_common.scroll_step);
1650 if (text->vadj->value > 0.0) {
1651 old_value = text->vadj->value;
1652 last_value = text->vadj->value -
1653 text->vadj->step_increment * 4;
1654 last_value = MAX(last_value, 0.0);
1656 textview_smooth_scroll_do(textview, old_value,
1658 prefs_common.scroll_step);
1663 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1665 GtkSText *text = GTK_STEXT(textview->text);
1671 if (prefs_common.scroll_halfpage)
1672 page_incr = text->vadj->page_increment / 2;
1674 page_incr = text->vadj->page_increment;
1677 upper = text->vadj->upper - text->vadj->page_size;
1678 if (text->vadj->value < upper) {
1679 old_value = text->vadj->value;
1680 last_value = text->vadj->value + page_incr;
1681 last_value = MIN(last_value, upper);
1683 textview_smooth_scroll_do(textview, old_value,
1685 prefs_common.scroll_step);
1689 if (text->vadj->value > 0.0) {
1690 old_value = text->vadj->value;
1691 last_value = text->vadj->value - page_incr;
1692 last_value = MAX(last_value, 0.0);
1694 textview_smooth_scroll_do(textview, old_value,
1696 prefs_common.scroll_step);
1704 #define KEY_PRESS_EVENT_STOP() \
1705 if (gtk_signal_n_emissions_by_name \
1706 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1707 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1708 "key_press_event"); \
1711 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1714 SummaryView *summaryview = NULL;
1715 MessageView *messageview = textview->messageview;
1717 if (!event) return FALSE;
1718 if (messageview->mainwin)
1719 summaryview = messageview->mainwin->summaryview;
1721 switch (event->keyval) {
1736 summary_pass_key_press_event(summaryview, event);
1738 textview_scroll_page(textview, FALSE);
1741 textview_scroll_page(textview, TRUE);
1744 textview_scroll_one_line(textview,
1745 (event->state & GDK_MOD1_MASK) != 0);
1749 summary_pass_key_press_event(summaryview, event);
1754 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1755 KEY_PRESS_EVENT_STOP();
1756 mimeview_pass_key_press_event(messageview->mimeview,
1760 /* possible fall through */
1763 event->window != messageview->mainwin->window->window) {
1764 GdkEventKey tmpev = *event;
1766 tmpev.window = messageview->mainwin->window->window;
1767 KEY_PRESS_EVENT_STOP();
1768 gtk_widget_event(messageview->mainwin->window,
1769 (GdkEvent *)&tmpev);
1777 static gint show_url_timeout_cb(gpointer data)
1779 TextView *textview = (TextView *)data;
1781 TEXTVIEW_STATUSBAR_POP(textview);
1782 textview->show_url_timeout_tag = 0;
1786 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1790 textview->last_buttonpress = event->type;
1794 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1798 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1801 ((event->button == 1)
1802 || event->button == 2 || event->button == 3)) {
1805 /* double click seems to set the cursor after the current
1806 * word. The cursor position needs fixing, otherwise the
1807 * last word of a clickable zone will not work */
1808 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1809 textview->cur_pos--;
1812 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1813 RemoteURI *uri = (RemoteURI *)cur->data;
1815 if (textview->cur_pos >= uri->start &&
1816 textview->cur_pos <= uri->end) {
1819 trimmed_uri = trim_string(uri->uri, 60);
1820 /* single click: display url in statusbar */
1821 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1822 if (textview->messageview->mainwin) {
1823 if (textview->show_url_timeout_tag != 0) {
1824 gtk_timeout_remove(textview->show_url_timeout_tag);
1825 TEXTVIEW_STATUSBAR_POP(textview);
1827 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1828 textview->show_url_timeout_tag = gtk_timeout_add
1829 (4000, show_url_timeout_cb, textview);
1831 } else if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1832 if (event->button == 3) {
1833 gchar *fromname, *fromaddress;
1836 fromaddress = g_strdup(uri->uri + 7);
1837 /* Hiroyuki: please put this function in utils.c! */
1838 fromname = procheader_get_fromname(fromaddress);
1839 extract_address(fromaddress);
1840 g_message("adding from textview %s <%s>", fromname, fromaddress);
1841 /* Add to address book - Match */
1842 addressbook_add_contact( fromname, fromaddress, NULL );
1844 g_free(fromaddress);
1847 PrefsAccount *account = NULL;
1849 if (textview->messageview && textview->messageview->msginfo &&
1850 textview->messageview->msginfo->folder) {
1851 FolderItem *folder_item;
1853 folder_item = textview->messageview->msginfo->folder;
1854 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1855 account = account_find_from_id(folder_item->prefs->default_account);
1857 compose_new(account, uri->uri + 7, NULL);
1860 if (textview_uri_security_check(textview, uri) == TRUE)
1862 prefs_common.uri_cmd);
1864 g_free(trimmed_uri);
1869 textview->last_buttonpress = event->type;
1874 *\brief Check to see if a web URL has been disguised as a different
1875 * URL (possible with HTML email).
1877 *\param uri The uri to check
1879 *\param textview The TextView the URL is contained in
1881 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1882 * it anyway, otherwise FALSE
1884 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
1887 gboolean retval = TRUE;
1889 if (is_uri_string(uri->uri) == FALSE)
1892 visible_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1893 uri->start, uri->end);
1894 if (visible_str == NULL)
1897 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
1899 gchar *visible_uri_path;
1901 uri_path = get_uri_path(uri->uri);
1902 visible_uri_path = get_uri_path(visible_str);
1903 if (strcmp(uri_path, visible_uri_path) != 0)
1907 if (retval == FALSE) {
1911 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1912 "the apparent URL (%s).\n"
1914 uri->uri, visible_str);
1915 aval = alertpanel_with_type(_("Warning"), msg, _("Yes"),
1916 _("No"), NULL, NULL, ALERT_WARNING);
1918 if (aval == G_ALERTDEFAULT)
1922 g_free(visible_str);
1927 static void textview_uri_list_remove_all(GSList *uri_list)
1931 for (cur = uri_list; cur != NULL; cur = cur->next) {
1933 g_free(((RemoteURI *)cur->data)->uri);
1938 g_slist_free(uri_list);