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-*");
1317 void textview_set_text(TextView *textview, const gchar *text)
1321 g_return_if_fail(textview != NULL);
1322 g_return_if_fail(text != NULL);
1324 textview_clear(textview);
1326 stext = GTK_STEXT(textview->text);
1327 gtk_stext_freeze(stext);
1328 gtk_stext_insert(stext, textview->msgfont, NULL, NULL, text, strlen(text));
1329 gtk_stext_thaw(stext);
1345 H_ORGANIZATION = 11,
1348 void textview_set_position(TextView *textview, gint pos)
1352 gtk_stext_get_length(GTK_STEXT(textview->text));
1354 textview->cur_pos = pos;
1358 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1360 gchar buf[BUFFSIZE];
1361 GPtrArray *headers, *sorted_headers;
1362 GSList *disphdr_list;
1366 g_return_val_if_fail(fp != NULL, NULL);
1368 if (textview->show_all_headers)
1369 return procheader_get_header_array_asis(fp);
1371 if (!prefs_common.display_header) {
1372 while (fgets(buf, sizeof(buf), fp) != NULL)
1373 if (buf[0] == '\r' || buf[0] == '\n') break;
1377 headers = procheader_get_header_array_asis(fp);
1379 sorted_headers = g_ptr_array_new();
1381 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1382 disphdr_list = disphdr_list->next) {
1383 DisplayHeaderProp *dp =
1384 (DisplayHeaderProp *)disphdr_list->data;
1386 for (i = 0; i < headers->len; i++) {
1387 header = g_ptr_array_index(headers, i);
1389 if (procheader_headername_equal(header->name,
1392 procheader_header_free(header);
1394 g_ptr_array_add(sorted_headers, header);
1396 g_ptr_array_remove_index(headers, i);
1402 if (prefs_common.show_other_header) {
1403 for (i = 0; i < headers->len; i++) {
1404 header = g_ptr_array_index(headers, i);
1405 g_ptr_array_add(sorted_headers, header);
1407 g_ptr_array_free(headers, TRUE);
1409 procheader_header_array_destroy(headers);
1412 return sorted_headers;
1415 static void textview_show_header(TextView *textview, GPtrArray *headers)
1417 GtkSText *text = GTK_STEXT(textview->text);
1421 g_return_if_fail(headers != NULL);
1423 gtk_stext_freeze(text);
1425 for (i = 0; i < headers->len; i++) {
1426 header = g_ptr_array_index(headers, i);
1427 g_return_if_fail(header->name != NULL);
1429 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1431 if (header->name[strlen(header->name) - 1] != ' ')
1432 gtk_stext_insert(text, textview->boldfont,
1433 NULL, NULL, " ", 1);
1435 if (procheader_headername_equal(header->name, "Subject") ||
1436 procheader_headername_equal(header->name, "From") ||
1437 procheader_headername_equal(header->name, "To") ||
1438 procheader_headername_equal(header->name, "Cc"))
1439 unfold_line(header->body);
1441 if (textview->text_is_mb == TRUE)
1442 conv_unreadable_locale(header->body);
1444 if (prefs_common.enable_color &&
1445 (procheader_headername_equal(header->name, "X-Mailer") ||
1446 procheader_headername_equal(header->name,
1448 strstr(header->body, "Sylpheed") != NULL)
1449 gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1451 else if (prefs_common.enable_color) {
1452 textview_make_clickable_parts(textview,
1453 NULL, NULL, &uri_color,
1456 textview_make_clickable_parts(textview,
1460 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1463 gtk_stext_thaw(text);
1466 gboolean textview_search_string(TextView *textview, const gchar *str,
1469 GtkSText *text = GTK_STEXT(textview->text);
1473 g_return_val_if_fail(str != NULL, FALSE);
1475 len = get_mbs_len(str);
1476 g_return_val_if_fail(len >= 0, FALSE);
1478 pos = textview->cur_pos;
1479 if (pos < textview->body_pos)
1480 pos = textview->body_pos;
1482 if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1483 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1484 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1485 textview_set_position(textview, pos + len);
1492 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1495 GtkSText *text = GTK_STEXT(textview->text);
1500 gboolean found = FALSE;
1502 g_return_val_if_fail(str != NULL, FALSE);
1504 wcs = strdup_mbstowcs(str);
1505 g_return_val_if_fail(wcs != NULL, FALSE);
1507 pos = textview->cur_pos;
1508 text_len = gtk_stext_get_length(text);
1509 if (text_len - textview->body_pos < len) {
1513 if (pos <= textview->body_pos || text_len - pos < len)
1514 pos = text_len - len;
1516 for (; pos >= textview->body_pos; pos--) {
1517 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1519 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1520 gtk_editable_select_region(GTK_EDITABLE(text),
1522 textview_set_position(textview, pos - 1);
1526 if (pos == textview->body_pos) break;
1533 void textview_scroll_one_line(TextView *textview, gboolean up)
1535 GtkSText *text = GTK_STEXT(textview->text);
1538 if (prefs_common.enable_smooth_scroll) {
1539 textview_smooth_scroll_one_line(textview, up);
1544 upper = text->vadj->upper - text->vadj->page_size;
1545 if (text->vadj->value < upper) {
1546 text->vadj->value +=
1547 text->vadj->step_increment * 4;
1549 MIN(text->vadj->value, upper);
1550 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1554 if (text->vadj->value > 0.0) {
1555 text->vadj->value -=
1556 text->vadj->step_increment * 4;
1558 MAX(text->vadj->value, 0.0);
1559 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1565 gboolean textview_scroll_page(TextView *textview, gboolean up)
1567 GtkSText *text = GTK_STEXT(textview->text);
1571 if (prefs_common.enable_smooth_scroll)
1572 return textview_smooth_scroll_page(textview, up);
1574 if (prefs_common.scroll_halfpage)
1575 page_incr = text->vadj->page_increment / 2;
1577 page_incr = text->vadj->page_increment;
1580 upper = text->vadj->upper - text->vadj->page_size;
1581 if (text->vadj->value < upper) {
1582 text->vadj->value += page_incr;
1583 text->vadj->value = MIN(text->vadj->value, upper);
1584 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1589 if (text->vadj->value > 0.0) {
1590 text->vadj->value -= page_incr;
1591 text->vadj->value = MAX(text->vadj->value, 0.0);
1592 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1601 static void textview_smooth_scroll_do(TextView *textview,
1602 gfloat old_value, gfloat last_value,
1605 GtkSText *text = GTK_STEXT(textview->text);
1610 if (old_value < last_value) {
1611 change_value = last_value - old_value;
1614 change_value = old_value - last_value;
1618 gdk_key_repeat_disable();
1620 for (i = step; i <= change_value; i += step) {
1621 text->vadj->value = old_value + (up ? -i : i);
1622 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1626 text->vadj->value = last_value;
1627 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1629 gdk_key_repeat_restore();
1632 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1634 GtkSText *text = GTK_STEXT(textview->text);
1640 upper = text->vadj->upper - text->vadj->page_size;
1641 if (text->vadj->value < upper) {
1642 old_value = text->vadj->value;
1643 last_value = text->vadj->value +
1644 text->vadj->step_increment * 4;
1645 last_value = MIN(last_value, upper);
1647 textview_smooth_scroll_do(textview, old_value,
1649 prefs_common.scroll_step);
1652 if (text->vadj->value > 0.0) {
1653 old_value = text->vadj->value;
1654 last_value = text->vadj->value -
1655 text->vadj->step_increment * 4;
1656 last_value = MAX(last_value, 0.0);
1658 textview_smooth_scroll_do(textview, old_value,
1660 prefs_common.scroll_step);
1665 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1667 GtkSText *text = GTK_STEXT(textview->text);
1673 if (prefs_common.scroll_halfpage)
1674 page_incr = text->vadj->page_increment / 2;
1676 page_incr = text->vadj->page_increment;
1679 upper = text->vadj->upper - text->vadj->page_size;
1680 if (text->vadj->value < upper) {
1681 old_value = text->vadj->value;
1682 last_value = text->vadj->value + page_incr;
1683 last_value = MIN(last_value, upper);
1685 textview_smooth_scroll_do(textview, old_value,
1687 prefs_common.scroll_step);
1691 if (text->vadj->value > 0.0) {
1692 old_value = text->vadj->value;
1693 last_value = text->vadj->value - page_incr;
1694 last_value = MAX(last_value, 0.0);
1696 textview_smooth_scroll_do(textview, old_value,
1698 prefs_common.scroll_step);
1706 #define KEY_PRESS_EVENT_STOP() \
1707 if (gtk_signal_n_emissions_by_name \
1708 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1709 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1710 "key_press_event"); \
1713 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1716 SummaryView *summaryview = NULL;
1717 MessageView *messageview = textview->messageview;
1719 if (!event) return FALSE;
1720 if (messageview->mainwin)
1721 summaryview = messageview->mainwin->summaryview;
1723 switch (event->keyval) {
1738 summary_pass_key_press_event(summaryview, event);
1740 textview_scroll_page(textview, FALSE);
1743 textview_scroll_page(textview, TRUE);
1746 textview_scroll_one_line(textview,
1747 (event->state & GDK_MOD1_MASK) != 0);
1751 summary_pass_key_press_event(summaryview, event);
1756 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1757 KEY_PRESS_EVENT_STOP();
1758 mimeview_pass_key_press_event(messageview->mimeview,
1762 /* possible fall through */
1765 event->window != messageview->mainwin->window->window) {
1766 GdkEventKey tmpev = *event;
1768 tmpev.window = messageview->mainwin->window->window;
1769 KEY_PRESS_EVENT_STOP();
1770 gtk_widget_event(messageview->mainwin->window,
1771 (GdkEvent *)&tmpev);
1779 static gint show_url_timeout_cb(gpointer data)
1781 TextView *textview = (TextView *)data;
1783 if (textview->messageview->mainwin)
1784 if (textview->show_url_msgid)
1785 gtk_statusbar_remove(GTK_STATUSBAR(
1786 textview->messageview->mainwin->statusbar),
1787 textview->messageview->mainwin->folderview_cid,
1788 textview->show_url_msgid);
1792 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1796 textview->last_buttonpress = event->type;
1800 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1804 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1807 ((event->button == 1)
1808 || event->button == 2 || event->button == 3)) {
1811 /* double click seems to set the cursor after the current
1812 * word. The cursor position needs fixing, otherwise the
1813 * last word of a clickable zone will not work */
1814 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1815 textview->cur_pos--;
1818 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1819 RemoteURI *uri = (RemoteURI *)cur->data;
1821 if (textview->cur_pos >= uri->start &&
1822 textview->cur_pos <= uri->end) {
1825 trimmed_uri = trim_string(uri->uri, 60);
1826 /* single click: display url in statusbar */
1827 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1828 if (textview->messageview->mainwin) {
1829 if (textview->show_url_msgid) {
1830 gtk_timeout_remove(textview->show_url_timeout_tag);
1831 gtk_statusbar_remove(GTK_STATUSBAR(
1832 textview->messageview->mainwin->statusbar),
1833 textview->messageview->mainwin->folderview_cid,
1834 textview->show_url_msgid);
1835 textview->show_url_msgid = 0;
1837 textview->show_url_msgid = gtk_statusbar_push(
1838 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1839 textview->messageview->mainwin->folderview_cid,
1841 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1842 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1845 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1846 if (event->button == 3) {
1847 gchar *fromname, *fromaddress;
1850 fromaddress = g_strdup(uri->uri + 7);
1851 /* Hiroyuki: please put this function in utils.c! */
1852 fromname = procheader_get_fromname(fromaddress);
1853 extract_address(fromaddress);
1854 g_message("adding from textview %s <%s>", fromname, fromaddress);
1855 /* Add to address book - Match */
1856 addressbook_add_contact( fromname, fromaddress, NULL );
1858 g_free(fromaddress);
1861 PrefsAccount *account = NULL;
1862 FolderItem *folder_item;
1864 if (textview->messageview && textview->messageview->mainwin
1865 && textview->messageview->mainwin->summaryview
1866 && textview->messageview->mainwin->summaryview->folder_item) {
1867 folder_item = textview->messageview->mainwin->summaryview->folder_item;
1868 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1869 account = account_find_from_id(folder_item->prefs->default_account);
1871 compose_new(account, uri->uri + 7, NULL);
1875 prefs_common.uri_cmd);
1877 g_free(trimmed_uri);
1882 textview->last_buttonpress = event->type;
1886 static void textview_uri_list_remove_all(GSList *uri_list)
1890 for (cur = uri_list; cur != NULL; cur = cur->next) {
1892 g_free(((RemoteURI *)cur->data)->uri);
1897 g_slist_free(uri_list);