2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2004 Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 #include <gdk/gdkkeysyms.h>
29 #include <gtk/gtkvbox.h>
30 #include <gtk/gtkscrolledwindow.h>
31 #include <gtk/gtksignal.h>
39 #include "summaryview.h"
40 #include "procheader.h"
41 #include "prefs_common.h"
49 #include "addressbook.h"
50 #include "displayheader.h"
53 #include "alertpanel.h"
63 static GdkColor quote_colors[3] = {
64 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
65 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
66 {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
69 static GdkColor signature_color = {
76 static GdkColor uri_color = {
83 static GdkColor emphasis_color = {
91 static GdkColor error_color = {
100 static GdkCursor *hand_cursor = NULL;
101 static GdkCursor *text_cursor = NULL;
103 #define TEXTVIEW_STATUSBAR_PUSH(textview, str) \
105 gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
106 textview->messageview->statusbar_cid, str); \
109 #define TEXTVIEW_STATUSBAR_POP(textview) \
111 gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
112 textview->messageview->statusbar_cid); \
115 static void textview_show_ertf (TextView *textview,
117 CodeConverter *conv);
118 static void textview_add_part (TextView *textview,
120 static void textview_add_parts (TextView *textview,
122 static void textview_write_body (TextView *textview,
124 static void textview_show_html (TextView *textview,
126 CodeConverter *conv);
128 static void textview_write_line (TextView *textview,
130 CodeConverter *conv);
131 static void textview_write_link (TextView *textview,
134 CodeConverter *conv);
136 static GPtrArray *textview_scan_header (TextView *textview,
138 static void textview_show_header (TextView *textview,
141 static gint textview_key_pressed (GtkWidget *widget,
144 static gboolean textview_motion_notify (GtkWidget *widget,
145 GdkEventMotion *motion,
147 static gboolean textview_leave_notify (GtkWidget *widget,
148 GdkEventCrossing *event,
150 static gboolean textview_visibility_notify (GtkWidget *widget,
151 GdkEventVisibility *event,
153 static void textview_uri_update (TextView *textview,
156 static gboolean textview_get_uri_range (TextView *textview,
159 GtkTextIter *start_iter,
160 GtkTextIter *end_iter);
161 static RemoteURI *textview_get_uri_from_range (TextView *textview,
164 GtkTextIter *start_iter,
165 GtkTextIter *end_iter);
166 static RemoteURI *textview_get_uri (TextView *textview,
169 static gboolean textview_uri_button_pressed (GtkTextTag *tag,
175 static void textview_smooth_scroll_do (TextView *textview,
179 static void textview_smooth_scroll_one_line (TextView *textview,
181 static gboolean textview_smooth_scroll_page (TextView *textview,
184 static gboolean textview_uri_security_check (TextView *textview,
186 static void textview_uri_list_remove_all (GSList *uri_list);
189 static void populate_popup(GtkTextView *textview, GtkMenu *menu,
192 gtk_menu_detach(menu);
195 TextView *textview_create(void)
199 GtkWidget *scrolledwin;
201 GtkTextBuffer *buffer;
202 GtkClipboard *clipboard;
203 PangoFontDescription *font_desc = NULL;
205 debug_print("Creating text view...\n");
206 textview = g_new0(TextView, 1);
208 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
209 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
210 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
211 gtk_widget_set_size_request(scrolledwin, prefs_common.mainview_width, -1);
212 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
215 /* create GtkSText widgets for single-byte and multi-byte character */
216 text = gtk_text_view_new();
217 gtk_widget_add_events(text, GDK_LEAVE_NOTIFY_MASK);
218 gtk_widget_show(text);
219 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
220 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
221 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
222 g_signal_connect(G_OBJECT(text), "populate-popup",
223 G_CALLBACK(populate_popup), NULL);
226 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
227 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
228 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
230 gtk_widget_ensure_style(text);
232 if (prefs_common.normalfont)
233 font_desc = pango_font_description_from_string
234 (prefs_common.normalfont);
236 gtk_widget_modify_font(text, font_desc);
238 pango_font_description_free(font_desc);
240 gtk_widget_ref(scrolledwin);
242 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
245 hand_cursor = gdk_cursor_new(GDK_HAND2);
247 text_cursor = gdk_cursor_new(GDK_XTERM);
249 g_signal_connect(G_OBJECT(text), "key_press_event",
250 G_CALLBACK(textview_key_pressed),
252 g_signal_connect(G_OBJECT(text), "motion_notify_event",
253 G_CALLBACK(textview_motion_notify),
255 g_signal_connect(G_OBJECT(text), "leave_notify_event",
256 G_CALLBACK(textview_leave_notify),
258 g_signal_connect(G_OBJECT(text), "visibility_notify_event",
259 G_CALLBACK(textview_visibility_notify),
262 gtk_widget_show(scrolledwin);
264 vbox = gtk_vbox_new(FALSE, 0);
265 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
267 gtk_widget_show(vbox);
269 textview->vbox = vbox;
270 textview->scrolledwin = scrolledwin;
271 textview->text = text;
272 textview->uri_list = NULL;
273 textview->body_pos = 0;
274 textview->show_all_headers = FALSE;
275 textview->last_buttonpress = GDK_NOTHING;
280 static void textview_create_tags(GtkTextView *text, TextView *textview)
282 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
285 gtk_text_buffer_create_tag(buffer, "header",
286 "pixels-above-lines", 0,
287 "pixels-above-lines-set", TRUE,
288 "pixels-below-lines", 0,
289 "pixels-below-lines-set", TRUE,
291 "left-margin-set", TRUE,
293 gtk_text_buffer_create_tag(buffer, "header_title",
294 "font", prefs_common.boldfont,
296 gtk_text_buffer_create_tag(buffer, "quote0",
297 "foreground-gdk", "e_colors[0],
299 gtk_text_buffer_create_tag(buffer, "quote1",
300 "foreground-gdk", "e_colors[1],
302 gtk_text_buffer_create_tag(buffer, "quote2",
303 "foreground-gdk", "e_colors[2],
305 gtk_text_buffer_create_tag(buffer, "emphasis",
306 "foreground-gdk", &emphasis_color,
308 gtk_text_buffer_create_tag(buffer, "signature",
309 "foreground-gdk", &signature_color,
311 tag = gtk_text_buffer_create_tag(buffer, "link",
312 "foreground-gdk", &uri_color,
314 gtk_text_buffer_create_tag(buffer, "link-hover",
315 "underline", PANGO_UNDERLINE_SINGLE,
318 g_signal_connect(G_OBJECT(tag), "event",
319 G_CALLBACK(textview_uri_button_pressed), textview);
322 void textview_init(TextView *textview)
324 gtkut_widget_disable_theme_engine(textview->text);
325 textview_update_message_colors();
326 textview_set_all_headers(textview, FALSE);
327 textview_set_font(textview, NULL);
329 textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
332 void textview_update_message_colors(void)
334 GdkColor black = {0, 0, 0, 0};
336 if (prefs_common.enable_color) {
337 /* grab the quote colors, converting from an int to a GdkColor */
338 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
340 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
342 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
344 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
346 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
349 quote_colors[0] = quote_colors[1] = quote_colors[2] =
350 uri_color = emphasis_color = signature_color = black;
354 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
359 if ((fp = fopen(file, "rb")) == NULL) {
360 FILE_OP_ERROR(file, "fopen");
364 textview_clear(textview);
366 textview_add_parts(textview, mimeinfo);
370 textview_set_position(textview, 0);
373 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
377 g_return_if_fail(mimeinfo != NULL);
378 g_return_if_fail(fp != NULL);
380 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
381 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822"))) {
382 textview_clear(textview);
383 textview_add_parts(textview, mimeinfo);
387 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
390 textview_clear(textview);
392 if (mimeinfo->type == MIMETYPE_MULTIPART)
393 textview_add_parts(textview, mimeinfo);
395 textview_write_body(textview, mimeinfo);
399 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
402 GtkTextBuffer *buffer;
405 GPtrArray *headers = NULL;
410 g_return_if_fail(mimeinfo != NULL);
411 text = GTK_TEXT_VIEW(textview->text);
412 buffer = gtk_text_view_get_buffer(text);
413 charcount = gtk_text_buffer_get_char_count(buffer);
414 gtk_text_buffer_get_end_iter(buffer, &iter);
416 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
418 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
421 fp = fopen(mimeinfo->data.filename, "rb");
422 fseek(fp, mimeinfo->offset, SEEK_SET);
423 headers = textview_scan_header(textview, fp);
426 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
427 textview_show_header(textview, headers);
428 procheader_header_array_destroy(headers);
434 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
435 content_type = procmime_get_content_type_str(mimeinfo->type,
438 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
440 g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n",
441 name, content_type, mimeinfo->length);
443 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
444 content_type, mimeinfo->length);
446 g_free(content_type);
448 if (mimeinfo->type != MIMETYPE_TEXT) {
449 gtk_text_buffer_insert(buffer, &iter, buf, -1);
450 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
451 if (prefs_common.display_header && (charcount > 0))
452 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
454 textview_write_body(textview, mimeinfo);
458 static void recursive_add_parts(TextView *textview, GNode *node)
463 mimeinfo = (MimeInfo *) node->data;
465 textview_add_part(textview, mimeinfo);
467 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
468 (mimeinfo->type != MIMETYPE_MESSAGE))
471 if (g_ascii_strcasecmp(mimeinfo->subtype, "alternative") == 0) {
472 GNode * prefered_body;
480 prefered_body = NULL;
483 for(iter = g_node_first_child(node) ; iter != NULL ;
484 iter = g_node_next_sibling(iter)) {
489 submime = (MimeInfo *) iter->data;
490 if (submime->type == MIMETYPE_TEXT)
493 if (submime->subtype != NULL) {
494 if (g_ascii_strcasecmp(submime->subtype, "plain") == 0)
498 if (score > prefered_score) {
499 prefered_score = score;
500 prefered_body = iter;
504 if (prefered_body != NULL) {
505 recursive_add_parts(textview, prefered_body);
509 for(iter = g_node_first_child(node) ; iter != NULL ;
510 iter = g_node_next_sibling(iter)) {
511 recursive_add_parts(textview, iter);
516 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
518 g_return_if_fail(mimeinfo != NULL);
520 recursive_add_parts(textview, mimeinfo->node);
523 #define TEXT_INSERT(str) \
524 gtk_text_buffer_insert(buffer, &iter, str, -1)
526 void textview_show_error(TextView *textview)
529 GtkTextBuffer *buffer;
532 textview_set_font(textview, NULL);
533 textview_clear(textview);
535 text = GTK_TEXT_VIEW(textview->text);
536 buffer = gtk_text_view_get_buffer(text);
537 gtk_text_buffer_get_start_iter(buffer, &iter);
539 TEXT_INSERT(_("This message can't be displayed.\n"));
543 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
546 GtkTextBuffer *buffer;
549 if (!partinfo) return;
551 textview_set_font(textview, NULL);
552 textview_clear(textview);
554 text = GTK_TEXT_VIEW(textview->text);
555 buffer = gtk_text_view_get_buffer(text);
556 gtk_text_buffer_get_start_iter(buffer, &iter);
558 TEXT_INSERT(_("The following can be performed on this part by "));
559 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
561 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
562 TEXT_INSERT(_(" To display as text select 'Display as text' "));
563 TEXT_INSERT(_("(Shortcut key: 't')\n"));
564 TEXT_INSERT(_(" To open with an external program select 'Open' "));
565 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
566 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
567 TEXT_INSERT(_("mouse button),\n"));
568 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
574 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
579 const gchar *charset;
581 if (textview->messageview->forced_charset)
582 charset = textview->messageview->forced_charset;
584 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
586 textview_set_font(textview, charset);
588 conv = conv_code_converter_new(charset);
590 procmime_force_encoding(textview->messageview->forced_encoding);
592 textview->is_in_signature = FALSE;
594 procmime_decode_content(mimeinfo);
596 if (!g_ascii_strcasecmp(mimeinfo->subtype, "html")) {
599 filename = procmime_get_tmp_file_name(mimeinfo);
600 if (procmime_get_part(filename, mimeinfo) == 0) {
601 tmpfp = fopen(filename, "rb");
602 textview_show_html(textview, tmpfp, conv);
607 } else if (!g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
610 filename = procmime_get_tmp_file_name(mimeinfo);
611 if (procmime_get_part(filename, mimeinfo) == 0) {
612 tmpfp = fopen(filename, "rb");
613 textview_show_ertf(textview, tmpfp, conv);
619 tmpfp = fopen(mimeinfo->data.filename, "rb");
620 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
621 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
622 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
623 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
624 textview_write_line(textview, buf, conv);
628 conv_code_converter_destroy(conv);
629 procmime_force_encoding(0);
632 static void textview_show_html(TextView *textview, FILE *fp,
638 parser = html_parser_new(fp, conv);
639 g_return_if_fail(parser != NULL);
641 while ((str = html_parse(parser)) != NULL) {
642 if (parser->state == HTML_HREF) {
643 /* first time : get and copy the URL */
644 if (parser->href == NULL) {
645 /* ALF - the sylpheed html parser returns an empty string,
646 * if still inside an <a>, but already parsed past HREF */
647 str = strtok(str, " ");
649 parser->href = g_strdup(str);
650 /* the URL may (or not) be followed by the
652 str = strtok(NULL, "");
656 textview_write_link(textview, str, parser->href, NULL);
658 textview_write_line(textview, str, NULL);
660 html_parser_destroy(parser);
663 static void textview_show_ertf(TextView *textview, FILE *fp,
669 parser = ertf_parser_new(fp, conv);
670 g_return_if_fail(parser != NULL);
672 while ((str = ertf_parse(parser)) != NULL) {
673 textview_write_line(textview, str, NULL);
676 ertf_parser_destroy(parser);
679 /* get_uri_part() - retrieves a URI starting from scanpos.
680 Returns TRUE if succesful */
681 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
682 const gchar **bp, const gchar **ep)
686 g_return_val_if_fail(start != NULL, FALSE);
687 g_return_val_if_fail(scanpos != NULL, FALSE);
688 g_return_val_if_fail(bp != NULL, FALSE);
689 g_return_val_if_fail(ep != NULL, FALSE);
693 /* find end point of URI */
694 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
695 if (!isgraph(*(const guchar *)ep_) ||
696 !IS_ASCII(*(const guchar *)ep_) ||
697 strchr("()<>\"", *ep_))
701 /* no punctuation at end of string */
703 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
704 * should pass some URI type to this function and decide on that whether
705 * to perform punctuation stripping */
707 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
709 for (; ep_ - 1 > scanpos + 1 &&
710 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
721 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
723 return g_strndup(bp, ep - bp);
726 /* valid mail address characters */
727 #define IS_RFC822_CHAR(ch) \
732 !strchr("(),;<>\"", (ch)))
734 /* alphabet and number within 7bit ASCII */
735 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && isalnum(ch))
736 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
738 static GHashTable *create_domain_tab(void)
740 static const gchar *toplvl_domains [] = {
742 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
743 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
744 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
745 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
746 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
747 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
748 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
749 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
750 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
751 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
752 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
753 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
754 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
755 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
756 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
757 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
758 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
759 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
760 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
761 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
762 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
763 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
764 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
765 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
766 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
767 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
770 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
772 g_return_val_if_fail(htab, NULL);
773 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
774 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
778 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
780 const gint MAX_LVL_DOM_NAME_LEN = 6;
781 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
782 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
785 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
788 for (p = buf; p < m && first < last; *p++ = *first++)
792 return g_hash_table_lookup(tab, buf) != NULL;
795 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
796 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
797 const gchar **bp, const gchar **ep)
799 /* more complex than the uri part because we need to scan back and forward starting from
800 * the scan position. */
801 gboolean result = FALSE;
802 const gchar *bp_ = NULL;
803 const gchar *ep_ = NULL;
804 static GHashTable *dom_tab;
805 const gchar *last_dot = NULL;
806 const gchar *prelast_dot = NULL;
807 const gchar *last_tld_char = NULL;
809 /* the informative part of the email address (describing the name
810 * of the email address owner) may contain quoted parts. the
811 * closure stack stores the last encountered quotes. */
812 gchar closure_stack[128];
813 gchar *ptr = closure_stack;
815 g_return_val_if_fail(start != NULL, FALSE);
816 g_return_val_if_fail(scanpos != NULL, FALSE);
817 g_return_val_if_fail(bp != NULL, FALSE);
818 g_return_val_if_fail(ep != NULL, FALSE);
821 dom_tab = create_domain_tab();
822 g_return_val_if_fail(dom_tab, FALSE);
824 /* scan start of address */
825 for (bp_ = scanpos - 1;
826 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
829 /* TODO: should start with an alnum? */
831 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
834 if (bp_ != scanpos) {
835 /* scan end of address */
836 for (ep_ = scanpos + 1;
837 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
839 prelast_dot = last_dot;
841 if (*(last_dot + 1) == '.') {
842 if (prelast_dot == NULL)
844 last_dot = prelast_dot;
849 /* TODO: really should terminate with an alnum? */
850 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
855 if (last_dot == NULL)
858 last_dot = prelast_dot;
859 if (last_dot == NULL || (scanpos + 1 >= last_dot))
863 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
864 if (*last_tld_char == '?')
867 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
874 if (!result) return FALSE;
876 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
877 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
880 /* see if this is <bracketed>; in this case we also scan for the informative part. */
881 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
884 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
885 #define IN_STACK() (ptr > closure_stack)
886 /* has underrun check */
887 #define POP_STACK() if(IN_STACK()) --ptr
888 /* has overrun check */
889 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
890 /* has underrun check */
891 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
895 /* scan for the informative part. */
896 for (bp_ -= 2; bp_ >= start; bp_--) {
897 /* if closure on the stack keep scanning */
898 if (PEEK_STACK() == *bp_) {
902 if (*bp_ == '\'' || *bp_ == '"') {
907 /* if nothing in the closure stack, do the special conditions
908 * the following if..else expression simply checks whether
909 * a token is acceptable. if not acceptable, the clause
910 * should terminate the loop with a 'break' */
913 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
914 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
915 /* hyphens are allowed, but only in
917 } else if (!ispunct(*bp_)) {
918 /* but anything not being a punctiation
921 break; /* anything else is rejected */
934 /* scan forward (should start with an alnum) */
935 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
945 #undef IS_ASCII_ALNUM
946 #undef IS_RFC822_CHAR
948 static gchar *make_email_string(const gchar *bp, const gchar *ep)
950 /* returns a mailto: URI; mailto: is also used to detect the
951 * uri type later on in the button_pressed signal handler */
955 tmp = g_strndup(bp, ep - bp);
956 result = g_strconcat("mailto:", tmp, NULL);
962 static gchar *make_http_string(const gchar *bp, const gchar *ep)
964 /* returns an http: URI; */
968 tmp = g_strndup(bp, ep - bp);
969 result = g_strconcat("http://", tmp, NULL);
975 #define ADD_TXT_POS(bp_, ep_, pti_) \
976 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
978 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
981 g_warning("alloc error scanning URIs\n"); \
982 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
988 /* textview_make_clickable_parts() - colorizes clickable parts */
989 static void textview_make_clickable_parts(TextView *textview,
991 const gchar *uri_tag,
992 const gchar *linebuf)
994 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
995 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
998 /* parse table - in order of priority */
1000 const gchar *needle; /* token */
1002 /* token search function */
1003 gchar *(*search) (const gchar *haystack,
1004 const gchar *needle);
1005 /* part parsing function */
1006 gboolean (*parse) (const gchar *start,
1007 const gchar *scanpos,
1010 /* part to URI function */
1011 gchar *(*build_uri) (const gchar *bp,
1015 static struct table parser[] = {
1016 {"http://", strcasestr, get_uri_part, make_uri_string},
1017 {"https://", strcasestr, get_uri_part, make_uri_string},
1018 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1019 {"www.", strcasestr, get_uri_part, make_http_string},
1020 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1021 {"@", strcasestr, get_email_part, make_email_string}
1023 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1026 const gchar *walk, *bp, *ep;
1029 const gchar *bp, *ep; /* text position */
1030 gint pti; /* index in parse table */
1031 struct txtpos *next; /* next */
1032 } head = {NULL, NULL, 0, NULL}, *last = &head;
1034 gtk_text_buffer_get_end_iter(buffer, &iter);
1036 /* parse for clickable parts, and build a list of begin and end positions */
1037 for (walk = linebuf, n = 0;;) {
1038 gint last_index = PARSE_ELEMS;
1039 gchar *scanpos = NULL;
1041 /* FIXME: this looks phony. scanning for anything in the parse table */
1042 for (n = 0; n < PARSE_ELEMS; n++) {
1045 tmp = parser[n].search(walk, parser[n].needle);
1047 if (scanpos == NULL || tmp < scanpos) {
1055 /* check if URI can be parsed */
1056 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1057 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1058 ADD_TXT_POS(bp, ep, last_index);
1062 strlen(parser[last_index].needle);
1067 /* colorize this line */
1069 const gchar *normal_text = linebuf;
1072 for (last = head.next; last != NULL;
1073 normal_text = last->ep, last = last->next) {
1076 uri = g_new(RemoteURI, 1);
1077 if (last->bp - normal_text > 0)
1078 gtk_text_buffer_insert_with_tags_by_name
1081 last->bp - normal_text,
1083 uri->uri = parser[last->pti].build_uri(last->bp,
1085 uri->start = gtk_text_iter_get_offset(&iter);
1086 gtk_text_buffer_insert_with_tags_by_name
1088 last->bp, last->ep - last->bp,
1090 uri->end = gtk_text_iter_get_offset(&iter);
1091 textview->uri_list =
1092 g_slist_append(textview->uri_list, uri);
1096 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1100 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1108 static void textview_write_line(TextView *textview, const gchar *str,
1109 CodeConverter *conv)
1112 GtkTextBuffer *buffer;
1114 gchar buf[BUFFSIZE];
1116 gint quotelevel = -1;
1117 gchar quote_tag_str[10];
1119 text = GTK_TEXT_VIEW(textview->text);
1120 buffer = gtk_text_view_get_buffer(text);
1121 gtk_text_buffer_get_end_iter(buffer, &iter);
1124 strncpy2(buf, str, sizeof(buf));
1125 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
1126 conv_localetodisp(buf, sizeof(buf), str);
1130 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1133 /* change color of quotation
1134 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1135 Up to 3 levels of quotations are detected, and each
1136 level is colored using a different color. */
1137 if (prefs_common.enable_color
1138 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1139 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1141 /* set up the correct foreground color */
1142 if (quotelevel > 2) {
1143 /* recycle colors */
1144 if (prefs_common.recycle_quote_colors)
1151 if (quotelevel == -1)
1154 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1155 "quote%d", quotelevel);
1156 fg_color = quote_tag_str;
1159 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1160 fg_color = "signature";
1161 textview->is_in_signature = TRUE;
1164 if (prefs_common.enable_color)
1165 textview_make_clickable_parts(textview, fg_color, "link", buf);
1167 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1170 void textview_write_link(TextView *textview, const gchar *str,
1171 const gchar *uri, CodeConverter *conv)
1173 GdkColor *link_color = NULL;
1175 GtkTextBuffer *buffer;
1177 gchar buf[BUFFSIZE];
1184 text = GTK_TEXT_VIEW(textview->text);
1185 buffer = gtk_text_view_get_buffer(text);
1186 gtk_text_buffer_get_end_iter(buffer, &iter);
1191 if (textview->text_is_mb)
1192 conv_localetodisp(buf, sizeof(buf), str);
1194 strncpy2(buf, str, sizeof(buf));
1195 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1196 conv_localetodisp(buf, sizeof(buf), str);
1197 else if (textview->text_is_mb)
1198 conv_unreadable_locale(buf);
1201 strncpy2(buf, str, sizeof(buf));
1202 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1203 conv_localetodisp(buf, sizeof(buf), str);
1208 gtk_text_buffer_get_end_iter(buffer, &iter);
1210 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1211 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1213 if (prefs_common.enable_color) {
1214 link_color = &uri_color;
1216 r_uri = g_new(RemoteURI, 1);
1217 r_uri->uri = g_strdup(uri);
1218 r_uri->start = gtk_text_iter_get_offset(&iter);
1219 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, bufp, -1,
1221 r_uri->end = gtk_text_iter_get_offset(&iter);
1222 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1225 void textview_clear(TextView *textview)
1227 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1228 GtkTextBuffer *buffer;
1230 buffer = gtk_text_view_get_buffer(text);
1231 gtk_text_buffer_set_text(buffer, "\0", -1);
1233 TEXTVIEW_STATUSBAR_POP(textview);
1234 textview_uri_list_remove_all(textview->uri_list);
1235 textview->uri_list = NULL;
1237 textview->body_pos = 0;
1240 void textview_destroy(TextView *textview)
1242 textview_uri_list_remove_all(textview->uri_list);
1243 textview->uri_list = NULL;
1248 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1250 textview->show_all_headers = all_headers;
1253 void textview_set_font(TextView *textview, const gchar *codeset)
1255 if (prefs_common.textfont) {
1256 PangoFontDescription *font_desc = NULL;
1258 if (prefs_common.textfont)
1259 font_desc = pango_font_description_from_string
1260 (prefs_common.textfont);
1262 gtk_widget_modify_font(textview->text, font_desc);
1263 pango_font_description_free(font_desc);
1266 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1267 prefs_common.line_space / 2);
1268 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1269 prefs_common.line_space / 2);
1270 if (prefs_common.head_space) {
1271 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 6);
1273 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 0);
1277 void textview_set_text(TextView *textview, const gchar *text)
1280 GtkTextBuffer *buffer;
1282 g_return_if_fail(textview != NULL);
1283 g_return_if_fail(text != NULL);
1285 textview_clear(textview);
1287 view = GTK_TEXT_VIEW(textview->text);
1288 buffer = gtk_text_view_get_buffer(view);
1289 gtk_text_buffer_set_text(buffer, text, strlen(text));
1305 H_ORGANIZATION = 11,
1308 void textview_set_position(TextView *textview, gint pos)
1310 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1311 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1314 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1315 gtk_text_buffer_place_cursor(buffer, &iter);
1316 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1319 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1321 gchar buf[BUFFSIZE];
1322 GPtrArray *headers, *sorted_headers;
1323 GSList *disphdr_list;
1327 g_return_val_if_fail(fp != NULL, NULL);
1329 if (textview->show_all_headers)
1330 return procheader_get_header_array_asis(fp);
1332 if (!prefs_common.display_header) {
1333 while (fgets(buf, sizeof(buf), fp) != NULL)
1334 if (buf[0] == '\r' || buf[0] == '\n') break;
1338 headers = procheader_get_header_array_asis(fp);
1340 sorted_headers = g_ptr_array_new();
1342 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1343 disphdr_list = disphdr_list->next) {
1344 DisplayHeaderProp *dp =
1345 (DisplayHeaderProp *)disphdr_list->data;
1347 for (i = 0; i < headers->len; i++) {
1348 header = g_ptr_array_index(headers, i);
1350 if (procheader_headername_equal(header->name,
1353 procheader_header_free(header);
1355 g_ptr_array_add(sorted_headers, header);
1357 g_ptr_array_remove_index(headers, i);
1363 if (prefs_common.show_other_header) {
1364 for (i = 0; i < headers->len; i++) {
1365 header = g_ptr_array_index(headers, i);
1366 g_ptr_array_add(sorted_headers, header);
1368 g_ptr_array_free(headers, TRUE);
1370 procheader_header_array_destroy(headers);
1373 return sorted_headers;
1376 static void textview_show_header(TextView *textview, GPtrArray *headers)
1378 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1379 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1384 g_return_if_fail(headers != NULL);
1386 for (i = 0; i < headers->len; i++) {
1387 header = g_ptr_array_index(headers, i);
1388 g_return_if_fail(header->name != NULL);
1390 gtk_text_buffer_get_end_iter (buffer, &iter);
1391 gtk_text_buffer_insert_with_tags_by_name
1392 (buffer, &iter, header->name, -1,
1393 "header_title", "header", NULL);
1394 if (header->name[strlen(header->name) - 1] != ' ')
1395 gtk_text_buffer_insert_with_tags_by_name
1396 (buffer, &iter, " ", 1,
1397 "header_title", "header", NULL);
1399 if (procheader_headername_equal(header->name, "Subject") ||
1400 procheader_headername_equal(header->name, "From") ||
1401 procheader_headername_equal(header->name, "To") ||
1402 procheader_headername_equal(header->name, "Cc"))
1403 unfold_line(header->body);
1407 if (textview->text_is_mb == TRUE)
1408 conv_unreadable_locale(header->body);
1411 if (prefs_common.enable_color &&
1412 (procheader_headername_equal(header->name, "X-Mailer") ||
1413 procheader_headername_equal(header->name,
1415 strstr(header->body, "Sylpheed") != NULL) {
1416 gtk_text_buffer_get_end_iter (buffer, &iter);
1417 gtk_text_buffer_insert_with_tags_by_name
1418 (buffer, &iter, header->body, -1,
1419 "header", "emphasis", NULL);
1420 } else if (prefs_common.enable_color) {
1421 textview_make_clickable_parts(textview, "header", "link",
1424 textview_make_clickable_parts(textview, "header", NULL,
1427 gtk_text_buffer_get_end_iter (buffer, &iter);
1428 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1433 gboolean textview_search_string(TextView *textview, const gchar *str,
1436 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1437 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1439 GtkTextIter iter, start, end, real_end, *pos;
1440 gboolean found = FALSE;
1441 gint insert_offset, selbound_offset;
1443 /* reset selection */
1444 mark = gtk_text_buffer_get_mark(buffer, "insert");
1445 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1446 insert_offset = gtk_text_iter_get_offset(&start);
1447 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1448 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1449 selbound_offset = gtk_text_iter_get_offset(&end);
1451 pos = insert_offset > selbound_offset ? &start : &end;
1452 gtk_text_buffer_place_cursor(buffer, pos);
1455 mark = gtk_text_buffer_get_insert(buffer);
1456 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1458 found = gtk_text_iter_forward_search(&iter, str,
1459 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1460 &start, &end, NULL);
1464 gtk_text_buffer_get_end_iter(buffer, &real_end);
1465 text = gtk_text_buffer_get_text(buffer, &iter,
1468 while (!found && i++ < strlen(text) - 1) {
1469 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1472 i += gtk_text_iter_get_offset(&end);
1475 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1476 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1484 gtk_text_buffer_place_cursor(buffer, &start);
1485 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1487 mark = gtk_text_buffer_get_mark(buffer, "insert");
1488 gtk_text_view_scroll_mark_onscreen(text, mark);
1494 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1497 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1498 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1500 GtkTextIter iter, start, real_start, end, *pos;
1501 gboolean found = FALSE;
1502 gint insert_offset, selbound_offset;
1504 /* reset selection */
1505 mark = gtk_text_buffer_get_mark(buffer, "insert");
1506 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1507 insert_offset = gtk_text_iter_get_offset(&start);
1508 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1509 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1510 selbound_offset = gtk_text_iter_get_offset(&end);
1512 pos = insert_offset < selbound_offset ? &start : &end;
1513 gtk_text_buffer_place_cursor(buffer, pos);
1516 mark = gtk_text_buffer_get_insert(buffer);
1517 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1519 found = gtk_text_iter_backward_search(&iter, str,
1520 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1521 &start, &end, NULL);
1525 if (gtk_text_iter_get_offset(&iter) == 0)
1526 gtk_text_buffer_get_end_iter(buffer, &iter);
1528 i = gtk_text_iter_get_offset(&iter) - strlen(str) - 1;
1529 gtk_text_buffer_get_start_iter(buffer, &real_start);
1531 text = gtk_text_buffer_get_text(buffer, &real_start,
1534 while (!found && i-- > 0) {
1535 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1539 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1540 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1548 gtk_text_buffer_place_cursor(buffer, &end);
1549 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1551 mark = gtk_text_buffer_get_mark(buffer, "insert");
1552 gtk_text_view_scroll_mark_onscreen(text, mark);
1558 void textview_scroll_one_line(TextView *textview, gboolean up)
1560 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1561 GtkAdjustment *vadj = text->vadjustment;
1564 if (prefs_common.enable_smooth_scroll) {
1565 textview_smooth_scroll_one_line(textview, up);
1570 upper = vadj->upper - vadj->page_size;
1571 if (vadj->value < upper) {
1573 vadj->step_increment * 4;
1575 MIN(vadj->value, upper);
1576 g_signal_emit_by_name(G_OBJECT(vadj),
1577 "value_changed", 0);
1580 if (vadj->value > 0.0) {
1582 vadj->step_increment * 4;
1584 MAX(vadj->value, 0.0);
1585 g_signal_emit_by_name(G_OBJECT(vadj),
1586 "value_changed", 0);
1591 gboolean textview_scroll_page(TextView *textview, gboolean up)
1593 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1594 GtkAdjustment *vadj = text->vadjustment;
1598 if (prefs_common.enable_smooth_scroll)
1599 return textview_smooth_scroll_page(textview, up);
1601 if (prefs_common.scroll_halfpage)
1602 page_incr = vadj->page_increment / 2;
1604 page_incr = vadj->page_increment;
1607 upper = vadj->upper - vadj->page_size;
1608 if (vadj->value < upper) {
1609 vadj->value += page_incr;
1610 vadj->value = MIN(vadj->value, upper);
1611 g_signal_emit_by_name(G_OBJECT(vadj),
1612 "value_changed", 0);
1616 if (vadj->value > 0.0) {
1617 vadj->value -= page_incr;
1618 vadj->value = MAX(vadj->value, 0.0);
1619 g_signal_emit_by_name(G_OBJECT(vadj),
1620 "value_changed", 0);
1628 static void textview_smooth_scroll_do(TextView *textview,
1629 gfloat old_value, gfloat last_value,
1632 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1633 GtkAdjustment *vadj = text->vadjustment;
1638 if (old_value < last_value) {
1639 change_value = last_value - old_value;
1642 change_value = old_value - last_value;
1646 for (i = step; i <= change_value; i += step) {
1647 vadj->value = old_value + (up ? -i : i);
1648 g_signal_emit_by_name(G_OBJECT(vadj),
1649 "value_changed", 0);
1652 vadj->value = last_value;
1653 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1656 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1658 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1659 GtkAdjustment *vadj = text->vadjustment;
1665 upper = vadj->upper - vadj->page_size;
1666 if (vadj->value < upper) {
1667 old_value = vadj->value;
1668 last_value = vadj->value +
1669 vadj->step_increment * 4;
1670 last_value = MIN(last_value, upper);
1672 textview_smooth_scroll_do(textview, old_value,
1674 prefs_common.scroll_step);
1677 if (vadj->value > 0.0) {
1678 old_value = vadj->value;
1679 last_value = vadj->value -
1680 vadj->step_increment * 4;
1681 last_value = MAX(last_value, 0.0);
1683 textview_smooth_scroll_do(textview, old_value,
1685 prefs_common.scroll_step);
1690 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1692 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1693 GtkAdjustment *vadj = text->vadjustment;
1699 if (prefs_common.scroll_halfpage)
1700 page_incr = vadj->page_increment / 2;
1702 page_incr = vadj->page_increment;
1705 upper = vadj->upper - vadj->page_size;
1706 if (vadj->value < upper) {
1707 old_value = vadj->value;
1708 last_value = vadj->value + page_incr;
1709 last_value = MIN(last_value, upper);
1711 textview_smooth_scroll_do(textview, old_value,
1713 prefs_common.scroll_step);
1717 if (vadj->value > 0.0) {
1718 old_value = vadj->value;
1719 last_value = vadj->value - page_incr;
1720 last_value = MAX(last_value, 0.0);
1722 textview_smooth_scroll_do(textview, old_value,
1724 prefs_common.scroll_step);
1732 #define KEY_PRESS_EVENT_STOP() \
1733 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1736 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1739 SummaryView *summaryview = NULL;
1740 MessageView *messageview = textview->messageview;
1742 if (!event) return FALSE;
1743 if (messageview->mainwin)
1744 summaryview = messageview->mainwin->summaryview;
1746 switch (event->keyval) {
1761 summary_pass_key_press_event(summaryview, event);
1763 textview_scroll_page(textview, FALSE);
1766 textview_scroll_page(textview, TRUE);
1769 textview_scroll_one_line(textview,
1770 (event->state & GDK_MOD1_MASK) != 0);
1774 summary_pass_key_press_event(summaryview, event);
1779 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1780 KEY_PRESS_EVENT_STOP();
1781 mimeview_pass_key_press_event(messageview->mimeview,
1785 /* possible fall through */
1788 event->window != messageview->mainwin->window->window) {
1789 GdkEventKey tmpev = *event;
1791 tmpev.window = messageview->mainwin->window->window;
1792 KEY_PRESS_EVENT_STOP();
1793 gtk_widget_event(messageview->mainwin->window,
1794 (GdkEvent *)&tmpev);
1803 *\brief Check to see if a web URL has been disguised as a different
1804 * URL (possible with HTML email).
1806 *\param uri The uri to check
1808 *\param textview The TextView the URL is contained in
1810 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1811 * it anyway, otherwise FALSE
1813 static gboolean uri_security_check(RemoteURI *uri, TextView *textview)
1816 gboolean retval = TRUE;
1818 if (g_ascii_strncasecmp(uri->uri, "http:", 5) &&
1819 g_ascii_strncasecmp(uri->uri, "https:", 6) &&
1820 g_ascii_strncasecmp(uri->uri, "www.", 4))
1823 clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1826 if (clicked_str == NULL)
1829 if (strcmp(clicked_str, uri->uri) &&
1830 (!g_ascii_strncasecmp(clicked_str, "http:", 5) ||
1831 !g_ascii_strncasecmp(clicked_str, "https:", 6) ||
1832 !g_ascii_strncasecmp(clicked_str, "www.", 4))) {
1836 /* allow uri->uri == http://somewhere.com
1837 and clicked_str == somewhere.com */
1838 str = g_strconcat("http://", clicked_str, NULL);
1840 if (!g_ascii_strcasecmp(str, uri->uri))
1845 if (retval == FALSE) {
1849 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1850 "the apparent URL (%s). \n"
1852 uri->uri, clicked_str);
1853 resp = alertpanel_with_type(_("Warning"),
1857 NULL, NULL, ALERT_WARNING);
1859 if (resp == G_ALERTDEFAULT)
1862 g_free(clicked_str);
1866 static gboolean textview_motion_notify(GtkWidget *widget,
1867 GdkEventMotion *event,
1870 textview_uri_update(textview, event->x, event->y);
1871 gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1876 static gboolean textview_leave_notify(GtkWidget *widget,
1877 GdkEventCrossing *event,
1880 textview_uri_update(textview, -1, -1);
1885 static gboolean textview_visibility_notify(GtkWidget *widget,
1886 GdkEventVisibility *event,
1891 gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1892 textview_uri_update(textview, wx, wy);
1897 static void textview_uri_update(TextView *textview, gint x, gint y)
1899 GtkTextBuffer *buffer;
1900 GtkTextIter start_iter, end_iter;
1901 RemoteURI *uri = NULL;
1903 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1905 if (x != -1 && y != -1) {
1911 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text),
1912 GTK_TEXT_WINDOW_WIDGET,
1914 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1917 tags = gtk_text_iter_get_tags(&iter);
1918 for (cur = tags; cur != NULL; cur = cur->next) {
1919 GtkTextTag *tag = cur->data;
1922 g_object_get(G_OBJECT(tag), "name", &name, NULL);
1923 if (!strcmp(name, "link")
1924 && textview_get_uri_range(textview, &iter, tag,
1925 &start_iter, &end_iter))
1926 uri = textview_get_uri_from_range(textview,
1938 if (uri != textview->uri_hover) {
1941 if (textview->uri_hover)
1942 gtk_text_buffer_remove_tag_by_name(buffer,
1944 &textview->uri_hover_start_iter,
1945 &textview->uri_hover_end_iter);
1947 textview->uri_hover = uri;
1949 textview->uri_hover_start_iter = start_iter;
1950 textview->uri_hover_end_iter = end_iter;
1953 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1954 GTK_TEXT_WINDOW_TEXT);
1955 gdk_window_set_cursor(window, uri ? hand_cursor : text_cursor);
1957 TEXTVIEW_STATUSBAR_POP(textview);
1962 gtk_text_buffer_apply_tag_by_name(buffer,
1967 trimmed_uri = trim_string(uri->uri, 60);
1968 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1969 g_free(trimmed_uri);
1974 static gboolean textview_get_uri_range(TextView *textview,
1977 GtkTextIter *start_iter,
1978 GtkTextIter *end_iter)
1980 GtkTextIter _start_iter, _end_iter;
1982 _start_iter = *iter;
1983 if(!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
1984 debug_print("Can't find start.");
1989 if(!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
1990 debug_print("Can't find end");
1994 *start_iter = _start_iter;
1995 *end_iter = _end_iter;
2000 static RemoteURI *textview_get_uri_from_range(TextView *textview,
2003 GtkTextIter *start_iter,
2004 GtkTextIter *end_iter)
2006 gint start_pos, end_pos, cur_pos;
2007 RemoteURI *uri = NULL;
2010 start_pos = gtk_text_iter_get_offset(start_iter);
2011 end_pos = gtk_text_iter_get_offset(end_iter);
2012 cur_pos = gtk_text_iter_get_offset(iter);
2014 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
2015 RemoteURI *uri_ = (RemoteURI *)cur->data;
2016 if (start_pos == uri_->start &&
2017 end_pos == uri_->end) {
2020 } else if (start_pos == uri_->start ||
2021 end_pos == uri_->end) {
2022 /* in case of contiguous links, textview_get_uri_range
2023 * returns a broader range (start of 1st link to end
2025 * In that case, correct link is the one covering
2028 if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
2038 static RemoteURI *textview_get_uri(TextView *textview,
2042 GtkTextIter start_iter, end_iter;
2043 RemoteURI *uri = NULL;
2045 if (textview_get_uri_range(textview, iter, tag, &start_iter,
2047 uri = textview_get_uri_from_range(textview, iter, tag,
2048 &start_iter, &end_iter);
2053 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
2054 GdkEvent *event, GtkTextIter *iter,
2057 GdkEventButton *bevent;
2058 RemoteURI *uri = NULL;
2065 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2066 && event->type != GDK_MOTION_NOTIFY)
2069 uri = textview_get_uri(textview, iter, tag);
2073 bevent = (GdkEventButton *) event;
2075 /* doubleclick: open compose / add address / browser */
2076 if ((event->type == GDK_BUTTON_PRESS && bevent->button == 1) ||
2077 bevent->button == 2 || bevent->button == 3) {
2078 if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2079 if (bevent->button == 3) {
2080 gchar *fromname, *fromaddress;
2083 fromaddress = g_strdup(uri->uri + 7);
2084 /* Hiroyuki: please put this function in utils.c! */
2085 fromname = procheader_get_fromname(fromaddress);
2086 extract_address(fromaddress);
2087 g_message("adding from textview %s <%s>", fromname, fromaddress);
2088 /* Add to address book - Match */
2089 addressbook_add_contact( fromname, fromaddress, NULL );
2091 g_free(fromaddress);
2094 PrefsAccount *account = NULL;
2096 if (textview->messageview && textview->messageview->msginfo &&
2097 textview->messageview->msginfo->folder) {
2098 FolderItem *folder_item;
2100 folder_item = textview->messageview->msginfo->folder;
2101 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2102 account = account_find_from_id(folder_item->prefs->default_account);
2104 compose_new(account, uri->uri + 7, NULL);
2108 if (textview_uri_security_check(textview, uri) == TRUE)
2110 prefs_common.uri_cmd);
2119 *\brief Check to see if a web URL has been disguised as a different
2120 * URL (possible with HTML email).
2122 *\param uri The uri to check
2124 *\param textview The TextView the URL is contained in
2126 *\return gboolean TRUE if the URL is ok, or if the user chose to open
2127 * it anyway, otherwise FALSE
2129 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2132 gboolean retval = TRUE;
2134 if (is_uri_string(uri->uri) == FALSE)
2137 visible_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
2138 uri->start, uri->end);
2139 if (visible_str == NULL)
2142 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2144 gchar *visible_uri_path;
2146 uri_path = get_uri_path(uri->uri);
2147 visible_uri_path = get_uri_path(visible_str);
2148 if (strcmp(uri_path, visible_uri_path) != 0)
2152 if (retval == FALSE) {
2156 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2157 "the apparent URL (%s).\n"
2159 uri->uri, visible_str);
2160 aval = alertpanel_with_type(_("Warning"), msg, _("Yes"), _("No"), NULL,
2161 NULL, ALERT_WARNING);
2163 if (aval == G_ALERTDEFAULT)
2167 g_free(visible_str);
2172 static void textview_uri_list_remove_all(GSList *uri_list)
2176 for (cur = uri_list; cur != NULL; cur = cur->next) {
2178 g_free(((RemoteURI *)cur->data)->uri);
2183 g_slist_free(uri_list);