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;
416 g_return_if_fail(mimeinfo != NULL);
418 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
420 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822")) {
423 fp = fopen(mimeinfo->filename, "rb");
424 fseek(fp, mimeinfo->offset, SEEK_SET);
425 headers = textview_scan_header(textview, fp);
427 gtk_stext_freeze(text);
428 if (gtk_stext_get_length(text) > 0)
429 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
430 textview_show_header(textview, headers);
431 procheader_header_array_destroy(headers);
432 gtk_stext_thaw(text);
438 gtk_stext_freeze(text);
440 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
442 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
444 g_snprintf(buf, sizeof(buf), "\n[%s %s/%s (%d bytes)]\n",
446 procmime_get_type_str(mimeinfo->type),
447 mimeinfo->subtype, mimeinfo->length);
449 g_snprintf(buf, sizeof(buf), "\n[%s/%s (%d bytes)]\n",
450 procmime_get_type_str(mimeinfo->type),
451 mimeinfo->subtype, mimeinfo->length);
453 if (mimeinfo->type != MIMETYPE_TEXT) {
454 gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
456 if (prefs_common.display_header && (gtk_stext_get_length(text) > 0))
457 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
458 if (textview->messageview->forced_charset)
459 charset = textview->messageview->forced_charset;
460 else if (prefs_common.force_charset)
461 charset = prefs_common.force_charset;
463 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
465 textview_write_body(textview, mimeinfo, charset);
468 gtk_stext_thaw(text);
472 static gboolean add_parts_func(GNode *node, gpointer data)
474 MimeInfo *mimeinfo = (MimeInfo *) node->data;
475 TextView *textview = (TextView *) data;
477 g_return_val_if_fail(mimeinfo != NULL, FALSE);
479 textview_add_part(textview, mimeinfo);
484 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
486 g_return_if_fail(mimeinfo != NULL);
488 g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, add_parts_func, textview);
492 static void recursive_add_parts(TextView *textview, GNode *node)
497 mimeinfo = (MimeInfo *) node->data;
499 textview_add_part(textview, mimeinfo);
501 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
502 (mimeinfo->type != MIMETYPE_MESSAGE))
505 if (strcasecmp(mimeinfo->subtype, "alternative") == 0) {
506 GNode * prefered_body;
514 prefered_body = NULL;
517 for(iter = g_node_first_child(node) ; iter != NULL ;
518 iter = g_node_next_sibling(iter)) {
523 submime = (MimeInfo *) iter->data;
524 if (submime->type == MIMETYPE_TEXT)
527 if (submime->subtype != NULL) {
528 if (strcasecmp(submime->subtype, "plain") == 0)
532 if (score > prefered_score) {
533 prefered_score = score;
534 prefered_body = iter;
538 if (prefered_body != NULL) {
539 recursive_add_parts(textview, prefered_body);
543 for(iter = g_node_first_child(node) ; iter != NULL ;
544 iter = g_node_next_sibling(iter)) {
545 recursive_add_parts(textview, iter);
550 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
552 g_return_if_fail(mimeinfo != NULL);
554 recursive_add_parts(textview, mimeinfo->node);
557 #define TEXT_INSERT(str) \
558 gtk_stext_insert(text, textview->msgfont, NULL, NULL, str, -1)
560 void textview_show_error(TextView *textview)
564 textview_set_font(textview, NULL);
565 text = GTK_STEXT(textview->text);
566 textview_clear(textview);
568 gtk_stext_freeze(text);
570 TEXT_INSERT(_("This message can't be displayed.\n"));
572 gtk_stext_thaw(text);
575 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
579 if (!partinfo) return;
581 textview_set_font(textview, NULL);
582 text = GTK_STEXT(textview->text);
583 textview_clear(textview);
585 gtk_stext_freeze(text);
587 TEXT_INSERT(_("The following can be performed on this part by "));
588 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
590 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
591 TEXT_INSERT(_(" To display as text select 'Display as text' "));
592 TEXT_INSERT(_("(Shortcut key: 't')\n"));
593 TEXT_INSERT(_(" To open with an external program select 'Open' "));
594 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
595 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
596 TEXT_INSERT(_("mouse button),\n"));
597 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
599 gtk_stext_thaw(text);
604 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
605 const gchar *charset)
611 conv = conv_code_converter_new(charset);
613 textview->is_in_signature = FALSE;
615 if(mimeinfo->encoding_type != ENC_BINARY &&
616 mimeinfo->encoding_type != ENC_7BIT &&
617 mimeinfo->encoding_type != ENC_8BIT)
618 procmime_decode_content(mimeinfo);
620 if (!g_strcasecmp(mimeinfo->subtype, "html")) {
623 filename = procmime_get_tmp_file_name(mimeinfo);
624 if (procmime_get_part(filename, mimeinfo) == 0) {
625 tmpfp = fopen(filename, "rb");
626 textview_show_html(textview, tmpfp, conv);
631 } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
634 filename = procmime_get_tmp_file_name(mimeinfo);
635 if (procmime_get_part(filename, mimeinfo) == 0) {
636 tmpfp = fopen(filename, "rb");
637 textview_show_ertf(textview, tmpfp, conv);
643 tmpfp = fopen(mimeinfo->filename, "rb");
644 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
645 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
646 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
647 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
648 textview_write_line(textview, buf, conv);
652 conv_code_converter_destroy(conv);
655 static void textview_show_html(TextView *textview, FILE *fp,
661 parser = html_parser_new(fp, conv);
662 g_return_if_fail(parser != NULL);
664 while ((str = html_parse(parser)) != NULL) {
665 if (parser->state == HTML_HREF) {
666 /* first time : get and copy the URL */
667 if (parser->href == NULL) {
668 /* ALF - the sylpheed html parser returns an empty string,
669 * if still inside an <a>, but already parsed past HREF */
670 str = strtok(str, " ");
672 parser->href = strdup(str);
673 /* the URL may (or not) be followed by the
675 str = strtok(NULL, "");
679 textview_write_link(textview, str, parser->href, NULL);
681 textview_write_line(textview, str, NULL);
683 html_parser_destroy(parser);
686 static void textview_show_ertf(TextView *textview, FILE *fp,
692 parser = ertf_parser_new(fp, conv);
693 g_return_if_fail(parser != NULL);
695 while ((str = ertf_parse(parser)) != NULL) {
696 textview_write_line(textview, str, NULL);
699 ertf_parser_destroy(parser);
702 /* get_uri_part() - retrieves a URI starting from scanpos.
703 Returns TRUE if succesful */
704 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
705 const gchar **bp, const gchar **ep)
709 g_return_val_if_fail(start != NULL, FALSE);
710 g_return_val_if_fail(scanpos != NULL, FALSE);
711 g_return_val_if_fail(bp != NULL, FALSE);
712 g_return_val_if_fail(ep != NULL, FALSE);
716 /* find end point of URI */
717 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
718 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
722 /* no punctuation at end of string */
724 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
725 * should pass some URI type to this function and decide on that whether
726 * to perform punctuation stripping */
728 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
730 for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
740 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
742 return g_strndup(bp, ep - bp);
745 /* valid mail address characters */
746 #define IS_RFC822_CHAR(ch) \
751 !strchr("(),;<>\"", (ch)))
753 /* alphabet and number within 7bit ASCII */
754 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
755 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
757 static GHashTable *create_domain_tab(void)
759 static const gchar *toplvl_domains [] = {
761 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
762 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
763 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
764 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
765 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
766 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
767 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
768 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
769 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
770 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
771 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
772 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
773 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
774 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
775 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
776 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
777 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
778 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
779 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
780 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
781 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
782 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
783 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
784 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
785 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
786 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
789 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
791 g_return_val_if_fail(htab, NULL);
792 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
793 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
797 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
799 const gint MAX_LVL_DOM_NAME_LEN = 6;
800 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
801 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
804 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
807 for (p = buf; p < m && first < last; *p++ = *first++)
811 return g_hash_table_lookup(tab, buf) != NULL;
814 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
815 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
816 const gchar **bp, const gchar **ep)
818 /* more complex than the uri part because we need to scan back and forward starting from
819 * the scan position. */
820 gboolean result = FALSE;
821 const gchar *bp_ = NULL;
822 const gchar *ep_ = NULL;
823 static GHashTable *dom_tab;
824 const gchar *last_dot = NULL;
825 const gchar *prelast_dot = NULL;
826 const gchar *last_tld_char = NULL;
828 /* the informative part of the email address (describing the name
829 * of the email address owner) may contain quoted parts. the
830 * closure stack stores the last encountered quotes. */
831 gchar closure_stack[128];
832 gchar *ptr = closure_stack;
834 g_return_val_if_fail(start != NULL, FALSE);
835 g_return_val_if_fail(scanpos != NULL, FALSE);
836 g_return_val_if_fail(bp != NULL, FALSE);
837 g_return_val_if_fail(ep != NULL, FALSE);
840 dom_tab = create_domain_tab();
841 g_return_val_if_fail(dom_tab, FALSE);
843 /* scan start of address */
844 for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
847 /* TODO: should start with an alnum? */
849 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
852 if (bp_ != scanpos) {
853 /* scan end of address */
854 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
856 prelast_dot = last_dot;
858 if (*(last_dot + 1) == '.') {
859 if (prelast_dot == NULL)
861 last_dot = prelast_dot;
866 /* TODO: really should terminate with an alnum? */
867 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
871 if (last_dot == NULL)
874 last_dot = prelast_dot;
875 if (last_dot == NULL || (scanpos + 1 >= last_dot))
879 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
880 if (*last_tld_char == '?')
883 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
890 if (!result) return FALSE;
892 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
893 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
896 /* see if this is <bracketed>; in this case we also scan for the informative part. */
897 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
900 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
901 #define IN_STACK() (ptr > closure_stack)
902 /* has underrun check */
903 #define POP_STACK() if(IN_STACK()) --ptr
904 /* has overrun check */
905 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
906 /* has underrun check */
907 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
911 /* scan for the informative part. */
912 for (bp_ -= 2; bp_ >= start; bp_--) {
913 /* if closure on the stack keep scanning */
914 if (PEEK_STACK() == *bp_) {
918 if (*bp_ == '\'' || *bp_ == '"') {
923 /* if nothing in the closure stack, do the special conditions
924 * the following if..else expression simply checks whether
925 * a token is acceptable. if not acceptable, the clause
926 * should terminate the loop with a 'break' */
929 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
930 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
931 /* hyphens are allowed, but only in
933 } else if (!ispunct(*bp_)) {
934 /* but anything not being a punctiation
937 break; /* anything else is rejected */
950 /* scan forward (should start with an alnum) */
951 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
961 #undef IS_RFC822_CHAR
963 static gchar *make_email_string(const gchar *bp, const gchar *ep)
965 /* returns a mailto: URI; mailto: is also used to detect the
966 * uri type later on in the button_pressed signal handler */
970 tmp = g_strndup(bp, ep - bp);
971 result = g_strconcat("mailto:", tmp, NULL);
977 #define ADD_TXT_POS(bp_, ep_, pti_) \
978 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
980 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
983 g_warning("alloc error scanning URIs\n"); \
984 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
989 /* textview_make_clickable_parts() - colorizes clickable parts */
990 static void textview_make_clickable_parts(TextView *textview,
994 const gchar *linebuf)
996 /* parse table - in order of priority */
998 const gchar *needle; /* token */
1000 /* token search function */
1001 gchar *(*search) (const gchar *haystack,
1002 const gchar *needle);
1003 /* part parsing function */
1004 gboolean (*parse) (const gchar *start,
1005 const gchar *scanpos,
1008 /* part to URI function */
1009 gchar *(*build_uri) (const gchar *bp,
1013 static struct table parser[] = {
1014 {"http://", strcasestr, get_uri_part, make_uri_string},
1015 {"https://", strcasestr, get_uri_part, make_uri_string},
1016 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1017 {"www.", strcasestr, get_uri_part, make_uri_string},
1018 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1019 {"@", strcasestr, get_email_part, make_email_string}
1021 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1024 const gchar *walk, *bp, *ep;
1027 const gchar *bp, *ep; /* text position */
1028 gint pti; /* index in parse table */
1029 struct txtpos *next; /* next */
1030 } head = {NULL, NULL, 0, NULL}, *last = &head;
1032 GtkSText *text = GTK_STEXT(textview->text);
1034 /* parse for clickable parts, and build a list of begin and end positions */
1035 for (walk = linebuf, n = 0;;) {
1036 gint last_index = PARSE_ELEMS;
1037 gchar *scanpos = NULL;
1039 /* FIXME: this looks phony. scanning for anything in the parse table */
1040 for (n = 0; n < PARSE_ELEMS; n++) {
1043 tmp = parser[n].search(walk, parser[n].needle);
1045 if (scanpos == NULL || tmp < scanpos) {
1053 /* check if URI can be parsed */
1054 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1055 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1056 ADD_TXT_POS(bp, ep, last_index);
1060 strlen(parser[last_index].needle);
1065 /* colorize this line */
1067 const gchar *normal_text = linebuf;
1070 for (last = head.next; last != NULL;
1071 normal_text = last->ep, last = last->next) {
1074 uri = g_new(RemoteURI, 1);
1075 if (last->bp - normal_text > 0)
1076 gtk_stext_insert(text, font,
1079 last->bp - normal_text);
1080 uri->uri = parser[last->pti].build_uri(last->bp,
1082 uri->start = gtk_stext_get_point(text);
1083 gtk_stext_insert(text, font, uri_color,
1084 NULL, last->bp, last->ep - last->bp);
1085 uri->end = gtk_stext_get_point(text);
1086 textview->uri_list =
1087 g_slist_append(textview->uri_list, uri);
1091 gtk_stext_insert(text, font, fg_color,
1092 NULL, normal_text, -1);
1094 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1099 static void textview_write_line(TextView *textview, const gchar *str,
1100 CodeConverter *conv)
1102 GtkSText *text = GTK_STEXT(textview->text);
1103 gchar buf[BUFFSIZE];
1105 gint quotelevel = -1;
1108 if (textview->text_is_mb)
1109 conv_localetodisp(buf, sizeof(buf), str);
1111 strncpy2(buf, str, sizeof(buf));
1112 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1113 conv_localetodisp(buf, sizeof(buf), str);
1114 else if (textview->text_is_mb)
1115 conv_unreadable_locale(buf);
1118 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1121 /* change color of quotation
1122 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1123 Up to 3 levels of quotations are detected, and each
1124 level is colored using a different color. */
1125 if (prefs_common.enable_color
1126 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1127 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1129 /* set up the correct foreground color */
1130 if (quotelevel > 2) {
1131 /* recycle colors */
1132 if (prefs_common.recycle_quote_colors)
1139 if (quotelevel == -1)
1142 fg_color = "e_colors[quotelevel];
1144 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1145 fg_color = &signature_color;
1146 textview->is_in_signature = TRUE;
1149 if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1150 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1152 if (prefs_common.enable_color)
1153 textview_make_clickable_parts(textview, textview->msgfont,
1154 fg_color, &uri_color, buf);
1156 textview_make_clickable_parts(textview, textview->msgfont,
1157 fg_color, NULL, buf);
1160 void textview_write_link(TextView *textview, const gchar *str,
1161 const gchar *uri, CodeConverter *conv)
1163 GdkColor *link_color = NULL;
1164 GtkSText *text = GTK_STEXT(textview->text);
1165 gchar buf[BUFFSIZE];
1173 if (textview->text_is_mb)
1174 conv_localetodisp(buf, sizeof(buf), str);
1176 strncpy2(buf, str, sizeof(buf));
1177 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1178 conv_localetodisp(buf, sizeof(buf), str);
1179 else if (textview->text_is_mb)
1180 conv_unreadable_locale(buf);
1184 for (bufp = buf; isspace(*bufp); bufp++)
1185 gtk_stext_insert(text, textview->msgfont, NULL, NULL, bufp, 1);
1187 if (prefs_common.enable_color) {
1188 link_color = &uri_color;
1190 r_uri = g_new(RemoteURI, 1);
1191 r_uri->uri = g_strdup(uri);
1192 r_uri->start = gtk_stext_get_point(text);
1193 gtk_stext_insert(text, textview->msgfont, link_color, NULL, bufp, -1);
1194 r_uri->end = gtk_stext_get_point(text);
1195 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1198 void textview_clear(TextView *textview)
1200 GtkSText *text = GTK_STEXT(textview->text);
1202 gtk_stext_freeze(text);
1203 gtk_stext_set_point(text, 0);
1204 gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1205 gtk_stext_thaw(text);
1207 textview_uri_list_remove_all(textview->uri_list);
1208 textview->uri_list = NULL;
1210 textview->body_pos = 0;
1211 textview->cur_pos = 0;
1214 void textview_destroy(TextView *textview)
1216 textview_uri_list_remove_all(textview->uri_list);
1217 textview->uri_list = NULL;
1219 if (!textview->scrolledwin_sb->parent)
1220 gtk_widget_destroy(textview->scrolledwin_sb);
1221 if (!textview->scrolledwin_mb->parent)
1222 gtk_widget_destroy(textview->scrolledwin_mb);
1224 if (textview->msgfont)
1225 gdk_font_unref(textview->msgfont);
1226 if (textview->boldfont)
1227 gdk_font_unref(textview->boldfont);
1232 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1234 textview->show_all_headers = all_headers;
1237 void textview_set_font(TextView *textview, const gchar *codeset)
1239 gboolean use_fontset = TRUE;
1241 /* In multi-byte mode, GtkSText can't display 8bit characters
1242 correctly, so it must be single-byte mode. */
1243 if (MB_CUR_MAX > 1) {
1244 if (codeset && conv_get_current_charset() != C_UTF_8) {
1245 if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1246 !g_strcasecmp(codeset, "BALTIC"))
1247 use_fontset = FALSE;
1248 else if (conv_get_current_charset() != C_EUC_JP &&
1249 (!g_strncasecmp(codeset, "KOI8-", 5) ||
1250 !g_strncasecmp(codeset, "CP", 2) ||
1251 !g_strncasecmp(codeset, "WINDOWS-", 8)))
1252 use_fontset = FALSE;
1255 use_fontset = FALSE;
1257 if (textview->text_is_mb && !use_fontset) {
1260 parent = textview->scrolledwin_mb->parent;
1261 gtkut_container_remove(GTK_CONTAINER(parent),
1262 textview->scrolledwin_mb);
1263 gtk_container_add(GTK_CONTAINER(parent),
1264 textview->scrolledwin_sb);
1266 textview->text = textview->text_sb;
1267 textview->text_is_mb = FALSE;
1268 } else if (!textview->text_is_mb && use_fontset) {
1271 parent = textview->scrolledwin_sb->parent;
1272 gtkut_container_remove(GTK_CONTAINER(parent),
1273 textview->scrolledwin_sb);
1274 gtk_container_add(GTK_CONTAINER(parent),
1275 textview->scrolledwin_mb);
1277 textview->text = textview->text_mb;
1278 textview->text_is_mb = TRUE;
1281 if (prefs_common.textfont) {
1286 text_mb_font->ascent = text_mb_font_orig_ascent;
1287 text_mb_font->descent = text_mb_font_orig_descent;
1289 font = gdk_fontset_load(prefs_common.textfont);
1290 if (font && text_mb_font != font) {
1292 gdk_font_unref(text_mb_font);
1293 text_mb_font = font;
1294 text_mb_font_orig_ascent = font->ascent;
1295 text_mb_font_orig_descent = font->descent;
1299 text_sb_font->ascent = text_sb_font_orig_ascent;
1300 text_sb_font->descent = text_sb_font_orig_descent;
1303 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1305 font = gtkut_font_load_from_fontset
1306 (prefs_common.textfont);
1307 if (font && text_sb_font != font) {
1309 gdk_font_unref(text_sb_font);
1310 text_sb_font = font;
1311 text_sb_font_orig_ascent = font->ascent;
1312 text_sb_font_orig_descent = font->descent;
1317 gint ascent, descent;
1319 descent = prefs_common.line_space / 2;
1320 ascent = prefs_common.line_space - descent;
1321 font->ascent += ascent;
1322 font->descent += descent;
1324 if (textview->msgfont)
1325 gdk_font_unref(textview->msgfont);
1326 textview->msgfont = font;
1331 if (!textview->boldfont && prefs_common.boldfont)
1332 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1334 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1337 void textview_set_text(TextView *textview, const gchar *text)
1341 g_return_if_fail(textview != NULL);
1342 g_return_if_fail(text != NULL);
1344 textview_clear(textview);
1346 stext = GTK_STEXT(textview->text);
1347 gtk_stext_freeze(stext);
1348 gtk_stext_insert(stext, textview->msgfont, NULL, NULL, text, strlen(text));
1349 gtk_stext_thaw(stext);
1365 H_ORGANIZATION = 11,
1368 void textview_set_position(TextView *textview, gint pos)
1372 gtk_stext_get_length(GTK_STEXT(textview->text));
1374 textview->cur_pos = pos;
1378 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1380 gchar buf[BUFFSIZE];
1381 GPtrArray *headers, *sorted_headers;
1382 GSList *disphdr_list;
1386 g_return_val_if_fail(fp != NULL, NULL);
1388 if (textview->show_all_headers)
1389 return procheader_get_header_array_asis(fp);
1391 if (!prefs_common.display_header) {
1392 while (fgets(buf, sizeof(buf), fp) != NULL)
1393 if (buf[0] == '\r' || buf[0] == '\n') break;
1397 headers = procheader_get_header_array_asis(fp);
1399 sorted_headers = g_ptr_array_new();
1401 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1402 disphdr_list = disphdr_list->next) {
1403 DisplayHeaderProp *dp =
1404 (DisplayHeaderProp *)disphdr_list->data;
1406 for (i = 0; i < headers->len; i++) {
1407 header = g_ptr_array_index(headers, i);
1409 if (procheader_headername_equal(header->name,
1412 procheader_header_free(header);
1414 g_ptr_array_add(sorted_headers, header);
1416 g_ptr_array_remove_index(headers, i);
1422 if (prefs_common.show_other_header) {
1423 for (i = 0; i < headers->len; i++) {
1424 header = g_ptr_array_index(headers, i);
1425 g_ptr_array_add(sorted_headers, header);
1427 g_ptr_array_free(headers, TRUE);
1429 procheader_header_array_destroy(headers);
1432 return sorted_headers;
1435 static void textview_show_header(TextView *textview, GPtrArray *headers)
1437 GtkSText *text = GTK_STEXT(textview->text);
1441 g_return_if_fail(headers != NULL);
1443 gtk_stext_freeze(text);
1445 for (i = 0; i < headers->len; i++) {
1446 header = g_ptr_array_index(headers, i);
1447 g_return_if_fail(header->name != NULL);
1449 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1451 if (header->name[strlen(header->name) - 1] != ' ')
1452 gtk_stext_insert(text, textview->boldfont,
1453 NULL, NULL, " ", 1);
1455 if (procheader_headername_equal(header->name, "Subject") ||
1456 procheader_headername_equal(header->name, "From") ||
1457 procheader_headername_equal(header->name, "To") ||
1458 procheader_headername_equal(header->name, "Cc"))
1459 unfold_line(header->body);
1461 if (textview->text_is_mb == TRUE)
1462 conv_unreadable_locale(header->body);
1464 if (prefs_common.enable_color &&
1465 (procheader_headername_equal(header->name, "X-Mailer") ||
1466 procheader_headername_equal(header->name,
1468 strstr(header->body, "Sylpheed") != NULL)
1469 gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1471 else if (prefs_common.enable_color) {
1472 textview_make_clickable_parts(textview,
1473 NULL, NULL, &uri_color,
1476 textview_make_clickable_parts(textview,
1480 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1483 gtk_stext_thaw(text);
1486 gboolean textview_search_string(TextView *textview, const gchar *str,
1489 GtkSText *text = GTK_STEXT(textview->text);
1493 g_return_val_if_fail(str != NULL, FALSE);
1495 len = get_mbs_len(str);
1496 g_return_val_if_fail(len >= 0, FALSE);
1498 pos = textview->cur_pos;
1499 if (pos < textview->body_pos)
1500 pos = textview->body_pos;
1502 if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1503 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1504 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1505 textview_set_position(textview, pos + len);
1512 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1515 GtkSText *text = GTK_STEXT(textview->text);
1520 gboolean found = FALSE;
1522 g_return_val_if_fail(str != NULL, FALSE);
1524 wcs = strdup_mbstowcs(str);
1525 g_return_val_if_fail(wcs != NULL, FALSE);
1527 pos = textview->cur_pos;
1528 text_len = gtk_stext_get_length(text);
1529 if (text_len - textview->body_pos < len) {
1533 if (pos <= textview->body_pos || text_len - pos < len)
1534 pos = text_len - len;
1536 for (; pos >= textview->body_pos; pos--) {
1537 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1539 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1540 gtk_editable_select_region(GTK_EDITABLE(text),
1542 textview_set_position(textview, pos - 1);
1546 if (pos == textview->body_pos) break;
1553 void textview_scroll_one_line(TextView *textview, gboolean up)
1555 GtkSText *text = GTK_STEXT(textview->text);
1558 if (prefs_common.enable_smooth_scroll) {
1559 textview_smooth_scroll_one_line(textview, up);
1564 upper = text->vadj->upper - text->vadj->page_size;
1565 if (text->vadj->value < upper) {
1566 text->vadj->value +=
1567 text->vadj->step_increment * 4;
1569 MIN(text->vadj->value, upper);
1570 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1574 if (text->vadj->value > 0.0) {
1575 text->vadj->value -=
1576 text->vadj->step_increment * 4;
1578 MAX(text->vadj->value, 0.0);
1579 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1585 gboolean textview_scroll_page(TextView *textview, gboolean up)
1587 GtkSText *text = GTK_STEXT(textview->text);
1591 if (prefs_common.enable_smooth_scroll)
1592 return textview_smooth_scroll_page(textview, up);
1594 if (prefs_common.scroll_halfpage)
1595 page_incr = text->vadj->page_increment / 2;
1597 page_incr = text->vadj->page_increment;
1600 upper = text->vadj->upper - text->vadj->page_size;
1601 if (text->vadj->value < upper) {
1602 text->vadj->value += page_incr;
1603 text->vadj->value = MIN(text->vadj->value, upper);
1604 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1609 if (text->vadj->value > 0.0) {
1610 text->vadj->value -= page_incr;
1611 text->vadj->value = MAX(text->vadj->value, 0.0);
1612 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1621 static void textview_smooth_scroll_do(TextView *textview,
1622 gfloat old_value, gfloat last_value,
1625 GtkSText *text = GTK_STEXT(textview->text);
1630 if (old_value < last_value) {
1631 change_value = last_value - old_value;
1634 change_value = old_value - last_value;
1638 gdk_key_repeat_disable();
1640 for (i = step; i <= change_value; i += step) {
1641 text->vadj->value = old_value + (up ? -i : i);
1642 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1646 text->vadj->value = last_value;
1647 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1649 gdk_key_repeat_restore();
1652 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1654 GtkSText *text = GTK_STEXT(textview->text);
1660 upper = text->vadj->upper - text->vadj->page_size;
1661 if (text->vadj->value < upper) {
1662 old_value = text->vadj->value;
1663 last_value = text->vadj->value +
1664 text->vadj->step_increment * 4;
1665 last_value = MIN(last_value, upper);
1667 textview_smooth_scroll_do(textview, old_value,
1669 prefs_common.scroll_step);
1672 if (text->vadj->value > 0.0) {
1673 old_value = text->vadj->value;
1674 last_value = text->vadj->value -
1675 text->vadj->step_increment * 4;
1676 last_value = MAX(last_value, 0.0);
1678 textview_smooth_scroll_do(textview, old_value,
1680 prefs_common.scroll_step);
1685 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1687 GtkSText *text = GTK_STEXT(textview->text);
1693 if (prefs_common.scroll_halfpage)
1694 page_incr = text->vadj->page_increment / 2;
1696 page_incr = text->vadj->page_increment;
1699 upper = text->vadj->upper - text->vadj->page_size;
1700 if (text->vadj->value < upper) {
1701 old_value = text->vadj->value;
1702 last_value = text->vadj->value + page_incr;
1703 last_value = MIN(last_value, upper);
1705 textview_smooth_scroll_do(textview, old_value,
1707 prefs_common.scroll_step);
1711 if (text->vadj->value > 0.0) {
1712 old_value = text->vadj->value;
1713 last_value = text->vadj->value - page_incr;
1714 last_value = MAX(last_value, 0.0);
1716 textview_smooth_scroll_do(textview, old_value,
1718 prefs_common.scroll_step);
1726 #define KEY_PRESS_EVENT_STOP() \
1727 if (gtk_signal_n_emissions_by_name \
1728 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1729 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1730 "key_press_event"); \
1733 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1736 SummaryView *summaryview = NULL;
1737 MessageView *messageview = textview->messageview;
1739 if (!event) return FALSE;
1740 if (messageview->mainwin)
1741 summaryview = messageview->mainwin->summaryview;
1743 switch (event->keyval) {
1758 summary_pass_key_press_event(summaryview, event);
1760 textview_scroll_page(textview, FALSE);
1763 textview_scroll_page(textview, TRUE);
1766 textview_scroll_one_line(textview,
1767 (event->state & GDK_MOD1_MASK) != 0);
1771 summary_pass_key_press_event(summaryview, event);
1776 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1777 KEY_PRESS_EVENT_STOP();
1778 mimeview_pass_key_press_event(messageview->mimeview,
1782 /* possible fall through */
1785 event->window != messageview->mainwin->window->window) {
1786 GdkEventKey tmpev = *event;
1788 tmpev.window = messageview->mainwin->window->window;
1789 KEY_PRESS_EVENT_STOP();
1790 gtk_widget_event(messageview->mainwin->window,
1791 (GdkEvent *)&tmpev);
1799 static gint show_url_timeout_cb(gpointer data)
1801 TextView *textview = (TextView *)data;
1803 if (textview->messageview->mainwin)
1804 if (textview->show_url_msgid)
1805 gtk_statusbar_remove(GTK_STATUSBAR(
1806 textview->messageview->mainwin->statusbar),
1807 textview->messageview->mainwin->folderview_cid,
1808 textview->show_url_msgid);
1813 *\brief Check to see if a web URL has been disguised as a different
1814 * URL (possible with HTML email).
1816 *\param uri The uri to check
1818 *\param textview The TextView the URL is contained in
1820 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1821 * it anyway, otherwise FALSE
1823 static gboolean uri_security_check(RemoteURI *uri, TextView *textview)
1826 gboolean retval = TRUE;
1828 if (g_strncasecmp(uri->uri, "http:", 5) &&
1829 g_strncasecmp(uri->uri, "https:", 6) &&
1830 g_strncasecmp(uri->uri, "www.", 4))
1833 clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1836 if (clicked_str == NULL)
1839 if (strcmp(clicked_str, uri->uri) &&
1840 (!g_strncasecmp(clicked_str, "http:", 5) ||
1841 !g_strncasecmp(clicked_str, "https:", 6) ||
1842 !g_strncasecmp(clicked_str, "www.", 4))) {
1846 /* allow uri->uri == http://somewhere.com
1847 and clicked_str == somewhere.com */
1848 str = g_strconcat("http://", clicked_str, NULL);
1850 if (!g_strcasecmp(str, uri->uri))
1855 if (retval == FALSE) {
1859 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1860 "the apparent URL (%s). \n"
1862 uri->uri, clicked_str);
1863 resp = alertpanel(_("Warning"),
1869 if (resp == G_ALERTDEFAULT)
1872 g_free(clicked_str);
1876 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1880 textview->last_buttonpress = event->type;
1884 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1888 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1891 ((event->button == 1)
1892 || event->button == 2 || event->button == 3)) {
1895 /* double click seems to set the cursor after the current
1896 * word. The cursor position needs fixing, otherwise the
1897 * last word of a clickable zone will not work */
1898 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1899 textview->cur_pos--;
1902 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1903 RemoteURI *uri = (RemoteURI *)cur->data;
1905 if (textview->cur_pos >= uri->start &&
1906 textview->cur_pos <= uri->end) {
1909 trimmed_uri = trim_string(uri->uri, 60);
1910 /* single click: display url in statusbar */
1911 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1912 if (textview->messageview->mainwin) {
1913 if (textview->show_url_msgid) {
1914 gtk_timeout_remove(textview->show_url_timeout_tag);
1915 gtk_statusbar_remove(GTK_STATUSBAR(
1916 textview->messageview->mainwin->statusbar),
1917 textview->messageview->mainwin->folderview_cid,
1918 textview->show_url_msgid);
1919 textview->show_url_msgid = 0;
1921 textview->show_url_msgid = gtk_statusbar_push(
1922 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1923 textview->messageview->mainwin->folderview_cid,
1925 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1926 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1929 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1930 if (event->button == 3) {
1931 gchar *fromname, *fromaddress;
1934 fromaddress = g_strdup(uri->uri + 7);
1935 /* Hiroyuki: please put this function in utils.c! */
1936 fromname = procheader_get_fromname(fromaddress);
1937 extract_address(fromaddress);
1938 g_message("adding from textview %s <%s>", fromname, fromaddress);
1939 /* Add to address book - Match */
1940 addressbook_add_contact( fromname, fromaddress, NULL );
1942 g_free(fromaddress);
1945 PrefsAccount *account = NULL;
1946 FolderItem *folder_item;
1948 if (textview->messageview && textview->messageview->mainwin
1949 && textview->messageview->mainwin->summaryview
1950 && textview->messageview->mainwin->summaryview->folder_item) {
1951 folder_item = textview->messageview->mainwin->summaryview->folder_item;
1952 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1953 account = account_find_from_id(folder_item->prefs->default_account);
1955 compose_new(account, uri->uri + 7, NULL);
1958 if (uri_security_check(uri, textview) == TRUE)
1960 prefs_common.uri_cmd);
1962 g_free(trimmed_uri);
1967 textview->last_buttonpress = event->type;
1971 static void textview_uri_list_remove_all(GSList *uri_list)
1975 for (cur = uri_list; cur != NULL; cur = cur->next) {
1977 g_free(((RemoteURI *)cur->data)->uri);
1982 g_slist_free(uri_list);