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, *next = NULL;
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 /* fix "colin@colino.net" <colin@colino.net> types of URIs
1077 /* FIXME would be better to fix it in the email parser */
1078 if (next && next->bp == last->ep) {
1079 next->bp = last->bp;
1080 if (*(next->bp -1 )=='"' && strchr(next->bp, '"'))
1084 uri = g_new(RemoteURI, 1);
1085 if (last->bp - normal_text > 0)
1086 gtk_text_buffer_insert_with_tags_by_name
1089 last->bp - normal_text,
1091 uri->uri = parser[last->pti].build_uri(last->bp,
1093 uri->start = gtk_text_iter_get_offset(&iter);
1094 gtk_text_buffer_insert_with_tags_by_name
1096 last->bp, last->ep - last->bp,
1098 uri->end = gtk_text_iter_get_offset(&iter);
1099 textview->uri_list =
1100 g_slist_append(textview->uri_list, uri);
1104 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1108 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1116 static void textview_write_line(TextView *textview, const gchar *str,
1117 CodeConverter *conv)
1120 GtkTextBuffer *buffer;
1122 gchar buf[BUFFSIZE];
1124 gint quotelevel = -1;
1125 gchar quote_tag_str[10];
1127 text = GTK_TEXT_VIEW(textview->text);
1128 buffer = gtk_text_view_get_buffer(text);
1129 gtk_text_buffer_get_end_iter(buffer, &iter);
1132 strncpy2(buf, str, sizeof(buf));
1133 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
1134 conv_localetodisp(buf, sizeof(buf), str);
1138 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1141 /* change color of quotation
1142 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1143 Up to 3 levels of quotations are detected, and each
1144 level is colored using a different color. */
1145 if (prefs_common.enable_color
1146 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1147 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1149 /* set up the correct foreground color */
1150 if (quotelevel > 2) {
1151 /* recycle colors */
1152 if (prefs_common.recycle_quote_colors)
1159 if (quotelevel == -1)
1162 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1163 "quote%d", quotelevel);
1164 fg_color = quote_tag_str;
1167 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1168 fg_color = "signature";
1169 textview->is_in_signature = TRUE;
1172 if (prefs_common.enable_color)
1173 textview_make_clickable_parts(textview, fg_color, "link", buf);
1175 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1178 void textview_write_link(TextView *textview, const gchar *str,
1179 const gchar *uri, CodeConverter *conv)
1181 GdkColor *link_color = NULL;
1183 GtkTextBuffer *buffer;
1185 gchar buf[BUFFSIZE];
1192 text = GTK_TEXT_VIEW(textview->text);
1193 buffer = gtk_text_view_get_buffer(text);
1194 gtk_text_buffer_get_end_iter(buffer, &iter);
1199 if (textview->text_is_mb)
1200 conv_localetodisp(buf, sizeof(buf), str);
1202 strncpy2(buf, str, sizeof(buf));
1203 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1204 conv_localetodisp(buf, sizeof(buf), str);
1205 else if (textview->text_is_mb)
1206 conv_unreadable_locale(buf);
1209 strncpy2(buf, str, sizeof(buf));
1210 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1211 conv_localetodisp(buf, sizeof(buf), str);
1216 gtk_text_buffer_get_end_iter(buffer, &iter);
1218 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1219 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1221 if (prefs_common.enable_color) {
1222 link_color = &uri_color;
1224 r_uri = g_new(RemoteURI, 1);
1225 r_uri->uri = g_strdup(uri);
1226 r_uri->start = gtk_text_iter_get_offset(&iter);
1227 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, bufp, -1,
1229 r_uri->end = gtk_text_iter_get_offset(&iter);
1230 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1233 void textview_clear(TextView *textview)
1235 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1236 GtkTextBuffer *buffer;
1238 buffer = gtk_text_view_get_buffer(text);
1239 gtk_text_buffer_set_text(buffer, "\0", -1);
1241 TEXTVIEW_STATUSBAR_POP(textview);
1242 textview_uri_list_remove_all(textview->uri_list);
1243 textview->uri_list = NULL;
1245 textview->body_pos = 0;
1248 void textview_destroy(TextView *textview)
1250 textview_uri_list_remove_all(textview->uri_list);
1251 textview->uri_list = NULL;
1256 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1258 textview->show_all_headers = all_headers;
1261 void textview_set_font(TextView *textview, const gchar *codeset)
1263 if (prefs_common.textfont) {
1264 PangoFontDescription *font_desc = NULL;
1266 if (prefs_common.textfont)
1267 font_desc = pango_font_description_from_string
1268 (prefs_common.textfont);
1270 gtk_widget_modify_font(textview->text, font_desc);
1271 pango_font_description_free(font_desc);
1274 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1275 prefs_common.line_space / 2);
1276 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1277 prefs_common.line_space / 2);
1278 if (prefs_common.head_space) {
1279 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 6);
1281 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 0);
1285 void textview_set_text(TextView *textview, const gchar *text)
1288 GtkTextBuffer *buffer;
1290 g_return_if_fail(textview != NULL);
1291 g_return_if_fail(text != NULL);
1293 textview_clear(textview);
1295 view = GTK_TEXT_VIEW(textview->text);
1296 buffer = gtk_text_view_get_buffer(view);
1297 gtk_text_buffer_set_text(buffer, text, strlen(text));
1313 H_ORGANIZATION = 11,
1316 void textview_set_position(TextView *textview, gint pos)
1318 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1319 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1322 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1323 gtk_text_buffer_place_cursor(buffer, &iter);
1324 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1327 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1329 gchar buf[BUFFSIZE];
1330 GPtrArray *headers, *sorted_headers;
1331 GSList *disphdr_list;
1335 g_return_val_if_fail(fp != NULL, NULL);
1337 if (textview->show_all_headers)
1338 return procheader_get_header_array_asis(fp);
1340 if (!prefs_common.display_header) {
1341 while (fgets(buf, sizeof(buf), fp) != NULL)
1342 if (buf[0] == '\r' || buf[0] == '\n') break;
1346 headers = procheader_get_header_array_asis(fp);
1348 sorted_headers = g_ptr_array_new();
1350 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1351 disphdr_list = disphdr_list->next) {
1352 DisplayHeaderProp *dp =
1353 (DisplayHeaderProp *)disphdr_list->data;
1355 for (i = 0; i < headers->len; i++) {
1356 header = g_ptr_array_index(headers, i);
1358 if (procheader_headername_equal(header->name,
1361 procheader_header_free(header);
1363 g_ptr_array_add(sorted_headers, header);
1365 g_ptr_array_remove_index(headers, i);
1371 if (prefs_common.show_other_header) {
1372 for (i = 0; i < headers->len; i++) {
1373 header = g_ptr_array_index(headers, i);
1374 g_ptr_array_add(sorted_headers, header);
1376 g_ptr_array_free(headers, TRUE);
1378 procheader_header_array_destroy(headers);
1381 return sorted_headers;
1384 static void textview_show_header(TextView *textview, GPtrArray *headers)
1386 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1387 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1392 g_return_if_fail(headers != NULL);
1394 for (i = 0; i < headers->len; i++) {
1395 header = g_ptr_array_index(headers, i);
1396 g_return_if_fail(header->name != NULL);
1398 gtk_text_buffer_get_end_iter (buffer, &iter);
1399 gtk_text_buffer_insert_with_tags_by_name
1400 (buffer, &iter, header->name, -1,
1401 "header_title", "header", NULL);
1402 if (header->name[strlen(header->name) - 1] != ' ')
1403 gtk_text_buffer_insert_with_tags_by_name
1404 (buffer, &iter, " ", 1,
1405 "header_title", "header", NULL);
1407 if (procheader_headername_equal(header->name, "Subject") ||
1408 procheader_headername_equal(header->name, "From") ||
1409 procheader_headername_equal(header->name, "To") ||
1410 procheader_headername_equal(header->name, "Cc"))
1411 unfold_line(header->body);
1415 if (textview->text_is_mb == TRUE)
1416 conv_unreadable_locale(header->body);
1419 if (prefs_common.enable_color &&
1420 (procheader_headername_equal(header->name, "X-Mailer") ||
1421 procheader_headername_equal(header->name,
1423 strstr(header->body, "Sylpheed") != NULL) {
1424 gtk_text_buffer_get_end_iter (buffer, &iter);
1425 gtk_text_buffer_insert_with_tags_by_name
1426 (buffer, &iter, header->body, -1,
1427 "header", "emphasis", NULL);
1428 } else if (prefs_common.enable_color) {
1429 textview_make_clickable_parts(textview, "header", "link",
1432 textview_make_clickable_parts(textview, "header", NULL,
1435 gtk_text_buffer_get_end_iter (buffer, &iter);
1436 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1441 gboolean textview_search_string(TextView *textview, const gchar *str,
1444 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1445 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1447 GtkTextIter iter, start, end, real_end, *pos;
1448 gboolean found = FALSE;
1449 gint insert_offset, selbound_offset;
1451 /* reset selection */
1452 mark = gtk_text_buffer_get_mark(buffer, "insert");
1453 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1454 insert_offset = gtk_text_iter_get_offset(&start);
1455 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1456 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1457 selbound_offset = gtk_text_iter_get_offset(&end);
1459 pos = insert_offset > selbound_offset ? &start : &end;
1460 gtk_text_buffer_place_cursor(buffer, pos);
1463 mark = gtk_text_buffer_get_insert(buffer);
1464 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1466 found = gtk_text_iter_forward_search(&iter, str,
1467 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1468 &start, &end, NULL);
1472 gtk_text_buffer_get_end_iter(buffer, &real_end);
1473 text = gtk_text_buffer_get_text(buffer, &iter,
1476 while (!found && i++ < strlen(text) - 1) {
1477 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1480 i += gtk_text_iter_get_offset(&end);
1483 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1484 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1492 gtk_text_buffer_place_cursor(buffer, &start);
1493 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1495 mark = gtk_text_buffer_get_mark(buffer, "insert");
1496 gtk_text_view_scroll_mark_onscreen(text, mark);
1502 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1505 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1506 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1508 GtkTextIter iter, start, real_start, end, *pos;
1509 gboolean found = FALSE;
1510 gint insert_offset, selbound_offset;
1512 /* reset selection */
1513 mark = gtk_text_buffer_get_mark(buffer, "insert");
1514 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1515 insert_offset = gtk_text_iter_get_offset(&start);
1516 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1517 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1518 selbound_offset = gtk_text_iter_get_offset(&end);
1520 pos = insert_offset < selbound_offset ? &start : &end;
1521 gtk_text_buffer_place_cursor(buffer, pos);
1524 mark = gtk_text_buffer_get_insert(buffer);
1525 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1527 found = gtk_text_iter_backward_search(&iter, str,
1528 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1529 &start, &end, NULL);
1533 if (gtk_text_iter_get_offset(&iter) == 0)
1534 gtk_text_buffer_get_end_iter(buffer, &iter);
1536 i = gtk_text_iter_get_offset(&iter) - strlen(str) - 1;
1537 gtk_text_buffer_get_start_iter(buffer, &real_start);
1539 text = gtk_text_buffer_get_text(buffer, &real_start,
1542 while (!found && i-- > 0) {
1543 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1547 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1548 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1556 gtk_text_buffer_place_cursor(buffer, &end);
1557 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1559 mark = gtk_text_buffer_get_mark(buffer, "insert");
1560 gtk_text_view_scroll_mark_onscreen(text, mark);
1566 void textview_scroll_one_line(TextView *textview, gboolean up)
1568 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1569 GtkAdjustment *vadj = text->vadjustment;
1572 if (prefs_common.enable_smooth_scroll) {
1573 textview_smooth_scroll_one_line(textview, up);
1578 upper = vadj->upper - vadj->page_size;
1579 if (vadj->value < upper) {
1581 vadj->step_increment * 4;
1583 MIN(vadj->value, upper);
1584 g_signal_emit_by_name(G_OBJECT(vadj),
1585 "value_changed", 0);
1588 if (vadj->value > 0.0) {
1590 vadj->step_increment * 4;
1592 MAX(vadj->value, 0.0);
1593 g_signal_emit_by_name(G_OBJECT(vadj),
1594 "value_changed", 0);
1599 gboolean textview_scroll_page(TextView *textview, gboolean up)
1601 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1602 GtkAdjustment *vadj = text->vadjustment;
1606 if (prefs_common.enable_smooth_scroll)
1607 return textview_smooth_scroll_page(textview, up);
1609 if (prefs_common.scroll_halfpage)
1610 page_incr = vadj->page_increment / 2;
1612 page_incr = vadj->page_increment;
1615 upper = vadj->upper - vadj->page_size;
1616 if (vadj->value < upper) {
1617 vadj->value += page_incr;
1618 vadj->value = MIN(vadj->value, upper);
1619 g_signal_emit_by_name(G_OBJECT(vadj),
1620 "value_changed", 0);
1624 if (vadj->value > 0.0) {
1625 vadj->value -= page_incr;
1626 vadj->value = MAX(vadj->value, 0.0);
1627 g_signal_emit_by_name(G_OBJECT(vadj),
1628 "value_changed", 0);
1636 static void textview_smooth_scroll_do(TextView *textview,
1637 gfloat old_value, gfloat last_value,
1640 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1641 GtkAdjustment *vadj = text->vadjustment;
1646 if (old_value < last_value) {
1647 change_value = last_value - old_value;
1650 change_value = old_value - last_value;
1654 for (i = step; i <= change_value; i += step) {
1655 vadj->value = old_value + (up ? -i : i);
1656 g_signal_emit_by_name(G_OBJECT(vadj),
1657 "value_changed", 0);
1660 vadj->value = last_value;
1661 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1664 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1666 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1667 GtkAdjustment *vadj = text->vadjustment;
1673 upper = vadj->upper - vadj->page_size;
1674 if (vadj->value < upper) {
1675 old_value = vadj->value;
1676 last_value = vadj->value +
1677 vadj->step_increment * 4;
1678 last_value = MIN(last_value, upper);
1680 textview_smooth_scroll_do(textview, old_value,
1682 prefs_common.scroll_step);
1685 if (vadj->value > 0.0) {
1686 old_value = vadj->value;
1687 last_value = vadj->value -
1688 vadj->step_increment * 4;
1689 last_value = MAX(last_value, 0.0);
1691 textview_smooth_scroll_do(textview, old_value,
1693 prefs_common.scroll_step);
1698 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1700 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1701 GtkAdjustment *vadj = text->vadjustment;
1707 if (prefs_common.scroll_halfpage)
1708 page_incr = vadj->page_increment / 2;
1710 page_incr = vadj->page_increment;
1713 upper = vadj->upper - vadj->page_size;
1714 if (vadj->value < upper) {
1715 old_value = vadj->value;
1716 last_value = vadj->value + page_incr;
1717 last_value = MIN(last_value, upper);
1719 textview_smooth_scroll_do(textview, old_value,
1721 prefs_common.scroll_step);
1725 if (vadj->value > 0.0) {
1726 old_value = vadj->value;
1727 last_value = vadj->value - page_incr;
1728 last_value = MAX(last_value, 0.0);
1730 textview_smooth_scroll_do(textview, old_value,
1732 prefs_common.scroll_step);
1740 #define KEY_PRESS_EVENT_STOP() \
1741 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1744 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1747 SummaryView *summaryview = NULL;
1748 MessageView *messageview = textview->messageview;
1750 if (!event) return FALSE;
1751 if (messageview->mainwin)
1752 summaryview = messageview->mainwin->summaryview;
1754 switch (event->keyval) {
1769 summary_pass_key_press_event(summaryview, event);
1771 textview_scroll_page(textview, FALSE);
1774 textview_scroll_page(textview, TRUE);
1777 textview_scroll_one_line(textview,
1778 (event->state & GDK_MOD1_MASK) != 0);
1782 summary_pass_key_press_event(summaryview, event);
1787 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1788 KEY_PRESS_EVENT_STOP();
1789 mimeview_pass_key_press_event(messageview->mimeview,
1793 /* possible fall through */
1796 event->window != messageview->mainwin->window->window) {
1797 GdkEventKey tmpev = *event;
1799 tmpev.window = messageview->mainwin->window->window;
1800 KEY_PRESS_EVENT_STOP();
1801 gtk_widget_event(messageview->mainwin->window,
1802 (GdkEvent *)&tmpev);
1811 *\brief Check to see if a web URL has been disguised as a different
1812 * URL (possible with HTML email).
1814 *\param uri The uri to check
1816 *\param textview The TextView the URL is contained in
1818 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1819 * it anyway, otherwise FALSE
1821 static gboolean uri_security_check(RemoteURI *uri, TextView *textview)
1824 gboolean retval = TRUE;
1826 if (g_ascii_strncasecmp(uri->uri, "http:", 5) &&
1827 g_ascii_strncasecmp(uri->uri, "https:", 6) &&
1828 g_ascii_strncasecmp(uri->uri, "www.", 4))
1831 clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1834 if (clicked_str == NULL)
1837 if (strcmp(clicked_str, uri->uri) &&
1838 (!g_ascii_strncasecmp(clicked_str, "http:", 5) ||
1839 !g_ascii_strncasecmp(clicked_str, "https:", 6) ||
1840 !g_ascii_strncasecmp(clicked_str, "www.", 4))) {
1844 /* allow uri->uri == http://somewhere.com
1845 and clicked_str == somewhere.com */
1846 str = g_strconcat("http://", clicked_str, NULL);
1848 if (!g_ascii_strcasecmp(str, uri->uri))
1853 if (retval == FALSE) {
1857 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1858 "the apparent URL (%s). \n"
1860 uri->uri, clicked_str);
1861 resp = alertpanel_with_type(_("Warning"),
1865 NULL, NULL, ALERT_WARNING);
1867 if (resp == G_ALERTDEFAULT)
1870 g_free(clicked_str);
1874 static gboolean textview_motion_notify(GtkWidget *widget,
1875 GdkEventMotion *event,
1878 textview_uri_update(textview, event->x, event->y);
1879 gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1884 static gboolean textview_leave_notify(GtkWidget *widget,
1885 GdkEventCrossing *event,
1888 textview_uri_update(textview, -1, -1);
1893 static gboolean textview_visibility_notify(GtkWidget *widget,
1894 GdkEventVisibility *event,
1899 gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1900 textview_uri_update(textview, wx, wy);
1905 static void textview_uri_update(TextView *textview, gint x, gint y)
1907 GtkTextBuffer *buffer;
1908 GtkTextIter start_iter, end_iter;
1909 RemoteURI *uri = NULL;
1911 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1913 if (x != -1 && y != -1) {
1919 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text),
1920 GTK_TEXT_WINDOW_WIDGET,
1922 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1925 tags = gtk_text_iter_get_tags(&iter);
1926 for (cur = tags; cur != NULL; cur = cur->next) {
1927 GtkTextTag *tag = cur->data;
1930 g_object_get(G_OBJECT(tag), "name", &name, NULL);
1931 if (!strcmp(name, "link")
1932 && textview_get_uri_range(textview, &iter, tag,
1933 &start_iter, &end_iter))
1934 uri = textview_get_uri_from_range(textview,
1946 if (uri != textview->uri_hover) {
1949 if (textview->uri_hover)
1950 gtk_text_buffer_remove_tag_by_name(buffer,
1952 &textview->uri_hover_start_iter,
1953 &textview->uri_hover_end_iter);
1955 textview->uri_hover = uri;
1957 textview->uri_hover_start_iter = start_iter;
1958 textview->uri_hover_end_iter = end_iter;
1961 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1962 GTK_TEXT_WINDOW_TEXT);
1963 gdk_window_set_cursor(window, uri ? hand_cursor : text_cursor);
1965 TEXTVIEW_STATUSBAR_POP(textview);
1970 gtk_text_buffer_apply_tag_by_name(buffer,
1975 trimmed_uri = trim_string(uri->uri, 60);
1976 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1977 g_free(trimmed_uri);
1982 static gboolean textview_get_uri_range(TextView *textview,
1985 GtkTextIter *start_iter,
1986 GtkTextIter *end_iter)
1988 GtkTextIter _start_iter, _end_iter;
1990 _start_iter = *iter;
1991 if(!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
1992 debug_print("Can't find start.");
1997 if(!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
1998 debug_print("Can't find end");
2002 *start_iter = _start_iter;
2003 *end_iter = _end_iter;
2008 static RemoteURI *textview_get_uri_from_range(TextView *textview,
2011 GtkTextIter *start_iter,
2012 GtkTextIter *end_iter)
2014 gint start_pos, end_pos, cur_pos;
2015 RemoteURI *uri = NULL;
2018 start_pos = gtk_text_iter_get_offset(start_iter);
2019 end_pos = gtk_text_iter_get_offset(end_iter);
2020 cur_pos = gtk_text_iter_get_offset(iter);
2022 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
2023 RemoteURI *uri_ = (RemoteURI *)cur->data;
2024 if (start_pos == uri_->start &&
2025 end_pos == uri_->end) {
2028 } else if (start_pos == uri_->start ||
2029 end_pos == uri_->end) {
2030 /* in case of contiguous links, textview_get_uri_range
2031 * returns a broader range (start of 1st link to end
2033 * In that case, correct link is the one covering
2036 if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
2046 static RemoteURI *textview_get_uri(TextView *textview,
2050 GtkTextIter start_iter, end_iter;
2051 RemoteURI *uri = NULL;
2053 if (textview_get_uri_range(textview, iter, tag, &start_iter,
2055 uri = textview_get_uri_from_range(textview, iter, tag,
2056 &start_iter, &end_iter);
2061 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
2062 GdkEvent *event, GtkTextIter *iter,
2065 GdkEventButton *bevent;
2066 RemoteURI *uri = NULL;
2073 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2074 && event->type != GDK_MOTION_NOTIFY)
2077 uri = textview_get_uri(textview, iter, tag);
2081 bevent = (GdkEventButton *) event;
2083 /* doubleclick: open compose / add address / browser */
2084 if ((event->type == GDK_BUTTON_PRESS && bevent->button == 1) ||
2085 bevent->button == 2 || bevent->button == 3) {
2086 if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2087 if (bevent->button == 3) {
2088 gchar *fromname, *fromaddress;
2091 fromaddress = g_strdup(uri->uri + 7);
2092 /* Hiroyuki: please put this function in utils.c! */
2093 fromname = procheader_get_fromname(fromaddress);
2094 extract_address(fromaddress);
2095 g_message("adding from textview %s <%s>", fromname, fromaddress);
2096 /* Add to address book - Match */
2097 addressbook_add_contact( fromname, fromaddress, NULL );
2099 g_free(fromaddress);
2102 PrefsAccount *account = NULL;
2104 if (textview->messageview && textview->messageview->msginfo &&
2105 textview->messageview->msginfo->folder) {
2106 FolderItem *folder_item;
2108 folder_item = textview->messageview->msginfo->folder;
2109 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2110 account = account_find_from_id(folder_item->prefs->default_account);
2112 compose_new(account, uri->uri + 7, NULL);
2116 if (textview_uri_security_check(textview, uri) == TRUE)
2118 prefs_common.uri_cmd);
2127 *\brief Check to see if a web URL has been disguised as a different
2128 * URL (possible with HTML email).
2130 *\param uri The uri to check
2132 *\param textview The TextView the URL is contained in
2134 *\return gboolean TRUE if the URL is ok, or if the user chose to open
2135 * it anyway, otherwise FALSE
2137 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2140 gboolean retval = TRUE;
2142 if (is_uri_string(uri->uri) == FALSE)
2145 visible_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
2146 uri->start, uri->end);
2147 if (visible_str == NULL)
2150 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2152 gchar *visible_uri_path;
2154 uri_path = get_uri_path(uri->uri);
2155 visible_uri_path = get_uri_path(visible_str);
2156 if (strcmp(uri_path, visible_uri_path) != 0)
2160 if (retval == FALSE) {
2164 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2165 "the apparent URL (%s).\n"
2167 uri->uri, visible_str);
2168 aval = alertpanel_with_type(_("Warning"), msg, _("Yes"), _("No"), NULL,
2169 NULL, ALERT_WARNING);
2171 if (aval == G_ALERTDEFAULT)
2175 g_free(visible_str);
2180 static void textview_uri_list_remove_all(GSList *uri_list)
2184 for (cur = uri_list; cur != NULL; cur = cur->next) {
2186 g_free(((RemoteURI *)cur->data)->uri);
2191 g_slist_free(uri_list);