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"
64 static GdkColor quote_colors[3] = {
65 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
66 {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
67 {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
70 static GdkColor signature_color = {
77 static GdkColor uri_color = {
84 static GdkColor emphasis_color = {
92 static GdkColor error_color = {
101 static GdkCursor *hand_cursor = NULL;
102 static GdkCursor *text_cursor = NULL;
104 #define TEXTVIEW_STATUSBAR_PUSH(textview, str) \
106 gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
107 textview->messageview->statusbar_cid, str); \
110 #define TEXTVIEW_STATUSBAR_POP(textview) \
112 gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
113 textview->messageview->statusbar_cid); \
116 static void textview_show_ertf (TextView *textview,
118 CodeConverter *conv);
119 static void textview_add_part (TextView *textview,
121 static void textview_add_parts (TextView *textview,
123 static void textview_write_body (TextView *textview,
125 static void textview_show_html (TextView *textview,
127 CodeConverter *conv);
129 static void textview_write_line (TextView *textview,
131 CodeConverter *conv);
132 static void textview_write_link (TextView *textview,
135 CodeConverter *conv);
137 static GPtrArray *textview_scan_header (TextView *textview,
139 static void textview_show_header (TextView *textview,
142 static gint textview_key_pressed (GtkWidget *widget,
145 static gboolean textview_motion_notify (GtkWidget *widget,
146 GdkEventMotion *motion,
148 static gboolean textview_leave_notify (GtkWidget *widget,
149 GdkEventCrossing *event,
151 static gboolean textview_visibility_notify (GtkWidget *widget,
152 GdkEventVisibility *event,
154 static void textview_uri_update (TextView *textview,
157 static gboolean textview_get_uri_range (TextView *textview,
160 GtkTextIter *start_iter,
161 GtkTextIter *end_iter);
162 static RemoteURI *textview_get_uri_from_range (TextView *textview,
165 GtkTextIter *start_iter,
166 GtkTextIter *end_iter);
167 static RemoteURI *textview_get_uri (TextView *textview,
170 static gboolean textview_uri_button_pressed (GtkTextTag *tag,
176 static void textview_smooth_scroll_do (TextView *textview,
180 static void textview_smooth_scroll_one_line (TextView *textview,
182 static gboolean textview_smooth_scroll_page (TextView *textview,
185 static gboolean textview_uri_security_check (TextView *textview,
187 static void textview_uri_list_remove_all (GSList *uri_list);
189 static void open_uri_cb (TextView *textview,
192 static void copy_uri_cb (TextView *textview,
196 static GtkItemFactoryEntry textview_popup_entries[] =
198 {N_("/_Open link"), NULL, open_uri_cb, 0, NULL},
199 {N_("/_Copy link location"), NULL, copy_uri_cb, 0, NULL},
203 TextView *textview_create(void)
207 GtkWidget *scrolledwin;
209 GtkTextBuffer *buffer;
210 GtkClipboard *clipboard;
211 GtkItemFactory *popupfactory;
212 GtkWidget *popupmenu;
214 PangoFontDescription *font_desc = NULL;
216 debug_print("Creating text view...\n");
217 textview = g_new0(TextView, 1);
219 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
220 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
221 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
222 gtk_widget_set_size_request(scrolledwin, prefs_common.mainview_width, -1);
223 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
226 /* create GtkSText widgets for single-byte and multi-byte character */
227 text = gtk_text_view_new();
228 gtk_widget_add_events(text, GDK_LEAVE_NOTIFY_MASK);
229 gtk_widget_show(text);
230 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
231 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
232 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
234 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
235 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
236 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
238 gtk_widget_ensure_style(text);
240 if (prefs_common.normalfont)
241 font_desc = pango_font_description_from_string
242 (prefs_common.normalfont);
244 gtk_widget_modify_font(text, font_desc);
246 pango_font_description_free(font_desc);
248 gtk_widget_ref(scrolledwin);
250 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
253 hand_cursor = gdk_cursor_new(GDK_HAND2);
255 text_cursor = gdk_cursor_new(GDK_XTERM);
257 g_signal_connect(G_OBJECT(text), "key_press_event",
258 G_CALLBACK(textview_key_pressed),
260 g_signal_connect(G_OBJECT(text), "motion_notify_event",
261 G_CALLBACK(textview_motion_notify),
263 g_signal_connect(G_OBJECT(text), "leave_notify_event",
264 G_CALLBACK(textview_leave_notify),
266 g_signal_connect(G_OBJECT(text), "visibility_notify_event",
267 G_CALLBACK(textview_visibility_notify),
270 gtk_widget_show(scrolledwin);
272 vbox = gtk_vbox_new(FALSE, 0);
273 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
275 gtk_widget_show(vbox);
277 n_entries = sizeof(textview_popup_entries) /
278 sizeof(textview_popup_entries[0]);
279 popupmenu = menu_create_items(textview_popup_entries, n_entries,
280 "<UriPopupMenu>", &popupfactory,
283 textview->vbox = vbox;
284 textview->scrolledwin = scrolledwin;
285 textview->text = text;
286 textview->uri_list = NULL;
287 textview->body_pos = 0;
288 textview->show_all_headers = FALSE;
289 textview->last_buttonpress = GDK_NOTHING;
290 textview->popup_menu = popupmenu;
291 textview->popup_factory = popupfactory;
296 static void textview_create_tags(GtkTextView *text, TextView *textview)
298 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
301 gtk_text_buffer_create_tag(buffer, "header",
302 "pixels-above-lines", 0,
303 "pixels-above-lines-set", TRUE,
304 "pixels-below-lines", 0,
305 "pixels-below-lines-set", TRUE,
307 "left-margin-set", TRUE,
309 gtk_text_buffer_create_tag(buffer, "header_title",
310 "font", prefs_common.boldfont,
312 gtk_text_buffer_create_tag(buffer, "quote0",
313 "foreground-gdk", "e_colors[0],
315 gtk_text_buffer_create_tag(buffer, "quote1",
316 "foreground-gdk", "e_colors[1],
318 gtk_text_buffer_create_tag(buffer, "quote2",
319 "foreground-gdk", "e_colors[2],
321 gtk_text_buffer_create_tag(buffer, "emphasis",
322 "foreground-gdk", &emphasis_color,
324 gtk_text_buffer_create_tag(buffer, "signature",
325 "foreground-gdk", &signature_color,
327 tag = gtk_text_buffer_create_tag(buffer, "link",
328 "foreground-gdk", &uri_color,
330 gtk_text_buffer_create_tag(buffer, "link-hover",
331 "underline", PANGO_UNDERLINE_SINGLE,
334 g_signal_connect(G_OBJECT(tag), "event",
335 G_CALLBACK(textview_uri_button_pressed), textview);
338 void textview_init(TextView *textview)
340 gtkut_widget_disable_theme_engine(textview->text);
341 textview_update_message_colors();
342 textview_set_all_headers(textview, FALSE);
343 textview_set_font(textview, NULL);
345 textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
348 void textview_update_message_colors(void)
350 GdkColor black = {0, 0, 0, 0};
352 if (prefs_common.enable_color) {
353 /* grab the quote colors, converting from an int to a GdkColor */
354 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
356 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
358 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
360 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
362 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
365 quote_colors[0] = quote_colors[1] = quote_colors[2] =
366 uri_color = emphasis_color = signature_color = black;
370 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
375 if ((fp = fopen(file, "rb")) == NULL) {
376 FILE_OP_ERROR(file, "fopen");
380 textview_clear(textview);
382 textview_add_parts(textview, mimeinfo);
386 textview_set_position(textview, 0);
389 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
393 g_return_if_fail(mimeinfo != NULL);
394 g_return_if_fail(fp != NULL);
396 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
397 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822"))) {
398 textview_clear(textview);
399 textview_add_parts(textview, mimeinfo);
403 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
406 textview_clear(textview);
408 if (mimeinfo->type == MIMETYPE_MULTIPART)
409 textview_add_parts(textview, mimeinfo);
411 textview_write_body(textview, mimeinfo);
415 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
418 GtkTextBuffer *buffer;
421 GPtrArray *headers = NULL;
426 g_return_if_fail(mimeinfo != NULL);
427 text = GTK_TEXT_VIEW(textview->text);
428 buffer = gtk_text_view_get_buffer(text);
429 charcount = gtk_text_buffer_get_char_count(buffer);
430 gtk_text_buffer_get_end_iter(buffer, &iter);
432 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
434 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
437 fp = fopen(mimeinfo->data.filename, "rb");
438 fseek(fp, mimeinfo->offset, SEEK_SET);
439 headers = textview_scan_header(textview, fp);
442 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
443 textview_show_header(textview, headers);
444 procheader_header_array_destroy(headers);
450 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
451 content_type = procmime_get_content_type_str(mimeinfo->type,
454 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
456 g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n",
457 name, content_type, mimeinfo->length);
459 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
460 content_type, mimeinfo->length);
462 g_free(content_type);
464 if (mimeinfo->type != MIMETYPE_TEXT) {
465 gtk_text_buffer_insert(buffer, &iter, buf, -1);
466 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
467 if (prefs_common.display_header && (charcount > 0))
468 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
470 textview_write_body(textview, mimeinfo);
474 static void recursive_add_parts(TextView *textview, GNode *node)
479 mimeinfo = (MimeInfo *) node->data;
481 textview_add_part(textview, mimeinfo);
483 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
484 (mimeinfo->type != MIMETYPE_MESSAGE))
487 if (g_ascii_strcasecmp(mimeinfo->subtype, "alternative") == 0) {
488 GNode * prefered_body;
496 prefered_body = NULL;
499 for (iter = g_node_first_child(node) ; iter != NULL ;
500 iter = g_node_next_sibling(iter)) {
505 submime = (MimeInfo *) iter->data;
506 if (submime->type == MIMETYPE_TEXT)
509 if (submime->subtype != NULL) {
510 if (g_ascii_strcasecmp(submime->subtype, "plain") == 0)
514 if (score > prefered_score) {
515 prefered_score = score;
516 prefered_body = iter;
520 if (prefered_body != NULL) {
521 recursive_add_parts(textview, prefered_body);
525 for (iter = g_node_first_child(node) ; iter != NULL ;
526 iter = g_node_next_sibling(iter)) {
527 recursive_add_parts(textview, iter);
532 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
534 g_return_if_fail(mimeinfo != NULL);
536 recursive_add_parts(textview, mimeinfo->node);
539 #define TEXT_INSERT(str) \
540 gtk_text_buffer_insert(buffer, &iter, str, -1)
542 void textview_show_error(TextView *textview)
545 GtkTextBuffer *buffer;
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(_("This message can't be displayed.\n"));
559 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
562 GtkTextBuffer *buffer;
565 if (!partinfo) return;
567 textview_set_font(textview, NULL);
568 textview_clear(textview);
570 text = GTK_TEXT_VIEW(textview->text);
571 buffer = gtk_text_view_get_buffer(text);
572 gtk_text_buffer_get_start_iter(buffer, &iter);
574 TEXT_INSERT(_("The following can be performed on this part by "));
575 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
577 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
578 TEXT_INSERT(_(" To display as text select 'Display as text' "));
579 TEXT_INSERT(_("(Shortcut key: 't')\n"));
580 TEXT_INSERT(_(" To open with an external program select 'Open' "));
581 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
582 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
583 TEXT_INSERT(_("mouse button),\n"));
584 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
590 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
595 const gchar *charset;
597 if (textview->messageview->forced_charset)
598 charset = textview->messageview->forced_charset;
600 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
602 textview_set_font(textview, charset);
604 conv = conv_code_converter_new(charset);
606 procmime_force_encoding(textview->messageview->forced_encoding);
608 textview->is_in_signature = FALSE;
610 procmime_decode_content(mimeinfo);
612 if (!g_ascii_strcasecmp(mimeinfo->subtype, "html")) {
615 filename = procmime_get_tmp_file_name(mimeinfo);
616 if (procmime_get_part(filename, mimeinfo) == 0) {
617 tmpfp = fopen(filename, "rb");
618 textview_show_html(textview, tmpfp, conv);
623 } else if (!g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
626 filename = procmime_get_tmp_file_name(mimeinfo);
627 if (procmime_get_part(filename, mimeinfo) == 0) {
628 tmpfp = fopen(filename, "rb");
629 textview_show_ertf(textview, tmpfp, conv);
635 tmpfp = fopen(mimeinfo->data.filename, "rb");
636 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
637 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
638 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
639 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
640 textview_write_line(textview, buf, conv);
644 conv_code_converter_destroy(conv);
645 procmime_force_encoding(0);
648 static void textview_show_html(TextView *textview, FILE *fp,
654 parser = html_parser_new(fp, conv);
655 g_return_if_fail(parser != NULL);
657 while ((str = html_parse(parser)) != NULL) {
658 if (parser->state == HTML_HREF) {
659 /* first time : get and copy the URL */
660 if (parser->href == NULL) {
661 /* ALF - the sylpheed html parser returns an empty string,
662 * if still inside an <a>, but already parsed past HREF */
663 str = strtok(str, " ");
665 parser->href = g_strdup(str);
666 /* the URL may (or not) be followed by the
668 str = strtok(NULL, "");
672 textview_write_link(textview, str, parser->href, NULL);
674 textview_write_line(textview, str, NULL);
676 html_parser_destroy(parser);
679 static void textview_show_ertf(TextView *textview, FILE *fp,
685 parser = ertf_parser_new(fp, conv);
686 g_return_if_fail(parser != NULL);
688 while ((str = ertf_parse(parser)) != NULL) {
689 textview_write_line(textview, str, NULL);
692 ertf_parser_destroy(parser);
695 /* get_uri_part() - retrieves a URI starting from scanpos.
696 Returns TRUE if succesful */
697 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
698 const gchar **bp, const gchar **ep)
702 g_return_val_if_fail(start != NULL, FALSE);
703 g_return_val_if_fail(scanpos != NULL, FALSE);
704 g_return_val_if_fail(bp != NULL, FALSE);
705 g_return_val_if_fail(ep != NULL, FALSE);
709 /* find end point of URI */
710 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
711 if (!isgraph(*(const guchar *)ep_) ||
712 !IS_ASCII(*(const guchar *)ep_) ||
713 strchr("()<>\"", *ep_))
717 /* no punctuation at end of string */
719 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
720 * should pass some URI type to this function and decide on that whether
721 * to perform punctuation stripping */
723 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
725 for (; ep_ - 1 > scanpos + 1 &&
726 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
737 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
739 return g_strndup(bp, ep - bp);
742 /* valid mail address characters */
743 #define IS_RFC822_CHAR(ch) \
748 !strchr("(),;<>\"", (ch)))
750 /* alphabet and number within 7bit ASCII */
751 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && isalnum(ch))
752 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
754 static GHashTable *create_domain_tab(void)
756 static const gchar *toplvl_domains [] = {
758 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
759 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
760 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
761 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
762 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
763 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
764 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
765 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
766 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
767 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
768 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
769 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
770 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
771 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
772 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
773 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
774 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
775 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
776 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
777 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
778 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
779 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
780 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
781 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
782 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
783 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
786 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
788 g_return_val_if_fail(htab, NULL);
789 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
790 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
794 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
796 const gint MAX_LVL_DOM_NAME_LEN = 6;
797 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
798 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
801 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
804 for (p = buf; p < m && first < last; *p++ = *first++)
808 return g_hash_table_lookup(tab, buf) != NULL;
811 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
812 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
813 const gchar **bp, const gchar **ep)
815 /* more complex than the uri part because we need to scan back and forward starting from
816 * the scan position. */
817 gboolean result = FALSE;
818 const gchar *bp_ = NULL;
819 const gchar *ep_ = NULL;
820 static GHashTable *dom_tab;
821 const gchar *last_dot = NULL;
822 const gchar *prelast_dot = NULL;
823 const gchar *last_tld_char = NULL;
825 /* the informative part of the email address (describing the name
826 * of the email address owner) may contain quoted parts. the
827 * closure stack stores the last encountered quotes. */
828 gchar closure_stack[128];
829 gchar *ptr = closure_stack;
831 g_return_val_if_fail(start != NULL, FALSE);
832 g_return_val_if_fail(scanpos != NULL, FALSE);
833 g_return_val_if_fail(bp != NULL, FALSE);
834 g_return_val_if_fail(ep != NULL, FALSE);
837 dom_tab = create_domain_tab();
838 g_return_val_if_fail(dom_tab, FALSE);
840 /* scan start of address */
841 for (bp_ = scanpos - 1;
842 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
845 /* TODO: should start with an alnum? */
847 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
850 if (bp_ != scanpos) {
851 /* scan end of address */
852 for (ep_ = scanpos + 1;
853 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
855 prelast_dot = last_dot;
857 if (*(last_dot + 1) == '.') {
858 if (prelast_dot == NULL)
860 last_dot = prelast_dot;
865 /* TODO: really should terminate with an alnum? */
866 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
871 if (last_dot == NULL)
874 last_dot = prelast_dot;
875 if (last_dot == NULL || (scanpos + 1 >= last_dot))
879 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
880 if (*last_tld_char == '?')
883 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
890 if (!result) return FALSE;
892 if (*ep_ && *(bp_ - 1) == '"' && *(ep_) == '"'
893 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
894 && IS_RFC822_CHAR(*(ep_ + 3))) {
895 /* this informative part with an @ in it is
896 * followed by the email address */
899 /* go to matching '>' (or next non-rfc822 char, like \n) */
900 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
903 /* include the bracket */
904 if (*ep_ == '>') ep_++;
906 /* include the leading quote */
914 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
915 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
918 /* see if this is <bracketed>; in this case we also scan for the informative part. */
919 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
922 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
923 #define IN_STACK() (ptr > closure_stack)
924 /* has underrun check */
925 #define POP_STACK() if(IN_STACK()) --ptr
926 /* has overrun check */
927 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
928 /* has underrun check */
929 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
933 /* scan for the informative part. */
934 for (bp_ -= 2; bp_ >= start; bp_--) {
935 /* if closure on the stack keep scanning */
936 if (PEEK_STACK() == *bp_) {
940 if (*bp_ == '\'' || *bp_ == '"') {
945 /* if nothing in the closure stack, do the special conditions
946 * the following if..else expression simply checks whether
947 * a token is acceptable. if not acceptable, the clause
948 * should terminate the loop with a 'break' */
951 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
952 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
953 /* hyphens are allowed, but only in
955 } else if (!ispunct(*bp_)) {
956 /* but anything not being a punctiation
959 break; /* anything else is rejected */
972 /* scan forward (should start with an alnum) */
973 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
983 #undef IS_ASCII_ALNUM
984 #undef IS_RFC822_CHAR
986 static gchar *make_email_string(const gchar *bp, const gchar *ep)
988 /* returns a mailto: URI; mailto: is also used to detect the
989 * uri type later on in the button_pressed signal handler */
993 tmp = g_strndup(bp, ep - bp);
994 result = g_strconcat("mailto:", tmp, NULL);
1000 static gchar *make_http_string(const gchar *bp, const gchar *ep)
1002 /* returns an http: URI; */
1006 tmp = g_strndup(bp, ep - bp);
1007 result = g_strconcat("http://", tmp, NULL);
1013 #define ADD_TXT_POS(bp_, ep_, pti_) \
1014 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1015 last = last->next; \
1016 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1017 last->next = NULL; \
1019 g_warning("alloc error scanning URIs\n"); \
1020 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1026 /* textview_make_clickable_parts() - colorizes clickable parts */
1027 static void textview_make_clickable_parts(TextView *textview,
1028 const gchar *fg_tag,
1029 const gchar *uri_tag,
1030 const gchar *linebuf)
1032 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1033 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1036 /* parse table - in order of priority */
1038 const gchar *needle; /* token */
1040 /* token search function */
1041 gchar *(*search) (const gchar *haystack,
1042 const gchar *needle);
1043 /* part parsing function */
1044 gboolean (*parse) (const gchar *start,
1045 const gchar *scanpos,
1048 /* part to URI function */
1049 gchar *(*build_uri) (const gchar *bp,
1053 static struct table parser[] = {
1054 {"http://", strcasestr, get_uri_part, make_uri_string},
1055 {"https://", strcasestr, get_uri_part, make_uri_string},
1056 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1057 {"www.", strcasestr, get_uri_part, make_http_string},
1058 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1059 {"@", strcasestr, get_email_part, make_email_string}
1061 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1064 const gchar *walk, *bp, *ep;
1067 const gchar *bp, *ep; /* text position */
1068 gint pti; /* index in parse table */
1069 struct txtpos *next; /* next */
1070 } head = {NULL, NULL, 0, NULL}, *last = &head;
1072 gtk_text_buffer_get_end_iter(buffer, &iter);
1074 /* parse for clickable parts, and build a list of begin and end positions */
1075 for (walk = linebuf, n = 0;;) {
1076 gint last_index = PARSE_ELEMS;
1077 gchar *scanpos = NULL;
1079 /* FIXME: this looks phony. scanning for anything in the parse table */
1080 for (n = 0; n < PARSE_ELEMS; n++) {
1083 tmp = parser[n].search(walk, parser[n].needle);
1085 if (scanpos == NULL || tmp < scanpos) {
1093 /* check if URI can be parsed */
1094 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1095 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1096 ADD_TXT_POS(bp, ep, last_index);
1100 strlen(parser[last_index].needle);
1105 /* colorize this line */
1107 const gchar *normal_text = linebuf;
1110 for (last = head.next; last != NULL;
1111 normal_text = last->ep, last = last->next) {
1113 uri = g_new(RemoteURI, 1);
1114 if (last->bp - normal_text > 0)
1115 gtk_text_buffer_insert_with_tags_by_name
1118 last->bp - normal_text,
1120 uri->uri = parser[last->pti].build_uri(last->bp,
1122 uri->start = gtk_text_iter_get_offset(&iter);
1123 gtk_text_buffer_insert_with_tags_by_name
1125 last->bp, last->ep - last->bp,
1127 uri->end = gtk_text_iter_get_offset(&iter);
1128 textview->uri_list =
1129 g_slist_append(textview->uri_list, uri);
1133 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1137 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1145 static void textview_write_line(TextView *textview, const gchar *str,
1146 CodeConverter *conv)
1149 GtkTextBuffer *buffer;
1151 gchar buf[BUFFSIZE];
1153 gint quotelevel = -1;
1154 gchar quote_tag_str[10];
1156 text = GTK_TEXT_VIEW(textview->text);
1157 buffer = gtk_text_view_get_buffer(text);
1158 gtk_text_buffer_get_end_iter(buffer, &iter);
1161 strncpy2(buf, str, sizeof(buf));
1162 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
1163 conv_localetodisp(buf, sizeof(buf), str);
1167 if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1170 /* change color of quotation
1171 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1172 Up to 3 levels of quotations are detected, and each
1173 level is colored using a different color. */
1174 if (prefs_common.enable_color
1175 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1176 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1178 /* set up the correct foreground color */
1179 if (quotelevel > 2) {
1180 /* recycle colors */
1181 if (prefs_common.recycle_quote_colors)
1188 if (quotelevel == -1)
1191 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1192 "quote%d", quotelevel);
1193 fg_color = quote_tag_str;
1196 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1197 fg_color = "signature";
1198 textview->is_in_signature = TRUE;
1201 if (prefs_common.enable_color)
1202 textview_make_clickable_parts(textview, fg_color, "link", buf);
1204 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1207 void textview_write_link(TextView *textview, const gchar *str,
1208 const gchar *uri, CodeConverter *conv)
1210 GdkColor *link_color = NULL;
1212 GtkTextBuffer *buffer;
1214 gchar buf[BUFFSIZE];
1221 text = GTK_TEXT_VIEW(textview->text);
1222 buffer = gtk_text_view_get_buffer(text);
1223 gtk_text_buffer_get_end_iter(buffer, &iter);
1228 if (textview->text_is_mb)
1229 conv_localetodisp(buf, sizeof(buf), str);
1231 strncpy2(buf, str, sizeof(buf));
1232 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1233 conv_localetodisp(buf, sizeof(buf), str);
1234 else if (textview->text_is_mb)
1235 conv_unreadable_locale(buf);
1238 strncpy2(buf, str, sizeof(buf));
1239 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1240 conv_localetodisp(buf, sizeof(buf), str);
1245 gtk_text_buffer_get_end_iter(buffer, &iter);
1247 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1248 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1250 if (prefs_common.enable_color) {
1251 link_color = &uri_color;
1253 r_uri = g_new(RemoteURI, 1);
1254 r_uri->uri = g_strdup(uri);
1255 r_uri->start = gtk_text_iter_get_offset(&iter);
1256 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, bufp, -1,
1258 r_uri->end = gtk_text_iter_get_offset(&iter);
1259 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1262 void textview_clear(TextView *textview)
1264 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1265 GtkTextBuffer *buffer;
1267 buffer = gtk_text_view_get_buffer(text);
1268 gtk_text_buffer_set_text(buffer, "\0", -1);
1270 TEXTVIEW_STATUSBAR_POP(textview);
1271 textview_uri_list_remove_all(textview->uri_list);
1272 textview->uri_list = NULL;
1274 textview->body_pos = 0;
1277 void textview_destroy(TextView *textview)
1279 textview_uri_list_remove_all(textview->uri_list);
1280 textview->uri_list = NULL;
1285 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1287 textview->show_all_headers = all_headers;
1290 void textview_set_font(TextView *textview, const gchar *codeset)
1292 if (prefs_common.textfont) {
1293 PangoFontDescription *font_desc = NULL;
1295 if (prefs_common.textfont)
1296 font_desc = pango_font_description_from_string
1297 (prefs_common.textfont);
1299 gtk_widget_modify_font(textview->text, font_desc);
1300 pango_font_description_free(font_desc);
1303 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1304 prefs_common.line_space / 2);
1305 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1306 prefs_common.line_space / 2);
1307 if (prefs_common.head_space) {
1308 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 6);
1310 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 0);
1314 void textview_set_text(TextView *textview, const gchar *text)
1317 GtkTextBuffer *buffer;
1319 g_return_if_fail(textview != NULL);
1320 g_return_if_fail(text != NULL);
1322 textview_clear(textview);
1324 view = GTK_TEXT_VIEW(textview->text);
1325 buffer = gtk_text_view_get_buffer(view);
1326 gtk_text_buffer_set_text(buffer, text, strlen(text));
1342 H_ORGANIZATION = 11,
1345 void textview_set_position(TextView *textview, gint pos)
1347 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1348 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1351 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1352 gtk_text_buffer_place_cursor(buffer, &iter);
1353 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1356 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1358 gchar buf[BUFFSIZE];
1359 GPtrArray *headers, *sorted_headers;
1360 GSList *disphdr_list;
1364 g_return_val_if_fail(fp != NULL, NULL);
1366 if (textview->show_all_headers)
1367 return procheader_get_header_array_asis(fp);
1369 if (!prefs_common.display_header) {
1370 while (fgets(buf, sizeof(buf), fp) != NULL)
1371 if (buf[0] == '\r' || buf[0] == '\n') break;
1375 headers = procheader_get_header_array_asis(fp);
1377 sorted_headers = g_ptr_array_new();
1379 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1380 disphdr_list = disphdr_list->next) {
1381 DisplayHeaderProp *dp =
1382 (DisplayHeaderProp *)disphdr_list->data;
1384 for (i = 0; i < headers->len; i++) {
1385 header = g_ptr_array_index(headers, i);
1387 if (procheader_headername_equal(header->name,
1390 procheader_header_free(header);
1392 g_ptr_array_add(sorted_headers, header);
1394 g_ptr_array_remove_index(headers, i);
1400 if (prefs_common.show_other_header) {
1401 for (i = 0; i < headers->len; i++) {
1402 header = g_ptr_array_index(headers, i);
1403 g_ptr_array_add(sorted_headers, header);
1405 g_ptr_array_free(headers, TRUE);
1407 procheader_header_array_destroy(headers);
1410 return sorted_headers;
1413 static void textview_show_header(TextView *textview, GPtrArray *headers)
1415 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1416 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1421 g_return_if_fail(headers != NULL);
1423 for (i = 0; i < headers->len; i++) {
1424 header = g_ptr_array_index(headers, i);
1425 g_return_if_fail(header->name != NULL);
1427 gtk_text_buffer_get_end_iter (buffer, &iter);
1428 gtk_text_buffer_insert_with_tags_by_name
1429 (buffer, &iter, header->name, -1,
1430 "header_title", "header", NULL);
1431 if (header->name[strlen(header->name) - 1] != ' ')
1432 gtk_text_buffer_insert_with_tags_by_name
1433 (buffer, &iter, " ", 1,
1434 "header_title", "header", NULL);
1436 if (procheader_headername_equal(header->name, "Subject") ||
1437 procheader_headername_equal(header->name, "From") ||
1438 procheader_headername_equal(header->name, "To") ||
1439 procheader_headername_equal(header->name, "Cc"))
1440 unfold_line(header->body);
1444 if (textview->text_is_mb == TRUE)
1445 conv_unreadable_locale(header->body);
1448 if (prefs_common.enable_color &&
1449 (procheader_headername_equal(header->name, "X-Mailer") ||
1450 procheader_headername_equal(header->name,
1452 strstr(header->body, "Sylpheed") != NULL) {
1453 gtk_text_buffer_get_end_iter (buffer, &iter);
1454 gtk_text_buffer_insert_with_tags_by_name
1455 (buffer, &iter, header->body, -1,
1456 "header", "emphasis", NULL);
1457 } else if (prefs_common.enable_color) {
1458 textview_make_clickable_parts(textview, "header", "link",
1461 textview_make_clickable_parts(textview, "header", NULL,
1464 gtk_text_buffer_get_end_iter (buffer, &iter);
1465 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1470 gboolean textview_search_string(TextView *textview, const gchar *str,
1473 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1474 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1476 GtkTextIter iter, start, end, real_end, *pos;
1477 gboolean found = FALSE;
1478 gint insert_offset, selbound_offset;
1480 /* reset selection */
1481 mark = gtk_text_buffer_get_mark(buffer, "insert");
1482 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1483 insert_offset = gtk_text_iter_get_offset(&start);
1484 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1485 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1486 selbound_offset = gtk_text_iter_get_offset(&end);
1488 pos = insert_offset > selbound_offset ? &start : &end;
1489 gtk_text_buffer_place_cursor(buffer, pos);
1492 mark = gtk_text_buffer_get_insert(buffer);
1493 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1495 found = gtk_text_iter_forward_search(&iter, str,
1496 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1497 &start, &end, NULL);
1501 gtk_text_buffer_get_end_iter(buffer, &real_end);
1502 text = gtk_text_buffer_get_text(buffer, &iter,
1505 while (!found && i++ < strlen(text) - 1) {
1506 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1509 i += gtk_text_iter_get_offset(&end);
1512 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1513 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1521 gtk_text_buffer_place_cursor(buffer, &start);
1522 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1524 mark = gtk_text_buffer_get_mark(buffer, "insert");
1525 gtk_text_view_scroll_mark_onscreen(text, mark);
1531 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1534 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1535 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1537 GtkTextIter iter, start, real_start, end, *pos;
1538 gboolean found = FALSE;
1539 gint insert_offset, selbound_offset;
1541 /* reset selection */
1542 mark = gtk_text_buffer_get_mark(buffer, "insert");
1543 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1544 insert_offset = gtk_text_iter_get_offset(&start);
1545 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1546 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1547 selbound_offset = gtk_text_iter_get_offset(&end);
1549 pos = insert_offset < selbound_offset ? &start : &end;
1550 gtk_text_buffer_place_cursor(buffer, pos);
1553 mark = gtk_text_buffer_get_insert(buffer);
1554 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1556 found = gtk_text_iter_backward_search(&iter, str,
1557 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1558 &start, &end, NULL);
1562 if (gtk_text_iter_get_offset(&iter) == 0)
1563 gtk_text_buffer_get_end_iter(buffer, &iter);
1565 i = gtk_text_iter_get_offset(&iter) - strlen(str) - 1;
1566 gtk_text_buffer_get_start_iter(buffer, &real_start);
1568 text = gtk_text_buffer_get_text(buffer, &real_start,
1571 while (!found && i-- > 0) {
1572 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1576 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1577 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1585 gtk_text_buffer_place_cursor(buffer, &end);
1586 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1588 mark = gtk_text_buffer_get_mark(buffer, "insert");
1589 gtk_text_view_scroll_mark_onscreen(text, mark);
1595 void textview_scroll_one_line(TextView *textview, gboolean up)
1597 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1598 GtkAdjustment *vadj = text->vadjustment;
1601 if (prefs_common.enable_smooth_scroll) {
1602 textview_smooth_scroll_one_line(textview, up);
1607 upper = vadj->upper - vadj->page_size;
1608 if (vadj->value < upper) {
1610 vadj->step_increment * 4;
1612 MIN(vadj->value, upper);
1613 g_signal_emit_by_name(G_OBJECT(vadj),
1614 "value_changed", 0);
1617 if (vadj->value > 0.0) {
1619 vadj->step_increment * 4;
1621 MAX(vadj->value, 0.0);
1622 g_signal_emit_by_name(G_OBJECT(vadj),
1623 "value_changed", 0);
1628 gboolean textview_scroll_page(TextView *textview, gboolean up)
1630 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1631 GtkAdjustment *vadj = text->vadjustment;
1635 if (prefs_common.enable_smooth_scroll)
1636 return textview_smooth_scroll_page(textview, up);
1638 if (prefs_common.scroll_halfpage)
1639 page_incr = vadj->page_increment / 2;
1641 page_incr = vadj->page_increment;
1644 upper = vadj->upper - vadj->page_size;
1645 if (vadj->value < upper) {
1646 vadj->value += page_incr;
1647 vadj->value = MIN(vadj->value, upper);
1648 g_signal_emit_by_name(G_OBJECT(vadj),
1649 "value_changed", 0);
1653 if (vadj->value > 0.0) {
1654 vadj->value -= page_incr;
1655 vadj->value = MAX(vadj->value, 0.0);
1656 g_signal_emit_by_name(G_OBJECT(vadj),
1657 "value_changed", 0);
1665 static void textview_smooth_scroll_do(TextView *textview,
1666 gfloat old_value, gfloat last_value,
1669 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1670 GtkAdjustment *vadj = text->vadjustment;
1675 if (old_value < last_value) {
1676 change_value = last_value - old_value;
1679 change_value = old_value - last_value;
1683 for (i = step; i <= change_value; i += step) {
1684 vadj->value = old_value + (up ? -i : i);
1685 g_signal_emit_by_name(G_OBJECT(vadj),
1686 "value_changed", 0);
1689 vadj->value = last_value;
1690 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1693 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1695 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1696 GtkAdjustment *vadj = text->vadjustment;
1702 upper = vadj->upper - vadj->page_size;
1703 if (vadj->value < upper) {
1704 old_value = vadj->value;
1705 last_value = vadj->value +
1706 vadj->step_increment * 4;
1707 last_value = MIN(last_value, upper);
1709 textview_smooth_scroll_do(textview, old_value,
1711 prefs_common.scroll_step);
1714 if (vadj->value > 0.0) {
1715 old_value = vadj->value;
1716 last_value = vadj->value -
1717 vadj->step_increment * 4;
1718 last_value = MAX(last_value, 0.0);
1720 textview_smooth_scroll_do(textview, old_value,
1722 prefs_common.scroll_step);
1727 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1729 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1730 GtkAdjustment *vadj = text->vadjustment;
1736 if (prefs_common.scroll_halfpage)
1737 page_incr = vadj->page_increment / 2;
1739 page_incr = vadj->page_increment;
1742 upper = vadj->upper - vadj->page_size;
1743 if (vadj->value < upper) {
1744 old_value = vadj->value;
1745 last_value = vadj->value + page_incr;
1746 last_value = MIN(last_value, upper);
1748 textview_smooth_scroll_do(textview, old_value,
1750 prefs_common.scroll_step);
1754 if (vadj->value > 0.0) {
1755 old_value = vadj->value;
1756 last_value = vadj->value - page_incr;
1757 last_value = MAX(last_value, 0.0);
1759 textview_smooth_scroll_do(textview, old_value,
1761 prefs_common.scroll_step);
1769 #define KEY_PRESS_EVENT_STOP() \
1770 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1773 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1776 SummaryView *summaryview = NULL;
1777 MessageView *messageview = textview->messageview;
1779 if (!event) return FALSE;
1780 if (messageview->mainwin)
1781 summaryview = messageview->mainwin->summaryview;
1783 switch (event->keyval) {
1798 summary_pass_key_press_event(summaryview, event);
1800 textview_scroll_page
1803 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1806 textview_scroll_page(textview, TRUE);
1809 textview_scroll_one_line
1810 (textview, (event->state &
1811 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1815 summary_pass_key_press_event(summaryview, event);
1820 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1821 KEY_PRESS_EVENT_STOP();
1822 mimeview_pass_key_press_event(messageview->mimeview,
1826 /* possible fall through */
1829 event->window != messageview->mainwin->window->window) {
1830 GdkEventKey tmpev = *event;
1832 tmpev.window = messageview->mainwin->window->window;
1833 KEY_PRESS_EVENT_STOP();
1834 gtk_widget_event(messageview->mainwin->window,
1835 (GdkEvent *)&tmpev);
1843 static gboolean textview_motion_notify(GtkWidget *widget,
1844 GdkEventMotion *event,
1847 textview_uri_update(textview, event->x, event->y);
1848 gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1853 static gboolean textview_leave_notify(GtkWidget *widget,
1854 GdkEventCrossing *event,
1857 textview_uri_update(textview, -1, -1);
1862 static gboolean textview_visibility_notify(GtkWidget *widget,
1863 GdkEventVisibility *event,
1869 window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
1870 GTK_TEXT_WINDOW_TEXT);
1872 /* check if occurred for the text window part */
1873 if (window != event->window)
1876 gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1877 textview_uri_update(textview, wx, wy);
1882 static void textview_uri_update(TextView *textview, gint x, gint y)
1884 GtkTextBuffer *buffer;
1885 GtkTextIter start_iter, end_iter;
1886 RemoteURI *uri = NULL;
1888 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1890 if (x != -1 && y != -1) {
1896 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text),
1897 GTK_TEXT_WINDOW_WIDGET,
1899 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1902 tags = gtk_text_iter_get_tags(&iter);
1903 for (cur = tags; cur != NULL; cur = cur->next) {
1904 GtkTextTag *tag = cur->data;
1907 g_object_get(G_OBJECT(tag), "name", &name, NULL);
1908 if (!strcmp(name, "link")
1909 && textview_get_uri_range(textview, &iter, tag,
1910 &start_iter, &end_iter))
1911 uri = textview_get_uri_from_range(textview,
1923 if (uri != textview->uri_hover) {
1926 if (textview->uri_hover)
1927 gtk_text_buffer_remove_tag_by_name(buffer,
1929 &textview->uri_hover_start_iter,
1930 &textview->uri_hover_end_iter);
1932 textview->uri_hover = uri;
1934 textview->uri_hover_start_iter = start_iter;
1935 textview->uri_hover_end_iter = end_iter;
1938 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1939 GTK_TEXT_WINDOW_TEXT);
1940 gdk_window_set_cursor(window, uri ? hand_cursor : text_cursor);
1942 TEXTVIEW_STATUSBAR_POP(textview);
1947 gtk_text_buffer_apply_tag_by_name(buffer,
1952 trimmed_uri = trim_string(uri->uri, 60);
1953 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1954 g_free(trimmed_uri);
1959 static gboolean textview_get_uri_range(TextView *textview,
1962 GtkTextIter *start_iter,
1963 GtkTextIter *end_iter)
1965 GtkTextIter _start_iter, _end_iter;
1968 if (!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
1969 debug_print("Can't find end");
1973 _start_iter = _end_iter;
1974 if (!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
1975 debug_print("Can't find start.");
1979 *start_iter = _start_iter;
1980 *end_iter = _end_iter;
1985 static RemoteURI *textview_get_uri_from_range(TextView *textview,
1988 GtkTextIter *start_iter,
1989 GtkTextIter *end_iter)
1991 gint start_pos, end_pos, cur_pos;
1992 RemoteURI *uri = NULL;
1995 start_pos = gtk_text_iter_get_offset(start_iter);
1996 end_pos = gtk_text_iter_get_offset(end_iter);
1997 cur_pos = gtk_text_iter_get_offset(iter);
1999 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
2000 RemoteURI *uri_ = (RemoteURI *)cur->data;
2001 if (start_pos == uri_->start &&
2002 end_pos == uri_->end) {
2005 } else if (start_pos == uri_->start ||
2006 end_pos == uri_->end) {
2007 /* in case of contiguous links, textview_get_uri_range
2008 * returns a broader range (start of 1st link to end
2010 * In that case, correct link is the one covering
2013 if (uri_->start <= cur_pos && cur_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 (bevent->button == 1 &&
2094 textview_uri_security_check(textview, uri) == TRUE)
2096 prefs_common.uri_cmd);
2097 else if (bevent->button == 3) {
2099 G_OBJECT(textview->popup_menu),
2100 "menu_button", uri);
2101 gtk_menu_popup(GTK_MENU(textview->popup_menu),
2102 NULL, NULL, NULL, NULL,
2103 bevent->button, bevent->time);
2113 *\brief Check to see if a web URL has been disguised as a different
2114 * URL (possible with HTML email).
2116 *\param uri The uri to check
2118 *\param textview The TextView the URL is contained in
2120 *\return gboolean TRUE if the URL is ok, or if the user chose to open
2121 * it anyway, otherwise FALSE
2123 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2126 gboolean retval = TRUE;
2127 GtkTextBuffer *buffer;
2128 GtkTextIter start, end;
2130 if (is_uri_string(uri->uri) == FALSE)
2133 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2135 gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2136 gtk_text_buffer_get_iter_at_offset(buffer, &end, uri->end);
2138 visible_str = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2140 if (visible_str == NULL)
2143 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2145 gchar *visible_uri_path;
2147 uri_path = get_uri_path(uri->uri);
2148 visible_uri_path = get_uri_path(visible_str);
2149 if (strcmp(uri_path, visible_uri_path) != 0)
2153 if (retval == FALSE) {
2157 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2158 "the apparent URL (%s).\n"
2160 uri->uri, visible_str);
2161 aval = alertpanel_with_type(_("Warning"), msg, _("Yes"), _("No"), NULL,
2162 NULL, ALERT_WARNING);
2164 if (aval == G_ALERTDEFAULT)
2168 g_free(visible_str);
2173 static void textview_uri_list_remove_all(GSList *uri_list)
2177 for (cur = uri_list; cur != NULL; cur = cur->next) {
2179 g_free(((RemoteURI *)cur->data)->uri);
2184 g_slist_free(uri_list);
2187 static void open_uri_cb (TextView *textview, guint action, void *data)
2189 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->popup_menu),
2194 if (textview_uri_security_check(textview, uri) == TRUE)
2196 prefs_common.uri_cmd);
2197 g_object_set_data(G_OBJECT(textview->popup_menu), "menu_button",
2201 static void copy_uri_cb (TextView *textview, guint action, void *data)
2203 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->popup_menu),
2208 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), uri->uri, -1);
2209 g_object_set_data(G_OBJECT(textview->popup_menu), "menu_button",