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,
195 static void add_uri_to_addrbook_cb (TextView *textview,
198 static void mail_to_uri_cb (TextView *textview,
202 static GtkItemFactoryEntry textview_link_popup_entries[] =
204 {N_("/_Open link"), NULL, open_uri_cb, 0, NULL},
205 {N_("/_Copy link location"), NULL, copy_uri_cb, 0, NULL},
208 static GtkItemFactoryEntry textview_mail_popup_entries[] =
210 {N_("/_Add to addressbook"), NULL, add_uri_to_addrbook_cb, 0, NULL},
211 {N_("/_Email"), NULL, mail_to_uri_cb, 0, NULL},
215 TextView *textview_create(void)
219 GtkWidget *scrolledwin;
221 GtkTextBuffer *buffer;
222 GtkClipboard *clipboard;
223 GtkItemFactory *link_popupfactory, *mail_popupfactory;
224 GtkWidget *link_popupmenu, *mail_popupmenu;
227 debug_print("Creating text view...\n");
228 textview = g_new0(TextView, 1);
230 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
231 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
232 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
233 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
235 gtk_widget_set_size_request
236 (scrolledwin, prefs_common.mainview_width, -1);
238 /* create GtkSText widgets for single-byte and multi-byte character */
239 text = gtk_text_view_new();
240 gtk_widget_add_events(text, GDK_LEAVE_NOTIFY_MASK);
241 gtk_widget_show(text);
242 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
243 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
244 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
245 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
246 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
248 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
249 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
250 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
252 gtk_widget_ensure_style(text);
254 gtk_widget_ref(scrolledwin);
256 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
259 hand_cursor = gdk_cursor_new(GDK_HAND2);
261 text_cursor = gdk_cursor_new(GDK_XTERM);
263 g_signal_connect(G_OBJECT(text), "key_press_event",
264 G_CALLBACK(textview_key_pressed),
266 g_signal_connect(G_OBJECT(text), "motion_notify_event",
267 G_CALLBACK(textview_motion_notify),
269 g_signal_connect(G_OBJECT(text), "leave_notify_event",
270 G_CALLBACK(textview_leave_notify),
272 g_signal_connect(G_OBJECT(text), "visibility_notify_event",
273 G_CALLBACK(textview_visibility_notify),
276 gtk_widget_show(scrolledwin);
278 vbox = gtk_vbox_new(FALSE, 0);
279 gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
281 gtk_widget_show(vbox);
283 n_entries = sizeof(textview_link_popup_entries) /
284 sizeof(textview_link_popup_entries[0]);
285 link_popupmenu = menu_create_items(textview_link_popup_entries, n_entries,
286 "<UriPopupMenu>", &link_popupfactory,
289 n_entries = sizeof(textview_mail_popup_entries) /
290 sizeof(textview_mail_popup_entries[0]);
291 mail_popupmenu = menu_create_items(textview_mail_popup_entries, n_entries,
292 "<UriPopupMenu>", &mail_popupfactory,
295 textview->vbox = vbox;
296 textview->scrolledwin = scrolledwin;
297 textview->text = text;
298 textview->uri_list = NULL;
299 textview->body_pos = 0;
300 textview->show_all_headers = FALSE;
301 textview->last_buttonpress = GDK_NOTHING;
302 textview->link_popup_menu = link_popupmenu;
303 textview->link_popup_factory = link_popupfactory;
304 textview->mail_popup_menu = mail_popupmenu;
305 textview->mail_popup_factory = mail_popupfactory;
310 static void textview_create_tags(GtkTextView *text, TextView *textview)
312 GtkTextBuffer *buffer;
314 static PangoFontDescription *font_desc, *bold_font_desc;
317 font_desc = pango_font_description_from_string
320 if (!bold_font_desc) {
321 bold_font_desc = pango_font_description_from_string
323 pango_font_description_set_weight
324 (bold_font_desc, PANGO_WEIGHT_BOLD);
327 buffer = gtk_text_view_get_buffer(text);
329 gtk_text_buffer_create_tag(buffer, "header",
330 "pixels-above-lines", 0,
331 "pixels-above-lines-set", TRUE,
332 "pixels-below-lines", 0,
333 "pixels-below-lines-set", TRUE,
334 "font-desc", font_desc,
336 "left-margin-set", TRUE,
338 gtk_text_buffer_create_tag(buffer, "header_title",
339 "font-desc", bold_font_desc,
341 gtk_text_buffer_create_tag(buffer, "quote0",
342 "foreground-gdk", "e_colors[0],
344 gtk_text_buffer_create_tag(buffer, "quote1",
345 "foreground-gdk", "e_colors[1],
347 gtk_text_buffer_create_tag(buffer, "quote2",
348 "foreground-gdk", "e_colors[2],
350 gtk_text_buffer_create_tag(buffer, "emphasis",
351 "foreground-gdk", &emphasis_color,
353 gtk_text_buffer_create_tag(buffer, "signature",
354 "foreground-gdk", &signature_color,
356 tag = gtk_text_buffer_create_tag(buffer, "link",
357 "foreground-gdk", &uri_color,
359 gtk_text_buffer_create_tag(buffer, "link-hover",
360 "underline", PANGO_UNDERLINE_SINGLE,
363 g_signal_connect(G_OBJECT(tag), "event",
364 G_CALLBACK(textview_uri_button_pressed), textview);
367 void textview_init(TextView *textview)
369 textview_update_message_colors();
370 textview_set_all_headers(textview, FALSE);
371 textview_set_font(textview, NULL);
372 textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
375 void textview_update_message_colors(void)
377 GdkColor black = {0, 0, 0, 0};
379 if (prefs_common.enable_color) {
380 /* grab the quote colors, converting from an int to a GdkColor */
381 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
383 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
385 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
387 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
389 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
392 quote_colors[0] = quote_colors[1] = quote_colors[2] =
393 uri_color = emphasis_color = signature_color = black;
397 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
402 if ((fp = fopen(file, "rb")) == NULL) {
403 FILE_OP_ERROR(file, "fopen");
407 textview_clear(textview);
409 textview_add_parts(textview, mimeinfo);
413 textview_set_position(textview, 0);
416 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
420 g_return_if_fail(mimeinfo != NULL);
421 g_return_if_fail(fp != NULL);
423 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
424 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822"))) {
425 textview_clear(textview);
426 textview_add_parts(textview, mimeinfo);
430 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
433 textview_clear(textview);
435 if (mimeinfo->type == MIMETYPE_MULTIPART)
436 textview_add_parts(textview, mimeinfo);
438 textview_write_body(textview, mimeinfo);
442 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
445 GtkTextBuffer *buffer;
448 GPtrArray *headers = NULL;
453 g_return_if_fail(mimeinfo != NULL);
454 text = GTK_TEXT_VIEW(textview->text);
455 buffer = gtk_text_view_get_buffer(text);
456 charcount = gtk_text_buffer_get_char_count(buffer);
457 gtk_text_buffer_get_end_iter(buffer, &iter);
459 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
461 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
464 fp = fopen(mimeinfo->data.filename, "rb");
465 fseek(fp, mimeinfo->offset, SEEK_SET);
466 headers = textview_scan_header(textview, fp);
469 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
470 textview_show_header(textview, headers);
471 procheader_header_array_destroy(headers);
477 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
478 content_type = procmime_get_content_type_str(mimeinfo->type,
481 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
483 g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n",
484 name, content_type, mimeinfo->length);
486 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
487 content_type, mimeinfo->length);
489 g_free(content_type);
491 if (mimeinfo->type != MIMETYPE_TEXT) {
492 gtk_text_buffer_insert(buffer, &iter, buf, -1);
493 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
494 if (prefs_common.display_header && (charcount > 0))
495 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
497 textview_write_body(textview, mimeinfo);
501 static void recursive_add_parts(TextView *textview, GNode *node)
506 mimeinfo = (MimeInfo *) node->data;
508 textview_add_part(textview, mimeinfo);
510 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
511 (mimeinfo->type != MIMETYPE_MESSAGE))
514 if (g_ascii_strcasecmp(mimeinfo->subtype, "alternative") == 0) {
515 GNode * prefered_body;
523 prefered_body = NULL;
526 for (iter = g_node_first_child(node) ; iter != NULL ;
527 iter = g_node_next_sibling(iter)) {
532 submime = (MimeInfo *) iter->data;
533 if (submime->type == MIMETYPE_TEXT)
536 if (submime->subtype != NULL) {
537 if (g_ascii_strcasecmp(submime->subtype, "plain") == 0)
541 if (score > prefered_score) {
542 prefered_score = score;
543 prefered_body = iter;
547 if (prefered_body != NULL) {
548 recursive_add_parts(textview, prefered_body);
552 for (iter = g_node_first_child(node) ; iter != NULL ;
553 iter = g_node_next_sibling(iter)) {
554 recursive_add_parts(textview, iter);
559 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
561 g_return_if_fail(mimeinfo != NULL);
563 recursive_add_parts(textview, mimeinfo->node);
566 #define TEXT_INSERT(str) \
567 gtk_text_buffer_insert(buffer, &iter, str, -1)
569 void textview_show_error(TextView *textview)
572 GtkTextBuffer *buffer;
575 textview_set_font(textview, NULL);
576 textview_clear(textview);
578 text = GTK_TEXT_VIEW(textview->text);
579 buffer = gtk_text_view_get_buffer(text);
580 gtk_text_buffer_get_start_iter(buffer, &iter);
582 TEXT_INSERT(_("This message can't be displayed.\n"));
586 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
589 GtkTextBuffer *buffer;
592 if (!partinfo) return;
594 textview_set_font(textview, NULL);
595 textview_clear(textview);
597 text = GTK_TEXT_VIEW(textview->text);
598 buffer = gtk_text_view_get_buffer(text);
599 gtk_text_buffer_get_start_iter(buffer, &iter);
601 TEXT_INSERT(_("The following can be performed on this part by "));
602 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
604 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
605 TEXT_INSERT(_(" To display as text select 'Display as text' "));
606 TEXT_INSERT(_("(Shortcut key: 't')\n"));
607 TEXT_INSERT(_(" To open with an external program select 'Open' "));
608 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
609 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
610 TEXT_INSERT(_("mouse button),\n"));
611 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
617 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
622 const gchar *charset;
624 if (textview->messageview->forced_charset)
625 charset = textview->messageview->forced_charset;
627 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
629 textview_set_font(textview, charset);
631 conv = conv_code_converter_new(charset);
633 procmime_force_encoding(textview->messageview->forced_encoding);
635 textview->is_in_signature = FALSE;
637 procmime_decode_content(mimeinfo);
639 if (!g_ascii_strcasecmp(mimeinfo->subtype, "html")) {
642 filename = procmime_get_tmp_file_name(mimeinfo);
643 if (procmime_get_part(filename, mimeinfo) == 0) {
644 tmpfp = fopen(filename, "rb");
645 textview_show_html(textview, tmpfp, conv);
650 } else if (!g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
653 filename = procmime_get_tmp_file_name(mimeinfo);
654 if (procmime_get_part(filename, mimeinfo) == 0) {
655 tmpfp = fopen(filename, "rb");
656 textview_show_ertf(textview, tmpfp, conv);
662 tmpfp = fopen(mimeinfo->data.filename, "rb");
663 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
664 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
665 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
666 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
667 textview_write_line(textview, buf, conv);
671 conv_code_converter_destroy(conv);
672 procmime_force_encoding(0);
675 static void textview_show_html(TextView *textview, FILE *fp,
681 parser = html_parser_new(fp, conv);
682 g_return_if_fail(parser != NULL);
684 while ((str = html_parse(parser)) != NULL) {
685 if (parser->state == HTML_HREF) {
686 /* first time : get and copy the URL */
687 if (parser->href == NULL) {
688 /* ALF - the sylpheed html parser returns an empty string,
689 * if still inside an <a>, but already parsed past HREF */
690 str = strtok(str, " ");
692 parser->href = g_strdup(str);
693 /* the URL may (or not) be followed by the
695 str = strtok(NULL, "");
699 textview_write_link(textview, str, parser->href, NULL);
701 textview_write_line(textview, str, NULL);
703 html_parser_destroy(parser);
706 static void textview_show_ertf(TextView *textview, FILE *fp,
712 parser = ertf_parser_new(fp, conv);
713 g_return_if_fail(parser != NULL);
715 while ((str = ertf_parse(parser)) != NULL) {
716 textview_write_line(textview, str, NULL);
719 ertf_parser_destroy(parser);
722 /* get_uri_part() - retrieves a URI starting from scanpos.
723 Returns TRUE if succesful */
724 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
725 const gchar **bp, const gchar **ep)
729 g_return_val_if_fail(start != NULL, FALSE);
730 g_return_val_if_fail(scanpos != NULL, FALSE);
731 g_return_val_if_fail(bp != NULL, FALSE);
732 g_return_val_if_fail(ep != NULL, FALSE);
736 /* find end point of URI */
737 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
738 if (!isgraph(*(const guchar *)ep_) ||
739 !IS_ASCII(*(const guchar *)ep_) ||
740 strchr("()<>\"", *ep_))
744 /* no punctuation at end of string */
746 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
747 * should pass some URI type to this function and decide on that whether
748 * to perform punctuation stripping */
750 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
752 for (; ep_ - 1 > scanpos + 1 &&
753 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
764 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
766 return g_strndup(bp, ep - bp);
769 /* valid mail address characters */
770 #define IS_RFC822_CHAR(ch) \
775 !strchr("(),;<>\"", (ch)))
777 /* alphabet and number within 7bit ASCII */
778 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && isalnum(ch))
779 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
781 static GHashTable *create_domain_tab(void)
783 static const gchar *toplvl_domains [] = {
785 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
786 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
787 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
788 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
789 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
790 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
791 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
792 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
793 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
794 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
795 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
796 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
797 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
798 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
799 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
800 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
801 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
802 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
803 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
804 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
805 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
806 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
807 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
808 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
809 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
810 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
813 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
815 g_return_val_if_fail(htab, NULL);
816 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
817 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
821 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
823 const gint MAX_LVL_DOM_NAME_LEN = 6;
824 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
825 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
828 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
831 for (p = buf; p < m && first < last; *p++ = *first++)
835 return g_hash_table_lookup(tab, buf) != NULL;
838 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
839 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
840 const gchar **bp, const gchar **ep)
842 /* more complex than the uri part because we need to scan back and forward starting from
843 * the scan position. */
844 gboolean result = FALSE;
845 const gchar *bp_ = NULL;
846 const gchar *ep_ = NULL;
847 static GHashTable *dom_tab;
848 const gchar *last_dot = NULL;
849 const gchar *prelast_dot = NULL;
850 const gchar *last_tld_char = NULL;
852 /* the informative part of the email address (describing the name
853 * of the email address owner) may contain quoted parts. the
854 * closure stack stores the last encountered quotes. */
855 gchar closure_stack[128];
856 gchar *ptr = closure_stack;
858 g_return_val_if_fail(start != NULL, FALSE);
859 g_return_val_if_fail(scanpos != NULL, FALSE);
860 g_return_val_if_fail(bp != NULL, FALSE);
861 g_return_val_if_fail(ep != NULL, FALSE);
864 dom_tab = create_domain_tab();
865 g_return_val_if_fail(dom_tab, FALSE);
867 /* scan start of address */
868 for (bp_ = scanpos - 1;
869 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
872 /* TODO: should start with an alnum? */
874 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
877 if (bp_ != scanpos) {
878 /* scan end of address */
879 for (ep_ = scanpos + 1;
880 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
882 prelast_dot = last_dot;
884 if (*(last_dot + 1) == '.') {
885 if (prelast_dot == NULL)
887 last_dot = prelast_dot;
892 /* TODO: really should terminate with an alnum? */
893 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
898 if (last_dot == NULL)
901 last_dot = prelast_dot;
902 if (last_dot == NULL || (scanpos + 1 >= last_dot))
906 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
907 if (*last_tld_char == '?')
910 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
917 if (!result) return FALSE;
919 if (*ep_ && *(bp_ - 1) == '"' && *(ep_) == '"'
920 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
921 && IS_RFC822_CHAR(*(ep_ + 3))) {
922 /* this informative part with an @ in it is
923 * followed by the email address */
926 /* go to matching '>' (or next non-rfc822 char, like \n) */
927 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
930 /* include the bracket */
931 if (*ep_ == '>') ep_++;
933 /* include the leading quote */
941 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
942 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
945 /* see if this is <bracketed>; in this case we also scan for the informative part. */
946 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
949 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
950 #define IN_STACK() (ptr > closure_stack)
951 /* has underrun check */
952 #define POP_STACK() if(IN_STACK()) --ptr
953 /* has overrun check */
954 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
955 /* has underrun check */
956 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
960 /* scan for the informative part. */
961 for (bp_ -= 2; bp_ >= start; bp_--) {
962 /* if closure on the stack keep scanning */
963 if (PEEK_STACK() == *bp_) {
967 if (*bp_ == '\'' || *bp_ == '"') {
972 /* if nothing in the closure stack, do the special conditions
973 * the following if..else expression simply checks whether
974 * a token is acceptable. if not acceptable, the clause
975 * should terminate the loop with a 'break' */
978 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
979 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
980 /* hyphens are allowed, but only in
982 } else if (!ispunct(*bp_)) {
983 /* but anything not being a punctiation
986 break; /* anything else is rejected */
999 /* scan forward (should start with an alnum) */
1000 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
1010 #undef IS_ASCII_ALNUM
1011 #undef IS_RFC822_CHAR
1013 static gchar *make_email_string(const gchar *bp, const gchar *ep)
1015 /* returns a mailto: URI; mailto: is also used to detect the
1016 * uri type later on in the button_pressed signal handler */
1020 tmp = g_strndup(bp, ep - bp);
1021 result = g_strconcat("mailto:", tmp, NULL);
1027 static gchar *make_http_string(const gchar *bp, const gchar *ep)
1029 /* returns an http: URI; */
1033 tmp = g_strndup(bp, ep - bp);
1034 result = g_strconcat("http://", tmp, NULL);
1040 #define ADD_TXT_POS(bp_, ep_, pti_) \
1041 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1042 last = last->next; \
1043 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1044 last->next = NULL; \
1046 g_warning("alloc error scanning URIs\n"); \
1047 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1053 /* textview_make_clickable_parts() - colorizes clickable parts */
1054 static void textview_make_clickable_parts(TextView *textview,
1055 const gchar *fg_tag,
1056 const gchar *uri_tag,
1057 const gchar *linebuf)
1059 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1060 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1063 /* parse table - in order of priority */
1065 const gchar *needle; /* token */
1067 /* token search function */
1068 gchar *(*search) (const gchar *haystack,
1069 const gchar *needle);
1070 /* part parsing function */
1071 gboolean (*parse) (const gchar *start,
1072 const gchar *scanpos,
1075 /* part to URI function */
1076 gchar *(*build_uri) (const gchar *bp,
1080 static struct table parser[] = {
1081 {"http://", strcasestr, get_uri_part, make_uri_string},
1082 {"https://", strcasestr, get_uri_part, make_uri_string},
1083 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1084 {"www.", strcasestr, get_uri_part, make_http_string},
1085 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1086 {"@", strcasestr, get_email_part, make_email_string}
1088 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1091 const gchar *walk, *bp, *ep;
1094 const gchar *bp, *ep; /* text position */
1095 gint pti; /* index in parse table */
1096 struct txtpos *next; /* next */
1097 } head = {NULL, NULL, 0, NULL}, *last = &head;
1099 gtk_text_buffer_get_end_iter(buffer, &iter);
1101 /* parse for clickable parts, and build a list of begin and end positions */
1102 for (walk = linebuf, n = 0;;) {
1103 gint last_index = PARSE_ELEMS;
1104 gchar *scanpos = NULL;
1106 /* FIXME: this looks phony. scanning for anything in the parse table */
1107 for (n = 0; n < PARSE_ELEMS; n++) {
1110 tmp = parser[n].search(walk, parser[n].needle);
1112 if (scanpos == NULL || tmp < scanpos) {
1120 /* check if URI can be parsed */
1121 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1122 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1123 ADD_TXT_POS(bp, ep, last_index);
1127 strlen(parser[last_index].needle);
1132 /* colorize this line */
1134 const gchar *normal_text = linebuf;
1137 for (last = head.next; last != NULL;
1138 normal_text = last->ep, last = last->next) {
1140 uri = g_new(RemoteURI, 1);
1141 if (last->bp - normal_text > 0)
1142 gtk_text_buffer_insert_with_tags_by_name
1145 last->bp - normal_text,
1147 uri->uri = parser[last->pti].build_uri(last->bp,
1149 uri->start = gtk_text_iter_get_offset(&iter);
1150 gtk_text_buffer_insert_with_tags_by_name
1151 (buffer, &iter, last->bp, last->ep - last->bp,
1152 uri_tag, fg_tag, NULL);
1153 uri->end = gtk_text_iter_get_offset(&iter);
1154 textview->uri_list =
1155 g_slist_append(textview->uri_list, uri);
1159 gtk_text_buffer_insert_with_tags_by_name
1160 (buffer, &iter, normal_text, -1, fg_tag, NULL);
1162 gtk_text_buffer_insert_with_tags_by_name
1163 (buffer, &iter, linebuf, -1, fg_tag, NULL);
1169 static void textview_write_line(TextView *textview, const gchar *str,
1170 CodeConverter *conv)
1173 GtkTextBuffer *buffer;
1175 gchar buf[BUFFSIZE];
1177 gint quotelevel = -1;
1178 gchar quote_tag_str[10];
1180 text = GTK_TEXT_VIEW(textview->text);
1181 buffer = gtk_text_view_get_buffer(text);
1182 gtk_text_buffer_get_end_iter(buffer, &iter);
1185 strncpy2(buf, str, sizeof(buf));
1186 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1187 conv_utf8todisp(buf, sizeof(buf), str);
1190 //if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1193 /* change color of quotation
1194 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1195 Up to 3 levels of quotations are detected, and each
1196 level is colored using a different color. */
1197 if (prefs_common.enable_color
1198 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1199 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1201 /* set up the correct foreground color */
1202 if (quotelevel > 2) {
1203 /* recycle colors */
1204 if (prefs_common.recycle_quote_colors)
1211 if (quotelevel == -1)
1214 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1215 "quote%d", quotelevel);
1216 fg_color = quote_tag_str;
1219 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1220 fg_color = "signature";
1221 textview->is_in_signature = TRUE;
1224 if (prefs_common.enable_color)
1225 textview_make_clickable_parts(textview, fg_color, "link", buf);
1227 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1230 void textview_write_link(TextView *textview, const gchar *str,
1231 const gchar *uri, CodeConverter *conv)
1233 GdkColor *link_color = NULL;
1235 GtkTextBuffer *buffer;
1237 gchar buf[BUFFSIZE];
1244 text = GTK_TEXT_VIEW(textview->text);
1245 buffer = gtk_text_view_get_buffer(text);
1246 gtk_text_buffer_get_end_iter(buffer, &iter);
1249 strncpy2(buf, str, sizeof(buf));
1250 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1251 conv_utf8todisp(buf, sizeof(buf), str);
1255 gtk_text_buffer_get_end_iter(buffer, &iter);
1257 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1258 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1260 if (prefs_common.enable_color) {
1261 link_color = &uri_color;
1263 r_uri = g_new(RemoteURI, 1);
1264 r_uri->uri = g_strdup(uri);
1265 r_uri->start = gtk_text_iter_get_offset(&iter);
1266 gtk_text_buffer_insert_with_tags_by_name
1267 (buffer, &iter, bufp, -1, "link", NULL);
1268 r_uri->end = gtk_text_iter_get_offset(&iter);
1269 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1272 void textview_clear(TextView *textview)
1274 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1275 GtkTextBuffer *buffer;
1277 buffer = gtk_text_view_get_buffer(text);
1278 gtk_text_buffer_set_text(buffer, "", -1);
1280 TEXTVIEW_STATUSBAR_POP(textview);
1281 textview_uri_list_remove_all(textview->uri_list);
1282 textview->uri_list = NULL;
1284 textview->body_pos = 0;
1287 void textview_destroy(TextView *textview)
1289 textview_uri_list_remove_all(textview->uri_list);
1290 textview->uri_list = NULL;
1295 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1297 textview->show_all_headers = all_headers;
1300 void textview_set_font(TextView *textview, const gchar *codeset)
1302 if (prefs_common.textfont) {
1303 PangoFontDescription *font_desc = NULL;
1305 font_desc = pango_font_description_from_string
1306 (prefs_common.textfont);
1308 gtk_widget_modify_font(textview->text, font_desc);
1309 pango_font_description_free(font_desc);
1312 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1313 prefs_common.line_space / 2);
1314 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1315 prefs_common.line_space / 2);
1318 void textview_set_text(TextView *textview, const gchar *text)
1321 GtkTextBuffer *buffer;
1323 g_return_if_fail(textview != NULL);
1324 g_return_if_fail(text != NULL);
1326 textview_clear(textview);
1328 view = GTK_TEXT_VIEW(textview->text);
1329 buffer = gtk_text_view_get_buffer(view);
1330 gtk_text_buffer_set_text(buffer, text, strlen(text));
1346 H_ORGANIZATION = 11,
1349 void textview_set_position(TextView *textview, gint pos)
1351 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1352 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1355 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1356 gtk_text_buffer_place_cursor(buffer, &iter);
1357 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1360 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1362 gchar buf[BUFFSIZE];
1363 GPtrArray *headers, *sorted_headers;
1364 GSList *disphdr_list;
1368 g_return_val_if_fail(fp != NULL, NULL);
1370 if (textview->show_all_headers)
1371 return procheader_get_header_array_asis(fp);
1373 if (!prefs_common.display_header) {
1374 while (fgets(buf, sizeof(buf), fp) != NULL)
1375 if (buf[0] == '\r' || buf[0] == '\n') break;
1379 headers = procheader_get_header_array_asis(fp);
1381 sorted_headers = g_ptr_array_new();
1383 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1384 disphdr_list = disphdr_list->next) {
1385 DisplayHeaderProp *dp =
1386 (DisplayHeaderProp *)disphdr_list->data;
1388 for (i = 0; i < headers->len; i++) {
1389 header = g_ptr_array_index(headers, i);
1391 if (procheader_headername_equal(header->name,
1394 procheader_header_free(header);
1396 g_ptr_array_add(sorted_headers, header);
1398 g_ptr_array_remove_index(headers, i);
1404 if (prefs_common.show_other_header) {
1405 for (i = 0; i < headers->len; i++) {
1406 header = g_ptr_array_index(headers, i);
1407 g_ptr_array_add(sorted_headers, header);
1409 g_ptr_array_free(headers, TRUE);
1411 procheader_header_array_destroy(headers);
1414 return sorted_headers;
1417 static void textview_show_header(TextView *textview, GPtrArray *headers)
1419 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1420 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1425 g_return_if_fail(headers != NULL);
1427 for (i = 0; i < headers->len; i++) {
1428 header = g_ptr_array_index(headers, i);
1429 g_return_if_fail(header->name != NULL);
1431 gtk_text_buffer_get_end_iter (buffer, &iter);
1432 gtk_text_buffer_insert_with_tags_by_name
1433 (buffer, &iter, header->name, -1,
1434 "header_title", "header", NULL);
1435 if (header->name[strlen(header->name) - 1] != ' ')
1436 gtk_text_buffer_insert_with_tags_by_name
1437 (buffer, &iter, " ", 1,
1438 "header_title", "header", NULL);
1440 if (procheader_headername_equal(header->name, "Subject") ||
1441 procheader_headername_equal(header->name, "From") ||
1442 procheader_headername_equal(header->name, "To") ||
1443 procheader_headername_equal(header->name, "Cc"))
1444 unfold_line(header->body);
1448 if (textview->text_is_mb == TRUE)
1449 conv_unreadable_locale(header->body);
1452 if (prefs_common.enable_color &&
1453 (procheader_headername_equal(header->name, "X-Mailer") ||
1454 procheader_headername_equal(header->name,
1456 strstr(header->body, "Sylpheed") != NULL) {
1457 gtk_text_buffer_get_end_iter (buffer, &iter);
1458 gtk_text_buffer_insert_with_tags_by_name
1459 (buffer, &iter, header->body, -1,
1460 "header", "emphasis", NULL);
1461 } else if (prefs_common.enable_color) {
1462 textview_make_clickable_parts(textview, "header", "link",
1465 textview_make_clickable_parts(textview, "header", NULL,
1468 gtk_text_buffer_get_end_iter (buffer, &iter);
1469 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1474 gboolean textview_search_string(TextView *textview, const gchar *str,
1477 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1478 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1480 GtkTextIter iter, start, end, real_end, *pos;
1481 gboolean found = FALSE;
1482 gint insert_offset, selbound_offset;
1484 /* reset selection */
1485 mark = gtk_text_buffer_get_mark(buffer, "insert");
1486 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1487 insert_offset = gtk_text_iter_get_offset(&start);
1488 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1489 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1490 selbound_offset = gtk_text_iter_get_offset(&end);
1492 pos = insert_offset > selbound_offset ? &start : &end;
1493 gtk_text_buffer_place_cursor(buffer, pos);
1496 mark = gtk_text_buffer_get_insert(buffer);
1497 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1499 found = gtk_text_iter_forward_search(&iter, str,
1500 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1501 &start, &end, NULL);
1505 gtk_text_buffer_get_end_iter(buffer, &real_end);
1506 text = gtk_text_buffer_get_text(buffer, &iter,
1509 while (!found && i++ < strlen(text) - 1) {
1510 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1513 i += gtk_text_iter_get_offset(&end);
1516 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1517 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1525 gtk_text_buffer_place_cursor(buffer, &start);
1526 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1528 mark = gtk_text_buffer_get_mark(buffer, "insert");
1529 gtk_text_view_scroll_mark_onscreen(text, mark);
1535 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1538 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1539 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1541 GtkTextIter iter, start, real_start, end, *pos;
1542 gboolean found = FALSE;
1543 gint insert_offset, selbound_offset;
1545 /* reset selection */
1546 mark = gtk_text_buffer_get_mark(buffer, "insert");
1547 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1548 insert_offset = gtk_text_iter_get_offset(&start);
1549 mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1550 gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1551 selbound_offset = gtk_text_iter_get_offset(&end);
1553 pos = insert_offset < selbound_offset ? &start : &end;
1554 gtk_text_buffer_place_cursor(buffer, pos);
1557 mark = gtk_text_buffer_get_insert(buffer);
1558 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1560 found = gtk_text_iter_backward_search(&iter, str,
1561 GTK_TEXT_SEARCH_VISIBLE_ONLY,
1562 &start, &end, NULL);
1566 if (gtk_text_iter_get_offset(&iter) == 0)
1567 gtk_text_buffer_get_end_iter(buffer, &iter);
1569 i = gtk_text_iter_get_offset(&iter) - strlen(str) - 1;
1570 gtk_text_buffer_get_start_iter(buffer, &real_start);
1572 text = gtk_text_buffer_get_text(buffer, &real_start,
1575 while (!found && i-- > 0) {
1576 found = (strncasecmp(text+i, str, strlen(str)) == 0);
1580 gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1581 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1589 gtk_text_buffer_place_cursor(buffer, &end);
1590 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound",
1592 mark = gtk_text_buffer_get_mark(buffer, "insert");
1593 gtk_text_view_scroll_mark_onscreen(text, mark);
1599 void textview_scroll_one_line(TextView *textview, gboolean up)
1601 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1602 GtkAdjustment *vadj = text->vadjustment;
1605 if (prefs_common.enable_smooth_scroll) {
1606 textview_smooth_scroll_one_line(textview, up);
1611 upper = vadj->upper - vadj->page_size;
1612 if (vadj->value < upper) {
1613 vadj->value += vadj->step_increment;
1614 vadj->value = MIN(vadj->value, upper);
1615 g_signal_emit_by_name(G_OBJECT(vadj),
1616 "value_changed", 0);
1619 if (vadj->value > 0.0) {
1620 vadj->value -= vadj->step_increment;
1621 vadj->value = MAX(vadj->value, 0.0);
1622 g_signal_emit_by_name(G_OBJECT(vadj),
1623 "value_changed", 0);
1628 gboolean textview_scroll_page(TextView *textview, gboolean up)
1630 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1631 GtkAdjustment *vadj = text->vadjustment;
1635 if (prefs_common.enable_smooth_scroll)
1636 return textview_smooth_scroll_page(textview, up);
1638 if (prefs_common.scroll_halfpage)
1639 page_incr = vadj->page_increment / 2;
1641 page_incr = vadj->page_increment;
1644 upper = vadj->upper - vadj->page_size;
1645 if (vadj->value < upper) {
1646 vadj->value += page_incr;
1647 vadj->value = MIN(vadj->value, upper);
1648 g_signal_emit_by_name(G_OBJECT(vadj),
1649 "value_changed", 0);
1653 if (vadj->value > 0.0) {
1654 vadj->value -= page_incr;
1655 vadj->value = MAX(vadj->value, 0.0);
1656 g_signal_emit_by_name(G_OBJECT(vadj),
1657 "value_changed", 0);
1665 static void textview_smooth_scroll_do(TextView *textview,
1666 gfloat old_value, gfloat last_value,
1669 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1670 GtkAdjustment *vadj = text->vadjustment;
1675 if (old_value < last_value) {
1676 change_value = last_value - old_value;
1679 change_value = old_value - last_value;
1683 for (i = step; i <= change_value; i += step) {
1684 vadj->value = old_value + (up ? -i : i);
1685 g_signal_emit_by_name(G_OBJECT(vadj),
1686 "value_changed", 0);
1689 vadj->value = last_value;
1690 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1693 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1695 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1696 GtkAdjustment *vadj = text->vadjustment;
1702 upper = vadj->upper - vadj->page_size;
1703 if (vadj->value < upper) {
1704 old_value = vadj->value;
1705 last_value = vadj->value + vadj->step_increment;
1706 last_value = MIN(last_value, upper);
1708 textview_smooth_scroll_do(textview, old_value,
1710 prefs_common.scroll_step);
1713 if (vadj->value > 0.0) {
1714 old_value = vadj->value;
1715 last_value = vadj->value - vadj->step_increment;
1716 last_value = MAX(last_value, 0.0);
1718 textview_smooth_scroll_do(textview, old_value,
1720 prefs_common.scroll_step);
1725 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1727 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1728 GtkAdjustment *vadj = text->vadjustment;
1734 if (prefs_common.scroll_halfpage)
1735 page_incr = vadj->page_increment / 2;
1737 page_incr = vadj->page_increment;
1740 upper = vadj->upper - vadj->page_size;
1741 if (vadj->value < upper) {
1742 old_value = vadj->value;
1743 last_value = vadj->value + page_incr;
1744 last_value = MIN(last_value, upper);
1746 textview_smooth_scroll_do(textview, old_value,
1748 prefs_common.scroll_step);
1752 if (vadj->value > 0.0) {
1753 old_value = vadj->value;
1754 last_value = vadj->value - page_incr;
1755 last_value = MAX(last_value, 0.0);
1757 textview_smooth_scroll_do(textview, old_value,
1759 prefs_common.scroll_step);
1767 #define KEY_PRESS_EVENT_STOP() \
1768 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1771 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1774 SummaryView *summaryview = NULL;
1775 MessageView *messageview = textview->messageview;
1777 if (!event) return FALSE;
1778 if (messageview->mainwin)
1779 summaryview = messageview->mainwin->summaryview;
1781 switch (event->keyval) {
1796 summary_pass_key_press_event(summaryview, event);
1798 textview_scroll_page
1801 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1804 textview_scroll_page(textview, TRUE);
1807 textview_scroll_one_line
1808 (textview, (event->state &
1809 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1813 summary_pass_key_press_event(summaryview, event);
1818 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1819 KEY_PRESS_EVENT_STOP();
1820 mimeview_pass_key_press_event(messageview->mimeview,
1824 /* possible fall through */
1827 event->window != messageview->mainwin->window->window) {
1828 GdkEventKey tmpev = *event;
1830 tmpev.window = messageview->mainwin->window->window;
1831 KEY_PRESS_EVENT_STOP();
1832 gtk_widget_event(messageview->mainwin->window,
1833 (GdkEvent *)&tmpev);
1841 static gboolean textview_motion_notify(GtkWidget *widget,
1842 GdkEventMotion *event,
1845 textview_uri_update(textview, event->x, event->y);
1846 gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1851 static gboolean textview_leave_notify(GtkWidget *widget,
1852 GdkEventCrossing *event,
1855 textview_uri_update(textview, -1, -1);
1860 static gboolean textview_visibility_notify(GtkWidget *widget,
1861 GdkEventVisibility *event,
1867 window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
1868 GTK_TEXT_WINDOW_TEXT);
1870 /* check if occurred for the text window part */
1871 if (window != event->window)
1874 gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1875 textview_uri_update(textview, wx, wy);
1880 static void textview_uri_update(TextView *textview, gint x, gint y)
1882 GtkTextBuffer *buffer;
1883 GtkTextIter start_iter, end_iter;
1884 RemoteURI *uri = NULL;
1886 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1888 if (x != -1 && y != -1) {
1894 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text),
1895 GTK_TEXT_WINDOW_WIDGET,
1897 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1900 tags = gtk_text_iter_get_tags(&iter);
1901 for (cur = tags; cur != NULL; cur = cur->next) {
1902 GtkTextTag *tag = cur->data;
1905 g_object_get(G_OBJECT(tag), "name", &name, NULL);
1906 if (!strcmp(name, "link")
1907 && textview_get_uri_range(textview, &iter, tag,
1908 &start_iter, &end_iter))
1909 uri = textview_get_uri_from_range(textview,
1921 if (uri != textview->uri_hover) {
1924 if (textview->uri_hover)
1925 gtk_text_buffer_remove_tag_by_name(buffer,
1927 &textview->uri_hover_start_iter,
1928 &textview->uri_hover_end_iter);
1930 textview->uri_hover = uri;
1932 textview->uri_hover_start_iter = start_iter;
1933 textview->uri_hover_end_iter = end_iter;
1936 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1937 GTK_TEXT_WINDOW_TEXT);
1938 gdk_window_set_cursor(window, uri ? hand_cursor : text_cursor);
1940 TEXTVIEW_STATUSBAR_POP(textview);
1945 gtk_text_buffer_apply_tag_by_name(buffer,
1950 trimmed_uri = trim_string(uri->uri, 60);
1951 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1952 g_free(trimmed_uri);
1957 static gboolean textview_get_uri_range(TextView *textview,
1960 GtkTextIter *start_iter,
1961 GtkTextIter *end_iter)
1963 GtkTextIter _start_iter, _end_iter;
1966 if (!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
1967 debug_print("Can't find end");
1971 _start_iter = _end_iter;
1972 if (!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
1973 debug_print("Can't find start.");
1977 *start_iter = _start_iter;
1978 *end_iter = _end_iter;
1983 static RemoteURI *textview_get_uri_from_range(TextView *textview,
1986 GtkTextIter *start_iter,
1987 GtkTextIter *end_iter)
1989 gint start_pos, end_pos, cur_pos;
1990 RemoteURI *uri = NULL;
1993 start_pos = gtk_text_iter_get_offset(start_iter);
1994 end_pos = gtk_text_iter_get_offset(end_iter);
1995 cur_pos = gtk_text_iter_get_offset(iter);
1997 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1998 RemoteURI *uri_ = (RemoteURI *)cur->data;
1999 if (start_pos == uri_->start &&
2000 end_pos == uri_->end) {
2003 } else if (start_pos == uri_->start ||
2004 end_pos == uri_->end) {
2005 /* in case of contiguous links, textview_get_uri_range
2006 * returns a broader range (start of 1st link to end
2008 * In that case, correct link is the one covering
2011 if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
2021 static RemoteURI *textview_get_uri(TextView *textview,
2025 GtkTextIter start_iter, end_iter;
2026 RemoteURI *uri = NULL;
2028 if (textview_get_uri_range(textview, iter, tag, &start_iter,
2030 uri = textview_get_uri_from_range(textview, iter, tag,
2031 &start_iter, &end_iter);
2036 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
2037 GdkEvent *event, GtkTextIter *iter,
2040 GdkEventButton *bevent;
2041 RemoteURI *uri = NULL;
2048 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2049 && event->type != GDK_MOTION_NOTIFY)
2052 uri = textview_get_uri(textview, iter, tag);
2056 bevent = (GdkEventButton *) event;
2058 /* doubleclick: open compose / add address / browser */
2059 if ((event->type == GDK_BUTTON_PRESS && bevent->button == 1) ||
2060 bevent->button == 2 || bevent->button == 3) {
2061 if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2062 if (bevent->button == 3) {
2064 G_OBJECT(textview->mail_popup_menu),
2065 "menu_button", uri);
2066 gtk_menu_popup(GTK_MENU(textview->mail_popup_menu),
2067 NULL, NULL, NULL, NULL,
2068 bevent->button, bevent->time);
2070 PrefsAccount *account = NULL;
2072 if (textview->messageview && textview->messageview->msginfo &&
2073 textview->messageview->msginfo->folder) {
2074 FolderItem *folder_item;
2076 folder_item = textview->messageview->msginfo->folder;
2077 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2078 account = account_find_from_id(folder_item->prefs->default_account);
2080 compose_new(account, uri->uri + 7, NULL);
2084 if (bevent->button == 1 &&
2085 textview_uri_security_check(textview, uri) == TRUE)
2087 prefs_common.uri_cmd);
2088 else if (bevent->button == 3) {
2090 G_OBJECT(textview->link_popup_menu),
2091 "menu_button", uri);
2092 gtk_menu_popup(GTK_MENU(textview->link_popup_menu),
2093 NULL, NULL, NULL, NULL,
2094 bevent->button, bevent->time);
2104 *\brief Check to see if a web URL has been disguised as a different
2105 * URL (possible with HTML email).
2107 *\param uri The uri to check
2109 *\param textview The TextView the URL is contained in
2111 *\return gboolean TRUE if the URL is ok, or if the user chose to open
2112 * it anyway, otherwise FALSE
2114 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2117 gboolean retval = TRUE;
2118 GtkTextBuffer *buffer;
2119 GtkTextIter start, end;
2121 if (is_uri_string(uri->uri) == FALSE)
2124 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2126 gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2127 gtk_text_buffer_get_iter_at_offset(buffer, &end, uri->end);
2129 visible_str = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2131 if (visible_str == NULL)
2134 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2136 gchar *visible_uri_path;
2138 uri_path = get_uri_path(uri->uri);
2139 visible_uri_path = get_uri_path(visible_str);
2140 if (strcmp(uri_path, visible_uri_path) != 0)
2144 if (retval == FALSE) {
2148 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2149 "the apparent URL (%s).\n"
2151 uri->uri, visible_str);
2152 aval = alertpanel_with_type(_("Warning"), msg,
2153 GTK_STOCK_YES, GTK_STOCK_NO,
2154 NULL, NULL, ALERT_WARNING);
2156 if (aval == G_ALERTDEFAULT)
2160 g_free(visible_str);
2165 static void textview_uri_list_remove_all(GSList *uri_list)
2169 for (cur = uri_list; cur != NULL; cur = cur->next) {
2171 g_free(((RemoteURI *)cur->data)->uri);
2176 g_slist_free(uri_list);
2179 static void open_uri_cb (TextView *textview, guint action, void *data)
2181 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2186 if (textview_uri_security_check(textview, uri) == TRUE)
2188 prefs_common.uri_cmd);
2189 g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2193 static void copy_uri_cb (TextView *textview, guint action, void *data)
2195 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2200 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), uri->uri, -1);
2201 g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2205 static void add_uri_to_addrbook_cb (TextView *textview, guint action, void *data)
2207 gchar *fromname, *fromaddress;
2208 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2214 fromaddress = g_strdup(uri->uri + 7);
2215 /* Hiroyuki: please put this function in utils.c! */
2216 fromname = procheader_get_fromname(fromaddress);
2217 extract_address(fromaddress);
2218 g_message("adding from textview %s <%s>", fromname, fromaddress);
2219 /* Add to address book - Match */
2220 addressbook_add_contact( fromname, fromaddress, NULL );
2222 g_free(fromaddress);
2226 static void mail_to_uri_cb (TextView *textview, guint action, void *data)
2228 PrefsAccount *account = NULL;
2229 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2234 if (textview->messageview && textview->messageview->msginfo &&
2235 textview->messageview->msginfo->folder) {
2236 FolderItem *folder_item;
2238 folder_item = textview->messageview->msginfo->folder;
2239 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2240 account = account_find_from_id(folder_item->prefs->default_account);
2242 compose_new(account, uri->uri + 7, NULL);