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_error(TextView *textview)
560 textview_set_font(textview, NULL);
561 text = GTK_STEXT(textview->text);
562 textview_clear(textview);
564 gtk_stext_freeze(text);
566 TEXT_INSERT(_("This message can't be displayed.\n"));
568 gtk_stext_thaw(text);
571 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
575 if (!partinfo) return;
577 textview_set_font(textview, NULL);
578 text = GTK_STEXT(textview->text);
579 textview_clear(textview);
581 gtk_stext_freeze(text);
583 TEXT_INSERT(_("The following can be performed on this part by "));
584 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
586 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
587 TEXT_INSERT(_(" To display as text select 'Display as text' "));
588 TEXT_INSERT(_("(Shortcut key: 't')\n"));
589 TEXT_INSERT(_(" To open with an external program select 'Open' "));
590 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
591 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
592 TEXT_INSERT(_("mouse button),\n"));
593 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
595 gtk_stext_thaw(text);
600 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
601 const gchar *charset)
607 conv = conv_code_converter_new(charset);
609 textview->is_in_signature = FALSE;
611 if(mimeinfo->encoding_type != ENC_BINARY &&
612 mimeinfo->encoding_type != ENC_7BIT &&
613 mimeinfo->encoding_type != ENC_8BIT)
614 procmime_decode_content(mimeinfo);
616 if (!g_strcasecmp(mimeinfo->subtype, "html")) {
619 filename = procmime_get_tmp_file_name(mimeinfo);
620 if (procmime_get_part(filename, mimeinfo) == 0) {
621 tmpfp = fopen(filename, "rb");
622 textview_show_html(textview, tmpfp, conv);
627 } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
630 filename = procmime_get_tmp_file_name(mimeinfo);
631 if (procmime_get_part(filename, mimeinfo) == 0) {
632 tmpfp = fopen(filename, "rb");
633 textview_show_ertf(textview, tmpfp, conv);
639 tmpfp = fopen(mimeinfo->filename, "rb");
640 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
641 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
642 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
643 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
644 textview_write_line(textview, buf, conv);
648 conv_code_converter_destroy(conv);
651 static void textview_show_html(TextView *textview, FILE *fp,
657 parser = html_parser_new(fp, conv);
658 g_return_if_fail(parser != NULL);
660 while ((str = html_parse(parser)) != NULL) {
661 if (parser->state == HTML_HREF) {
662 /* first time : get and copy the URL */
663 if (parser->href == NULL) {
664 /* ALF - the sylpheed html parser returns an empty string,
665 * if still inside an <a>, but already parsed past HREF */
666 str = strtok(str, " ");
668 parser->href = strdup(str);
669 /* the URL may (or not) be followed by the
671 str = strtok(NULL, "");
675 textview_write_link(textview, str, parser->href, NULL);
677 textview_write_line(textview, str, NULL);
679 html_parser_destroy(parser);
682 static void textview_show_ertf(TextView *textview, FILE *fp,
688 parser = ertf_parser_new(fp, conv);
689 g_return_if_fail(parser != NULL);
691 while ((str = ertf_parse(parser)) != NULL) {
692 textview_write_line(textview, str, NULL);
695 ertf_parser_destroy(parser);
698 /* get_uri_part() - retrieves a URI starting from scanpos.
699 Returns TRUE if succesful */
700 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
701 const gchar **bp, const gchar **ep)
705 g_return_val_if_fail(start != NULL, FALSE);
706 g_return_val_if_fail(scanpos != NULL, FALSE);
707 g_return_val_if_fail(bp != NULL, FALSE);
708 g_return_val_if_fail(ep != NULL, FALSE);
712 /* find end point of URI */
713 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
714 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
718 /* no punctuation at end of string */
720 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
721 * should pass some URI type to this function and decide on that whether
722 * to perform punctuation stripping */
724 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
726 for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
736 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
738 return g_strndup(bp, ep - bp);
741 /* valid mail address characters */
742 #define IS_RFC822_CHAR(ch) \
747 !strchr("(),;<>\"", (ch)))
749 /* alphabet and number within 7bit ASCII */
750 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
751 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
753 static GHashTable *create_domain_tab(void)
755 static const gchar *toplvl_domains [] = {
757 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
758 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
759 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
760 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
761 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
762 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
763 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
764 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
765 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
766 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
767 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
768 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
769 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
770 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
771 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
772 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
773 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
774 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
775 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
776 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
777 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
778 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
779 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
780 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
781 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
782 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
785 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
787 g_return_val_if_fail(htab, NULL);
788 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
789 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
793 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
795 const gint MAX_LVL_DOM_NAME_LEN = 6;
796 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
797 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
800 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
803 for (p = buf; p < m && first < last; *p++ = *first++)
807 return g_hash_table_lookup(tab, buf) != NULL;
810 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
811 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
812 const gchar **bp, const gchar **ep)
814 /* more complex than the uri part because we need to scan back and forward starting from
815 * the scan position. */
816 gboolean result = FALSE;
817 const gchar *bp_ = NULL;
818 const gchar *ep_ = NULL;
819 static GHashTable *dom_tab;
820 const gchar *last_dot = NULL;
821 const gchar *prelast_dot = NULL;
822 const gchar *last_tld_char = NULL;
824 /* the informative part of the email address (describing the name
825 * of the email address owner) may contain quoted parts. the
826 * closure stack stores the last encountered quotes. */
827 gchar closure_stack[128];
828 gchar *ptr = closure_stack;
830 g_return_val_if_fail(start != NULL, FALSE);
831 g_return_val_if_fail(scanpos != NULL, FALSE);
832 g_return_val_if_fail(bp != NULL, FALSE);
833 g_return_val_if_fail(ep != NULL, FALSE);
836 dom_tab = create_domain_tab();
837 g_return_val_if_fail(dom_tab, FALSE);
839 /* scan start of address */
840 for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
843 /* TODO: should start with an alnum? */
845 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
848 if (bp_ != scanpos) {
849 /* scan end of address */
850 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
852 prelast_dot = last_dot;
854 if (*(last_dot + 1) == '.') {
855 if (prelast_dot == NULL)
857 last_dot = prelast_dot;
862 /* TODO: really should terminate with an alnum? */
863 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
867 if (last_dot == NULL)
870 last_dot = prelast_dot;
871 if (last_dot == NULL || (scanpos + 1 >= last_dot))
875 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
876 if (*last_tld_char == '?')
879 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
886 if (!result) return FALSE;
888 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
889 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
892 /* see if this is <bracketed>; in this case we also scan for the informative part. */
893 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
896 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
897 #define IN_STACK() (ptr > closure_stack)
898 /* has underrun check */
899 #define POP_STACK() if(IN_STACK()) --ptr
900 /* has overrun check */
901 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
902 /* has underrun check */
903 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
907 /* scan for the informative part. */
908 for (bp_ -= 2; bp_ >= start; bp_--) {
909 /* if closure on the stack keep scanning */
910 if (PEEK_STACK() == *bp_) {
914 if (*bp_ == '\'' || *bp_ == '"') {
919 /* if nothing in the closure stack, do the special conditions
920 * the following if..else expression simply checks whether
921 * a token is acceptable. if not acceptable, the clause
922 * should terminate the loop with a 'break' */
925 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
926 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
927 /* hyphens are allowed, but only in
929 } else if (!ispunct(*bp_)) {
930 /* but anything not being a punctiation
933 break; /* anything else is rejected */
946 /* scan forward (should start with an alnum) */
947 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
957 #undef IS_RFC822_CHAR
959 static gchar *make_email_string(const gchar *bp, const gchar *ep)
961 /* returns a mailto: URI; mailto: is also used to detect the
962 * uri type later on in the button_pressed signal handler */
966 tmp = g_strndup(bp, ep - bp);
967 result = g_strconcat("mailto:", tmp, NULL);
973 #define ADD_TXT_POS(bp_, ep_, pti_) \
974 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
976 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
979 g_warning("alloc error scanning URIs\n"); \
980 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
985 /* textview_make_clickable_parts() - colorizes clickable parts */
986 static void textview_make_clickable_parts(TextView *textview,
990 const gchar *linebuf)
992 /* parse table - in order of priority */
994 const gchar *needle; /* token */
996 /* token search function */
997 gchar *(*search) (const gchar *haystack,
998 const gchar *needle);
999 /* part parsing function */
1000 gboolean (*parse) (const gchar *start,
1001 const gchar *scanpos,
1004 /* part to URI function */
1005 gchar *(*build_uri) (const gchar *bp,
1009 static struct table parser[] = {
1010 {"http://", strcasestr, get_uri_part, make_uri_string},
1011 {"https://", strcasestr, get_uri_part, make_uri_string},
1012 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1013 {"www.", strcasestr, get_uri_part, make_uri_string},
1014 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1015 {"@", strcasestr, get_email_part, make_email_string}
1017 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1020 const gchar *walk, *bp, *ep;
1023 const gchar *bp, *ep; /* text position */
1024 gint pti; /* index in parse table */
1025 struct txtpos *next; /* next */
1026 } head = {NULL, NULL, 0, NULL}, *last = &head;
1028 GtkSText *text = GTK_STEXT(textview->text);
1030 /* parse for clickable parts, and build a list of begin and end positions */
1031 for (walk = linebuf, n = 0;;) {
1032 gint last_index = PARSE_ELEMS;
1033 gchar *scanpos = NULL;
1035 /* FIXME: this looks phony. scanning for anything in the parse table */
1036 for (n = 0; n < PARSE_ELEMS; n++) {
1039 tmp = parser[n].search(walk, parser[n].needle);
1041 if (scanpos == NULL || tmp < scanpos) {
1049 /* check if URI can be parsed */
1050 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1051 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1052 ADD_TXT_POS(bp, ep, last_index);
1056 strlen(parser[last_index].needle);
1061 /* colorize this line */
1063 const gchar *normal_text = linebuf;
1066 for (last = head.next; last != NULL;
1067 normal_text = last->ep, last = last->next) {
1070 uri = g_new(RemoteURI, 1);
1071 if (last->bp - normal_text > 0)
1072 gtk_stext_insert(text, font,
1075 last->bp - normal_text);
1076 uri->uri = parser[last->pti].build_uri(last->bp,
1078 uri->start = gtk_stext_get_point(text);
1079 gtk_stext_insert(text, font, uri_color,
1080 NULL, last->bp, last->ep - last->bp);
1081 uri->end = gtk_stext_get_point(text);
1082 textview->uri_list =
1083 g_slist_append(textview->uri_list, uri);
1087 gtk_stext_insert(text, font, fg_color,
1088 NULL, normal_text, -1);
1090 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1095 static void textview_write_line(TextView *textview, const gchar *str,
1096 CodeConverter *conv)
1098 GtkSText *text = GTK_STEXT(textview->text);
1099 gchar buf[BUFFSIZE];
1101 gint quotelevel = -1;
1104 if (textview->text_is_mb)
1105 conv_localetodisp(buf, sizeof(buf), str);
1107 strncpy2(buf, str, sizeof(buf));
1108 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1109 conv_localetodisp(buf, sizeof(buf), str);
1110 else if (textview->text_is_mb)
1111 conv_unreadable_locale(buf);
1114 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1117 /* change color of quotation
1118 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1119 Up to 3 levels of quotations are detected, and each
1120 level is colored using a different color. */
1121 if (prefs_common.enable_color
1122 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1123 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1125 /* set up the correct foreground color */
1126 if (quotelevel > 2) {
1127 /* recycle colors */
1128 if (prefs_common.recycle_quote_colors)
1135 if (quotelevel == -1)
1138 fg_color = "e_colors[quotelevel];
1140 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1141 fg_color = &signature_color;
1142 textview->is_in_signature = TRUE;
1145 if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1146 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1148 if (prefs_common.enable_color)
1149 textview_make_clickable_parts(textview, textview->msgfont,
1150 fg_color, &uri_color, buf);
1152 textview_make_clickable_parts(textview, textview->msgfont,
1153 fg_color, NULL, buf);
1156 void textview_write_link(TextView *textview, const gchar *str,
1157 const gchar *uri, CodeConverter *conv)
1159 GdkColor *link_color = NULL;
1160 GtkSText *text = GTK_STEXT(textview->text);
1161 gchar buf[BUFFSIZE];
1169 if (textview->text_is_mb)
1170 conv_localetodisp(buf, sizeof(buf), str);
1172 strncpy2(buf, str, sizeof(buf));
1173 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1174 conv_localetodisp(buf, sizeof(buf), str);
1175 else if (textview->text_is_mb)
1176 conv_unreadable_locale(buf);
1180 for (bufp = buf; isspace(*bufp); bufp++)
1181 gtk_stext_insert(text, textview->msgfont, NULL, NULL, bufp, 1);
1183 if (prefs_common.enable_color) {
1184 link_color = &uri_color;
1186 r_uri = g_new(RemoteURI, 1);
1187 r_uri->uri = g_strdup(uri);
1188 r_uri->start = gtk_stext_get_point(text);
1189 gtk_stext_insert(text, textview->msgfont, link_color, NULL, bufp, -1);
1190 r_uri->end = gtk_stext_get_point(text);
1191 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1194 void textview_clear(TextView *textview)
1196 GtkSText *text = GTK_STEXT(textview->text);
1198 gtk_stext_freeze(text);
1199 gtk_stext_set_point(text, 0);
1200 gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1201 gtk_stext_thaw(text);
1203 textview_uri_list_remove_all(textview->uri_list);
1204 textview->uri_list = NULL;
1206 textview->body_pos = 0;
1207 textview->cur_pos = 0;
1210 void textview_destroy(TextView *textview)
1212 textview_uri_list_remove_all(textview->uri_list);
1213 textview->uri_list = NULL;
1215 if (!textview->scrolledwin_sb->parent)
1216 gtk_widget_destroy(textview->scrolledwin_sb);
1217 if (!textview->scrolledwin_mb->parent)
1218 gtk_widget_destroy(textview->scrolledwin_mb);
1220 if (textview->msgfont)
1221 gdk_font_unref(textview->msgfont);
1222 if (textview->boldfont)
1223 gdk_font_unref(textview->boldfont);
1228 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1230 textview->show_all_headers = all_headers;
1233 void textview_set_font(TextView *textview, const gchar *codeset)
1235 gboolean use_fontset = TRUE;
1237 /* In multi-byte mode, GtkSText can't display 8bit characters
1238 correctly, so it must be single-byte mode. */
1239 if (MB_CUR_MAX > 1) {
1240 if (codeset && conv_get_current_charset() != C_UTF_8) {
1241 if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1242 !g_strcasecmp(codeset, "BALTIC"))
1243 use_fontset = FALSE;
1244 else if (conv_get_current_charset() != C_EUC_JP &&
1245 (!g_strncasecmp(codeset, "KOI8-", 5) ||
1246 !g_strncasecmp(codeset, "CP", 2) ||
1247 !g_strncasecmp(codeset, "WINDOWS-", 8)))
1248 use_fontset = FALSE;
1251 use_fontset = FALSE;
1253 if (textview->text_is_mb && !use_fontset) {
1256 parent = textview->scrolledwin_mb->parent;
1257 gtkut_container_remove(GTK_CONTAINER(parent),
1258 textview->scrolledwin_mb);
1259 gtk_container_add(GTK_CONTAINER(parent),
1260 textview->scrolledwin_sb);
1262 textview->text = textview->text_sb;
1263 textview->text_is_mb = FALSE;
1264 } else if (!textview->text_is_mb && use_fontset) {
1267 parent = textview->scrolledwin_sb->parent;
1268 gtkut_container_remove(GTK_CONTAINER(parent),
1269 textview->scrolledwin_sb);
1270 gtk_container_add(GTK_CONTAINER(parent),
1271 textview->scrolledwin_mb);
1273 textview->text = textview->text_mb;
1274 textview->text_is_mb = TRUE;
1277 if (prefs_common.textfont) {
1282 text_mb_font->ascent = text_mb_font_orig_ascent;
1283 text_mb_font->descent = text_mb_font_orig_descent;
1285 font = gdk_fontset_load(prefs_common.textfont);
1286 if (font && text_mb_font != font) {
1288 gdk_font_unref(text_mb_font);
1289 text_mb_font = font;
1290 text_mb_font_orig_ascent = font->ascent;
1291 text_mb_font_orig_descent = font->descent;
1295 text_sb_font->ascent = text_sb_font_orig_ascent;
1296 text_sb_font->descent = text_sb_font_orig_descent;
1299 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1301 font = gtkut_font_load_from_fontset
1302 (prefs_common.textfont);
1303 if (font && text_sb_font != font) {
1305 gdk_font_unref(text_sb_font);
1306 text_sb_font = font;
1307 text_sb_font_orig_ascent = font->ascent;
1308 text_sb_font_orig_descent = font->descent;
1313 gint ascent, descent;
1315 descent = prefs_common.line_space / 2;
1316 ascent = prefs_common.line_space - descent;
1317 font->ascent += ascent;
1318 font->descent += descent;
1320 if (textview->msgfont)
1321 gdk_font_unref(textview->msgfont);
1322 textview->msgfont = font;
1327 if (!textview->boldfont && prefs_common.boldfont)
1328 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1330 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1333 void textview_set_text(TextView *textview, const gchar *text)
1337 g_return_if_fail(textview != NULL);
1338 g_return_if_fail(text != NULL);
1340 textview_clear(textview);
1342 stext = GTK_STEXT(textview->text);
1343 gtk_stext_freeze(stext);
1344 gtk_stext_insert(stext, textview->msgfont, NULL, NULL, text, strlen(text));
1345 gtk_stext_thaw(stext);
1361 H_ORGANIZATION = 11,
1364 void textview_set_position(TextView *textview, gint pos)
1368 gtk_stext_get_length(GTK_STEXT(textview->text));
1370 textview->cur_pos = pos;
1374 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1376 gchar buf[BUFFSIZE];
1377 GPtrArray *headers, *sorted_headers;
1378 GSList *disphdr_list;
1382 g_return_val_if_fail(fp != NULL, NULL);
1384 if (textview->show_all_headers)
1385 return procheader_get_header_array_asis(fp);
1387 if (!prefs_common.display_header) {
1388 while (fgets(buf, sizeof(buf), fp) != NULL)
1389 if (buf[0] == '\r' || buf[0] == '\n') break;
1393 headers = procheader_get_header_array_asis(fp);
1395 sorted_headers = g_ptr_array_new();
1397 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1398 disphdr_list = disphdr_list->next) {
1399 DisplayHeaderProp *dp =
1400 (DisplayHeaderProp *)disphdr_list->data;
1402 for (i = 0; i < headers->len; i++) {
1403 header = g_ptr_array_index(headers, i);
1405 if (procheader_headername_equal(header->name,
1408 procheader_header_free(header);
1410 g_ptr_array_add(sorted_headers, header);
1412 g_ptr_array_remove_index(headers, i);
1418 if (prefs_common.show_other_header) {
1419 for (i = 0; i < headers->len; i++) {
1420 header = g_ptr_array_index(headers, i);
1421 g_ptr_array_add(sorted_headers, header);
1423 g_ptr_array_free(headers, TRUE);
1425 procheader_header_array_destroy(headers);
1428 return sorted_headers;
1431 static void textview_show_header(TextView *textview, GPtrArray *headers)
1433 GtkSText *text = GTK_STEXT(textview->text);
1437 g_return_if_fail(headers != NULL);
1439 gtk_stext_freeze(text);
1441 for (i = 0; i < headers->len; i++) {
1442 header = g_ptr_array_index(headers, i);
1443 g_return_if_fail(header->name != NULL);
1445 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1447 if (header->name[strlen(header->name) - 1] != ' ')
1448 gtk_stext_insert(text, textview->boldfont,
1449 NULL, NULL, " ", 1);
1451 if (procheader_headername_equal(header->name, "Subject") ||
1452 procheader_headername_equal(header->name, "From") ||
1453 procheader_headername_equal(header->name, "To") ||
1454 procheader_headername_equal(header->name, "Cc"))
1455 unfold_line(header->body);
1457 if (textview->text_is_mb == TRUE)
1458 conv_unreadable_locale(header->body);
1460 if (prefs_common.enable_color &&
1461 (procheader_headername_equal(header->name, "X-Mailer") ||
1462 procheader_headername_equal(header->name,
1464 strstr(header->body, "Sylpheed") != NULL)
1465 gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1467 else if (prefs_common.enable_color) {
1468 textview_make_clickable_parts(textview,
1469 NULL, NULL, &uri_color,
1472 textview_make_clickable_parts(textview,
1476 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1479 gtk_stext_thaw(text);
1482 gboolean textview_search_string(TextView *textview, const gchar *str,
1485 GtkSText *text = GTK_STEXT(textview->text);
1489 g_return_val_if_fail(str != NULL, FALSE);
1491 len = get_mbs_len(str);
1492 g_return_val_if_fail(len >= 0, FALSE);
1494 pos = textview->cur_pos;
1495 if (pos < textview->body_pos)
1496 pos = textview->body_pos;
1498 if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1499 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1500 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1501 textview_set_position(textview, pos + len);
1508 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1511 GtkSText *text = GTK_STEXT(textview->text);
1516 gboolean found = FALSE;
1518 g_return_val_if_fail(str != NULL, FALSE);
1520 wcs = strdup_mbstowcs(str);
1521 g_return_val_if_fail(wcs != NULL, FALSE);
1523 pos = textview->cur_pos;
1524 text_len = gtk_stext_get_length(text);
1525 if (text_len - textview->body_pos < len) {
1529 if (pos <= textview->body_pos || text_len - pos < len)
1530 pos = text_len - len;
1532 for (; pos >= textview->body_pos; pos--) {
1533 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1535 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1536 gtk_editable_select_region(GTK_EDITABLE(text),
1538 textview_set_position(textview, pos - 1);
1542 if (pos == textview->body_pos) break;
1549 void textview_scroll_one_line(TextView *textview, gboolean up)
1551 GtkSText *text = GTK_STEXT(textview->text);
1554 if (prefs_common.enable_smooth_scroll) {
1555 textview_smooth_scroll_one_line(textview, up);
1560 upper = text->vadj->upper - text->vadj->page_size;
1561 if (text->vadj->value < upper) {
1562 text->vadj->value +=
1563 text->vadj->step_increment * 4;
1565 MIN(text->vadj->value, upper);
1566 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1570 if (text->vadj->value > 0.0) {
1571 text->vadj->value -=
1572 text->vadj->step_increment * 4;
1574 MAX(text->vadj->value, 0.0);
1575 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1581 gboolean textview_scroll_page(TextView *textview, gboolean up)
1583 GtkSText *text = GTK_STEXT(textview->text);
1587 if (prefs_common.enable_smooth_scroll)
1588 return textview_smooth_scroll_page(textview, up);
1590 if (prefs_common.scroll_halfpage)
1591 page_incr = text->vadj->page_increment / 2;
1593 page_incr = text->vadj->page_increment;
1596 upper = text->vadj->upper - text->vadj->page_size;
1597 if (text->vadj->value < upper) {
1598 text->vadj->value += page_incr;
1599 text->vadj->value = MIN(text->vadj->value, upper);
1600 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1605 if (text->vadj->value > 0.0) {
1606 text->vadj->value -= page_incr;
1607 text->vadj->value = MAX(text->vadj->value, 0.0);
1608 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1617 static void textview_smooth_scroll_do(TextView *textview,
1618 gfloat old_value, gfloat last_value,
1621 GtkSText *text = GTK_STEXT(textview->text);
1626 if (old_value < last_value) {
1627 change_value = last_value - old_value;
1630 change_value = old_value - last_value;
1634 gdk_key_repeat_disable();
1636 for (i = step; i <= change_value; i += step) {
1637 text->vadj->value = old_value + (up ? -i : i);
1638 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1642 text->vadj->value = last_value;
1643 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1645 gdk_key_repeat_restore();
1648 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1650 GtkSText *text = GTK_STEXT(textview->text);
1656 upper = text->vadj->upper - text->vadj->page_size;
1657 if (text->vadj->value < upper) {
1658 old_value = text->vadj->value;
1659 last_value = text->vadj->value +
1660 text->vadj->step_increment * 4;
1661 last_value = MIN(last_value, upper);
1663 textview_smooth_scroll_do(textview, old_value,
1665 prefs_common.scroll_step);
1668 if (text->vadj->value > 0.0) {
1669 old_value = text->vadj->value;
1670 last_value = text->vadj->value -
1671 text->vadj->step_increment * 4;
1672 last_value = MAX(last_value, 0.0);
1674 textview_smooth_scroll_do(textview, old_value,
1676 prefs_common.scroll_step);
1681 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1683 GtkSText *text = GTK_STEXT(textview->text);
1689 if (prefs_common.scroll_halfpage)
1690 page_incr = text->vadj->page_increment / 2;
1692 page_incr = text->vadj->page_increment;
1695 upper = text->vadj->upper - text->vadj->page_size;
1696 if (text->vadj->value < upper) {
1697 old_value = text->vadj->value;
1698 last_value = text->vadj->value + page_incr;
1699 last_value = MIN(last_value, upper);
1701 textview_smooth_scroll_do(textview, old_value,
1703 prefs_common.scroll_step);
1707 if (text->vadj->value > 0.0) {
1708 old_value = text->vadj->value;
1709 last_value = text->vadj->value - page_incr;
1710 last_value = MAX(last_value, 0.0);
1712 textview_smooth_scroll_do(textview, old_value,
1714 prefs_common.scroll_step);
1722 #define KEY_PRESS_EVENT_STOP() \
1723 if (gtk_signal_n_emissions_by_name \
1724 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1725 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1726 "key_press_event"); \
1729 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1732 SummaryView *summaryview = NULL;
1733 MessageView *messageview = textview->messageview;
1735 if (!event) return FALSE;
1736 if (messageview->mainwin)
1737 summaryview = messageview->mainwin->summaryview;
1739 switch (event->keyval) {
1754 summary_pass_key_press_event(summaryview, event);
1756 textview_scroll_page(textview, FALSE);
1759 textview_scroll_page(textview, TRUE);
1762 textview_scroll_one_line(textview,
1763 (event->state & GDK_MOD1_MASK) != 0);
1767 summary_pass_key_press_event(summaryview, event);
1772 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1773 KEY_PRESS_EVENT_STOP();
1774 mimeview_pass_key_press_event(messageview->mimeview,
1778 /* possible fall through */
1781 event->window != messageview->mainwin->window->window) {
1782 GdkEventKey tmpev = *event;
1784 tmpev.window = messageview->mainwin->window->window;
1785 KEY_PRESS_EVENT_STOP();
1786 gtk_widget_event(messageview->mainwin->window,
1787 (GdkEvent *)&tmpev);
1795 static gint show_url_timeout_cb(gpointer data)
1797 TextView *textview = (TextView *)data;
1799 if (textview->messageview->mainwin)
1800 if (textview->show_url_msgid)
1801 gtk_statusbar_remove(GTK_STATUSBAR(
1802 textview->messageview->mainwin->statusbar),
1803 textview->messageview->mainwin->folderview_cid,
1804 textview->show_url_msgid);
1809 *\brief Check to see if a web URL has been disguised as a different
1810 * URL (possible with HTML email).
1812 *\param uri The uri to check
1814 *\param textview The TextView the URL is contained in
1816 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1817 * it anyway, otherwise FALSE
1819 static gboolean uri_security_check(RemoteURI *uri, TextView *textview)
1822 gboolean retval = TRUE;
1824 if (g_strncasecmp(uri->uri, "http:", 5) &&
1825 g_strncasecmp(uri->uri, "https:", 6) &&
1826 g_strncasecmp(uri->uri, "www.", 4))
1829 clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1832 if (clicked_str == NULL)
1835 if (strcmp(clicked_str, uri->uri) &&
1836 (!g_strncasecmp(clicked_str, "http:", 5) ||
1837 !g_strncasecmp(clicked_str, "https:", 6) ||
1838 !g_strncasecmp(clicked_str, "www.", 4))) {
1842 /* allow uri->uri == http://somewhere.com
1843 and clicked_str == somewhere.com */
1844 str = g_strconcat("http://", clicked_str, NULL);
1846 if (!g_strcasecmp(str, uri->uri))
1851 if (retval == FALSE) {
1855 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1856 "the apparent URL (%s). \n"
1858 uri->uri, clicked_str);
1859 resp = alertpanel(_("Warning"),
1865 if (resp == G_ALERTDEFAULT)
1868 g_free(clicked_str);
1872 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1876 textview->last_buttonpress = event->type;
1880 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1884 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1887 ((event->button == 1)
1888 || event->button == 2 || event->button == 3)) {
1891 /* double click seems to set the cursor after the current
1892 * word. The cursor position needs fixing, otherwise the
1893 * last word of a clickable zone will not work */
1894 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1895 textview->cur_pos--;
1898 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1899 RemoteURI *uri = (RemoteURI *)cur->data;
1901 if (textview->cur_pos >= uri->start &&
1902 textview->cur_pos <= uri->end) {
1905 trimmed_uri = trim_string(uri->uri, 60);
1906 /* single click: display url in statusbar */
1907 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1908 if (textview->messageview->mainwin) {
1909 if (textview->show_url_msgid) {
1910 gtk_timeout_remove(textview->show_url_timeout_tag);
1911 gtk_statusbar_remove(GTK_STATUSBAR(
1912 textview->messageview->mainwin->statusbar),
1913 textview->messageview->mainwin->folderview_cid,
1914 textview->show_url_msgid);
1915 textview->show_url_msgid = 0;
1917 textview->show_url_msgid = gtk_statusbar_push(
1918 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1919 textview->messageview->mainwin->folderview_cid,
1921 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1922 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1925 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1926 if (event->button == 3) {
1927 gchar *fromname, *fromaddress;
1930 fromaddress = g_strdup(uri->uri + 7);
1931 /* Hiroyuki: please put this function in utils.c! */
1932 fromname = procheader_get_fromname(fromaddress);
1933 extract_address(fromaddress);
1934 g_message("adding from textview %s <%s>", fromname, fromaddress);
1935 /* Add to address book - Match */
1936 addressbook_add_contact( fromname, fromaddress, NULL );
1938 g_free(fromaddress);
1941 PrefsAccount *account = NULL;
1942 FolderItem *folder_item;
1944 if (textview->messageview && textview->messageview->mainwin
1945 && textview->messageview->mainwin->summaryview
1946 && textview->messageview->mainwin->summaryview->folder_item) {
1947 folder_item = textview->messageview->mainwin->summaryview->folder_item;
1948 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1949 account = account_find_from_id(folder_item->prefs->default_account);
1951 compose_new(account, uri->uri + 7, NULL);
1954 if (uri_security_check(uri, textview) == TRUE)
1956 prefs_common.uri_cmd);
1958 g_free(trimmed_uri);
1963 textview->last_buttonpress = event->type;
1967 static void textview_uri_list_remove_all(GSList *uri_list)
1971 for (cur = uri_list; cur != NULL; cur = cur->next) {
1973 g_free(((RemoteURI *)cur->data)->uri);
1978 g_slist_free(uri_list);