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,
136 static void textview_add_parts (TextView *textview,
139 static void textview_write_body (TextView *textview,
142 const gchar *charset);
143 static void textview_show_html (TextView *textview,
145 CodeConverter *conv);
146 static void textview_write_line (TextView *textview,
148 CodeConverter *conv);
149 static void textview_write_link (TextView *textview,
152 CodeConverter *conv);
153 static GPtrArray *textview_scan_header (TextView *textview,
155 static void textview_show_header (TextView *textview,
158 static gint textview_key_pressed (GtkWidget *widget,
161 static gint textview_button_pressed (GtkWidget *widget,
162 GdkEventButton *event,
164 static gint textview_button_released (GtkWidget *widget,
165 GdkEventButton *event,
168 static void textview_uri_list_remove_all(GSList *uri_list);
170 static void textview_smooth_scroll_do (TextView *textview,
174 static void textview_smooth_scroll_one_line (TextView *textview,
176 static gboolean textview_smooth_scroll_page (TextView *textview,
180 TextView *textview_create(void)
184 GtkWidget *scrolledwin_sb;
185 GtkWidget *scrolledwin_mb;
189 debug_print("Creating text view...\n");
190 textview = g_new0(TextView, 1);
192 scrolledwin_sb = gtk_scrolled_window_new(NULL, NULL);
193 scrolledwin_mb = gtk_scrolled_window_new(NULL, NULL);
194 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_sb),
195 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
196 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin_mb),
197 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
198 gtk_widget_set_usize(scrolledwin_sb, prefs_common.mainview_width, -1);
199 gtk_widget_set_usize(scrolledwin_mb, prefs_common.mainview_width, -1);
201 /* create GtkSText widgets for single-byte and multi-byte character */
202 text_sb = gtk_stext_new(NULL, NULL);
203 text_mb = gtk_stext_new(NULL, NULL);
204 GTK_STEXT(text_sb)->default_tab_width = 8;
205 GTK_STEXT(text_mb)->default_tab_width = 8;
206 gtk_widget_show(text_sb);
207 gtk_widget_show(text_mb);
208 gtk_stext_set_word_wrap(GTK_STEXT(text_sb), TRUE);
209 gtk_stext_set_word_wrap(GTK_STEXT(text_mb), TRUE);
210 gtk_widget_ensure_style(text_sb);
211 gtk_widget_ensure_style(text_mb);
212 if (text_sb->style && text_sb->style->font->type == GDK_FONT_FONTSET) {
216 font = gtkut_font_load_from_fontset(prefs_common.normalfont);
218 style = gtk_style_copy(text_sb->style);
219 gdk_font_unref(style->font);
221 gtk_widget_set_style(text_sb, style);
224 if (text_mb->style && text_mb->style->font->type == GDK_FONT_FONT) {
228 font = gdk_fontset_load(prefs_common.normalfont);
230 style = gtk_style_copy(text_mb->style);
231 gdk_font_unref(style->font);
233 gtk_widget_set_style(text_mb, style);
236 gtk_widget_ref(scrolledwin_sb);
237 gtk_widget_ref(scrolledwin_mb);
239 gtk_container_add(GTK_CONTAINER(scrolledwin_sb), text_sb);
240 gtk_container_add(GTK_CONTAINER(scrolledwin_mb), text_mb);
241 gtk_signal_connect(GTK_OBJECT(text_sb), "key_press_event",
242 GTK_SIGNAL_FUNC(textview_key_pressed),
244 gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_press_event",
245 GTK_SIGNAL_FUNC(textview_button_pressed),
247 gtk_signal_connect_after(GTK_OBJECT(text_sb), "button_release_event",
248 GTK_SIGNAL_FUNC(textview_button_released),
250 gtk_signal_connect(GTK_OBJECT(text_mb), "key_press_event",
251 GTK_SIGNAL_FUNC(textview_key_pressed),
253 gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_press_event",
254 GTK_SIGNAL_FUNC(textview_button_pressed),
256 gtk_signal_connect_after(GTK_OBJECT(text_mb), "button_release_event",
257 GTK_SIGNAL_FUNC(textview_button_released),
260 gtk_widget_show(scrolledwin_sb);
261 gtk_widget_show(scrolledwin_mb);
263 vbox = gtk_vbox_new(FALSE, 0);
264 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin_sb, TRUE, TRUE, 0);
266 gtk_widget_show(vbox);
268 textview->vbox = vbox;
269 textview->scrolledwin = scrolledwin_sb;
270 textview->scrolledwin_sb = scrolledwin_sb;
271 textview->scrolledwin_mb = scrolledwin_mb;
272 textview->text = text_sb;
273 textview->text_sb = text_sb;
274 textview->text_mb = text_mb;
275 textview->text_is_mb = FALSE;
276 textview->uri_list = NULL;
277 textview->body_pos = 0;
278 textview->cur_pos = 0;
279 textview->show_all_headers = FALSE;
280 textview->last_buttonpress = GDK_NOTHING;
281 textview->show_url_msgid = 0;
286 void textview_init(TextView *textview)
288 gtkut_widget_disable_theme_engine(textview->text_sb);
289 gtkut_widget_disable_theme_engine(textview->text_mb);
290 textview_update_message_colors();
291 textview_set_all_headers(textview, FALSE);
292 textview_set_font(textview, NULL);
295 void textview_update_message_colors(void)
297 GdkColor black = {0, 0, 0, 0};
299 if (prefs_common.enable_color) {
300 /* grab the quote colors, converting from an int to a GdkColor */
301 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
303 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
305 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
307 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
309 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
312 quote_colors[0] = quote_colors[1] = quote_colors[2] =
313 uri_color = emphasis_color = signature_color = black;
317 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
322 const gchar *charset = NULL;
324 if ((fp = fopen(file, "rb")) == NULL) {
325 FILE_OP_ERROR(file, "fopen");
329 if (textview->messageview->forced_charset)
330 charset = textview->messageview->forced_charset;
331 else if (prefs_common.force_charset)
332 charset = prefs_common.force_charset;
333 else if (mimeinfo->charset)
334 charset = mimeinfo->charset;
336 textview_set_font(textview, charset);
337 textview_clear(textview);
339 text = GTK_STEXT(textview->text);
341 gtk_stext_freeze(text);
344 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) perror("fseek");
345 headers = textview_scan_header(textview, fp);
347 textview_show_header(textview, headers);
348 procheader_header_array_destroy(headers);
349 textview->body_pos = gtk_stext_get_length(text);
352 textview_add_parts(textview, mimeinfo, fp);
354 gtk_stext_thaw(text);
359 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
362 const gchar *charset = NULL;
364 g_return_if_fail(mimeinfo != NULL);
365 g_return_if_fail(fp != NULL);
367 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
368 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822"))) {
369 textview_clear(textview);
370 textview_add_parts(textview, mimeinfo, fp);
374 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
377 headers = textview_scan_header(textview, fp);
379 if (textview->messageview->forced_charset)
380 charset = textview->messageview->forced_charset;
381 else if (prefs_common.force_charset)
382 charset = prefs_common.force_charset;
383 else if (mimeinfo->charset)
384 charset = mimeinfo->charset;
386 textview_set_font(textview, charset);
388 text = GTK_STEXT(textview->text);
390 gtk_stext_freeze(text);
391 textview_clear(textview);
395 textview_show_header(textview, headers);
396 procheader_header_array_destroy(headers);
397 textview->body_pos = gtk_stext_get_length(text);
399 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
402 if (mimeinfo->type == MIMETYPE_MULTIPART)
403 textview_add_parts(textview, mimeinfo, fp);
405 textview_write_body(textview, mimeinfo, fp, charset);
407 gtk_stext_thaw(text);
410 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
412 GtkSText *text = GTK_STEXT(textview->text);
414 const gchar *charset = NULL;
415 GPtrArray *headers = NULL;
417 g_return_if_fail(mimeinfo != NULL);
418 g_return_if_fail(fp != NULL);
420 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
422 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) {
427 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822")) {
428 headers = textview_scan_header(textview, fp);
430 gtk_stext_freeze(text);
431 if (gtk_stext_get_length(text) > 0)
432 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
433 textview_show_header(textview, headers);
434 procheader_header_array_destroy(headers);
435 gtk_stext_thaw(text);
440 gtk_stext_freeze(text);
442 if (g_hash_table_lookup(mimeinfo->parameters, "name") != NULL)
443 g_snprintf(buf, sizeof(buf), "\n[%s %s/%s (%d bytes)]\n",
444 (gchar *) g_hash_table_lookup(mimeinfo->parameters, "name"),
445 procmime_get_type_str(mimeinfo->type),
446 mimeinfo->subtype, mimeinfo->length);
448 g_snprintf(buf, sizeof(buf), "\n[%s/%s (%d bytes)]\n",
449 procmime_get_type_str(mimeinfo->type),
450 mimeinfo->subtype, mimeinfo->length);
452 if (mimeinfo->type != MIMETYPE_TEXT) {
453 gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
455 if (!mimeinfo->main &&
457 mimeinfo->parent->children != mimeinfo)
458 gtk_stext_insert(text, NULL, NULL, NULL, buf, -1);
459 else if (prefs_common.display_header && (gtk_stext_get_length(text) > 0))
460 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1);
461 if (textview->messageview->forced_charset)
462 charset = textview->messageview->forced_charset;
463 else if (prefs_common.force_charset)
464 charset = prefs_common.force_charset;
465 else if (mimeinfo->charset)
466 charset = mimeinfo->charset;
467 textview_write_body(textview, mimeinfo, fp, charset);
470 gtk_stext_thaw(text);
473 static void textview_add_parts_func(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
475 g_return_if_fail(mimeinfo != NULL);
476 g_return_if_fail(fp != NULL);
479 textview_add_part(textview, mimeinfo, fp);
480 if(mimeinfo->children)
481 textview_add_parts_func(textview, mimeinfo->children, fp);
482 mimeinfo = mimeinfo->next;
486 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
488 g_return_if_fail(mimeinfo != NULL);
489 g_return_if_fail(fp != NULL);
491 textview_add_part(textview, mimeinfo, fp);
492 if(mimeinfo->children)
493 textview_add_parts_func(textview, mimeinfo->children, fp);
496 #define TEXT_INSERT(str) \
497 gtk_stext_insert(text, textview->msgfont, NULL, NULL, str, -1)
499 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
503 if (!partinfo) return;
505 textview_set_font(textview, NULL);
506 text = GTK_STEXT(textview->text);
507 textview_clear(textview);
509 gtk_stext_freeze(text);
511 TEXT_INSERT(_("To save this part, pop up the context menu with "));
512 TEXT_INSERT(_("right click and select `Save as...', "));
513 TEXT_INSERT(_("or press `y' key.\n\n"));
515 TEXT_INSERT(_("To display this part as a text message, select "));
516 TEXT_INSERT(_("`Display as text', or press `t' key.\n\n"));
518 TEXT_INSERT(_("To open this part with external program, select "));
519 TEXT_INSERT(_("`Open' or `Open with...', "));
520 TEXT_INSERT(_("or double-click, or click the center button, "));
521 TEXT_INSERT(_("or press `l' key."));
523 gtk_stext_thaw(text);
528 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo,
529 FILE *fp, const gchar *charset)
535 conv = conv_code_converter_new(charset);
537 textview->is_in_signature = FALSE;
539 if(mimeinfo->encoding_type != ENC_BINARY &&
540 mimeinfo->encoding_type != ENC_7BIT &&
541 mimeinfo->encoding_type != ENC_8BIT)
542 procmime_decode_content(mimeinfo);
545 if (!g_strcasecmp(mimeinfo->subtype, "html")) {
548 filename = procmime_get_tmp_file_name(mimeinfo);
549 if (procmime_get_part(filename, mimeinfo) == 0) {
550 tmpfp = fopen(filename, "rb");
551 textview_show_html(textview, tmpfp, conv);
556 } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
559 filename = procmime_get_tmp_file_name(mimeinfo);
560 if (procmime_get_part(filename, mimeinfo) == 0) {
561 tmpfp = fopen(filename, "rb");
562 textview_show_ertf(textview, tmpfp, conv);
568 tmpfp = fopen(mimeinfo->filename, "rb");
569 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
570 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
571 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
572 (ftell(tmpfp) < mimeinfo->offset + mimeinfo->length))
573 textview_write_line(textview, buf, conv);
577 conv_code_converter_destroy(conv);
580 static void textview_show_html(TextView *textview, FILE *fp,
586 parser = html_parser_new(fp, conv);
587 g_return_if_fail(parser != NULL);
589 while ((str = html_parse(parser)) != NULL) {
590 if (parser->state == HTML_HREF) {
591 /* first time : get and copy the URL */
592 if (parser->href == NULL) {
593 /* ALF - the sylpheed html parser returns an empty string,
594 * if still inside an <a>, but already parsed past HREF */
595 str = strtok(str, " ");
597 parser->href = strdup(str);
598 /* the URL may (or not) be followed by the
600 str = strtok(NULL, "");
604 textview_write_link(textview, str, parser->href, NULL);
606 textview_write_line(textview, str, NULL);
608 html_parser_destroy(parser);
611 static void textview_show_ertf(TextView *textview, FILE *fp,
617 parser = ertf_parser_new(fp, conv);
618 g_return_if_fail(parser != NULL);
620 while ((str = ertf_parse(parser)) != NULL) {
621 textview_write_line(textview, str, NULL);
624 ertf_parser_destroy(parser);
627 /* get_uri_part() - retrieves a URI starting from scanpos.
628 Returns TRUE if succesful */
629 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
630 const gchar **bp, const gchar **ep)
634 g_return_val_if_fail(start != NULL, FALSE);
635 g_return_val_if_fail(scanpos != NULL, FALSE);
636 g_return_val_if_fail(bp != NULL, FALSE);
637 g_return_val_if_fail(ep != NULL, FALSE);
641 /* find end point of URI */
642 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
643 if (!isgraph(*ep_) || !isascii(*ep_) || strchr("()<>\"", *ep_))
647 /* no punctuation at end of string */
649 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
650 * should pass some URI type to this function and decide on that whether
651 * to perform punctuation stripping */
653 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
655 for (; ep_ - 1 > scanpos + 1 && IS_REAL_PUNCT(*(ep_ - 1)); ep_--)
665 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
667 return g_strndup(bp, ep - bp);
670 /* valid mail address characters */
671 #define IS_RFC822_CHAR(ch) \
676 !strchr("(),;<>\"", (ch)))
678 /* alphabet and number within 7bit ASCII */
679 #define IS_ASCII_ALNUM(ch) (isascii(ch) && isalnum(ch))
680 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
682 static GHashTable *create_domain_tab(void)
684 static const gchar *toplvl_domains [] = {
686 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
687 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
688 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
689 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
690 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
691 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
692 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
693 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
694 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
695 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
696 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
697 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
698 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
699 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
700 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
701 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
702 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
703 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
704 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
705 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
706 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
707 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
708 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
709 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
710 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
711 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
714 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
716 g_return_val_if_fail(htab, NULL);
717 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
718 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
722 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
724 const gint MAX_LVL_DOM_NAME_LEN = 6;
725 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
726 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
729 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
732 for (p = buf; p < m && first < last; *p++ = *first++)
736 return g_hash_table_lookup(tab, buf) != NULL;
739 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
740 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
741 const gchar **bp, const gchar **ep)
743 /* more complex than the uri part because we need to scan back and forward starting from
744 * the scan position. */
745 gboolean result = FALSE;
746 const gchar *bp_ = NULL;
747 const gchar *ep_ = NULL;
748 static GHashTable *dom_tab;
749 const gchar *last_dot = NULL;
750 const gchar *prelast_dot = NULL;
751 const gchar *last_tld_char = NULL;
753 /* the informative part of the email address (describing the name
754 * of the email address owner) may contain quoted parts. the
755 * closure stack stores the last encountered quotes. */
756 gchar closure_stack[128];
757 gchar *ptr = closure_stack;
759 g_return_val_if_fail(start != NULL, FALSE);
760 g_return_val_if_fail(scanpos != NULL, FALSE);
761 g_return_val_if_fail(bp != NULL, FALSE);
762 g_return_val_if_fail(ep != NULL, FALSE);
765 dom_tab = create_domain_tab();
766 g_return_val_if_fail(dom_tab, FALSE);
768 /* scan start of address */
769 for (bp_ = scanpos - 1; bp_ >= start && IS_RFC822_CHAR(*bp_); bp_--)
772 /* TODO: should start with an alnum? */
774 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*bp_); bp_++)
777 if (bp_ != scanpos) {
778 /* scan end of address */
779 for (ep_ = scanpos + 1; *ep_ && IS_RFC822_CHAR(*ep_); ep_++)
781 prelast_dot = last_dot;
783 if (*(last_dot + 1) == '.') {
784 if (prelast_dot == NULL)
786 last_dot = prelast_dot;
791 /* TODO: really should terminate with an alnum? */
792 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*ep_); --ep_)
796 if (last_dot == NULL)
799 last_dot = prelast_dot;
800 if (last_dot == NULL || (scanpos + 1 >= last_dot))
804 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
805 if (*last_tld_char == '?')
808 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
815 if (!result) return FALSE;
817 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
818 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
821 /* see if this is <bracketed>; in this case we also scan for the informative part. */
822 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
825 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
826 #define IN_STACK() (ptr > closure_stack)
827 /* has underrun check */
828 #define POP_STACK() if(IN_STACK()) --ptr
829 /* has overrun check */
830 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
831 /* has underrun check */
832 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
836 /* scan for the informative part. */
837 for (bp_ -= 2; bp_ >= start; bp_--) {
838 /* if closure on the stack keep scanning */
839 if (PEEK_STACK() == *bp_) {
843 if (*bp_ == '\'' || *bp_ == '"') {
848 /* if nothing in the closure stack, do the special conditions
849 * the following if..else expression simply checks whether
850 * a token is acceptable. if not acceptable, the clause
851 * should terminate the loop with a 'break' */
854 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
855 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
856 /* hyphens are allowed, but only in
858 } else if (!ispunct(*bp_)) {
859 /* but anything not being a punctiation
862 break; /* anything else is rejected */
875 /* scan forward (should start with an alnum) */
876 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
886 #undef IS_RFC822_CHAR
888 static gchar *make_email_string(const gchar *bp, const gchar *ep)
890 /* returns a mailto: URI; mailto: is also used to detect the
891 * uri type later on in the button_pressed signal handler */
895 tmp = g_strndup(bp, ep - bp);
896 result = g_strconcat("mailto:", tmp, NULL);
902 #define ADD_TXT_POS(bp_, ep_, pti_) \
903 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
905 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
908 g_warning("alloc error scanning URIs\n"); \
909 gtk_stext_insert(text, textview->msgfont, fg_color, NULL, \
914 /* textview_make_clickable_parts() - colorizes clickable parts */
915 static void textview_make_clickable_parts(TextView *textview,
919 const gchar *linebuf)
921 /* parse table - in order of priority */
923 const gchar *needle; /* token */
925 /* token search function */
926 gchar *(*search) (const gchar *haystack,
927 const gchar *needle);
928 /* part parsing function */
929 gboolean (*parse) (const gchar *start,
930 const gchar *scanpos,
933 /* part to URI function */
934 gchar *(*build_uri) (const gchar *bp,
938 static struct table parser[] = {
939 {"http://", strcasestr, get_uri_part, make_uri_string},
940 {"https://", strcasestr, get_uri_part, make_uri_string},
941 {"ftp://", strcasestr, get_uri_part, make_uri_string},
942 {"www.", strcasestr, get_uri_part, make_uri_string},
943 {"mailto:", strcasestr, get_uri_part, make_uri_string},
944 {"@", strcasestr, get_email_part, make_email_string}
946 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
949 const gchar *walk, *bp, *ep;
952 const gchar *bp, *ep; /* text position */
953 gint pti; /* index in parse table */
954 struct txtpos *next; /* next */
955 } head = {NULL, NULL, 0, NULL}, *last = &head;
957 GtkSText *text = GTK_STEXT(textview->text);
959 /* parse for clickable parts, and build a list of begin and end positions */
960 for (walk = linebuf, n = 0;;) {
961 gint last_index = PARSE_ELEMS;
962 gchar *scanpos = NULL;
964 /* FIXME: this looks phony. scanning for anything in the parse table */
965 for (n = 0; n < PARSE_ELEMS; n++) {
968 tmp = parser[n].search(walk, parser[n].needle);
970 if (scanpos == NULL || tmp < scanpos) {
978 /* check if URI can be parsed */
979 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
980 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
981 ADD_TXT_POS(bp, ep, last_index);
985 strlen(parser[last_index].needle);
990 /* colorize this line */
992 const gchar *normal_text = linebuf;
995 for (last = head.next; last != NULL;
996 normal_text = last->ep, last = last->next) {
999 uri = g_new(RemoteURI, 1);
1000 if (last->bp - normal_text > 0)
1001 gtk_stext_insert(text, font,
1004 last->bp - normal_text);
1005 uri->uri = parser[last->pti].build_uri(last->bp,
1007 uri->start = gtk_stext_get_point(text);
1008 gtk_stext_insert(text, font, uri_color,
1009 NULL, last->bp, last->ep - last->bp);
1010 uri->end = gtk_stext_get_point(text);
1011 textview->uri_list =
1012 g_slist_append(textview->uri_list, uri);
1016 gtk_stext_insert(text, font, fg_color,
1017 NULL, normal_text, -1);
1019 gtk_stext_insert(text, font, fg_color, NULL, linebuf, -1);
1024 static void textview_write_line(TextView *textview, const gchar *str,
1025 CodeConverter *conv)
1027 GtkSText *text = GTK_STEXT(textview->text);
1028 gchar buf[BUFFSIZE];
1030 gint quotelevel = -1;
1033 if (textview->text_is_mb)
1034 conv_localetodisp(buf, sizeof(buf), str);
1036 strncpy2(buf, str, sizeof(buf));
1037 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1038 conv_localetodisp(buf, sizeof(buf), str);
1039 else if (textview->text_is_mb)
1040 conv_unreadable_locale(buf);
1043 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1046 /* change color of quotation
1047 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1048 Up to 3 levels of quotations are detected, and each
1049 level is colored using a different color. */
1050 if (prefs_common.enable_color
1051 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1052 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1054 /* set up the correct foreground color */
1055 if (quotelevel > 2) {
1056 /* recycle colors */
1057 if (prefs_common.recycle_quote_colors)
1064 if (quotelevel == -1)
1067 fg_color = "e_colors[quotelevel];
1069 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1070 fg_color = &signature_color;
1071 textview->is_in_signature = TRUE;
1074 if (prefs_common.head_space && spacingfont && buf[0] != '\n')
1075 gtk_stext_insert(text, spacingfont, NULL, NULL, " ", 1);
1077 if (prefs_common.enable_color)
1078 textview_make_clickable_parts(textview, textview->msgfont,
1079 fg_color, &uri_color, buf);
1081 textview_make_clickable_parts(textview, textview->msgfont,
1082 fg_color, NULL, buf);
1085 void textview_write_link(TextView *textview, const gchar *str,
1086 const gchar *uri, CodeConverter *conv)
1088 GdkColor *link_color = NULL;
1089 GtkSText *text = GTK_STEXT(textview->text);
1090 gchar buf[BUFFSIZE];
1098 if (textview->text_is_mb)
1099 conv_localetodisp(buf, sizeof(buf), str);
1101 strncpy2(buf, str, sizeof(buf));
1102 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1103 conv_localetodisp(buf, sizeof(buf), str);
1104 else if (textview->text_is_mb)
1105 conv_unreadable_locale(buf);
1109 for (bufp = buf; isspace(*bufp); bufp++)
1110 gtk_stext_insert(text, textview->msgfont, NULL, NULL, bufp, 1);
1112 if (prefs_common.enable_color) {
1113 link_color = &uri_color;
1115 r_uri = g_new(RemoteURI, 1);
1116 r_uri->uri = g_strdup(uri);
1117 r_uri->start = gtk_stext_get_point(text);
1118 gtk_stext_insert(text, textview->msgfont, link_color, NULL, bufp, -1);
1119 r_uri->end = gtk_stext_get_point(text);
1120 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1123 void textview_clear(TextView *textview)
1125 GtkSText *text = GTK_STEXT(textview->text);
1127 gtk_stext_freeze(text);
1128 gtk_stext_set_point(text, 0);
1129 gtk_stext_forward_delete(text, gtk_stext_get_length(text));
1130 gtk_stext_thaw(text);
1132 textview_uri_list_remove_all(textview->uri_list);
1133 textview->uri_list = NULL;
1135 textview->body_pos = 0;
1136 textview->cur_pos = 0;
1139 void textview_destroy(TextView *textview)
1141 textview_uri_list_remove_all(textview->uri_list);
1142 textview->uri_list = NULL;
1144 if (!textview->scrolledwin_sb->parent)
1145 gtk_widget_destroy(textview->scrolledwin_sb);
1146 if (!textview->scrolledwin_mb->parent)
1147 gtk_widget_destroy(textview->scrolledwin_mb);
1149 if (textview->msgfont)
1150 gdk_font_unref(textview->msgfont);
1151 if (textview->boldfont)
1152 gdk_font_unref(textview->boldfont);
1157 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1159 textview->show_all_headers = all_headers;
1162 void textview_set_font(TextView *textview, const gchar *codeset)
1164 gboolean use_fontset = TRUE;
1166 /* In multi-byte mode, GtkSText can't display 8bit characters
1167 correctly, so it must be single-byte mode. */
1168 if (MB_CUR_MAX > 1) {
1169 if (codeset && conv_get_current_charset() != C_UTF_8) {
1170 if (!g_strncasecmp(codeset, "ISO-8859-", 9) ||
1171 !g_strcasecmp(codeset, "BALTIC"))
1172 use_fontset = FALSE;
1173 else if (conv_get_current_charset() != C_EUC_JP &&
1174 (!g_strncasecmp(codeset, "KOI8-", 5) ||
1175 !g_strncasecmp(codeset, "CP", 2) ||
1176 !g_strncasecmp(codeset, "WINDOWS-", 8)))
1177 use_fontset = FALSE;
1180 use_fontset = FALSE;
1182 if (textview->text_is_mb && !use_fontset) {
1185 parent = textview->scrolledwin_mb->parent;
1186 gtkut_container_remove(GTK_CONTAINER(parent),
1187 textview->scrolledwin_mb);
1188 gtk_container_add(GTK_CONTAINER(parent),
1189 textview->scrolledwin_sb);
1191 textview->text = textview->text_sb;
1192 textview->text_is_mb = FALSE;
1193 } else if (!textview->text_is_mb && use_fontset) {
1196 parent = textview->scrolledwin_sb->parent;
1197 gtkut_container_remove(GTK_CONTAINER(parent),
1198 textview->scrolledwin_sb);
1199 gtk_container_add(GTK_CONTAINER(parent),
1200 textview->scrolledwin_mb);
1202 textview->text = textview->text_mb;
1203 textview->text_is_mb = TRUE;
1206 if (prefs_common.textfont) {
1211 text_mb_font->ascent = text_mb_font_orig_ascent;
1212 text_mb_font->descent = text_mb_font_orig_descent;
1214 font = gdk_fontset_load(prefs_common.textfont);
1215 if (font && text_mb_font != font) {
1217 gdk_font_unref(text_mb_font);
1218 text_mb_font = font;
1219 text_mb_font_orig_ascent = font->ascent;
1220 text_mb_font_orig_descent = font->descent;
1224 text_sb_font->ascent = text_sb_font_orig_ascent;
1225 text_sb_font->descent = text_sb_font_orig_descent;
1228 font = gdk_font_load("-*-courier-medium-r-normal--14-*-*-*-*-*-iso8859-1");
1230 font = gtkut_font_load_from_fontset
1231 (prefs_common.textfont);
1232 if (font && text_sb_font != font) {
1234 gdk_font_unref(text_sb_font);
1235 text_sb_font = font;
1236 text_sb_font_orig_ascent = font->ascent;
1237 text_sb_font_orig_descent = font->descent;
1242 gint ascent, descent;
1244 descent = prefs_common.line_space / 2;
1245 ascent = prefs_common.line_space - descent;
1246 font->ascent += ascent;
1247 font->descent += descent;
1249 if (textview->msgfont)
1250 gdk_font_unref(textview->msgfont);
1251 textview->msgfont = font;
1256 if (!textview->boldfont && prefs_common.boldfont)
1257 textview->boldfont = gtkut_font_load(prefs_common.boldfont);
1259 spacingfont = gdk_font_load("-*-*-medium-r-normal--6-*");
1275 H_ORGANIZATION = 11,
1278 void textview_set_position(TextView *textview, gint pos)
1282 gtk_stext_get_length(GTK_STEXT(textview->text));
1284 textview->cur_pos = pos;
1288 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1290 gchar buf[BUFFSIZE];
1291 GPtrArray *headers, *sorted_headers;
1292 GSList *disphdr_list;
1296 g_return_val_if_fail(fp != NULL, NULL);
1298 if (textview->show_all_headers)
1299 return procheader_get_header_array_asis(fp);
1301 if (!prefs_common.display_header) {
1302 while (fgets(buf, sizeof(buf), fp) != NULL)
1303 if (buf[0] == '\r' || buf[0] == '\n') break;
1307 headers = procheader_get_header_array_asis(fp);
1309 sorted_headers = g_ptr_array_new();
1311 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1312 disphdr_list = disphdr_list->next) {
1313 DisplayHeaderProp *dp =
1314 (DisplayHeaderProp *)disphdr_list->data;
1316 for (i = 0; i < headers->len; i++) {
1317 header = g_ptr_array_index(headers, i);
1319 if (procheader_headername_equal(header->name,
1322 procheader_header_free(header);
1324 g_ptr_array_add(sorted_headers, header);
1326 g_ptr_array_remove_index(headers, i);
1332 if (prefs_common.show_other_header) {
1333 for (i = 0; i < headers->len; i++) {
1334 header = g_ptr_array_index(headers, i);
1335 g_ptr_array_add(sorted_headers, header);
1337 g_ptr_array_free(headers, TRUE);
1339 procheader_header_array_destroy(headers);
1342 return sorted_headers;
1345 static void textview_show_header(TextView *textview, GPtrArray *headers)
1347 GtkSText *text = GTK_STEXT(textview->text);
1351 g_return_if_fail(headers != NULL);
1353 gtk_stext_freeze(text);
1355 for (i = 0; i < headers->len; i++) {
1356 header = g_ptr_array_index(headers, i);
1357 g_return_if_fail(header->name != NULL);
1359 gtk_stext_insert(text, textview->boldfont, NULL, NULL,
1361 if (header->name[strlen(header->name) - 1] != ' ')
1362 gtk_stext_insert(text, textview->boldfont,
1363 NULL, NULL, " ", 1);
1365 if (procheader_headername_equal(header->name, "Subject") ||
1366 procheader_headername_equal(header->name, "From") ||
1367 procheader_headername_equal(header->name, "To") ||
1368 procheader_headername_equal(header->name, "Cc"))
1369 unfold_line(header->body);
1371 if (textview->text_is_mb == TRUE)
1372 conv_unreadable_locale(header->body);
1374 if (prefs_common.enable_color &&
1375 (procheader_headername_equal(header->name, "X-Mailer") ||
1376 procheader_headername_equal(header->name,
1378 strstr(header->body, "Sylpheed") != NULL)
1379 gtk_stext_insert(text, NULL, &emphasis_color, NULL,
1381 else if (prefs_common.enable_color) {
1382 textview_make_clickable_parts(textview,
1383 NULL, NULL, &uri_color,
1386 textview_make_clickable_parts(textview,
1390 gtk_stext_insert(text, textview->msgfont, NULL, NULL, "\n", 1);
1393 gtk_stext_thaw(text);
1396 gboolean textview_search_string(TextView *textview, const gchar *str,
1399 GtkSText *text = GTK_STEXT(textview->text);
1403 g_return_val_if_fail(str != NULL, FALSE);
1405 len = get_mbs_len(str);
1406 g_return_val_if_fail(len >= 0, FALSE);
1408 pos = textview->cur_pos;
1409 if (pos < textview->body_pos)
1410 pos = textview->body_pos;
1412 if ((pos = gtkut_stext_find(text, pos, str, case_sens)) != -1) {
1413 gtk_editable_set_position(GTK_EDITABLE(text), pos + len);
1414 gtk_editable_select_region(GTK_EDITABLE(text), pos, pos + len);
1415 textview_set_position(textview, pos + len);
1422 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1425 GtkSText *text = GTK_STEXT(textview->text);
1430 gboolean found = FALSE;
1432 g_return_val_if_fail(str != NULL, FALSE);
1434 wcs = strdup_mbstowcs(str);
1435 g_return_val_if_fail(wcs != NULL, FALSE);
1437 pos = textview->cur_pos;
1438 text_len = gtk_stext_get_length(text);
1439 if (text_len - textview->body_pos < len) {
1443 if (pos <= textview->body_pos || text_len - pos < len)
1444 pos = text_len - len;
1446 for (; pos >= textview->body_pos; pos--) {
1447 if (gtk_stext_match_string(text, pos, wcs, len, case_sens)
1449 gtk_editable_set_position(GTK_EDITABLE(text), pos);
1450 gtk_editable_select_region(GTK_EDITABLE(text),
1452 textview_set_position(textview, pos - 1);
1456 if (pos == textview->body_pos) break;
1463 void textview_scroll_one_line(TextView *textview, gboolean up)
1465 GtkSText *text = GTK_STEXT(textview->text);
1468 if (prefs_common.enable_smooth_scroll) {
1469 textview_smooth_scroll_one_line(textview, up);
1474 upper = text->vadj->upper - text->vadj->page_size;
1475 if (text->vadj->value < upper) {
1476 text->vadj->value +=
1477 text->vadj->step_increment * 4;
1479 MIN(text->vadj->value, upper);
1480 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1484 if (text->vadj->value > 0.0) {
1485 text->vadj->value -=
1486 text->vadj->step_increment * 4;
1488 MAX(text->vadj->value, 0.0);
1489 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1495 gboolean textview_scroll_page(TextView *textview, gboolean up)
1497 GtkSText *text = GTK_STEXT(textview->text);
1501 if (prefs_common.enable_smooth_scroll)
1502 return textview_smooth_scroll_page(textview, up);
1504 if (prefs_common.scroll_halfpage)
1505 page_incr = text->vadj->page_increment / 2;
1507 page_incr = text->vadj->page_increment;
1510 upper = text->vadj->upper - text->vadj->page_size;
1511 if (text->vadj->value < upper) {
1512 text->vadj->value += page_incr;
1513 text->vadj->value = MIN(text->vadj->value, upper);
1514 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1519 if (text->vadj->value > 0.0) {
1520 text->vadj->value -= page_incr;
1521 text->vadj->value = MAX(text->vadj->value, 0.0);
1522 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1531 static void textview_smooth_scroll_do(TextView *textview,
1532 gfloat old_value, gfloat last_value,
1535 GtkSText *text = GTK_STEXT(textview->text);
1540 if (old_value < last_value) {
1541 change_value = last_value - old_value;
1544 change_value = old_value - last_value;
1548 gdk_key_repeat_disable();
1550 for (i = step; i <= change_value; i += step) {
1551 text->vadj->value = old_value + (up ? -i : i);
1552 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj),
1556 text->vadj->value = last_value;
1557 gtk_signal_emit_by_name(GTK_OBJECT(text->vadj), "value_changed");
1559 gdk_key_repeat_restore();
1562 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1564 GtkSText *text = GTK_STEXT(textview->text);
1570 upper = text->vadj->upper - text->vadj->page_size;
1571 if (text->vadj->value < upper) {
1572 old_value = text->vadj->value;
1573 last_value = text->vadj->value +
1574 text->vadj->step_increment * 4;
1575 last_value = MIN(last_value, upper);
1577 textview_smooth_scroll_do(textview, old_value,
1579 prefs_common.scroll_step);
1582 if (text->vadj->value > 0.0) {
1583 old_value = text->vadj->value;
1584 last_value = text->vadj->value -
1585 text->vadj->step_increment * 4;
1586 last_value = MAX(last_value, 0.0);
1588 textview_smooth_scroll_do(textview, old_value,
1590 prefs_common.scroll_step);
1595 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1597 GtkSText *text = GTK_STEXT(textview->text);
1603 if (prefs_common.scroll_halfpage)
1604 page_incr = text->vadj->page_increment / 2;
1606 page_incr = text->vadj->page_increment;
1609 upper = text->vadj->upper - text->vadj->page_size;
1610 if (text->vadj->value < upper) {
1611 old_value = text->vadj->value;
1612 last_value = text->vadj->value + page_incr;
1613 last_value = MIN(last_value, upper);
1615 textview_smooth_scroll_do(textview, old_value,
1617 prefs_common.scroll_step);
1621 if (text->vadj->value > 0.0) {
1622 old_value = text->vadj->value;
1623 last_value = text->vadj->value - page_incr;
1624 last_value = MAX(last_value, 0.0);
1626 textview_smooth_scroll_do(textview, old_value,
1628 prefs_common.scroll_step);
1636 #define KEY_PRESS_EVENT_STOP() \
1637 if (gtk_signal_n_emissions_by_name \
1638 (GTK_OBJECT(widget), "key_press_event") > 0) { \
1639 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), \
1640 "key_press_event"); \
1643 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1646 SummaryView *summaryview = NULL;
1647 MessageView *messageview = textview->messageview;
1649 if (!event) return FALSE;
1650 if (messageview->mainwin)
1651 summaryview = messageview->mainwin->summaryview;
1653 switch (event->keyval) {
1668 summary_pass_key_press_event(summaryview, event);
1670 textview_scroll_page(textview, FALSE);
1673 textview_scroll_page(textview, TRUE);
1676 textview_scroll_one_line(textview,
1677 (event->state & GDK_MOD1_MASK) != 0);
1681 summary_pass_key_press_event(summaryview, event);
1690 KEY_PRESS_EVENT_STOP();
1691 mimeview_pass_key_press_event(messageview->mimeview,
1696 event->window != messageview->mainwin->window->window) {
1697 GdkEventKey tmpev = *event;
1699 tmpev.window = messageview->mainwin->window->window;
1700 KEY_PRESS_EVENT_STOP();
1701 gtk_widget_event(messageview->mainwin->window,
1702 (GdkEvent *)&tmpev);
1710 static gint show_url_timeout_cb(gpointer data)
1712 TextView *textview = (TextView *)data;
1714 if (textview->messageview->mainwin)
1715 if (textview->show_url_msgid)
1716 gtk_statusbar_remove(GTK_STATUSBAR(
1717 textview->messageview->mainwin->statusbar),
1718 textview->messageview->mainwin->folderview_cid,
1719 textview->show_url_msgid);
1723 static gint textview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1727 textview->last_buttonpress = event->type;
1731 static gint textview_button_released(GtkWidget *widget, GdkEventButton *event,
1735 gtk_editable_get_position(GTK_EDITABLE(textview->text));
1738 ((event->button == 1)
1739 || event->button == 2 || event->button == 3)) {
1742 /* double click seems to set the cursor after the current
1743 * word. The cursor position needs fixing, otherwise the
1744 * last word of a clickable zone will not work */
1745 if (event->button == 1 && textview->last_buttonpress == GDK_2BUTTON_PRESS) {
1746 textview->cur_pos--;
1749 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1750 RemoteURI *uri = (RemoteURI *)cur->data;
1752 if (textview->cur_pos >= uri->start &&
1753 textview->cur_pos <= uri->end) {
1756 trimmed_uri = trim_string(uri->uri, 60);
1757 /* single click: display url in statusbar */
1758 if (event->button == 1 && textview->last_buttonpress != GDK_2BUTTON_PRESS) {
1759 if (textview->messageview->mainwin) {
1760 if (textview->show_url_msgid) {
1761 gtk_timeout_remove(textview->show_url_timeout_tag);
1762 gtk_statusbar_remove(GTK_STATUSBAR(
1763 textview->messageview->mainwin->statusbar),
1764 textview->messageview->mainwin->folderview_cid,
1765 textview->show_url_msgid);
1766 textview->show_url_msgid = 0;
1768 textview->show_url_msgid = gtk_statusbar_push(
1769 GTK_STATUSBAR(textview->messageview->mainwin->statusbar),
1770 textview->messageview->mainwin->folderview_cid,
1772 textview->show_url_timeout_tag = gtk_timeout_add( 4000, show_url_timeout_cb, textview );
1773 gtkut_widget_wait_for_draw(textview->messageview->mainwin->hbox_stat);
1776 if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1777 if (event->button == 3) {
1778 gchar *fromname, *fromaddress;
1781 fromaddress = g_strdup(uri->uri + 7);
1782 /* Hiroyuki: please put this function in utils.c! */
1783 fromname = procheader_get_fromname(fromaddress);
1784 extract_address(fromaddress);
1785 g_message("adding from textview %s <%s>", fromname, fromaddress);
1786 /* Add to address book - Match */
1787 addressbook_add_contact( fromname, fromaddress, NULL );
1789 g_free(fromaddress);
1792 PrefsAccount *account = NULL;
1793 FolderItem *folder_item;
1795 if (textview->messageview && textview->messageview->mainwin
1796 && textview->messageview->mainwin->summaryview
1797 && textview->messageview->mainwin->summaryview->folder_item) {
1798 folder_item = textview->messageview->mainwin->summaryview->folder_item;
1799 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1800 account = account_find_from_id(folder_item->prefs->default_account);
1802 compose_new(account, uri->uri + 7, NULL);
1806 prefs_common.uri_cmd);
1808 g_free(trimmed_uri);
1813 textview->last_buttonpress = event->type;
1817 static void textview_uri_list_remove_all(GSList *uri_list)
1821 for (cur = uri_list; cur != NULL; cur = cur->next) {
1823 g_free(((RemoteURI *)cur->data)->uri);
1828 g_slist_free(uri_list);