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;
102 #define TEXTVIEW_STATUSBAR_PUSH(textview, str) \
104 gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
105 textview->messageview->statusbar_cid, str); \
108 #define TEXTVIEW_STATUSBAR_POP(textview) \
110 gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
111 textview->messageview->statusbar_cid); \
114 static void textview_show_ertf (TextView *textview,
116 CodeConverter *conv);
117 static void textview_add_part (TextView *textview,
119 static void textview_add_parts (TextView *textview,
121 static void textview_write_body (TextView *textview,
123 static void textview_show_html (TextView *textview,
125 CodeConverter *conv);
127 static void textview_write_line (TextView *textview,
129 CodeConverter *conv);
130 static void textview_write_link (TextView *textview,
133 CodeConverter *conv);
135 static GPtrArray *textview_scan_header (TextView *textview,
137 static void textview_show_header (TextView *textview,
140 static gint textview_key_pressed (GtkWidget *widget,
143 static gboolean textview_motion_notify (GtkWidget *widget,
144 GdkEventMotion *motion,
146 static gboolean textview_leave_notify (GtkWidget *widget,
147 GdkEventCrossing *event,
149 static gboolean textview_visibility_notify (GtkWidget *widget,
150 GdkEventVisibility *event,
152 static void textview_uri_update (TextView *textview,
155 static gboolean textview_get_uri_range (TextView *textview,
158 GtkTextIter *start_iter,
159 GtkTextIter *end_iter);
160 static RemoteURI *textview_get_uri_from_range (TextView *textview,
163 GtkTextIter *start_iter,
164 GtkTextIter *end_iter);
165 static RemoteURI *textview_get_uri (TextView *textview,
168 static gboolean textview_uri_button_pressed (GtkTextTag *tag,
174 static void textview_smooth_scroll_do (TextView *textview,
178 static void textview_smooth_scroll_one_line (TextView *textview,
180 static gboolean textview_smooth_scroll_page (TextView *textview,
183 static gboolean textview_uri_security_check (TextView *textview,
185 static void textview_uri_list_remove_all (GSList *uri_list);
188 static void populate_popup(GtkTextView *textview, GtkMenu *menu,
191 gtk_menu_detach(menu);
194 TextView *textview_create(void)
198 GtkWidget *scrolledwin;
200 GtkTextBuffer *buffer;
201 GtkClipboard *clipboard;
202 PangoFontDescription *font_desc = NULL;
204 debug_print("Creating text view...\n");
205 textview = g_new0(TextView, 1);
207 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
208 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
209 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
210 gtk_widget_set_size_request(scrolledwin, prefs_common.mainview_width, -1);
211 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
214 /* create GtkSText widgets for single-byte and multi-byte character */
215 text = gtk_text_view_new();
216 gtk_widget_add_events(text, GDK_LEAVE_NOTIFY_MASK);
217 gtk_widget_show(text);
218 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
219 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
220 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
221 g_signal_connect(G_OBJECT(text), "populate-popup",
222 G_CALLBACK(populate_popup), NULL);
225 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
226 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
227 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
229 gtk_widget_ensure_style(text);
231 if (prefs_common.normalfont)
232 font_desc = pango_font_description_from_string
233 (prefs_common.normalfont);
235 gtk_widget_modify_font(text, font_desc);
237 pango_font_description_free(font_desc);
239 gtk_widget_ref(scrolledwin);
241 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
244 hand_cursor = gdk_cursor_new(GDK_HAND2);
246 g_signal_connect(G_OBJECT(text), "key_press_event",
247 G_CALLBACK(textview_key_pressed),
249 g_signal_connect(G_OBJECT(text), "motion_notify_event",
250 G_CALLBACK(textview_motion_notify),
252 g_signal_connect(G_OBJECT(text), "leave_notify_event",
253 G_CALLBACK(textview_leave_notify),
255 g_signal_connect(G_OBJECT(text), "visibility_notify_event",
256 G_CALLBACK(textview_visibility_notify),
259 gtk_widget_show(scrolledwin);
261 vbox = gtk_vbox_new(FALSE, 0);
262 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
264 gtk_widget_show(vbox);
266 textview->vbox = vbox;
267 textview->scrolledwin = scrolledwin;
268 textview->text = text;
269 textview->uri_list = NULL;
270 textview->body_pos = 0;
271 textview->show_all_headers = FALSE;
272 textview->last_buttonpress = GDK_NOTHING;
277 static void textview_create_tags(GtkTextView *text, TextView *textview)
279 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
282 gtk_text_buffer_create_tag(buffer, "header",
283 "pixels-above-lines", 0,
284 "pixels-above-lines-set", TRUE,
285 "pixels-below-lines", 0,
286 "pixels-below-lines-set", TRUE,
288 "left-margin-set", TRUE,
290 gtk_text_buffer_create_tag(buffer, "header_title",
291 "font", prefs_common.boldfont,
293 gtk_text_buffer_create_tag(buffer, "quote0",
294 "foreground-gdk", "e_colors[0],
296 gtk_text_buffer_create_tag(buffer, "quote1",
297 "foreground-gdk", "e_colors[1],
299 gtk_text_buffer_create_tag(buffer, "quote2",
300 "foreground-gdk", "e_colors[2],
302 gtk_text_buffer_create_tag(buffer, "emphasis",
303 "foreground-gdk", &emphasis_color,
305 gtk_text_buffer_create_tag(buffer, "signature",
306 "foreground-gdk", &signature_color,
308 tag = gtk_text_buffer_create_tag(buffer, "link",
309 "foreground-gdk", &uri_color,
311 gtk_text_buffer_create_tag(buffer, "link-hover",
312 "underline", PANGO_UNDERLINE_SINGLE,
315 g_signal_connect(G_OBJECT(tag), "event",
316 G_CALLBACK(textview_uri_button_pressed), textview);
319 void textview_init(TextView *textview)
321 gtkut_widget_disable_theme_engine(textview->text);
322 textview_update_message_colors();
323 textview_set_all_headers(textview, FALSE);
324 textview_set_font(textview, NULL);
326 textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
329 void textview_update_message_colors(void)
331 GdkColor black = {0, 0, 0, 0};
333 if (prefs_common.enable_color) {
334 /* grab the quote colors, converting from an int to a GdkColor */
335 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
337 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
339 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
341 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
343 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
346 quote_colors[0] = quote_colors[1] = quote_colors[2] =
347 uri_color = emphasis_color = signature_color = black;
351 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
356 if ((fp = fopen(file, "rb")) == NULL) {
357 FILE_OP_ERROR(file, "fopen");
361 textview_clear(textview);
363 textview_add_parts(textview, mimeinfo);
367 textview_set_position(textview, 0);
370 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
374 g_return_if_fail(mimeinfo != NULL);
375 g_return_if_fail(fp != NULL);
377 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
378 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822"))) {
379 textview_clear(textview);
380 textview_add_parts(textview, mimeinfo);
384 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
387 textview_clear(textview);
389 if (mimeinfo->type == MIMETYPE_MULTIPART)
390 textview_add_parts(textview, mimeinfo);
392 textview_write_body(textview, mimeinfo);
396 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
399 GtkTextBuffer *buffer;
402 GPtrArray *headers = NULL;
407 g_return_if_fail(mimeinfo != NULL);
408 text = GTK_TEXT_VIEW(textview->text);
409 buffer = gtk_text_view_get_buffer(text);
410 charcount = gtk_text_buffer_get_char_count(buffer);
411 gtk_text_buffer_get_end_iter(buffer, &iter);
413 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
415 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
418 fp = fopen(mimeinfo->data.filename, "rb");
419 fseek(fp, mimeinfo->offset, SEEK_SET);
420 headers = textview_scan_header(textview, fp);
423 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
424 textview_show_header(textview, headers);
425 procheader_header_array_destroy(headers);
431 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
432 content_type = procmime_get_content_type_str(mimeinfo->type,
435 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
437 g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n",
438 name, content_type, mimeinfo->length);
440 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
441 content_type, mimeinfo->length);
443 g_free(content_type);
445 if (mimeinfo->type != MIMETYPE_TEXT) {
446 gtk_text_buffer_insert(buffer, &iter, buf, -1);
447 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
448 if (prefs_common.display_header && (charcount > 0))
449 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
451 textview_write_body(textview, mimeinfo);
455 static void recursive_add_parts(TextView *textview, GNode *node)
460 mimeinfo = (MimeInfo *) node->data;
462 textview_add_part(textview, mimeinfo);
464 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
465 (mimeinfo->type != MIMETYPE_MESSAGE))
468 if (g_ascii_strcasecmp(mimeinfo->subtype, "alternative") == 0) {
469 GNode * prefered_body;
477 prefered_body = NULL;
480 for(iter = g_node_first_child(node) ; iter != NULL ;
481 iter = g_node_next_sibling(iter)) {
486 submime = (MimeInfo *) iter->data;
487 if (submime->type == MIMETYPE_TEXT)
490 if (submime->subtype != NULL) {
491 if (g_ascii_strcasecmp(submime->subtype, "plain") == 0)
495 if (score > prefered_score) {
496 prefered_score = score;
497 prefered_body = iter;
501 if (prefered_body != NULL) {
502 recursive_add_parts(textview, prefered_body);
506 for(iter = g_node_first_child(node) ; iter != NULL ;
507 iter = g_node_next_sibling(iter)) {
508 recursive_add_parts(textview, iter);
513 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
515 g_return_if_fail(mimeinfo != NULL);
517 recursive_add_parts(textview, mimeinfo->node);
520 #define TEXT_INSERT(str) \
521 gtk_text_buffer_insert(buffer, &iter, str, -1)
523 void textview_show_error(TextView *textview)
526 GtkTextBuffer *buffer;
529 textview_set_font(textview, NULL);
530 textview_clear(textview);
532 text = GTK_TEXT_VIEW(textview->text);
533 buffer = gtk_text_view_get_buffer(text);
534 gtk_text_buffer_get_start_iter(buffer, &iter);
536 TEXT_INSERT(_("This message can't be displayed.\n"));
540 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
543 GtkTextBuffer *buffer;
546 if (!partinfo) return;
548 textview_set_font(textview, NULL);
549 textview_clear(textview);
551 text = GTK_TEXT_VIEW(textview->text);
552 buffer = gtk_text_view_get_buffer(text);
553 gtk_text_buffer_get_start_iter(buffer, &iter);
555 TEXT_INSERT(_("The following can be performed on this part by "));
556 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
558 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
559 TEXT_INSERT(_(" To display as text select 'Display as text' "));
560 TEXT_INSERT(_("(Shortcut key: 't')\n"));
561 TEXT_INSERT(_(" To open with an external program select 'Open' "));
562 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
563 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
564 TEXT_INSERT(_("mouse button),\n"));
565 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
571 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
576 const gchar *charset;
578 if (textview->messageview->forced_charset)
579 charset = textview->messageview->forced_charset;
581 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
583 textview_set_font(textview, charset);
585 conv = conv_code_converter_new(charset);
587 procmime_force_encoding(textview->messageview->forced_encoding);
589 textview->is_in_signature = FALSE;
591 procmime_decode_content(mimeinfo);
593 if (!g_ascii_strcasecmp(mimeinfo->subtype, "html")) {
596 filename = procmime_get_tmp_file_name(mimeinfo);
597 if (procmime_get_part(filename, mimeinfo) == 0) {
598 tmpfp = fopen(filename, "rb");
599 textview_show_html(textview, tmpfp, conv);
604 } else if (!g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
607 filename = procmime_get_tmp_file_name(mimeinfo);
608 if (procmime_get_part(filename, mimeinfo) == 0) {
609 tmpfp = fopen(filename, "rb");
610 textview_show_ertf(textview, tmpfp, conv);
616 tmpfp = fopen(mimeinfo->data.filename, "rb");
617 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
618 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
619 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
620 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
621 textview_write_line(textview, buf, conv);
625 conv_code_converter_destroy(conv);
626 procmime_force_encoding(0);
629 static void textview_show_html(TextView *textview, FILE *fp,
635 parser = html_parser_new(fp, conv);
636 g_return_if_fail(parser != NULL);
638 while ((str = html_parse(parser)) != NULL) {
639 if (parser->state == HTML_HREF) {
640 /* first time : get and copy the URL */
641 if (parser->href == NULL) {
642 /* ALF - the sylpheed html parser returns an empty string,
643 * if still inside an <a>, but already parsed past HREF */
644 str = strtok(str, " ");
646 parser->href = g_strdup(str);
647 /* the URL may (or not) be followed by the
649 str = strtok(NULL, "");
653 textview_write_link(textview, str, parser->href, NULL);
655 textview_write_line(textview, str, NULL);
657 html_parser_destroy(parser);
660 static void textview_show_ertf(TextView *textview, FILE *fp,
666 parser = ertf_parser_new(fp, conv);
667 g_return_if_fail(parser != NULL);
669 while ((str = ertf_parse(parser)) != NULL) {
670 textview_write_line(textview, str, NULL);
673 ertf_parser_destroy(parser);
676 /* get_uri_part() - retrieves a URI starting from scanpos.
677 Returns TRUE if succesful */
678 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
679 const gchar **bp, const gchar **ep)
683 g_return_val_if_fail(start != NULL, FALSE);
684 g_return_val_if_fail(scanpos != NULL, FALSE);
685 g_return_val_if_fail(bp != NULL, FALSE);
686 g_return_val_if_fail(ep != NULL, FALSE);
690 /* find end point of URI */
691 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
692 if (!isgraph(*(const guchar *)ep_) ||
693 !IS_ASCII(*(const guchar *)ep_) ||
694 strchr("()<>\"", *ep_))
698 /* no punctuation at end of string */
700 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
701 * should pass some URI type to this function and decide on that whether
702 * to perform punctuation stripping */
704 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
706 for (; ep_ - 1 > scanpos + 1 &&
707 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
718 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
720 return g_strndup(bp, ep - bp);
723 /* valid mail address characters */
724 #define IS_RFC822_CHAR(ch) \
729 !strchr("(),;<>\"", (ch)))
731 /* alphabet and number within 7bit ASCII */
732 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && isalnum(ch))
733 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
735 static GHashTable *create_domain_tab(void)
737 static const gchar *toplvl_domains [] = {
739 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
740 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
741 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
742 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
743 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
744 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
745 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
746 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
747 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
748 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
749 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
750 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
751 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
752 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
753 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
754 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
755 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
756 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
757 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
758 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
759 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
760 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
761 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
762 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
763 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
764 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
767 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
769 g_return_val_if_fail(htab, NULL);
770 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
771 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
775 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
777 const gint MAX_LVL_DOM_NAME_LEN = 6;
778 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
779 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
782 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
785 for (p = buf; p < m && first < last; *p++ = *first++)
789 return g_hash_table_lookup(tab, buf) != NULL;
792 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
793 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
794 const gchar **bp, const gchar **ep)
796 /* more complex than the uri part because we need to scan back and forward starting from
797 * the scan position. */
798 gboolean result = FALSE;
799 const gchar *bp_ = NULL;
800 const gchar *ep_ = NULL;
801 static GHashTable *dom_tab;
802 const gchar *last_dot = NULL;
803 const gchar *prelast_dot = NULL;
804 const gchar *last_tld_char = NULL;
806 /* the informative part of the email address (describing the name
807 * of the email address owner) may contain quoted parts. the
808 * closure stack stores the last encountered quotes. */
809 gchar closure_stack[128];
810 gchar *ptr = closure_stack;
812 g_return_val_if_fail(start != NULL, FALSE);
813 g_return_val_if_fail(scanpos != NULL, FALSE);
814 g_return_val_if_fail(bp != NULL, FALSE);
815 g_return_val_if_fail(ep != NULL, FALSE);
818 dom_tab = create_domain_tab();
819 g_return_val_if_fail(dom_tab, FALSE);
821 /* scan start of address */
822 for (bp_ = scanpos - 1;
823 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
826 /* TODO: should start with an alnum? */
828 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
831 if (bp_ != scanpos) {
832 /* scan end of address */
833 for (ep_ = scanpos + 1;
834 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
836 prelast_dot = last_dot;
838 if (*(last_dot + 1) == '.') {
839 if (prelast_dot == NULL)
841 last_dot = prelast_dot;
846 /* TODO: really should terminate with an alnum? */
847 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
852 if (last_dot == NULL)
855 last_dot = prelast_dot;
856 if (last_dot == NULL || (scanpos + 1 >= last_dot))
860 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
861 if (*last_tld_char == '?')
864 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
871 if (!result) return FALSE;
873 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
874 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
877 /* see if this is <bracketed>; in this case we also scan for the informative part. */
878 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
881 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
882 #define IN_STACK() (ptr > closure_stack)
883 /* has underrun check */
884 #define POP_STACK() if(IN_STACK()) --ptr
885 /* has overrun check */
886 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
887 /* has underrun check */
888 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
892 /* scan for the informative part. */
893 for (bp_ -= 2; bp_ >= start; bp_--) {
894 /* if closure on the stack keep scanning */
895 if (PEEK_STACK() == *bp_) {
899 if (*bp_ == '\'' || *bp_ == '"') {
904 /* if nothing in the closure stack, do the special conditions
905 * the following if..else expression simply checks whether
906 * a token is acceptable. if not acceptable, the clause
907 * should terminate the loop with a 'break' */
910 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
911 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
912 /* hyphens are allowed, but only in
914 } else if (!ispunct(*bp_)) {
915 /* but anything not being a punctiation
918 break; /* anything else is rejected */
931 /* scan forward (should start with an alnum) */
932 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
942 #undef IS_ASCII_ALNUM
943 #undef IS_RFC822_CHAR
945 static gchar *make_email_string(const gchar *bp, const gchar *ep)
947 /* returns a mailto: URI; mailto: is also used to detect the
948 * uri type later on in the button_pressed signal handler */
952 tmp = g_strndup(bp, ep - bp);
953 result = g_strconcat("mailto:", tmp, NULL);
959 static gchar *make_http_string(const gchar *bp, const gchar *ep)
961 /* returns an http: URI; */
965 tmp = g_strndup(bp, ep - bp);
966 result = g_strconcat("http://", tmp, NULL);
972 #define ADD_TXT_POS(bp_, ep_, pti_) \
973 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
975 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
978 g_warning("alloc error scanning URIs\n"); \
979 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
985 /* textview_make_clickable_parts() - colorizes clickable parts */
986 static void textview_make_clickable_parts(TextView *textview,
988 const gchar *uri_tag,
989 const gchar *linebuf)
991 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
992 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
995 /* parse table - in order of priority */
997 const gchar *needle; /* token */
999 /* token search function */
1000 gchar *(*search) (const gchar *haystack,
1001 const gchar *needle);
1002 /* part parsing function */
1003 gboolean (*parse) (const gchar *start,
1004 const gchar *scanpos,
1007 /* part to URI function */
1008 gchar *(*build_uri) (const gchar *bp,
1012 static struct table parser[] = {
1013 {"http://", strcasestr, get_uri_part, make_uri_string},
1014 {"https://", strcasestr, get_uri_part, make_uri_string},
1015 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1016 {"www.", strcasestr, get_uri_part, make_http_string},
1017 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1018 {"@", strcasestr, get_email_part, make_email_string}
1020 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1023 const gchar *walk, *bp, *ep;
1026 const gchar *bp, *ep; /* text position */
1027 gint pti; /* index in parse table */
1028 struct txtpos *next; /* next */
1029 } head = {NULL, NULL, 0, NULL}, *last = &head;
1031 gtk_text_buffer_get_end_iter(buffer, &iter);
1033 /* parse for clickable parts, and build a list of begin and end positions */
1034 for (walk = linebuf, n = 0;;) {
1035 gint last_index = PARSE_ELEMS;
1036 gchar *scanpos = NULL;
1038 /* FIXME: this looks phony. scanning for anything in the parse table */
1039 for (n = 0; n < PARSE_ELEMS; n++) {
1042 tmp = parser[n].search(walk, parser[n].needle);
1044 if (scanpos == NULL || tmp < scanpos) {
1052 /* check if URI can be parsed */
1053 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1054 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1055 ADD_TXT_POS(bp, ep, last_index);
1059 strlen(parser[last_index].needle);
1064 /* colorize this line */
1066 const gchar *normal_text = linebuf;
1069 for (last = head.next; last != NULL;
1070 normal_text = last->ep, last = last->next) {
1073 uri = g_new(RemoteURI, 1);
1074 if (last->bp - normal_text > 0)
1075 gtk_text_buffer_insert_with_tags_by_name
1078 last->bp - normal_text,
1080 uri->uri = parser[last->pti].build_uri(last->bp,
1082 uri->start = gtk_text_iter_get_offset(&iter);
1083 gtk_text_buffer_insert_with_tags_by_name
1085 last->bp, last->ep - last->bp,
1087 uri->end = gtk_text_iter_get_offset(&iter);
1088 textview->uri_list =
1089 g_slist_append(textview->uri_list, uri);
1093 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1097 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1105 static void textview_write_line(TextView *textview, const gchar *str,
1106 CodeConverter *conv)
1109 GtkTextBuffer *buffer;
1111 gchar buf[BUFFSIZE];
1113 gint quotelevel = -1;
1114 gchar quote_tag_str[10];
1116 text = GTK_TEXT_VIEW(textview->text);
1117 buffer = gtk_text_view_get_buffer(text);
1118 gtk_text_buffer_get_end_iter(buffer, &iter);
1121 strncpy2(buf, str, sizeof(buf));
1122 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
1123 conv_localetodisp(buf, sizeof(buf), str);
1127 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1130 /* change color of quotation
1131 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1132 Up to 3 levels of quotations are detected, and each
1133 level is colored using a different color. */
1134 if (prefs_common.enable_color
1135 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1136 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1138 /* set up the correct foreground color */
1139 if (quotelevel > 2) {
1140 /* recycle colors */
1141 if (prefs_common.recycle_quote_colors)
1148 if (quotelevel == -1)
1151 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1152 "quote%d", quotelevel);
1153 fg_color = quote_tag_str;
1156 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1157 fg_color = "signature";
1158 textview->is_in_signature = TRUE;
1161 if (prefs_common.enable_color)
1162 textview_make_clickable_parts(textview, fg_color, "link", buf);
1164 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1167 void textview_write_link(TextView *textview, const gchar *str,
1168 const gchar *uri, CodeConverter *conv)
1170 GdkColor *link_color = NULL;
1172 GtkTextBuffer *buffer;
1174 gchar buf[BUFFSIZE];
1181 text = GTK_TEXT_VIEW(textview->text);
1182 buffer = gtk_text_view_get_buffer(text);
1183 gtk_text_buffer_get_end_iter(buffer, &iter);
1188 if (textview->text_is_mb)
1189 conv_localetodisp(buf, sizeof(buf), str);
1191 strncpy2(buf, str, sizeof(buf));
1192 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1193 conv_localetodisp(buf, sizeof(buf), str);
1194 else if (textview->text_is_mb)
1195 conv_unreadable_locale(buf);
1198 strncpy2(buf, str, sizeof(buf));
1199 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1200 conv_localetodisp(buf, sizeof(buf), str);
1205 gtk_text_buffer_get_end_iter(buffer, &iter);
1207 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1208 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1210 if (prefs_common.enable_color) {
1211 link_color = &uri_color;
1213 r_uri = g_new(RemoteURI, 1);
1214 r_uri->uri = g_strdup(uri);
1215 r_uri->start = gtk_text_iter_get_offset(&iter);
1216 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, bufp, -1,
1218 r_uri->end = gtk_text_iter_get_offset(&iter);
1219 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1222 void textview_clear(TextView *textview)
1224 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1225 GtkTextBuffer *buffer;
1227 buffer = gtk_text_view_get_buffer(text);
1228 gtk_text_buffer_set_text(buffer, "\0", -1);
1230 TEXTVIEW_STATUSBAR_POP(textview);
1231 textview_uri_list_remove_all(textview->uri_list);
1232 textview->uri_list = NULL;
1234 textview->body_pos = 0;
1237 void textview_destroy(TextView *textview)
1239 textview_uri_list_remove_all(textview->uri_list);
1240 textview->uri_list = NULL;
1245 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1247 textview->show_all_headers = all_headers;
1250 void textview_set_font(TextView *textview, const gchar *codeset)
1252 if (prefs_common.textfont) {
1253 PangoFontDescription *font_desc = NULL;
1255 if (prefs_common.textfont)
1256 font_desc = pango_font_description_from_string
1257 (prefs_common.textfont);
1259 gtk_widget_modify_font(textview->text, font_desc);
1260 pango_font_description_free(font_desc);
1263 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1264 prefs_common.line_space / 2);
1265 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1266 prefs_common.line_space / 2);
1267 if (prefs_common.head_space) {
1268 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 6);
1270 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 0);
1274 void textview_set_text(TextView *textview, const gchar *text)
1277 GtkTextBuffer *buffer;
1279 g_return_if_fail(textview != NULL);
1280 g_return_if_fail(text != NULL);
1282 textview_clear(textview);
1284 view = GTK_TEXT_VIEW(textview->text);
1285 buffer = gtk_text_view_get_buffer(view);
1286 gtk_text_buffer_set_text(buffer, text, strlen(text));
1302 H_ORGANIZATION = 11,
1305 void textview_set_position(TextView *textview, gint pos)
1307 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1308 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1311 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1312 gtk_text_buffer_place_cursor(buffer, &iter);
1313 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1316 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1318 gchar buf[BUFFSIZE];
1319 GPtrArray *headers, *sorted_headers;
1320 GSList *disphdr_list;
1324 g_return_val_if_fail(fp != NULL, NULL);
1326 if (textview->show_all_headers)
1327 return procheader_get_header_array_asis(fp);
1329 if (!prefs_common.display_header) {
1330 while (fgets(buf, sizeof(buf), fp) != NULL)
1331 if (buf[0] == '\r' || buf[0] == '\n') break;
1335 headers = procheader_get_header_array_asis(fp);
1337 sorted_headers = g_ptr_array_new();
1339 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1340 disphdr_list = disphdr_list->next) {
1341 DisplayHeaderProp *dp =
1342 (DisplayHeaderProp *)disphdr_list->data;
1344 for (i = 0; i < headers->len; i++) {
1345 header = g_ptr_array_index(headers, i);
1347 if (procheader_headername_equal(header->name,
1350 procheader_header_free(header);
1352 g_ptr_array_add(sorted_headers, header);
1354 g_ptr_array_remove_index(headers, i);
1360 if (prefs_common.show_other_header) {
1361 for (i = 0; i < headers->len; i++) {
1362 header = g_ptr_array_index(headers, i);
1363 g_ptr_array_add(sorted_headers, header);
1365 g_ptr_array_free(headers, TRUE);
1367 procheader_header_array_destroy(headers);
1370 return sorted_headers;
1373 static void textview_show_header(TextView *textview, GPtrArray *headers)
1375 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1376 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1381 g_return_if_fail(headers != NULL);
1383 for (i = 0; i < headers->len; i++) {
1384 header = g_ptr_array_index(headers, i);
1385 g_return_if_fail(header->name != NULL);
1387 gtk_text_buffer_get_end_iter (buffer, &iter);
1388 gtk_text_buffer_insert_with_tags_by_name
1389 (buffer, &iter, header->name, -1,
1390 "header_title", "header", NULL);
1391 if (header->name[strlen(header->name) - 1] != ' ')
1392 gtk_text_buffer_insert_with_tags_by_name
1393 (buffer, &iter, " ", 1,
1394 "header_title", "header", NULL);
1396 if (procheader_headername_equal(header->name, "Subject") ||
1397 procheader_headername_equal(header->name, "From") ||
1398 procheader_headername_equal(header->name, "To") ||
1399 procheader_headername_equal(header->name, "Cc"))
1400 unfold_line(header->body);
1404 if (textview->text_is_mb == TRUE)
1405 conv_unreadable_locale(header->body);
1408 if (prefs_common.enable_color &&
1409 (procheader_headername_equal(header->name, "X-Mailer") ||
1410 procheader_headername_equal(header->name,
1412 strstr(header->body, "Sylpheed") != NULL) {
1413 gtk_text_buffer_get_end_iter (buffer, &iter);
1414 gtk_text_buffer_insert_with_tags_by_name
1415 (buffer, &iter, header->body, -1,
1416 "header", "emphasis", NULL);
1417 } else if (prefs_common.enable_color) {
1418 textview_make_clickable_parts(textview, "header", "link",
1421 textview_make_clickable_parts(textview, "header", NULL,
1424 gtk_text_buffer_get_end_iter (buffer, &iter);
1425 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1430 gboolean textview_search_string(TextView *textview, const gchar *str,
1433 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1434 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1436 GtkTextIter iter, start, end, real_end, *pos;
1437 gboolean found = FALSE;
1438 gint insert_offset, selbound_offset;
1440 /* reset selection */
1441 mark = gtk_text_buffer_get_mark(buffer, "insert");
1442 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1443 insert_offset = gtk_text_iter_get_offset(&start);
1444 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1445 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1446 selbound_offset = gtk_text_iter_get_offset(&end);
1448 pos = insert_offset > selbound_offset ? &start : &end;
1449 gtk_text_buffer_place_cursor(buffer, pos);
1452 mark = gtk_text_buffer_get_insert(buffer);
1453 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1455 found = gtk_text_iter_forward_search(&iter, str,
1456 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1457 &start, &end, NULL);
1461 gtk_text_buffer_get_end_iter(buffer, &real_end);
1462 text = gtk_text_buffer_get_text(buffer, &iter,
1465 while (!found && i++ < strlen(text) - 1) {
1466 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1469 i += gtk_text_iter_get_offset(&end);
1472 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1473 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1481 gtk_text_buffer_place_cursor(buffer, &start);
1482 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1484 mark = gtk_text_buffer_get_mark(buffer, "insert");
1485 gtk_text_view_scroll_mark_onscreen(text, mark);
1491 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1494 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1495 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1497 GtkTextIter iter, start, real_start, end, *pos;
1498 gboolean found = FALSE;
1499 gint insert_offset, selbound_offset;
1501 /* reset selection */
1502 mark = gtk_text_buffer_get_mark(buffer, "insert");
1503 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1504 insert_offset = gtk_text_iter_get_offset(&start);
1505 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1506 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1507 selbound_offset = gtk_text_iter_get_offset(&end);
1509 pos = insert_offset < selbound_offset ? &start : &end;
1510 gtk_text_buffer_place_cursor(buffer, pos);
1513 mark = gtk_text_buffer_get_insert(buffer);
1514 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1516 found = gtk_text_iter_backward_search(&iter, str,
1517 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1518 &start, &end, NULL);
1522 if (gtk_text_iter_get_offset(&iter) == 0)
1523 gtk_text_buffer_get_end_iter(buffer, &iter);
1525 i = gtk_text_iter_get_offset(&iter) - strlen(str) - 1;
1526 gtk_text_buffer_get_start_iter(buffer, &real_start);
1528 text = gtk_text_buffer_get_text(buffer, &real_start,
1531 while (!found && i-- > 0) {
1532 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1536 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1537 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1545 gtk_text_buffer_place_cursor(buffer, &end);
1546 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1548 mark = gtk_text_buffer_get_mark(buffer, "insert");
1549 gtk_text_view_scroll_mark_onscreen(text, mark);
1555 void textview_scroll_one_line(TextView *textview, gboolean up)
1557 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1558 GtkAdjustment *vadj = text->vadjustment;
1561 if (prefs_common.enable_smooth_scroll) {
1562 textview_smooth_scroll_one_line(textview, up);
1567 upper = vadj->upper - vadj->page_size;
1568 if (vadj->value < upper) {
1570 vadj->step_increment * 4;
1572 MIN(vadj->value, upper);
1573 g_signal_emit_by_name(G_OBJECT(vadj),
1574 "value_changed", 0);
1577 if (vadj->value > 0.0) {
1579 vadj->step_increment * 4;
1581 MAX(vadj->value, 0.0);
1582 g_signal_emit_by_name(G_OBJECT(vadj),
1583 "value_changed", 0);
1588 gboolean textview_scroll_page(TextView *textview, gboolean up)
1590 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1591 GtkAdjustment *vadj = text->vadjustment;
1595 if (prefs_common.enable_smooth_scroll)
1596 return textview_smooth_scroll_page(textview, up);
1598 if (prefs_common.scroll_halfpage)
1599 page_incr = vadj->page_increment / 2;
1601 page_incr = vadj->page_increment;
1604 upper = vadj->upper - vadj->page_size;
1605 if (vadj->value < upper) {
1606 vadj->value += page_incr;
1607 vadj->value = MIN(vadj->value, upper);
1608 g_signal_emit_by_name(G_OBJECT(vadj),
1609 "value_changed", 0);
1613 if (vadj->value > 0.0) {
1614 vadj->value -= page_incr;
1615 vadj->value = MAX(vadj->value, 0.0);
1616 g_signal_emit_by_name(G_OBJECT(vadj),
1617 "value_changed", 0);
1625 static void textview_smooth_scroll_do(TextView *textview,
1626 gfloat old_value, gfloat last_value,
1629 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1630 GtkAdjustment *vadj = text->vadjustment;
1635 if (old_value < last_value) {
1636 change_value = last_value - old_value;
1639 change_value = old_value - last_value;
1643 for (i = step; i <= change_value; i += step) {
1644 vadj->value = old_value + (up ? -i : i);
1645 g_signal_emit_by_name(G_OBJECT(vadj),
1646 "value_changed", 0);
1649 vadj->value = last_value;
1650 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1653 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1655 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1656 GtkAdjustment *vadj = text->vadjustment;
1662 upper = vadj->upper - vadj->page_size;
1663 if (vadj->value < upper) {
1664 old_value = vadj->value;
1665 last_value = vadj->value +
1666 vadj->step_increment * 4;
1667 last_value = MIN(last_value, upper);
1669 textview_smooth_scroll_do(textview, old_value,
1671 prefs_common.scroll_step);
1674 if (vadj->value > 0.0) {
1675 old_value = vadj->value;
1676 last_value = vadj->value -
1677 vadj->step_increment * 4;
1678 last_value = MAX(last_value, 0.0);
1680 textview_smooth_scroll_do(textview, old_value,
1682 prefs_common.scroll_step);
1687 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1689 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1690 GtkAdjustment *vadj = text->vadjustment;
1696 if (prefs_common.scroll_halfpage)
1697 page_incr = vadj->page_increment / 2;
1699 page_incr = vadj->page_increment;
1702 upper = vadj->upper - vadj->page_size;
1703 if (vadj->value < upper) {
1704 old_value = vadj->value;
1705 last_value = vadj->value + page_incr;
1706 last_value = MIN(last_value, upper);
1708 textview_smooth_scroll_do(textview, old_value,
1710 prefs_common.scroll_step);
1714 if (vadj->value > 0.0) {
1715 old_value = vadj->value;
1716 last_value = vadj->value - page_incr;
1717 last_value = MAX(last_value, 0.0);
1719 textview_smooth_scroll_do(textview, old_value,
1721 prefs_common.scroll_step);
1729 #define KEY_PRESS_EVENT_STOP() \
1730 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1733 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1736 SummaryView *summaryview = NULL;
1737 MessageView *messageview = textview->messageview;
1739 if (!event) return FALSE;
1740 if (messageview->mainwin)
1741 summaryview = messageview->mainwin->summaryview;
1743 switch (event->keyval) {
1758 summary_pass_key_press_event(summaryview, event);
1760 textview_scroll_page(textview, FALSE);
1763 textview_scroll_page(textview, TRUE);
1766 textview_scroll_one_line(textview,
1767 (event->state & GDK_MOD1_MASK) != 0);
1771 summary_pass_key_press_event(summaryview, event);
1776 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1777 KEY_PRESS_EVENT_STOP();
1778 mimeview_pass_key_press_event(messageview->mimeview,
1782 /* possible fall through */
1785 event->window != messageview->mainwin->window->window) {
1786 GdkEventKey tmpev = *event;
1788 tmpev.window = messageview->mainwin->window->window;
1789 KEY_PRESS_EVENT_STOP();
1790 gtk_widget_event(messageview->mainwin->window,
1791 (GdkEvent *)&tmpev);
1800 *\brief Check to see if a web URL has been disguised as a different
1801 * URL (possible with HTML email).
1803 *\param uri The uri to check
1805 *\param textview The TextView the URL is contained in
1807 *\return gboolean TRUE if the URL is ok, or if the user chose to open
1808 * it anyway, otherwise FALSE
1810 static gboolean uri_security_check(RemoteURI *uri, TextView *textview)
1813 gboolean retval = TRUE;
1815 if (g_ascii_strncasecmp(uri->uri, "http:", 5) &&
1816 g_ascii_strncasecmp(uri->uri, "https:", 6) &&
1817 g_ascii_strncasecmp(uri->uri, "www.", 4))
1820 clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1823 if (clicked_str == NULL)
1826 if (strcmp(clicked_str, uri->uri) &&
1827 (!g_ascii_strncasecmp(clicked_str, "http:", 5) ||
1828 !g_ascii_strncasecmp(clicked_str, "https:", 6) ||
1829 !g_ascii_strncasecmp(clicked_str, "www.", 4))) {
1833 /* allow uri->uri == http://somewhere.com
1834 and clicked_str == somewhere.com */
1835 str = g_strconcat("http://", clicked_str, NULL);
1837 if (!g_ascii_strcasecmp(str, uri->uri))
1842 if (retval == FALSE) {
1846 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1847 "the apparent URL (%s). \n"
1849 uri->uri, clicked_str);
1850 resp = alertpanel_with_type(_("Warning"),
1854 NULL, NULL, ALERT_WARNING);
1856 if (resp == G_ALERTDEFAULT)
1859 g_free(clicked_str);
1863 static gboolean textview_motion_notify(GtkWidget *widget,
1864 GdkEventMotion *event,
1867 textview_uri_update(textview, event->x, event->y);
1868 gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1873 static gboolean textview_leave_notify(GtkWidget *widget,
1874 GdkEventCrossing *event,
1877 textview_uri_update(textview, -1, -1);
1882 static gboolean textview_visibility_notify(GtkWidget *widget,
1883 GdkEventVisibility *event,
1888 gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1889 textview_uri_update(textview, wx, wy);
1894 static void textview_uri_update(TextView *textview, gint x, gint y)
1896 GtkTextBuffer *buffer;
1897 GtkTextIter start_iter, end_iter;
1898 RemoteURI *uri = NULL;
1900 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1902 if (x != -1 && y != -1) {
1908 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text),
1909 GTK_TEXT_WINDOW_WIDGET,
1911 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1914 tags = gtk_text_iter_get_tags(&iter);
1915 for (cur = tags; cur != NULL; cur = cur->next) {
1916 GtkTextTag *tag = cur->data;
1919 g_object_get(G_OBJECT(tag), "name", &name, NULL);
1920 if (!strcmp(name, "link")
1921 && textview_get_uri_range(textview, &iter, tag,
1922 &start_iter, &end_iter))
1923 uri = textview_get_uri_from_range(textview,
1935 if (uri != textview->uri_hover) {
1938 if (textview->uri_hover)
1939 gtk_text_buffer_remove_tag_by_name(buffer,
1941 &textview->uri_hover_start_iter,
1942 &textview->uri_hover_end_iter);
1944 textview->uri_hover = uri;
1946 textview->uri_hover_start_iter = start_iter;
1947 textview->uri_hover_end_iter = end_iter;
1950 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1951 GTK_TEXT_WINDOW_TEXT);
1952 gdk_window_set_cursor(window, uri ? hand_cursor : NULL);
1954 TEXTVIEW_STATUSBAR_POP(textview);
1959 gtk_text_buffer_apply_tag_by_name(buffer,
1964 trimmed_uri = trim_string(uri->uri, 60);
1965 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1966 g_free(trimmed_uri);
1971 static gboolean textview_get_uri_range(TextView *textview,
1974 GtkTextIter *start_iter,
1975 GtkTextIter *end_iter)
1977 GtkTextIter _start_iter, _end_iter;
1979 _start_iter = *iter;
1980 if(!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
1981 debug_print("Can't find start.");
1986 if(!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
1987 debug_print("Can't find end");
1991 *start_iter = _start_iter;
1992 *end_iter = _end_iter;
1997 static RemoteURI *textview_get_uri_from_range(TextView *textview,
2000 GtkTextIter *start_iter,
2001 GtkTextIter *end_iter)
2003 gint start_pos, end_pos;
2004 RemoteURI *uri = NULL;
2007 start_pos = gtk_text_iter_get_offset(start_iter);
2008 end_pos = gtk_text_iter_get_offset(end_iter);
2010 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
2011 RemoteURI *uri_ = (RemoteURI *)cur->data;
2013 if (start_pos == uri_->start &&
2014 end_pos == uri_->end) {
2023 static RemoteURI *textview_get_uri(TextView *textview,
2027 GtkTextIter start_iter, end_iter;
2028 RemoteURI *uri = NULL;
2030 if (textview_get_uri_range(textview, iter, tag, &start_iter,
2032 uri = textview_get_uri_from_range(textview, iter, tag,
2033 &start_iter, &end_iter);
2038 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
2039 GdkEvent *event, GtkTextIter *iter,
2042 GdkEventButton *bevent;
2043 RemoteURI *uri = NULL;
2050 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2051 && event->type != GDK_MOTION_NOTIFY)
2054 uri = textview_get_uri(textview, iter, tag);
2058 bevent = (GdkEventButton *) event;
2060 /* doubleclick: open compose / add address / browser */
2061 if ((event->type == GDK_BUTTON_PRESS && bevent->button == 1) ||
2062 bevent->button == 2 || bevent->button == 3) {
2063 if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2064 if (bevent->button == 3) {
2065 gchar *fromname, *fromaddress;
2068 fromaddress = g_strdup(uri->uri + 7);
2069 /* Hiroyuki: please put this function in utils.c! */
2070 fromname = procheader_get_fromname(fromaddress);
2071 extract_address(fromaddress);
2072 g_message("adding from textview %s <%s>", fromname, fromaddress);
2073 /* Add to address book - Match */
2074 addressbook_add_contact( fromname, fromaddress, NULL );
2076 g_free(fromaddress);
2079 PrefsAccount *account = NULL;
2081 if (textview->messageview && textview->messageview->msginfo &&
2082 textview->messageview->msginfo->folder) {
2083 FolderItem *folder_item;
2085 folder_item = textview->messageview->msginfo->folder;
2086 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2087 account = account_find_from_id(folder_item->prefs->default_account);
2089 compose_new(account, uri->uri + 7, NULL);
2093 if (textview_uri_security_check(textview, uri) == TRUE)
2095 prefs_common.uri_cmd);
2104 *\brief Check to see if a web URL has been disguised as a different
2105 * URL (possible with HTML email).
2107 *\param uri The uri to check
2109 *\param textview The TextView the URL is contained in
2111 *\return gboolean TRUE if the URL is ok, or if the user chose to open
2112 * it anyway, otherwise FALSE
2114 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2117 gboolean retval = TRUE;
2119 if (is_uri_string(uri->uri) == FALSE)
2122 visible_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
2123 uri->start, uri->end);
2124 if (visible_str == NULL)
2127 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2129 gchar *visible_uri_path;
2131 uri_path = get_uri_path(uri->uri);
2132 visible_uri_path = get_uri_path(visible_str);
2133 if (strcmp(uri_path, visible_uri_path) != 0)
2137 if (retval == FALSE) {
2141 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2142 "the apparent URL (%s).\n"
2144 uri->uri, visible_str);
2145 aval = alertpanel_with_type(_("Warning"), msg, _("Yes"), _("No"), NULL,
2146 NULL, ALERT_WARNING);
2148 if (aval == G_ALERTDEFAULT)
2152 g_free(visible_str);
2157 static void textview_uri_list_remove_all(GSList *uri_list)
2161 for (cur = uri_list; cur != NULL; cur = cur->next) {
2163 g_free(((RemoteURI *)cur->data)->uri);
2168 g_slist_free(uri_list);