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.
27 #include <glib/gi18n.h>
29 #include <gdk/gdkkeysyms.h>
30 #include <gtk/gtkvbox.h>
31 #include <gtk/gtkscrolledwindow.h>
32 #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,
201 static void copy_mail_to_uri_cb (TextView *textview,
205 static GtkItemFactoryEntry textview_link_popup_entries[] =
207 {N_("/_Open link"), NULL, open_uri_cb, 0, NULL},
208 {N_("/_Copy link location"), NULL, copy_uri_cb, 0, NULL},
211 static GtkItemFactoryEntry textview_mail_popup_entries[] =
213 {N_("/_Add to addressbook"), NULL, add_uri_to_addrbook_cb, 0, NULL},
214 {N_("/_Email"), NULL, mail_to_uri_cb, 0, NULL},
215 {N_("/_Copy"), NULL, copy_mail_to_uri_cb, 0, NULL},
219 TextView *textview_create(void)
223 GtkWidget *scrolledwin;
225 GtkTextBuffer *buffer;
226 GtkClipboard *clipboard;
227 GtkItemFactory *link_popupfactory, *mail_popupfactory;
228 GtkWidget *link_popupmenu, *mail_popupmenu;
231 debug_print("Creating text view...\n");
232 textview = g_new0(TextView, 1);
234 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
235 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
236 GTK_POLICY_AUTOMATIC,
237 GTK_POLICY_AUTOMATIC);
238 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
240 gtk_widget_set_size_request
241 (scrolledwin, prefs_common.mainview_width, -1);
243 /* create GtkSText widgets for single-byte and multi-byte character */
244 text = gtk_text_view_new();
245 gtk_widget_add_events(text, GDK_LEAVE_NOTIFY_MASK);
246 gtk_widget_show(text);
247 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
248 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
249 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
250 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
251 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
253 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
254 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
255 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
257 gtk_widget_ensure_style(text);
259 gtk_widget_ref(scrolledwin);
261 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
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 "foreground-gdk", &uri_color,
361 "underline", PANGO_UNDERLINE_SINGLE,
364 g_signal_connect(G_OBJECT(tag), "event",
365 G_CALLBACK(textview_uri_button_pressed), textview);
368 void textview_init(TextView *textview)
371 hand_cursor = gdk_cursor_new(GDK_HAND2);
373 text_cursor = gdk_cursor_new(GDK_XTERM);
375 textview_update_message_colors();
376 textview_set_all_headers(textview, FALSE);
377 textview_set_font(textview, NULL);
378 textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
381 void textview_update_message_colors(void)
383 GdkColor black = {0, 0, 0, 0};
385 if (prefs_common.enable_color) {
386 /* grab the quote colors, converting from an int to a GdkColor */
387 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
389 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
391 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
393 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
395 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
398 quote_colors[0] = quote_colors[1] = quote_colors[2] =
399 uri_color = emphasis_color = signature_color = black;
403 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
408 if ((fp = fopen(file, "rb")) == NULL) {
409 FILE_OP_ERROR(file, "fopen");
413 textview_clear(textview);
415 textview_add_parts(textview, mimeinfo);
419 textview_set_position(textview, 0);
422 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
426 g_return_if_fail(mimeinfo != NULL);
427 g_return_if_fail(fp != NULL);
429 if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
430 ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822"))) {
431 textview_clear(textview);
432 textview_add_parts(textview, mimeinfo);
436 if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
439 textview_clear(textview);
441 if (mimeinfo->type == MIMETYPE_MULTIPART)
442 textview_add_parts(textview, mimeinfo);
444 textview_write_body(textview, mimeinfo);
448 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
451 GtkTextBuffer *buffer;
454 GPtrArray *headers = NULL;
459 g_return_if_fail(mimeinfo != NULL);
460 text = GTK_TEXT_VIEW(textview->text);
461 buffer = gtk_text_view_get_buffer(text);
462 charcount = gtk_text_buffer_get_char_count(buffer);
463 gtk_text_buffer_get_end_iter(buffer, &iter);
465 if (mimeinfo->type == MIMETYPE_MULTIPART) return;
467 if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
470 fp = fopen(mimeinfo->data.filename, "rb");
471 fseek(fp, mimeinfo->offset, SEEK_SET);
472 headers = textview_scan_header(textview, fp);
475 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
476 textview_show_header(textview, headers);
477 procheader_header_array_destroy(headers);
483 name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
484 content_type = procmime_get_content_type_str(mimeinfo->type,
487 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
489 g_snprintf(buf, sizeof(buf), "\n[%s %s (%d bytes)]\n",
490 name, content_type, mimeinfo->length);
492 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
493 content_type, mimeinfo->length);
495 g_free(content_type);
497 if (mimeinfo->type != MIMETYPE_TEXT) {
498 gtk_text_buffer_insert(buffer, &iter, buf, -1);
499 } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
500 if (prefs_common.display_header && (charcount > 0))
501 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
503 textview_write_body(textview, mimeinfo);
507 static void recursive_add_parts(TextView *textview, GNode *node)
512 mimeinfo = (MimeInfo *) node->data;
514 textview_add_part(textview, mimeinfo);
516 if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
517 (mimeinfo->type != MIMETYPE_MESSAGE))
520 if (g_ascii_strcasecmp(mimeinfo->subtype, "alternative") == 0) {
521 GNode * prefered_body;
529 prefered_body = NULL;
532 for (iter = g_node_first_child(node) ; iter != NULL ;
533 iter = g_node_next_sibling(iter)) {
538 submime = (MimeInfo *) iter->data;
539 if (submime->type == MIMETYPE_TEXT)
542 if (submime->subtype != NULL) {
543 if (g_ascii_strcasecmp(submime->subtype, "plain") == 0)
547 if (score > prefered_score) {
548 prefered_score = score;
549 prefered_body = iter;
553 if (prefered_body != NULL) {
554 recursive_add_parts(textview, prefered_body);
558 for (iter = g_node_first_child(node) ; iter != NULL ;
559 iter = g_node_next_sibling(iter)) {
560 recursive_add_parts(textview, iter);
565 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
567 g_return_if_fail(mimeinfo != NULL);
569 recursive_add_parts(textview, mimeinfo->node);
572 #define TEXT_INSERT(str) \
573 gtk_text_buffer_insert(buffer, &iter, str, -1)
575 void textview_show_error(TextView *textview)
578 GtkTextBuffer *buffer;
581 textview_set_font(textview, NULL);
582 textview_clear(textview);
584 text = GTK_TEXT_VIEW(textview->text);
585 buffer = gtk_text_view_get_buffer(text);
586 gtk_text_buffer_get_start_iter(buffer, &iter);
588 TEXT_INSERT(_("This message can't be displayed.\n"));
592 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
595 GtkTextBuffer *buffer;
598 if (!partinfo) return;
600 textview_set_font(textview, NULL);
601 textview_clear(textview);
603 text = GTK_TEXT_VIEW(textview->text);
604 buffer = gtk_text_view_get_buffer(text);
605 gtk_text_buffer_get_start_iter(buffer, &iter);
607 TEXT_INSERT(_("The following can be performed on this part by "));
608 TEXT_INSERT(_("right-clicking the icon or list item:\n"));
610 TEXT_INSERT(_(" To save select 'Save as...' (Shortcut key: 'y')\n"));
611 TEXT_INSERT(_(" To display as text select 'Display as text' "));
612 TEXT_INSERT(_("(Shortcut key: 't')\n"));
613 TEXT_INSERT(_(" To open with an external program select 'Open' "));
614 TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
615 TEXT_INSERT(_(" (alternately double-click, or click the middle "));
616 TEXT_INSERT(_("mouse button),\n"));
617 TEXT_INSERT(_(" or 'Open with...' (Shortcut key: 'o')\n"));
623 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
628 const gchar *charset;
630 if (textview->messageview->forced_charset)
631 charset = textview->messageview->forced_charset;
633 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
635 textview_set_font(textview, charset);
637 conv = conv_code_converter_new(charset);
639 procmime_force_encoding(textview->messageview->forced_encoding);
641 textview->is_in_signature = FALSE;
643 procmime_decode_content(mimeinfo);
645 if (!g_ascii_strcasecmp(mimeinfo->subtype, "html")) {
648 filename = procmime_get_tmp_file_name(mimeinfo);
649 if (procmime_get_part(filename, mimeinfo) == 0) {
650 tmpfp = fopen(filename, "rb");
651 textview_show_html(textview, tmpfp, conv);
656 } else if (!g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
659 filename = procmime_get_tmp_file_name(mimeinfo);
660 if (procmime_get_part(filename, mimeinfo) == 0) {
661 tmpfp = fopen(filename, "rb");
662 textview_show_ertf(textview, tmpfp, conv);
668 tmpfp = fopen(mimeinfo->data.filename, "rb");
669 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
670 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
671 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) &&
672 (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
673 textview_write_line(textview, buf, conv);
677 conv_code_converter_destroy(conv);
678 procmime_force_encoding(0);
681 static void textview_show_html(TextView *textview, FILE *fp,
687 parser = html_parser_new(fp, conv);
688 g_return_if_fail(parser != NULL);
690 while ((str = html_parse(parser)) != NULL) {
691 if (parser->state == HTML_HREF) {
692 /* first time : get and copy the URL */
693 if (parser->href == NULL) {
694 /* ALF - the sylpheed html parser returns an empty string,
695 * if still inside an <a>, but already parsed past HREF */
696 str = strtok(str, " ");
698 parser->href = g_strdup(str);
699 /* the URL may (or not) be followed by the
701 str = strtok(NULL, "");
705 textview_write_link(textview, str, parser->href, NULL);
707 textview_write_line(textview, str, NULL);
709 html_parser_destroy(parser);
712 static void textview_show_ertf(TextView *textview, FILE *fp,
718 parser = ertf_parser_new(fp, conv);
719 g_return_if_fail(parser != NULL);
721 while ((str = ertf_parse(parser)) != NULL) {
722 textview_write_line(textview, str, NULL);
725 ertf_parser_destroy(parser);
728 /* get_uri_part() - retrieves a URI starting from scanpos.
729 Returns TRUE if succesful */
730 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
731 const gchar **bp, const gchar **ep)
735 g_return_val_if_fail(start != NULL, FALSE);
736 g_return_val_if_fail(scanpos != NULL, FALSE);
737 g_return_val_if_fail(bp != NULL, FALSE);
738 g_return_val_if_fail(ep != NULL, FALSE);
742 /* find end point of URI */
743 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
744 if (!isgraph(*(const guchar *)ep_) ||
745 !IS_ASCII(*(const guchar *)ep_) ||
746 strchr("()<>\"", *ep_))
750 /* no punctuation at end of string */
752 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
753 * should pass some URI type to this function and decide on that whether
754 * to perform punctuation stripping */
756 #define IS_REAL_PUNCT(ch) (ispunct(ch) && ((ch) != '/'))
758 for (; ep_ - 1 > scanpos + 1 &&
759 IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
770 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
772 return g_strndup(bp, ep - bp);
775 /* valid mail address characters */
776 #define IS_RFC822_CHAR(ch) \
781 !strchr("(),;<>\"", (ch)))
783 /* alphabet and number within 7bit ASCII */
784 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && isalnum(ch))
785 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
787 static GHashTable *create_domain_tab(void)
789 static const gchar *toplvl_domains [] = {
791 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
792 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
793 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
794 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
795 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
796 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
797 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
798 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
799 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
800 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
801 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
802 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
803 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
804 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
805 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
806 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
807 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
808 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
809 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
810 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
811 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
812 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
813 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
814 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
815 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
816 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
819 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
821 g_return_val_if_fail(htab, NULL);
822 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
823 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
827 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
829 const gint MAX_LVL_DOM_NAME_LEN = 6;
830 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
831 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
834 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
837 for (p = buf; p < m && first < last; *p++ = *first++)
841 return g_hash_table_lookup(tab, buf) != NULL;
844 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
845 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
846 const gchar **bp, const gchar **ep)
848 /* more complex than the uri part because we need to scan back and forward starting from
849 * the scan position. */
850 gboolean result = FALSE;
851 const gchar *bp_ = NULL;
852 const gchar *ep_ = NULL;
853 static GHashTable *dom_tab;
854 const gchar *last_dot = NULL;
855 const gchar *prelast_dot = NULL;
856 const gchar *last_tld_char = NULL;
858 /* the informative part of the email address (describing the name
859 * of the email address owner) may contain quoted parts. the
860 * closure stack stores the last encountered quotes. */
861 gchar closure_stack[128];
862 gchar *ptr = closure_stack;
864 g_return_val_if_fail(start != NULL, FALSE);
865 g_return_val_if_fail(scanpos != NULL, FALSE);
866 g_return_val_if_fail(bp != NULL, FALSE);
867 g_return_val_if_fail(ep != NULL, FALSE);
870 dom_tab = create_domain_tab();
871 g_return_val_if_fail(dom_tab, FALSE);
873 /* scan start of address */
874 for (bp_ = scanpos - 1;
875 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
878 /* TODO: should start with an alnum? */
880 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
883 if (bp_ != scanpos) {
884 /* scan end of address */
885 for (ep_ = scanpos + 1;
886 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
888 prelast_dot = last_dot;
890 if (*(last_dot + 1) == '.') {
891 if (prelast_dot == NULL)
893 last_dot = prelast_dot;
898 /* TODO: really should terminate with an alnum? */
899 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
904 if (last_dot == NULL)
907 last_dot = prelast_dot;
908 if (last_dot == NULL || (scanpos + 1 >= last_dot))
912 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
913 if (*last_tld_char == '?')
916 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
923 if (!result) return FALSE;
925 if (*ep_ && *(bp_ - 1) == '"' && *(ep_) == '"'
926 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
927 && IS_RFC822_CHAR(*(ep_ + 3))) {
928 /* this informative part with an @ in it is
929 * followed by the email address */
932 /* go to matching '>' (or next non-rfc822 char, like \n) */
933 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
936 /* include the bracket */
937 if (*ep_ == '>') ep_++;
939 /* include the leading quote */
947 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
948 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
951 /* see if this is <bracketed>; in this case we also scan for the informative part. */
952 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
955 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
956 #define IN_STACK() (ptr > closure_stack)
957 /* has underrun check */
958 #define POP_STACK() if(IN_STACK()) --ptr
959 /* has overrun check */
960 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
961 /* has underrun check */
962 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
966 /* scan for the informative part. */
967 for (bp_ -= 2; bp_ >= start; bp_--) {
968 /* if closure on the stack keep scanning */
969 if (PEEK_STACK() == *bp_) {
973 if (*bp_ == '\'' || *bp_ == '"') {
978 /* if nothing in the closure stack, do the special conditions
979 * the following if..else expression simply checks whether
980 * a token is acceptable. if not acceptable, the clause
981 * should terminate the loop with a 'break' */
984 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
985 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
986 /* hyphens are allowed, but only in
988 } else if (!ispunct(*bp_)) {
989 /* but anything not being a punctiation
992 break; /* anything else is rejected */
1005 /* scan forward (should start with an alnum) */
1006 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
1016 #undef IS_ASCII_ALNUM
1017 #undef IS_RFC822_CHAR
1019 static gchar *make_email_string(const gchar *bp, const gchar *ep)
1021 /* returns a mailto: URI; mailto: is also used to detect the
1022 * uri type later on in the button_pressed signal handler */
1026 tmp = g_strndup(bp, ep - bp);
1027 result = g_strconcat("mailto:", tmp, NULL);
1033 static gchar *make_http_string(const gchar *bp, const gchar *ep)
1035 /* returns an http: URI; */
1039 tmp = g_strndup(bp, ep - bp);
1040 result = g_strconcat("http://", tmp, NULL);
1046 #define ADD_TXT_POS(bp_, ep_, pti_) \
1047 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1048 last = last->next; \
1049 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1050 last->next = NULL; \
1052 g_warning("alloc error scanning URIs\n"); \
1053 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1059 /* textview_make_clickable_parts() - colorizes clickable parts */
1060 static void textview_make_clickable_parts(TextView *textview,
1061 const gchar *fg_tag,
1062 const gchar *uri_tag,
1063 const gchar *linebuf)
1065 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1066 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1069 /* parse table - in order of priority */
1071 const gchar *needle; /* token */
1073 /* token search function */
1074 gchar *(*search) (const gchar *haystack,
1075 const gchar *needle);
1076 /* part parsing function */
1077 gboolean (*parse) (const gchar *start,
1078 const gchar *scanpos,
1081 /* part to URI function */
1082 gchar *(*build_uri) (const gchar *bp,
1086 static struct table parser[] = {
1087 {"http://", strcasestr, get_uri_part, make_uri_string},
1088 {"https://", strcasestr, get_uri_part, make_uri_string},
1089 {"ftp://", strcasestr, get_uri_part, make_uri_string},
1090 {"www.", strcasestr, get_uri_part, make_http_string},
1091 {"mailto:", strcasestr, get_uri_part, make_uri_string},
1092 {"@", strcasestr, get_email_part, make_email_string}
1094 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1097 const gchar *walk, *bp, *ep;
1100 const gchar *bp, *ep; /* text position */
1101 gint pti; /* index in parse table */
1102 struct txtpos *next; /* next */
1103 } head = {NULL, NULL, 0, NULL}, *last = &head;
1105 gtk_text_buffer_get_end_iter(buffer, &iter);
1107 /* parse for clickable parts, and build a list of begin and end positions */
1108 for (walk = linebuf, n = 0;;) {
1109 gint last_index = PARSE_ELEMS;
1110 gchar *scanpos = NULL;
1112 /* FIXME: this looks phony. scanning for anything in the parse table */
1113 for (n = 0; n < PARSE_ELEMS; n++) {
1116 tmp = parser[n].search(walk, parser[n].needle);
1118 if (scanpos == NULL || tmp < scanpos) {
1126 /* check if URI can be parsed */
1127 if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1128 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1129 ADD_TXT_POS(bp, ep, last_index);
1133 strlen(parser[last_index].needle);
1138 /* colorize this line */
1140 const gchar *normal_text = linebuf;
1143 for (last = head.next; last != NULL;
1144 normal_text = last->ep, last = last->next) {
1146 uri = g_new(RemoteURI, 1);
1147 if (last->bp - normal_text > 0)
1148 gtk_text_buffer_insert_with_tags_by_name
1151 last->bp - normal_text,
1153 uri->uri = parser[last->pti].build_uri(last->bp,
1155 uri->start = gtk_text_iter_get_offset(&iter);
1156 gtk_text_buffer_insert_with_tags_by_name
1157 (buffer, &iter, last->bp, last->ep - last->bp,
1158 uri_tag, fg_tag, NULL);
1159 uri->end = gtk_text_iter_get_offset(&iter);
1160 textview->uri_list =
1161 g_slist_append(textview->uri_list, uri);
1165 gtk_text_buffer_insert_with_tags_by_name
1166 (buffer, &iter, normal_text, -1, fg_tag, NULL);
1168 gtk_text_buffer_insert_with_tags_by_name
1169 (buffer, &iter, linebuf, -1, fg_tag, NULL);
1175 static void textview_write_line(TextView *textview, const gchar *str,
1176 CodeConverter *conv)
1179 GtkTextBuffer *buffer;
1181 gchar buf[BUFFSIZE];
1183 gint quotelevel = -1;
1184 gchar quote_tag_str[10];
1186 text = GTK_TEXT_VIEW(textview->text);
1187 buffer = gtk_text_view_get_buffer(text);
1188 gtk_text_buffer_get_end_iter(buffer, &iter);
1191 strncpy2(buf, str, sizeof(buf));
1192 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1193 conv_utf8todisp(buf, sizeof(buf), str);
1196 //if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1199 /* change color of quotation
1200 >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1201 Up to 3 levels of quotations are detected, and each
1202 level is colored using a different color. */
1203 if (prefs_common.enable_color
1204 && line_has_quote_char(buf, prefs_common.quote_chars)) {
1205 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1207 /* set up the correct foreground color */
1208 if (quotelevel > 2) {
1209 /* recycle colors */
1210 if (prefs_common.recycle_quote_colors)
1217 if (quotelevel == -1)
1220 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1221 "quote%d", quotelevel);
1222 fg_color = quote_tag_str;
1225 if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1226 fg_color = "signature";
1227 textview->is_in_signature = TRUE;
1230 if (prefs_common.enable_color)
1231 textview_make_clickable_parts(textview, fg_color, "link", buf);
1233 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1236 void textview_write_link(TextView *textview, const gchar *str,
1237 const gchar *uri, CodeConverter *conv)
1239 GdkColor *link_color = NULL;
1241 GtkTextBuffer *buffer;
1243 gchar buf[BUFFSIZE];
1250 text = GTK_TEXT_VIEW(textview->text);
1251 buffer = gtk_text_view_get_buffer(text);
1252 gtk_text_buffer_get_end_iter(buffer, &iter);
1255 strncpy2(buf, str, sizeof(buf));
1256 else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1257 conv_utf8todisp(buf, sizeof(buf), str);
1261 gtk_text_buffer_get_end_iter(buffer, &iter);
1263 for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1264 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1266 if (prefs_common.enable_color) {
1267 link_color = &uri_color;
1269 r_uri = g_new(RemoteURI, 1);
1270 r_uri->uri = g_strdup(uri);
1271 r_uri->start = gtk_text_iter_get_offset(&iter);
1272 gtk_text_buffer_insert_with_tags_by_name
1273 (buffer, &iter, bufp, -1, "link", NULL);
1274 r_uri->end = gtk_text_iter_get_offset(&iter);
1275 textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1278 void textview_clear(TextView *textview)
1280 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1281 GtkTextBuffer *buffer;
1283 buffer = gtk_text_view_get_buffer(text);
1284 gtk_text_buffer_set_text(buffer, "", -1);
1286 TEXTVIEW_STATUSBAR_POP(textview);
1287 textview_uri_list_remove_all(textview->uri_list);
1288 textview->uri_list = NULL;
1290 textview->body_pos = 0;
1293 void textview_destroy(TextView *textview)
1295 textview_uri_list_remove_all(textview->uri_list);
1296 textview->uri_list = NULL;
1301 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1303 textview->show_all_headers = all_headers;
1306 void textview_set_font(TextView *textview, const gchar *codeset)
1308 if (prefs_common.textfont) {
1309 PangoFontDescription *font_desc = NULL;
1311 font_desc = pango_font_description_from_string
1312 (prefs_common.textfont);
1314 gtk_widget_modify_font(textview->text, font_desc);
1315 pango_font_description_free(font_desc);
1318 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1319 prefs_common.line_space / 2);
1320 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1321 prefs_common.line_space / 2);
1324 void textview_set_text(TextView *textview, const gchar *text)
1327 GtkTextBuffer *buffer;
1329 g_return_if_fail(textview != NULL);
1330 g_return_if_fail(text != NULL);
1332 textview_clear(textview);
1334 view = GTK_TEXT_VIEW(textview->text);
1335 buffer = gtk_text_view_get_buffer(view);
1336 gtk_text_buffer_set_text(buffer, text, strlen(text));
1352 H_ORGANIZATION = 11,
1355 void textview_set_position(TextView *textview, gint pos)
1357 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1358 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1361 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1362 gtk_text_buffer_place_cursor(buffer, &iter);
1363 gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1366 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1368 gchar buf[BUFFSIZE];
1369 GPtrArray *headers, *sorted_headers;
1370 GSList *disphdr_list;
1374 g_return_val_if_fail(fp != NULL, NULL);
1376 if (textview->show_all_headers)
1377 return procheader_get_header_array_asis(fp);
1379 if (!prefs_common.display_header) {
1380 while (fgets(buf, sizeof(buf), fp) != NULL)
1381 if (buf[0] == '\r' || buf[0] == '\n') break;
1385 headers = procheader_get_header_array_asis(fp);
1387 sorted_headers = g_ptr_array_new();
1389 for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1390 disphdr_list = disphdr_list->next) {
1391 DisplayHeaderProp *dp =
1392 (DisplayHeaderProp *)disphdr_list->data;
1394 for (i = 0; i < headers->len; i++) {
1395 header = g_ptr_array_index(headers, i);
1397 if (procheader_headername_equal(header->name,
1400 procheader_header_free(header);
1402 g_ptr_array_add(sorted_headers, header);
1404 g_ptr_array_remove_index(headers, i);
1410 if (prefs_common.show_other_header) {
1411 for (i = 0; i < headers->len; i++) {
1412 header = g_ptr_array_index(headers, i);
1413 g_ptr_array_add(sorted_headers, header);
1415 g_ptr_array_free(headers, TRUE);
1417 procheader_header_array_destroy(headers);
1420 return sorted_headers;
1423 static void textview_show_header(TextView *textview, GPtrArray *headers)
1425 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1426 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1431 g_return_if_fail(headers != NULL);
1433 for (i = 0; i < headers->len; i++) {
1434 header = g_ptr_array_index(headers, i);
1435 g_return_if_fail(header->name != NULL);
1437 gtk_text_buffer_get_end_iter (buffer, &iter);
1438 gtk_text_buffer_insert_with_tags_by_name
1439 (buffer, &iter, header->name, -1,
1440 "header_title", "header", NULL);
1441 if (header->name[strlen(header->name) - 1] != ' ')
1442 gtk_text_buffer_insert_with_tags_by_name
1443 (buffer, &iter, " ", 1,
1444 "header_title", "header", NULL);
1446 if (procheader_headername_equal(header->name, "Subject") ||
1447 procheader_headername_equal(header->name, "From") ||
1448 procheader_headername_equal(header->name, "To") ||
1449 procheader_headername_equal(header->name, "Cc"))
1450 unfold_line(header->body);
1454 if (textview->text_is_mb == TRUE)
1455 conv_unreadable_locale(header->body);
1458 if (prefs_common.enable_color &&
1459 (procheader_headername_equal(header->name, "X-Mailer") ||
1460 procheader_headername_equal(header->name,
1462 strstr(header->body, "Sylpheed") != NULL) {
1463 gtk_text_buffer_get_end_iter (buffer, &iter);
1464 gtk_text_buffer_insert_with_tags_by_name
1465 (buffer, &iter, header->body, -1,
1466 "header", "emphasis", NULL);
1467 } else if (prefs_common.enable_color) {
1468 textview_make_clickable_parts(textview, "header", "link",
1471 textview_make_clickable_parts(textview, "header", NULL,
1474 gtk_text_buffer_get_end_iter (buffer, &iter);
1475 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1480 gboolean textview_search_string(TextView *textview, const gchar *str,
1483 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1484 GtkTextBuffer *buffer;
1485 GtkTextIter iter, end_iter;
1490 g_return_val_if_fail(str != NULL, FALSE);
1492 buffer = gtk_text_view_get_buffer(text);
1494 len = g_utf8_strlen(str, -1);
1495 g_return_val_if_fail(len >= 0, FALSE);
1497 mark = gtk_text_buffer_get_insert(buffer);
1498 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1499 pos = gtk_text_iter_get_offset(&iter);
1501 if ((pos = gtkut_text_buffer_find(buffer, pos, str, case_sens)) != -1) {
1502 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, pos);
1503 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos + len);
1504 gtk_text_buffer_select_range(buffer, &iter, &end_iter);
1505 gtk_text_view_scroll_to_mark(text, mark, 0.0, FALSE, 0.0, 0.0);
1512 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1515 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1516 GtkTextBuffer *buffer;
1517 GtkTextIter iter, end_iter;
1522 glong items_read = 0, items_written = 0;
1523 GError *error = NULL;
1524 gboolean found = FALSE;
1526 g_return_val_if_fail(str != NULL, FALSE);
1528 buffer = gtk_text_view_get_buffer(text);
1530 wcs = g_utf8_to_ucs4(str, -1, &items_read, &items_written, &error);
1531 if (error != NULL) {
1532 g_warning("An error occured while converting a string from UTF-8 to UCS-4: %s\n", error->message);
1533 g_error_free(error);
1535 if (!wcs || items_written <= 0) return FALSE;
1536 len = (gint)items_written;
1538 mark = gtk_text_buffer_get_insert(buffer);
1539 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1541 while (gtk_text_iter_backward_char(&iter)) {
1542 pos = gtk_text_iter_get_offset(&iter);
1543 if (gtkut_text_buffer_match_string
1544 (buffer, pos, wcs, len, case_sens) == TRUE) {
1545 gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1546 gtk_text_buffer_get_iter_at_offset
1547 (buffer, &end_iter, pos + len);
1548 gtk_text_buffer_select_range(buffer, &iter, &end_iter);
1549 gtk_text_view_scroll_to_mark
1550 (text, mark, 0.0, FALSE, 0.0, 0.0);
1560 void textview_scroll_one_line(TextView *textview, gboolean up)
1562 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1563 GtkAdjustment *vadj = text->vadjustment;
1566 if (prefs_common.enable_smooth_scroll) {
1567 textview_smooth_scroll_one_line(textview, up);
1572 upper = vadj->upper - vadj->page_size;
1573 if (vadj->value < upper) {
1574 vadj->value += vadj->step_increment;
1575 vadj->value = MIN(vadj->value, upper);
1576 g_signal_emit_by_name(G_OBJECT(vadj),
1577 "value_changed", 0);
1580 if (vadj->value > 0.0) {
1581 vadj->value -= vadj->step_increment;
1582 vadj->value = MAX(vadj->value, 0.0);
1583 g_signal_emit_by_name(G_OBJECT(vadj),
1584 "value_changed", 0);
1589 gboolean textview_scroll_page(TextView *textview, gboolean up)
1591 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1592 GtkAdjustment *vadj = text->vadjustment;
1596 if (prefs_common.enable_smooth_scroll)
1597 return textview_smooth_scroll_page(textview, up);
1599 if (prefs_common.scroll_halfpage)
1600 page_incr = vadj->page_increment / 2;
1602 page_incr = vadj->page_increment;
1605 upper = vadj->upper - vadj->page_size;
1606 if (vadj->value < upper) {
1607 vadj->value += page_incr;
1608 vadj->value = MIN(vadj->value, upper);
1609 g_signal_emit_by_name(G_OBJECT(vadj),
1610 "value_changed", 0);
1614 if (vadj->value > 0.0) {
1615 vadj->value -= page_incr;
1616 vadj->value = MAX(vadj->value, 0.0);
1617 g_signal_emit_by_name(G_OBJECT(vadj),
1618 "value_changed", 0);
1626 static void textview_smooth_scroll_do(TextView *textview,
1627 gfloat old_value, gfloat last_value,
1630 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1631 GtkAdjustment *vadj = text->vadjustment;
1636 if (old_value < last_value) {
1637 change_value = last_value - old_value;
1640 change_value = old_value - last_value;
1644 for (i = step; i <= change_value; i += step) {
1645 vadj->value = old_value + (up ? -i : i);
1646 g_signal_emit_by_name(G_OBJECT(vadj),
1647 "value_changed", 0);
1650 vadj->value = last_value;
1651 g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1654 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1656 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1657 GtkAdjustment *vadj = text->vadjustment;
1663 upper = vadj->upper - vadj->page_size;
1664 if (vadj->value < upper) {
1665 old_value = vadj->value;
1666 last_value = vadj->value + vadj->step_increment;
1667 last_value = MIN(last_value, upper);
1669 textview_smooth_scroll_do(textview, old_value,
1671 prefs_common.scroll_step);
1674 if (vadj->value > 0.0) {
1675 old_value = vadj->value;
1676 last_value = vadj->value - vadj->step_increment;
1677 last_value = MAX(last_value, 0.0);
1679 textview_smooth_scroll_do(textview, old_value,
1681 prefs_common.scroll_step);
1686 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1688 GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1689 GtkAdjustment *vadj = text->vadjustment;
1695 if (prefs_common.scroll_halfpage)
1696 page_incr = vadj->page_increment / 2;
1698 page_incr = vadj->page_increment;
1701 upper = vadj->upper - vadj->page_size;
1702 if (vadj->value < upper) {
1703 old_value = vadj->value;
1704 last_value = vadj->value + page_incr;
1705 last_value = MIN(last_value, upper);
1707 textview_smooth_scroll_do(textview, old_value,
1709 prefs_common.scroll_step);
1713 if (vadj->value > 0.0) {
1714 old_value = vadj->value;
1715 last_value = vadj->value - page_incr;
1716 last_value = MAX(last_value, 0.0);
1718 textview_smooth_scroll_do(textview, old_value,
1720 prefs_common.scroll_step);
1728 #define KEY_PRESS_EVENT_STOP() \
1729 g_signal_stop_emission_by_name(G_OBJECT(widget), \
1732 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1735 SummaryView *summaryview = NULL;
1736 MessageView *messageview = textview->messageview;
1738 if (!event) return FALSE;
1739 if (messageview->mainwin)
1740 summaryview = messageview->mainwin->summaryview;
1742 switch (event->keyval) {
1757 summary_pass_key_press_event(summaryview, event);
1759 textview_scroll_page
1762 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1765 textview_scroll_page(textview, TRUE);
1768 textview_scroll_one_line
1769 (textview, (event->state &
1770 (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
1774 summary_pass_key_press_event(summaryview, event);
1779 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1780 KEY_PRESS_EVENT_STOP();
1781 mimeview_pass_key_press_event(messageview->mimeview,
1785 /* possible fall through */
1788 event->window != messageview->mainwin->window->window) {
1789 GdkEventKey tmpev = *event;
1791 tmpev.window = messageview->mainwin->window->window;
1792 KEY_PRESS_EVENT_STOP();
1793 gtk_widget_event(messageview->mainwin->window,
1794 (GdkEvent *)&tmpev);
1802 static gboolean textview_motion_notify(GtkWidget *widget,
1803 GdkEventMotion *event,
1806 textview_uri_update(textview, event->x, event->y);
1807 gdk_window_get_pointer(widget->window, NULL, NULL, NULL);
1812 static gboolean textview_leave_notify(GtkWidget *widget,
1813 GdkEventCrossing *event,
1816 textview_uri_update(textview, -1, -1);
1821 static gboolean textview_visibility_notify(GtkWidget *widget,
1822 GdkEventVisibility *event,
1828 window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
1829 GTK_TEXT_WINDOW_TEXT);
1831 /* check if occurred for the text window part */
1832 if (window != event->window)
1835 gdk_window_get_pointer(widget->window, &wx, &wy, NULL);
1836 textview_uri_update(textview, wx, wy);
1841 static void textview_uri_update(TextView *textview, gint x, gint y)
1843 GtkTextBuffer *buffer;
1844 GtkTextIter start_iter, end_iter;
1845 RemoteURI *uri = NULL;
1847 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1849 if (x != -1 && y != -1) {
1855 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text),
1856 GTK_TEXT_WINDOW_WIDGET,
1858 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
1861 tags = gtk_text_iter_get_tags(&iter);
1862 for (cur = tags; cur != NULL; cur = cur->next) {
1863 GtkTextTag *tag = cur->data;
1866 g_object_get(G_OBJECT(tag), "name", &name, NULL);
1867 if (!strcmp(name, "link")
1868 && textview_get_uri_range(textview, &iter, tag,
1869 &start_iter, &end_iter))
1870 uri = textview_get_uri_from_range(textview,
1882 if (uri != textview->uri_hover) {
1885 if (textview->uri_hover)
1886 gtk_text_buffer_remove_tag_by_name(buffer,
1888 &textview->uri_hover_start_iter,
1889 &textview->uri_hover_end_iter);
1891 textview->uri_hover = uri;
1893 textview->uri_hover_start_iter = start_iter;
1894 textview->uri_hover_end_iter = end_iter;
1897 window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
1898 GTK_TEXT_WINDOW_TEXT);
1899 gdk_window_set_cursor(window, uri ? hand_cursor : text_cursor);
1901 TEXTVIEW_STATUSBAR_POP(textview);
1906 gtk_text_buffer_apply_tag_by_name(buffer,
1911 trimmed_uri = trim_string(uri->uri, 60);
1912 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1913 g_free(trimmed_uri);
1918 static gboolean textview_get_uri_range(TextView *textview,
1921 GtkTextIter *start_iter,
1922 GtkTextIter *end_iter)
1924 GtkTextIter _start_iter, _end_iter;
1927 if (!gtk_text_iter_forward_to_tag_toggle(&_end_iter, tag)) {
1928 debug_print("Can't find end");
1932 _start_iter = _end_iter;
1933 if (!gtk_text_iter_backward_to_tag_toggle(&_start_iter, tag)) {
1934 debug_print("Can't find start.");
1938 *start_iter = _start_iter;
1939 *end_iter = _end_iter;
1944 static RemoteURI *textview_get_uri_from_range(TextView *textview,
1947 GtkTextIter *start_iter,
1948 GtkTextIter *end_iter)
1950 gint start_pos, end_pos, cur_pos;
1951 RemoteURI *uri = NULL;
1954 start_pos = gtk_text_iter_get_offset(start_iter);
1955 end_pos = gtk_text_iter_get_offset(end_iter);
1956 cur_pos = gtk_text_iter_get_offset(iter);
1958 for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1959 RemoteURI *uri_ = (RemoteURI *)cur->data;
1960 if (start_pos == uri_->start &&
1961 end_pos == uri_->end) {
1964 } else if (start_pos == uri_->start ||
1965 end_pos == uri_->end) {
1966 /* in case of contiguous links, textview_get_uri_range
1967 * returns a broader range (start of 1st link to end
1969 * In that case, correct link is the one covering
1972 if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
1982 static RemoteURI *textview_get_uri(TextView *textview,
1986 GtkTextIter start_iter, end_iter;
1987 RemoteURI *uri = NULL;
1989 if (textview_get_uri_range(textview, iter, tag, &start_iter,
1991 uri = textview_get_uri_from_range(textview, iter, tag,
1992 &start_iter, &end_iter);
1997 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
1998 GdkEvent *event, GtkTextIter *iter,
2001 GdkEventButton *bevent;
2002 RemoteURI *uri = NULL;
2009 if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2010 && event->type != GDK_MOTION_NOTIFY)
2013 uri = textview_get_uri(textview, iter, tag);
2017 bevent = (GdkEventButton *) event;
2019 /* doubleclick: open compose / add address / browser */
2020 if ((event->type == GDK_BUTTON_PRESS && bevent->button == 1) ||
2021 bevent->button == 2 || bevent->button == 3) {
2022 if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2023 if (bevent->button == 3) {
2025 G_OBJECT(textview->mail_popup_menu),
2026 "menu_button", uri);
2027 gtk_menu_popup(GTK_MENU(textview->mail_popup_menu),
2028 NULL, NULL, NULL, NULL,
2029 bevent->button, bevent->time);
2031 PrefsAccount *account = NULL;
2033 if (textview->messageview && textview->messageview->msginfo &&
2034 textview->messageview->msginfo->folder) {
2035 FolderItem *folder_item;
2037 folder_item = textview->messageview->msginfo->folder;
2038 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2039 account = account_find_from_id(folder_item->prefs->default_account);
2041 compose_new(account, uri->uri + 7, NULL);
2045 if (bevent->button == 1 &&
2046 textview_uri_security_check(textview, uri) == TRUE)
2048 prefs_common.uri_cmd);
2049 else if (bevent->button == 3) {
2051 G_OBJECT(textview->link_popup_menu),
2052 "menu_button", uri);
2053 gtk_menu_popup(GTK_MENU(textview->link_popup_menu),
2054 NULL, NULL, NULL, NULL,
2055 bevent->button, bevent->time);
2065 *\brief Check to see if a web URL has been disguised as a different
2066 * URL (possible with HTML email).
2068 *\param uri The uri to check
2070 *\param textview The TextView the URL is contained in
2072 *\return gboolean TRUE if the URL is ok, or if the user chose to open
2073 * it anyway, otherwise FALSE
2075 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
2078 gboolean retval = TRUE;
2079 GtkTextBuffer *buffer;
2080 GtkTextIter start, end;
2082 if (is_uri_string(uri->uri) == FALSE)
2085 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2087 gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2088 gtk_text_buffer_get_iter_at_offset(buffer, &end, uri->end);
2090 visible_str = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2092 if (visible_str == NULL)
2095 if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2097 gchar *visible_uri_path;
2099 uri_path = get_uri_path(uri->uri);
2100 visible_uri_path = get_uri_path(visible_str);
2101 if (strcmp(uri_path, visible_uri_path) != 0)
2105 if (retval == FALSE) {
2109 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
2110 "the apparent URL (%s).\n"
2112 uri->uri, visible_str);
2113 aval = alertpanel_with_type(_("Warning"), msg,
2114 GTK_STOCK_YES, GTK_STOCK_NO,
2115 NULL, NULL, ALERT_WARNING);
2117 if (aval == G_ALERTDEFAULT)
2121 g_free(visible_str);
2126 static void textview_uri_list_remove_all(GSList *uri_list)
2130 for (cur = uri_list; cur != NULL; cur = cur->next) {
2132 g_free(((RemoteURI *)cur->data)->uri);
2137 g_slist_free(uri_list);
2140 static void open_uri_cb (TextView *textview, guint action, void *data)
2142 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2147 if (textview_uri_security_check(textview, uri) == TRUE)
2149 prefs_common.uri_cmd);
2150 g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2154 static void copy_uri_cb (TextView *textview, guint action, void *data)
2156 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2161 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), uri->uri, -1);
2162 g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2166 static void add_uri_to_addrbook_cb (TextView *textview, guint action, void *data)
2168 gchar *fromname, *fromaddress;
2169 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2175 fromaddress = g_strdup(uri->uri + 7);
2176 /* Hiroyuki: please put this function in utils.c! */
2177 fromname = procheader_get_fromname(fromaddress);
2178 extract_address(fromaddress);
2179 g_message("adding from textview %s <%s>", fromname, fromaddress);
2180 /* Add to address book - Match */
2181 addressbook_add_contact( fromname, fromaddress, NULL );
2183 g_free(fromaddress);
2187 static void mail_to_uri_cb (TextView *textview, guint action, void *data)
2189 PrefsAccount *account = NULL;
2190 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2195 if (textview->messageview && textview->messageview->msginfo &&
2196 textview->messageview->msginfo->folder) {
2197 FolderItem *folder_item;
2199 folder_item = textview->messageview->msginfo->folder;
2200 if (folder_item->prefs && folder_item->prefs->enable_default_account)
2201 account = account_find_from_id(folder_item->prefs->default_account);
2203 compose_new(account, uri->uri + 7, NULL);
2206 static void copy_mail_to_uri_cb (TextView *textview, guint action, void *data)
2208 RemoteURI *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2213 gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), uri->uri + 7, -1);
2214 g_object_set_data(G_OBJECT(textview->mail_popup_menu), "menu_button",