2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2004 Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 #include <gdk/gdkkeysyms.h>
29 #include <gtk/gtkvbox.h>
30 #include <gtk/gtkscrolledwindow.h>
31 #include <gtk/gtksignal.h>
39 #include "summaryview.h"
40 #include "procheader.h"
41 #include "prefs_common.h"
50 #include "addressbook.h"
51 #include "displayheader.h"
54 #include "alertpanel.h"
56 typedef struct _RemoteURI RemoteURI;
66 static GdkColor quote_colors[3] = {
67 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
68 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
69 {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
72 static GdkColor signature_color = {
79 static GdkColor uri_color = {
86 static GdkColor emphasis_color = {
94 static GdkColor error_color = {
102 static 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 #define TEXTVIEW_STATUSBAR_PUSH(textview, str) \
133 gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
134 textview->messageview->statusbar_cid, str); \
137 #define TEXTVIEW_STATUSBAR_POP(textview) \
139 gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
140 textview->messageview->statusbar_cid); \
143 static void textview_show_ertf (TextView *textview,
145 CodeConverter *conv);
146 static void textview_add_part (TextView *textview,
148 static void textview_add_parts (TextView *textview,
150 static void textview_write_body (TextView *textview,
152 const gchar *charset);
153 static void textview_show_html (TextView *textview,
155 CodeConverter *conv);
157 static void textview_write_line (TextView *textview,
159 CodeConverter *conv);
160 static void textview_write_link (TextView *textview,
163 CodeConverter *conv);
165 static GPtrArray *textview_scan_header (TextView *textview,
167 static void textview_show_header (TextView *textview,
170 static gint textview_key_pressed (GtkWidget *widget,
173 static gint textview_button_pressed (GtkWidget *widget,
174 GdkEventButton *event,
176 static gint textview_button_released (GtkWidget *widget,
177 GdkEventButton *event,
180 static void textview_smooth_scroll_do (TextView *textview,
184 static void textview_smooth_scroll_one_line (TextView *textview,
186 static gboolean textview_smooth_scroll_page (TextView *textview,
189 static gboolean textview_uri_security_check (TextView *textview,
191 static void textview_uri_list_remove_all (GSList *uri_list);
194 TextView *textview_create(void)
198 GtkWidget *scrolledwin_sb;
199 GtkWidget *scrolledwin_mb;
203 debug_print("Creating text view...\n");
204 textview = g_new0(TextView, 1);
206 scrolledwin_sb = gtk_scrolled_window_new(NULL, NULL);
207 scrolledwin_mb = gtk_scrolled_window_new(NULL, NULL);
208 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_sb),
209 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
210 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_mb),
211 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
212 gtk_widget_set_usize(scrolledwin_sb, prefs_common.mainview_width, -1);
213 gtk_widget_set_usize(scrolledwin_mb, prefs_common.mainview_width, -1);
215 /* create GtkSText widgets for single-byte and multi-byte character */
216 text_sb = gtk_stext_new(NULL, NULL);
217 text_mb = gtk_stext_new(NULL, NULL);
218 GTK_STEXT(text_sb)->default_tab_width = 8;
219 GTK_STEXT(text_mb)->default_tab_width = 8;
220 gtk_widget_show(text_sb);
221 gtk_widget_show(text_mb);
222 gtk_stext_set_word_wrap(GTK_STEXT(text_sb), TRUE);
223 gtk_stext_set_word_wrap(GTK_STEXT(text_mb), TRUE);
224 gtk_widget_ensure_style(text_sb);
225 gtk_widget_ensure_style(text_mb);
226 if (text_sb->style && text_sb->style->font->type == GDK_FONT_FONTSET) {
230 font = gtkut_font_load_from_fontset(prefs_common.normalfont);
232 style = gtk_style_copy(text_sb->style);
233 gdk_font_unref(style->font);
235 gtk_widget_set_style(text_sb, style);
238 if (text_mb->style && text_mb->style->font->type == GDK_FONT_FONT) {
242 font = gdk_fontset_load(prefs_common.normalfont);
244 style = gtk_style_copy(text_mb->style);
245 gdk_font_unref(style->font);
247 gtk_widget_set_style(text_mb, style);
250 gtk_widget_ref(scrolledwin_sb);
251 gtk_widget_ref(scrolledwin_mb);
253 gtk_container_add(GTK_CONTAINER(scrolledwin_sb), text_sb);
254 gtk_container_add(GTK_CONTAINER(scrolledwin_mb), text_mb);
255 gtk_signal_connect(GTK_OBJECT(text_sb), "key_press_event",
256 GTK_SIGNAL_FUNC(textview_key_pressed),
258 gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_press_event",
259 GTK_SIGNAL_FUNC(textview_button_pressed),
261 gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_release_event",
262 GTK_SIGNAL_FUNC(textview_button_released),
264 gtk_signal_connect(GTK_OBJECT(text_mb), "key_press_event",
265 GTK_SIGNAL_FUNC(textview_key_pressed),
267 gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_press_event",
268 GTK_SIGNAL_FUNC(textview_button_pressed),
270 gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_release_event",
271 GTK_SIGNAL_FUNC(textview_button_released),
274 gtk_widget_show(scrolledwin_sb);
275 gtk_widget_show(scrolledwin_mb);
277 vbox = gtk_vbox_new(FALSE, 0);
278 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin_sb, TRUE, TRUE, 0);
280 gtk_widget_show(vbox);
282 textview->vbox = vbox;
283 textview->scrolledwin = scrolledwin_sb;
284 textview->scrolledwin_sb = scrolledwin_sb;
285 textview->scrolledwin_mb = scrolledwin_mb;
286 textview->text = text_sb;
287 textview->text_sb = text_sb;
288 textview->text_mb = text_mb;
289 textview->text_is_mb = FALSE;
290 textview->uri_list = NULL;
291 textview->body_pos = 0;
292 textview->cur_pos = 0;
293 textview->show_all_headers = FALSE;
294 textview->last_buttonpress = GDK_NOTHING;
299 void textview_init(TextView *textview)
301 gtkut_widget_disable_theme_engine(textview->text_sb);
302 gtkut_widget_disable_theme_engine(textview->text_mb);
303 textview_update_message_colors();
304 textview_set_all_headers(textview, FALSE);
305 textview_set_font(textview, NULL);
308 void textview_update_message_colors(void)
310 GdkColor black = {0, 0, 0, 0};
312 if (prefs_common.enable_color) {
313 /* grab the quote colors, converting from an int to a GdkColor */
314 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
316 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
318 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
320 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
322 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
325 quote_colors[0] = quote_colors[1] = quote_colors[2] =
326 uri_color = emphasis_color = signature_color = black;
330 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
335 const gchar *charset = NULL;
337 if ((fp = fopen(file, "rb")) == NULL) {
338 FILE_OP_ERROR(file, "fopen");
342 if (textview->messageview->forced_charset)
343 charset = textview->messageview->forced_charset;
344 else if (prefs_common.force_charset)
345 charset = prefs_common.force_charset;
347 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
349 textview_set_font(textview, charset);
350 textview_clear(textview);
352 text = GTK_STEXT(textview->text);
354 gtk_stext_freeze(text);
357 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) perror("fseek");
358 headers = textview_scan_header(textview, fp);
360 textview_show_header(textview, headers);
361 procheader_header_array_destroy(headers);
362 textview->body_pos = gtk_stext_get_length(text);
365 textview_add_parts(textview, mimeinfo);
367 gtk_stext_thaw(text);
372 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
375 const gchar *charset = NULL;
377 g_return_if_fail(mimeinfo != NULL);
378 g_return_if_fail(fp != NULL);
380 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
381 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822"))) {
382 textview_clear(textview);
383 textview_add_parts(textview, mimeinfo);
387 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
390 headers = textview_scan_header(textview, fp);
392 if (textview->messageview->forced_charset)
393 charset = textview->messageview->forced_charset;
394 else if (prefs_common.force_charset)
395 charset = prefs_common.force_charset;
397 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
399 textview_set_font(textview, charset);
401 text = GTK_STEXT(textview->text);
403 gtk_stext_freeze(text);
404 textview_clear(textview);
408 textview_show_header(textview, headers);
409 procheader_header_array_destroy(headers);
410 textview->body_pos = gtk_stext_get_length(text);
412 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
415 if (mimeinfo->type == MIMETYPE_MULTIPART)
416 textview_add_parts(textview, mimeinfo);
418 textview_write_body(textview, mimeinfo, charset);
420 gtk_stext_thaw(text);
423 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
425 GtkSText *text = GTK_STEXT(textview->text);
427 const gchar *charset = NULL;
428 GPtrArray *headers = NULL;
432 g_return_if_fail(mimeinfo != NULL);
434 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
436 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822")) {
439 fp = fopen(mimeinfo->filename, "rb");
440 fseek(fp, mimeinfo->offset, SEEK_SET);
441 headers = textview_scan_header(textview, fp);
443 gtk_stext_freeze(text);
444 if (gtk_stext_get_length(text) > 0)
445 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
446 textview_show_header(textview, headers);
447 procheader_header_array_destroy(headers);
448 gtk_stext_thaw(text);
454 gtk_stext_freeze(text);
456 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
457 content_type = procmime_get_content_type_str(mimeinfo->type,
460 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
462 g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n",
463 name, content_type, mimeinfo->length);
465 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
466 content_type, mimeinfo->length);
468 g_free(content_type);
470 if (mimeinfo->type != MIMETYPE_TEXT) {
471 gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
472 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
473 if (prefs_common.display_header && (gtk_stext_get_length(text) > 0))
474 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
475 if (textview->messageview->forced_charset)
476 charset = textview->messageview->forced_charset;
477 else if (prefs_common.force_charset)
478 charset = prefs_common.force_charset;
480 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
482 textview_write_body(textview, mimeinfo, charset);
485 gtk_stext_thaw(text);
489 static gboolean add_parts_func(GNode *node, gpointer data)
491 MimeInfo *mimeinfo = (MimeInfo *) node->data;
492 TextView *textview = (TextView *) data;
494 g_return_val_if_fail(mimeinfo != NULL, FALSE);
496 textview_add_part(textview, mimeinfo);
501 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
503 g_return_if_fail(mimeinfo != NULL);
505 g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, add_parts_func, textview);
509 static void recursive_add_parts(TextView *textview, GNode *node)
514 mimeinfo = (MimeInfo *) node->data;
516 textview_add_part(textview, mimeinfo);
518 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
519 (mimeinfo->type != MIMETYPE_MESSAGE))
522 if (strcasecmp(mimeinfo->subtype, "alternative") == 0) {
523 GNode * prefered_body;
531 prefered_body = NULL;
534 for(iter = g_node_first_child(node) ; iter != NULL ;
535 iter = g_node_next_sibling(iter)) {
540 submime = (MimeInfo *) iter->data;
541 if (submime->type == MIMETYPE_TEXT)
544 if (submime->subtype != NULL) {
545 if (strcasecmp(submime->subtype, "plain") == 0)
549 if (score > prefered_score) {
550 prefered_score = score;
551 prefered_body = iter;
555 if (prefered_body != NULL) {
556 recursive_add_parts(textview, prefered_body);
560 for(iter = g_node_first_child(node) ; iter != NULL ;
561 iter = g_node_next_sibling(iter)) {
562 recursive_add_parts(textview, iter);
567 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
569 g_return_if_fail(mimeinfo != NULL);
571 recursive_add_parts(textview, mimeinfo->node);
574 #define TEXT_INSERT(str) \
575 gtk_stext_insert(text, textview->msgfont, NULL, NULL, str, -1)
577 void textview_show_error(TextView *textview)
581 textview_set_font(textview, NULL);
582 text = GTK_STEXT(textview->text);
583 textview_clear(textview);
585 gtk_stext_freeze(text);
587 TEXT_INSERT(_("This message can't be displayed.\n"));
589 gtk_stext_thaw(text);
592 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
596 if (!partinfo) return;
598 textview_set_font(textview, NULL);
599 text = GTK_STEXT(textview->text);
600 textview_clear(textview);
602 gtk_stext_freeze(text);
604 TEXT_INSERT(_("The following can be performed on this part by "));
605 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
607 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
608 TEXT_INSERT(_(" To display as text select 'Display as text' "));
609 TEXT_INSERT(_("(Shortcut key: 't')\n"));
610 TEXT_INSERT(_(" To open with an external program select 'Open' "));
611 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
612 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
613 TEXT_INSERT(_("mouse button),\n"));
614 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
616 gtk_stext_thaw(text);
621 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
622 const gchar *charset)
628 conv = conv_code_converter_new(charset);
630 textview->is_in_signature = FALSE;
632 if(mimeinfo->encoding_type != ENC_BINARY &&
633 mimeinfo->encoding_type != ENC_7BIT &&
634 mimeinfo->encoding_type != ENC_8BIT)
635 procmime_decode_content(mimeinfo);
637 if (!g_strcasecmp(mimeinfo->subtype, "html")) {
640 filename = procmime_get_tmp_file_name(mimeinfo);
641 if (procmime_get_part(filename, mimeinfo) == 0) {
642 tmpfp = fopen(filename, "rb");
643 textview_show_html(textview, tmpfp, conv);
648 } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
651 filename = procmime_get_tmp_file_name(mimeinfo);
652 if (procmime_get_part(filename, mimeinfo) == 0) {
653 tmpfp = fopen(filename, "rb");
654 textview_show_ertf(textview, tmpfp, conv);
660 tmpfp = fopen(mimeinfo->filename, "rb");
661 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
662 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
663 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
664 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
665 textview_write_line(textview, buf, conv);
669 conv_code_converter_destroy(conv);
672 static void textview_show_html(TextView *textview, FILE *fp,
678 parser = html_parser_new(fp, conv);
679 g_return_if_fail(parser != NULL);
681 while ((str = html_parse(parser)) != NULL) {
682 if (parser->state == HTML_HREF) {
683 /* first time : get and copy the URL */
684 if (parser->href == NULL) {
685 /* ALF - the sylpheed html parser returns an empty string,
686 * if still inside an <a>, but already parsed past HREF */
687 str = strtok(str, " ");
689 parser->href = strdup(str);
690 /* the URL may (or not) be followed by the
692 str = strtok(NULL, "");
696 textview_write_link(textview, str, parser->href, NULL);
698 textview_write_line(textview, str, NULL);
700 html_parser_destroy(parser);
703 static void textview_show_ertf(TextView *textview, FILE *fp,
709 parser = ertf_parser_new(fp, conv);
710 g_return_if_fail(parser != NULL);
712 while ((str = ertf_parse(parser)) != NULL) {
713 textview_write_line(textview, str, NULL);
716 ertf_parser_destroy(parser);
719 /* get_uri_part() - retrieves a URI starting from scanpos.
720 Returns TRUE if succesful */
721 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
722 const gchar **bp, const gchar **ep)
726 g_return_val_if_fail(start != NULL, FALSE);
727 g_return_val_if_fail(scanpos != NULL, FALSE);
728 g_return_val_if_fail(bp != NULL, FALSE);
729 g_return_val_if_fail(ep != NULL, FALSE);
733 /* find end point of URI */
734 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
735 if (!isgraph(*(const guchar *)ep_) ||
736 !isascii(*(const guchar *)ep_) ||
737 strchr("()<>\"", *ep_))
741 /* no punctuation at end of string */
743 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
744 * should pass some URI type to this function and decide on that whether
745 * to perform punctuation stripping */
747 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
749 for (; ep_ - 1 > scanpos + 1 &&
750 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
761 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
763 return g_strndup(bp, ep - bp);
766 /* valid mail address characters */
767 #define IS_RFC822_CHAR(ch) \
772 !strchr("(),;<>\"", (ch)))
774 /* alphabet and number within 7bit ASCII */
775 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
776 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
778 static GHashTable *create_domain_tab(void)
780 static const gchar *toplvl_domains [] = {
782 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
783 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
784 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
785 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
786 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
787 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
788 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
789 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
790 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
791 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
792 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
793 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
794 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
795 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
796 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
797 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
798 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
799 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
800 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
801 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
802 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
803 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
804 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
805 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
806 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
807 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
810 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
812 g_return_val_if_fail(htab, NULL);
813 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
814 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
818 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
820 const gint MAX_LVL_DOM_NAME_LEN = 6;
821 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
822 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
825 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
828 for (p = buf; p < m && first < last; *p++ = *first++)
832 return g_hash_table_lookup(tab, buf) != NULL;
835 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
836 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
837 const gchar **bp, const gchar **ep)
839 /* more complex than the uri part because we need to scan back and forward starting from
840 * the scan position. */
841 gboolean result = FALSE;
842 const gchar *bp_ = NULL;
843 const gchar *ep_ = NULL;
844 static GHashTable *dom_tab;
845 const gchar *last_dot = NULL;
846 const gchar *prelast_dot = NULL;
847 const gchar *last_tld_char = NULL;
849 /* the informative part of the email address (describing the name
850 * of the email address owner) may contain quoted parts. the
851 * closure stack stores the last encountered quotes. */
852 gchar closure_stack[128];
853 gchar *ptr = closure_stack;
855 g_return_val_if_fail(start != NULL, FALSE);
856 g_return_val_if_fail(scanpos != NULL, FALSE);
857 g_return_val_if_fail(bp != NULL, FALSE);
858 g_return_val_if_fail(ep != NULL, FALSE);
861 dom_tab = create_domain_tab();
862 g_return_val_if_fail(dom_tab, FALSE);
864 /* scan start of address */
865 for (bp_ = scanpos - 1;
866 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
869 /* TODO: should start with an alnum? */
871 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
874 if (bp_ != scanpos) {
875 /* scan end of address */
876 for (ep_ = scanpos + 1;
877 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
879 prelast_dot = last_dot;
881 if (*(last_dot + 1) == '.') {
882 if (prelast_dot == NULL)
884 last_dot = prelast_dot;
889 /* TODO: really should terminate with an alnum? */
890 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
895 if (last_dot == NULL)
898 last_dot = prelast_dot;
899 if (last_dot == NULL || (scanpos + 1 >= last_dot))
903 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
904 if (*last_tld_char == '?')
907 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
914 if (!result) return FALSE;
916 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
917 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
920 /* see if this is <bracketed>; in this case we also scan for the informative part. */
921 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
924 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
925 #define IN_STACK() (ptr > closure_stack)
926 /* has underrun check */
927 #define POP_STACK() if(IN_STACK()) --ptr
928 /* has overrun check */
929 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
930 /* has underrun check */
931 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
935 /* scan for the informative part. */
936 for (bp_ -= 2; bp_ >= start; bp_--) {
937 /* if closure on the stack keep scanning */
938 if (PEEK_STACK() == *bp_) {
942 if (*bp_ == '\'' || *bp_ == '"') {
947 /* if nothing in the closure stack, do the special conditions
948 * the following if..else expression simply checks whether
949 * a token is acceptable. if not acceptable, the clause
950 * should terminate the loop with a 'break' */
953 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
954 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
955 /* hyphens are allowed, but only in
957 } else if (!ispunct(*bp_)) {
958 /* but anything not being a punctiation
961 break; /* anything else is rejected */
974 /* scan forward (should start with an alnum) */
975 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
985 #undef IS_ASCII_ALNUM
986 #undef IS_RFC822_CHAR
988 static gchar *make_email_string(const gchar *bp, const gchar *ep)
990 /* returns a mailto: URI; mailto: is also used to detect the
991 * uri type later on in the button_pressed signal handler */
995 tmp = g_strndup(bp, ep - bp);
996 result = g_strconcat("mailto:", tmp, NULL);
1002 static gchar *make_http_string(const gchar *bp, const gchar *ep)
1004 /* returns an http: URI; */
1008 tmp = g_strndup(bp, ep - bp);
1009 result = g_strconcat("http://", tmp, NULL);
1015 #define ADD_TXT_POS(bp_, ep_, pti_) \
1016 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1017 last = last->next; \
1018 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1019 last->next = NULL; \
1021 g_warning("alloc error scanning URIs\n"); \
1022 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
1027 /* textview_make_clickable_parts() - colorizes clickable parts */
1028 static void textview_make_clickable_parts(TextView *textview,
1031 GdkColor *uri_color,
1032 const gchar *linebuf)
1034 /* parse table - in order of priority */
1036 const gchar *needle; /* token */
1038 /* token search function */
1039 gchar *(*search) (const gchar *haystack,
1040 const gchar *needle);
1041 /* part parsing function */
1042 gboolean (*parse) (const gchar *start,
1043 const gchar *scanpos,
1046 /* part to URI function */
1047 gchar *(*build_uri) (const gchar *bp,
1051 static struct table parser[] = {
1052 {"http://", strcasestr, get_uri_part, make_uri_string},
1053 {"https://", strcasestr, get_uri_part, make_uri_string},
1054 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1055 {"www.", strcasestr, get_uri_part, make_http_string},
1056 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1057 {"@", strcasestr, get_email_part, make_email_string}
1059 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1062 const gchar *walk, *bp, *ep;
1065 const gchar *bp, *ep; /* text position */
1066 gint pti; /* index in parse table */
1067 struct txtpos *next; /* next */
1068 } head = {NULL, NULL, 0, NULL}, *last = &head;
1070 GtkSText *text = GTK_STEXT(textview->text);
1072 /* parse for clickable parts, and build a list of begin and end positions */
1073 for (walk = linebuf, n = 0;;) {
1074 gint last_index = PARSE_ELEMS;
1075 gchar *scanpos = NULL;
1077 /* FIXME: this looks phony. scanning for anything in the parse table */
1078 for (n = 0; n < PARSE_ELEMS; n++) {
1081 tmp = parser[n].search(walk, parser[n].needle);
1083 if (scanpos == NULL || tmp < scanpos) {
1091 /* check if URI can be parsed */
1092 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1093 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1094 ADD_TXT_POS(bp, ep, last_index);
1098 strlen(parser[last_index].needle);
1103 /* colorize this line */
1105 const gchar *normal_text = linebuf;
1108 for (last = head.next; last != NULL;
1109 normal_text = last->ep, last = last->next) {
1112 uri = g_new(RemoteURI, 1);
1113 if (last->bp - normal_text > 0)
1114 gtk_stext_insert(text, font,
1117 last->bp - normal_text);
1118 uri->uri = parser[last->pti].build_uri(last->bp,
1120 uri->start = gtk_stext_get_point(text);
1121 gtk_stext_insert(text, font, uri_color,
1122 NULL, last->bp, last->ep - last->bp);
1123 uri->end = gtk_stext_get_point(text);
1124 textview->uri_list =
1125 g_slist_append(textview->uri_list, uri);
1129 gtk_stext_insert(text, font, fg_color,
1130 NULL, normal_text, -1);
1132 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1137 static void textview_write_line(TextView *textview, const gchar *str,
1138 CodeConverter *conv)
1140 GtkSText *text = GTK_STEXT(textview->text);
1141 gchar buf[BUFFSIZE];
1143 gint quotelevel = -1;
1146 if (textview->text_is_mb)
1147 conv_localetodisp(buf, sizeof(buf), str);
1149 strncpy2(buf, str, sizeof(buf));
1150 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1151 conv_localetodisp(buf, sizeof(buf), str);
1152 else if (textview->text_is_mb)
1153 conv_unreadable_locale(buf);
1156 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1159 /* change color of quotation
1160 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1161 Up to 3 levels of quotations are detected, and each
1162 level is colored using a different color. */
1163 if (prefs_common.enable_color
1164 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1165 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1167 /* set up the correct foreground color */
1168 if (quotelevel > 2) {
1169 /* recycle colors */
1170 if (prefs_common.recycle_quote_colors)
1177 if (quotelevel == -1)
1180 fg_color = "e_colors[quotelevel];
1182 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1183 fg_color = &signature_color;
1184 textview->is_in_signature = TRUE;
1187 if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1188 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1190 if (prefs_common.enable_color)
1191 textview_make_clickable_parts(textview, textview->msgfont,
1192 fg_color, &uri_color, buf);
1194 textview_make_clickable_parts(textview, textview->msgfont,
1195 fg_color, NULL, buf);
1198 void textview_write_link(TextView *textview, const gchar *str,
1199 const gchar *uri, CodeConverter *conv)
1201 GdkColor *link_color = NULL;
1202 GtkSText *text = GTK_STEXT(textview->text);
1203 gchar buf[BUFFSIZE];
1211 if (textview->text_is_mb)
1212 conv_localetodisp(buf, sizeof(buf), str);
1214 strncpy2(buf, str, sizeof(buf));
1215 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1216 conv_localetodisp(buf, sizeof(buf), str);
1217 else if (textview->text_is_mb)
1218 conv_unreadable_locale(buf);
1222 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1223 gtk_stext_insert(text, textview->msgfont, NULL, NULL, bufp, 1);
1225 if (prefs_common.enable_color) {
1226 link_color = &uri_color;
1228 r_uri = g_new(RemoteURI, 1);
1229 r_uri->uri = g_strdup(uri);
1230 r_uri->start = gtk_stext_get_point(text);
1231 gtk_stext_insert(text, textview->msgfont, link_color, NULL, bufp, -1);
1232 r_uri->end = gtk_stext_get_point(text);
1233 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1236 void textview_clear(TextView *textview)
1238 GtkSText *text = GTK_STEXT(textview->text);
1240 gtk_stext_freeze(text);
1241 gtk_stext_set_point(text, 0);
1242 gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1243 gtk_stext_thaw(text);
1245 TEXTVIEW_STATUSBAR_POP(textview);
1246 textview_uri_list_remove_all(textview->uri_list);
1247 textview->uri_list = NULL;
1249 textview->body_pos = 0;
1250 textview->cur_pos = 0;
1253 void textview_destroy(TextView *textview)
1255 textview_uri_list_remove_all(textview->uri_list);
1256 textview->uri_list = NULL;
1258 if (!textview->scrolledwin_sb->parent)
1259 gtk_widget_destroy(textview->scrolledwin_sb);
1260 if (!textview->scrolledwin_mb->parent)
1261 gtk_widget_destroy(textview->scrolledwin_mb);
1263 if (textview->msgfont)
1264 gdk_font_unref(textview->msgfont);
1265 if (textview->boldfont)
1266 gdk_font_unref(textview->boldfont);
1271 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1273 textview->show_all_headers = all_headers;
1276 void textview_set_font(TextView *textview, const gchar *codeset)
1278 gboolean use_fontset = TRUE;
1280 /* In multi-byte mode, GtkSText can't display 8bit characters
1281 correctly, so it must be single-byte mode. */
1282 if (MB_CUR_MAX > 1) {
1283 if (codeset && conv_get_current_charset() != C_UTF_8) {
1284 if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1285 !g_strcasecmp(codeset, "BALTIC"))
1286 use_fontset = FALSE;
1287 else if (conv_get_current_charset() != C_EUC_JP &&
1288 (!g_strncasecmp(codeset, "KOI8-", 5) ||
1289 !g_strncasecmp(codeset, "CP", 2) ||
1290 !g_strncasecmp(codeset, "WINDOWS-", 8)))
1291 use_fontset = FALSE;
1294 use_fontset = FALSE;
1296 if (textview->text_is_mb && !use_fontset) {
1299 parent = textview->scrolledwin_mb->parent;
1300 gtkut_container_remove(GTK_CONTAINER(parent),
1301 textview->scrolledwin_mb);
1302 gtk_container_add(GTK_CONTAINER(parent),
1303 textview->scrolledwin_sb);
1305 textview->text = textview->text_sb;
1306 textview->text_is_mb = FALSE;
1307 } else if (!textview->text_is_mb && use_fontset) {
1310 parent = textview->scrolledwin_sb->parent;
1311 gtkut_container_remove(GTK_CONTAINER(parent),
1312 textview->scrolledwin_sb);
1313 gtk_container_add(GTK_CONTAINER(parent),
1314 textview->scrolledwin_mb);
1316 textview->text = textview->text_mb;
1317 textview->text_is_mb = TRUE;
1320 if (prefs_common.textfont) {
1325 text_mb_font->ascent = text_mb_font_orig_ascent;
1326 text_mb_font->descent = text_mb_font_orig_descent;
1328 font = gdk_fontset_load(prefs_common.textfont);
1329 if (font && text_mb_font != font) {
1331 gdk_font_unref(text_mb_font);
1332 text_mb_font = font;
1333 text_mb_font_orig_ascent = font->ascent;
1334 text_mb_font_orig_descent = font->descent;
1338 text_sb_font->ascent = text_sb_font_orig_ascent;
1339 text_sb_font->descent = text_sb_font_orig_descent;
1342 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1344 font = gtkut_font_load_from_fontset
1345 (prefs_common.textfont);
1346 if (font && text_sb_font != font) {
1348 gdk_font_unref(text_sb_font);
1349 text_sb_font = font;
1350 text_sb_font_orig_ascent = font->ascent;
1351 text_sb_font_orig_descent = font->descent;
1356 gint ascent, descent;
1358 descent = prefs_common.line_space / 2;
1359 ascent = prefs_common.line_space - descent;
1360 font->ascent += ascent;
1361 font->descent += descent;
1363 if (textview->msgfont)
1364 gdk_font_unref(textview->msgfont);
1365 textview->msgfont = font;
1370 if (!textview->boldfont && prefs_common.boldfont)
1371 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1373 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1376 void textview_set_text(TextView *textview, const gchar *text)
1380 g_return_if_fail(textview != NULL);
1381 g_return_if_fail(text != NULL);
1383 textview_clear(textview);
1385 stext = GTK_STEXT(textview->text);
1386 gtk_stext_freeze(stext);
1387 gtk_stext_insert(stext, textview->msgfont, NULL, NULL, text, strlen(text));
1388 gtk_stext_thaw(stext);
1404 H_ORGANIZATION = 11,
1407 void textview_set_position(TextView *textview, gint pos)
1411 gtk_stext_get_length(GTK_STEXT(textview->text));
1413 textview->cur_pos = pos;
1417 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1419 gchar buf[BUFFSIZE];
1420 GPtrArray *headers, *sorted_headers;
1421 GSList *disphdr_list;
1425 g_return_val_if_fail(fp != NULL, NULL);
1427 if (textview->show_all_headers)
1428 return procheader_get_header_array_asis(fp);
1430 if (!prefs_common.display_header) {
1431 while (fgets(buf, sizeof(buf), fp) != NULL)
1432 if (buf[0] == '\r' || buf[0] == '\n') break;
1436 headers = procheader_get_header_array_asis(fp);
1438 sorted_headers = g_ptr_array_new();
1440 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1441 disphdr_list = disphdr_list->next) {
1442 DisplayHeaderProp *dp =
1443 (DisplayHeaderProp *)disphdr_list->data;
1445 for (i = 0; i < headers->len; i++) {
1446 header = g_ptr_array_index(headers, i);
1448 if (procheader_headername_equal(header->name,
1451 procheader_header_free(header);
1453 g_ptr_array_add(sorted_headers, header);
1455 g_ptr_array_remove_index(headers, i);
1461 if (prefs_common.show_other_header) {
1462 for (i = 0; i < headers->len; i++) {
1463 header = g_ptr_array_index(headers, i);
1464 g_ptr_array_add(sorted_headers, header);
1466 g_ptr_array_free(headers, TRUE);
1468 procheader_header_array_destroy(headers);
1471 return sorted_headers;
1474 static void textview_show_header(TextView *textview, GPtrArray *headers)
1476 GtkSText *text = GTK_STEXT(textview->text);
1480 g_return_if_fail(headers != NULL);
1482 gtk_stext_freeze(text);
1484 for (i = 0; i < headers->len; i++) {
1485 header = g_ptr_array_index(headers, i);
1486 g_return_if_fail(header->name != NULL);
1488 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1490 if (header->name[strlen(header->name) - 1] != ' ')
1491 gtk_stext_insert(text, textview->boldfont,
1492 NULL, NULL, " ", 1);
1494 if (procheader_headername_equal(header->name, "Subject") ||
1495 procheader_headername_equal(header->name, "From") ||
1496 procheader_headername_equal(header->name, "To") ||
1497 procheader_headername_equal(header->name, "Cc"))
1498 unfold_line(header->body);
1500 if (textview->text_is_mb == TRUE)
1501 conv_unreadable_locale(header->body);
1503 if (prefs_common.enable_color &&
1504 (procheader_headername_equal(header->name, "X-Mailer") ||
1505 procheader_headername_equal(header->name,
1507 strstr(header->body, "Sylpheed") != NULL)
1508 gtk_stext_insert(text, textview->msgfont, &emphasis_color, NULL,
1510 else if (prefs_common.enable_color) {
1511 textview_make_clickable_parts(textview,
1512 textview->msgfont, NULL, &uri_color,
1515 textview_make_clickable_parts(textview,
1516 textview->msgfont, NULL, NULL,
1519 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1522 gtk_stext_thaw(text);
1525 gboolean textview_search_string(TextView *textview, const gchar *str,
1528 GtkSText *text = GTK_STEXT(textview->text);
1532 g_return_val_if_fail(str != NULL, FALSE);
1534 len = get_mbs_len(str);
1535 g_return_val_if_fail(len >= 0, FALSE);
1537 pos = textview->cur_pos;
1538 if (pos < textview->body_pos)
1539 pos = textview->body_pos;
1541 if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1542 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1543 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1544 textview_set_position(textview, pos + len);
1551 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1554 GtkSText *text = GTK_STEXT(textview->text);
1559 gboolean found = FALSE;
1561 g_return_val_if_fail(str != NULL, FALSE);
1563 wcs = strdup_mbstowcs(str);
1564 g_return_val_if_fail(wcs != NULL, FALSE);
1566 pos = textview->cur_pos;
1567 text_len = gtk_stext_get_length(text);
1568 if (text_len - textview->body_pos < len) {
1572 if (pos <= textview->body_pos || text_len - pos < len)
1573 pos = text_len - len;
1575 for (; pos >= textview->body_pos; pos--) {
1576 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1578 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1579 gtk_editable_select_region(GTK_EDITABLE(text),
1581 textview_set_position(textview, pos - 1);
1585 if (pos == textview->body_pos) break;
1592 void textview_scroll_one_line(TextView *textview, gboolean up)
1594 GtkSText *text = GTK_STEXT(textview->text);
1597 if (prefs_common.enable_smooth_scroll) {
1598 textview_smooth_scroll_one_line(textview, up);
1603 upper = text->vadj->upper - text->vadj->page_size;
1604 if (text->vadj->value < upper) {
1605 text->vadj->value +=
1606 text->vadj->step_increment * 4;
1608 MIN(text->vadj->value, upper);
1609 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1613 if (text->vadj->value > 0.0) {
1614 text->vadj->value -=
1615 text->vadj->step_increment * 4;
1617 MAX(text->vadj->value, 0.0);
1618 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1624 gboolean textview_scroll_page(TextView *textview, gboolean up)
1626 GtkSText *text = GTK_STEXT(textview->text);
1630 if (prefs_common.enable_smooth_scroll)
1631 return textview_smooth_scroll_page(textview, up);
1633 if (prefs_common.scroll_halfpage)
1634 page_incr = text->vadj->page_increment / 2;
1636 page_incr = text->vadj->page_increment;
1639 upper = text->vadj->upper - text->vadj->page_size;
1640 if (text->vadj->value < upper) {
1641 text->vadj->value += page_incr;
1642 text->vadj->value = MIN(text->vadj->value, upper);
1643 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1648 if (text->vadj->value > 0.0) {
1649 text->vadj->value -= page_incr;
1650 text->vadj->value = MAX(text->vadj->value, 0.0);
1651 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1660 static void textview_smooth_scroll_do(TextView *textview,
1661 gfloat old_value, gfloat last_value,
1664 GtkSText *text = GTK_STEXT(textview->text);
1669 if (old_value < last_value) {
1670 change_value = last_value - old_value;
1673 change_value = old_value - last_value;
1677 gdk_key_repeat_disable();
1679 for (i = step; i <= change_value; i += step) {
1680 text->vadj->value = old_value + (up ? -i : i);
1681 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1685 text->vadj->value = last_value;
1686 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1688 gdk_key_repeat_restore();
1691 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1693 GtkSText *text = GTK_STEXT(textview->text);
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 +
1703 text->vadj->step_increment * 4;
1704 last_value = MIN(last_value, upper);
1706 textview_smooth_scroll_do(textview, old_value,
1708 prefs_common.scroll_step);
1711 if (text->vadj->value > 0.0) {
1712 old_value = text->vadj->value;
1713 last_value = text->vadj->value -
1714 text->vadj->step_increment * 4;
1715 last_value = MAX(last_value, 0.0);
1717 textview_smooth_scroll_do(textview, old_value,
1719 prefs_common.scroll_step);
1724 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1726 GtkSText *text = GTK_STEXT(textview->text);
1732 if (prefs_common.scroll_halfpage)
1733 page_incr = text->vadj->page_increment / 2;
1735 page_incr = text->vadj->page_increment;
1738 upper = text->vadj->upper - text->vadj->page_size;
1739 if (text->vadj->value < upper) {
1740 old_value = text->vadj->value;
1741 last_value = text->vadj->value + page_incr;
1742 last_value = MIN(last_value, upper);
1744 textview_smooth_scroll_do(textview, old_value,
1746 prefs_common.scroll_step);
1750 if (text->vadj->value > 0.0) {
1751 old_value = text->vadj->value;
1752 last_value = text->vadj->value - page_incr;
1753 last_value = MAX(last_value, 0.0);
1755 textview_smooth_scroll_do(textview, old_value,
1757 prefs_common.scroll_step);
1765 #define KEY_PRESS_EVENT_STOP() \
1766 if (gtk_signal_n_emissions_by_name \
1767 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1768 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1769 "key_press_event"); \
1772 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1775 SummaryView *summaryview = NULL;
1776 MessageView *messageview = textview->messageview;
1778 if (!event) return FALSE;
1779 if (messageview->mainwin)
1780 summaryview = messageview->mainwin->summaryview;
1782 switch (event->keyval) {
1797 summary_pass_key_press_event(summaryview, event);
1799 textview_scroll_page(textview, FALSE);
1802 textview_scroll_page(textview, TRUE);
1805 textview_scroll_one_line(textview,
1806 (event->state & GDK_MOD1_MASK) != 0);
1810 summary_pass_key_press_event(summaryview, event);
1815 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1816 KEY_PRESS_EVENT_STOP();
1817 mimeview_pass_key_press_event(messageview->mimeview,
1821 /* possible fall through */
1824 event->window != messageview->mainwin->window->window) {
1825 GdkEventKey tmpev = *event;
1827 tmpev.window = messageview->mainwin->window->window;
1828 KEY_PRESS_EVENT_STOP();
1829 gtk_widget_event(messageview->mainwin->window,
1830 (GdkEvent *)&tmpev);
1838 static gint show_url_timeout_cb(gpointer data)
1840 TextView *textview = (TextView *)data;
1842 TEXTVIEW_STATUSBAR_POP(textview);
1846 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1850 textview->last_buttonpress = event->type;
1854 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1858 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1861 ((event->button == 1)
1862 || event->button == 2 || event->button == 3)) {
1865 /* double click seems to set the cursor after the current
1866 * word. The cursor position needs fixing, otherwise the
1867 * last word of a clickable zone will not work */
1868 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1869 textview->cur_pos--;
1872 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1873 RemoteURI *uri = (RemoteURI *)cur->data;
1875 if (textview->cur_pos >= uri->start &&
1876 textview->cur_pos <= uri->end) {
1879 trimmed_uri = trim_string(uri->uri, 60);
1880 /* single click: display url in statusbar */
1881 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1882 if (textview->messageview->mainwin) {
1883 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1884 textview->show_url_timeout_tag = gtk_timeout_add
1885 (4000, show_url_timeout_cb, textview);
1887 } else if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1888 if (event->button == 3) {
1889 gchar *fromname, *fromaddress;
1892 fromaddress = g_strdup(uri->uri + 7);
1893 /* Hiroyuki: please put this function in utils.c! */
1894 fromname = procheader_get_fromname(fromaddress);
1895 extract_address(fromaddress);
1896 g_message("adding from textview %s <%s>", fromname, fromaddress);
1897 /* Add to address book - Match */
1898 addressbook_add_contact( fromname, fromaddress, NULL );
1900 g_free(fromaddress);
1903 PrefsAccount *account = NULL;
1904 FolderItem *folder_item;
1906 if (textview->messageview && textview->messageview->mainwin
1907 && textview->messageview->mainwin->summaryview
1908 && textview->messageview->mainwin->summaryview->folder_item) {
1909 folder_item = textview->messageview->mainwin->summaryview->folder_item;
1910 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1911 account = account_find_from_id(folder_item->prefs->default_account);
1913 compose_new(account, uri->uri + 7, NULL);
1916 if (textview_uri_security_check(textview, uri) == TRUE)
1918 prefs_common.uri_cmd);
1920 g_free(trimmed_uri);
1925 textview->last_buttonpress = event->type;
1930 *\brief Check to see if a web URL has been disguised as a different
1931 * URL (possible with HTML email).
1933 *\param uri The uri to check
1935 *\param textview The TextView the URL is contained in
1937 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1938 * it anyway, otherwise FALSE
1940 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
1943 gboolean retval = TRUE;
1945 if (is_uri_string(uri->uri) == FALSE)
1948 visible_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1949 uri->start, uri->end);
1950 if (visible_str == NULL)
1953 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
1955 gchar *visible_uri_path;
1957 uri_path = get_uri_path(uri->uri);
1958 visible_uri_path = get_uri_path(visible_str);
1959 if (strcmp(uri_path, visible_uri_path) != 0)
1963 if (retval == FALSE) {
1967 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1968 "the apparent URL (%s).\n"
1970 uri->uri, visible_str);
1971 aval = alertpanel(_("Warning"), msg, _("Yes"), _("No"), NULL);
1973 if (aval == G_ALERTDEFAULT)
1977 g_free(visible_str);
1982 static void textview_uri_list_remove_all(GSList *uri_list)
1986 for (cur = uri_list; cur != NULL; cur = cur->next) {
1988 g_free(((RemoteURI *)cur->data)->uri);
1993 g_slist_free(uri_list);