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_utf8todisp(buf, sizeof(buf), str);
1164 //if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1167 /* change color of quotation
1168 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1169 Up to 3 levels of quotations are detected, and each
1170 level is colored using a different color. */
1171 if (prefs_common.enable_color
1172 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1173 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1175 /* set up the correct foreground color */
1176 if (quotelevel > 2) {
1177 /* recycle colors */
1178 if (prefs_common.recycle_quote_colors)
1185 if (quotelevel == -1)
1188 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1189 "quote%d", quotelevel);
1190 fg_color = quote_tag_str;
1193 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1194 fg_color = "signature";
1195 textview->is_in_signature = TRUE;
1198 if (prefs_common.enable_color)
1199 textview_make_clickable_parts(textview, fg_color, "link", buf);
1201 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1204 void textview_write_link(TextView *textview, const gchar *str,
1205 const gchar *uri, CodeConverter *conv)
1207 GdkColor *link_color = NULL;
1209 GtkTextBuffer *buffer;
1211 gchar buf[BUFFSIZE];
1218 text = GTK_TEXT_VIEW(textview->text);
1219 buffer = gtk_text_view_get_buffer(text);
1220 gtk_text_buffer_get_end_iter(buffer, &iter);
1223 strncpy2(buf, str, sizeof(buf));
1224 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1225 conv_utf8todisp(buf, sizeof(buf), str);
1229 gtk_text_buffer_get_end_iter(buffer, &iter);
1231 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1232 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1234 if (prefs_common.enable_color) {
1235 link_color = &uri_color;
1237 r_uri = g_new(RemoteURI, 1);
1238 r_uri->uri = g_strdup(uri);
1239 r_uri->start = gtk_text_iter_get_offset(&iter);
1240 gtk_text_buffer_insert_with_tags_by_name
1241 (buffer, &iter, bufp, -1, "link", NULL);
1242 r_uri->end = gtk_text_iter_get_offset(&iter);
1243 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1246 void textview_clear(TextView *textview)
1248 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1249 GtkTextBuffer *buffer;
1251 buffer = gtk_text_view_get_buffer(text);
1252 gtk_text_buffer_set_text(buffer, "", -1);
1254 TEXTVIEW_STATUSBAR_POP(textview);
1255 textview_uri_list_remove_all(textview->uri_list);
1256 textview->uri_list = NULL;
1258 textview->body_pos = 0;
1261 void textview_destroy(TextView *textview)
1263 textview_uri_list_remove_all(textview->uri_list);
1264 textview->uri_list = NULL;
1269 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1271 textview->show_all_headers = all_headers;
1274 void textview_set_font(TextView *textview, const gchar *codeset)
1276 if (prefs_common.textfont) {
1277 PangoFontDescription *font_desc = NULL;
1279 if (prefs_common.textfont)
1280 font_desc = pango_font_description_from_string
1281 (prefs_common.textfont);
1283 gtk_widget_modify_font(textview->text, font_desc);
1284 pango_font_description_free(font_desc);
1287 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1288 prefs_common.line_space / 2);
1289 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1290 prefs_common.line_space / 2);
1293 void textview_set_text(TextView *textview, const gchar *text)
1296 GtkTextBuffer *buffer;
1298 g_return_if_fail(textview != NULL);
1299 g_return_if_fail(text != NULL);
1301 textview_clear(textview);
1303 view = GTK_TEXT_VIEW(textview->text);
1304 buffer = gtk_text_view_get_buffer(view);
1305 gtk_text_buffer_set_text(buffer, text, strlen(text));
1321 H_ORGANIZATION = 11,
1324 void textview_set_position(TextView *textview, gint pos)
1326 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1327 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1330 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1331 gtk_text_buffer_place_cursor(buffer, &iter);
1332 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1335 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1337 gchar buf[BUFFSIZE];
1338 GPtrArray *headers, *sorted_headers;
1339 GSList *disphdr_list;
1343 g_return_val_if_fail(fp != NULL, NULL);
1345 if (textview->show_all_headers)
1346 return procheader_get_header_array_asis(fp);
1348 if (!prefs_common.display_header) {
1349 while (fgets(buf, sizeof(buf), fp) != NULL)
1350 if (buf[0] == '\r' || buf[0] == '\n') break;
1354 headers = procheader_get_header_array_asis(fp);
1356 sorted_headers = g_ptr_array_new();
1358 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1359 disphdr_list = disphdr_list->next) {
1360 DisplayHeaderProp *dp =
1361 (DisplayHeaderProp *)disphdr_list->data;
1363 for (i = 0; i < headers->len; i++) {
1364 header = g_ptr_array_index(headers, i);
1366 if (procheader_headername_equal(header->name,
1369 procheader_header_free(header);
1371 g_ptr_array_add(sorted_headers, header);
1373 g_ptr_array_remove_index(headers, i);
1379 if (prefs_common.show_other_header) {
1380 for (i = 0; i < headers->len; i++) {
1381 header = g_ptr_array_index(headers, i);
1382 g_ptr_array_add(sorted_headers, header);
1384 g_ptr_array_free(headers, TRUE);
1386 procheader_header_array_destroy(headers);
1389 return sorted_headers;
1392 static void textview_show_header(TextView *textview, GPtrArray *headers)
1394 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1395 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1400 g_return_if_fail(headers != NULL);
1402 for (i = 0; i < headers->len; i++) {
1403 header = g_ptr_array_index(headers, i);
1404 g_return_if_fail(header->name != NULL);
1406 gtk_text_buffer_get_end_iter (buffer, &iter);
1407 gtk_text_buffer_insert_with_tags_by_name
1408 (buffer, &iter, header->name, -1,
1409 "header_title", "header", NULL);
1410 if (header->name[strlen(header->name) - 1] != ' ')
1411 gtk_text_buffer_insert_with_tags_by_name
1412 (buffer, &iter, " ", 1,
1413 "header_title", "header", NULL);
1415 if (procheader_headername_equal(header->name, "Subject") ||
1416 procheader_headername_equal(header->name, "From") ||
1417 procheader_headername_equal(header->name, "To") ||
1418 procheader_headername_equal(header->name, "Cc"))
1419 unfold_line(header->body);
1423 if (textview->text_is_mb == TRUE)
1424 conv_unreadable_locale(header->body);
1427 if (prefs_common.enable_color &&
1428 (procheader_headername_equal(header->name, "X-Mailer") ||
1429 procheader_headername_equal(header->name,
1431 strstr(header->body, "Sylpheed") != NULL) {
1432 gtk_text_buffer_get_end_iter (buffer, &iter);
1433 gtk_text_buffer_insert_with_tags_by_name
1434 (buffer, &iter, header->body, -1,
1435 "header", "emphasis", NULL);
1436 } else if (prefs_common.enable_color) {
1437 textview_make_clickable_parts(textview, "header", "link",
1440 textview_make_clickable_parts(textview, "header", NULL,
1443 gtk_text_buffer_get_end_iter (buffer, &iter);
1444 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1449 gboolean textview_search_string(TextView *textview, const gchar *str,
1452 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1453 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1455 GtkTextIter iter, start, end, real_end, *pos;
1456 gboolean found = FALSE;
1457 gint insert_offset, selbound_offset;
1459 /* reset selection */
1460 mark = gtk_text_buffer_get_mark(buffer, "insert");
1461 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1462 insert_offset = gtk_text_iter_get_offset(&start);
1463 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1464 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1465 selbound_offset = gtk_text_iter_get_offset(&end);
1467 pos = insert_offset > selbound_offset ? &start : &end;
1468 gtk_text_buffer_place_cursor(buffer, pos);
1471 mark = gtk_text_buffer_get_insert(buffer);
1472 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1474 found = gtk_text_iter_forward_search(&iter, str,
1475 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1476 &start, &end, NULL);
1480 gtk_text_buffer_get_end_iter(buffer, &real_end);
1481 text = gtk_text_buffer_get_text(buffer, &iter,
1484 while (!found && i++ < strlen(text) - 1) {
1485 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1488 i += gtk_text_iter_get_offset(&end);
1491 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1492 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1500 gtk_text_buffer_place_cursor(buffer, &start);
1501 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1503 mark = gtk_text_buffer_get_mark(buffer, "insert");
1504 gtk_text_view_scroll_mark_onscreen(text, mark);
1510 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1513 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1514 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1516 GtkTextIter iter, start, real_start, end, *pos;
1517 gboolean found = FALSE;
1518 gint insert_offset, selbound_offset;
1520 /* reset selection */
1521 mark = gtk_text_buffer_get_mark(buffer, "insert");
1522 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1523 insert_offset = gtk_text_iter_get_offset(&start);
1524 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1525 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1526 selbound_offset = gtk_text_iter_get_offset(&end);
1528 pos = insert_offset < selbound_offset ? &start : &end;
1529 gtk_text_buffer_place_cursor(buffer, pos);
1532 mark = gtk_text_buffer_get_insert(buffer);
1533 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1535 found = gtk_text_iter_backward_search(&iter, str,
1536 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1537 &start, &end, NULL);
1541 if (gtk_text_iter_get_offset(&iter) == 0)
1542 gtk_text_buffer_get_end_iter(buffer, &iter);
1544 i = gtk_text_iter_get_offset(&iter) - strlen(str) - 1;
1545 gtk_text_buffer_get_start_iter(buffer, &real_start);
1547 text = gtk_text_buffer_get_text(buffer, &real_start,
1550 while (!found && i-- > 0) {
1551 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1555 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1556 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1564 gtk_text_buffer_place_cursor(buffer, &end);
1565 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1567 mark = gtk_text_buffer_get_mark(buffer, "insert");
1568 gtk_text_view_scroll_mark_onscreen(text, mark);
1574 void textview_scroll_one_line(TextView *textview, gboolean up)
1576 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1577 GtkAdjustment *vadj = text->vadjustment;
1580 if (prefs_common.enable_smooth_scroll) {
1581 textview_smooth_scroll_one_line(textview, up);
1586 upper = vadj->upper - vadj->page_size;
1587 if (vadj->value < upper) {
1588 vadj->value += vadj->step_increment;
1589 vadj->value = MIN(vadj->value, upper);
1590 g_signal_emit_by_name(G_OBJECT(vadj),
1591 "value_changed", 0);
1594 if (vadj->value > 0.0) {
1595 vadj->value -= vadj->step_increment;
1596 vadj->value = MAX(vadj->value, 0.0);
1597 g_signal_emit_by_name(G_OBJECT(vadj),
1598 "value_changed", 0);
1603 gboolean textview_scroll_page(TextView *textview, gboolean up)
1605 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1606 GtkAdjustment *vadj = text->vadjustment;
1610 if (prefs_common.enable_smooth_scroll)
1611 return textview_smooth_scroll_page(textview, up);
1613 if (prefs_common.scroll_halfpage)
1614 page_incr = vadj->page_increment / 2;
1616 page_incr = vadj->page_increment;
1619 upper = vadj->upper - vadj->page_size;
1620 if (vadj->value < upper) {
1621 vadj->value += page_incr;
1622 vadj->value = MIN(vadj->value, upper);
1623 g_signal_emit_by_name(G_OBJECT(vadj),
1624 "value_changed", 0);
1628 if (vadj->value > 0.0) {
1629 vadj->value -= page_incr;
1630 vadj->value = MAX(vadj->value, 0.0);
1631 g_signal_emit_by_name(G_OBJECT(vadj),
1632 "value_changed", 0);
1640 static void textview_smooth_scroll_do(TextView *textview,
1641 gfloat old_value, gfloat last_value,
1644 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1645 GtkAdjustment *vadj = text->vadjustment;
1650 if (old_value < last_value) {
1651 change_value = last_value - old_value;
1654 change_value = old_value - last_value;
1658 for (i = step; i <= change_value; i += step) {
1659 vadj->value = old_value + (up ? -i : i);
1660 g_signal_emit_by_name(G_OBJECT(vadj),
1661 "value_changed", 0);
1664 vadj->value = last_value;
1665 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1668 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1670 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1671 GtkAdjustment *vadj = text->vadjustment;
1677 upper = vadj->upper - vadj->page_size;
1678 if (vadj->value < upper) {
1679 old_value = vadj->value;
1680 last_value = vadj->value + vadj->step_increment;
1681 last_value = MIN(last_value, upper);
1683 textview_smooth_scroll_do(textview, old_value,
1685 prefs_common.scroll_step);
1688 if (vadj->value > 0.0) {
1689 old_value = vadj->value;
1690 last_value = vadj->value - vadj->step_increment;
1691 last_value = MAX(last_value, 0.0);
1693 textview_smooth_scroll_do(textview, old_value,
1695 prefs_common.scroll_step);
1700 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1702 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1703 GtkAdjustment *vadj = text->vadjustment;
1709 if (prefs_common.scroll_halfpage)
1710 page_incr = vadj->page_increment / 2;
1712 page_incr = vadj->page_increment;
1715 upper = vadj->upper - vadj->page_size;
1716 if (vadj->value < upper) {
1717 old_value = vadj->value;
1718 last_value = vadj->value + page_incr;
1719 last_value = MIN(last_value, upper);
1721 textview_smooth_scroll_do(textview, old_value,
1723 prefs_common.scroll_step);
1727 if (vadj->value > 0.0) {
1728 old_value = vadj->value;
1729 last_value = vadj->value - page_incr;
1730 last_value = MAX(last_value, 0.0);
1732 textview_smooth_scroll_do(textview, old_value,
1734 prefs_common.scroll_step);
1742 #define KEY_PRESS_EVENT_STOP() \
1743 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1746 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1749 SummaryView *summaryview = NULL;
1750 MessageView *messageview = textview->messageview;
1752 if (!event) return FALSE;
1753 if (messageview->mainwin)
1754 summaryview = messageview->mainwin->summaryview;
1756 switch (event->keyval) {
1771 summary_pass_key_press_event(summaryview, event);
1773 textview_scroll_page
1776 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1779 textview_scroll_page(textview, TRUE);
1782 textview_scroll_one_line
1783 (textview, (event->state &
1784 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1788 summary_pass_key_press_event(summaryview, event);
1793 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1794 KEY_PRESS_EVENT_STOP();
1795 mimeview_pass_key_press_event(messageview->mimeview,
1799 /* possible fall through */
1802 event->window != messageview->mainwin->window->window) {
1803 GdkEventKey tmpev = *event;
1805 tmpev.window = messageview->mainwin->window->window;
1806 KEY_PRESS_EVENT_STOP();
1807 gtk_widget_event(messageview->mainwin->window,
1808 (GdkEvent *)&tmpev);
1816 static gboolean textview_motion_notify(GtkWidget *widget,
1817 GdkEventMotion *event,
1820 textview_uri_update(textview, event->x, event->y);
1821 gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1826 static gboolean textview_leave_notify(GtkWidget *widget,
1827 GdkEventCrossing *event,
1830 textview_uri_update(textview, -1, -1);
1835 static gboolean textview_visibility_notify(GtkWidget *widget,
1836 GdkEventVisibility *event,
1842 window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
1843 GTK_TEXT_WINDOW_TEXT);
1845 /* check if occurred for the text window part */
1846 if (window != event->window)
1849 gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1850 textview_uri_update(textview, wx, wy);
1855 static void textview_uri_update(TextView *textview, gint x, gint y)
1857 GtkTextBuffer *buffer;
1858 GtkTextIter start_iter, end_iter;
1859 RemoteURI *uri = NULL;
1861 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1863 if (x != -1 && y != -1) {
1869 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text),
1870 GTK_TEXT_WINDOW_WIDGET,
1872 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1875 tags = gtk_text_iter_get_tags(&iter);
1876 for (cur = tags; cur != NULL; cur = cur->next) {
1877 GtkTextTag *tag = cur->data;
1880 g_object_get(G_OBJECT(tag), "name", &name, NULL);
1881 if (!strcmp(name, "link")
1882 && textview_get_uri_range(textview, &iter, tag,
1883 &start_iter, &end_iter))
1884 uri = textview_get_uri_from_range(textview,
1896 if (uri != textview->uri_hover) {
1899 if (textview->uri_hover)
1900 gtk_text_buffer_remove_tag_by_name(buffer,
1902 &textview->uri_hover_start_iter,
1903 &textview->uri_hover_end_iter);
1905 textview->uri_hover = uri;
1907 textview->uri_hover_start_iter = start_iter;
1908 textview->uri_hover_end_iter = end_iter;
1911 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1912 GTK_TEXT_WINDOW_TEXT);
1913 gdk_window_set_cursor(window, uri ? hand_cursor : text_cursor);
1915 TEXTVIEW_STATUSBAR_POP(textview);
1920 gtk_text_buffer_apply_tag_by_name(buffer,
1925 trimmed_uri = trim_string(uri->uri, 60);
1926 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1927 g_free(trimmed_uri);
1932 static gboolean textview_get_uri_range(TextView *textview,
1935 GtkTextIter *start_iter,
1936 GtkTextIter *end_iter)
1938 GtkTextIter _start_iter, _end_iter;
1941 if (!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
1942 debug_print("Can't find end");
1946 _start_iter = _end_iter;
1947 if (!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
1948 debug_print("Can't find start.");
1952 *start_iter = _start_iter;
1953 *end_iter = _end_iter;
1958 static RemoteURI *textview_get_uri_from_range(TextView *textview,
1961 GtkTextIter *start_iter,
1962 GtkTextIter *end_iter)
1964 gint start_pos, end_pos, cur_pos;
1965 RemoteURI *uri = NULL;
1968 start_pos = gtk_text_iter_get_offset(start_iter);
1969 end_pos = gtk_text_iter_get_offset(end_iter);
1970 cur_pos = gtk_text_iter_get_offset(iter);
1972 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1973 RemoteURI *uri_ = (RemoteURI *)cur->data;
1974 if (start_pos == uri_->start &&
1975 end_pos == uri_->end) {
1978 } else if (start_pos == uri_->start ||
1979 end_pos == uri_->end) {
1980 /* in case of contiguous links, textview_get_uri_range
1981 * returns a broader range (start of 1st link to end
1983 * In that case, correct link is the one covering
1986 if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
1996 static RemoteURI *textview_get_uri(TextView *textview,
2000 GtkTextIter start_iter, end_iter;
2001 RemoteURI *uri = NULL;
2003 if (textview_get_uri_range(textview, iter, tag, &start_iter,
2005 uri = textview_get_uri_from_range(textview, iter, tag,
2006 &start_iter, &end_iter);
2011 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
2012 GdkEvent *event, GtkTextIter *iter,
2015 GdkEventButton *bevent;
2016 RemoteURI *uri = NULL;
2023 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2024 && event->type != GDK_MOTION_NOTIFY)
2027 uri = textview_get_uri(textview, iter, tag);
2031 bevent = (GdkEventButton *) event;
2033 /* doubleclick: open compose / add address / browser */
2034 if ((event->type == GDK_BUTTON_PRESS && bevent->button == 1) ||
2035 bevent->button == 2 || bevent->button == 3) {
2036 if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2037 if (bevent->button == 3) {
2038 gchar *fromname, *fromaddress;
2041 fromaddress = g_strdup(uri->uri + 7);
2042 /* Hiroyuki: please put this function in utils.c! */
2043 fromname = procheader_get_fromname(fromaddress);
2044 extract_address(fromaddress);
2045 g_message("adding from textview %s <%s>", fromname, fromaddress);
2046 /* Add to address book - Match */
2047 addressbook_add_contact( fromname, fromaddress, NULL );
2049 g_free(fromaddress);
2052 PrefsAccount *account = NULL;
2054 if (textview->messageview && textview->messageview->msginfo &&
2055 textview->messageview->msginfo->folder) {
2056 FolderItem *folder_item;
2058 folder_item = textview->messageview->msginfo->folder;
2059 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2060 account = account_find_from_id(folder_item->prefs->default_account);
2062 compose_new(account, uri->uri + 7, NULL);
2066 if (bevent->button == 1 &&
2067 textview_uri_security_check(textview, uri) == TRUE)
2069 prefs_common.uri_cmd);
2070 else if (bevent->button == 3) {
2072 G_OBJECT(textview->popup_menu),
2073 "menu_button", uri);
2074 gtk_menu_popup(GTK_MENU(textview->popup_menu),
2075 NULL, NULL, NULL, NULL,
2076 bevent->button, bevent->time);
2086 *\brief Check to see if a web URL has been disguised as a different
2087 * URL (possible with HTML email).
2089 *\param uri The uri to check
2091 *\param textview The TextView the URL is contained in
2093 *\return gboolean TRUE if the URL is ok, or if the user chose to open
2094 * it anyway, otherwise FALSE
2096 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2099 gboolean retval = TRUE;
2100 GtkTextBuffer *buffer;
2101 GtkTextIter start, end;
2103 if (is_uri_string(uri->uri) == FALSE)
2106 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2108 gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2109 gtk_text_buffer_get_iter_at_offset(buffer, &end, uri->end);
2111 visible_str = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2113 if (visible_str == NULL)
2116 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2118 gchar *visible_uri_path;
2120 uri_path = get_uri_path(uri->uri);
2121 visible_uri_path = get_uri_path(visible_str);
2122 if (strcmp(uri_path, visible_uri_path) != 0)
2126 if (retval == FALSE) {
2130 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2131 "the apparent URL (%s).\n"
2133 uri->uri, visible_str);
2134 aval = alertpanel_with_type(_("Warning"), msg,
2135 GTK_STOCK_YES, GTK_STOCK_NO,
2136 NULL, NULL, ALERT_WARNING);
2138 if (aval == G_ALERTDEFAULT)
2142 g_free(visible_str);
2147 static void textview_uri_list_remove_all(GSList *uri_list)
2151 for (cur = uri_list; cur != NULL; cur = cur->next) {
2153 g_free(((RemoteURI *)cur->data)->uri);
2158 g_slist_free(uri_list);
2161 static void open_uri_cb (TextView *textview, guint action, void *data)
2163 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->popup_menu),
2168 if (textview_uri_security_check(textview, uri) == TRUE)
2170 prefs_common.uri_cmd);
2171 g_object_set_data(G_OBJECT(textview->popup_menu), "menu_button",
2175 static void copy_uri_cb (TextView *textview, guint action, void *data)
2177 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->popup_menu),
2182 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), uri->uri, -1);
2183 g_object_set_data(G_OBJECT(textview->popup_menu), "menu_button",