2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2005 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_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
224 gtk_widget_set_size_request
225 (scrolledwin, prefs_common.mainview_width, -1);
227 /* create GtkSText widgets for single-byte and multi-byte character */
228 text = gtk_text_view_new();
229 gtk_widget_add_events(text, GDK_LEAVE_NOTIFY_MASK);
230 gtk_widget_show(text);
231 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
232 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
233 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
234 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
235 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
237 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
238 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
239 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
241 gtk_widget_ensure_style(text);
243 if (prefs_common.normalfont)
244 font_desc = pango_font_description_from_string
245 (prefs_common.normalfont);
247 gtk_widget_modify_font(text, font_desc);
249 pango_font_description_free(font_desc);
251 gtk_widget_ref(scrolledwin);
253 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
256 hand_cursor = gdk_cursor_new(GDK_HAND2);
258 text_cursor = gdk_cursor_new(GDK_XTERM);
260 g_signal_connect(G_OBJECT(text), "key_press_event",
261 G_CALLBACK(textview_key_pressed),
263 g_signal_connect(G_OBJECT(text), "motion_notify_event",
264 G_CALLBACK(textview_motion_notify),
266 g_signal_connect(G_OBJECT(text), "leave_notify_event",
267 G_CALLBACK(textview_leave_notify),
269 g_signal_connect(G_OBJECT(text), "visibility_notify_event",
270 G_CALLBACK(textview_visibility_notify),
273 gtk_widget_show(scrolledwin);
275 vbox = gtk_vbox_new(FALSE, 0);
276 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
278 gtk_widget_show(vbox);
280 n_entries = sizeof(textview_popup_entries) /
281 sizeof(textview_popup_entries[0]);
282 popupmenu = menu_create_items(textview_popup_entries, n_entries,
283 "<UriPopupMenu>", &popupfactory,
286 textview->vbox = vbox;
287 textview->scrolledwin = scrolledwin;
288 textview->text = text;
289 textview->uri_list = NULL;
290 textview->body_pos = 0;
291 textview->show_all_headers = FALSE;
292 textview->last_buttonpress = GDK_NOTHING;
293 textview->popup_menu = popupmenu;
294 textview->popup_factory = popupfactory;
299 static void textview_create_tags(GtkTextView *text, TextView *textview)
301 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
304 gtk_text_buffer_create_tag(buffer, "header",
305 "pixels-above-lines", 0,
306 "pixels-above-lines-set", TRUE,
307 "pixels-below-lines", 0,
308 "pixels-below-lines-set", TRUE,
310 "left-margin-set", TRUE,
312 gtk_text_buffer_create_tag(buffer, "header_title",
313 "font", prefs_common.boldfont,
315 gtk_text_buffer_create_tag(buffer, "quote0",
316 "foreground-gdk", "e_colors[0],
318 gtk_text_buffer_create_tag(buffer, "quote1",
319 "foreground-gdk", "e_colors[1],
321 gtk_text_buffer_create_tag(buffer, "quote2",
322 "foreground-gdk", "e_colors[2],
324 gtk_text_buffer_create_tag(buffer, "emphasis",
325 "foreground-gdk", &emphasis_color,
327 gtk_text_buffer_create_tag(buffer, "signature",
328 "foreground-gdk", &signature_color,
330 tag = gtk_text_buffer_create_tag(buffer, "link",
331 "foreground-gdk", &uri_color,
333 gtk_text_buffer_create_tag(buffer, "link-hover",
334 "underline", PANGO_UNDERLINE_SINGLE,
337 g_signal_connect(G_OBJECT(tag), "event",
338 G_CALLBACK(textview_uri_button_pressed), textview);
341 void textview_init(TextView *textview)
343 textview_update_message_colors();
344 textview_set_all_headers(textview, FALSE);
345 textview_set_font(textview, NULL);
346 textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
349 void textview_update_message_colors(void)
351 GdkColor black = {0, 0, 0, 0};
353 if (prefs_common.enable_color) {
354 /* grab the quote colors, converting from an int to a GdkColor */
355 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
357 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
359 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
361 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
363 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
366 quote_colors[0] = quote_colors[1] = quote_colors[2] =
367 uri_color = emphasis_color = signature_color = black;
371 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
376 if ((fp = fopen(file, "rb")) == NULL) {
377 FILE_OP_ERROR(file, "fopen");
381 textview_clear(textview);
383 textview_add_parts(textview, mimeinfo);
387 textview_set_position(textview, 0);
390 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
394 g_return_if_fail(mimeinfo != NULL);
395 g_return_if_fail(fp != NULL);
397 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
398 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822"))) {
399 textview_clear(textview);
400 textview_add_parts(textview, mimeinfo);
404 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
407 textview_clear(textview);
409 if (mimeinfo->type == MIMETYPE_MULTIPART)
410 textview_add_parts(textview, mimeinfo);
412 textview_write_body(textview, mimeinfo);
416 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
419 GtkTextBuffer *buffer;
422 GPtrArray *headers = NULL;
427 g_return_if_fail(mimeinfo != NULL);
428 text = GTK_TEXT_VIEW(textview->text);
429 buffer = gtk_text_view_get_buffer(text);
430 charcount = gtk_text_buffer_get_char_count(buffer);
431 gtk_text_buffer_get_end_iter(buffer, &iter);
433 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
435 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
438 fp = fopen(mimeinfo->data.filename, "rb");
439 fseek(fp, mimeinfo->offset, SEEK_SET);
440 headers = textview_scan_header(textview, fp);
443 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
444 textview_show_header(textview, headers);
445 procheader_header_array_destroy(headers);
451 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
452 content_type = procmime_get_content_type_str(mimeinfo->type,
455 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
457 g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n",
458 name, content_type, mimeinfo->length);
460 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
461 content_type, mimeinfo->length);
463 g_free(content_type);
465 if (mimeinfo->type != MIMETYPE_TEXT) {
466 gtk_text_buffer_insert(buffer, &iter, buf, -1);
467 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
468 if (prefs_common.display_header && (charcount > 0))
469 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
471 textview_write_body(textview, mimeinfo);
475 static void recursive_add_parts(TextView *textview, GNode *node)
480 mimeinfo = (MimeInfo *) node->data;
482 textview_add_part(textview, mimeinfo);
484 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
485 (mimeinfo->type != MIMETYPE_MESSAGE))
488 if (g_ascii_strcasecmp(mimeinfo->subtype, "alternative") == 0) {
489 GNode * prefered_body;
497 prefered_body = NULL;
500 for (iter = g_node_first_child(node) ; iter != NULL ;
501 iter = g_node_next_sibling(iter)) {
506 submime = (MimeInfo *) iter->data;
507 if (submime->type == MIMETYPE_TEXT)
510 if (submime->subtype != NULL) {
511 if (g_ascii_strcasecmp(submime->subtype, "plain") == 0)
515 if (score > prefered_score) {
516 prefered_score = score;
517 prefered_body = iter;
521 if (prefered_body != NULL) {
522 recursive_add_parts(textview, prefered_body);
526 for (iter = g_node_first_child(node) ; iter != NULL ;
527 iter = g_node_next_sibling(iter)) {
528 recursive_add_parts(textview, iter);
533 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
535 g_return_if_fail(mimeinfo != NULL);
537 recursive_add_parts(textview, mimeinfo->node);
540 #define TEXT_INSERT(str) \
541 gtk_text_buffer_insert(buffer, &iter, str, -1)
543 void textview_show_error(TextView *textview)
546 GtkTextBuffer *buffer;
549 textview_set_font(textview, NULL);
550 textview_clear(textview);
552 text = GTK_TEXT_VIEW(textview->text);
553 buffer = gtk_text_view_get_buffer(text);
554 gtk_text_buffer_get_start_iter(buffer, &iter);
556 TEXT_INSERT(_("This message can't be displayed.\n"));
560 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
563 GtkTextBuffer *buffer;
566 if (!partinfo) return;
568 textview_set_font(textview, NULL);
569 textview_clear(textview);
571 text = GTK_TEXT_VIEW(textview->text);
572 buffer = gtk_text_view_get_buffer(text);
573 gtk_text_buffer_get_start_iter(buffer, &iter);
575 TEXT_INSERT(_("The following can be performed on this part by "));
576 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
578 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
579 TEXT_INSERT(_(" To display as text select 'Display as text' "));
580 TEXT_INSERT(_("(Shortcut key: 't')\n"));
581 TEXT_INSERT(_(" To open with an external program select 'Open' "));
582 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
583 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
584 TEXT_INSERT(_("mouse button),\n"));
585 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
591 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
596 const gchar *charset;
598 if (textview->messageview->forced_charset)
599 charset = textview->messageview->forced_charset;
601 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
603 textview_set_font(textview, charset);
605 conv = conv_code_converter_new(charset);
607 procmime_force_encoding(textview->messageview->forced_encoding);
609 textview->is_in_signature = FALSE;
611 procmime_decode_content(mimeinfo);
613 if (!g_ascii_strcasecmp(mimeinfo->subtype, "html")) {
616 filename = procmime_get_tmp_file_name(mimeinfo);
617 if (procmime_get_part(filename, mimeinfo) == 0) {
618 tmpfp = fopen(filename, "rb");
619 textview_show_html(textview, tmpfp, conv);
624 } else if (!g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
627 filename = procmime_get_tmp_file_name(mimeinfo);
628 if (procmime_get_part(filename, mimeinfo) == 0) {
629 tmpfp = fopen(filename, "rb");
630 textview_show_ertf(textview, tmpfp, conv);
636 tmpfp = fopen(mimeinfo->data.filename, "rb");
637 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
638 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
639 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
640 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
641 textview_write_line(textview, buf, conv);
645 conv_code_converter_destroy(conv);
646 procmime_force_encoding(0);
649 static void textview_show_html(TextView *textview, FILE *fp,
655 parser = html_parser_new(fp, conv);
656 g_return_if_fail(parser != NULL);
658 while ((str = html_parse(parser)) != NULL) {
659 if (parser->state == HTML_HREF) {
660 /* first time : get and copy the URL */
661 if (parser->href == NULL) {
662 /* ALF - the sylpheed html parser returns an empty string,
663 * if still inside an <a>, but already parsed past HREF */
664 str = strtok(str, " ");
666 parser->href = g_strdup(str);
667 /* the URL may (or not) be followed by the
669 str = strtok(NULL, "");
673 textview_write_link(textview, str, parser->href, NULL);
675 textview_write_line(textview, str, NULL);
677 html_parser_destroy(parser);
680 static void textview_show_ertf(TextView *textview, FILE *fp,
686 parser = ertf_parser_new(fp, conv);
687 g_return_if_fail(parser != NULL);
689 while ((str = ertf_parse(parser)) != NULL) {
690 textview_write_line(textview, str, NULL);
693 ertf_parser_destroy(parser);
696 /* get_uri_part() - retrieves a URI starting from scanpos.
697 Returns TRUE if succesful */
698 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
699 const gchar **bp, const gchar **ep)
703 g_return_val_if_fail(start != NULL, FALSE);
704 g_return_val_if_fail(scanpos != NULL, FALSE);
705 g_return_val_if_fail(bp != NULL, FALSE);
706 g_return_val_if_fail(ep != NULL, FALSE);
710 /* find end point of URI */
711 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
712 if (!isgraph(*(const guchar *)ep_) ||
713 !IS_ASCII(*(const guchar *)ep_) ||
714 strchr("()<>\"", *ep_))
718 /* no punctuation at end of string */
720 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
721 * should pass some URI type to this function and decide on that whether
722 * to perform punctuation stripping */
724 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
726 for (; ep_ - 1 > scanpos + 1 &&
727 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
738 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
740 return g_strndup(bp, ep - bp);
743 /* valid mail address characters */
744 #define IS_RFC822_CHAR(ch) \
749 !strchr("(),;<>\"", (ch)))
751 /* alphabet and number within 7bit ASCII */
752 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && isalnum(ch))
753 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
755 static GHashTable *create_domain_tab(void)
757 static const gchar *toplvl_domains [] = {
759 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
760 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
761 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
762 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
763 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
764 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
765 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
766 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
767 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
768 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
769 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
770 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
771 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
772 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
773 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
774 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
775 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
776 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
777 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
778 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
779 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
780 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
781 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
782 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
783 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
784 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
787 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
789 g_return_val_if_fail(htab, NULL);
790 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
791 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
795 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
797 const gint MAX_LVL_DOM_NAME_LEN = 6;
798 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
799 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
802 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
805 for (p = buf; p < m && first < last; *p++ = *first++)
809 return g_hash_table_lookup(tab, buf) != NULL;
812 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
813 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
814 const gchar **bp, const gchar **ep)
816 /* more complex than the uri part because we need to scan back and forward starting from
817 * the scan position. */
818 gboolean result = FALSE;
819 const gchar *bp_ = NULL;
820 const gchar *ep_ = NULL;
821 static GHashTable *dom_tab;
822 const gchar *last_dot = NULL;
823 const gchar *prelast_dot = NULL;
824 const gchar *last_tld_char = NULL;
826 /* the informative part of the email address (describing the name
827 * of the email address owner) may contain quoted parts. the
828 * closure stack stores the last encountered quotes. */
829 gchar closure_stack[128];
830 gchar *ptr = closure_stack;
832 g_return_val_if_fail(start != NULL, FALSE);
833 g_return_val_if_fail(scanpos != NULL, FALSE);
834 g_return_val_if_fail(bp != NULL, FALSE);
835 g_return_val_if_fail(ep != NULL, FALSE);
838 dom_tab = create_domain_tab();
839 g_return_val_if_fail(dom_tab, FALSE);
841 /* scan start of address */
842 for (bp_ = scanpos - 1;
843 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
846 /* TODO: should start with an alnum? */
848 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
851 if (bp_ != scanpos) {
852 /* scan end of address */
853 for (ep_ = scanpos + 1;
854 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
856 prelast_dot = last_dot;
858 if (*(last_dot + 1) == '.') {
859 if (prelast_dot == NULL)
861 last_dot = prelast_dot;
866 /* TODO: really should terminate with an alnum? */
867 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
872 if (last_dot == NULL)
875 last_dot = prelast_dot;
876 if (last_dot == NULL || (scanpos + 1 >= last_dot))
880 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
881 if (*last_tld_char == '?')
884 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
891 if (!result) return FALSE;
893 if (*ep_ && *(bp_ - 1) == '"' && *(ep_) == '"'
894 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
895 && IS_RFC822_CHAR(*(ep_ + 3))) {
896 /* this informative part with an @ in it is
897 * followed by the email address */
900 /* go to matching '>' (or next non-rfc822 char, like \n) */
901 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
904 /* include the bracket */
905 if (*ep_ == '>') ep_++;
907 /* include the leading quote */
915 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
916 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
919 /* see if this is <bracketed>; in this case we also scan for the informative part. */
920 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
923 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
924 #define IN_STACK() (ptr > closure_stack)
925 /* has underrun check */
926 #define POP_STACK() if(IN_STACK()) --ptr
927 /* has overrun check */
928 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
929 /* has underrun check */
930 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
934 /* scan for the informative part. */
935 for (bp_ -= 2; bp_ >= start; bp_--) {
936 /* if closure on the stack keep scanning */
937 if (PEEK_STACK() == *bp_) {
941 if (*bp_ == '\'' || *bp_ == '"') {
946 /* if nothing in the closure stack, do the special conditions
947 * the following if..else expression simply checks whether
948 * a token is acceptable. if not acceptable, the clause
949 * should terminate the loop with a 'break' */
952 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
953 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
954 /* hyphens are allowed, but only in
956 } else if (!ispunct(*bp_)) {
957 /* but anything not being a punctiation
960 break; /* anything else is rejected */
973 /* scan forward (should start with an alnum) */
974 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
984 #undef IS_ASCII_ALNUM
985 #undef IS_RFC822_CHAR
987 static gchar *make_email_string(const gchar *bp, const gchar *ep)
989 /* returns a mailto: URI; mailto: is also used to detect the
990 * uri type later on in the button_pressed signal handler */
994 tmp = g_strndup(bp, ep - bp);
995 result = g_strconcat("mailto:", tmp, NULL);
1001 static gchar *make_http_string(const gchar *bp, const gchar *ep)
1003 /* returns an http: URI; */
1007 tmp = g_strndup(bp, ep - bp);
1008 result = g_strconcat("http://", tmp, NULL);
1014 #define ADD_TXT_POS(bp_, ep_, pti_) \
1015 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1016 last = last->next; \
1017 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1018 last->next = NULL; \
1020 g_warning("alloc error scanning URIs\n"); \
1021 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1027 /* textview_make_clickable_parts() - colorizes clickable parts */
1028 static void textview_make_clickable_parts(TextView *textview,
1029 const gchar *fg_tag,
1030 const gchar *uri_tag,
1031 const gchar *linebuf)
1033 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1034 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1037 /* parse table - in order of priority */
1039 const gchar *needle; /* token */
1041 /* token search function */
1042 gchar *(*search) (const gchar *haystack,
1043 const gchar *needle);
1044 /* part parsing function */
1045 gboolean (*parse) (const gchar *start,
1046 const gchar *scanpos,
1049 /* part to URI function */
1050 gchar *(*build_uri) (const gchar *bp,
1054 static struct table parser[] = {
1055 {"http://", strcasestr, get_uri_part, make_uri_string},
1056 {"https://", strcasestr, get_uri_part, make_uri_string},
1057 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1058 {"www.", strcasestr, get_uri_part, make_http_string},
1059 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1060 {"@", strcasestr, get_email_part, make_email_string}
1062 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1065 const gchar *walk, *bp, *ep;
1068 const gchar *bp, *ep; /* text position */
1069 gint pti; /* index in parse table */
1070 struct txtpos *next; /* next */
1071 } head = {NULL, NULL, 0, NULL}, *last = &head;
1073 gtk_text_buffer_get_end_iter(buffer, &iter);
1075 /* parse for clickable parts, and build a list of begin and end positions */
1076 for (walk = linebuf, n = 0;;) {
1077 gint last_index = PARSE_ELEMS;
1078 gchar *scanpos = NULL;
1080 /* FIXME: this looks phony. scanning for anything in the parse table */
1081 for (n = 0; n < PARSE_ELEMS; n++) {
1084 tmp = parser[n].search(walk, parser[n].needle);
1086 if (scanpos == NULL || tmp < scanpos) {
1094 /* check if URI can be parsed */
1095 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1096 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1097 ADD_TXT_POS(bp, ep, last_index);
1101 strlen(parser[last_index].needle);
1106 /* colorize this line */
1108 const gchar *normal_text = linebuf;
1111 for (last = head.next; last != NULL;
1112 normal_text = last->ep, last = last->next) {
1114 uri = g_new(RemoteURI, 1);
1115 if (last->bp - normal_text > 0)
1116 gtk_text_buffer_insert_with_tags_by_name
1119 last->bp - normal_text,
1121 uri->uri = parser[last->pti].build_uri(last->bp,
1123 uri->start = gtk_text_iter_get_offset(&iter);
1124 gtk_text_buffer_insert_with_tags_by_name
1125 (buffer, &iter, last->bp, last->ep - last->bp,
1126 uri_tag, fg_tag, NULL);
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
1134 (buffer, &iter, normal_text, -1, fg_tag, NULL);
1136 gtk_text_buffer_insert_with_tags_by_name
1137 (buffer, &iter, linebuf, -1, fg_tag, NULL);
1143 static void textview_write_line(TextView *textview, const gchar *str,
1144 CodeConverter *conv)
1147 GtkTextBuffer *buffer;
1149 gchar buf[BUFFSIZE];
1151 gint quotelevel = -1;
1152 gchar quote_tag_str[10];
1154 text = GTK_TEXT_VIEW(textview->text);
1155 buffer = gtk_text_view_get_buffer(text);
1156 gtk_text_buffer_get_end_iter(buffer, &iter);
1159 strncpy2(buf, str, sizeof(buf));
1160 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
1161 conv_localetodisp(buf, sizeof(buf), str);
1165 //if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1168 /* change color of quotation
1169 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1170 Up to 3 levels of quotations are detected, and each
1171 level is colored using a different color. */
1172 if (prefs_common.enable_color
1173 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1174 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1176 /* set up the correct foreground color */
1177 if (quotelevel > 2) {
1178 /* recycle colors */
1179 if (prefs_common.recycle_quote_colors)
1186 if (quotelevel == -1)
1189 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1190 "quote%d", quotelevel);
1191 fg_color = quote_tag_str;
1194 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1195 fg_color = "signature";
1196 textview->is_in_signature = TRUE;
1199 if (prefs_common.enable_color)
1200 textview_make_clickable_parts(textview, fg_color, "link", buf);
1202 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1205 void textview_write_link(TextView *textview, const gchar *str,
1206 const gchar *uri, CodeConverter *conv)
1208 GdkColor *link_color = NULL;
1210 GtkTextBuffer *buffer;
1212 gchar buf[BUFFSIZE];
1219 text = GTK_TEXT_VIEW(textview->text);
1220 buffer = gtk_text_view_get_buffer(text);
1221 gtk_text_buffer_get_end_iter(buffer, &iter);
1226 if (textview->text_is_mb)
1227 conv_localetodisp(buf, sizeof(buf), str);
1229 strncpy2(buf, str, sizeof(buf));
1230 } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1231 conv_localetodisp(buf, sizeof(buf), str);
1232 else if (textview->text_is_mb)
1233 conv_unreadable_locale(buf);
1236 strncpy2(buf, str, sizeof(buf));
1237 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1238 conv_localetodisp(buf, sizeof(buf), str);
1243 gtk_text_buffer_get_end_iter(buffer, &iter);
1245 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1246 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1248 if (prefs_common.enable_color) {
1249 link_color = &uri_color;
1251 r_uri = g_new(RemoteURI, 1);
1252 r_uri->uri = g_strdup(uri);
1253 r_uri->start = gtk_text_iter_get_offset(&iter);
1254 gtk_text_buffer_insert_with_tags_by_name
1255 (buffer, &iter, bufp, -1, "link", NULL);
1256 r_uri->end = gtk_text_iter_get_offset(&iter);
1257 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1260 void textview_clear(TextView *textview)
1262 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1263 GtkTextBuffer *buffer;
1265 buffer = gtk_text_view_get_buffer(text);
1266 gtk_text_buffer_set_text(buffer, "", -1);
1268 TEXTVIEW_STATUSBAR_POP(textview);
1269 textview_uri_list_remove_all(textview->uri_list);
1270 textview->uri_list = NULL;
1272 textview->body_pos = 0;
1275 void textview_destroy(TextView *textview)
1277 textview_uri_list_remove_all(textview->uri_list);
1278 textview->uri_list = NULL;
1283 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1285 textview->show_all_headers = all_headers;
1288 void textview_set_font(TextView *textview, const gchar *codeset)
1290 if (prefs_common.textfont) {
1291 PangoFontDescription *font_desc = NULL;
1293 if (prefs_common.textfont)
1294 font_desc = pango_font_description_from_string
1295 (prefs_common.textfont);
1297 gtk_widget_modify_font(textview->text, font_desc);
1298 pango_font_description_free(font_desc);
1301 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1302 prefs_common.line_space / 2);
1303 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1304 prefs_common.line_space / 2);
1307 void textview_set_text(TextView *textview, const gchar *text)
1310 GtkTextBuffer *buffer;
1312 g_return_if_fail(textview != NULL);
1313 g_return_if_fail(text != NULL);
1315 textview_clear(textview);
1317 view = GTK_TEXT_VIEW(textview->text);
1318 buffer = gtk_text_view_get_buffer(view);
1319 gtk_text_buffer_set_text(buffer, text, strlen(text));
1335 H_ORGANIZATION = 11,
1338 void textview_set_position(TextView *textview, gint pos)
1340 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1341 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1344 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1345 gtk_text_buffer_place_cursor(buffer, &iter);
1346 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1349 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1351 gchar buf[BUFFSIZE];
1352 GPtrArray *headers, *sorted_headers;
1353 GSList *disphdr_list;
1357 g_return_val_if_fail(fp != NULL, NULL);
1359 if (textview->show_all_headers)
1360 return procheader_get_header_array_asis(fp);
1362 if (!prefs_common.display_header) {
1363 while (fgets(buf, sizeof(buf), fp) != NULL)
1364 if (buf[0] == '\r' || buf[0] == '\n') break;
1368 headers = procheader_get_header_array_asis(fp);
1370 sorted_headers = g_ptr_array_new();
1372 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1373 disphdr_list = disphdr_list->next) {
1374 DisplayHeaderProp *dp =
1375 (DisplayHeaderProp *)disphdr_list->data;
1377 for (i = 0; i < headers->len; i++) {
1378 header = g_ptr_array_index(headers, i);
1380 if (procheader_headername_equal(header->name,
1383 procheader_header_free(header);
1385 g_ptr_array_add(sorted_headers, header);
1387 g_ptr_array_remove_index(headers, i);
1393 if (prefs_common.show_other_header) {
1394 for (i = 0; i < headers->len; i++) {
1395 header = g_ptr_array_index(headers, i);
1396 g_ptr_array_add(sorted_headers, header);
1398 g_ptr_array_free(headers, TRUE);
1400 procheader_header_array_destroy(headers);
1403 return sorted_headers;
1406 static void textview_show_header(TextView *textview, GPtrArray *headers)
1408 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1409 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1414 g_return_if_fail(headers != NULL);
1416 for (i = 0; i < headers->len; i++) {
1417 header = g_ptr_array_index(headers, i);
1418 g_return_if_fail(header->name != NULL);
1420 gtk_text_buffer_get_end_iter (buffer, &iter);
1421 gtk_text_buffer_insert_with_tags_by_name
1422 (buffer, &iter, header->name, -1,
1423 "header_title", "header", NULL);
1424 if (header->name[strlen(header->name) - 1] != ' ')
1425 gtk_text_buffer_insert_with_tags_by_name
1426 (buffer, &iter, " ", 1,
1427 "header_title", "header", NULL);
1429 if (procheader_headername_equal(header->name, "Subject") ||
1430 procheader_headername_equal(header->name, "From") ||
1431 procheader_headername_equal(header->name, "To") ||
1432 procheader_headername_equal(header->name, "Cc"))
1433 unfold_line(header->body);
1437 if (textview->text_is_mb == TRUE)
1438 conv_unreadable_locale(header->body);
1441 if (prefs_common.enable_color &&
1442 (procheader_headername_equal(header->name, "X-Mailer") ||
1443 procheader_headername_equal(header->name,
1445 strstr(header->body, "Sylpheed") != NULL) {
1446 gtk_text_buffer_get_end_iter (buffer, &iter);
1447 gtk_text_buffer_insert_with_tags_by_name
1448 (buffer, &iter, header->body, -1,
1449 "header", "emphasis", NULL);
1450 } else if (prefs_common.enable_color) {
1451 textview_make_clickable_parts(textview, "header", "link",
1454 textview_make_clickable_parts(textview, "header", NULL,
1457 gtk_text_buffer_get_end_iter (buffer, &iter);
1458 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1463 gboolean textview_search_string(TextView *textview, const gchar *str,
1466 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1467 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1469 GtkTextIter iter, start, end, real_end, *pos;
1470 gboolean found = FALSE;
1471 gint insert_offset, selbound_offset;
1473 /* reset selection */
1474 mark = gtk_text_buffer_get_mark(buffer, "insert");
1475 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1476 insert_offset = gtk_text_iter_get_offset(&start);
1477 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1478 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1479 selbound_offset = gtk_text_iter_get_offset(&end);
1481 pos = insert_offset > selbound_offset ? &start : &end;
1482 gtk_text_buffer_place_cursor(buffer, pos);
1485 mark = gtk_text_buffer_get_insert(buffer);
1486 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1488 found = gtk_text_iter_forward_search(&iter, str,
1489 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1490 &start, &end, NULL);
1494 gtk_text_buffer_get_end_iter(buffer, &real_end);
1495 text = gtk_text_buffer_get_text(buffer, &iter,
1498 while (!found && i++ < strlen(text) - 1) {
1499 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1502 i += gtk_text_iter_get_offset(&end);
1505 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1506 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1514 gtk_text_buffer_place_cursor(buffer, &start);
1515 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1517 mark = gtk_text_buffer_get_mark(buffer, "insert");
1518 gtk_text_view_scroll_mark_onscreen(text, mark);
1524 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1527 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1528 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1530 GtkTextIter iter, start, real_start, end, *pos;
1531 gboolean found = FALSE;
1532 gint insert_offset, selbound_offset;
1534 /* reset selection */
1535 mark = gtk_text_buffer_get_mark(buffer, "insert");
1536 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1537 insert_offset = gtk_text_iter_get_offset(&start);
1538 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1539 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1540 selbound_offset = gtk_text_iter_get_offset(&end);
1542 pos = insert_offset < selbound_offset ? &start : &end;
1543 gtk_text_buffer_place_cursor(buffer, pos);
1546 mark = gtk_text_buffer_get_insert(buffer);
1547 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1549 found = gtk_text_iter_backward_search(&iter, str,
1550 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1551 &start, &end, NULL);
1555 if (gtk_text_iter_get_offset(&iter) == 0)
1556 gtk_text_buffer_get_end_iter(buffer, &iter);
1558 i = gtk_text_iter_get_offset(&iter) - strlen(str) - 1;
1559 gtk_text_buffer_get_start_iter(buffer, &real_start);
1561 text = gtk_text_buffer_get_text(buffer, &real_start,
1564 while (!found && i-- > 0) {
1565 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1569 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1570 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1578 gtk_text_buffer_place_cursor(buffer, &end);
1579 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1581 mark = gtk_text_buffer_get_mark(buffer, "insert");
1582 gtk_text_view_scroll_mark_onscreen(text, mark);
1588 void textview_scroll_one_line(TextView *textview, gboolean up)
1590 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1591 GtkAdjustment *vadj = text->vadjustment;
1594 if (prefs_common.enable_smooth_scroll) {
1595 textview_smooth_scroll_one_line(textview, up);
1600 upper = vadj->upper - vadj->page_size;
1601 if (vadj->value < upper) {
1602 vadj->value += vadj->step_increment;
1603 vadj->value = MIN(vadj->value, upper);
1604 g_signal_emit_by_name(G_OBJECT(vadj),
1605 "value_changed", 0);
1608 if (vadj->value > 0.0) {
1609 vadj->value -= vadj->step_increment;
1610 vadj->value = MAX(vadj->value, 0.0);
1611 g_signal_emit_by_name(G_OBJECT(vadj),
1612 "value_changed", 0);
1617 gboolean textview_scroll_page(TextView *textview, gboolean up)
1619 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1620 GtkAdjustment *vadj = text->vadjustment;
1624 if (prefs_common.enable_smooth_scroll)
1625 return textview_smooth_scroll_page(textview, up);
1627 if (prefs_common.scroll_halfpage)
1628 page_incr = vadj->page_increment / 2;
1630 page_incr = vadj->page_increment;
1633 upper = vadj->upper - vadj->page_size;
1634 if (vadj->value < upper) {
1635 vadj->value += page_incr;
1636 vadj->value = MIN(vadj->value, upper);
1637 g_signal_emit_by_name(G_OBJECT(vadj),
1638 "value_changed", 0);
1642 if (vadj->value > 0.0) {
1643 vadj->value -= page_incr;
1644 vadj->value = MAX(vadj->value, 0.0);
1645 g_signal_emit_by_name(G_OBJECT(vadj),
1646 "value_changed", 0);
1654 static void textview_smooth_scroll_do(TextView *textview,
1655 gfloat old_value, gfloat last_value,
1658 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1659 GtkAdjustment *vadj = text->vadjustment;
1664 if (old_value < last_value) {
1665 change_value = last_value - old_value;
1668 change_value = old_value - last_value;
1672 for (i = step; i <= change_value; i += step) {
1673 vadj->value = old_value + (up ? -i : i);
1674 g_signal_emit_by_name(G_OBJECT(vadj),
1675 "value_changed", 0);
1678 vadj->value = last_value;
1679 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1682 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1684 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1685 GtkAdjustment *vadj = text->vadjustment;
1691 upper = vadj->upper - vadj->page_size;
1692 if (vadj->value < upper) {
1693 old_value = vadj->value;
1694 last_value = vadj->value + vadj->step_increment;
1695 last_value = MIN(last_value, upper);
1697 textview_smooth_scroll_do(textview, old_value,
1699 prefs_common.scroll_step);
1702 if (vadj->value > 0.0) {
1703 old_value = vadj->value;
1704 last_value = vadj->value - vadj->step_increment;
1705 last_value = MAX(last_value, 0.0);
1707 textview_smooth_scroll_do(textview, old_value,
1709 prefs_common.scroll_step);
1714 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1716 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1717 GtkAdjustment *vadj = text->vadjustment;
1723 if (prefs_common.scroll_halfpage)
1724 page_incr = vadj->page_increment / 2;
1726 page_incr = vadj->page_increment;
1729 upper = vadj->upper - vadj->page_size;
1730 if (vadj->value < upper) {
1731 old_value = vadj->value;
1732 last_value = vadj->value + page_incr;
1733 last_value = MIN(last_value, upper);
1735 textview_smooth_scroll_do(textview, old_value,
1737 prefs_common.scroll_step);
1741 if (vadj->value > 0.0) {
1742 old_value = vadj->value;
1743 last_value = vadj->value - page_incr;
1744 last_value = MAX(last_value, 0.0);
1746 textview_smooth_scroll_do(textview, old_value,
1748 prefs_common.scroll_step);
1756 #define KEY_PRESS_EVENT_STOP() \
1757 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1760 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1763 SummaryView *summaryview = NULL;
1764 MessageView *messageview = textview->messageview;
1766 if (!event) return FALSE;
1767 if (messageview->mainwin)
1768 summaryview = messageview->mainwin->summaryview;
1770 switch (event->keyval) {
1785 summary_pass_key_press_event(summaryview, event);
1787 textview_scroll_page
1790 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1793 textview_scroll_page(textview, TRUE);
1796 textview_scroll_one_line
1797 (textview, (event->state &
1798 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1802 summary_pass_key_press_event(summaryview, event);
1807 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1808 KEY_PRESS_EVENT_STOP();
1809 mimeview_pass_key_press_event(messageview->mimeview,
1813 /* possible fall through */
1816 event->window != messageview->mainwin->window->window) {
1817 GdkEventKey tmpev = *event;
1819 tmpev.window = messageview->mainwin->window->window;
1820 KEY_PRESS_EVENT_STOP();
1821 gtk_widget_event(messageview->mainwin->window,
1822 (GdkEvent *)&tmpev);
1830 static gboolean textview_motion_notify(GtkWidget *widget,
1831 GdkEventMotion *event,
1834 textview_uri_update(textview, event->x, event->y);
1835 gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1840 static gboolean textview_leave_notify(GtkWidget *widget,
1841 GdkEventCrossing *event,
1844 textview_uri_update(textview, -1, -1);
1849 static gboolean textview_visibility_notify(GtkWidget *widget,
1850 GdkEventVisibility *event,
1856 window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
1857 GTK_TEXT_WINDOW_TEXT);
1859 /* check if occurred for the text window part */
1860 if (window != event->window)
1863 gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1864 textview_uri_update(textview, wx, wy);
1869 static void textview_uri_update(TextView *textview, gint x, gint y)
1871 GtkTextBuffer *buffer;
1872 GtkTextIter start_iter, end_iter;
1873 RemoteURI *uri = NULL;
1875 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1877 if (x != -1 && y != -1) {
1883 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text),
1884 GTK_TEXT_WINDOW_WIDGET,
1886 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1889 tags = gtk_text_iter_get_tags(&iter);
1890 for (cur = tags; cur != NULL; cur = cur->next) {
1891 GtkTextTag *tag = cur->data;
1894 g_object_get(G_OBJECT(tag), "name", &name, NULL);
1895 if (!strcmp(name, "link")
1896 && textview_get_uri_range(textview, &iter, tag,
1897 &start_iter, &end_iter))
1898 uri = textview_get_uri_from_range(textview,
1910 if (uri != textview->uri_hover) {
1913 if (textview->uri_hover)
1914 gtk_text_buffer_remove_tag_by_name(buffer,
1916 &textview->uri_hover_start_iter,
1917 &textview->uri_hover_end_iter);
1919 textview->uri_hover = uri;
1921 textview->uri_hover_start_iter = start_iter;
1922 textview->uri_hover_end_iter = end_iter;
1925 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1926 GTK_TEXT_WINDOW_TEXT);
1927 gdk_window_set_cursor(window, uri ? hand_cursor : text_cursor);
1929 TEXTVIEW_STATUSBAR_POP(textview);
1934 gtk_text_buffer_apply_tag_by_name(buffer,
1939 trimmed_uri = trim_string(uri->uri, 60);
1940 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1941 g_free(trimmed_uri);
1946 static gboolean textview_get_uri_range(TextView *textview,
1949 GtkTextIter *start_iter,
1950 GtkTextIter *end_iter)
1952 GtkTextIter _start_iter, _end_iter;
1955 if (!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
1956 debug_print("Can't find end");
1960 _start_iter = _end_iter;
1961 if (!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
1962 debug_print("Can't find start.");
1966 *start_iter = _start_iter;
1967 *end_iter = _end_iter;
1972 static RemoteURI *textview_get_uri_from_range(TextView *textview,
1975 GtkTextIter *start_iter,
1976 GtkTextIter *end_iter)
1978 gint start_pos, end_pos, cur_pos;
1979 RemoteURI *uri = NULL;
1982 start_pos = gtk_text_iter_get_offset(start_iter);
1983 end_pos = gtk_text_iter_get_offset(end_iter);
1984 cur_pos = gtk_text_iter_get_offset(iter);
1986 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1987 RemoteURI *uri_ = (RemoteURI *)cur->data;
1988 if (start_pos == uri_->start &&
1989 end_pos == uri_->end) {
1992 } else if (start_pos == uri_->start ||
1993 end_pos == uri_->end) {
1994 /* in case of contiguous links, textview_get_uri_range
1995 * returns a broader range (start of 1st link to end
1997 * In that case, correct link is the one covering
2000 if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
2010 static RemoteURI *textview_get_uri(TextView *textview,
2014 GtkTextIter start_iter, end_iter;
2015 RemoteURI *uri = NULL;
2017 if (textview_get_uri_range(textview, iter, tag, &start_iter,
2019 uri = textview_get_uri_from_range(textview, iter, tag,
2020 &start_iter, &end_iter);
2025 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
2026 GdkEvent *event, GtkTextIter *iter,
2029 GdkEventButton *bevent;
2030 RemoteURI *uri = NULL;
2037 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2038 && event->type != GDK_MOTION_NOTIFY)
2041 uri = textview_get_uri(textview, iter, tag);
2045 bevent = (GdkEventButton *) event;
2047 /* doubleclick: open compose / add address / browser */
2048 if ((event->type == GDK_BUTTON_PRESS && bevent->button == 1) ||
2049 bevent->button == 2 || bevent->button == 3) {
2050 if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2051 if (bevent->button == 3) {
2052 gchar *fromname, *fromaddress;
2055 fromaddress = g_strdup(uri->uri + 7);
2056 /* Hiroyuki: please put this function in utils.c! */
2057 fromname = procheader_get_fromname(fromaddress);
2058 extract_address(fromaddress);
2059 g_message("adding from textview %s <%s>", fromname, fromaddress);
2060 /* Add to address book - Match */
2061 addressbook_add_contact( fromname, fromaddress, NULL );
2063 g_free(fromaddress);
2066 PrefsAccount *account = NULL;
2068 if (textview->messageview && textview->messageview->msginfo &&
2069 textview->messageview->msginfo->folder) {
2070 FolderItem *folder_item;
2072 folder_item = textview->messageview->msginfo->folder;
2073 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2074 account = account_find_from_id(folder_item->prefs->default_account);
2076 compose_new(account, uri->uri + 7, NULL);
2080 if (bevent->button == 1 &&
2081 textview_uri_security_check(textview, uri) == TRUE)
2083 prefs_common.uri_cmd);
2084 else if (bevent->button == 3) {
2086 G_OBJECT(textview->popup_menu),
2087 "menu_button", uri);
2088 gtk_menu_popup(GTK_MENU(textview->popup_menu),
2089 NULL, NULL, NULL, NULL,
2090 bevent->button, bevent->time);
2100 *\brief Check to see if a web URL has been disguised as a different
2101 * URL (possible with HTML email).
2103 *\param uri The uri to check
2105 *\param textview The TextView the URL is contained in
2107 *\return gboolean TRUE if the URL is ok, or if the user chose to open
2108 * it anyway, otherwise FALSE
2110 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2113 gboolean retval = TRUE;
2114 GtkTextBuffer *buffer;
2115 GtkTextIter start, end;
2117 if (is_uri_string(uri->uri) == FALSE)
2120 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2122 gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2123 gtk_text_buffer_get_iter_at_offset(buffer, &end, uri->end);
2125 visible_str = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2127 if (visible_str == NULL)
2130 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2132 gchar *visible_uri_path;
2134 uri_path = get_uri_path(uri->uri);
2135 visible_uri_path = get_uri_path(visible_str);
2136 if (strcmp(uri_path, visible_uri_path) != 0)
2140 if (retval == FALSE) {
2144 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2145 "the apparent URL (%s).\n"
2147 uri->uri, visible_str);
2148 aval = alertpanel_with_type(_("Warning"), msg,
2149 GTK_STOCK_YES, GTK_STOCK_NO,
2150 NULL, NULL, ALERT_WARNING);
2152 if (aval == G_ALERTDEFAULT)
2156 g_free(visible_str);
2161 static void textview_uri_list_remove_all(GSList *uri_list)
2165 for (cur = uri_list; cur != NULL; cur = cur->next) {
2167 g_free(((RemoteURI *)cur->data)->uri);
2172 g_slist_free(uri_list);
2175 static void open_uri_cb (TextView *textview, guint action, void *data)
2177 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->popup_menu),
2182 if (textview_uri_security_check(textview, uri) == TRUE)
2184 prefs_common.uri_cmd);
2185 g_object_set_data(G_OBJECT(textview->popup_menu), "menu_button",
2189 static void copy_uri_cb (TextView *textview, guint action, void *data)
2191 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->popup_menu),
2196 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), uri->uri, -1);
2197 g_object_set_data(G_OBJECT(textview->popup_menu), "menu_button",