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);
466 static gboolean add_parts_func(GNode *node, gpointer data)
468 MimeInfo *mimeinfo = (MimeInfo *) node->data;
469 TextView *textview = (TextView *) data;
471 g_return_val_if_fail(mimeinfo != NULL, FALSE);
473 textview_add_part(textview, mimeinfo);
478 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
480 g_return_if_fail(mimeinfo != NULL);
482 g_node_traverse(mimeinfo->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, add_parts_func, textview);
485 #define TEXT_INSERT(str) \
486 gtk_stext_insert(text, textview->msgfont, NULL, NULL, str, -1)
488 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
492 if (!partinfo) return;
494 textview_set_font(textview, NULL);
495 text = GTK_STEXT(textview->text);
496 textview_clear(textview);
498 gtk_stext_freeze(text);
500 TEXT_INSERT(_("To save this part, pop up the context menu with "));
501 TEXT_INSERT(_("right click and select `Save as...', "));
502 TEXT_INSERT(_("or press `y' key.\n\n"));
504 TEXT_INSERT(_("To display this part as a text message, select "));
505 TEXT_INSERT(_("`Display as text', or press `t' key.\n\n"));
507 TEXT_INSERT(_("To open this part with external program, select "));
508 TEXT_INSERT(_("`Open' or `Open with...', "));
509 TEXT_INSERT(_("or double-click, or click the center button, "));
510 TEXT_INSERT(_("or press `l' key."));
512 gtk_stext_thaw(text);
517 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
518 const gchar *charset)
524 conv = conv_code_converter_new(charset);
526 textview->is_in_signature = FALSE;
528 if(mimeinfo->encoding_type != ENC_BINARY &&
529 mimeinfo->encoding_type != ENC_7BIT &&
530 mimeinfo->encoding_type != ENC_8BIT)
531 procmime_decode_content(mimeinfo);
533 if (!g_strcasecmp(mimeinfo->subtype, "html")) {
536 filename = procmime_get_tmp_file_name(mimeinfo);
537 if (procmime_get_part(filename, mimeinfo) == 0) {
538 tmpfp = fopen(filename, "rb");
539 textview_show_html(textview, tmpfp, conv);
544 } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
547 filename = procmime_get_tmp_file_name(mimeinfo);
548 if (procmime_get_part(filename, mimeinfo) == 0) {
549 tmpfp = fopen(filename, "rb");
550 textview_show_ertf(textview, tmpfp, conv);
556 tmpfp = fopen(mimeinfo->filename, "rb");
557 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
558 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
559 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
560 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
561 textview_write_line(textview, buf, conv);
565 conv_code_converter_destroy(conv);
568 static void textview_show_html(TextView *textview, FILE *fp,
574 parser = html_parser_new(fp, conv);
575 g_return_if_fail(parser != NULL);
577 while ((str = html_parse(parser)) != NULL) {
578 if (parser->state == HTML_HREF) {
579 /* first time : get and copy the URL */
580 if (parser->href == NULL) {
581 /* ALF - the sylpheed html parser returns an empty string,
582 * if still inside an <a>, but already parsed past HREF */
583 str = strtok(str, " ");
585 parser->href = strdup(str);
586 /* the URL may (or not) be followed by the
588 str = strtok(NULL, "");
592 textview_write_link(textview, str, parser->href, NULL);
594 textview_write_line(textview, str, NULL);
596 html_parser_destroy(parser);
599 static void textview_show_ertf(TextView *textview, FILE *fp,
605 parser = ertf_parser_new(fp, conv);
606 g_return_if_fail(parser != NULL);
608 while ((str = ertf_parse(parser)) != NULL) {
609 textview_write_line(textview, str, NULL);
612 ertf_parser_destroy(parser);
615 /* get_uri_part() - retrieves a URI starting from scanpos.
616 Returns TRUE if succesful */
617 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
618 const gchar **bp, const gchar **ep)
622 g_return_val_if_fail(start != NULL, FALSE);
623 g_return_val_if_fail(scanpos != NULL, FALSE);
624 g_return_val_if_fail(bp != NULL, FALSE);
625 g_return_val_if_fail(ep != NULL, FALSE);
629 /* find end point of URI */
630 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
631 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
635 /* no punctuation at end of string */
637 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
638 * should pass some URI type to this function and decide on that whether
639 * to perform punctuation stripping */
641 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
643 for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
653 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
655 return g_strndup(bp, ep - bp);
658 /* valid mail address characters */
659 #define IS_RFC822_CHAR(ch) \
664 !strchr("(),;<>\"", (ch)))
666 /* alphabet and number within 7bit ASCII */
667 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
668 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
670 static GHashTable *create_domain_tab(void)
672 static const gchar *toplvl_domains [] = {
674 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
675 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
676 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
677 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
678 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
679 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
680 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
681 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
682 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
683 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
684 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
685 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
686 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
687 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
688 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
689 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
690 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
691 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
692 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
693 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
694 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
695 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
696 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
697 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
698 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
699 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
702 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
704 g_return_val_if_fail(htab, NULL);
705 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
706 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
710 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
712 const gint MAX_LVL_DOM_NAME_LEN = 6;
713 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
714 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
717 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
720 for (p = buf; p < m && first < last; *p++ = *first++)
724 return g_hash_table_lookup(tab, buf) != NULL;
727 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
728 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
729 const gchar **bp, const gchar **ep)
731 /* more complex than the uri part because we need to scan back and forward starting from
732 * the scan position. */
733 gboolean result = FALSE;
734 const gchar *bp_ = NULL;
735 const gchar *ep_ = NULL;
736 static GHashTable *dom_tab;
737 const gchar *last_dot = NULL;
738 const gchar *prelast_dot = NULL;
739 const gchar *last_tld_char = NULL;
741 /* the informative part of the email address (describing the name
742 * of the email address owner) may contain quoted parts. the
743 * closure stack stores the last encountered quotes. */
744 gchar closure_stack[128];
745 gchar *ptr = closure_stack;
747 g_return_val_if_fail(start != NULL, FALSE);
748 g_return_val_if_fail(scanpos != NULL, FALSE);
749 g_return_val_if_fail(bp != NULL, FALSE);
750 g_return_val_if_fail(ep != NULL, FALSE);
753 dom_tab = create_domain_tab();
754 g_return_val_if_fail(dom_tab, FALSE);
756 /* scan start of address */
757 for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
760 /* TODO: should start with an alnum? */
762 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
765 if (bp_ != scanpos) {
766 /* scan end of address */
767 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
769 prelast_dot = last_dot;
771 if (*(last_dot + 1) == '.') {
772 if (prelast_dot == NULL)
774 last_dot = prelast_dot;
779 /* TODO: really should terminate with an alnum? */
780 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
784 if (last_dot == NULL)
787 last_dot = prelast_dot;
788 if (last_dot == NULL || (scanpos + 1 >= last_dot))
792 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
793 if (*last_tld_char == '?')
796 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
803 if (!result) return FALSE;
805 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
806 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
809 /* see if this is <bracketed>; in this case we also scan for the informative part. */
810 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
813 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
814 #define IN_STACK() (ptr > closure_stack)
815 /* has underrun check */
816 #define POP_STACK() if(IN_STACK()) --ptr
817 /* has overrun check */
818 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
819 /* has underrun check */
820 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
824 /* scan for the informative part. */
825 for (bp_ -= 2; bp_ >= start; bp_--) {
826 /* if closure on the stack keep scanning */
827 if (PEEK_STACK() == *bp_) {
831 if (*bp_ == '\'' || *bp_ == '"') {
836 /* if nothing in the closure stack, do the special conditions
837 * the following if..else expression simply checks whether
838 * a token is acceptable. if not acceptable, the clause
839 * should terminate the loop with a 'break' */
842 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
843 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
844 /* hyphens are allowed, but only in
846 } else if (!ispunct(*bp_)) {
847 /* but anything not being a punctiation
850 break; /* anything else is rejected */
863 /* scan forward (should start with an alnum) */
864 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
874 #undef IS_RFC822_CHAR
876 static gchar *make_email_string(const gchar *bp, const gchar *ep)
878 /* returns a mailto: URI; mailto: is also used to detect the
879 * uri type later on in the button_pressed signal handler */
883 tmp = g_strndup(bp, ep - bp);
884 result = g_strconcat("mailto:", tmp, NULL);
890 #define ADD_TXT_POS(bp_, ep_, pti_) \
891 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
893 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
896 g_warning("alloc error scanning URIs\n"); \
897 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
902 /* textview_make_clickable_parts() - colorizes clickable parts */
903 static void textview_make_clickable_parts(TextView *textview,
907 const gchar *linebuf)
909 /* parse table - in order of priority */
911 const gchar *needle; /* token */
913 /* token search function */
914 gchar *(*search) (const gchar *haystack,
915 const gchar *needle);
916 /* part parsing function */
917 gboolean (*parse) (const gchar *start,
918 const gchar *scanpos,
921 /* part to URI function */
922 gchar *(*build_uri) (const gchar *bp,
926 static struct table parser[] = {
927 {"http://", strcasestr, get_uri_part, make_uri_string},
928 {"https://", strcasestr, get_uri_part, make_uri_string},
929 {"ftp://", strcasestr, get_uri_part, make_uri_string},
930 {"www.", strcasestr, get_uri_part, make_uri_string},
931 {"mailto:", strcasestr, get_uri_part, make_uri_string},
932 {"@", strcasestr, get_email_part, make_email_string}
934 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
937 const gchar *walk, *bp, *ep;
940 const gchar *bp, *ep; /* text position */
941 gint pti; /* index in parse table */
942 struct txtpos *next; /* next */
943 } head = {NULL, NULL, 0, NULL}, *last = &head;
945 GtkSText *text = GTK_STEXT(textview->text);
947 /* parse for clickable parts, and build a list of begin and end positions */
948 for (walk = linebuf, n = 0;;) {
949 gint last_index = PARSE_ELEMS;
950 gchar *scanpos = NULL;
952 /* FIXME: this looks phony. scanning for anything in the parse table */
953 for (n = 0; n < PARSE_ELEMS; n++) {
956 tmp = parser[n].search(walk, parser[n].needle);
958 if (scanpos == NULL || tmp < scanpos) {
966 /* check if URI can be parsed */
967 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
968 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
969 ADD_TXT_POS(bp, ep, last_index);
973 strlen(parser[last_index].needle);
978 /* colorize this line */
980 const gchar *normal_text = linebuf;
983 for (last = head.next; last != NULL;
984 normal_text = last->ep, last = last->next) {
987 uri = g_new(RemoteURI, 1);
988 if (last->bp - normal_text > 0)
989 gtk_stext_insert(text, font,
992 last->bp - normal_text);
993 uri->uri = parser[last->pti].build_uri(last->bp,
995 uri->start = gtk_stext_get_point(text);
996 gtk_stext_insert(text, font, uri_color,
997 NULL, last->bp, last->ep - last->bp);
998 uri->end = gtk_stext_get_point(text);
1000 g_slist_append(textview->uri_list, uri);
1004 gtk_stext_insert(text, font, fg_color,
1005 NULL, normal_text, -1);
1007 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1012 static void textview_write_line(TextView *textview, const gchar *str,
1013 CodeConverter *conv)
1015 GtkSText *text = GTK_STEXT(textview->text);
1016 gchar buf[BUFFSIZE];
1018 gint quotelevel = -1;
1021 if (textview->text_is_mb)
1022 conv_localetodisp(buf, sizeof(buf), str);
1024 strncpy2(buf, str, sizeof(buf));
1025 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1026 conv_localetodisp(buf, sizeof(buf), str);
1027 else if (textview->text_is_mb)
1028 conv_unreadable_locale(buf);
1031 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1034 /* change color of quotation
1035 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1036 Up to 3 levels of quotations are detected, and each
1037 level is colored using a different color. */
1038 if (prefs_common.enable_color
1039 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1040 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1042 /* set up the correct foreground color */
1043 if (quotelevel > 2) {
1044 /* recycle colors */
1045 if (prefs_common.recycle_quote_colors)
1052 if (quotelevel == -1)
1055 fg_color = "e_colors[quotelevel];
1057 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1058 fg_color = &signature_color;
1059 textview->is_in_signature = TRUE;
1062 if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1063 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1065 if (prefs_common.enable_color)
1066 textview_make_clickable_parts(textview, textview->msgfont,
1067 fg_color, &uri_color, buf);
1069 textview_make_clickable_parts(textview, textview->msgfont,
1070 fg_color, NULL, buf);
1073 void textview_write_link(TextView *textview, const gchar *str,
1074 const gchar *uri, CodeConverter *conv)
1076 GdkColor *link_color = NULL;
1077 GtkSText *text = GTK_STEXT(textview->text);
1078 gchar buf[BUFFSIZE];
1086 if (textview->text_is_mb)
1087 conv_localetodisp(buf, sizeof(buf), str);
1089 strncpy2(buf, str, sizeof(buf));
1090 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1091 conv_localetodisp(buf, sizeof(buf), str);
1092 else if (textview->text_is_mb)
1093 conv_unreadable_locale(buf);
1097 for (bufp = buf; isspace(*bufp); bufp++)
1098 gtk_stext_insert(text, textview->msgfont, NULL, NULL, bufp, 1);
1100 if (prefs_common.enable_color) {
1101 link_color = &uri_color;
1103 r_uri = g_new(RemoteURI, 1);
1104 r_uri->uri = g_strdup(uri);
1105 r_uri->start = gtk_stext_get_point(text);
1106 gtk_stext_insert(text, textview->msgfont, link_color, NULL, bufp, -1);
1107 r_uri->end = gtk_stext_get_point(text);
1108 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1111 void textview_clear(TextView *textview)
1113 GtkSText *text = GTK_STEXT(textview->text);
1115 gtk_stext_freeze(text);
1116 gtk_stext_set_point(text, 0);
1117 gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1118 gtk_stext_thaw(text);
1120 textview_uri_list_remove_all(textview->uri_list);
1121 textview->uri_list = NULL;
1123 textview->body_pos = 0;
1124 textview->cur_pos = 0;
1127 void textview_destroy(TextView *textview)
1129 textview_uri_list_remove_all(textview->uri_list);
1130 textview->uri_list = NULL;
1132 if (!textview->scrolledwin_sb->parent)
1133 gtk_widget_destroy(textview->scrolledwin_sb);
1134 if (!textview->scrolledwin_mb->parent)
1135 gtk_widget_destroy(textview->scrolledwin_mb);
1137 if (textview->msgfont)
1138 gdk_font_unref(textview->msgfont);
1139 if (textview->boldfont)
1140 gdk_font_unref(textview->boldfont);
1145 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1147 textview->show_all_headers = all_headers;
1150 void textview_set_font(TextView *textview, const gchar *codeset)
1152 gboolean use_fontset = TRUE;
1154 /* In multi-byte mode, GtkSText can't display 8bit characters
1155 correctly, so it must be single-byte mode. */
1156 if (MB_CUR_MAX > 1) {
1157 if (codeset && conv_get_current_charset() != C_UTF_8) {
1158 if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1159 !g_strcasecmp(codeset, "BALTIC"))
1160 use_fontset = FALSE;
1161 else if (conv_get_current_charset() != C_EUC_JP &&
1162 (!g_strncasecmp(codeset, "KOI8-", 5) ||
1163 !g_strncasecmp(codeset, "CP", 2) ||
1164 !g_strncasecmp(codeset, "WINDOWS-", 8)))
1165 use_fontset = FALSE;
1168 use_fontset = FALSE;
1170 if (textview->text_is_mb && !use_fontset) {
1173 parent = textview->scrolledwin_mb->parent;
1174 gtkut_container_remove(GTK_CONTAINER(parent),
1175 textview->scrolledwin_mb);
1176 gtk_container_add(GTK_CONTAINER(parent),
1177 textview->scrolledwin_sb);
1179 textview->text = textview->text_sb;
1180 textview->text_is_mb = FALSE;
1181 } else if (!textview->text_is_mb && use_fontset) {
1184 parent = textview->scrolledwin_sb->parent;
1185 gtkut_container_remove(GTK_CONTAINER(parent),
1186 textview->scrolledwin_sb);
1187 gtk_container_add(GTK_CONTAINER(parent),
1188 textview->scrolledwin_mb);
1190 textview->text = textview->text_mb;
1191 textview->text_is_mb = TRUE;
1194 if (prefs_common.textfont) {
1199 text_mb_font->ascent = text_mb_font_orig_ascent;
1200 text_mb_font->descent = text_mb_font_orig_descent;
1202 font = gdk_fontset_load(prefs_common.textfont);
1203 if (font && text_mb_font != font) {
1205 gdk_font_unref(text_mb_font);
1206 text_mb_font = font;
1207 text_mb_font_orig_ascent = font->ascent;
1208 text_mb_font_orig_descent = font->descent;
1212 text_sb_font->ascent = text_sb_font_orig_ascent;
1213 text_sb_font->descent = text_sb_font_orig_descent;
1216 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1218 font = gtkut_font_load_from_fontset
1219 (prefs_common.textfont);
1220 if (font && text_sb_font != font) {
1222 gdk_font_unref(text_sb_font);
1223 text_sb_font = font;
1224 text_sb_font_orig_ascent = font->ascent;
1225 text_sb_font_orig_descent = font->descent;
1230 gint ascent, descent;
1232 descent = prefs_common.line_space / 2;
1233 ascent = prefs_common.line_space - descent;
1234 font->ascent += ascent;
1235 font->descent += descent;
1237 if (textview->msgfont)
1238 gdk_font_unref(textview->msgfont);
1239 textview->msgfont = font;
1244 if (!textview->boldfont && prefs_common.boldfont)
1245 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1247 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1263 H_ORGANIZATION = 11,
1266 void textview_set_position(TextView *textview, gint pos)
1270 gtk_stext_get_length(GTK_STEXT(textview->text));
1272 textview->cur_pos = pos;
1276 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1278 gchar buf[BUFFSIZE];
1279 GPtrArray *headers, *sorted_headers;
1280 GSList *disphdr_list;
1284 g_return_val_if_fail(fp != NULL, NULL);
1286 if (textview->show_all_headers)
1287 return procheader_get_header_array_asis(fp);
1289 if (!prefs_common.display_header) {
1290 while (fgets(buf, sizeof(buf), fp) != NULL)
1291 if (buf[0] == '\r' || buf[0] == '\n') break;
1295 headers = procheader_get_header_array_asis(fp);
1297 sorted_headers = g_ptr_array_new();
1299 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1300 disphdr_list = disphdr_list->next) {
1301 DisplayHeaderProp *dp =
1302 (DisplayHeaderProp *)disphdr_list->data;
1304 for (i = 0; i < headers->len; i++) {
1305 header = g_ptr_array_index(headers, i);
1307 if (procheader_headername_equal(header->name,
1310 procheader_header_free(header);
1312 g_ptr_array_add(sorted_headers, header);
1314 g_ptr_array_remove_index(headers, i);
1320 if (prefs_common.show_other_header) {
1321 for (i = 0; i < headers->len; i++) {
1322 header = g_ptr_array_index(headers, i);
1323 g_ptr_array_add(sorted_headers, header);
1325 g_ptr_array_free(headers, TRUE);
1327 procheader_header_array_destroy(headers);
1330 return sorted_headers;
1333 static void textview_show_header(TextView *textview, GPtrArray *headers)
1335 GtkSText *text = GTK_STEXT(textview->text);
1339 g_return_if_fail(headers != NULL);
1341 gtk_stext_freeze(text);
1343 for (i = 0; i < headers->len; i++) {
1344 header = g_ptr_array_index(headers, i);
1345 g_return_if_fail(header->name != NULL);
1347 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1349 if (header->name[strlen(header->name) - 1] != ' ')
1350 gtk_stext_insert(text, textview->boldfont,
1351 NULL, NULL, " ", 1);
1353 if (procheader_headername_equal(header->name, "Subject") ||
1354 procheader_headername_equal(header->name, "From") ||
1355 procheader_headername_equal(header->name, "To") ||
1356 procheader_headername_equal(header->name, "Cc"))
1357 unfold_line(header->body);
1359 if (textview->text_is_mb == TRUE)
1360 conv_unreadable_locale(header->body);
1362 if (prefs_common.enable_color &&
1363 (procheader_headername_equal(header->name, "X-Mailer") ||
1364 procheader_headername_equal(header->name,
1366 strstr(header->body, "Sylpheed") != NULL)
1367 gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1369 else if (prefs_common.enable_color) {
1370 textview_make_clickable_parts(textview,
1371 NULL, NULL, &uri_color,
1374 textview_make_clickable_parts(textview,
1378 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1381 gtk_stext_thaw(text);
1384 gboolean textview_search_string(TextView *textview, const gchar *str,
1387 GtkSText *text = GTK_STEXT(textview->text);
1391 g_return_val_if_fail(str != NULL, FALSE);
1393 len = get_mbs_len(str);
1394 g_return_val_if_fail(len >= 0, FALSE);
1396 pos = textview->cur_pos;
1397 if (pos < textview->body_pos)
1398 pos = textview->body_pos;
1400 if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1401 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1402 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1403 textview_set_position(textview, pos + len);
1410 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1413 GtkSText *text = GTK_STEXT(textview->text);
1418 gboolean found = FALSE;
1420 g_return_val_if_fail(str != NULL, FALSE);
1422 wcs = strdup_mbstowcs(str);
1423 g_return_val_if_fail(wcs != NULL, FALSE);
1425 pos = textview->cur_pos;
1426 text_len = gtk_stext_get_length(text);
1427 if (text_len - textview->body_pos < len) {
1431 if (pos <= textview->body_pos || text_len - pos < len)
1432 pos = text_len - len;
1434 for (; pos >= textview->body_pos; pos--) {
1435 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1437 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1438 gtk_editable_select_region(GTK_EDITABLE(text),
1440 textview_set_position(textview, pos - 1);
1444 if (pos == textview->body_pos) break;
1451 void textview_scroll_one_line(TextView *textview, gboolean up)
1453 GtkSText *text = GTK_STEXT(textview->text);
1456 if (prefs_common.enable_smooth_scroll) {
1457 textview_smooth_scroll_one_line(textview, up);
1462 upper = text->vadj->upper - text->vadj->page_size;
1463 if (text->vadj->value < upper) {
1464 text->vadj->value +=
1465 text->vadj->step_increment * 4;
1467 MIN(text->vadj->value, upper);
1468 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1472 if (text->vadj->value > 0.0) {
1473 text->vadj->value -=
1474 text->vadj->step_increment * 4;
1476 MAX(text->vadj->value, 0.0);
1477 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1483 gboolean textview_scroll_page(TextView *textview, gboolean up)
1485 GtkSText *text = GTK_STEXT(textview->text);
1489 if (prefs_common.enable_smooth_scroll)
1490 return textview_smooth_scroll_page(textview, up);
1492 if (prefs_common.scroll_halfpage)
1493 page_incr = text->vadj->page_increment / 2;
1495 page_incr = text->vadj->page_increment;
1498 upper = text->vadj->upper - text->vadj->page_size;
1499 if (text->vadj->value < upper) {
1500 text->vadj->value += page_incr;
1501 text->vadj->value = MIN(text->vadj->value, upper);
1502 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1507 if (text->vadj->value > 0.0) {
1508 text->vadj->value -= page_incr;
1509 text->vadj->value = MAX(text->vadj->value, 0.0);
1510 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1519 static void textview_smooth_scroll_do(TextView *textview,
1520 gfloat old_value, gfloat last_value,
1523 GtkSText *text = GTK_STEXT(textview->text);
1528 if (old_value < last_value) {
1529 change_value = last_value - old_value;
1532 change_value = old_value - last_value;
1536 gdk_key_repeat_disable();
1538 for (i = step; i <= change_value; i += step) {
1539 text->vadj->value = old_value + (up ? -i : i);
1540 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1544 text->vadj->value = last_value;
1545 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1547 gdk_key_repeat_restore();
1550 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1552 GtkSText *text = GTK_STEXT(textview->text);
1558 upper = text->vadj->upper - text->vadj->page_size;
1559 if (text->vadj->value < upper) {
1560 old_value = text->vadj->value;
1561 last_value = text->vadj->value +
1562 text->vadj->step_increment * 4;
1563 last_value = MIN(last_value, upper);
1565 textview_smooth_scroll_do(textview, old_value,
1567 prefs_common.scroll_step);
1570 if (text->vadj->value > 0.0) {
1571 old_value = text->vadj->value;
1572 last_value = text->vadj->value -
1573 text->vadj->step_increment * 4;
1574 last_value = MAX(last_value, 0.0);
1576 textview_smooth_scroll_do(textview, old_value,
1578 prefs_common.scroll_step);
1583 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1585 GtkSText *text = GTK_STEXT(textview->text);
1591 if (prefs_common.scroll_halfpage)
1592 page_incr = text->vadj->page_increment / 2;
1594 page_incr = text->vadj->page_increment;
1597 upper = text->vadj->upper - text->vadj->page_size;
1598 if (text->vadj->value < upper) {
1599 old_value = text->vadj->value;
1600 last_value = text->vadj->value + page_incr;
1601 last_value = MIN(last_value, upper);
1603 textview_smooth_scroll_do(textview, old_value,
1605 prefs_common.scroll_step);
1609 if (text->vadj->value > 0.0) {
1610 old_value = text->vadj->value;
1611 last_value = text->vadj->value - page_incr;
1612 last_value = MAX(last_value, 0.0);
1614 textview_smooth_scroll_do(textview, old_value,
1616 prefs_common.scroll_step);
1624 #define KEY_PRESS_EVENT_STOP() \
1625 if (gtk_signal_n_emissions_by_name \
1626 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1627 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1628 "key_press_event"); \
1631 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1634 SummaryView *summaryview = NULL;
1635 MessageView *messageview = textview->messageview;
1637 if (!event) return FALSE;
1638 if (messageview->mainwin)
1639 summaryview = messageview->mainwin->summaryview;
1641 switch (event->keyval) {
1656 summary_pass_key_press_event(summaryview, event);
1658 textview_scroll_page(textview, FALSE);
1661 textview_scroll_page(textview, TRUE);
1664 textview_scroll_one_line(textview,
1665 (event->state & GDK_MOD1_MASK) != 0);
1669 summary_pass_key_press_event(summaryview, event);
1678 KEY_PRESS_EVENT_STOP();
1679 mimeview_pass_key_press_event(messageview->mimeview,
1684 event->window != messageview->mainwin->window->window) {
1685 GdkEventKey tmpev = *event;
1687 tmpev.window = messageview->mainwin->window->window;
1688 KEY_PRESS_EVENT_STOP();
1689 gtk_widget_event(messageview->mainwin->window,
1690 (GdkEvent *)&tmpev);
1698 static gint show_url_timeout_cb(gpointer data)
1700 TextView *textview = (TextView *)data;
1702 if (textview->messageview->mainwin)
1703 if (textview->show_url_msgid)
1704 gtk_statusbar_remove(GTK_STATUSBAR(
1705 textview->messageview->mainwin->statusbar),
1706 textview->messageview->mainwin->folderview_cid,
1707 textview->show_url_msgid);
1711 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1715 textview->last_buttonpress = event->type;
1719 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1723 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1726 ((event->button == 1)
1727 || event->button == 2 || event->button == 3)) {
1730 /* double click seems to set the cursor after the current
1731 * word. The cursor position needs fixing, otherwise the
1732 * last word of a clickable zone will not work */
1733 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1734 textview->cur_pos--;
1737 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1738 RemoteURI *uri = (RemoteURI *)cur->data;
1740 if (textview->cur_pos >= uri->start &&
1741 textview->cur_pos <= uri->end) {
1744 trimmed_uri = trim_string(uri->uri, 60);
1745 /* single click: display url in statusbar */
1746 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1747 if (textview->messageview->mainwin) {
1748 if (textview->show_url_msgid) {
1749 gtk_timeout_remove(textview->show_url_timeout_tag);
1750 gtk_statusbar_remove(GTK_STATUSBAR(
1751 textview->messageview->mainwin->statusbar),
1752 textview->messageview->mainwin->folderview_cid,
1753 textview->show_url_msgid);
1754 textview->show_url_msgid = 0;
1756 textview->show_url_msgid = gtk_statusbar_push(
1757 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1758 textview->messageview->mainwin->folderview_cid,
1760 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1761 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1764 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1765 if (event->button == 3) {
1766 gchar *fromname, *fromaddress;
1769 fromaddress = g_strdup(uri->uri + 7);
1770 /* Hiroyuki: please put this function in utils.c! */
1771 fromname = procheader_get_fromname(fromaddress);
1772 extract_address(fromaddress);
1773 g_message("adding from textview %s <%s>", fromname, fromaddress);
1774 /* Add to address book - Match */
1775 addressbook_add_contact( fromname, fromaddress, NULL );
1777 g_free(fromaddress);
1780 PrefsAccount *account = NULL;
1781 FolderItem *folder_item;
1783 if (textview->messageview && textview->messageview->mainwin
1784 && textview->messageview->mainwin->summaryview
1785 && textview->messageview->mainwin->summaryview->folder_item) {
1786 folder_item = textview->messageview->mainwin->summaryview->folder_item;
1787 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1788 account = account_find_from_id(folder_item->prefs->default_account);
1790 compose_new(account, uri->uri + 7, NULL);
1794 prefs_common.uri_cmd);
1796 g_free(trimmed_uri);
1801 textview->last_buttonpress = event->type;
1805 static void textview_uri_list_remove_all(GSList *uri_list)
1809 for (cur = uri_list; cur != NULL; cur = cur->next) {
1811 g_free(((RemoteURI *)cur->data)->uri);
1816 g_slist_free(uri_list);