2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2003 Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 #include <gdk/gdkkeysyms.h>
29 #include <gtk/gtkvbox.h>
30 #include <gtk/gtkscrolledwindow.h>
31 #include <gtk/gtksignal.h>
39 #include "summaryview.h"
40 #include "procheader.h"
41 #include "prefs_common.h"
50 #include "addressbook.h"
51 #include "displayheader.h"
55 typedef struct _RemoteURI RemoteURI;
65 static GdkColor quote_colors[3] = {
66 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
67 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
68 {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
71 static GdkColor signature_color = {
78 static GdkColor uri_color = {
85 static GdkColor emphasis_color = {
93 static GdkColor error_color = {
101 static GdkColor good_sig_color = {
108 static GdkColor nocheck_sig_color = {
115 static GdkColor bad_sig_color = {
122 static GdkFont *text_sb_font;
123 static GdkFont *text_mb_font;
124 static gint text_sb_font_orig_ascent;
125 static gint text_sb_font_orig_descent;
126 static gint text_mb_font_orig_ascent;
127 static gint text_mb_font_orig_descent;
128 static GdkFont *spacingfont;
130 static void textview_show_ertf (TextView *textview,
132 CodeConverter *conv);
133 static void textview_add_part (TextView *textview,
135 static void textview_add_parts (TextView *textview,
137 static void textview_write_body (TextView *textview,
139 const gchar *charset);
140 static void textview_show_html (TextView *textview,
142 CodeConverter *conv);
143 static void textview_write_line (TextView *textview,
145 CodeConverter *conv);
146 static void textview_write_link (TextView *textview,
149 CodeConverter *conv);
150 static GPtrArray *textview_scan_header (TextView *textview,
152 static void textview_show_header (TextView *textview,
155 static gint textview_key_pressed (GtkWidget *widget,
158 static gint textview_button_pressed (GtkWidget *widget,
159 GdkEventButton *event,
161 static gint textview_button_released (GtkWidget *widget,
162 GdkEventButton *event,
165 static void textview_uri_list_remove_all(GSList *uri_list);
167 static void textview_smooth_scroll_do (TextView *textview,
171 static void textview_smooth_scroll_one_line (TextView *textview,
173 static gboolean textview_smooth_scroll_page (TextView *textview,
177 TextView *textview_create(void)
181 GtkWidget *scrolledwin_sb;
182 GtkWidget *scrolledwin_mb;
186 debug_print("Creating text view...\n");
187 textview = g_new0(TextView, 1);
189 scrolledwin_sb = gtk_scrolled_window_new(NULL, NULL);
190 scrolledwin_mb = gtk_scrolled_window_new(NULL, NULL);
191 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_sb),
192 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
193 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_mb),
194 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
195 gtk_widget_set_usize(scrolledwin_sb, prefs_common.mainview_width, -1);
196 gtk_widget_set_usize(scrolledwin_mb, prefs_common.mainview_width, -1);
198 /* create GtkSText widgets for single-byte and multi-byte character */
199 text_sb = gtk_stext_new(NULL, NULL);
200 text_mb = gtk_stext_new(NULL, NULL);
201 GTK_STEXT(text_sb)->default_tab_width = 8;
202 GTK_STEXT(text_mb)->default_tab_width = 8;
203 gtk_widget_show(text_sb);
204 gtk_widget_show(text_mb);
205 gtk_stext_set_word_wrap(GTK_STEXT(text_sb), TRUE);
206 gtk_stext_set_word_wrap(GTK_STEXT(text_mb), TRUE);
207 gtk_widget_ensure_style(text_sb);
208 gtk_widget_ensure_style(text_mb);
209 if (text_sb->style && text_sb->style->font->type == GDK_FONT_FONTSET) {
213 font = gtkut_font_load_from_fontset(prefs_common.normalfont);
215 style = gtk_style_copy(text_sb->style);
216 gdk_font_unref(style->font);
218 gtk_widget_set_style(text_sb, style);
221 if (text_mb->style && text_mb->style->font->type == GDK_FONT_FONT) {
225 font = gdk_fontset_load(prefs_common.normalfont);
227 style = gtk_style_copy(text_mb->style);
228 gdk_font_unref(style->font);
230 gtk_widget_set_style(text_mb, style);
233 gtk_widget_ref(scrolledwin_sb);
234 gtk_widget_ref(scrolledwin_mb);
236 gtk_container_add(GTK_CONTAINER(scrolledwin_sb), text_sb);
237 gtk_container_add(GTK_CONTAINER(scrolledwin_mb), text_mb);
238 gtk_signal_connect(GTK_OBJECT(text_sb), "key_press_event",
239 GTK_SIGNAL_FUNC(textview_key_pressed),
241 gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_press_event",
242 GTK_SIGNAL_FUNC(textview_button_pressed),
244 gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_release_event",
245 GTK_SIGNAL_FUNC(textview_button_released),
247 gtk_signal_connect(GTK_OBJECT(text_mb), "key_press_event",
248 GTK_SIGNAL_FUNC(textview_key_pressed),
250 gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_press_event",
251 GTK_SIGNAL_FUNC(textview_button_pressed),
253 gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_release_event",
254 GTK_SIGNAL_FUNC(textview_button_released),
257 gtk_widget_show(scrolledwin_sb);
258 gtk_widget_show(scrolledwin_mb);
260 vbox = gtk_vbox_new(FALSE, 0);
261 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin_sb, TRUE, TRUE, 0);
263 gtk_widget_show(vbox);
265 textview->vbox = vbox;
266 textview->scrolledwin = scrolledwin_sb;
267 textview->scrolledwin_sb = scrolledwin_sb;
268 textview->scrolledwin_mb = scrolledwin_mb;
269 textview->text = text_sb;
270 textview->text_sb = text_sb;
271 textview->text_mb = text_mb;
272 textview->text_is_mb = FALSE;
273 textview->uri_list = NULL;
274 textview->body_pos = 0;
275 textview->cur_pos = 0;
276 textview->show_all_headers = FALSE;
277 textview->last_buttonpress = GDK_NOTHING;
278 textview->show_url_msgid = 0;
283 void textview_init(TextView *textview)
285 gtkut_widget_disable_theme_engine(textview->text_sb);
286 gtkut_widget_disable_theme_engine(textview->text_mb);
287 textview_update_message_colors();
288 textview_set_all_headers(textview, FALSE);
289 textview_set_font(textview, NULL);
292 void textview_update_message_colors(void)
294 GdkColor black = {0, 0, 0, 0};
296 if (prefs_common.enable_color) {
297 /* grab the quote colors, converting from an int to a GdkColor */
298 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
300 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
302 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
304 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
306 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
309 quote_colors[0] = quote_colors[1] = quote_colors[2] =
310 uri_color = emphasis_color = signature_color = black;
314 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
319 const gchar *charset = NULL;
321 if ((fp = fopen(file, "rb")) == NULL) {
322 FILE_OP_ERROR(file, "fopen");
326 if (textview->messageview->forced_charset)
327 charset = textview->messageview->forced_charset;
328 else if (prefs_common.force_charset)
329 charset = prefs_common.force_charset;
331 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
333 textview_set_font(textview, charset);
334 textview_clear(textview);
336 text = GTK_STEXT(textview->text);
338 gtk_stext_freeze(text);
341 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) perror("fseek");
342 headers = textview_scan_header(textview, fp);
344 textview_show_header(textview, headers);
345 procheader_header_array_destroy(headers);
346 textview->body_pos = gtk_stext_get_length(text);
349 textview_add_parts(textview, mimeinfo);
351 gtk_stext_thaw(text);
356 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
359 const gchar *charset = NULL;
361 g_return_if_fail(mimeinfo != NULL);
362 g_return_if_fail(fp != NULL);
364 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
365 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822"))) {
366 textview_clear(textview);
367 textview_add_parts(textview, mimeinfo);
371 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
374 headers = textview_scan_header(textview, fp);
376 if (textview->messageview->forced_charset)
377 charset = textview->messageview->forced_charset;
378 else if (prefs_common.force_charset)
379 charset = prefs_common.force_charset;
381 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
383 textview_set_font(textview, charset);
385 text = GTK_STEXT(textview->text);
387 gtk_stext_freeze(text);
388 textview_clear(textview);
392 textview_show_header(textview, headers);
393 procheader_header_array_destroy(headers);
394 textview->body_pos = gtk_stext_get_length(text);
396 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
399 if (mimeinfo->type == MIMETYPE_MULTIPART)
400 textview_add_parts(textview, mimeinfo);
402 textview_write_body(textview, mimeinfo, charset);
404 gtk_stext_thaw(text);
407 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
409 GtkSText *text = GTK_STEXT(textview->text);
411 const gchar *charset = NULL;
412 GPtrArray *headers = NULL;
414 g_return_if_fail(mimeinfo != NULL);
416 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
418 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822")) {
421 fp = fopen(mimeinfo->filename, "rb");
422 fseek(fp, mimeinfo->offset, SEEK_SET);
423 headers = textview_scan_header(textview, fp);
425 gtk_stext_freeze(text);
426 if (gtk_stext_get_length(text) > 0)
427 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
428 textview_show_header(textview, headers);
429 procheader_header_array_destroy(headers);
430 gtk_stext_thaw(text);
436 gtk_stext_freeze(text);
438 if (g_hash_table_lookup(mimeinfo->parameters, "name") != NULL)
439 g_snprintf(buf, sizeof(buf), "\n[%s %s/%s (%d bytes)]\n",
440 (gchar *) g_hash_table_lookup(mimeinfo->parameters, "name"),
441 procmime_get_type_str(mimeinfo->type),
442 mimeinfo->subtype, mimeinfo->length);
444 g_snprintf(buf, sizeof(buf), "\n[%s/%s (%d bytes)]\n",
445 procmime_get_type_str(mimeinfo->type),
446 mimeinfo->subtype, mimeinfo->length);
448 if (mimeinfo->type != MIMETYPE_TEXT) {
449 gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
451 if (prefs_common.display_header && (gtk_stext_get_length(text) > 0))
452 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
453 if (textview->messageview->forced_charset)
454 charset = textview->messageview->forced_charset;
455 else if (prefs_common.force_charset)
456 charset = prefs_common.force_charset;
458 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
460 textview_write_body(textview, mimeinfo, charset);
463 gtk_stext_thaw(text);
467 static gboolean add_parts_func(GNode *node, gpointer data)
469 MimeInfo *mimeinfo = (MimeInfo *) node->data;
470 TextView *textview = (TextView *) data;
472 g_return_val_if_fail(mimeinfo != NULL, FALSE);
474 textview_add_part(textview, mimeinfo);
479 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
481 g_return_if_fail(mimeinfo != NULL);
483 g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, add_parts_func, textview);
487 static void recursive_add_parts(TextView *textview, GNode *node)
492 mimeinfo = (MimeInfo *) node->data;
494 textview_add_part(textview, mimeinfo);
496 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
497 (mimeinfo->type != MIMETYPE_MESSAGE))
500 if (strcasecmp(mimeinfo->subtype, "alternative") == 0) {
501 GNode * prefered_body;
509 prefered_body = NULL;
512 for(iter = g_node_first_child(node) ; iter != NULL ;
513 iter = g_node_next_sibling(iter)) {
518 submime = (MimeInfo *) iter->data;
519 if (submime->type == MIMETYPE_TEXT)
522 if (submime->subtype != NULL) {
523 if (strcasecmp(submime->subtype, "plain") == 0)
527 if (score > prefered_score) {
528 prefered_score = score;
529 prefered_body = iter;
533 if (prefered_body != NULL) {
534 recursive_add_parts(textview, prefered_body);
538 for(iter = g_node_first_child(node) ; iter != NULL ;
539 iter = g_node_next_sibling(iter)) {
540 recursive_add_parts(textview, iter);
545 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
547 g_return_if_fail(mimeinfo != NULL);
549 recursive_add_parts(textview, mimeinfo->node);
552 #define TEXT_INSERT(str) \
553 gtk_stext_insert(text, textview->msgfont, NULL, NULL, str, -1)
555 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
559 if (!partinfo) return;
561 textview_set_font(textview, NULL);
562 text = GTK_STEXT(textview->text);
563 textview_clear(textview);
565 gtk_stext_freeze(text);
567 TEXT_INSERT(_("To save this part, pop up the context menu with "));
568 TEXT_INSERT(_("right click and select `Save as...', "));
569 TEXT_INSERT(_("or press `y' key.\n\n"));
571 TEXT_INSERT(_("To display this part as a text message, select "));
572 TEXT_INSERT(_("`Display as text', or press `t' key.\n\n"));
574 TEXT_INSERT(_("To open this part with external program, select "));
575 TEXT_INSERT(_("`Open' or `Open with...', "));
576 TEXT_INSERT(_("or double-click, or click the center button, "));
577 TEXT_INSERT(_("or press `l' key."));
579 gtk_stext_thaw(text);
584 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
585 const gchar *charset)
591 conv = conv_code_converter_new(charset);
593 textview->is_in_signature = FALSE;
595 if(mimeinfo->encoding_type != ENC_BINARY &&
596 mimeinfo->encoding_type != ENC_7BIT &&
597 mimeinfo->encoding_type != ENC_8BIT)
598 procmime_decode_content(mimeinfo);
600 if (!g_strcasecmp(mimeinfo->subtype, "html")) {
603 filename = procmime_get_tmp_file_name(mimeinfo);
604 if (procmime_get_part(filename, mimeinfo) == 0) {
605 tmpfp = fopen(filename, "rb");
606 textview_show_html(textview, tmpfp, conv);
611 } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
614 filename = procmime_get_tmp_file_name(mimeinfo);
615 if (procmime_get_part(filename, mimeinfo) == 0) {
616 tmpfp = fopen(filename, "rb");
617 textview_show_ertf(textview, tmpfp, conv);
623 tmpfp = fopen(mimeinfo->filename, "rb");
624 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
625 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
626 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
627 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
628 textview_write_line(textview, buf, conv);
632 conv_code_converter_destroy(conv);
635 static void textview_show_html(TextView *textview, FILE *fp,
641 parser = html_parser_new(fp, conv);
642 g_return_if_fail(parser != NULL);
644 while ((str = html_parse(parser)) != NULL) {
645 if (parser->state == HTML_HREF) {
646 /* first time : get and copy the URL */
647 if (parser->href == NULL) {
648 /* ALF - the sylpheed html parser returns an empty string,
649 * if still inside an <a>, but already parsed past HREF */
650 str = strtok(str, " ");
652 parser->href = strdup(str);
653 /* the URL may (or not) be followed by the
655 str = strtok(NULL, "");
659 textview_write_link(textview, str, parser->href, NULL);
661 textview_write_line(textview, str, NULL);
663 html_parser_destroy(parser);
666 static void textview_show_ertf(TextView *textview, FILE *fp,
672 parser = ertf_parser_new(fp, conv);
673 g_return_if_fail(parser != NULL);
675 while ((str = ertf_parse(parser)) != NULL) {
676 textview_write_line(textview, str, NULL);
679 ertf_parser_destroy(parser);
682 /* get_uri_part() - retrieves a URI starting from scanpos.
683 Returns TRUE if succesful */
684 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
685 const gchar **bp, const gchar **ep)
689 g_return_val_if_fail(start != NULL, FALSE);
690 g_return_val_if_fail(scanpos != NULL, FALSE);
691 g_return_val_if_fail(bp != NULL, FALSE);
692 g_return_val_if_fail(ep != NULL, FALSE);
696 /* find end point of URI */
697 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
698 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
702 /* no punctuation at end of string */
704 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
705 * should pass some URI type to this function and decide on that whether
706 * to perform punctuation stripping */
708 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
710 for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
720 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
722 return g_strndup(bp, ep - bp);
725 /* valid mail address characters */
726 #define IS_RFC822_CHAR(ch) \
731 !strchr("(),;<>\"", (ch)))
733 /* alphabet and number within 7bit ASCII */
734 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
735 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
737 static GHashTable *create_domain_tab(void)
739 static const gchar *toplvl_domains [] = {
741 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
742 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
743 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
744 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
745 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
746 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
747 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
748 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
749 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
750 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
751 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
752 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
753 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
754 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
755 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
756 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
757 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
758 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
759 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
760 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
761 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
762 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
763 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
764 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
765 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
766 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
769 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
771 g_return_val_if_fail(htab, NULL);
772 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
773 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
777 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
779 const gint MAX_LVL_DOM_NAME_LEN = 6;
780 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
781 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
784 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
787 for (p = buf; p < m && first < last; *p++ = *first++)
791 return g_hash_table_lookup(tab, buf) != NULL;
794 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
795 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
796 const gchar **bp, const gchar **ep)
798 /* more complex than the uri part because we need to scan back and forward starting from
799 * the scan position. */
800 gboolean result = FALSE;
801 const gchar *bp_ = NULL;
802 const gchar *ep_ = NULL;
803 static GHashTable *dom_tab;
804 const gchar *last_dot = NULL;
805 const gchar *prelast_dot = NULL;
806 const gchar *last_tld_char = NULL;
808 /* the informative part of the email address (describing the name
809 * of the email address owner) may contain quoted parts. the
810 * closure stack stores the last encountered quotes. */
811 gchar closure_stack[128];
812 gchar *ptr = closure_stack;
814 g_return_val_if_fail(start != NULL, FALSE);
815 g_return_val_if_fail(scanpos != NULL, FALSE);
816 g_return_val_if_fail(bp != NULL, FALSE);
817 g_return_val_if_fail(ep != NULL, FALSE);
820 dom_tab = create_domain_tab();
821 g_return_val_if_fail(dom_tab, FALSE);
823 /* scan start of address */
824 for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
827 /* TODO: should start with an alnum? */
829 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
832 if (bp_ != scanpos) {
833 /* scan end of address */
834 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
836 prelast_dot = last_dot;
838 if (*(last_dot + 1) == '.') {
839 if (prelast_dot == NULL)
841 last_dot = prelast_dot;
846 /* TODO: really should terminate with an alnum? */
847 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
851 if (last_dot == NULL)
854 last_dot = prelast_dot;
855 if (last_dot == NULL || (scanpos + 1 >= last_dot))
859 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
860 if (*last_tld_char == '?')
863 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
870 if (!result) return FALSE;
872 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
873 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
876 /* see if this is <bracketed>; in this case we also scan for the informative part. */
877 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
880 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
881 #define IN_STACK() (ptr > closure_stack)
882 /* has underrun check */
883 #define POP_STACK() if(IN_STACK()) --ptr
884 /* has overrun check */
885 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
886 /* has underrun check */
887 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
891 /* scan for the informative part. */
892 for (bp_ -= 2; bp_ >= start; bp_--) {
893 /* if closure on the stack keep scanning */
894 if (PEEK_STACK() == *bp_) {
898 if (*bp_ == '\'' || *bp_ == '"') {
903 /* if nothing in the closure stack, do the special conditions
904 * the following if..else expression simply checks whether
905 * a token is acceptable. if not acceptable, the clause
906 * should terminate the loop with a 'break' */
909 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
910 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
911 /* hyphens are allowed, but only in
913 } else if (!ispunct(*bp_)) {
914 /* but anything not being a punctiation
917 break; /* anything else is rejected */
930 /* scan forward (should start with an alnum) */
931 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
941 #undef IS_RFC822_CHAR
943 static gchar *make_email_string(const gchar *bp, const gchar *ep)
945 /* returns a mailto: URI; mailto: is also used to detect the
946 * uri type later on in the button_pressed signal handler */
950 tmp = g_strndup(bp, ep - bp);
951 result = g_strconcat("mailto:", tmp, NULL);
957 #define ADD_TXT_POS(bp_, ep_, pti_) \
958 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
960 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
963 g_warning("alloc error scanning URIs\n"); \
964 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
969 /* textview_make_clickable_parts() - colorizes clickable parts */
970 static void textview_make_clickable_parts(TextView *textview,
974 const gchar *linebuf)
976 /* parse table - in order of priority */
978 const gchar *needle; /* token */
980 /* token search function */
981 gchar *(*search) (const gchar *haystack,
982 const gchar *needle);
983 /* part parsing function */
984 gboolean (*parse) (const gchar *start,
985 const gchar *scanpos,
988 /* part to URI function */
989 gchar *(*build_uri) (const gchar *bp,
993 static struct table parser[] = {
994 {"http://", strcasestr, get_uri_part, make_uri_string},
995 {"https://", strcasestr, get_uri_part, make_uri_string},
996 {"ftp://", strcasestr, get_uri_part, make_uri_string},
997 {"www.", strcasestr, get_uri_part, make_uri_string},
998 {"mailto:", strcasestr, get_uri_part, make_uri_string},
999 {"@", strcasestr, get_email_part, make_email_string}
1001 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1004 const gchar *walk, *bp, *ep;
1007 const gchar *bp, *ep; /* text position */
1008 gint pti; /* index in parse table */
1009 struct txtpos *next; /* next */
1010 } head = {NULL, NULL, 0, NULL}, *last = &head;
1012 GtkSText *text = GTK_STEXT(textview->text);
1014 /* parse for clickable parts, and build a list of begin and end positions */
1015 for (walk = linebuf, n = 0;;) {
1016 gint last_index = PARSE_ELEMS;
1017 gchar *scanpos = NULL;
1019 /* FIXME: this looks phony. scanning for anything in the parse table */
1020 for (n = 0; n < PARSE_ELEMS; n++) {
1023 tmp = parser[n].search(walk, parser[n].needle);
1025 if (scanpos == NULL || tmp < scanpos) {
1033 /* check if URI can be parsed */
1034 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1035 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1036 ADD_TXT_POS(bp, ep, last_index);
1040 strlen(parser[last_index].needle);
1045 /* colorize this line */
1047 const gchar *normal_text = linebuf;
1050 for (last = head.next; last != NULL;
1051 normal_text = last->ep, last = last->next) {
1054 uri = g_new(RemoteURI, 1);
1055 if (last->bp - normal_text > 0)
1056 gtk_stext_insert(text, font,
1059 last->bp - normal_text);
1060 uri->uri = parser[last->pti].build_uri(last->bp,
1062 uri->start = gtk_stext_get_point(text);
1063 gtk_stext_insert(text, font, uri_color,
1064 NULL, last->bp, last->ep - last->bp);
1065 uri->end = gtk_stext_get_point(text);
1066 textview->uri_list =
1067 g_slist_append(textview->uri_list, uri);
1071 gtk_stext_insert(text, font, fg_color,
1072 NULL, normal_text, -1);
1074 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1079 static void textview_write_line(TextView *textview, const gchar *str,
1080 CodeConverter *conv)
1082 GtkSText *text = GTK_STEXT(textview->text);
1083 gchar buf[BUFFSIZE];
1085 gint quotelevel = -1;
1088 if (textview->text_is_mb)
1089 conv_localetodisp(buf, sizeof(buf), str);
1091 strncpy2(buf, str, sizeof(buf));
1092 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1093 conv_localetodisp(buf, sizeof(buf), str);
1094 else if (textview->text_is_mb)
1095 conv_unreadable_locale(buf);
1098 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1101 /* change color of quotation
1102 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1103 Up to 3 levels of quotations are detected, and each
1104 level is colored using a different color. */
1105 if (prefs_common.enable_color
1106 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1107 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1109 /* set up the correct foreground color */
1110 if (quotelevel > 2) {
1111 /* recycle colors */
1112 if (prefs_common.recycle_quote_colors)
1119 if (quotelevel == -1)
1122 fg_color = "e_colors[quotelevel];
1124 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1125 fg_color = &signature_color;
1126 textview->is_in_signature = TRUE;
1129 if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1130 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1132 if (prefs_common.enable_color)
1133 textview_make_clickable_parts(textview, textview->msgfont,
1134 fg_color, &uri_color, buf);
1136 textview_make_clickable_parts(textview, textview->msgfont,
1137 fg_color, NULL, buf);
1140 void textview_write_link(TextView *textview, const gchar *str,
1141 const gchar *uri, CodeConverter *conv)
1143 GdkColor *link_color = NULL;
1144 GtkSText *text = GTK_STEXT(textview->text);
1145 gchar buf[BUFFSIZE];
1153 if (textview->text_is_mb)
1154 conv_localetodisp(buf, sizeof(buf), str);
1156 strncpy2(buf, str, sizeof(buf));
1157 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1158 conv_localetodisp(buf, sizeof(buf), str);
1159 else if (textview->text_is_mb)
1160 conv_unreadable_locale(buf);
1164 for (bufp = buf; isspace(*bufp); bufp++)
1165 gtk_stext_insert(text, textview->msgfont, NULL, NULL, bufp, 1);
1167 if (prefs_common.enable_color) {
1168 link_color = &uri_color;
1170 r_uri = g_new(RemoteURI, 1);
1171 r_uri->uri = g_strdup(uri);
1172 r_uri->start = gtk_stext_get_point(text);
1173 gtk_stext_insert(text, textview->msgfont, link_color, NULL, bufp, -1);
1174 r_uri->end = gtk_stext_get_point(text);
1175 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1178 void textview_clear(TextView *textview)
1180 GtkSText *text = GTK_STEXT(textview->text);
1182 gtk_stext_freeze(text);
1183 gtk_stext_set_point(text, 0);
1184 gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1185 gtk_stext_thaw(text);
1187 textview_uri_list_remove_all(textview->uri_list);
1188 textview->uri_list = NULL;
1190 textview->body_pos = 0;
1191 textview->cur_pos = 0;
1194 void textview_destroy(TextView *textview)
1196 textview_uri_list_remove_all(textview->uri_list);
1197 textview->uri_list = NULL;
1199 if (!textview->scrolledwin_sb->parent)
1200 gtk_widget_destroy(textview->scrolledwin_sb);
1201 if (!textview->scrolledwin_mb->parent)
1202 gtk_widget_destroy(textview->scrolledwin_mb);
1204 if (textview->msgfont)
1205 gdk_font_unref(textview->msgfont);
1206 if (textview->boldfont)
1207 gdk_font_unref(textview->boldfont);
1212 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1214 textview->show_all_headers = all_headers;
1217 void textview_set_font(TextView *textview, const gchar *codeset)
1219 gboolean use_fontset = TRUE;
1221 /* In multi-byte mode, GtkSText can't display 8bit characters
1222 correctly, so it must be single-byte mode. */
1223 if (MB_CUR_MAX > 1) {
1224 if (codeset && conv_get_current_charset() != C_UTF_8) {
1225 if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1226 !g_strcasecmp(codeset, "BALTIC"))
1227 use_fontset = FALSE;
1228 else if (conv_get_current_charset() != C_EUC_JP &&
1229 (!g_strncasecmp(codeset, "KOI8-", 5) ||
1230 !g_strncasecmp(codeset, "CP", 2) ||
1231 !g_strncasecmp(codeset, "WINDOWS-", 8)))
1232 use_fontset = FALSE;
1235 use_fontset = FALSE;
1237 if (textview->text_is_mb && !use_fontset) {
1240 parent = textview->scrolledwin_mb->parent;
1241 gtkut_container_remove(GTK_CONTAINER(parent),
1242 textview->scrolledwin_mb);
1243 gtk_container_add(GTK_CONTAINER(parent),
1244 textview->scrolledwin_sb);
1246 textview->text = textview->text_sb;
1247 textview->text_is_mb = FALSE;
1248 } else if (!textview->text_is_mb && use_fontset) {
1251 parent = textview->scrolledwin_sb->parent;
1252 gtkut_container_remove(GTK_CONTAINER(parent),
1253 textview->scrolledwin_sb);
1254 gtk_container_add(GTK_CONTAINER(parent),
1255 textview->scrolledwin_mb);
1257 textview->text = textview->text_mb;
1258 textview->text_is_mb = TRUE;
1261 if (prefs_common.textfont) {
1266 text_mb_font->ascent = text_mb_font_orig_ascent;
1267 text_mb_font->descent = text_mb_font_orig_descent;
1269 font = gdk_fontset_load(prefs_common.textfont);
1270 if (font && text_mb_font != font) {
1272 gdk_font_unref(text_mb_font);
1273 text_mb_font = font;
1274 text_mb_font_orig_ascent = font->ascent;
1275 text_mb_font_orig_descent = font->descent;
1279 text_sb_font->ascent = text_sb_font_orig_ascent;
1280 text_sb_font->descent = text_sb_font_orig_descent;
1283 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1285 font = gtkut_font_load_from_fontset
1286 (prefs_common.textfont);
1287 if (font && text_sb_font != font) {
1289 gdk_font_unref(text_sb_font);
1290 text_sb_font = font;
1291 text_sb_font_orig_ascent = font->ascent;
1292 text_sb_font_orig_descent = font->descent;
1297 gint ascent, descent;
1299 descent = prefs_common.line_space / 2;
1300 ascent = prefs_common.line_space - descent;
1301 font->ascent += ascent;
1302 font->descent += descent;
1304 if (textview->msgfont)
1305 gdk_font_unref(textview->msgfont);
1306 textview->msgfont = font;
1311 if (!textview->boldfont && prefs_common.boldfont)
1312 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1314 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1330 H_ORGANIZATION = 11,
1333 void textview_set_position(TextView *textview, gint pos)
1337 gtk_stext_get_length(GTK_STEXT(textview->text));
1339 textview->cur_pos = pos;
1343 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1345 gchar buf[BUFFSIZE];
1346 GPtrArray *headers, *sorted_headers;
1347 GSList *disphdr_list;
1351 g_return_val_if_fail(fp != NULL, NULL);
1353 if (textview->show_all_headers)
1354 return procheader_get_header_array_asis(fp);
1356 if (!prefs_common.display_header) {
1357 while (fgets(buf, sizeof(buf), fp) != NULL)
1358 if (buf[0] == '\r' || buf[0] == '\n') break;
1362 headers = procheader_get_header_array_asis(fp);
1364 sorted_headers = g_ptr_array_new();
1366 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1367 disphdr_list = disphdr_list->next) {
1368 DisplayHeaderProp *dp =
1369 (DisplayHeaderProp *)disphdr_list->data;
1371 for (i = 0; i < headers->len; i++) {
1372 header = g_ptr_array_index(headers, i);
1374 if (procheader_headername_equal(header->name,
1377 procheader_header_free(header);
1379 g_ptr_array_add(sorted_headers, header);
1381 g_ptr_array_remove_index(headers, i);
1387 if (prefs_common.show_other_header) {
1388 for (i = 0; i < headers->len; i++) {
1389 header = g_ptr_array_index(headers, i);
1390 g_ptr_array_add(sorted_headers, header);
1392 g_ptr_array_free(headers, TRUE);
1394 procheader_header_array_destroy(headers);
1397 return sorted_headers;
1400 static void textview_show_header(TextView *textview, GPtrArray *headers)
1402 GtkSText *text = GTK_STEXT(textview->text);
1406 g_return_if_fail(headers != NULL);
1408 gtk_stext_freeze(text);
1410 for (i = 0; i < headers->len; i++) {
1411 header = g_ptr_array_index(headers, i);
1412 g_return_if_fail(header->name != NULL);
1414 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1416 if (header->name[strlen(header->name) - 1] != ' ')
1417 gtk_stext_insert(text, textview->boldfont,
1418 NULL, NULL, " ", 1);
1420 if (procheader_headername_equal(header->name, "Subject") ||
1421 procheader_headername_equal(header->name, "From") ||
1422 procheader_headername_equal(header->name, "To") ||
1423 procheader_headername_equal(header->name, "Cc"))
1424 unfold_line(header->body);
1426 if (textview->text_is_mb == TRUE)
1427 conv_unreadable_locale(header->body);
1429 if (prefs_common.enable_color &&
1430 (procheader_headername_equal(header->name, "X-Mailer") ||
1431 procheader_headername_equal(header->name,
1433 strstr(header->body, "Sylpheed") != NULL)
1434 gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1436 else if (prefs_common.enable_color) {
1437 textview_make_clickable_parts(textview,
1438 NULL, NULL, &uri_color,
1441 textview_make_clickable_parts(textview,
1445 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1448 gtk_stext_thaw(text);
1451 gboolean textview_search_string(TextView *textview, const gchar *str,
1454 GtkSText *text = GTK_STEXT(textview->text);
1458 g_return_val_if_fail(str != NULL, FALSE);
1460 len = get_mbs_len(str);
1461 g_return_val_if_fail(len >= 0, FALSE);
1463 pos = textview->cur_pos;
1464 if (pos < textview->body_pos)
1465 pos = textview->body_pos;
1467 if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1468 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1469 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1470 textview_set_position(textview, pos + len);
1477 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1480 GtkSText *text = GTK_STEXT(textview->text);
1485 gboolean found = FALSE;
1487 g_return_val_if_fail(str != NULL, FALSE);
1489 wcs = strdup_mbstowcs(str);
1490 g_return_val_if_fail(wcs != NULL, FALSE);
1492 pos = textview->cur_pos;
1493 text_len = gtk_stext_get_length(text);
1494 if (text_len - textview->body_pos < len) {
1498 if (pos <= textview->body_pos || text_len - pos < len)
1499 pos = text_len - len;
1501 for (; pos >= textview->body_pos; pos--) {
1502 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1504 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1505 gtk_editable_select_region(GTK_EDITABLE(text),
1507 textview_set_position(textview, pos - 1);
1511 if (pos == textview->body_pos) break;
1518 void textview_scroll_one_line(TextView *textview, gboolean up)
1520 GtkSText *text = GTK_STEXT(textview->text);
1523 if (prefs_common.enable_smooth_scroll) {
1524 textview_smooth_scroll_one_line(textview, up);
1529 upper = text->vadj->upper - text->vadj->page_size;
1530 if (text->vadj->value < upper) {
1531 text->vadj->value +=
1532 text->vadj->step_increment * 4;
1534 MIN(text->vadj->value, upper);
1535 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1539 if (text->vadj->value > 0.0) {
1540 text->vadj->value -=
1541 text->vadj->step_increment * 4;
1543 MAX(text->vadj->value, 0.0);
1544 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1550 gboolean textview_scroll_page(TextView *textview, gboolean up)
1552 GtkSText *text = GTK_STEXT(textview->text);
1556 if (prefs_common.enable_smooth_scroll)
1557 return textview_smooth_scroll_page(textview, up);
1559 if (prefs_common.scroll_halfpage)
1560 page_incr = text->vadj->page_increment / 2;
1562 page_incr = text->vadj->page_increment;
1565 upper = text->vadj->upper - text->vadj->page_size;
1566 if (text->vadj->value < upper) {
1567 text->vadj->value += page_incr;
1568 text->vadj->value = MIN(text->vadj->value, upper);
1569 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1574 if (text->vadj->value > 0.0) {
1575 text->vadj->value -= page_incr;
1576 text->vadj->value = MAX(text->vadj->value, 0.0);
1577 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1586 static void textview_smooth_scroll_do(TextView *textview,
1587 gfloat old_value, gfloat last_value,
1590 GtkSText *text = GTK_STEXT(textview->text);
1595 if (old_value < last_value) {
1596 change_value = last_value - old_value;
1599 change_value = old_value - last_value;
1603 gdk_key_repeat_disable();
1605 for (i = step; i <= change_value; i += step) {
1606 text->vadj->value = old_value + (up ? -i : i);
1607 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1611 text->vadj->value = last_value;
1612 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1614 gdk_key_repeat_restore();
1617 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1619 GtkSText *text = GTK_STEXT(textview->text);
1625 upper = text->vadj->upper - text->vadj->page_size;
1626 if (text->vadj->value < upper) {
1627 old_value = text->vadj->value;
1628 last_value = text->vadj->value +
1629 text->vadj->step_increment * 4;
1630 last_value = MIN(last_value, upper);
1632 textview_smooth_scroll_do(textview, old_value,
1634 prefs_common.scroll_step);
1637 if (text->vadj->value > 0.0) {
1638 old_value = text->vadj->value;
1639 last_value = text->vadj->value -
1640 text->vadj->step_increment * 4;
1641 last_value = MAX(last_value, 0.0);
1643 textview_smooth_scroll_do(textview, old_value,
1645 prefs_common.scroll_step);
1650 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1652 GtkSText *text = GTK_STEXT(textview->text);
1658 if (prefs_common.scroll_halfpage)
1659 page_incr = text->vadj->page_increment / 2;
1661 page_incr = text->vadj->page_increment;
1664 upper = text->vadj->upper - text->vadj->page_size;
1665 if (text->vadj->value < upper) {
1666 old_value = text->vadj->value;
1667 last_value = text->vadj->value + page_incr;
1668 last_value = MIN(last_value, upper);
1670 textview_smooth_scroll_do(textview, old_value,
1672 prefs_common.scroll_step);
1676 if (text->vadj->value > 0.0) {
1677 old_value = text->vadj->value;
1678 last_value = text->vadj->value - page_incr;
1679 last_value = MAX(last_value, 0.0);
1681 textview_smooth_scroll_do(textview, old_value,
1683 prefs_common.scroll_step);
1691 #define KEY_PRESS_EVENT_STOP() \
1692 if (gtk_signal_n_emissions_by_name \
1693 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1694 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1695 "key_press_event"); \
1698 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1701 SummaryView *summaryview = NULL;
1702 MessageView *messageview = textview->messageview;
1704 if (!event) return FALSE;
1705 if (messageview->mainwin)
1706 summaryview = messageview->mainwin->summaryview;
1708 switch (event->keyval) {
1723 summary_pass_key_press_event(summaryview, event);
1725 textview_scroll_page(textview, FALSE);
1728 textview_scroll_page(textview, TRUE);
1731 textview_scroll_one_line(textview,
1732 (event->state & GDK_MOD1_MASK) != 0);
1736 summary_pass_key_press_event(summaryview, event);
1741 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1742 KEY_PRESS_EVENT_STOP();
1743 mimeview_pass_key_press_event(messageview->mimeview,
1747 /* possible fall through */
1750 event->window != messageview->mainwin->window->window) {
1751 GdkEventKey tmpev = *event;
1753 tmpev.window = messageview->mainwin->window->window;
1754 KEY_PRESS_EVENT_STOP();
1755 gtk_widget_event(messageview->mainwin->window,
1756 (GdkEvent *)&tmpev);
1764 static gint show_url_timeout_cb(gpointer data)
1766 TextView *textview = (TextView *)data;
1768 if (textview->messageview->mainwin)
1769 if (textview->show_url_msgid)
1770 gtk_statusbar_remove(GTK_STATUSBAR(
1771 textview->messageview->mainwin->statusbar),
1772 textview->messageview->mainwin->folderview_cid,
1773 textview->show_url_msgid);
1777 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1781 textview->last_buttonpress = event->type;
1785 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1789 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1792 ((event->button == 1)
1793 || event->button == 2 || event->button == 3)) {
1796 /* double click seems to set the cursor after the current
1797 * word. The cursor position needs fixing, otherwise the
1798 * last word of a clickable zone will not work */
1799 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1800 textview->cur_pos--;
1803 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1804 RemoteURI *uri = (RemoteURI *)cur->data;
1806 if (textview->cur_pos >= uri->start &&
1807 textview->cur_pos <= uri->end) {
1810 trimmed_uri = trim_string(uri->uri, 60);
1811 /* single click: display url in statusbar */
1812 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1813 if (textview->messageview->mainwin) {
1814 if (textview->show_url_msgid) {
1815 gtk_timeout_remove(textview->show_url_timeout_tag);
1816 gtk_statusbar_remove(GTK_STATUSBAR(
1817 textview->messageview->mainwin->statusbar),
1818 textview->messageview->mainwin->folderview_cid,
1819 textview->show_url_msgid);
1820 textview->show_url_msgid = 0;
1822 textview->show_url_msgid = gtk_statusbar_push(
1823 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1824 textview->messageview->mainwin->folderview_cid,
1826 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1827 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1830 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1831 if (event->button == 3) {
1832 gchar *fromname, *fromaddress;
1835 fromaddress = g_strdup(uri->uri + 7);
1836 /* Hiroyuki: please put this function in utils.c! */
1837 fromname = procheader_get_fromname(fromaddress);
1838 extract_address(fromaddress);
1839 g_message("adding from textview %s <%s>", fromname, fromaddress);
1840 /* Add to address book - Match */
1841 addressbook_add_contact( fromname, fromaddress, NULL );
1843 g_free(fromaddress);
1846 PrefsAccount *account = NULL;
1847 FolderItem *folder_item;
1849 if (textview->messageview && textview->messageview->mainwin
1850 && textview->messageview->mainwin->summaryview
1851 && textview->messageview->mainwin->summaryview->folder_item) {
1852 folder_item = textview->messageview->mainwin->summaryview->folder_item;
1853 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1854 account = account_find_from_id(folder_item->prefs->default_account);
1856 compose_new(account, uri->uri + 7, NULL);
1860 prefs_common.uri_cmd);
1862 g_free(trimmed_uri);
1867 textview->last_buttonpress = event->type;
1871 static void textview_uri_list_remove_all(GSList *uri_list)
1875 for (cur = uri_list; cur != NULL; cur = cur->next) {
1877 g_free(((RemoteURI *)cur->data)->uri);
1882 g_slist_free(uri_list);