2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2004 Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 #include <gdk/gdkkeysyms.h>
29 #include <gtk/gtkvbox.h>
30 #include <gtk/gtkscrolledwindow.h>
31 #include <gtk/gtksignal.h>
39 #include "summaryview.h"
40 #include "procheader.h"
41 #include "prefs_common.h"
50 #include "addressbook.h"
51 #include "displayheader.h"
54 #include "alertpanel.h"
56 typedef struct _RemoteURI RemoteURI;
66 static GdkColor quote_colors[3] = {
67 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
68 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
69 {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
72 static GdkColor signature_color = {
79 static GdkColor uri_color = {
86 static GdkColor emphasis_color = {
94 static GdkColor error_color = {
102 static GdkFont *text_sb_font;
103 static GdkFont *text_mb_font;
104 static gint text_sb_font_orig_ascent;
105 static gint text_sb_font_orig_descent;
106 static gint text_mb_font_orig_ascent;
107 static gint text_mb_font_orig_descent;
108 static GdkFont *spacingfont;
110 #define TEXTVIEW_STATUSBAR_PUSH(textview, str) \
112 gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
113 textview->messageview->statusbar_cid, str); \
116 #define TEXTVIEW_STATUSBAR_POP(textview) \
118 gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
119 textview->messageview->statusbar_cid); \
122 static void textview_show_ertf (TextView *textview,
124 CodeConverter *conv);
125 static void textview_add_part (TextView *textview,
127 static void textview_add_parts (TextView *textview,
129 static void textview_write_body (TextView *textview,
131 const gchar *charset);
132 static void textview_show_html (TextView *textview,
134 CodeConverter *conv);
136 static void textview_write_line (TextView *textview,
138 CodeConverter *conv);
139 static void textview_write_link (TextView *textview,
142 CodeConverter *conv);
144 static GPtrArray *textview_scan_header (TextView *textview,
146 static void textview_show_header (TextView *textview,
149 static gint textview_key_pressed (GtkWidget *widget,
152 static gint textview_button_pressed (GtkWidget *widget,
153 GdkEventButton *event,
155 static gint textview_button_released (GtkWidget *widget,
156 GdkEventButton *event,
159 static void textview_smooth_scroll_do (TextView *textview,
163 static void textview_smooth_scroll_one_line (TextView *textview,
165 static gboolean textview_smooth_scroll_page (TextView *textview,
168 static gboolean textview_uri_security_check (TextView *textview,
170 static void textview_uri_list_remove_all (GSList *uri_list);
173 TextView *textview_create(void)
177 GtkWidget *scrolledwin_sb;
178 GtkWidget *scrolledwin_mb;
182 debug_print("Creating text view...\n");
183 textview = g_new0(TextView, 1);
185 scrolledwin_sb = gtk_scrolled_window_new(NULL, NULL);
186 scrolledwin_mb = gtk_scrolled_window_new(NULL, NULL);
187 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_sb),
188 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
189 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_mb),
190 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
191 gtk_widget_set_usize(scrolledwin_sb, prefs_common.mainview_width, -1);
192 gtk_widget_set_usize(scrolledwin_mb, prefs_common.mainview_width, -1);
194 /* create GtkSText widgets for single-byte and multi-byte character */
195 text_sb = gtk_stext_new(NULL, NULL);
196 text_mb = gtk_stext_new(NULL, NULL);
197 GTK_STEXT(text_sb)->default_tab_width = 8;
198 GTK_STEXT(text_mb)->default_tab_width = 8;
199 gtk_widget_show(text_sb);
200 gtk_widget_show(text_mb);
201 gtk_stext_set_word_wrap(GTK_STEXT(text_sb), TRUE);
202 gtk_stext_set_word_wrap(GTK_STEXT(text_mb), TRUE);
203 gtk_widget_ensure_style(text_sb);
204 gtk_widget_ensure_style(text_mb);
205 if (text_sb->style && text_sb->style->font->type == GDK_FONT_FONTSET) {
209 font = gtkut_font_load_from_fontset(prefs_common.normalfont);
211 style = gtk_style_copy(text_sb->style);
212 gdk_font_unref(style->font);
214 gtk_widget_set_style(text_sb, style);
217 if (text_mb->style && text_mb->style->font->type == GDK_FONT_FONT) {
221 font = gdk_fontset_load(prefs_common.normalfont);
223 style = gtk_style_copy(text_mb->style);
224 gdk_font_unref(style->font);
226 gtk_widget_set_style(text_mb, style);
229 gtk_widget_ref(scrolledwin_sb);
230 gtk_widget_ref(scrolledwin_mb);
232 gtk_container_add(GTK_CONTAINER(scrolledwin_sb), text_sb);
233 gtk_container_add(GTK_CONTAINER(scrolledwin_mb), text_mb);
234 gtk_signal_connect(GTK_OBJECT(text_sb), "key_press_event",
235 GTK_SIGNAL_FUNC(textview_key_pressed),
237 gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_press_event",
238 GTK_SIGNAL_FUNC(textview_button_pressed),
240 gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_release_event",
241 GTK_SIGNAL_FUNC(textview_button_released),
243 gtk_signal_connect(GTK_OBJECT(text_mb), "key_press_event",
244 GTK_SIGNAL_FUNC(textview_key_pressed),
246 gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_press_event",
247 GTK_SIGNAL_FUNC(textview_button_pressed),
249 gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_release_event",
250 GTK_SIGNAL_FUNC(textview_button_released),
253 gtk_widget_show(scrolledwin_sb);
254 gtk_widget_show(scrolledwin_mb);
256 vbox = gtk_vbox_new(FALSE, 0);
257 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin_sb, TRUE, TRUE, 0);
259 gtk_widget_show(vbox);
261 textview->vbox = vbox;
262 textview->scrolledwin = scrolledwin_sb;
263 textview->scrolledwin_sb = scrolledwin_sb;
264 textview->scrolledwin_mb = scrolledwin_mb;
265 textview->text = text_sb;
266 textview->text_sb = text_sb;
267 textview->text_mb = text_mb;
268 textview->text_is_mb = FALSE;
269 textview->uri_list = NULL;
270 textview->body_pos = 0;
271 textview->cur_pos = 0;
272 textview->show_all_headers = FALSE;
273 textview->last_buttonpress = GDK_NOTHING;
278 void textview_init(TextView *textview)
280 gtkut_widget_disable_theme_engine(textview->text_sb);
281 gtkut_widget_disable_theme_engine(textview->text_mb);
282 textview_update_message_colors();
283 textview_set_all_headers(textview, FALSE);
284 textview_set_font(textview, NULL);
287 void textview_update_message_colors(void)
289 GdkColor black = {0, 0, 0, 0};
291 if (prefs_common.enable_color) {
292 /* grab the quote colors, converting from an int to a GdkColor */
293 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
295 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
297 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
299 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
301 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
304 quote_colors[0] = quote_colors[1] = quote_colors[2] =
305 uri_color = emphasis_color = signature_color = black;
309 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
314 const gchar *charset = NULL;
316 if ((fp = fopen(file, "rb")) == NULL) {
317 FILE_OP_ERROR(file, "fopen");
321 if (textview->messageview->forced_charset)
322 charset = textview->messageview->forced_charset;
323 else if (prefs_common.force_charset)
324 charset = prefs_common.force_charset;
326 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
328 textview_set_font(textview, charset);
329 textview_clear(textview);
331 text = GTK_STEXT(textview->text);
333 gtk_stext_freeze(text);
336 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) perror("fseek");
337 headers = textview_scan_header(textview, fp);
339 textview_show_header(textview, headers);
340 procheader_header_array_destroy(headers);
341 textview->body_pos = gtk_stext_get_length(text);
344 textview_add_parts(textview, mimeinfo);
346 gtk_stext_thaw(text);
351 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
354 const gchar *charset = NULL;
356 g_return_if_fail(mimeinfo != NULL);
357 g_return_if_fail(fp != NULL);
359 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
360 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822"))) {
361 textview_clear(textview);
362 textview_add_parts(textview, mimeinfo);
366 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
369 headers = textview_scan_header(textview, fp);
371 if (textview->messageview->forced_charset)
372 charset = textview->messageview->forced_charset;
373 else if (prefs_common.force_charset)
374 charset = prefs_common.force_charset;
376 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
378 textview_set_font(textview, charset);
380 text = GTK_STEXT(textview->text);
382 gtk_stext_freeze(text);
383 textview_clear(textview);
387 textview_show_header(textview, headers);
388 procheader_header_array_destroy(headers);
389 textview->body_pos = gtk_stext_get_length(text);
391 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
394 if (mimeinfo->type == MIMETYPE_MULTIPART)
395 textview_add_parts(textview, mimeinfo);
397 textview_write_body(textview, mimeinfo, charset);
399 gtk_stext_thaw(text);
402 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
404 GtkSText *text = GTK_STEXT(textview->text);
406 const gchar *charset = NULL;
407 GPtrArray *headers = NULL;
411 g_return_if_fail(mimeinfo != NULL);
413 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
415 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822")) {
418 fp = fopen(mimeinfo->filename, "rb");
419 fseek(fp, mimeinfo->offset, SEEK_SET);
420 headers = textview_scan_header(textview, fp);
422 gtk_stext_freeze(text);
423 if (gtk_stext_get_length(text) > 0)
424 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
425 textview_show_header(textview, headers);
426 procheader_header_array_destroy(headers);
427 gtk_stext_thaw(text);
433 gtk_stext_freeze(text);
435 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
436 content_type = procmime_get_content_type_str(mimeinfo->type,
439 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
441 g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n",
442 name, content_type, mimeinfo->length);
444 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
445 content_type, mimeinfo->length);
447 g_free(content_type);
449 if (mimeinfo->type != MIMETYPE_TEXT) {
450 gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
451 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
452 if (prefs_common.display_header && (gtk_stext_get_length(text) > 0))
453 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
454 if (textview->messageview->forced_charset)
455 charset = textview->messageview->forced_charset;
456 else if (prefs_common.force_charset)
457 charset = prefs_common.force_charset;
459 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
461 textview_write_body(textview, mimeinfo, charset);
464 gtk_stext_thaw(text);
468 static gboolean add_parts_func(GNode *node, gpointer data)
470 MimeInfo *mimeinfo = (MimeInfo *) node->data;
471 TextView *textview = (TextView *) data;
473 g_return_val_if_fail(mimeinfo != NULL, FALSE);
475 textview_add_part(textview, mimeinfo);
480 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
482 g_return_if_fail(mimeinfo != NULL);
484 g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, add_parts_func, textview);
488 static void recursive_add_parts(TextView *textview, GNode *node)
493 mimeinfo = (MimeInfo *) node->data;
495 textview_add_part(textview, mimeinfo);
497 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
498 (mimeinfo->type != MIMETYPE_MESSAGE))
501 if (strcasecmp(mimeinfo->subtype, "alternative") == 0) {
502 GNode * prefered_body;
510 prefered_body = NULL;
513 for(iter = g_node_first_child(node) ; iter != NULL ;
514 iter = g_node_next_sibling(iter)) {
519 submime = (MimeInfo *) iter->data;
520 if (submime->type == MIMETYPE_TEXT)
523 if (submime->subtype != NULL) {
524 if (strcasecmp(submime->subtype, "plain") == 0)
528 if (score > prefered_score) {
529 prefered_score = score;
530 prefered_body = iter;
534 if (prefered_body != NULL) {
535 recursive_add_parts(textview, prefered_body);
539 for(iter = g_node_first_child(node) ; iter != NULL ;
540 iter = g_node_next_sibling(iter)) {
541 recursive_add_parts(textview, iter);
546 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
548 g_return_if_fail(mimeinfo != NULL);
550 recursive_add_parts(textview, mimeinfo->node);
553 #define TEXT_INSERT(str) \
554 gtk_stext_insert(text, textview->msgfont, NULL, NULL, str, -1)
556 void textview_show_error(TextView *textview)
560 textview_set_font(textview, NULL);
561 text = GTK_STEXT(textview->text);
562 textview_clear(textview);
564 gtk_stext_freeze(text);
566 TEXT_INSERT(_("This message can't be displayed.\n"));
568 gtk_stext_thaw(text);
571 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
575 if (!partinfo) return;
577 textview_set_font(textview, NULL);
578 text = GTK_STEXT(textview->text);
579 textview_clear(textview);
581 gtk_stext_freeze(text);
583 TEXT_INSERT(_("The following can be performed on this part by "));
584 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
586 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
587 TEXT_INSERT(_(" To display as text select 'Display as text' "));
588 TEXT_INSERT(_("(Shortcut key: 't')\n"));
589 TEXT_INSERT(_(" To open with an external program select 'Open' "));
590 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
591 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
592 TEXT_INSERT(_("mouse button),\n"));
593 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
595 gtk_stext_thaw(text);
600 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
601 const gchar *charset)
607 conv = conv_code_converter_new(charset);
609 textview->is_in_signature = FALSE;
611 if(mimeinfo->encoding_type != ENC_BINARY &&
612 mimeinfo->encoding_type != ENC_7BIT &&
613 mimeinfo->encoding_type != ENC_8BIT)
614 procmime_decode_content(mimeinfo);
616 if (!g_strcasecmp(mimeinfo->subtype, "html")) {
619 filename = procmime_get_tmp_file_name(mimeinfo);
620 if (procmime_get_part(filename, mimeinfo) == 0) {
621 tmpfp = fopen(filename, "rb");
622 textview_show_html(textview, tmpfp, conv);
627 } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
630 filename = procmime_get_tmp_file_name(mimeinfo);
631 if (procmime_get_part(filename, mimeinfo) == 0) {
632 tmpfp = fopen(filename, "rb");
633 textview_show_ertf(textview, tmpfp, conv);
639 tmpfp = fopen(mimeinfo->filename, "rb");
640 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
641 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
642 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
643 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
644 textview_write_line(textview, buf, conv);
648 conv_code_converter_destroy(conv);
651 static void textview_show_html(TextView *textview, FILE *fp,
657 parser = html_parser_new(fp, conv);
658 g_return_if_fail(parser != NULL);
660 while ((str = html_parse(parser)) != NULL) {
661 if (parser->state == HTML_HREF) {
662 /* first time : get and copy the URL */
663 if (parser->href == NULL) {
664 /* ALF - the sylpheed html parser returns an empty string,
665 * if still inside an <a>, but already parsed past HREF */
666 str = strtok(str, " ");
668 parser->href = strdup(str);
669 /* the URL may (or not) be followed by the
671 str = strtok(NULL, "");
675 textview_write_link(textview, str, parser->href, NULL);
677 textview_write_line(textview, str, NULL);
679 html_parser_destroy(parser);
682 static void textview_show_ertf(TextView *textview, FILE *fp,
688 parser = ertf_parser_new(fp, conv);
689 g_return_if_fail(parser != NULL);
691 while ((str = ertf_parse(parser)) != NULL) {
692 textview_write_line(textview, str, NULL);
695 ertf_parser_destroy(parser);
698 /* get_uri_part() - retrieves a URI starting from scanpos.
699 Returns TRUE if succesful */
700 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
701 const gchar **bp, const gchar **ep)
705 g_return_val_if_fail(start != NULL, FALSE);
706 g_return_val_if_fail(scanpos != NULL, FALSE);
707 g_return_val_if_fail(bp != NULL, FALSE);
708 g_return_val_if_fail(ep != NULL, FALSE);
712 /* find end point of URI */
713 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
714 if (!isgraph(*(const guchar *)ep_) ||
715 !isascii(*(const guchar *)ep_) ||
716 strchr("()<>\"", *ep_))
720 /* no punctuation at end of string */
722 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
723 * should pass some URI type to this function and decide on that whether
724 * to perform punctuation stripping */
726 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
728 for (; ep_ - 1 > scanpos + 1 &&
729 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
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;
845 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
848 /* TODO: should start with an alnum? */
850 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
853 if (bp_ != scanpos) {
854 /* scan end of address */
855 for (ep_ = scanpos + 1;
856 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
858 prelast_dot = last_dot;
860 if (*(last_dot + 1) == '.') {
861 if (prelast_dot == NULL)
863 last_dot = prelast_dot;
868 /* TODO: really should terminate with an alnum? */
869 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
874 if (last_dot == NULL)
877 last_dot = prelast_dot;
878 if (last_dot == NULL || (scanpos + 1 >= last_dot))
882 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
883 if (*last_tld_char == '?')
886 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
893 if (!result) return FALSE;
895 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
896 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
899 /* see if this is <bracketed>; in this case we also scan for the informative part. */
900 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
903 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
904 #define IN_STACK() (ptr > closure_stack)
905 /* has underrun check */
906 #define POP_STACK() if(IN_STACK()) --ptr
907 /* has overrun check */
908 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
909 /* has underrun check */
910 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
914 /* scan for the informative part. */
915 for (bp_ -= 2; bp_ >= start; bp_--) {
916 /* if closure on the stack keep scanning */
917 if (PEEK_STACK() == *bp_) {
921 if (*bp_ == '\'' || *bp_ == '"') {
926 /* if nothing in the closure stack, do the special conditions
927 * the following if..else expression simply checks whether
928 * a token is acceptable. if not acceptable, the clause
929 * should terminate the loop with a 'break' */
932 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
933 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
934 /* hyphens are allowed, but only in
936 } else if (!ispunct(*bp_)) {
937 /* but anything not being a punctiation
940 break; /* anything else is rejected */
953 /* scan forward (should start with an alnum) */
954 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
964 #undef IS_ASCII_ALNUM
965 #undef IS_RFC822_CHAR
967 static gchar *make_email_string(const gchar *bp, const gchar *ep)
969 /* returns a mailto: URI; mailto: is also used to detect the
970 * uri type later on in the button_pressed signal handler */
974 tmp = g_strndup(bp, ep - bp);
975 result = g_strconcat("mailto:", tmp, NULL);
981 static gchar *make_http_string(const gchar *bp, const gchar *ep)
983 /* returns an http: URI; */
987 tmp = g_strndup(bp, ep - bp);
988 result = g_strconcat("http://", tmp, NULL);
994 #define ADD_TXT_POS(bp_, ep_, pti_) \
995 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
997 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1000 g_warning("alloc error scanning URIs\n"); \
1001 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
1006 /* textview_make_clickable_parts() - colorizes clickable parts */
1007 static void textview_make_clickable_parts(TextView *textview,
1010 GdkColor *uri_color,
1011 const gchar *linebuf)
1013 /* parse table - in order of priority */
1015 const gchar *needle; /* token */
1017 /* token search function */
1018 gchar *(*search) (const gchar *haystack,
1019 const gchar *needle);
1020 /* part parsing function */
1021 gboolean (*parse) (const gchar *start,
1022 const gchar *scanpos,
1025 /* part to URI function */
1026 gchar *(*build_uri) (const gchar *bp,
1030 static struct table parser[] = {
1031 {"http://", strcasestr, get_uri_part, make_uri_string},
1032 {"https://", strcasestr, get_uri_part, make_uri_string},
1033 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1034 {"www.", strcasestr, get_uri_part, make_http_string},
1035 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1036 {"@", strcasestr, get_email_part, make_email_string}
1038 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1041 const gchar *walk, *bp, *ep;
1044 const gchar *bp, *ep; /* text position */
1045 gint pti; /* index in parse table */
1046 struct txtpos *next; /* next */
1047 } head = {NULL, NULL, 0, NULL}, *last = &head;
1049 GtkSText *text = GTK_STEXT(textview->text);
1051 /* parse for clickable parts, and build a list of begin and end positions */
1052 for (walk = linebuf, n = 0;;) {
1053 gint last_index = PARSE_ELEMS;
1054 gchar *scanpos = NULL;
1056 /* FIXME: this looks phony. scanning for anything in the parse table */
1057 for (n = 0; n < PARSE_ELEMS; n++) {
1060 tmp = parser[n].search(walk, parser[n].needle);
1062 if (scanpos == NULL || tmp < scanpos) {
1070 /* check if URI can be parsed */
1071 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1072 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1073 ADD_TXT_POS(bp, ep, last_index);
1077 strlen(parser[last_index].needle);
1082 /* colorize this line */
1084 const gchar *normal_text = linebuf;
1087 for (last = head.next; last != NULL;
1088 normal_text = last->ep, last = last->next) {
1091 uri = g_new(RemoteURI, 1);
1092 if (last->bp - normal_text > 0)
1093 gtk_stext_insert(text, font,
1096 last->bp - normal_text);
1097 uri->uri = parser[last->pti].build_uri(last->bp,
1099 uri->start = gtk_stext_get_point(text);
1100 gtk_stext_insert(text, font, uri_color,
1101 NULL, last->bp, last->ep - last->bp);
1102 uri->end = gtk_stext_get_point(text);
1103 textview->uri_list =
1104 g_slist_append(textview->uri_list, uri);
1108 gtk_stext_insert(text, font, fg_color,
1109 NULL, normal_text, -1);
1111 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1116 static void textview_write_line(TextView *textview, const gchar *str,
1117 CodeConverter *conv)
1119 GtkSText *text = GTK_STEXT(textview->text);
1120 gchar buf[BUFFSIZE];
1122 gint quotelevel = -1;
1125 if (textview->text_is_mb)
1126 conv_localetodisp(buf, sizeof(buf), str);
1128 strncpy2(buf, str, sizeof(buf));
1129 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1130 conv_localetodisp(buf, sizeof(buf), str);
1131 else if (textview->text_is_mb)
1132 conv_unreadable_locale(buf);
1135 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1138 /* change color of quotation
1139 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1140 Up to 3 levels of quotations are detected, and each
1141 level is colored using a different color. */
1142 if (prefs_common.enable_color
1143 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1144 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1146 /* set up the correct foreground color */
1147 if (quotelevel > 2) {
1148 /* recycle colors */
1149 if (prefs_common.recycle_quote_colors)
1156 if (quotelevel == -1)
1159 fg_color = "e_colors[quotelevel];
1161 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1162 fg_color = &signature_color;
1163 textview->is_in_signature = TRUE;
1166 if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1167 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1169 if (prefs_common.enable_color)
1170 textview_make_clickable_parts(textview, textview->msgfont,
1171 fg_color, &uri_color, buf);
1173 textview_make_clickable_parts(textview, textview->msgfont,
1174 fg_color, NULL, buf);
1177 void textview_write_link(TextView *textview, const gchar *str,
1178 const gchar *uri, CodeConverter *conv)
1180 GdkColor *link_color = NULL;
1181 GtkSText *text = GTK_STEXT(textview->text);
1182 gchar buf[BUFFSIZE];
1190 if (textview->text_is_mb)
1191 conv_localetodisp(buf, sizeof(buf), str);
1193 strncpy2(buf, str, sizeof(buf));
1194 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1195 conv_localetodisp(buf, sizeof(buf), str);
1196 else if (textview->text_is_mb)
1197 conv_unreadable_locale(buf);
1201 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1202 gtk_stext_insert(text, textview->msgfont, NULL, NULL, bufp, 1);
1204 if (prefs_common.enable_color) {
1205 link_color = &uri_color;
1207 r_uri = g_new(RemoteURI, 1);
1208 r_uri->uri = g_strdup(uri);
1209 r_uri->start = gtk_stext_get_point(text);
1210 gtk_stext_insert(text, textview->msgfont, link_color, NULL, bufp, -1);
1211 r_uri->end = gtk_stext_get_point(text);
1212 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1215 void textview_clear(TextView *textview)
1217 GtkSText *text = GTK_STEXT(textview->text);
1219 gtk_stext_freeze(text);
1220 gtk_stext_set_point(text, 0);
1221 gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1222 gtk_stext_thaw(text);
1224 TEXTVIEW_STATUSBAR_POP(textview);
1225 textview_uri_list_remove_all(textview->uri_list);
1226 textview->uri_list = NULL;
1228 textview->body_pos = 0;
1229 textview->cur_pos = 0;
1232 void textview_destroy(TextView *textview)
1234 textview_uri_list_remove_all(textview->uri_list);
1235 textview->uri_list = NULL;
1237 if (!textview->scrolledwin_sb->parent)
1238 gtk_widget_destroy(textview->scrolledwin_sb);
1239 if (!textview->scrolledwin_mb->parent)
1240 gtk_widget_destroy(textview->scrolledwin_mb);
1242 if (textview->msgfont)
1243 gdk_font_unref(textview->msgfont);
1244 if (textview->boldfont)
1245 gdk_font_unref(textview->boldfont);
1250 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1252 textview->show_all_headers = all_headers;
1255 void textview_set_font(TextView *textview, const gchar *codeset)
1257 gboolean use_fontset = TRUE;
1259 /* In multi-byte mode, GtkSText can't display 8bit characters
1260 correctly, so it must be single-byte mode. */
1261 if (MB_CUR_MAX > 1) {
1262 if (codeset && conv_get_current_charset() != C_UTF_8) {
1263 if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1264 !g_strcasecmp(codeset, "BALTIC"))
1265 use_fontset = FALSE;
1266 else if (conv_get_current_charset() != C_EUC_JP &&
1267 (!g_strncasecmp(codeset, "KOI8-", 5) ||
1268 !g_strncasecmp(codeset, "CP", 2) ||
1269 !g_strncasecmp(codeset, "WINDOWS-", 8)))
1270 use_fontset = FALSE;
1273 use_fontset = FALSE;
1275 if (textview->text_is_mb && !use_fontset) {
1278 parent = textview->scrolledwin_mb->parent;
1279 gtkut_container_remove(GTK_CONTAINER(parent),
1280 textview->scrolledwin_mb);
1281 gtk_container_add(GTK_CONTAINER(parent),
1282 textview->scrolledwin_sb);
1284 textview->text = textview->text_sb;
1285 textview->text_is_mb = FALSE;
1286 } else if (!textview->text_is_mb && use_fontset) {
1289 parent = textview->scrolledwin_sb->parent;
1290 gtkut_container_remove(GTK_CONTAINER(parent),
1291 textview->scrolledwin_sb);
1292 gtk_container_add(GTK_CONTAINER(parent),
1293 textview->scrolledwin_mb);
1295 textview->text = textview->text_mb;
1296 textview->text_is_mb = TRUE;
1299 if (prefs_common.textfont) {
1304 text_mb_font->ascent = text_mb_font_orig_ascent;
1305 text_mb_font->descent = text_mb_font_orig_descent;
1307 font = gdk_fontset_load(prefs_common.textfont);
1308 if (font && text_mb_font != font) {
1310 gdk_font_unref(text_mb_font);
1311 text_mb_font = font;
1312 text_mb_font_orig_ascent = font->ascent;
1313 text_mb_font_orig_descent = font->descent;
1317 text_sb_font->ascent = text_sb_font_orig_ascent;
1318 text_sb_font->descent = text_sb_font_orig_descent;
1321 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1323 font = gtkut_font_load_from_fontset
1324 (prefs_common.textfont);
1325 if (font && text_sb_font != font) {
1327 gdk_font_unref(text_sb_font);
1328 text_sb_font = font;
1329 text_sb_font_orig_ascent = font->ascent;
1330 text_sb_font_orig_descent = font->descent;
1335 gint ascent, descent;
1337 descent = prefs_common.line_space / 2;
1338 ascent = prefs_common.line_space - descent;
1339 font->ascent += ascent;
1340 font->descent += descent;
1342 if (textview->msgfont)
1343 gdk_font_unref(textview->msgfont);
1344 textview->msgfont = font;
1349 if (!textview->boldfont && prefs_common.boldfont)
1350 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1352 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1355 void textview_set_text(TextView *textview, const gchar *text)
1359 g_return_if_fail(textview != NULL);
1360 g_return_if_fail(text != NULL);
1362 textview_clear(textview);
1364 stext = GTK_STEXT(textview->text);
1365 gtk_stext_freeze(stext);
1366 gtk_stext_insert(stext, textview->msgfont, NULL, NULL, text, strlen(text));
1367 gtk_stext_thaw(stext);
1383 H_ORGANIZATION = 11,
1386 void textview_set_position(TextView *textview, gint pos)
1390 gtk_stext_get_length(GTK_STEXT(textview->text));
1392 textview->cur_pos = pos;
1396 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1398 gchar buf[BUFFSIZE];
1399 GPtrArray *headers, *sorted_headers;
1400 GSList *disphdr_list;
1404 g_return_val_if_fail(fp != NULL, NULL);
1406 if (textview->show_all_headers)
1407 return procheader_get_header_array_asis(fp);
1409 if (!prefs_common.display_header) {
1410 while (fgets(buf, sizeof(buf), fp) != NULL)
1411 if (buf[0] == '\r' || buf[0] == '\n') break;
1415 headers = procheader_get_header_array_asis(fp);
1417 sorted_headers = g_ptr_array_new();
1419 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1420 disphdr_list = disphdr_list->next) {
1421 DisplayHeaderProp *dp =
1422 (DisplayHeaderProp *)disphdr_list->data;
1424 for (i = 0; i < headers->len; i++) {
1425 header = g_ptr_array_index(headers, i);
1427 if (procheader_headername_equal(header->name,
1430 procheader_header_free(header);
1432 g_ptr_array_add(sorted_headers, header);
1434 g_ptr_array_remove_index(headers, i);
1440 if (prefs_common.show_other_header) {
1441 for (i = 0; i < headers->len; i++) {
1442 header = g_ptr_array_index(headers, i);
1443 g_ptr_array_add(sorted_headers, header);
1445 g_ptr_array_free(headers, TRUE);
1447 procheader_header_array_destroy(headers);
1450 return sorted_headers;
1453 static void textview_show_header(TextView *textview, GPtrArray *headers)
1455 GtkSText *text = GTK_STEXT(textview->text);
1459 g_return_if_fail(headers != NULL);
1461 gtk_stext_freeze(text);
1463 for (i = 0; i < headers->len; i++) {
1464 header = g_ptr_array_index(headers, i);
1465 g_return_if_fail(header->name != NULL);
1467 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1469 if (header->name[strlen(header->name) - 1] != ' ')
1470 gtk_stext_insert(text, textview->boldfont,
1471 NULL, NULL, " ", 1);
1473 if (procheader_headername_equal(header->name, "Subject") ||
1474 procheader_headername_equal(header->name, "From") ||
1475 procheader_headername_equal(header->name, "To") ||
1476 procheader_headername_equal(header->name, "Cc"))
1477 unfold_line(header->body);
1479 if (textview->text_is_mb == TRUE)
1480 conv_unreadable_locale(header->body);
1482 if (prefs_common.enable_color &&
1483 (procheader_headername_equal(header->name, "X-Mailer") ||
1484 procheader_headername_equal(header->name,
1486 strstr(header->body, "Sylpheed") != NULL)
1487 gtk_stext_insert(text, textview->msgfont, &emphasis_color, NULL,
1489 else if (prefs_common.enable_color) {
1490 textview_make_clickable_parts(textview,
1491 textview->msgfont, NULL, &uri_color,
1494 textview_make_clickable_parts(textview,
1495 textview->msgfont, NULL, NULL,
1498 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1501 gtk_stext_thaw(text);
1504 gboolean textview_search_string(TextView *textview, const gchar *str,
1507 GtkSText *text = GTK_STEXT(textview->text);
1511 g_return_val_if_fail(str != NULL, FALSE);
1513 len = get_mbs_len(str);
1514 g_return_val_if_fail(len >= 0, FALSE);
1516 pos = textview->cur_pos;
1517 if (pos < textview->body_pos)
1518 pos = textview->body_pos;
1520 if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1521 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1522 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1523 textview_set_position(textview, pos + len);
1530 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1533 GtkSText *text = GTK_STEXT(textview->text);
1538 gboolean found = FALSE;
1540 g_return_val_if_fail(str != NULL, FALSE);
1542 wcs = strdup_mbstowcs(str);
1543 g_return_val_if_fail(wcs != NULL, FALSE);
1545 pos = textview->cur_pos;
1546 text_len = gtk_stext_get_length(text);
1547 if (text_len - textview->body_pos < len) {
1551 if (pos <= textview->body_pos || text_len - pos < len)
1552 pos = text_len - len;
1554 for (; pos >= textview->body_pos; pos--) {
1555 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1557 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1558 gtk_editable_select_region(GTK_EDITABLE(text),
1560 textview_set_position(textview, pos - 1);
1564 if (pos == textview->body_pos) break;
1571 void textview_scroll_one_line(TextView *textview, gboolean up)
1573 GtkSText *text = GTK_STEXT(textview->text);
1576 if (prefs_common.enable_smooth_scroll) {
1577 textview_smooth_scroll_one_line(textview, up);
1582 upper = text->vadj->upper - text->vadj->page_size;
1583 if (text->vadj->value < upper) {
1584 text->vadj->value +=
1585 text->vadj->step_increment * 4;
1587 MIN(text->vadj->value, upper);
1588 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1592 if (text->vadj->value > 0.0) {
1593 text->vadj->value -=
1594 text->vadj->step_increment * 4;
1596 MAX(text->vadj->value, 0.0);
1597 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1603 gboolean textview_scroll_page(TextView *textview, gboolean up)
1605 GtkSText *text = GTK_STEXT(textview->text);
1609 if (prefs_common.enable_smooth_scroll)
1610 return textview_smooth_scroll_page(textview, up);
1612 if (prefs_common.scroll_halfpage)
1613 page_incr = text->vadj->page_increment / 2;
1615 page_incr = text->vadj->page_increment;
1618 upper = text->vadj->upper - text->vadj->page_size;
1619 if (text->vadj->value < upper) {
1620 text->vadj->value += page_incr;
1621 text->vadj->value = MIN(text->vadj->value, upper);
1622 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1627 if (text->vadj->value > 0.0) {
1628 text->vadj->value -= page_incr;
1629 text->vadj->value = MAX(text->vadj->value, 0.0);
1630 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1639 static void textview_smooth_scroll_do(TextView *textview,
1640 gfloat old_value, gfloat last_value,
1643 GtkSText *text = GTK_STEXT(textview->text);
1648 if (old_value < last_value) {
1649 change_value = last_value - old_value;
1652 change_value = old_value - last_value;
1656 gdk_key_repeat_disable();
1658 for (i = step; i <= change_value; i += step) {
1659 text->vadj->value = old_value + (up ? -i : i);
1660 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1664 text->vadj->value = last_value;
1665 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1667 gdk_key_repeat_restore();
1670 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1672 GtkSText *text = GTK_STEXT(textview->text);
1678 upper = text->vadj->upper - text->vadj->page_size;
1679 if (text->vadj->value < upper) {
1680 old_value = text->vadj->value;
1681 last_value = text->vadj->value +
1682 text->vadj->step_increment * 4;
1683 last_value = MIN(last_value, upper);
1685 textview_smooth_scroll_do(textview, old_value,
1687 prefs_common.scroll_step);
1690 if (text->vadj->value > 0.0) {
1691 old_value = text->vadj->value;
1692 last_value = text->vadj->value -
1693 text->vadj->step_increment * 4;
1694 last_value = MAX(last_value, 0.0);
1696 textview_smooth_scroll_do(textview, old_value,
1698 prefs_common.scroll_step);
1703 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1705 GtkSText *text = GTK_STEXT(textview->text);
1711 if (prefs_common.scroll_halfpage)
1712 page_incr = text->vadj->page_increment / 2;
1714 page_incr = text->vadj->page_increment;
1717 upper = text->vadj->upper - text->vadj->page_size;
1718 if (text->vadj->value < upper) {
1719 old_value = text->vadj->value;
1720 last_value = text->vadj->value + page_incr;
1721 last_value = MIN(last_value, upper);
1723 textview_smooth_scroll_do(textview, old_value,
1725 prefs_common.scroll_step);
1729 if (text->vadj->value > 0.0) {
1730 old_value = text->vadj->value;
1731 last_value = text->vadj->value - page_incr;
1732 last_value = MAX(last_value, 0.0);
1734 textview_smooth_scroll_do(textview, old_value,
1736 prefs_common.scroll_step);
1744 #define KEY_PRESS_EVENT_STOP() \
1745 if (gtk_signal_n_emissions_by_name \
1746 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1747 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1748 "key_press_event"); \
1751 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1754 SummaryView *summaryview = NULL;
1755 MessageView *messageview = textview->messageview;
1757 if (!event) return FALSE;
1758 if (messageview->mainwin)
1759 summaryview = messageview->mainwin->summaryview;
1761 switch (event->keyval) {
1776 summary_pass_key_press_event(summaryview, event);
1778 textview_scroll_page(textview, FALSE);
1781 textview_scroll_page(textview, TRUE);
1784 textview_scroll_one_line(textview,
1785 (event->state & GDK_MOD1_MASK) != 0);
1789 summary_pass_key_press_event(summaryview, event);
1794 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1795 KEY_PRESS_EVENT_STOP();
1796 mimeview_pass_key_press_event(messageview->mimeview,
1800 /* possible fall through */
1803 event->window != messageview->mainwin->window->window) {
1804 GdkEventKey tmpev = *event;
1806 tmpev.window = messageview->mainwin->window->window;
1807 KEY_PRESS_EVENT_STOP();
1808 gtk_widget_event(messageview->mainwin->window,
1809 (GdkEvent *)&tmpev);
1817 static gint show_url_timeout_cb(gpointer data)
1819 TextView *textview = (TextView *)data;
1821 TEXTVIEW_STATUSBAR_POP(textview);
1822 textview->show_url_timeout_tag = 0;
1826 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1830 textview->last_buttonpress = event->type;
1834 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1838 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1841 ((event->button == 1)
1842 || event->button == 2 || event->button == 3)) {
1845 /* double click seems to set the cursor after the current
1846 * word. The cursor position needs fixing, otherwise the
1847 * last word of a clickable zone will not work */
1848 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1849 textview->cur_pos--;
1852 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1853 RemoteURI *uri = (RemoteURI *)cur->data;
1855 if (textview->cur_pos >= uri->start &&
1856 textview->cur_pos <= uri->end) {
1859 trimmed_uri = trim_string(uri->uri, 60);
1860 /* single click: display url in statusbar */
1861 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1862 if (textview->messageview->mainwin) {
1863 if (textview->show_url_timeout_tag != 0) {
1864 gtk_timeout_remove(textview->show_url_timeout_tag);
1865 TEXTVIEW_STATUSBAR_POP(textview);
1867 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1868 textview->show_url_timeout_tag = gtk_timeout_add
1869 (4000, show_url_timeout_cb, textview);
1871 } else if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1872 if (event->button == 3) {
1873 gchar *fromname, *fromaddress;
1876 fromaddress = g_strdup(uri->uri + 7);
1877 /* Hiroyuki: please put this function in utils.c! */
1878 fromname = procheader_get_fromname(fromaddress);
1879 extract_address(fromaddress);
1880 g_message("adding from textview %s <%s>", fromname, fromaddress);
1881 /* Add to address book - Match */
1882 addressbook_add_contact( fromname, fromaddress, NULL );
1884 g_free(fromaddress);
1887 PrefsAccount *account = NULL;
1889 if (textview->messageview && textview->messageview->msginfo &&
1890 textview->messageview->msginfo->folder) {
1891 FolderItem *folder_item;
1893 folder_item = textview->messageview->msginfo->folder;
1894 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1895 account = account_find_from_id(folder_item->prefs->default_account);
1897 compose_new(account, uri->uri + 7, NULL);
1900 if (textview_uri_security_check(textview, uri) == TRUE)
1902 prefs_common.uri_cmd);
1904 g_free(trimmed_uri);
1909 textview->last_buttonpress = event->type;
1914 *\brief Check to see if a web URL has been disguised as a different
1915 * URL (possible with HTML email).
1917 *\param uri The uri to check
1919 *\param textview The TextView the URL is contained in
1921 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1922 * it anyway, otherwise FALSE
1924 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
1927 gboolean retval = TRUE;
1929 if (is_uri_string(uri->uri) == FALSE)
1932 visible_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1933 uri->start, uri->end);
1934 if (visible_str == NULL)
1937 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
1939 gchar *visible_uri_path;
1941 uri_path = get_uri_path(uri->uri);
1942 visible_uri_path = get_uri_path(visible_str);
1943 if (strcmp(uri_path, visible_uri_path) != 0)
1947 if (retval == FALSE) {
1951 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1952 "the apparent URL (%s).\n"
1954 uri->uri, visible_str);
1955 aval = alertpanel(_("Warning"), msg, _("Yes"), _("No"), NULL);
1957 if (aval == G_ALERTDEFAULT)
1961 g_free(visible_str);
1966 static void textview_uri_list_remove_all(GSList *uri_list)
1970 for (cur = uri_list; cur != NULL; cur = cur->next) {
1972 g_free(((RemoteURI *)cur->data)->uri);
1977 g_slist_free(uri_list);