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 if (*(bp_ - 1) == '"' && *(ep_) == '"'
877 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
878 && IS_RFC822_CHAR(*(ep_ + 3))) {
879 /* this informative part with an @ in it is
880 * followed by the email address */
883 /* go to matching '>' (or next non-rfc822 char, like \n) */
884 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
887 /* include the bracket */
888 if (*ep_ == '>') ep_++;
890 /* include the leading quote */
898 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
899 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
902 /* see if this is <bracketed>; in this case we also scan for the informative part. */
903 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
906 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
907 #define IN_STACK() (ptr > closure_stack)
908 /* has underrun check */
909 #define POP_STACK() if(IN_STACK()) --ptr
910 /* has overrun check */
911 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
912 /* has underrun check */
913 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
917 /* scan for the informative part. */
918 for (bp_ -= 2; bp_ >= start; bp_--) {
919 /* if closure on the stack keep scanning */
920 if (PEEK_STACK() == *bp_) {
924 if (*bp_ == '\'' || *bp_ == '"') {
929 /* if nothing in the closure stack, do the special conditions
930 * the following if..else expression simply checks whether
931 * a token is acceptable. if not acceptable, the clause
932 * should terminate the loop with a 'break' */
935 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
936 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
937 /* hyphens are allowed, but only in
939 } else if (!ispunct(*bp_)) {
940 /* but anything not being a punctiation
943 break; /* anything else is rejected */
956 /* scan forward (should start with an alnum) */
957 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
967 #undef IS_ASCII_ALNUM
968 #undef IS_RFC822_CHAR
970 static gchar *make_email_string(const gchar *bp, const gchar *ep)
972 /* returns a mailto: URI; mailto: is also used to detect the
973 * uri type later on in the button_pressed signal handler */
977 tmp = g_strndup(bp, ep - bp);
978 result = g_strconcat("mailto:", tmp, NULL);
984 static gchar *make_http_string(const gchar *bp, const gchar *ep)
986 /* returns an http: URI; */
990 tmp = g_strndup(bp, ep - bp);
991 result = g_strconcat("http://", tmp, NULL);
997 #define ADD_TXT_POS(bp_, ep_, pti_) \
998 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1000 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1001 last->next = NULL; \
1003 g_warning("alloc error scanning URIs\n"); \
1004 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1010 /* textview_make_clickable_parts() - colorizes clickable parts */
1011 static void textview_make_clickable_parts(TextView *textview,
1012 const gchar *fg_tag,
1013 const gchar *uri_tag,
1014 const gchar *linebuf)
1016 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1017 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1020 /* parse table - in order of priority */
1022 const gchar *needle; /* token */
1024 /* token search function */
1025 gchar *(*search) (const gchar *haystack,
1026 const gchar *needle);
1027 /* part parsing function */
1028 gboolean (*parse) (const gchar *start,
1029 const gchar *scanpos,
1032 /* part to URI function */
1033 gchar *(*build_uri) (const gchar *bp,
1037 static struct table parser[] = {
1038 {"http://", strcasestr, get_uri_part, make_uri_string},
1039 {"https://", strcasestr, get_uri_part, make_uri_string},
1040 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1041 {"www.", strcasestr, get_uri_part, make_http_string},
1042 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1043 {"@", strcasestr, get_email_part, make_email_string}
1045 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1048 const gchar *walk, *bp, *ep;
1051 const gchar *bp, *ep; /* text position */
1052 gint pti; /* index in parse table */
1053 struct txtpos *next; /* next */
1054 } head = {NULL, NULL, 0, NULL}, *last = &head;
1056 gtk_text_buffer_get_end_iter(buffer, &iter);
1058 /* parse for clickable parts, and build a list of begin and end positions */
1059 for (walk = linebuf, n = 0;;) {
1060 gint last_index = PARSE_ELEMS;
1061 gchar *scanpos = NULL;
1063 /* FIXME: this looks phony. scanning for anything in the parse table */
1064 for (n = 0; n < PARSE_ELEMS; n++) {
1067 tmp = parser[n].search(walk, parser[n].needle);
1069 if (scanpos == NULL || tmp < scanpos) {
1077 /* check if URI can be parsed */
1078 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1079 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1080 ADD_TXT_POS(bp, ep, last_index);
1084 strlen(parser[last_index].needle);
1089 /* colorize this line */
1091 const gchar *normal_text = linebuf;
1094 for (last = head.next; last != NULL;
1095 normal_text = last->ep, last = last->next) {
1097 uri = g_new(RemoteURI, 1);
1098 if (last->bp - normal_text > 0)
1099 gtk_text_buffer_insert_with_tags_by_name
1102 last->bp - normal_text,
1104 uri->uri = parser[last->pti].build_uri(last->bp,
1106 uri->start = gtk_text_iter_get_offset(&iter);
1107 gtk_text_buffer_insert_with_tags_by_name
1109 last->bp, last->ep - last->bp,
1111 uri->end = gtk_text_iter_get_offset(&iter);
1112 textview->uri_list =
1113 g_slist_append(textview->uri_list, uri);
1117 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1121 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1129 static void textview_write_line(TextView *textview, const gchar *str,
1130 CodeConverter *conv)
1133 GtkTextBuffer *buffer;
1135 gchar buf[BUFFSIZE];
1137 gint quotelevel = -1;
1138 gchar quote_tag_str[10];
1140 text = GTK_TEXT_VIEW(textview->text);
1141 buffer = gtk_text_view_get_buffer(text);
1142 gtk_text_buffer_get_end_iter(buffer, &iter);
1145 strncpy2(buf, str, sizeof(buf));
1146 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
1147 conv_localetodisp(buf, sizeof(buf), str);
1151 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1154 /* change color of quotation
1155 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1156 Up to 3 levels of quotations are detected, and each
1157 level is colored using a different color. */
1158 if (prefs_common.enable_color
1159 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1160 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1162 /* set up the correct foreground color */
1163 if (quotelevel > 2) {
1164 /* recycle colors */
1165 if (prefs_common.recycle_quote_colors)
1172 if (quotelevel == -1)
1175 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1176 "quote%d", quotelevel);
1177 fg_color = quote_tag_str;
1180 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1181 fg_color = "signature";
1182 textview->is_in_signature = TRUE;
1185 if (prefs_common.enable_color)
1186 textview_make_clickable_parts(textview, fg_color, "link", buf);
1188 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1191 void textview_write_link(TextView *textview, const gchar *str,
1192 const gchar *uri, CodeConverter *conv)
1194 GdkColor *link_color = NULL;
1196 GtkTextBuffer *buffer;
1198 gchar buf[BUFFSIZE];
1205 text = GTK_TEXT_VIEW(textview->text);
1206 buffer = gtk_text_view_get_buffer(text);
1207 gtk_text_buffer_get_end_iter(buffer, &iter);
1212 if (textview->text_is_mb)
1213 conv_localetodisp(buf, sizeof(buf), str);
1215 strncpy2(buf, str, sizeof(buf));
1216 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1217 conv_localetodisp(buf, sizeof(buf), str);
1218 else if (textview->text_is_mb)
1219 conv_unreadable_locale(buf);
1222 strncpy2(buf, str, sizeof(buf));
1223 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1224 conv_localetodisp(buf, sizeof(buf), str);
1229 gtk_text_buffer_get_end_iter(buffer, &iter);
1231 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1232 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1234 if (prefs_common.enable_color) {
1235 link_color = &uri_color;
1237 r_uri = g_new(RemoteURI, 1);
1238 r_uri->uri = g_strdup(uri);
1239 r_uri->start = gtk_text_iter_get_offset(&iter);
1240 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, bufp, -1,
1242 r_uri->end = gtk_text_iter_get_offset(&iter);
1243 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1246 void textview_clear(TextView *textview)
1248 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1249 GtkTextBuffer *buffer;
1251 buffer = gtk_text_view_get_buffer(text);
1252 gtk_text_buffer_set_text(buffer, "\0", -1);
1254 TEXTVIEW_STATUSBAR_POP(textview);
1255 textview_uri_list_remove_all(textview->uri_list);
1256 textview->uri_list = NULL;
1258 textview->body_pos = 0;
1261 void textview_destroy(TextView *textview)
1263 textview_uri_list_remove_all(textview->uri_list);
1264 textview->uri_list = NULL;
1269 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1271 textview->show_all_headers = all_headers;
1274 void textview_set_font(TextView *textview, const gchar *codeset)
1276 if (prefs_common.textfont) {
1277 PangoFontDescription *font_desc = NULL;
1279 if (prefs_common.textfont)
1280 font_desc = pango_font_description_from_string
1281 (prefs_common.textfont);
1283 gtk_widget_modify_font(textview->text, font_desc);
1284 pango_font_description_free(font_desc);
1287 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1288 prefs_common.line_space / 2);
1289 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1290 prefs_common.line_space / 2);
1291 if (prefs_common.head_space) {
1292 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 6);
1294 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 0);
1298 void textview_set_text(TextView *textview, const gchar *text)
1301 GtkTextBuffer *buffer;
1303 g_return_if_fail(textview != NULL);
1304 g_return_if_fail(text != NULL);
1306 textview_clear(textview);
1308 view = GTK_TEXT_VIEW(textview->text);
1309 buffer = gtk_text_view_get_buffer(view);
1310 gtk_text_buffer_set_text(buffer, text, strlen(text));
1326 H_ORGANIZATION = 11,
1329 void textview_set_position(TextView *textview, gint pos)
1331 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1332 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1335 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1336 gtk_text_buffer_place_cursor(buffer, &iter);
1337 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1340 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1342 gchar buf[BUFFSIZE];
1343 GPtrArray *headers, *sorted_headers;
1344 GSList *disphdr_list;
1348 g_return_val_if_fail(fp != NULL, NULL);
1350 if (textview->show_all_headers)
1351 return procheader_get_header_array_asis(fp);
1353 if (!prefs_common.display_header) {
1354 while (fgets(buf, sizeof(buf), fp) != NULL)
1355 if (buf[0] == '\r' || buf[0] == '\n') break;
1359 headers = procheader_get_header_array_asis(fp);
1361 sorted_headers = g_ptr_array_new();
1363 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1364 disphdr_list = disphdr_list->next) {
1365 DisplayHeaderProp *dp =
1366 (DisplayHeaderProp *)disphdr_list->data;
1368 for (i = 0; i < headers->len; i++) {
1369 header = g_ptr_array_index(headers, i);
1371 if (procheader_headername_equal(header->name,
1374 procheader_header_free(header);
1376 g_ptr_array_add(sorted_headers, header);
1378 g_ptr_array_remove_index(headers, i);
1384 if (prefs_common.show_other_header) {
1385 for (i = 0; i < headers->len; i++) {
1386 header = g_ptr_array_index(headers, i);
1387 g_ptr_array_add(sorted_headers, header);
1389 g_ptr_array_free(headers, TRUE);
1391 procheader_header_array_destroy(headers);
1394 return sorted_headers;
1397 static void textview_show_header(TextView *textview, GPtrArray *headers)
1399 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1400 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1405 g_return_if_fail(headers != NULL);
1407 for (i = 0; i < headers->len; i++) {
1408 header = g_ptr_array_index(headers, i);
1409 g_return_if_fail(header->name != NULL);
1411 gtk_text_buffer_get_end_iter (buffer, &iter);
1412 gtk_text_buffer_insert_with_tags_by_name
1413 (buffer, &iter, header->name, -1,
1414 "header_title", "header", NULL);
1415 if (header->name[strlen(header->name) - 1] != ' ')
1416 gtk_text_buffer_insert_with_tags_by_name
1417 (buffer, &iter, " ", 1,
1418 "header_title", "header", NULL);
1420 if (procheader_headername_equal(header->name, "Subject") ||
1421 procheader_headername_equal(header->name, "From") ||
1422 procheader_headername_equal(header->name, "To") ||
1423 procheader_headername_equal(header->name, "Cc"))
1424 unfold_line(header->body);
1428 if (textview->text_is_mb == TRUE)
1429 conv_unreadable_locale(header->body);
1432 if (prefs_common.enable_color &&
1433 (procheader_headername_equal(header->name, "X-Mailer") ||
1434 procheader_headername_equal(header->name,
1436 strstr(header->body, "Sylpheed") != NULL) {
1437 gtk_text_buffer_get_end_iter (buffer, &iter);
1438 gtk_text_buffer_insert_with_tags_by_name
1439 (buffer, &iter, header->body, -1,
1440 "header", "emphasis", NULL);
1441 } else if (prefs_common.enable_color) {
1442 textview_make_clickable_parts(textview, "header", "link",
1445 textview_make_clickable_parts(textview, "header", NULL,
1448 gtk_text_buffer_get_end_iter (buffer, &iter);
1449 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1454 gboolean textview_search_string(TextView *textview, const gchar *str,
1457 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1458 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1460 GtkTextIter iter, start, end, real_end, *pos;
1461 gboolean found = FALSE;
1462 gint insert_offset, selbound_offset;
1464 /* reset selection */
1465 mark = gtk_text_buffer_get_mark(buffer, "insert");
1466 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1467 insert_offset = gtk_text_iter_get_offset(&start);
1468 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1469 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1470 selbound_offset = gtk_text_iter_get_offset(&end);
1472 pos = insert_offset > selbound_offset ? &start : &end;
1473 gtk_text_buffer_place_cursor(buffer, pos);
1476 mark = gtk_text_buffer_get_insert(buffer);
1477 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1479 found = gtk_text_iter_forward_search(&iter, str,
1480 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1481 &start, &end, NULL);
1485 gtk_text_buffer_get_end_iter(buffer, &real_end);
1486 text = gtk_text_buffer_get_text(buffer, &iter,
1489 while (!found && i++ < strlen(text) - 1) {
1490 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1493 i += gtk_text_iter_get_offset(&end);
1496 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1497 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1505 gtk_text_buffer_place_cursor(buffer, &start);
1506 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1508 mark = gtk_text_buffer_get_mark(buffer, "insert");
1509 gtk_text_view_scroll_mark_onscreen(text, mark);
1515 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1518 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1519 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1521 GtkTextIter iter, start, real_start, end, *pos;
1522 gboolean found = FALSE;
1523 gint insert_offset, selbound_offset;
1525 /* reset selection */
1526 mark = gtk_text_buffer_get_mark(buffer, "insert");
1527 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1528 insert_offset = gtk_text_iter_get_offset(&start);
1529 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1530 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1531 selbound_offset = gtk_text_iter_get_offset(&end);
1533 pos = insert_offset < selbound_offset ? &start : &end;
1534 gtk_text_buffer_place_cursor(buffer, pos);
1537 mark = gtk_text_buffer_get_insert(buffer);
1538 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1540 found = gtk_text_iter_backward_search(&iter, str,
1541 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1542 &start, &end, NULL);
1546 if (gtk_text_iter_get_offset(&iter) == 0)
1547 gtk_text_buffer_get_end_iter(buffer, &iter);
1549 i = gtk_text_iter_get_offset(&iter) - strlen(str) - 1;
1550 gtk_text_buffer_get_start_iter(buffer, &real_start);
1552 text = gtk_text_buffer_get_text(buffer, &real_start,
1555 while (!found && i-- > 0) {
1556 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1560 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1561 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1569 gtk_text_buffer_place_cursor(buffer, &end);
1570 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1572 mark = gtk_text_buffer_get_mark(buffer, "insert");
1573 gtk_text_view_scroll_mark_onscreen(text, mark);
1579 void textview_scroll_one_line(TextView *textview, gboolean up)
1581 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1582 GtkAdjustment *vadj = text->vadjustment;
1585 if (prefs_common.enable_smooth_scroll) {
1586 textview_smooth_scroll_one_line(textview, up);
1591 upper = vadj->upper - vadj->page_size;
1592 if (vadj->value < upper) {
1594 vadj->step_increment * 4;
1596 MIN(vadj->value, upper);
1597 g_signal_emit_by_name(G_OBJECT(vadj),
1598 "value_changed", 0);
1601 if (vadj->value > 0.0) {
1603 vadj->step_increment * 4;
1605 MAX(vadj->value, 0.0);
1606 g_signal_emit_by_name(G_OBJECT(vadj),
1607 "value_changed", 0);
1612 gboolean textview_scroll_page(TextView *textview, gboolean up)
1614 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1615 GtkAdjustment *vadj = text->vadjustment;
1619 if (prefs_common.enable_smooth_scroll)
1620 return textview_smooth_scroll_page(textview, up);
1622 if (prefs_common.scroll_halfpage)
1623 page_incr = vadj->page_increment / 2;
1625 page_incr = vadj->page_increment;
1628 upper = vadj->upper - vadj->page_size;
1629 if (vadj->value < upper) {
1630 vadj->value += page_incr;
1631 vadj->value = MIN(vadj->value, upper);
1632 g_signal_emit_by_name(G_OBJECT(vadj),
1633 "value_changed", 0);
1637 if (vadj->value > 0.0) {
1638 vadj->value -= page_incr;
1639 vadj->value = MAX(vadj->value, 0.0);
1640 g_signal_emit_by_name(G_OBJECT(vadj),
1641 "value_changed", 0);
1649 static void textview_smooth_scroll_do(TextView *textview,
1650 gfloat old_value, gfloat last_value,
1653 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1654 GtkAdjustment *vadj = text->vadjustment;
1659 if (old_value < last_value) {
1660 change_value = last_value - old_value;
1663 change_value = old_value - last_value;
1667 for (i = step; i <= change_value; i += step) {
1668 vadj->value = old_value + (up ? -i : i);
1669 g_signal_emit_by_name(G_OBJECT(vadj),
1670 "value_changed", 0);
1673 vadj->value = last_value;
1674 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1677 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1679 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1680 GtkAdjustment *vadj = text->vadjustment;
1686 upper = vadj->upper - vadj->page_size;
1687 if (vadj->value < upper) {
1688 old_value = vadj->value;
1689 last_value = vadj->value +
1690 vadj->step_increment * 4;
1691 last_value = MIN(last_value, upper);
1693 textview_smooth_scroll_do(textview, old_value,
1695 prefs_common.scroll_step);
1698 if (vadj->value > 0.0) {
1699 old_value = vadj->value;
1700 last_value = vadj->value -
1701 vadj->step_increment * 4;
1702 last_value = MAX(last_value, 0.0);
1704 textview_smooth_scroll_do(textview, old_value,
1706 prefs_common.scroll_step);
1711 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1713 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1714 GtkAdjustment *vadj = text->vadjustment;
1720 if (prefs_common.scroll_halfpage)
1721 page_incr = vadj->page_increment / 2;
1723 page_incr = vadj->page_increment;
1726 upper = vadj->upper - vadj->page_size;
1727 if (vadj->value < upper) {
1728 old_value = vadj->value;
1729 last_value = vadj->value + page_incr;
1730 last_value = MIN(last_value, upper);
1732 textview_smooth_scroll_do(textview, old_value,
1734 prefs_common.scroll_step);
1738 if (vadj->value > 0.0) {
1739 old_value = vadj->value;
1740 last_value = vadj->value - page_incr;
1741 last_value = MAX(last_value, 0.0);
1743 textview_smooth_scroll_do(textview, old_value,
1745 prefs_common.scroll_step);
1753 #define KEY_PRESS_EVENT_STOP() \
1754 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1757 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1760 SummaryView *summaryview = NULL;
1761 MessageView *messageview = textview->messageview;
1763 if (!event) return FALSE;
1764 if (messageview->mainwin)
1765 summaryview = messageview->mainwin->summaryview;
1767 switch (event->keyval) {
1782 summary_pass_key_press_event(summaryview, event);
1784 textview_scroll_page
1787 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1790 textview_scroll_page(textview, TRUE);
1793 textview_scroll_one_line
1794 (textview, (event->state &
1795 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1799 summary_pass_key_press_event(summaryview, event);
1804 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1805 KEY_PRESS_EVENT_STOP();
1806 mimeview_pass_key_press_event(messageview->mimeview,
1810 /* possible fall through */
1813 event->window != messageview->mainwin->window->window) {
1814 GdkEventKey tmpev = *event;
1816 tmpev.window = messageview->mainwin->window->window;
1817 KEY_PRESS_EVENT_STOP();
1818 gtk_widget_event(messageview->mainwin->window,
1819 (GdkEvent *)&tmpev);
1827 static gboolean textview_motion_notify(GtkWidget *widget,
1828 GdkEventMotion *event,
1831 textview_uri_update(textview, event->x, event->y);
1832 gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1837 static gboolean textview_leave_notify(GtkWidget *widget,
1838 GdkEventCrossing *event,
1841 textview_uri_update(textview, -1, -1);
1846 static gboolean textview_visibility_notify(GtkWidget *widget,
1847 GdkEventVisibility *event,
1853 window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
1854 GTK_TEXT_WINDOW_TEXT);
1856 /* check if occurred for the text window part */
1857 if (window != event->window)
1860 gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1861 textview_uri_update(textview, wx, wy);
1866 static void textview_uri_update(TextView *textview, gint x, gint y)
1868 GtkTextBuffer *buffer;
1869 GtkTextIter start_iter, end_iter;
1870 RemoteURI *uri = NULL;
1872 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1874 if (x != -1 && y != -1) {
1880 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text),
1881 GTK_TEXT_WINDOW_WIDGET,
1883 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1886 tags = gtk_text_iter_get_tags(&iter);
1887 for (cur = tags; cur != NULL; cur = cur->next) {
1888 GtkTextTag *tag = cur->data;
1891 g_object_get(G_OBJECT(tag), "name", &name, NULL);
1892 if (!strcmp(name, "link")
1893 && textview_get_uri_range(textview, &iter, tag,
1894 &start_iter, &end_iter))
1895 uri = textview_get_uri_from_range(textview,
1907 if (uri != textview->uri_hover) {
1910 if (textview->uri_hover)
1911 gtk_text_buffer_remove_tag_by_name(buffer,
1913 &textview->uri_hover_start_iter,
1914 &textview->uri_hover_end_iter);
1916 textview->uri_hover = uri;
1918 textview->uri_hover_start_iter = start_iter;
1919 textview->uri_hover_end_iter = end_iter;
1922 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1923 GTK_TEXT_WINDOW_TEXT);
1924 gdk_window_set_cursor(window, uri ? hand_cursor : text_cursor);
1926 TEXTVIEW_STATUSBAR_POP(textview);
1931 gtk_text_buffer_apply_tag_by_name(buffer,
1936 trimmed_uri = trim_string(uri->uri, 60);
1937 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1938 g_free(trimmed_uri);
1943 static gboolean textview_get_uri_range(TextView *textview,
1946 GtkTextIter *start_iter,
1947 GtkTextIter *end_iter)
1949 GtkTextIter _start_iter, _end_iter;
1952 if (!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
1953 debug_print("Can't find end");
1957 _start_iter = _end_iter;
1958 if (!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
1959 debug_print("Can't find start.");
1963 *start_iter = _start_iter;
1964 *end_iter = _end_iter;
1969 static RemoteURI *textview_get_uri_from_range(TextView *textview,
1972 GtkTextIter *start_iter,
1973 GtkTextIter *end_iter)
1975 gint start_pos, end_pos, cur_pos;
1976 RemoteURI *uri = NULL;
1979 start_pos = gtk_text_iter_get_offset(start_iter);
1980 end_pos = gtk_text_iter_get_offset(end_iter);
1981 cur_pos = gtk_text_iter_get_offset(iter);
1983 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1984 RemoteURI *uri_ = (RemoteURI *)cur->data;
1985 if (start_pos == uri_->start &&
1986 end_pos == uri_->end) {
1989 } else if (start_pos == uri_->start ||
1990 end_pos == uri_->end) {
1991 /* in case of contiguous links, textview_get_uri_range
1992 * returns a broader range (start of 1st link to end
1994 * In that case, correct link is the one covering
1997 if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
2007 static RemoteURI *textview_get_uri(TextView *textview,
2011 GtkTextIter start_iter, end_iter;
2012 RemoteURI *uri = NULL;
2014 if (textview_get_uri_range(textview, iter, tag, &start_iter,
2016 uri = textview_get_uri_from_range(textview, iter, tag,
2017 &start_iter, &end_iter);
2022 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
2023 GdkEvent *event, GtkTextIter *iter,
2026 GdkEventButton *bevent;
2027 RemoteURI *uri = NULL;
2034 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2035 && event->type != GDK_MOTION_NOTIFY)
2038 uri = textview_get_uri(textview, iter, tag);
2042 bevent = (GdkEventButton *) event;
2044 /* doubleclick: open compose / add address / browser */
2045 if ((event->type == GDK_BUTTON_PRESS && bevent->button == 1) ||
2046 bevent->button == 2 || bevent->button == 3) {
2047 if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2048 if (bevent->button == 3) {
2049 gchar *fromname, *fromaddress;
2052 fromaddress = g_strdup(uri->uri + 7);
2053 /* Hiroyuki: please put this function in utils.c! */
2054 fromname = procheader_get_fromname(fromaddress);
2055 extract_address(fromaddress);
2056 g_message("adding from textview %s <%s>", fromname, fromaddress);
2057 /* Add to address book - Match */
2058 addressbook_add_contact( fromname, fromaddress, NULL );
2060 g_free(fromaddress);
2063 PrefsAccount *account = NULL;
2065 if (textview->messageview && textview->messageview->msginfo &&
2066 textview->messageview->msginfo->folder) {
2067 FolderItem *folder_item;
2069 folder_item = textview->messageview->msginfo->folder;
2070 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2071 account = account_find_from_id(folder_item->prefs->default_account);
2073 compose_new(account, uri->uri + 7, NULL);
2077 if (textview_uri_security_check(textview, uri) == TRUE)
2079 prefs_common.uri_cmd);
2088 *\brief Check to see if a web URL has been disguised as a different
2089 * URL (possible with HTML email).
2091 *\param uri The uri to check
2093 *\param textview The TextView the URL is contained in
2095 *\return gboolean TRUE if the URL is ok, or if the user chose to open
2096 * it anyway, otherwise FALSE
2098 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2101 gboolean retval = TRUE;
2102 GtkTextBuffer *buffer;
2103 GtkTextIter start, end;
2105 if (is_uri_string(uri->uri) == FALSE)
2108 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2110 gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2111 gtk_text_buffer_get_iter_at_offset(buffer, &end, uri->end);
2113 visible_str = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2115 if (visible_str == NULL)
2118 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2120 gchar *visible_uri_path;
2122 uri_path = get_uri_path(uri->uri);
2123 visible_uri_path = get_uri_path(visible_str);
2124 if (strcmp(uri_path, visible_uri_path) != 0)
2128 if (retval == FALSE) {
2132 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2133 "the apparent URL (%s).\n"
2135 uri->uri, visible_str);
2136 aval = alertpanel_with_type(_("Warning"), msg, _("Yes"), _("No"), NULL,
2137 NULL, ALERT_WARNING);
2139 if (aval == G_ALERTDEFAULT)
2143 g_free(visible_str);
2148 static void textview_uri_list_remove_all(GSList *uri_list)
2152 for (cur = uri_list; cur != NULL; cur = cur->next) {
2154 g_free(((RemoteURI *)cur->data)->uri);
2159 g_slist_free(uri_list);