2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2003 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 GdkColor good_sig_color = {
109 static GdkColor nocheck_sig_color = {
116 static GdkColor bad_sig_color = {
123 static GdkFont *text_sb_font;
124 static GdkFont *text_mb_font;
125 static gint text_sb_font_orig_ascent;
126 static gint text_sb_font_orig_descent;
127 static gint text_mb_font_orig_ascent;
128 static gint text_mb_font_orig_descent;
129 static GdkFont *spacingfont;
131 static void textview_show_ertf (TextView *textview,
133 CodeConverter *conv);
134 static void textview_add_part (TextView *textview,
136 static void textview_add_parts (TextView *textview,
138 static void textview_write_body (TextView *textview,
140 const gchar *charset);
141 static void textview_show_html (TextView *textview,
143 CodeConverter *conv);
144 static void textview_write_line (TextView *textview,
146 CodeConverter *conv);
147 static void textview_write_link (TextView *textview,
150 CodeConverter *conv);
151 static GPtrArray *textview_scan_header (TextView *textview,
153 static void textview_show_header (TextView *textview,
156 static gint textview_key_pressed (GtkWidget *widget,
159 static gint textview_button_pressed (GtkWidget *widget,
160 GdkEventButton *event,
162 static gint textview_button_released (GtkWidget *widget,
163 GdkEventButton *event,
166 static void textview_uri_list_remove_all(GSList *uri_list);
168 static void textview_smooth_scroll_do (TextView *textview,
172 static void textview_smooth_scroll_one_line (TextView *textview,
174 static gboolean textview_smooth_scroll_page (TextView *textview,
178 TextView *textview_create(void)
182 GtkWidget *scrolledwin_sb;
183 GtkWidget *scrolledwin_mb;
187 debug_print("Creating text view...\n");
188 textview = g_new0(TextView, 1);
190 scrolledwin_sb = gtk_scrolled_window_new(NULL, NULL);
191 scrolledwin_mb = gtk_scrolled_window_new(NULL, NULL);
192 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_sb),
193 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
194 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_mb),
195 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
196 gtk_widget_set_usize(scrolledwin_sb, prefs_common.mainview_width, -1);
197 gtk_widget_set_usize(scrolledwin_mb, prefs_common.mainview_width, -1);
199 /* create GtkSText widgets for single-byte and multi-byte character */
200 text_sb = gtk_stext_new(NULL, NULL);
201 text_mb = gtk_stext_new(NULL, NULL);
202 GTK_STEXT(text_sb)->default_tab_width = 8;
203 GTK_STEXT(text_mb)->default_tab_width = 8;
204 gtk_widget_show(text_sb);
205 gtk_widget_show(text_mb);
206 gtk_stext_set_word_wrap(GTK_STEXT(text_sb), TRUE);
207 gtk_stext_set_word_wrap(GTK_STEXT(text_mb), TRUE);
208 gtk_widget_ensure_style(text_sb);
209 gtk_widget_ensure_style(text_mb);
210 if (text_sb->style && text_sb->style->font->type == GDK_FONT_FONTSET) {
214 font = gtkut_font_load_from_fontset(prefs_common.normalfont);
216 style = gtk_style_copy(text_sb->style);
217 gdk_font_unref(style->font);
219 gtk_widget_set_style(text_sb, style);
222 if (text_mb->style && text_mb->style->font->type == GDK_FONT_FONT) {
226 font = gdk_fontset_load(prefs_common.normalfont);
228 style = gtk_style_copy(text_mb->style);
229 gdk_font_unref(style->font);
231 gtk_widget_set_style(text_mb, style);
234 gtk_widget_ref(scrolledwin_sb);
235 gtk_widget_ref(scrolledwin_mb);
237 gtk_container_add(GTK_CONTAINER(scrolledwin_sb), text_sb);
238 gtk_container_add(GTK_CONTAINER(scrolledwin_mb), text_mb);
239 gtk_signal_connect(GTK_OBJECT(text_sb), "key_press_event",
240 GTK_SIGNAL_FUNC(textview_key_pressed),
242 gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_press_event",
243 GTK_SIGNAL_FUNC(textview_button_pressed),
245 gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_release_event",
246 GTK_SIGNAL_FUNC(textview_button_released),
248 gtk_signal_connect(GTK_OBJECT(text_mb), "key_press_event",
249 GTK_SIGNAL_FUNC(textview_key_pressed),
251 gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_press_event",
252 GTK_SIGNAL_FUNC(textview_button_pressed),
254 gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_release_event",
255 GTK_SIGNAL_FUNC(textview_button_released),
258 gtk_widget_show(scrolledwin_sb);
259 gtk_widget_show(scrolledwin_mb);
261 vbox = gtk_vbox_new(FALSE, 0);
262 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin_sb, TRUE, TRUE, 0);
264 gtk_widget_show(vbox);
266 textview->vbox = vbox;
267 textview->scrolledwin = scrolledwin_sb;
268 textview->scrolledwin_sb = scrolledwin_sb;
269 textview->scrolledwin_mb = scrolledwin_mb;
270 textview->text = text_sb;
271 textview->text_sb = text_sb;
272 textview->text_mb = text_mb;
273 textview->text_is_mb = FALSE;
274 textview->uri_list = NULL;
275 textview->body_pos = 0;
276 textview->cur_pos = 0;
277 textview->show_all_headers = FALSE;
278 textview->last_buttonpress = GDK_NOTHING;
279 textview->show_url_msgid = 0;
284 void textview_init(TextView *textview)
286 gtkut_widget_disable_theme_engine(textview->text_sb);
287 gtkut_widget_disable_theme_engine(textview->text_mb);
288 textview_update_message_colors();
289 textview_set_all_headers(textview, FALSE);
290 textview_set_font(textview, NULL);
293 void textview_update_message_colors(void)
295 GdkColor black = {0, 0, 0, 0};
297 if (prefs_common.enable_color) {
298 /* grab the quote colors, converting from an int to a GdkColor */
299 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
301 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
303 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
305 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
307 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
310 quote_colors[0] = quote_colors[1] = quote_colors[2] =
311 uri_color = emphasis_color = signature_color = black;
315 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
320 const gchar *charset = NULL;
322 if ((fp = fopen(file, "rb")) == NULL) {
323 FILE_OP_ERROR(file, "fopen");
327 if (textview->messageview->forced_charset)
328 charset = textview->messageview->forced_charset;
329 else if (prefs_common.force_charset)
330 charset = prefs_common.force_charset;
332 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
334 textview_set_font(textview, charset);
335 textview_clear(textview);
337 text = GTK_STEXT(textview->text);
339 gtk_stext_freeze(text);
342 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) perror("fseek");
343 headers = textview_scan_header(textview, fp);
345 textview_show_header(textview, headers);
346 procheader_header_array_destroy(headers);
347 textview->body_pos = gtk_stext_get_length(text);
350 textview_add_parts(textview, mimeinfo);
352 gtk_stext_thaw(text);
357 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
360 const gchar *charset = NULL;
362 g_return_if_fail(mimeinfo != NULL);
363 g_return_if_fail(fp != NULL);
365 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
366 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822"))) {
367 textview_clear(textview);
368 textview_add_parts(textview, mimeinfo);
372 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
375 headers = textview_scan_header(textview, fp);
377 if (textview->messageview->forced_charset)
378 charset = textview->messageview->forced_charset;
379 else if (prefs_common.force_charset)
380 charset = prefs_common.force_charset;
382 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
384 textview_set_font(textview, charset);
386 text = GTK_STEXT(textview->text);
388 gtk_stext_freeze(text);
389 textview_clear(textview);
393 textview_show_header(textview, headers);
394 procheader_header_array_destroy(headers);
395 textview->body_pos = gtk_stext_get_length(text);
397 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
400 if (mimeinfo->type == MIMETYPE_MULTIPART)
401 textview_add_parts(textview, mimeinfo);
403 textview_write_body(textview, mimeinfo, charset);
405 gtk_stext_thaw(text);
408 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
410 GtkSText *text = GTK_STEXT(textview->text);
412 const gchar *charset = NULL;
413 GPtrArray *headers = NULL;
415 g_return_if_fail(mimeinfo != NULL);
417 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
419 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822")) {
422 fp = fopen(mimeinfo->filename, "rb");
423 fseek(fp, mimeinfo->offset, SEEK_SET);
424 headers = textview_scan_header(textview, fp);
426 gtk_stext_freeze(text);
427 if (gtk_stext_get_length(text) > 0)
428 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
429 textview_show_header(textview, headers);
430 procheader_header_array_destroy(headers);
431 gtk_stext_thaw(text);
437 gtk_stext_freeze(text);
439 if (g_hash_table_lookup(mimeinfo->parameters, "name") != NULL)
440 g_snprintf(buf, sizeof(buf), "\n[%s %s/%s (%d bytes)]\n",
441 (gchar *) g_hash_table_lookup(mimeinfo->parameters, "name"),
442 procmime_get_type_str(mimeinfo->type),
443 mimeinfo->subtype, mimeinfo->length);
445 g_snprintf(buf, sizeof(buf), "\n[%s/%s (%d bytes)]\n",
446 procmime_get_type_str(mimeinfo->type),
447 mimeinfo->subtype, mimeinfo->length);
449 if (mimeinfo->type != MIMETYPE_TEXT) {
450 gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
452 if (prefs_common.display_header && (gtk_stext_get_length(text) > 0))
453 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
454 if (textview->messageview->forced_charset)
455 charset = textview->messageview->forced_charset;
456 else if (prefs_common.force_charset)
457 charset = prefs_common.force_charset;
459 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
461 textview_write_body(textview, mimeinfo, charset);
464 gtk_stext_thaw(text);
468 static gboolean add_parts_func(GNode *node, gpointer data)
470 MimeInfo *mimeinfo = (MimeInfo *) node->data;
471 TextView *textview = (TextView *) data;
473 g_return_val_if_fail(mimeinfo != NULL, FALSE);
475 textview_add_part(textview, mimeinfo);
480 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
482 g_return_if_fail(mimeinfo != NULL);
484 g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, add_parts_func, textview);
488 static void recursive_add_parts(TextView *textview, GNode *node)
493 mimeinfo = (MimeInfo *) node->data;
495 textview_add_part(textview, mimeinfo);
497 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
498 (mimeinfo->type != MIMETYPE_MESSAGE))
501 if (strcasecmp(mimeinfo->subtype, "alternative") == 0) {
502 GNode * prefered_body;
510 prefered_body = NULL;
513 for(iter = g_node_first_child(node) ; iter != NULL ;
514 iter = g_node_next_sibling(iter)) {
519 submime = (MimeInfo *) iter->data;
520 if (submime->type == MIMETYPE_TEXT)
523 if (submime->subtype != NULL) {
524 if (strcasecmp(submime->subtype, "plain") == 0)
528 if (score > prefered_score) {
529 prefered_score = score;
530 prefered_body = iter;
534 if (prefered_body != NULL) {
535 recursive_add_parts(textview, prefered_body);
539 for(iter = g_node_first_child(node) ; iter != NULL ;
540 iter = g_node_next_sibling(iter)) {
541 recursive_add_parts(textview, iter);
546 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
548 g_return_if_fail(mimeinfo != NULL);
550 recursive_add_parts(textview, mimeinfo->node);
553 #define TEXT_INSERT(str) \
554 gtk_stext_insert(text, textview->msgfont, NULL, NULL, str, -1)
556 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
560 if (!partinfo) return;
562 textview_set_font(textview, NULL);
563 text = GTK_STEXT(textview->text);
564 textview_clear(textview);
566 gtk_stext_freeze(text);
568 TEXT_INSERT(_("The following can be performed on this part by "));
569 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
571 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
572 TEXT_INSERT(_(" To display as text select 'Display as text' "));
573 TEXT_INSERT(_("(Shortcut key: 't')\n"));
574 TEXT_INSERT(_(" To open with an external program select 'Open' "));
575 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
576 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
577 TEXT_INSERT(_("mouse button),\n"));
578 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
580 gtk_stext_thaw(text);
585 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
586 const gchar *charset)
592 conv = conv_code_converter_new(charset);
594 textview->is_in_signature = FALSE;
596 if(mimeinfo->encoding_type != ENC_BINARY &&
597 mimeinfo->encoding_type != ENC_7BIT &&
598 mimeinfo->encoding_type != ENC_8BIT)
599 procmime_decode_content(mimeinfo);
601 if (!g_strcasecmp(mimeinfo->subtype, "html")) {
604 filename = procmime_get_tmp_file_name(mimeinfo);
605 if (procmime_get_part(filename, mimeinfo) == 0) {
606 tmpfp = fopen(filename, "rb");
607 textview_show_html(textview, tmpfp, conv);
612 } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
615 filename = procmime_get_tmp_file_name(mimeinfo);
616 if (procmime_get_part(filename, mimeinfo) == 0) {
617 tmpfp = fopen(filename, "rb");
618 textview_show_ertf(textview, tmpfp, conv);
624 tmpfp = fopen(mimeinfo->filename, "rb");
625 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
626 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
627 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
628 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
629 textview_write_line(textview, buf, conv);
633 conv_code_converter_destroy(conv);
636 static void textview_show_html(TextView *textview, FILE *fp,
642 parser = html_parser_new(fp, conv);
643 g_return_if_fail(parser != NULL);
645 while ((str = html_parse(parser)) != NULL) {
646 if (parser->state == HTML_HREF) {
647 /* first time : get and copy the URL */
648 if (parser->href == NULL) {
649 /* ALF - the sylpheed html parser returns an empty string,
650 * if still inside an <a>, but already parsed past HREF */
651 str = strtok(str, " ");
653 parser->href = strdup(str);
654 /* the URL may (or not) be followed by the
656 str = strtok(NULL, "");
660 textview_write_link(textview, str, parser->href, NULL);
662 textview_write_line(textview, str, NULL);
664 html_parser_destroy(parser);
667 static void textview_show_ertf(TextView *textview, FILE *fp,
673 parser = ertf_parser_new(fp, conv);
674 g_return_if_fail(parser != NULL);
676 while ((str = ertf_parse(parser)) != NULL) {
677 textview_write_line(textview, str, NULL);
680 ertf_parser_destroy(parser);
683 /* get_uri_part() - retrieves a URI starting from scanpos.
684 Returns TRUE if succesful */
685 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
686 const gchar **bp, const gchar **ep)
690 g_return_val_if_fail(start != NULL, FALSE);
691 g_return_val_if_fail(scanpos != NULL, FALSE);
692 g_return_val_if_fail(bp != NULL, FALSE);
693 g_return_val_if_fail(ep != NULL, FALSE);
697 /* find end point of URI */
698 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
699 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
703 /* no punctuation at end of string */
705 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
706 * should pass some URI type to this function and decide on that whether
707 * to perform punctuation stripping */
709 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
711 for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
721 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
723 return g_strndup(bp, ep - bp);
726 /* valid mail address characters */
727 #define IS_RFC822_CHAR(ch) \
732 !strchr("(),;<>\"", (ch)))
734 /* alphabet and number within 7bit ASCII */
735 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
736 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
738 static GHashTable *create_domain_tab(void)
740 static const gchar *toplvl_domains [] = {
742 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
743 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
744 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
745 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
746 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
747 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
748 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
749 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
750 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
751 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
752 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
753 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
754 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
755 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
756 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
757 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
758 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
759 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
760 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
761 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
762 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
763 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
764 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
765 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
766 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
767 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
770 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
772 g_return_val_if_fail(htab, NULL);
773 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
774 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
778 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
780 const gint MAX_LVL_DOM_NAME_LEN = 6;
781 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
782 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
785 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
788 for (p = buf; p < m && first < last; *p++ = *first++)
792 return g_hash_table_lookup(tab, buf) != NULL;
795 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
796 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
797 const gchar **bp, const gchar **ep)
799 /* more complex than the uri part because we need to scan back and forward starting from
800 * the scan position. */
801 gboolean result = FALSE;
802 const gchar *bp_ = NULL;
803 const gchar *ep_ = NULL;
804 static GHashTable *dom_tab;
805 const gchar *last_dot = NULL;
806 const gchar *prelast_dot = NULL;
807 const gchar *last_tld_char = NULL;
809 /* the informative part of the email address (describing the name
810 * of the email address owner) may contain quoted parts. the
811 * closure stack stores the last encountered quotes. */
812 gchar closure_stack[128];
813 gchar *ptr = closure_stack;
815 g_return_val_if_fail(start != NULL, FALSE);
816 g_return_val_if_fail(scanpos != NULL, FALSE);
817 g_return_val_if_fail(bp != NULL, FALSE);
818 g_return_val_if_fail(ep != NULL, FALSE);
821 dom_tab = create_domain_tab();
822 g_return_val_if_fail(dom_tab, FALSE);
824 /* scan start of address */
825 for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
828 /* TODO: should start with an alnum? */
830 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
833 if (bp_ != scanpos) {
834 /* scan end of address */
835 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
837 prelast_dot = last_dot;
839 if (*(last_dot + 1) == '.') {
840 if (prelast_dot == NULL)
842 last_dot = prelast_dot;
847 /* TODO: really should terminate with an alnum? */
848 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
852 if (last_dot == NULL)
855 last_dot = prelast_dot;
856 if (last_dot == NULL || (scanpos + 1 >= last_dot))
860 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
861 if (*last_tld_char == '?')
864 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
871 if (!result) return FALSE;
873 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
874 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
877 /* see if this is <bracketed>; in this case we also scan for the informative part. */
878 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
881 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
882 #define IN_STACK() (ptr > closure_stack)
883 /* has underrun check */
884 #define POP_STACK() if(IN_STACK()) --ptr
885 /* has overrun check */
886 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
887 /* has underrun check */
888 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
892 /* scan for the informative part. */
893 for (bp_ -= 2; bp_ >= start; bp_--) {
894 /* if closure on the stack keep scanning */
895 if (PEEK_STACK() == *bp_) {
899 if (*bp_ == '\'' || *bp_ == '"') {
904 /* if nothing in the closure stack, do the special conditions
905 * the following if..else expression simply checks whether
906 * a token is acceptable. if not acceptable, the clause
907 * should terminate the loop with a 'break' */
910 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
911 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
912 /* hyphens are allowed, but only in
914 } else if (!ispunct(*bp_)) {
915 /* but anything not being a punctiation
918 break; /* anything else is rejected */
931 /* scan forward (should start with an alnum) */
932 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
942 #undef IS_RFC822_CHAR
944 static gchar *make_email_string(const gchar *bp, const gchar *ep)
946 /* returns a mailto: URI; mailto: is also used to detect the
947 * uri type later on in the button_pressed signal handler */
951 tmp = g_strndup(bp, ep - bp);
952 result = g_strconcat("mailto:", tmp, NULL);
958 #define ADD_TXT_POS(bp_, ep_, pti_) \
959 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
961 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
964 g_warning("alloc error scanning URIs\n"); \
965 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
970 /* textview_make_clickable_parts() - colorizes clickable parts */
971 static void textview_make_clickable_parts(TextView *textview,
975 const gchar *linebuf)
977 /* parse table - in order of priority */
979 const gchar *needle; /* token */
981 /* token search function */
982 gchar *(*search) (const gchar *haystack,
983 const gchar *needle);
984 /* part parsing function */
985 gboolean (*parse) (const gchar *start,
986 const gchar *scanpos,
989 /* part to URI function */
990 gchar *(*build_uri) (const gchar *bp,
994 static struct table parser[] = {
995 {"http://", strcasestr, get_uri_part, make_uri_string},
996 {"https://", strcasestr, get_uri_part, make_uri_string},
997 {"ftp://", strcasestr, get_uri_part, make_uri_string},
998 {"www.", strcasestr, get_uri_part, make_uri_string},
999 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1000 {"@", strcasestr, get_email_part, make_email_string}
1002 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1005 const gchar *walk, *bp, *ep;
1008 const gchar *bp, *ep; /* text position */
1009 gint pti; /* index in parse table */
1010 struct txtpos *next; /* next */
1011 } head = {NULL, NULL, 0, NULL}, *last = &head;
1013 GtkSText *text = GTK_STEXT(textview->text);
1015 /* parse for clickable parts, and build a list of begin and end positions */
1016 for (walk = linebuf, n = 0;;) {
1017 gint last_index = PARSE_ELEMS;
1018 gchar *scanpos = NULL;
1020 /* FIXME: this looks phony. scanning for anything in the parse table */
1021 for (n = 0; n < PARSE_ELEMS; n++) {
1024 tmp = parser[n].search(walk, parser[n].needle);
1026 if (scanpos == NULL || tmp < scanpos) {
1034 /* check if URI can be parsed */
1035 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1036 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1037 ADD_TXT_POS(bp, ep, last_index);
1041 strlen(parser[last_index].needle);
1046 /* colorize this line */
1048 const gchar *normal_text = linebuf;
1051 for (last = head.next; last != NULL;
1052 normal_text = last->ep, last = last->next) {
1055 uri = g_new(RemoteURI, 1);
1056 if (last->bp - normal_text > 0)
1057 gtk_stext_insert(text, font,
1060 last->bp - normal_text);
1061 uri->uri = parser[last->pti].build_uri(last->bp,
1063 uri->start = gtk_stext_get_point(text);
1064 gtk_stext_insert(text, font, uri_color,
1065 NULL, last->bp, last->ep - last->bp);
1066 uri->end = gtk_stext_get_point(text);
1067 textview->uri_list =
1068 g_slist_append(textview->uri_list, uri);
1072 gtk_stext_insert(text, font, fg_color,
1073 NULL, normal_text, -1);
1075 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1080 static void textview_write_line(TextView *textview, const gchar *str,
1081 CodeConverter *conv)
1083 GtkSText *text = GTK_STEXT(textview->text);
1084 gchar buf[BUFFSIZE];
1086 gint quotelevel = -1;
1089 if (textview->text_is_mb)
1090 conv_localetodisp(buf, sizeof(buf), str);
1092 strncpy2(buf, str, sizeof(buf));
1093 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1094 conv_localetodisp(buf, sizeof(buf), str);
1095 else if (textview->text_is_mb)
1096 conv_unreadable_locale(buf);
1099 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1102 /* change color of quotation
1103 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1104 Up to 3 levels of quotations are detected, and each
1105 level is colored using a different color. */
1106 if (prefs_common.enable_color
1107 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1108 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1110 /* set up the correct foreground color */
1111 if (quotelevel > 2) {
1112 /* recycle colors */
1113 if (prefs_common.recycle_quote_colors)
1120 if (quotelevel == -1)
1123 fg_color = "e_colors[quotelevel];
1125 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1126 fg_color = &signature_color;
1127 textview->is_in_signature = TRUE;
1130 if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1131 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1133 if (prefs_common.enable_color)
1134 textview_make_clickable_parts(textview, textview->msgfont,
1135 fg_color, &uri_color, buf);
1137 textview_make_clickable_parts(textview, textview->msgfont,
1138 fg_color, NULL, buf);
1141 void textview_write_link(TextView *textview, const gchar *str,
1142 const gchar *uri, CodeConverter *conv)
1144 GdkColor *link_color = NULL;
1145 GtkSText *text = GTK_STEXT(textview->text);
1146 gchar buf[BUFFSIZE];
1154 if (textview->text_is_mb)
1155 conv_localetodisp(buf, sizeof(buf), str);
1157 strncpy2(buf, str, sizeof(buf));
1158 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1159 conv_localetodisp(buf, sizeof(buf), str);
1160 else if (textview->text_is_mb)
1161 conv_unreadable_locale(buf);
1165 for (bufp = buf; isspace(*bufp); bufp++)
1166 gtk_stext_insert(text, textview->msgfont, NULL, NULL, bufp, 1);
1168 if (prefs_common.enable_color) {
1169 link_color = &uri_color;
1171 r_uri = g_new(RemoteURI, 1);
1172 r_uri->uri = g_strdup(uri);
1173 r_uri->start = gtk_stext_get_point(text);
1174 gtk_stext_insert(text, textview->msgfont, link_color, NULL, bufp, -1);
1175 r_uri->end = gtk_stext_get_point(text);
1176 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1179 void textview_clear(TextView *textview)
1181 GtkSText *text = GTK_STEXT(textview->text);
1183 gtk_stext_freeze(text);
1184 gtk_stext_set_point(text, 0);
1185 gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1186 gtk_stext_thaw(text);
1188 textview_uri_list_remove_all(textview->uri_list);
1189 textview->uri_list = NULL;
1191 textview->body_pos = 0;
1192 textview->cur_pos = 0;
1195 void textview_destroy(TextView *textview)
1197 textview_uri_list_remove_all(textview->uri_list);
1198 textview->uri_list = NULL;
1200 if (!textview->scrolledwin_sb->parent)
1201 gtk_widget_destroy(textview->scrolledwin_sb);
1202 if (!textview->scrolledwin_mb->parent)
1203 gtk_widget_destroy(textview->scrolledwin_mb);
1205 if (textview->msgfont)
1206 gdk_font_unref(textview->msgfont);
1207 if (textview->boldfont)
1208 gdk_font_unref(textview->boldfont);
1213 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1215 textview->show_all_headers = all_headers;
1218 void textview_set_font(TextView *textview, const gchar *codeset)
1220 gboolean use_fontset = TRUE;
1222 /* In multi-byte mode, GtkSText can't display 8bit characters
1223 correctly, so it must be single-byte mode. */
1224 if (MB_CUR_MAX > 1) {
1225 if (codeset && conv_get_current_charset() != C_UTF_8) {
1226 if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1227 !g_strcasecmp(codeset, "BALTIC"))
1228 use_fontset = FALSE;
1229 else if (conv_get_current_charset() != C_EUC_JP &&
1230 (!g_strncasecmp(codeset, "KOI8-", 5) ||
1231 !g_strncasecmp(codeset, "CP", 2) ||
1232 !g_strncasecmp(codeset, "WINDOWS-", 8)))
1233 use_fontset = FALSE;
1236 use_fontset = FALSE;
1238 if (textview->text_is_mb && !use_fontset) {
1241 parent = textview->scrolledwin_mb->parent;
1242 gtkut_container_remove(GTK_CONTAINER(parent),
1243 textview->scrolledwin_mb);
1244 gtk_container_add(GTK_CONTAINER(parent),
1245 textview->scrolledwin_sb);
1247 textview->text = textview->text_sb;
1248 textview->text_is_mb = FALSE;
1249 } else if (!textview->text_is_mb && use_fontset) {
1252 parent = textview->scrolledwin_sb->parent;
1253 gtkut_container_remove(GTK_CONTAINER(parent),
1254 textview->scrolledwin_sb);
1255 gtk_container_add(GTK_CONTAINER(parent),
1256 textview->scrolledwin_mb);
1258 textview->text = textview->text_mb;
1259 textview->text_is_mb = TRUE;
1262 if (prefs_common.textfont) {
1267 text_mb_font->ascent = text_mb_font_orig_ascent;
1268 text_mb_font->descent = text_mb_font_orig_descent;
1270 font = gdk_fontset_load(prefs_common.textfont);
1271 if (font && text_mb_font != font) {
1273 gdk_font_unref(text_mb_font);
1274 text_mb_font = font;
1275 text_mb_font_orig_ascent = font->ascent;
1276 text_mb_font_orig_descent = font->descent;
1280 text_sb_font->ascent = text_sb_font_orig_ascent;
1281 text_sb_font->descent = text_sb_font_orig_descent;
1284 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1286 font = gtkut_font_load_from_fontset
1287 (prefs_common.textfont);
1288 if (font && text_sb_font != font) {
1290 gdk_font_unref(text_sb_font);
1291 text_sb_font = font;
1292 text_sb_font_orig_ascent = font->ascent;
1293 text_sb_font_orig_descent = font->descent;
1298 gint ascent, descent;
1300 descent = prefs_common.line_space / 2;
1301 ascent = prefs_common.line_space - descent;
1302 font->ascent += ascent;
1303 font->descent += descent;
1305 if (textview->msgfont)
1306 gdk_font_unref(textview->msgfont);
1307 textview->msgfont = font;
1312 if (!textview->boldfont && prefs_common.boldfont)
1313 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1315 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1318 void textview_set_text(TextView *textview, const gchar *text)
1322 g_return_if_fail(textview != NULL);
1323 g_return_if_fail(text != NULL);
1325 textview_clear(textview);
1327 stext = GTK_STEXT(textview->text);
1328 gtk_stext_freeze(stext);
1329 gtk_stext_insert(stext, textview->msgfont, NULL, NULL, text, strlen(text));
1330 gtk_stext_thaw(stext);
1346 H_ORGANIZATION = 11,
1349 void textview_set_position(TextView *textview, gint pos)
1353 gtk_stext_get_length(GTK_STEXT(textview->text));
1355 textview->cur_pos = pos;
1359 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1361 gchar buf[BUFFSIZE];
1362 GPtrArray *headers, *sorted_headers;
1363 GSList *disphdr_list;
1367 g_return_val_if_fail(fp != NULL, NULL);
1369 if (textview->show_all_headers)
1370 return procheader_get_header_array_asis(fp);
1372 if (!prefs_common.display_header) {
1373 while (fgets(buf, sizeof(buf), fp) != NULL)
1374 if (buf[0] == '\r' || buf[0] == '\n') break;
1378 headers = procheader_get_header_array_asis(fp);
1380 sorted_headers = g_ptr_array_new();
1382 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1383 disphdr_list = disphdr_list->next) {
1384 DisplayHeaderProp *dp =
1385 (DisplayHeaderProp *)disphdr_list->data;
1387 for (i = 0; i < headers->len; i++) {
1388 header = g_ptr_array_index(headers, i);
1390 if (procheader_headername_equal(header->name,
1393 procheader_header_free(header);
1395 g_ptr_array_add(sorted_headers, header);
1397 g_ptr_array_remove_index(headers, i);
1403 if (prefs_common.show_other_header) {
1404 for (i = 0; i < headers->len; i++) {
1405 header = g_ptr_array_index(headers, i);
1406 g_ptr_array_add(sorted_headers, header);
1408 g_ptr_array_free(headers, TRUE);
1410 procheader_header_array_destroy(headers);
1413 return sorted_headers;
1416 static void textview_show_header(TextView *textview, GPtrArray *headers)
1418 GtkSText *text = GTK_STEXT(textview->text);
1422 g_return_if_fail(headers != NULL);
1424 gtk_stext_freeze(text);
1426 for (i = 0; i < headers->len; i++) {
1427 header = g_ptr_array_index(headers, i);
1428 g_return_if_fail(header->name != NULL);
1430 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1432 if (header->name[strlen(header->name) - 1] != ' ')
1433 gtk_stext_insert(text, textview->boldfont,
1434 NULL, NULL, " ", 1);
1436 if (procheader_headername_equal(header->name, "Subject") ||
1437 procheader_headername_equal(header->name, "From") ||
1438 procheader_headername_equal(header->name, "To") ||
1439 procheader_headername_equal(header->name, "Cc"))
1440 unfold_line(header->body);
1442 if (textview->text_is_mb == TRUE)
1443 conv_unreadable_locale(header->body);
1445 if (prefs_common.enable_color &&
1446 (procheader_headername_equal(header->name, "X-Mailer") ||
1447 procheader_headername_equal(header->name,
1449 strstr(header->body, "Sylpheed") != NULL)
1450 gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1452 else if (prefs_common.enable_color) {
1453 textview_make_clickable_parts(textview,
1454 NULL, NULL, &uri_color,
1457 textview_make_clickable_parts(textview,
1461 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1464 gtk_stext_thaw(text);
1467 gboolean textview_search_string(TextView *textview, const gchar *str,
1470 GtkSText *text = GTK_STEXT(textview->text);
1474 g_return_val_if_fail(str != NULL, FALSE);
1476 len = get_mbs_len(str);
1477 g_return_val_if_fail(len >= 0, FALSE);
1479 pos = textview->cur_pos;
1480 if (pos < textview->body_pos)
1481 pos = textview->body_pos;
1483 if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1484 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1485 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1486 textview_set_position(textview, pos + len);
1493 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1496 GtkSText *text = GTK_STEXT(textview->text);
1501 gboolean found = FALSE;
1503 g_return_val_if_fail(str != NULL, FALSE);
1505 wcs = strdup_mbstowcs(str);
1506 g_return_val_if_fail(wcs != NULL, FALSE);
1508 pos = textview->cur_pos;
1509 text_len = gtk_stext_get_length(text);
1510 if (text_len - textview->body_pos < len) {
1514 if (pos <= textview->body_pos || text_len - pos < len)
1515 pos = text_len - len;
1517 for (; pos >= textview->body_pos; pos--) {
1518 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1520 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1521 gtk_editable_select_region(GTK_EDITABLE(text),
1523 textview_set_position(textview, pos - 1);
1527 if (pos == textview->body_pos) break;
1534 void textview_scroll_one_line(TextView *textview, gboolean up)
1536 GtkSText *text = GTK_STEXT(textview->text);
1539 if (prefs_common.enable_smooth_scroll) {
1540 textview_smooth_scroll_one_line(textview, up);
1545 upper = text->vadj->upper - text->vadj->page_size;
1546 if (text->vadj->value < upper) {
1547 text->vadj->value +=
1548 text->vadj->step_increment * 4;
1550 MIN(text->vadj->value, upper);
1551 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1555 if (text->vadj->value > 0.0) {
1556 text->vadj->value -=
1557 text->vadj->step_increment * 4;
1559 MAX(text->vadj->value, 0.0);
1560 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1566 gboolean textview_scroll_page(TextView *textview, gboolean up)
1568 GtkSText *text = GTK_STEXT(textview->text);
1572 if (prefs_common.enable_smooth_scroll)
1573 return textview_smooth_scroll_page(textview, up);
1575 if (prefs_common.scroll_halfpage)
1576 page_incr = text->vadj->page_increment / 2;
1578 page_incr = text->vadj->page_increment;
1581 upper = text->vadj->upper - text->vadj->page_size;
1582 if (text->vadj->value < upper) {
1583 text->vadj->value += page_incr;
1584 text->vadj->value = MIN(text->vadj->value, upper);
1585 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1590 if (text->vadj->value > 0.0) {
1591 text->vadj->value -= page_incr;
1592 text->vadj->value = MAX(text->vadj->value, 0.0);
1593 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1602 static void textview_smooth_scroll_do(TextView *textview,
1603 gfloat old_value, gfloat last_value,
1606 GtkSText *text = GTK_STEXT(textview->text);
1611 if (old_value < last_value) {
1612 change_value = last_value - old_value;
1615 change_value = old_value - last_value;
1619 gdk_key_repeat_disable();
1621 for (i = step; i <= change_value; i += step) {
1622 text->vadj->value = old_value + (up ? -i : i);
1623 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1627 text->vadj->value = last_value;
1628 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1630 gdk_key_repeat_restore();
1633 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1635 GtkSText *text = GTK_STEXT(textview->text);
1641 upper = text->vadj->upper - text->vadj->page_size;
1642 if (text->vadj->value < upper) {
1643 old_value = text->vadj->value;
1644 last_value = text->vadj->value +
1645 text->vadj->step_increment * 4;
1646 last_value = MIN(last_value, upper);
1648 textview_smooth_scroll_do(textview, old_value,
1650 prefs_common.scroll_step);
1653 if (text->vadj->value > 0.0) {
1654 old_value = text->vadj->value;
1655 last_value = text->vadj->value -
1656 text->vadj->step_increment * 4;
1657 last_value = MAX(last_value, 0.0);
1659 textview_smooth_scroll_do(textview, old_value,
1661 prefs_common.scroll_step);
1666 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1668 GtkSText *text = GTK_STEXT(textview->text);
1674 if (prefs_common.scroll_halfpage)
1675 page_incr = text->vadj->page_increment / 2;
1677 page_incr = text->vadj->page_increment;
1680 upper = text->vadj->upper - text->vadj->page_size;
1681 if (text->vadj->value < upper) {
1682 old_value = text->vadj->value;
1683 last_value = text->vadj->value + page_incr;
1684 last_value = MIN(last_value, upper);
1686 textview_smooth_scroll_do(textview, old_value,
1688 prefs_common.scroll_step);
1692 if (text->vadj->value > 0.0) {
1693 old_value = text->vadj->value;
1694 last_value = text->vadj->value - page_incr;
1695 last_value = MAX(last_value, 0.0);
1697 textview_smooth_scroll_do(textview, old_value,
1699 prefs_common.scroll_step);
1707 #define KEY_PRESS_EVENT_STOP() \
1708 if (gtk_signal_n_emissions_by_name \
1709 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1710 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1711 "key_press_event"); \
1714 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1717 SummaryView *summaryview = NULL;
1718 MessageView *messageview = textview->messageview;
1720 if (!event) return FALSE;
1721 if (messageview->mainwin)
1722 summaryview = messageview->mainwin->summaryview;
1724 switch (event->keyval) {
1739 summary_pass_key_press_event(summaryview, event);
1741 textview_scroll_page(textview, FALSE);
1744 textview_scroll_page(textview, TRUE);
1747 textview_scroll_one_line(textview,
1748 (event->state & GDK_MOD1_MASK) != 0);
1752 summary_pass_key_press_event(summaryview, event);
1757 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1758 KEY_PRESS_EVENT_STOP();
1759 mimeview_pass_key_press_event(messageview->mimeview,
1763 /* possible fall through */
1766 event->window != messageview->mainwin->window->window) {
1767 GdkEventKey tmpev = *event;
1769 tmpev.window = messageview->mainwin->window->window;
1770 KEY_PRESS_EVENT_STOP();
1771 gtk_widget_event(messageview->mainwin->window,
1772 (GdkEvent *)&tmpev);
1780 static gint show_url_timeout_cb(gpointer data)
1782 TextView *textview = (TextView *)data;
1784 if (textview->messageview->mainwin)
1785 if (textview->show_url_msgid)
1786 gtk_statusbar_remove(GTK_STATUSBAR(
1787 textview->messageview->mainwin->statusbar),
1788 textview->messageview->mainwin->folderview_cid,
1789 textview->show_url_msgid);
1794 *\brief Check to see if a web URL has been disguised as a different
1795 * URL (possible with HTML email).
1797 *\param uri The uri to check
1799 *\param textview The TextView the URL is contained in
1801 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1802 * it anyway, otherwise FALSE
1804 static gboolean uri_security_check(RemoteURI *uri, TextView *textview)
1807 gboolean retval = TRUE;
1809 if (g_strncasecmp(uri->uri, "http:", 5) &&
1810 g_strncasecmp(uri->uri, "https:", 6) &&
1811 g_strncasecmp(uri->uri, "www.", 4))
1814 clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1817 if (clicked_str == NULL)
1820 if (strcmp(clicked_str, uri->uri) &&
1821 (!g_strncasecmp(clicked_str, "http:", 5) ||
1822 !g_strncasecmp(clicked_str, "https:", 6) ||
1823 !g_strncasecmp(clicked_str, "www.", 4))) {
1827 /* allow uri->uri == http://somewhere.com
1828 and clicked_str == somewhere.com */
1829 str = g_strconcat("http://", clicked_str, NULL);
1831 if (!g_strcasecmp(str, uri->uri))
1836 if (retval == FALSE) {
1840 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1841 "the apparent URL (%s). \n"
1843 uri->uri, clicked_str);
1844 resp = alertpanel(_("Warning"),
1850 if (resp == G_ALERTDEFAULT)
1853 g_free(clicked_str);
1857 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1861 textview->last_buttonpress = event->type;
1865 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1869 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1872 ((event->button == 1)
1873 || event->button == 2 || event->button == 3)) {
1876 /* double click seems to set the cursor after the current
1877 * word. The cursor position needs fixing, otherwise the
1878 * last word of a clickable zone will not work */
1879 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1880 textview->cur_pos--;
1883 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1884 RemoteURI *uri = (RemoteURI *)cur->data;
1886 if (textview->cur_pos >= uri->start &&
1887 textview->cur_pos <= uri->end) {
1890 trimmed_uri = trim_string(uri->uri, 60);
1891 /* single click: display url in statusbar */
1892 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1893 if (textview->messageview->mainwin) {
1894 if (textview->show_url_msgid) {
1895 gtk_timeout_remove(textview->show_url_timeout_tag);
1896 gtk_statusbar_remove(GTK_STATUSBAR(
1897 textview->messageview->mainwin->statusbar),
1898 textview->messageview->mainwin->folderview_cid,
1899 textview->show_url_msgid);
1900 textview->show_url_msgid = 0;
1902 textview->show_url_msgid = gtk_statusbar_push(
1903 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1904 textview->messageview->mainwin->folderview_cid,
1906 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1907 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1910 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1911 if (event->button == 3) {
1912 gchar *fromname, *fromaddress;
1915 fromaddress = g_strdup(uri->uri + 7);
1916 /* Hiroyuki: please put this function in utils.c! */
1917 fromname = procheader_get_fromname(fromaddress);
1918 extract_address(fromaddress);
1919 g_message("adding from textview %s <%s>", fromname, fromaddress);
1920 /* Add to address book - Match */
1921 addressbook_add_contact( fromname, fromaddress, NULL );
1923 g_free(fromaddress);
1926 PrefsAccount *account = NULL;
1927 FolderItem *folder_item;
1929 if (textview->messageview && textview->messageview->mainwin
1930 && textview->messageview->mainwin->summaryview
1931 && textview->messageview->mainwin->summaryview->folder_item) {
1932 folder_item = textview->messageview->mainwin->summaryview->folder_item;
1933 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1934 account = account_find_from_id(folder_item->prefs->default_account);
1936 compose_new(account, uri->uri + 7, NULL);
1939 if (uri_security_check(uri, textview) == TRUE)
1941 prefs_common.uri_cmd);
1943 g_free(trimmed_uri);
1948 textview->last_buttonpress = event->type;
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);