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;
215 debug_print("Creating text view...\n");
216 textview = g_new0(TextView, 1);
218 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
219 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
220 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
221 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
223 gtk_widget_set_size_request
224 (scrolledwin, prefs_common.mainview_width, -1);
226 /* create GtkSText widgets for single-byte and multi-byte character */
227 text = gtk_text_view_new();
228 gtk_widget_add_events(text, GDK_LEAVE_NOTIFY_MASK);
229 gtk_widget_show(text);
230 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
231 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
232 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
233 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
234 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
236 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
237 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
238 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
240 gtk_widget_ensure_style(text);
242 gtk_widget_ref(scrolledwin);
244 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
247 hand_cursor = gdk_cursor_new(GDK_HAND2);
249 text_cursor = gdk_cursor_new(GDK_XTERM);
251 g_signal_connect(G_OBJECT(text), "key_press_event",
252 G_CALLBACK(textview_key_pressed),
254 g_signal_connect(G_OBJECT(text), "motion_notify_event",
255 G_CALLBACK(textview_motion_notify),
257 g_signal_connect(G_OBJECT(text), "leave_notify_event",
258 G_CALLBACK(textview_leave_notify),
260 g_signal_connect(G_OBJECT(text), "visibility_notify_event",
261 G_CALLBACK(textview_visibility_notify),
264 gtk_widget_show(scrolledwin);
266 vbox = gtk_vbox_new(FALSE, 0);
267 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
269 gtk_widget_show(vbox);
271 n_entries = sizeof(textview_popup_entries) /
272 sizeof(textview_popup_entries[0]);
273 popupmenu = menu_create_items(textview_popup_entries, n_entries,
274 "<UriPopupMenu>", &popupfactory,
277 textview->vbox = vbox;
278 textview->scrolledwin = scrolledwin;
279 textview->text = text;
280 textview->uri_list = NULL;
281 textview->body_pos = 0;
282 textview->show_all_headers = FALSE;
283 textview->last_buttonpress = GDK_NOTHING;
284 textview->popup_menu = popupmenu;
285 textview->popup_factory = popupfactory;
290 static void textview_create_tags(GtkTextView *text, TextView *textview)
292 GtkTextBuffer *buffer;
294 static PangoFontDescription *font_desc, *bold_font_desc;
297 font_desc = pango_font_description_from_string
300 if (!bold_font_desc) {
301 bold_font_desc = pango_font_description_from_string
303 pango_font_description_set_weight
304 (bold_font_desc, PANGO_WEIGHT_BOLD);
307 buffer = gtk_text_view_get_buffer(text);
309 gtk_text_buffer_create_tag(buffer, "header",
310 "pixels-above-lines", 0,
311 "pixels-above-lines-set", TRUE,
312 "pixels-below-lines", 0,
313 "pixels-below-lines-set", TRUE,
314 "font-desc", font_desc,
316 "left-margin-set", TRUE,
318 gtk_text_buffer_create_tag(buffer, "header_title",
319 "font-desc", bold_font_desc,
321 gtk_text_buffer_create_tag(buffer, "quote0",
322 "foreground-gdk", "e_colors[0],
324 gtk_text_buffer_create_tag(buffer, "quote1",
325 "foreground-gdk", "e_colors[1],
327 gtk_text_buffer_create_tag(buffer, "quote2",
328 "foreground-gdk", "e_colors[2],
330 gtk_text_buffer_create_tag(buffer, "emphasis",
331 "foreground-gdk", &emphasis_color,
333 gtk_text_buffer_create_tag(buffer, "signature",
334 "foreground-gdk", &signature_color,
336 tag = gtk_text_buffer_create_tag(buffer, "link",
337 "foreground-gdk", &uri_color,
339 gtk_text_buffer_create_tag(buffer, "link-hover",
340 "underline", PANGO_UNDERLINE_SINGLE,
343 g_signal_connect(G_OBJECT(tag), "event",
344 G_CALLBACK(textview_uri_button_pressed), textview);
347 void textview_init(TextView *textview)
349 textview_update_message_colors();
350 textview_set_all_headers(textview, FALSE);
351 textview_set_font(textview, NULL);
352 textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
355 void textview_update_message_colors(void)
357 GdkColor black = {0, 0, 0, 0};
359 if (prefs_common.enable_color) {
360 /* grab the quote colors, converting from an int to a GdkColor */
361 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
363 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
365 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
367 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
369 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
372 quote_colors[0] = quote_colors[1] = quote_colors[2] =
373 uri_color = emphasis_color = signature_color = black;
377 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
382 if ((fp = fopen(file, "rb")) == NULL) {
383 FILE_OP_ERROR(file, "fopen");
387 textview_clear(textview);
389 textview_add_parts(textview, mimeinfo);
393 textview_set_position(textview, 0);
396 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
400 g_return_if_fail(mimeinfo != NULL);
401 g_return_if_fail(fp != NULL);
403 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
404 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822"))) {
405 textview_clear(textview);
406 textview_add_parts(textview, mimeinfo);
410 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
413 textview_clear(textview);
415 if (mimeinfo->type == MIMETYPE_MULTIPART)
416 textview_add_parts(textview, mimeinfo);
418 textview_write_body(textview, mimeinfo);
422 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
425 GtkTextBuffer *buffer;
428 GPtrArray *headers = NULL;
433 g_return_if_fail(mimeinfo != NULL);
434 text = GTK_TEXT_VIEW(textview->text);
435 buffer = gtk_text_view_get_buffer(text);
436 charcount = gtk_text_buffer_get_char_count(buffer);
437 gtk_text_buffer_get_end_iter(buffer, &iter);
439 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
441 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
444 fp = fopen(mimeinfo->data.filename, "rb");
445 fseek(fp, mimeinfo->offset, SEEK_SET);
446 headers = textview_scan_header(textview, fp);
449 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
450 textview_show_header(textview, headers);
451 procheader_header_array_destroy(headers);
457 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
458 content_type = procmime_get_content_type_str(mimeinfo->type,
461 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
463 g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n",
464 name, content_type, mimeinfo->length);
466 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
467 content_type, mimeinfo->length);
469 g_free(content_type);
471 if (mimeinfo->type != MIMETYPE_TEXT) {
472 gtk_text_buffer_insert(buffer, &iter, buf, -1);
473 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
474 if (prefs_common.display_header && (charcount > 0))
475 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
477 textview_write_body(textview, mimeinfo);
481 static void recursive_add_parts(TextView *textview, GNode *node)
486 mimeinfo = (MimeInfo *) node->data;
488 textview_add_part(textview, mimeinfo);
490 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
491 (mimeinfo->type != MIMETYPE_MESSAGE))
494 if (g_ascii_strcasecmp(mimeinfo->subtype, "alternative") == 0) {
495 GNode * prefered_body;
503 prefered_body = NULL;
506 for (iter = g_node_first_child(node) ; iter != NULL ;
507 iter = g_node_next_sibling(iter)) {
512 submime = (MimeInfo *) iter->data;
513 if (submime->type == MIMETYPE_TEXT)
516 if (submime->subtype != NULL) {
517 if (g_ascii_strcasecmp(submime->subtype, "plain") == 0)
521 if (score > prefered_score) {
522 prefered_score = score;
523 prefered_body = iter;
527 if (prefered_body != NULL) {
528 recursive_add_parts(textview, prefered_body);
532 for (iter = g_node_first_child(node) ; iter != NULL ;
533 iter = g_node_next_sibling(iter)) {
534 recursive_add_parts(textview, iter);
539 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
541 g_return_if_fail(mimeinfo != NULL);
543 recursive_add_parts(textview, mimeinfo->node);
546 #define TEXT_INSERT(str) \
547 gtk_text_buffer_insert(buffer, &iter, str, -1)
549 void textview_show_error(TextView *textview)
552 GtkTextBuffer *buffer;
555 textview_set_font(textview, NULL);
556 textview_clear(textview);
558 text = GTK_TEXT_VIEW(textview->text);
559 buffer = gtk_text_view_get_buffer(text);
560 gtk_text_buffer_get_start_iter(buffer, &iter);
562 TEXT_INSERT(_("This message can't be displayed.\n"));
566 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
569 GtkTextBuffer *buffer;
572 if (!partinfo) return;
574 textview_set_font(textview, NULL);
575 textview_clear(textview);
577 text = GTK_TEXT_VIEW(textview->text);
578 buffer = gtk_text_view_get_buffer(text);
579 gtk_text_buffer_get_start_iter(buffer, &iter);
581 TEXT_INSERT(_("The following can be performed on this part by "));
582 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
584 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
585 TEXT_INSERT(_(" To display as text select 'Display as text' "));
586 TEXT_INSERT(_("(Shortcut key: 't')\n"));
587 TEXT_INSERT(_(" To open with an external program select 'Open' "));
588 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
589 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
590 TEXT_INSERT(_("mouse button),\n"));
591 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
597 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
602 const gchar *charset;
604 if (textview->messageview->forced_charset)
605 charset = textview->messageview->forced_charset;
607 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
609 textview_set_font(textview, charset);
611 conv = conv_code_converter_new(charset);
613 procmime_force_encoding(textview->messageview->forced_encoding);
615 textview->is_in_signature = FALSE;
617 procmime_decode_content(mimeinfo);
619 if (!g_ascii_strcasecmp(mimeinfo->subtype, "html")) {
622 filename = procmime_get_tmp_file_name(mimeinfo);
623 if (procmime_get_part(filename, mimeinfo) == 0) {
624 tmpfp = fopen(filename, "rb");
625 textview_show_html(textview, tmpfp, conv);
630 } else if (!g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
633 filename = procmime_get_tmp_file_name(mimeinfo);
634 if (procmime_get_part(filename, mimeinfo) == 0) {
635 tmpfp = fopen(filename, "rb");
636 textview_show_ertf(textview, tmpfp, conv);
642 tmpfp = fopen(mimeinfo->data.filename, "rb");
643 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
644 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
645 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
646 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
647 textview_write_line(textview, buf, conv);
651 conv_code_converter_destroy(conv);
652 procmime_force_encoding(0);
655 static void textview_show_html(TextView *textview, FILE *fp,
661 parser = html_parser_new(fp, conv);
662 g_return_if_fail(parser != NULL);
664 while ((str = html_parse(parser)) != NULL) {
665 if (parser->state == HTML_HREF) {
666 /* first time : get and copy the URL */
667 if (parser->href == NULL) {
668 /* ALF - the sylpheed html parser returns an empty string,
669 * if still inside an <a>, but already parsed past HREF */
670 str = strtok(str, " ");
672 parser->href = g_strdup(str);
673 /* the URL may (or not) be followed by the
675 str = strtok(NULL, "");
679 textview_write_link(textview, str, parser->href, NULL);
681 textview_write_line(textview, str, NULL);
683 html_parser_destroy(parser);
686 static void textview_show_ertf(TextView *textview, FILE *fp,
692 parser = ertf_parser_new(fp, conv);
693 g_return_if_fail(parser != NULL);
695 while ((str = ertf_parse(parser)) != NULL) {
696 textview_write_line(textview, str, NULL);
699 ertf_parser_destroy(parser);
702 /* get_uri_part() - retrieves a URI starting from scanpos.
703 Returns TRUE if succesful */
704 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
705 const gchar **bp, const gchar **ep)
709 g_return_val_if_fail(start != NULL, FALSE);
710 g_return_val_if_fail(scanpos != NULL, FALSE);
711 g_return_val_if_fail(bp != NULL, FALSE);
712 g_return_val_if_fail(ep != NULL, FALSE);
716 /* find end point of URI */
717 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
718 if (!isgraph(*(const guchar *)ep_) ||
719 !IS_ASCII(*(const guchar *)ep_) ||
720 strchr("()<>\"", *ep_))
724 /* no punctuation at end of string */
726 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
727 * should pass some URI type to this function and decide on that whether
728 * to perform punctuation stripping */
730 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
732 for (; ep_ - 1 > scanpos + 1 &&
733 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
744 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
746 return g_strndup(bp, ep - bp);
749 /* valid mail address characters */
750 #define IS_RFC822_CHAR(ch) \
755 !strchr("(),;<>\"", (ch)))
757 /* alphabet and number within 7bit ASCII */
758 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && isalnum(ch))
759 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
761 static GHashTable *create_domain_tab(void)
763 static const gchar *toplvl_domains [] = {
765 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
766 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
767 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
768 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
769 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
770 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
771 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
772 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
773 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
774 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
775 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
776 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
777 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
778 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
779 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
780 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
781 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
782 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
783 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
784 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
785 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
786 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
787 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
788 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
789 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
790 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
793 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
795 g_return_val_if_fail(htab, NULL);
796 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
797 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
801 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
803 const gint MAX_LVL_DOM_NAME_LEN = 6;
804 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
805 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
808 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
811 for (p = buf; p < m && first < last; *p++ = *first++)
815 return g_hash_table_lookup(tab, buf) != NULL;
818 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
819 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
820 const gchar **bp, const gchar **ep)
822 /* more complex than the uri part because we need to scan back and forward starting from
823 * the scan position. */
824 gboolean result = FALSE;
825 const gchar *bp_ = NULL;
826 const gchar *ep_ = NULL;
827 static GHashTable *dom_tab;
828 const gchar *last_dot = NULL;
829 const gchar *prelast_dot = NULL;
830 const gchar *last_tld_char = NULL;
832 /* the informative part of the email address (describing the name
833 * of the email address owner) may contain quoted parts. the
834 * closure stack stores the last encountered quotes. */
835 gchar closure_stack[128];
836 gchar *ptr = closure_stack;
838 g_return_val_if_fail(start != NULL, FALSE);
839 g_return_val_if_fail(scanpos != NULL, FALSE);
840 g_return_val_if_fail(bp != NULL, FALSE);
841 g_return_val_if_fail(ep != NULL, FALSE);
844 dom_tab = create_domain_tab();
845 g_return_val_if_fail(dom_tab, FALSE);
847 /* scan start of address */
848 for (bp_ = scanpos - 1;
849 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
852 /* TODO: should start with an alnum? */
854 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
857 if (bp_ != scanpos) {
858 /* scan end of address */
859 for (ep_ = scanpos + 1;
860 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
862 prelast_dot = last_dot;
864 if (*(last_dot + 1) == '.') {
865 if (prelast_dot == NULL)
867 last_dot = prelast_dot;
872 /* TODO: really should terminate with an alnum? */
873 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
878 if (last_dot == NULL)
881 last_dot = prelast_dot;
882 if (last_dot == NULL || (scanpos + 1 >= last_dot))
886 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
887 if (*last_tld_char == '?')
890 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
897 if (!result) return FALSE;
899 if (*ep_ && *(bp_ - 1) == '"' && *(ep_) == '"'
900 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
901 && IS_RFC822_CHAR(*(ep_ + 3))) {
902 /* this informative part with an @ in it is
903 * followed by the email address */
906 /* go to matching '>' (or next non-rfc822 char, like \n) */
907 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
910 /* include the bracket */
911 if (*ep_ == '>') ep_++;
913 /* include the leading quote */
921 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
922 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
925 /* see if this is <bracketed>; in this case we also scan for the informative part. */
926 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
929 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
930 #define IN_STACK() (ptr > closure_stack)
931 /* has underrun check */
932 #define POP_STACK() if(IN_STACK()) --ptr
933 /* has overrun check */
934 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
935 /* has underrun check */
936 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
940 /* scan for the informative part. */
941 for (bp_ -= 2; bp_ >= start; bp_--) {
942 /* if closure on the stack keep scanning */
943 if (PEEK_STACK() == *bp_) {
947 if (*bp_ == '\'' || *bp_ == '"') {
952 /* if nothing in the closure stack, do the special conditions
953 * the following if..else expression simply checks whether
954 * a token is acceptable. if not acceptable, the clause
955 * should terminate the loop with a 'break' */
958 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
959 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
960 /* hyphens are allowed, but only in
962 } else if (!ispunct(*bp_)) {
963 /* but anything not being a punctiation
966 break; /* anything else is rejected */
979 /* scan forward (should start with an alnum) */
980 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
990 #undef IS_ASCII_ALNUM
991 #undef IS_RFC822_CHAR
993 static gchar *make_email_string(const gchar *bp, const gchar *ep)
995 /* returns a mailto: URI; mailto: is also used to detect the
996 * uri type later on in the button_pressed signal handler */
1000 tmp = g_strndup(bp, ep - bp);
1001 result = g_strconcat("mailto:", tmp, NULL);
1007 static gchar *make_http_string(const gchar *bp, const gchar *ep)
1009 /* returns an http: URI; */
1013 tmp = g_strndup(bp, ep - bp);
1014 result = g_strconcat("http://", tmp, NULL);
1020 #define ADD_TXT_POS(bp_, ep_, pti_) \
1021 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1022 last = last->next; \
1023 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1024 last->next = NULL; \
1026 g_warning("alloc error scanning URIs\n"); \
1027 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1033 /* textview_make_clickable_parts() - colorizes clickable parts */
1034 static void textview_make_clickable_parts(TextView *textview,
1035 const gchar *fg_tag,
1036 const gchar *uri_tag,
1037 const gchar *linebuf)
1039 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1040 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1043 /* parse table - in order of priority */
1045 const gchar *needle; /* token */
1047 /* token search function */
1048 gchar *(*search) (const gchar *haystack,
1049 const gchar *needle);
1050 /* part parsing function */
1051 gboolean (*parse) (const gchar *start,
1052 const gchar *scanpos,
1055 /* part to URI function */
1056 gchar *(*build_uri) (const gchar *bp,
1060 static struct table parser[] = {
1061 {"http://", strcasestr, get_uri_part, make_uri_string},
1062 {"https://", strcasestr, get_uri_part, make_uri_string},
1063 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1064 {"www.", strcasestr, get_uri_part, make_http_string},
1065 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1066 {"@", strcasestr, get_email_part, make_email_string}
1068 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1071 const gchar *walk, *bp, *ep;
1074 const gchar *bp, *ep; /* text position */
1075 gint pti; /* index in parse table */
1076 struct txtpos *next; /* next */
1077 } head = {NULL, NULL, 0, NULL}, *last = &head;
1079 gtk_text_buffer_get_end_iter(buffer, &iter);
1081 /* parse for clickable parts, and build a list of begin and end positions */
1082 for (walk = linebuf, n = 0;;) {
1083 gint last_index = PARSE_ELEMS;
1084 gchar *scanpos = NULL;
1086 /* FIXME: this looks phony. scanning for anything in the parse table */
1087 for (n = 0; n < PARSE_ELEMS; n++) {
1090 tmp = parser[n].search(walk, parser[n].needle);
1092 if (scanpos == NULL || tmp < scanpos) {
1100 /* check if URI can be parsed */
1101 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1102 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1103 ADD_TXT_POS(bp, ep, last_index);
1107 strlen(parser[last_index].needle);
1112 /* colorize this line */
1114 const gchar *normal_text = linebuf;
1117 for (last = head.next; last != NULL;
1118 normal_text = last->ep, last = last->next) {
1120 uri = g_new(RemoteURI, 1);
1121 if (last->bp - normal_text > 0)
1122 gtk_text_buffer_insert_with_tags_by_name
1125 last->bp - normal_text,
1127 uri->uri = parser[last->pti].build_uri(last->bp,
1129 uri->start = gtk_text_iter_get_offset(&iter);
1130 gtk_text_buffer_insert_with_tags_by_name
1131 (buffer, &iter, last->bp, last->ep - last->bp,
1132 uri_tag, fg_tag, NULL);
1133 uri->end = gtk_text_iter_get_offset(&iter);
1134 textview->uri_list =
1135 g_slist_append(textview->uri_list, uri);
1139 gtk_text_buffer_insert_with_tags_by_name
1140 (buffer, &iter, normal_text, -1, fg_tag, NULL);
1142 gtk_text_buffer_insert_with_tags_by_name
1143 (buffer, &iter, linebuf, -1, fg_tag, NULL);
1149 static void textview_write_line(TextView *textview, const gchar *str,
1150 CodeConverter *conv)
1153 GtkTextBuffer *buffer;
1155 gchar buf[BUFFSIZE];
1157 gint quotelevel = -1;
1158 gchar quote_tag_str[10];
1160 text = GTK_TEXT_VIEW(textview->text);
1161 buffer = gtk_text_view_get_buffer(text);
1162 gtk_text_buffer_get_end_iter(buffer, &iter);
1165 strncpy2(buf, str, sizeof(buf));
1166 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1167 conv_utf8todisp(buf, sizeof(buf), str);
1170 //if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1173 /* change color of quotation
1174 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1175 Up to 3 levels of quotations are detected, and each
1176 level is colored using a different color. */
1177 if (prefs_common.enable_color
1178 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1179 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1181 /* set up the correct foreground color */
1182 if (quotelevel > 2) {
1183 /* recycle colors */
1184 if (prefs_common.recycle_quote_colors)
1191 if (quotelevel == -1)
1194 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1195 "quote%d", quotelevel);
1196 fg_color = quote_tag_str;
1199 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1200 fg_color = "signature";
1201 textview->is_in_signature = TRUE;
1204 if (prefs_common.enable_color)
1205 textview_make_clickable_parts(textview, fg_color, "link", buf);
1207 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1210 void textview_write_link(TextView *textview, const gchar *str,
1211 const gchar *uri, CodeConverter *conv)
1213 GdkColor *link_color = NULL;
1215 GtkTextBuffer *buffer;
1217 gchar buf[BUFFSIZE];
1224 text = GTK_TEXT_VIEW(textview->text);
1225 buffer = gtk_text_view_get_buffer(text);
1226 gtk_text_buffer_get_end_iter(buffer, &iter);
1229 strncpy2(buf, str, sizeof(buf));
1230 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1231 conv_utf8todisp(buf, sizeof(buf), str);
1235 gtk_text_buffer_get_end_iter(buffer, &iter);
1237 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1238 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1240 if (prefs_common.enable_color) {
1241 link_color = &uri_color;
1243 r_uri = g_new(RemoteURI, 1);
1244 r_uri->uri = g_strdup(uri);
1245 r_uri->start = gtk_text_iter_get_offset(&iter);
1246 gtk_text_buffer_insert_with_tags_by_name
1247 (buffer, &iter, bufp, -1, "link", NULL);
1248 r_uri->end = gtk_text_iter_get_offset(&iter);
1249 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1252 void textview_clear(TextView *textview)
1254 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1255 GtkTextBuffer *buffer;
1257 buffer = gtk_text_view_get_buffer(text);
1258 gtk_text_buffer_set_text(buffer, "", -1);
1260 TEXTVIEW_STATUSBAR_POP(textview);
1261 textview_uri_list_remove_all(textview->uri_list);
1262 textview->uri_list = NULL;
1264 textview->body_pos = 0;
1267 void textview_destroy(TextView *textview)
1269 textview_uri_list_remove_all(textview->uri_list);
1270 textview->uri_list = NULL;
1275 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1277 textview->show_all_headers = all_headers;
1280 void textview_set_font(TextView *textview, const gchar *codeset)
1282 if (prefs_common.textfont) {
1283 PangoFontDescription *font_desc = NULL;
1285 font_desc = pango_font_description_from_string
1286 (prefs_common.textfont);
1288 gtk_widget_modify_font(textview->text, font_desc);
1289 pango_font_description_free(font_desc);
1292 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1293 prefs_common.line_space / 2);
1294 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1295 prefs_common.line_space / 2);
1298 void textview_set_text(TextView *textview, const gchar *text)
1301 GtkTextBuffer *buffer;
1303 g_return_if_fail(textview != NULL);
1304 g_return_if_fail(text != NULL);
1306 textview_clear(textview);
1308 view = GTK_TEXT_VIEW(textview->text);
1309 buffer = gtk_text_view_get_buffer(view);
1310 gtk_text_buffer_set_text(buffer, text, strlen(text));
1326 H_ORGANIZATION = 11,
1329 void textview_set_position(TextView *textview, gint pos)
1331 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1332 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1335 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1336 gtk_text_buffer_place_cursor(buffer, &iter);
1337 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1340 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1342 gchar buf[BUFFSIZE];
1343 GPtrArray *headers, *sorted_headers;
1344 GSList *disphdr_list;
1348 g_return_val_if_fail(fp != NULL, NULL);
1350 if (textview->show_all_headers)
1351 return procheader_get_header_array_asis(fp);
1353 if (!prefs_common.display_header) {
1354 while (fgets(buf, sizeof(buf), fp) != NULL)
1355 if (buf[0] == '\r' || buf[0] == '\n') break;
1359 headers = procheader_get_header_array_asis(fp);
1361 sorted_headers = g_ptr_array_new();
1363 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1364 disphdr_list = disphdr_list->next) {
1365 DisplayHeaderProp *dp =
1366 (DisplayHeaderProp *)disphdr_list->data;
1368 for (i = 0; i < headers->len; i++) {
1369 header = g_ptr_array_index(headers, i);
1371 if (procheader_headername_equal(header->name,
1374 procheader_header_free(header);
1376 g_ptr_array_add(sorted_headers, header);
1378 g_ptr_array_remove_index(headers, i);
1384 if (prefs_common.show_other_header) {
1385 for (i = 0; i < headers->len; i++) {
1386 header = g_ptr_array_index(headers, i);
1387 g_ptr_array_add(sorted_headers, header);
1389 g_ptr_array_free(headers, TRUE);
1391 procheader_header_array_destroy(headers);
1394 return sorted_headers;
1397 static void textview_show_header(TextView *textview, GPtrArray *headers)
1399 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1400 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1405 g_return_if_fail(headers != NULL);
1407 for (i = 0; i < headers->len; i++) {
1408 header = g_ptr_array_index(headers, i);
1409 g_return_if_fail(header->name != NULL);
1411 gtk_text_buffer_get_end_iter (buffer, &iter);
1412 gtk_text_buffer_insert_with_tags_by_name
1413 (buffer, &iter, header->name, -1,
1414 "header_title", "header", NULL);
1415 if (header->name[strlen(header->name) - 1] != ' ')
1416 gtk_text_buffer_insert_with_tags_by_name
1417 (buffer, &iter, " ", 1,
1418 "header_title", "header", NULL);
1420 if (procheader_headername_equal(header->name, "Subject") ||
1421 procheader_headername_equal(header->name, "From") ||
1422 procheader_headername_equal(header->name, "To") ||
1423 procheader_headername_equal(header->name, "Cc"))
1424 unfold_line(header->body);
1428 if (textview->text_is_mb == TRUE)
1429 conv_unreadable_locale(header->body);
1432 if (prefs_common.enable_color &&
1433 (procheader_headername_equal(header->name, "X-Mailer") ||
1434 procheader_headername_equal(header->name,
1436 strstr(header->body, "Sylpheed") != NULL) {
1437 gtk_text_buffer_get_end_iter (buffer, &iter);
1438 gtk_text_buffer_insert_with_tags_by_name
1439 (buffer, &iter, header->body, -1,
1440 "header", "emphasis", NULL);
1441 } else if (prefs_common.enable_color) {
1442 textview_make_clickable_parts(textview, "header", "link",
1445 textview_make_clickable_parts(textview, "header", NULL,
1448 gtk_text_buffer_get_end_iter (buffer, &iter);
1449 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1454 gboolean textview_search_string(TextView *textview, const gchar *str,
1457 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1458 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1460 GtkTextIter iter, start, end, real_end, *pos;
1461 gboolean found = FALSE;
1462 gint insert_offset, selbound_offset;
1464 /* reset selection */
1465 mark = gtk_text_buffer_get_mark(buffer, "insert");
1466 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1467 insert_offset = gtk_text_iter_get_offset(&start);
1468 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1469 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1470 selbound_offset = gtk_text_iter_get_offset(&end);
1472 pos = insert_offset > selbound_offset ? &start : &end;
1473 gtk_text_buffer_place_cursor(buffer, pos);
1476 mark = gtk_text_buffer_get_insert(buffer);
1477 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1479 found = gtk_text_iter_forward_search(&iter, str,
1480 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1481 &start, &end, NULL);
1485 gtk_text_buffer_get_end_iter(buffer, &real_end);
1486 text = gtk_text_buffer_get_text(buffer, &iter,
1489 while (!found && i++ < strlen(text) - 1) {
1490 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1493 i += gtk_text_iter_get_offset(&end);
1496 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1497 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1505 gtk_text_buffer_place_cursor(buffer, &start);
1506 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1508 mark = gtk_text_buffer_get_mark(buffer, "insert");
1509 gtk_text_view_scroll_mark_onscreen(text, mark);
1515 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1518 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1519 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1521 GtkTextIter iter, start, real_start, end, *pos;
1522 gboolean found = FALSE;
1523 gint insert_offset, selbound_offset;
1525 /* reset selection */
1526 mark = gtk_text_buffer_get_mark(buffer, "insert");
1527 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1528 insert_offset = gtk_text_iter_get_offset(&start);
1529 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1530 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1531 selbound_offset = gtk_text_iter_get_offset(&end);
1533 pos = insert_offset < selbound_offset ? &start : &end;
1534 gtk_text_buffer_place_cursor(buffer, pos);
1537 mark = gtk_text_buffer_get_insert(buffer);
1538 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1540 found = gtk_text_iter_backward_search(&iter, str,
1541 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1542 &start, &end, NULL);
1546 if (gtk_text_iter_get_offset(&iter) == 0)
1547 gtk_text_buffer_get_end_iter(buffer, &iter);
1549 i = gtk_text_iter_get_offset(&iter) - strlen(str) - 1;
1550 gtk_text_buffer_get_start_iter(buffer, &real_start);
1552 text = gtk_text_buffer_get_text(buffer, &real_start,
1555 while (!found && i-- > 0) {
1556 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1560 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1561 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1569 gtk_text_buffer_place_cursor(buffer, &end);
1570 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1572 mark = gtk_text_buffer_get_mark(buffer, "insert");
1573 gtk_text_view_scroll_mark_onscreen(text, mark);
1579 void textview_scroll_one_line(TextView *textview, gboolean up)
1581 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1582 GtkAdjustment *vadj = text->vadjustment;
1585 if (prefs_common.enable_smooth_scroll) {
1586 textview_smooth_scroll_one_line(textview, up);
1591 upper = vadj->upper - vadj->page_size;
1592 if (vadj->value < upper) {
1593 vadj->value += vadj->step_increment;
1594 vadj->value = MIN(vadj->value, upper);
1595 g_signal_emit_by_name(G_OBJECT(vadj),
1596 "value_changed", 0);
1599 if (vadj->value > 0.0) {
1600 vadj->value -= vadj->step_increment;
1601 vadj->value = MAX(vadj->value, 0.0);
1602 g_signal_emit_by_name(G_OBJECT(vadj),
1603 "value_changed", 0);
1608 gboolean textview_scroll_page(TextView *textview, gboolean up)
1610 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1611 GtkAdjustment *vadj = text->vadjustment;
1615 if (prefs_common.enable_smooth_scroll)
1616 return textview_smooth_scroll_page(textview, up);
1618 if (prefs_common.scroll_halfpage)
1619 page_incr = vadj->page_increment / 2;
1621 page_incr = vadj->page_increment;
1624 upper = vadj->upper - vadj->page_size;
1625 if (vadj->value < upper) {
1626 vadj->value += page_incr;
1627 vadj->value = MIN(vadj->value, upper);
1628 g_signal_emit_by_name(G_OBJECT(vadj),
1629 "value_changed", 0);
1633 if (vadj->value > 0.0) {
1634 vadj->value -= page_incr;
1635 vadj->value = MAX(vadj->value, 0.0);
1636 g_signal_emit_by_name(G_OBJECT(vadj),
1637 "value_changed", 0);
1645 static void textview_smooth_scroll_do(TextView *textview,
1646 gfloat old_value, gfloat last_value,
1649 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1650 GtkAdjustment *vadj = text->vadjustment;
1655 if (old_value < last_value) {
1656 change_value = last_value - old_value;
1659 change_value = old_value - last_value;
1663 for (i = step; i <= change_value; i += step) {
1664 vadj->value = old_value + (up ? -i : i);
1665 g_signal_emit_by_name(G_OBJECT(vadj),
1666 "value_changed", 0);
1669 vadj->value = last_value;
1670 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1673 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1675 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1676 GtkAdjustment *vadj = text->vadjustment;
1682 upper = vadj->upper - vadj->page_size;
1683 if (vadj->value < upper) {
1684 old_value = vadj->value;
1685 last_value = vadj->value + vadj->step_increment;
1686 last_value = MIN(last_value, upper);
1688 textview_smooth_scroll_do(textview, old_value,
1690 prefs_common.scroll_step);
1693 if (vadj->value > 0.0) {
1694 old_value = vadj->value;
1695 last_value = vadj->value - vadj->step_increment;
1696 last_value = MAX(last_value, 0.0);
1698 textview_smooth_scroll_do(textview, old_value,
1700 prefs_common.scroll_step);
1705 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1707 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1708 GtkAdjustment *vadj = text->vadjustment;
1714 if (prefs_common.scroll_halfpage)
1715 page_incr = vadj->page_increment / 2;
1717 page_incr = vadj->page_increment;
1720 upper = vadj->upper - vadj->page_size;
1721 if (vadj->value < upper) {
1722 old_value = vadj->value;
1723 last_value = vadj->value + page_incr;
1724 last_value = MIN(last_value, upper);
1726 textview_smooth_scroll_do(textview, old_value,
1728 prefs_common.scroll_step);
1732 if (vadj->value > 0.0) {
1733 old_value = vadj->value;
1734 last_value = vadj->value - page_incr;
1735 last_value = MAX(last_value, 0.0);
1737 textview_smooth_scroll_do(textview, old_value,
1739 prefs_common.scroll_step);
1747 #define KEY_PRESS_EVENT_STOP() \
1748 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1751 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1754 SummaryView *summaryview = NULL;
1755 MessageView *messageview = textview->messageview;
1757 if (!event) return FALSE;
1758 if (messageview->mainwin)
1759 summaryview = messageview->mainwin->summaryview;
1761 switch (event->keyval) {
1776 summary_pass_key_press_event(summaryview, event);
1778 textview_scroll_page
1781 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1784 textview_scroll_page(textview, TRUE);
1787 textview_scroll_one_line
1788 (textview, (event->state &
1789 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1793 summary_pass_key_press_event(summaryview, event);
1798 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1799 KEY_PRESS_EVENT_STOP();
1800 mimeview_pass_key_press_event(messageview->mimeview,
1804 /* possible fall through */
1807 event->window != messageview->mainwin->window->window) {
1808 GdkEventKey tmpev = *event;
1810 tmpev.window = messageview->mainwin->window->window;
1811 KEY_PRESS_EVENT_STOP();
1812 gtk_widget_event(messageview->mainwin->window,
1813 (GdkEvent *)&tmpev);
1821 static gboolean textview_motion_notify(GtkWidget *widget,
1822 GdkEventMotion *event,
1825 textview_uri_update(textview, event->x, event->y);
1826 gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1831 static gboolean textview_leave_notify(GtkWidget *widget,
1832 GdkEventCrossing *event,
1835 textview_uri_update(textview, -1, -1);
1840 static gboolean textview_visibility_notify(GtkWidget *widget,
1841 GdkEventVisibility *event,
1847 window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
1848 GTK_TEXT_WINDOW_TEXT);
1850 /* check if occurred for the text window part */
1851 if (window != event->window)
1854 gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1855 textview_uri_update(textview, wx, wy);
1860 static void textview_uri_update(TextView *textview, gint x, gint y)
1862 GtkTextBuffer *buffer;
1863 GtkTextIter start_iter, end_iter;
1864 RemoteURI *uri = NULL;
1866 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1868 if (x != -1 && y != -1) {
1874 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text),
1875 GTK_TEXT_WINDOW_WIDGET,
1877 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1880 tags = gtk_text_iter_get_tags(&iter);
1881 for (cur = tags; cur != NULL; cur = cur->next) {
1882 GtkTextTag *tag = cur->data;
1885 g_object_get(G_OBJECT(tag), "name", &name, NULL);
1886 if (!strcmp(name, "link")
1887 && textview_get_uri_range(textview, &iter, tag,
1888 &start_iter, &end_iter))
1889 uri = textview_get_uri_from_range(textview,
1901 if (uri != textview->uri_hover) {
1904 if (textview->uri_hover)
1905 gtk_text_buffer_remove_tag_by_name(buffer,
1907 &textview->uri_hover_start_iter,
1908 &textview->uri_hover_end_iter);
1910 textview->uri_hover = uri;
1912 textview->uri_hover_start_iter = start_iter;
1913 textview->uri_hover_end_iter = end_iter;
1916 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1917 GTK_TEXT_WINDOW_TEXT);
1918 gdk_window_set_cursor(window, uri ? hand_cursor : text_cursor);
1920 TEXTVIEW_STATUSBAR_POP(textview);
1925 gtk_text_buffer_apply_tag_by_name(buffer,
1930 trimmed_uri = trim_string(uri->uri, 60);
1931 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1932 g_free(trimmed_uri);
1937 static gboolean textview_get_uri_range(TextView *textview,
1940 GtkTextIter *start_iter,
1941 GtkTextIter *end_iter)
1943 GtkTextIter _start_iter, _end_iter;
1946 if (!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
1947 debug_print("Can't find end");
1951 _start_iter = _end_iter;
1952 if (!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
1953 debug_print("Can't find start.");
1957 *start_iter = _start_iter;
1958 *end_iter = _end_iter;
1963 static RemoteURI *textview_get_uri_from_range(TextView *textview,
1966 GtkTextIter *start_iter,
1967 GtkTextIter *end_iter)
1969 gint start_pos, end_pos, cur_pos;
1970 RemoteURI *uri = NULL;
1973 start_pos = gtk_text_iter_get_offset(start_iter);
1974 end_pos = gtk_text_iter_get_offset(end_iter);
1975 cur_pos = gtk_text_iter_get_offset(iter);
1977 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1978 RemoteURI *uri_ = (RemoteURI *)cur->data;
1979 if (start_pos == uri_->start &&
1980 end_pos == uri_->end) {
1983 } else if (start_pos == uri_->start ||
1984 end_pos == uri_->end) {
1985 /* in case of contiguous links, textview_get_uri_range
1986 * returns a broader range (start of 1st link to end
1988 * In that case, correct link is the one covering
1991 if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
2001 static RemoteURI *textview_get_uri(TextView *textview,
2005 GtkTextIter start_iter, end_iter;
2006 RemoteURI *uri = NULL;
2008 if (textview_get_uri_range(textview, iter, tag, &start_iter,
2010 uri = textview_get_uri_from_range(textview, iter, tag,
2011 &start_iter, &end_iter);
2016 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
2017 GdkEvent *event, GtkTextIter *iter,
2020 GdkEventButton *bevent;
2021 RemoteURI *uri = NULL;
2028 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2029 && event->type != GDK_MOTION_NOTIFY)
2032 uri = textview_get_uri(textview, iter, tag);
2036 bevent = (GdkEventButton *) event;
2038 /* doubleclick: open compose / add address / browser */
2039 if ((event->type == GDK_BUTTON_PRESS && bevent->button == 1) ||
2040 bevent->button == 2 || bevent->button == 3) {
2041 if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2042 if (bevent->button == 3) {
2043 gchar *fromname, *fromaddress;
2046 fromaddress = g_strdup(uri->uri + 7);
2047 /* Hiroyuki: please put this function in utils.c! */
2048 fromname = procheader_get_fromname(fromaddress);
2049 extract_address(fromaddress);
2050 g_message("adding from textview %s <%s>", fromname, fromaddress);
2051 /* Add to address book - Match */
2052 addressbook_add_contact( fromname, fromaddress, NULL );
2054 g_free(fromaddress);
2057 PrefsAccount *account = NULL;
2059 if (textview->messageview && textview->messageview->msginfo &&
2060 textview->messageview->msginfo->folder) {
2061 FolderItem *folder_item;
2063 folder_item = textview->messageview->msginfo->folder;
2064 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2065 account = account_find_from_id(folder_item->prefs->default_account);
2067 compose_new(account, uri->uri + 7, NULL);
2071 if (bevent->button == 1 &&
2072 textview_uri_security_check(textview, uri) == TRUE)
2074 prefs_common.uri_cmd);
2075 else if (bevent->button == 3) {
2077 G_OBJECT(textview->popup_menu),
2078 "menu_button", uri);
2079 gtk_menu_popup(GTK_MENU(textview->popup_menu),
2080 NULL, NULL, NULL, NULL,
2081 bevent->button, bevent->time);
2091 *\brief Check to see if a web URL has been disguised as a different
2092 * URL (possible with HTML email).
2094 *\param uri The uri to check
2096 *\param textview The TextView the URL is contained in
2098 *\return gboolean TRUE if the URL is ok, or if the user chose to open
2099 * it anyway, otherwise FALSE
2101 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2104 gboolean retval = TRUE;
2105 GtkTextBuffer *buffer;
2106 GtkTextIter start, end;
2108 if (is_uri_string(uri->uri) == FALSE)
2111 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2113 gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2114 gtk_text_buffer_get_iter_at_offset(buffer, &end, uri->end);
2116 visible_str = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2118 if (visible_str == NULL)
2121 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2123 gchar *visible_uri_path;
2125 uri_path = get_uri_path(uri->uri);
2126 visible_uri_path = get_uri_path(visible_str);
2127 if (strcmp(uri_path, visible_uri_path) != 0)
2131 if (retval == FALSE) {
2135 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2136 "the apparent URL (%s).\n"
2138 uri->uri, visible_str);
2139 aval = alertpanel_with_type(_("Warning"), msg,
2140 GTK_STOCK_YES, GTK_STOCK_NO,
2141 NULL, NULL, ALERT_WARNING);
2143 if (aval == G_ALERTDEFAULT)
2147 g_free(visible_str);
2152 static void textview_uri_list_remove_all(GSList *uri_list)
2156 for (cur = uri_list; cur != NULL; cur = cur->next) {
2158 g_free(((RemoteURI *)cur->data)->uri);
2163 g_slist_free(uri_list);
2166 static void open_uri_cb (TextView *textview, guint action, void *data)
2168 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->popup_menu),
2173 if (textview_uri_security_check(textview, uri) == TRUE)
2175 prefs_common.uri_cmd);
2176 g_object_set_data(G_OBJECT(textview->popup_menu), "menu_button",
2180 static void copy_uri_cb (TextView *textview, guint action, void *data)
2182 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->popup_menu),
2187 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), uri->uri, -1);
2188 g_object_set_data(G_OBJECT(textview->popup_menu), "menu_button",